<commit_before>import numpy as np

import param

from ..core import Dimension, ElementOperation, CompositeOverlay, NdOverlay, Overlay
from ..core.util import find_minmax
from ..element.chart import Histogram, VectorField, Curve
from ..element.annotation import Contours
from ..element.raster import Matrix
from ..element.tabular import ItemTable

class chain(ElementOperation):
    """
    Defining an ElementOperation chain is an easy way to define a new
    ElementOperation from a series of existing ones. The single
    argument is a callable that accepts an input element and returns
    the final, transformed element. To create the custom
    ElementOperation, you will need to supply this argument to a new
    instance of chain. For example:

    chain.instance(
       chain=lambda x: colormap(operator(x, operator=np.add), cmap='jet'))

    This defines an ElementOperation that sums the data in the input
    and turns it into an RGB Matrix using the 'jet' colormap.
    """

    value = param.String(default='Chain', doc="""
        The value assigned to the result after having applied the chain.""")

    chain = param.Callable(doc="""A chain of existing ViewOperations.""")

    def _process(self, view, key=None):
        return self.p.chain(view).clone(value=self.p.value)

#==============================#
# Raster processing operations #
#==============================#

class operator(ElementOperation):
    """
    Applies any arbitrary collapsing operator across the data elements
    of the input overlay and returns the result.

    As applying collapse operations on arbitrary data works very
    naturally using arrays, the result is a Matrix containing the
    computed result data.
    """

    output_type = Matrix

    operator = param.Callable(np.add, doc="""
        The commutative operator to apply between the data attributes
        of the supplied Views used to collapse the data.

        By default applies elementwise addition across the input data.""")

    unpack = param.Boolean(default=True, doc="""
       Whether the operator is supplied the .data attributes as an
       unpack collection of arguments or as a list.""")

    value = param.String(default='Operation', doc="""
        The value assigned to the result after having applied the operator.""")

    def _process(self, overlay, key=None):
        if not isinstance(overlay, CompositeOverlay):
            raise Exception("Operation requires an Overlay type as input")

        if self.p.unpack:
            new_data = self.p.operator(*[el.data for el in overlay])
        else:
            new_data = self.p.operator([el.data for el in overlay])

        return Matrix(new_data, bounds=overlay[0].bounds,
                      label=self.get_overlay_label(overlay))

class threshold(ElementOperation):
    """
    Threshold a given Matrix whereby all values higher than a given
    level map to the specified high value and all values lower than
    that level map to the specified low value.
    """

    output_type = Matrix

    level = param.Number(default=0.5, doc="""
       The value at which the threshold is applied. Values lower than
       the threshold map to the 'low' value and values above map to
       the 'high' value.""")

    high = param.Number(default=1.0, doc="""
      The value given to elements greater than (or equal to) the
      threshold.""")

    low = param.Number(default=0.0, doc="""
      The value given to elements below the threshold.""")

    value = param.String(default='Threshold', doc="""
       The value assigned to the thresholded output.""")

    def _process(self, matrix, key=None):

        if not isinstance(matrix, Matrix):
            raise TypeError("The threshold operation requires a Matrix as input.")

        arr = matrix.data
        high = np.ones(arr.shape) * self.p.high
        low = np.ones(arr.shape) * self.p.low
        thresholded = np.where(arr > self.p.level, high, low)

        return matrix.clone(thresholded, value=self.p.value)

class convolve(ElementOperation):
    """
    Apply a convolution to an overlay using the top layer as the
    kernel for convolving the bottom layer. Both Matrix elements in
    the input overlay should have a single value dimension.
    """

    output_type = Matrix

    value = param.String(default='Convolution', doc="""
        The value assigned to the convolved output.""")

    kernel_roi = param.NumericTuple(default=(0,0,0,0), length=4, doc="""
        A 2-dimensional slice of the kernel layer to use in the
        convolution in lbrt (left, bottom, right, top) format. By
        default, no slicing is applied.""")

    def _process(self, overlay, key=None):
        if len(overlay) != 2:
            raise Exception("Overlay must contain at least to items.")

        [target, kernel] = overlay[0], overlay[1]

        if len(target.value_dimensions) != 1:
            raise Exception("Convolution requires inputs with single value dimensions.")

        xslice = slice(self.p.kernel_roi[0], self.p.kernel_roi[2])
        yslice = slice(self.p.kernel_roi[1], self.p.kernel_roi[3])

        k = kernel.data if self.p.kernel_roi == (0,0,0,0) else kernel[xslice, yslice].data

        fft1 = np.fft.fft2(target.data)
        fft2 = np.fft.fft2(k, s= target.data.shape)
        convolved_raw = np.fft.ifft2(fft1 * fft2).real

        k_rows, k_cols = k.shape
        rolled = np.roll(np.roll(convolved_raw, -(k_cols//2), axis=-1), -(k_rows//2), axis=-2)
        convolved = rolled / float(k.sum())

        return Matrix(convolved, bounds=target.bounds, value=self.p.value)

class split_raster(ElementOperation):
    """
    Given a Raster element, return the individual value dimensions as
    an overlay of Matrix elements.
    """

    value = param.String(default='', doc="""
       Optional suffix appended to the value dimensions in the
       components of the output overlay. Default keeps the value
       strings identical to those in the input raster.""")

    def _process(self, raster, key=None):
        matrices = []
        for i, dim in enumerate(raster.value_dimensions):
            matrix = Matrix(raster.data[:, :, i],
                            value_dimensions = [dim(name=dim.name+self.p.value)])
            matrices.append(matrix)
        return np.product(matrices)

class contours(ElementOperation):
    """
    Given a Matrix with a single channel, annotate it with contour
    lines for a given set of contour levels.

    The return is an NdOverlay with a Contours layer for each given
    level, overlaid on top of the input Matrix.
    """

    output_type = Overlay

    levels = param.NumericTuple(default=(0.5,), doc="""
        A list of scalar values used to specify the contour levels.""")

    value = param.String(default='Level', doc="""
        The value assigned to the output contours.""")

    def _process(self, matrix, key=None):
        from matplotlib import pyplot as plt

        figure_handle = plt.figure()
        (l, b, r, t) = matrix.extents
        contour_set = plt.contour(matrix.data, extent=(l, r, t, b),
                                  levels=self.p.levels)

        contours = NdOverlay(None, key_dimensions=['Levels'])
        for level, cset in zip(self.p.levels, contour_set.collections):
            paths = cset.get_paths()
            lines = [path.vertices for path in paths]
            contours[level] = Contours(lines, value=self.p.value)

        plt.close(figure_handle)
        return matrix * contours

class histogram(ElementOperation):
    """
    Returns a Histogram of the Raster data, binned into
    num_bins over the bin_range (if specified).

    If adjoin is True, the histogram will be returned adjoined to
    the Raster as a side-plot.
    """

    adjoin = param.Boolean(default=True, doc="""
      Whether to adjoin the histogram to the ViewableElement.""")

    bin_range = param.NumericTuple(default=(0, 0), doc="""
      Specifies the range within which to compute the bins.""")

    dimension = param.String(default=None, doc="""
      Along which dimension of the ViewableElement to compute the histogram.""")

    normed = param.Boolean(default=True, doc="""
      Whether the histogram frequencies are normalized.""")

    individually = param.Boolean(default=True, doc="""
      Specifies whether the histogram will be rescaled for each Raster in a UniformNdMapping.""")

    num_bins = param.Integer(default=20, doc="""
      Number of bins in the histogram .""")

    style_prefix = param.String(default=None, allow_None=None, doc="""
      Used for setting a common style for histograms in a HoloMap or AdjointLayout.""")

    def _process(self, view, key=None):
        if self.p.dimension:
            selected_dim = self.p.dimension
        else:
            selected_dim = [d.name for d in view.value_dimensions][0]
        data = np.array(view.dimension_values(selected_dim))
        range = find_minmax((np.min(data), np.max(data)), (0, -float('inf')))\
            if self.p.bin_range is None else self.p.bin_range

        # Avoids range issues including zero bin range and empty bins
        if range == (0, 0):
            range = (0.0, 0.1)
        try:
            data = data[np.invert(np.isnan(data))]
            hist, edges = np.histogram(data, normed=self.p.normed,
                                       range=range, bins=self.p.num_bins)
        except:
            edges = np.linspace(range[0], range[1], self.p.num_bins + 1)
            hist = np.zeros(self.p.num_bins)
        hist[np.isnan(hist)] = 0

        hist_view = Histogram(hist, edges, key_dimensions=[view.get_dimension(selected_dim)],
                              label=view.label)

        return (view << hist_view) if self.p.adjoin else hist_view

class vectorfield(ElementOperation):
    """
    Given a Matrix with a single channel, convert it to a VectorField
    object at a given spatial sampling interval. The values in the
    Matrix are assumed to correspond to the vector angle in radians
    and the value is assumed to be cyclic.

    If supplied with an Overlay, the second sheetview in the overlay
    will be interpreted as the third vector dimension.
    """

    output_type = VectorField

    rows = param.Integer(default=10, doc="""
       The number of rows in the vector field.""")

    cols = param.Integer(default=10, doc="""
       The number of columns in the vector field.""")

    value = param.String(default='Vectors', doc="""
       The value assigned to the output vector field.""")

    def _process(self, view, key=None):

        if isinstance(view, CompositeOverlay) and len(view) >= 2:
            radians, lengths = view[0], view[1]
        else:
            radians, lengths = view, None

        cyclic_dim = radians.value_dimensions[0]
        if not cyclic_dim.cyclic:
            raise Exception("First input Matrix must be declared cyclic")

        l, b, r, t = radians.bounds.lbrt()
        X, Y = np.meshgrid(np.linspace(l, r, self.p.cols+2)[1:-1],
                           np.linspace(b, t, self.p.rows+2)[1:-1])

        vector_data = []
        for x, y in zip(X.flat, Y.flat):
            components = (x,y, radians[x,y])
            if lengths is not None:
                components += (lengths[x,y],)

            vector_data.append(components)

        value_dimensions = [Dimension('Magnitude'),
                            Dimension('Angle', cyclic=True, range=cyclic_dim.range)]
        return VectorField(vector_data, label=radians.label, value=self.p.value,
                           value_dimensions=value_dimensions)

class analyze_roi(ElementOperation):
    """
    Compute a table of information from a Matrix within the indicated
    region-of-interest (ROI). The function applied must accept a numpy
    array and return either a single value or a dictionary of values
    which is returned as a ItemTable or HoloMap.

    The roi is specified using a boolean Matrix overlay (e.g as
    generated by threshold) where all zero values are excluded from
    the analysis.
    """

    output_type = ItemTable

    fn = param.Callable(default=np.mean, doc="""
        The function that is applied within the mask area to supply
        the relevant information.

        The function may return a single value (e.g np.sum, np.median,
        np.std) or a dictionary of values.""")

    heading = param.String(default='result', doc="""
       If the output of the function is not a dictionary, this is the
       row label given to the resulting value.""")

    value = param.String(default='ROI', doc="""
       The value assigned to the output analysis table.""")

    def _process(self, overlay, key=None):

        if not isinstance(overlay, CompositeOverlay) or len(overlay) != 2:
            raise Exception("A CompositeOverlay object of two Matrix elements is required.")

        matrix, mask = overlay[0], overlay[1]

        if matrix.data.shape != mask.data.shape:
            raise Exception("The data array shape of the mask layer"
                            " must match that of the data layer.")

        bit_mask = mask.data.astype(np.bool)
        roi = matrix.data[bit_mask]

        if len(roi) == 0:
            raise Exception("No region of interest defined in ROI mask" )

        results = self.p.fn(roi)
        if not isinstance(results, dict):
            results = {self.p.heading:results}

        return ItemTable(results,
                         label=self.get_overlay_label(overlay),
                         value=self.p.value)

#==================#
# Other operations #
#==================#

class collapse_curve(ElementOperation):
    """
    Given an overlay of Curves, compute a new curve which is collapsed
    for each x-value given a specified function.

    This is an example of an ElementOperation that does not involve
    any Raster types.
    """

    output_type = Curve

    fn = param.Callable(default=np.mean, doc="""
        The function that is used to collapse the curve y-values for
        each x-value.""")

    value = param.String(default='Collapses', doc="""
       The value assigned to the collapsed curve output.""")

    def _process(self, overlay, key=None):

        for curve in overlay:
            if not isinstance(curve, Curve):
                raise ValueError("The collapse_curve operation requires Curves as input.")
            if not all(curve.data[:,0] == overlay[0].data[:,0]):
                raise ValueError("All input curves must have same x-axis values.")

        data = []
        for i, xval in enumerate(overlay[0].data[:,0]):
            yval = self.p.fn([c.data[i,1]  for c in overlay])
            data.append((xval, yval))

        return Curve(np.array(data), value=self.p.value,
                     label=self.get_overlay_label(overlay))

<commit_msg>Introduced fft_power ElementOperation taken from Imagen<commit_after>import numpy as np

import param

from ..core import Dimension, ElementOperation, CompositeOverlay, \
                   NdOverlay, Overlay, BoundingBox
from ..core.util import find_minmax
from ..element.chart import Histogram, VectorField, Curve
from ..element.annotation import Contours
from ..element.raster import Matrix
from ..element.tabular import ItemTable
from .normalization import raster_normalization

class chain(ElementOperation):
    """
    Defining an ElementOperation chain is an easy way to define a new
    ElementOperation from a series of existing ones. The single
    argument is a callable that accepts an input element and returns
    the final, transformed element. To create the custom
    ElementOperation, you will need to supply this argument to a new
    instance of chain. For example:

    chain.instance(
       chain=lambda x: colormap(operator(x, operator=np.add), cmap='jet'))

    This defines an ElementOperation that sums the data in the input
    and turns it into an RGB Matrix using the 'jet' colormap.
    """

    value = param.String(default='Chain', doc="""
        The value assigned to the result after having applied the chain.""")

    chain = param.Callable(doc="""A chain of existing ViewOperations.""")

    def _process(self, view, key=None):
        return self.p.chain(view).clone(value=self.p.value)

#==============================#
# Raster processing operations #
#==============================#

class operator(ElementOperation):
    """
    Applies any arbitrary collapsing operator across the data elements
    of the input overlay and returns the result.

    As applying collapse operations on arbitrary data works very
    naturally using arrays, the result is a Matrix containing the
    computed result data.
    """

    output_type = Matrix

    operator = param.Callable(np.add, doc="""
        The commutative operator to apply between the data attributes
        of the supplied Views used to collapse the data.

        By default applies elementwise addition across the input data.""")

    unpack = param.Boolean(default=True, doc="""
       Whether the operator is supplied the .data attributes as an
       unpack collection of arguments or as a list.""")

    value = param.String(default='Operation', doc="""
        The value assigned to the result after having applied the operator.""")

    def _process(self, overlay, key=None):
        if not isinstance(overlay, CompositeOverlay):
            raise Exception("Operation requires an Overlay type as input")

        if self.p.unpack:
            new_data = self.p.operator(*[el.data for el in overlay])
        else:
            new_data = self.p.operator([el.data for el in overlay])

        return Matrix(new_data, bounds=overlay[0].bounds,
                      label=self.get_overlay_label(overlay))

class threshold(ElementOperation):
    """
    Threshold a given Matrix whereby all values higher than a given
    level map to the specified high value and all values lower than
    that level map to the specified low value.
    """

    output_type = Matrix

    level = param.Number(default=0.5, doc="""
       The value at which the threshold is applied. Values lower than
       the threshold map to the 'low' value and values above map to
       the 'high' value.""")

    high = param.Number(default=1.0, doc="""
      The value given to elements greater than (or equal to) the
      threshold.""")

    low = param.Number(default=0.0, doc="""
      The value given to elements below the threshold.""")

    value = param.String(default='Threshold', doc="""
       The value assigned to the thresholded output.""")

    def _process(self, matrix, key=None):

        if not isinstance(matrix, Matrix):
            raise TypeError("The threshold operation requires a Matrix as input.")

        arr = matrix.data
        high = np.ones(arr.shape) * self.p.high
        low = np.ones(arr.shape) * self.p.low
        thresholded = np.where(arr > self.p.level, high, low)

        return matrix.clone(thresholded, value=self.p.value)

class fft_power(ElementOperation):
    """
    Given a Matrix element, compute the power of the 2D Fast Fourier
    Transform (FFT).
    """

    output_type = Curve

    max_power = param.Number(default=1.0, doc="""
    The maximum power value of the output power spectrum.""")

    value = param.String(default='FFT Power', doc="""
    The value assigned to the output power spectrum.""")

    def _process(self, matrix, key=None):

        if self.p.input_ranges:
            normfn = raster_normalization.instance()
            matrix = normfn.process_element(matrix, key, *self.p.input_ranges)

        fft_spectrum = abs(np.fft.fftshift(np.fft.fft2(matrix.data - 0.5, s=None, axes=(-2, -1))))
        fft_spectrum = 1 - fft_spectrum # Inverted spectrum by convention
        zero_min_spectrum = fft_spectrum - fft_spectrum.min()
        spectrum_range = fft_spectrum.max() - fft_spectrum.min()
        spectrum = (self.p.max_power * zero_min_spectrum) / spectrum_range

        l, b, r, t = matrix.bounds.lbrt()
        density = matrix.xdensity
        bounds = BoundingBox(radius=(density/2)/(r-l))

        return Matrix(spectrum, bounds, label=matrix.label, value=self.p.value)

class convolve(ElementOperation):
    """
    Apply a convolution to an overlay using the top layer as the
    kernel for convolving the bottom layer. Both Matrix elements in
    the input overlay should have a single value dimension.
    """

    output_type = Matrix

    value = param.String(default='Convolution', doc="""
        The value assigned to the convolved output.""")

    kernel_roi = param.NumericTuple(default=(0,0,0,0), length=4, doc="""
        A 2-dimensional slice of the kernel layer to use in the
        convolution in lbrt (left, bottom, right, top) format. By
        default, no slicing is applied.""")

    def _process(self, overlay, key=None):
        if len(overlay) != 2:
            raise Exception("Overlay must contain at least to items.")

        [target, kernel] = overlay[0], overlay[1]

        if len(target.value_dimensions) != 1:
            raise Exception("Convolution requires inputs with single value dimensions.")

        xslice = slice(self.p.kernel_roi[0], self.p.kernel_roi[2])
        yslice = slice(self.p.kernel_roi[1], self.p.kernel_roi[3])

        k = kernel.data if self.p.kernel_roi == (0,0,0,0) else kernel[xslice, yslice].data

        fft1 = np.fft.fft2(target.data)
        fft2 = np.fft.fft2(k, s= target.data.shape)
        convolved_raw = np.fft.ifft2(fft1 * fft2).real

        k_rows, k_cols = k.shape
        rolled = np.roll(np.roll(convolved_raw, -(k_cols//2), axis=-1), -(k_rows//2), axis=-2)
        convolved = rolled / float(k.sum())

        return Matrix(convolved, bounds=target.bounds, value=self.p.value)

class split_raster(ElementOperation):
    """
    Given a Raster element, return the individual value dimensions as
    an overlay of Matrix elements.
    """

    value = param.String(default='', doc="""
       Optional suffix appended to the value dimensions in the
       components of the output overlay. Default keeps the value
       strings identical to those in the input raster.""")

    def _process(self, raster, key=None):
        matrices = []
        for i, dim in enumerate(raster.value_dimensions):
            matrix = Matrix(raster.data[:, :, i],
                            value_dimensions = [dim(name=dim.name+self.p.value)])
            matrices.append(matrix)
        return np.product(matrices)

class contours(ElementOperation):
    """
    Given a Matrix with a single channel, annotate it with contour
    lines for a given set of contour levels.

    The return is an NdOverlay with a Contours layer for each given
    level, overlaid on top of the input Matrix.
    """

    output_type = Overlay

    levels = param.NumericTuple(default=(0.5,), doc="""
        A list of scalar values used to specify the contour levels.""")

    value = param.String(default='Level', doc="""
        The value assigned to the output contours.""")

    def _process(self, matrix, key=None):
        from matplotlib import pyplot as plt

        figure_handle = plt.figure()
        (l, b, r, t) = matrix.extents
        contour_set = plt.contour(matrix.data, extent=(l, r, t, b),
                                  levels=self.p.levels)

        contours = NdOverlay(None, key_dimensions=['Levels'])
        for level, cset in zip(self.p.levels, contour_set.collections):
            paths = cset.get_paths()
            lines = [path.vertices for path in paths]
            contours[level] = Contours(lines, value=self.p.value)

        plt.close(figure_handle)
        return matrix * contours

class histogram(ElementOperation):
    """
    Returns a Histogram of the Raster data, binned into
    num_bins over the bin_range (if specified).

    If adjoin is True, the histogram will be returned adjoined to
    the Raster as a side-plot.
    """

    adjoin = param.Boolean(default=True, doc="""
      Whether to adjoin the histogram to the ViewableElement.""")

    bin_range = param.NumericTuple(default=(0, 0), doc="""
      Specifies the range within which to compute the bins.""")

    dimension = param.String(default=None, doc="""
      Along which dimension of the ViewableElement to compute the histogram.""")

    normed = param.Boolean(default=True, doc="""
      Whether the histogram frequencies are normalized.""")

    individually = param.Boolean(default=True, doc="""
      Specifies whether the histogram will be rescaled for each Raster in a UniformNdMapping.""")

    num_bins = param.Integer(default=20, doc="""
      Number of bins in the histogram .""")

    style_prefix = param.String(default=None, allow_None=None, doc="""
      Used for setting a common style for histograms in a HoloMap or AdjointLayout.""")

    def _process(self, view, key=None):
        if self.p.dimension:
            selected_dim = self.p.dimension
        else:
            selected_dim = [d.name for d in view.value_dimensions][0]
        data = np.array(view.dimension_values(selected_dim))
        range = find_minmax((np.min(data), np.max(data)), (0, -float('inf')))\
            if self.p.bin_range is None else self.p.bin_range

        # Avoids range issues including zero bin range and empty bins
        if range == (0, 0):
            range = (0.0, 0.1)
        try:
            data = data[np.invert(np.isnan(data))]
            hist, edges = np.histogram(data, normed=self.p.normed,
                                       range=range, bins=self.p.num_bins)
        except:
            edges = np.linspace(range[0], range[1], self.p.num_bins + 1)
            hist = np.zeros(self.p.num_bins)
        hist[np.isnan(hist)] = 0

        hist_view = Histogram(hist, edges, key_dimensions=[view.get_dimension(selected_dim)],
                              label=view.label)

        return (view << hist_view) if self.p.adjoin else hist_view

class vectorfield(ElementOperation):
    """
    Given a Matrix with a single channel, convert it to a VectorField
    object at a given spatial sampling interval. The values in the
    Matrix are assumed to correspond to the vector angle in radians
    and the value is assumed to be cyclic.

    If supplied with an Overlay, the second sheetview in the overlay
    will be interpreted as the third vector dimension.
    """

    output_type = VectorField

    rows = param.Integer(default=10, doc="""
       The number of rows in the vector field.""")

    cols = param.Integer(default=10, doc="""
       The number of columns in the vector field.""")

    value = param.String(default='Vectors', doc="""
       The value assigned to the output vector field.""")

    def _process(self, view, key=None):

        if isinstance(view, CompositeOverlay) and len(view) >= 2:
            radians, lengths = view[0], view[1]
        else:
            radians, lengths = view, None

        cyclic_dim = radians.value_dimensions[0]
        if not cyclic_dim.cyclic:
            raise Exception("First input Matrix must be declared cyclic")

        l, b, r, t = radians.bounds.lbrt()
        X, Y = np.meshgrid(np.linspace(l, r, self.p.cols+2)[1:-1],
                           np.linspace(b, t, self.p.rows+2)[1:-1])

        vector_data = []
        for x, y in zip(X.flat, Y.flat):
            components = (x,y, radians[x,y])
            if lengths is not None:
                components += (lengths[x,y],)

            vector_data.append(components)

        value_dimensions = [Dimension('Magnitude'),
                            Dimension('Angle', cyclic=True, range=cyclic_dim.range)]
        return VectorField(vector_data, label=radians.label, value=self.p.value,
                           value_dimensions=value_dimensions)

class analyze_roi(ElementOperation):
    """
    Compute a table of information from a Matrix within the indicated
    region-of-interest (ROI). The function applied must accept a numpy
    array and return either a single value or a dictionary of values
    which is returned as a ItemTable or HoloMap.

    The roi is specified using a boolean Matrix overlay (e.g as
    generated by threshold) where all zero values are excluded from
    the analysis.
    """

    output_type = ItemTable

    fn = param.Callable(default=np.mean, doc="""
        The function that is applied within the mask area to supply
        the relevant information.

        The function may return a single value (e.g np.sum, np.median,
        np.std) or a dictionary of values.""")

    heading = param.String(default='result', doc="""
       If the output of the function is not a dictionary, this is the
       row label given to the resulting value.""")

    value = param.String(default='ROI', doc="""
       The value assigned to the output analysis table.""")

    def _process(self, overlay, key=None):

        if not isinstance(overlay, CompositeOverlay) or len(overlay) != 2:
            raise Exception("A CompositeOverlay object of two Matrix elements is required.")

        matrix, mask = overlay[0], overlay[1]

        if matrix.data.shape != mask.data.shape:
            raise Exception("The data array shape of the mask layer"
                            " must match that of the data layer.")

        bit_mask = mask.data.astype(np.bool)
        roi = matrix.data[bit_mask]

        if len(roi) == 0:
            raise Exception("No region of interest defined in ROI mask" )

        results = self.p.fn(roi)
        if not isinstance(results, dict):
            results = {self.p.heading:results}

        return ItemTable(results,
                         label=self.get_overlay_label(overlay),
                         value=self.p.value)

#==================#
# Other operations #
#==================#

class collapse_curve(ElementOperation):
    """
    Given an overlay of Curves, compute a new curve which is collapsed
    for each x-value given a specified function.

    This is an example of an ElementOperation that does not involve
    any Raster types.
    """

    output_type = Curve

    fn = param.Callable(default=np.mean, doc="""
        The function that is used to collapse the curve y-values for
        each x-value.""")

    value = param.String(default='Collapses', doc="""
       The value assigned to the collapsed curve output.""")

    def _process(self, overlay, key=None):

        for curve in overlay:
            if not isinstance(curve, Curve):
                raise ValueError("The collapse_curve operation requires Curves as input.")
            if not all(curve.data[:,0] == overlay[0].data[:,0]):
                raise ValueError("All input curves must have same x-axis values.")

        data = []
        for i, xval in enumerate(overlay[0].data[:,0]):
            yval = self.p.fn([c.data[i,1]  for c in overlay])
            data.append((xval, yval))

        return Curve(np.array(data), value=self.p.value,
                     label=self.get_overlay_label(overlay))

<|endoftext|>


<commit_before># ------------------------------------------------------------------------
# coding=utf-8
# ------------------------------------------------------------------------

import re
import copy

from django import forms, template
from django.contrib import admin
from django.core.exceptions import ImproperlyConfigured
from django.db.models import loading
from django.forms.models import modelform_factory
from django.http import Http404
from django.shortcuts import render_to_response
from django.utils.encoding import force_unicode
from django.utils.functional import curry
from django.utils.translation import ugettext as _
from django.contrib.admin.options import InlineModelAdmin

from feincms import settings, ensure_completely_loaded
from feincms.signals import itemeditor_post_save_related

# ------------------------------------------------------------------------
FRONTEND_EDITING_MATCHER = re.compile(r'(\d+)\|(\w+)\|(\d+)')
FEINCMS_CONTENT_FIELDSET_NAME = 'FEINCMS_CONTENT'
FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {'fields': ()})

# ------------------------------------------------------------------------
class ItemEditorForm(forms.ModelForm):
    """
    The item editor form contains hidden region and ordering fields and should
    be used for all content type inlines.
    """

    region = forms.CharField(widget=forms.HiddenInput())
    ordering = forms.IntegerField(widget=forms.HiddenInput())

# ------------------------------------------------------------------------
class FeinCMSInline(InlineModelAdmin):
    """
    Custom ``InlineModelAdmin`` subclass used for content types.
    """

    form = ItemEditorForm
    extra = 0
    fk_name = 'parent'
    template = 'admin/feincms/content_inline.html'

# ------------------------------------------------------------------------
class ItemEditor(admin.ModelAdmin):
    """
    The ``ItemEditor`` is a drop-in replacement for ``ModelAdmin`` with the
    speciality of knowing how to work with :class:`feincms.models.Base`
    subclasses and associated content types.

    It does not have any public API except from everything inherited from'
    the standard ``ModelAdmin`` class.
    """

    def __init__(self, model, admin_site):
        ensure_completely_loaded()

        super(ItemEditor, self).__init__(model, admin_site)

    def get_inline_instances(self, request):
        inline_instances = super(ItemEditor, self).get_inline_instances(request)

        for inline_class in self.get_feincms_inlines(self.model):
            inline_instance = inline_class(self.model, self.admin_site)
            inline_instances.append(inline_instance)

        return inline_instances

    def get_feincms_inlines(self, model):
        """ Generate genuine django inlines for registered content types. """
        inlines = []
        for content_type in model._feincms_content_types:
            attrs = {
                '__module__': model.__module__,
                'model': content_type,
                }

            if hasattr(content_type, 'feincms_item_editor_inline'):
                inline = content_type.feincms_item_editor_inline
                attrs['form'] = inline.form

                if hasattr(content_type, 'feincms_item_editor_form'):
                    import warnings
                    warnings.warn(
                        'feincms_item_editor_form on %s is ignored because feincms_item_editor_inline is set too' % content_type,
                        RuntimeWarning)

            else:
                inline = FeinCMSInline
                attrs['form'] = getattr(content_type,
                    'feincms_item_editor_form', inline.form)

            name = '%sFeinCMSInline' % content_type.__name__
            inlines.append(type(name, (inline,), attrs))
        return inlines

    def _frontend_editing_view(self, request, cms_id, content_type, content_id):
        """
        This view is used strictly for frontend editing -- it is not used inside the
        standard administration interface.

        The code in feincms/templates/admin/feincms/fe_tools.html knows how to call
        this view correctly.
        """

        try:
            model_cls = loading.get_model(self.model._meta.app_label, content_type)
            obj = model_cls.objects.get(parent=cms_id, id=content_id)
        except:
            raise Http404

        form_class_base = getattr(model_cls, 'feincms_item_editor_form', ItemEditorForm)

        ModelForm = modelform_factory(model_cls,
            exclude=('parent', 'region', 'ordering'),
            form=form_class_base,
            formfield_callback=curry(self.formfield_for_dbfield, request=request))

        # we do not want to edit these two fields in the frontend editing mode; we are
        # strictly editing single content blocks there.
        # We have to remove them from the form because we explicitly redefined them in
        # the ItemEditorForm definition above. Just using exclude is not enough.
        del ModelForm.base_fields['region']
        del ModelForm.base_fields['ordering']

        if request.method == 'POST':
            # The prefix is used to replace the formset identifier from the ItemEditor
            # interface. Customization of the form is easily possible through either matching
            # the prefix (frontend editing) or the formset identifier (ItemEditor) as it is
            # done in the richtext and mediafile init.html item editor includes.
            form = ModelForm(request.POST, instance=obj, prefix=content_type)

            if form.is_valid():
                obj = form.save()

                return render_to_response('admin/feincms/fe_editor_done.html', {
                    'content': obj.render(request=request),
                    'identifier': obj.fe_identifier(),
                    'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA,
                    'FEINCMS_ADMIN_MEDIA_HOTLINKING': \
                        settings.FEINCMS_ADMIN_MEDIA_HOTLINKING,
                    'FEINCMS_JQUERY_NO_CONFLICT': \
                        settings.FEINCMS_JQUERY_NO_CONFLICT,
                    })
        else:
            form = ModelForm(instance=obj, prefix=content_type)

        context = self.get_extra_context(request)
        context.update({
            'frontend_editing': True,
            'title': _('Change %s') % force_unicode(model_cls._meta.verbose_name),
            'object': obj,
            'form': form,
            'is_popup': True,
            'media': self.media,
            })

        return render_to_response('admin/feincms/fe_editor.html', context,
            context_instance=template.RequestContext(request))

    def get_content_type_map(self):
        """ Prepare mapping of content types to their prettified names. """
        content_types = []
        for content_type in self.model._feincms_content_types:
            content_name = content_type._meta.verbose_name
            content_types.append((content_name, content_type.__name__.lower()))
        return content_types

    def get_extra_context(self, request):
        """ Return extra context parameters for add/change views. """

        extra_context = {
            'model': self.model,
            'available_templates':
                getattr(self.model, '_feincms_templates', ()),
            'has_parent_attribute': hasattr(self.model, 'parent'),
            'content_types': self.get_content_type_map(),
            'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA,
            'FEINCMS_ADMIN_MEDIA_HOTLINKING':
                settings.FEINCMS_ADMIN_MEDIA_HOTLINKING,
            'FEINCMS_JQUERY_NO_CONFLICT': settings.FEINCMS_JQUERY_NO_CONFLICT,
            'FEINCMS_CONTENT_FIELDSET_NAME': FEINCMS_CONTENT_FIELDSET_NAME,

            'FEINCMS_FRONTEND_EDITING': settings.FEINCMS_FRONTEND_EDITING,
            }

        for processor in self.model.feincms_item_editor_context_processors:
            extra_context.update(processor(request))

        return extra_context

    def add_view(self, request, form_url='', extra_context=None):
        context = {}

        # insert dummy object as 'original' so template code can grab defaults
        # for template, etc.
        context['original'] = self.model()

        # If there are errors in the form, we need to preserve the object's
        # template as it was set when the user attempted to save it, so that
        # the same regions appear on screen.
        if request.method == 'POST' and \
                hasattr(self.model, '_feincms_templates'):
            context['original'].template_key = request.POST['template_key']

        context.update(self.get_extra_context(request))
        context.update(extra_context or {})
        return super(ItemEditor, self).add_view(request, form_url, context)

    def change_view(self, request, object_id, extra_context=None):
        self.model._needs_content_types()

        # Recognize frontend editing requests
        # This is done here so that the developer does not need to add
        # additional entries to # urls.py or something...
        res = FRONTEND_EDITING_MATCHER.search(object_id)
        if res:
            return self._frontend_editing_view(
                request, res.group(1), res.group(2), res.group(3))

        context = {}
        context.update(self.get_extra_context(request))
        context.update(extra_context or {})
        return super(ItemEditor, self).change_view(request, object_id, context)

    # The next two add support for sending a "saving done" signal as soon
    # as all relevant data have been saved (especially all foreign key relations)
    # This can be used to keep functionality dependend on item content happy.
    # NOTE: These two can (and probably should) be replaced by overriding
    # `save_related` as soon as we don't depend on Django<1.4 any more.
    def response_add(self, request, obj, *args, **kwargs):
        r = super(ItemEditor, self).response_add(request, obj, *args, **kwargs)
        itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=True)
        return r

    def response_change(self, request, obj, *args, **kwargs):
        r = super(ItemEditor, self).response_change(request, obj, *args, **kwargs)
        itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=False)
        return r

    @property
    def change_form_template(self):
        return self.get_template_list()

    def get_template_list(self):
        # retained for backwards-compatibility, change_form_template wraps it
        opts = self.model._meta
        return [
            'admin/feincms/%s/%s/item_editor.html' % (
                opts.app_label, opts.object_name.lower()),
            'admin/feincms/%s/item_editor.html' % opts.app_label,
            'admin/feincms/item_editor.html',
            ]

    def get_fieldsets(self, request, obj=None):
        """
        Insert FEINCMS_CONTENT_FIELDSET it not present.
        Is it reasonable to assume this should always be included?
        """

        fieldsets = copy.deepcopy(
            super(ItemEditor, self).get_fieldsets(request, obj))

        if not FEINCMS_CONTENT_FIELDSET_NAME in dict(fieldsets).keys():
            fieldsets.append(FEINCMS_CONTENT_FIELDSET)

        return fieldsets

    # These next are only used if later we use a subclass of this class
    # which also inherits from VersionAdmin.
    revision_form_template = "admin/feincms/revision_form.html"

    recover_form_template = "admin/feincms/recover_form.html"

    def render_revision_form(self, request, obj, version, context, revert=False, recover=False):
        context.update(self.get_extra_context(request))
        return super(ItemEditor, self).render_revision_form(request, obj, version, context, revert, recover)
<commit_msg>Added condition to retain capability to work regardless of Django version. Common functionality for both variants is factored out into method.<commit_after># ------------------------------------------------------------------------
# coding=utf-8
# ------------------------------------------------------------------------

import re
import copy

from django import forms, template
from django.contrib import admin
from django.core.exceptions import ImproperlyConfigured
from django.db.models import loading
from django.forms.models import modelform_factory
from django.http import Http404
from django.shortcuts import render_to_response
from django.utils.encoding import force_unicode
from django.utils.functional import curry
from django.utils.translation import ugettext as _
from django.contrib.admin.options import InlineModelAdmin

from feincms import settings, ensure_completely_loaded
from feincms.signals import itemeditor_post_save_related

# ------------------------------------------------------------------------
FRONTEND_EDITING_MATCHER = re.compile(r'(\d+)\|(\w+)\|(\d+)')
FEINCMS_CONTENT_FIELDSET_NAME = 'FEINCMS_CONTENT'
FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {'fields': ()})

# ------------------------------------------------------------------------
class ItemEditorForm(forms.ModelForm):
    """
    The item editor form contains hidden region and ordering fields and should
    be used for all content type inlines.
    """

    region = forms.CharField(widget=forms.HiddenInput())
    ordering = forms.IntegerField(widget=forms.HiddenInput())

# ------------------------------------------------------------------------
class FeinCMSInline(InlineModelAdmin):
    """
    Custom ``InlineModelAdmin`` subclass used for content types.
    """

    form = ItemEditorForm
    extra = 0
    fk_name = 'parent'
    template = 'admin/feincms/content_inline.html'

# ------------------------------------------------------------------------
class ItemEditor(admin.ModelAdmin):
    """
    The ``ItemEditor`` is a drop-in replacement for ``ModelAdmin`` with the
    speciality of knowing how to work with :class:`feincms.models.Base`
    subclasses and associated content types.

    It does not have any public API except from everything inherited from'
    the standard ``ModelAdmin`` class.
    """

    def __init__(self, model, admin_site):
        ensure_completely_loaded()

        super(ItemEditor, self).__init__(model, admin_site)

        if hasattr(self, 'inline_instances'):
            # Add inline instances for FeinCMS content inlines
            # This works in Django 1.3 and lower
            # In Django 1.4 inline instances are generated using overridden get_inline_instances()
            self.append_feincms_inlines(self.inline_instances)

    def get_inline_instances(self, request):
        inline_instances = super(ItemEditor, self).get_inline_instances(request)
        self.append_feincms_inlines(inline_instances)

        return inline_instances

    def append_feincms_inlines(self, inline_instances):
        """ Append generated FeinCMS content inlines to native django inlines. """
        for inline_class in self.get_feincms_inlines(self.model):
            inline_instance = inline_class(self.model, self.admin_site)
            inline_instances.append(inline_instance)

    def get_feincms_inlines(self, model):
        """ Generate genuine django inlines for registered content types. """
        inlines = []
        for content_type in model._feincms_content_types:
            attrs = {
                '__module__': model.__module__,
                'model': content_type,
                }

            if hasattr(content_type, 'feincms_item_editor_inline'):
                inline = content_type.feincms_item_editor_inline
                attrs['form'] = inline.form

                if hasattr(content_type, 'feincms_item_editor_form'):
                    import warnings
                    warnings.warn(
                        'feincms_item_editor_form on %s is ignored because feincms_item_editor_inline is set too' % content_type,
                        RuntimeWarning)

            else:
                inline = FeinCMSInline
                attrs['form'] = getattr(content_type,
                    'feincms_item_editor_form', inline.form)

            name = '%sFeinCMSInline' % content_type.__name__
            inlines.append(type(name, (inline,), attrs))
        return inlines

    def _frontend_editing_view(self, request, cms_id, content_type, content_id):
        """
        This view is used strictly for frontend editing -- it is not used inside the
        standard administration interface.

        The code in feincms/templates/admin/feincms/fe_tools.html knows how to call
        this view correctly.
        """

        try:
            model_cls = loading.get_model(self.model._meta.app_label, content_type)
            obj = model_cls.objects.get(parent=cms_id, id=content_id)
        except:
            raise Http404

        form_class_base = getattr(model_cls, 'feincms_item_editor_form', ItemEditorForm)

        ModelForm = modelform_factory(model_cls,
            exclude=('parent', 'region', 'ordering'),
            form=form_class_base,
            formfield_callback=curry(self.formfield_for_dbfield, request=request))

        # we do not want to edit these two fields in the frontend editing mode; we are
        # strictly editing single content blocks there.
        # We have to remove them from the form because we explicitly redefined them in
        # the ItemEditorForm definition above. Just using exclude is not enough.
        del ModelForm.base_fields['region']
        del ModelForm.base_fields['ordering']

        if request.method == 'POST':
            # The prefix is used to replace the formset identifier from the ItemEditor
            # interface. Customization of the form is easily possible through either matching
            # the prefix (frontend editing) or the formset identifier (ItemEditor) as it is
            # done in the richtext and mediafile init.html item editor includes.
            form = ModelForm(request.POST, instance=obj, prefix=content_type)

            if form.is_valid():
                obj = form.save()

                return render_to_response('admin/feincms/fe_editor_done.html', {
                    'content': obj.render(request=request),
                    'identifier': obj.fe_identifier(),
                    'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA,
                    'FEINCMS_ADMIN_MEDIA_HOTLINKING': \
                        settings.FEINCMS_ADMIN_MEDIA_HOTLINKING,
                    'FEINCMS_JQUERY_NO_CONFLICT': \
                        settings.FEINCMS_JQUERY_NO_CONFLICT,
                    })
        else:
            form = ModelForm(instance=obj, prefix=content_type)

        context = self.get_extra_context(request)
        context.update({
            'frontend_editing': True,
            'title': _('Change %s') % force_unicode(model_cls._meta.verbose_name),
            'object': obj,
            'form': form,
            'is_popup': True,
            'media': self.media,
            })

        return render_to_response('admin/feincms/fe_editor.html', context,
            context_instance=template.RequestContext(request))

    def get_content_type_map(self):
        """ Prepare mapping of content types to their prettified names. """
        content_types = []
        for content_type in self.model._feincms_content_types:
            content_name = content_type._meta.verbose_name
            content_types.append((content_name, content_type.__name__.lower()))
        return content_types

    def get_extra_context(self, request):
        """ Return extra context parameters for add/change views. """

        extra_context = {
            'model': self.model,
            'available_templates':
                getattr(self.model, '_feincms_templates', ()),
            'has_parent_attribute': hasattr(self.model, 'parent'),
            'content_types': self.get_content_type_map(),
            'FEINCMS_ADMIN_MEDIA': settings.FEINCMS_ADMIN_MEDIA,
            'FEINCMS_ADMIN_MEDIA_HOTLINKING':
                settings.FEINCMS_ADMIN_MEDIA_HOTLINKING,
            'FEINCMS_JQUERY_NO_CONFLICT': settings.FEINCMS_JQUERY_NO_CONFLICT,
            'FEINCMS_CONTENT_FIELDSET_NAME': FEINCMS_CONTENT_FIELDSET_NAME,

            'FEINCMS_FRONTEND_EDITING': settings.FEINCMS_FRONTEND_EDITING,
            }

        for processor in self.model.feincms_item_editor_context_processors:
            extra_context.update(processor(request))

        return extra_context

    def add_view(self, request, form_url='', extra_context=None):
        context = {}

        # insert dummy object as 'original' so template code can grab defaults
        # for template, etc.
        context['original'] = self.model()

        # If there are errors in the form, we need to preserve the object's
        # template as it was set when the user attempted to save it, so that
        # the same regions appear on screen.
        if request.method == 'POST' and \
                hasattr(self.model, '_feincms_templates'):
            context['original'].template_key = request.POST['template_key']

        context.update(self.get_extra_context(request))
        context.update(extra_context or {})
        return super(ItemEditor, self).add_view(request, form_url, context)

    def change_view(self, request, object_id, extra_context=None):
        self.model._needs_content_types()

        # Recognize frontend editing requests
        # This is done here so that the developer does not need to add
        # additional entries to # urls.py or something...
        res = FRONTEND_EDITING_MATCHER.search(object_id)
        if res:
            return self._frontend_editing_view(
                request, res.group(1), res.group(2), res.group(3))

        context = {}
        context.update(self.get_extra_context(request))
        context.update(extra_context or {})
        return super(ItemEditor, self).change_view(request, object_id, context)

    # The next two add support for sending a "saving done" signal as soon
    # as all relevant data have been saved (especially all foreign key relations)
    # This can be used to keep functionality dependend on item content happy.
    # NOTE: These two can (and probably should) be replaced by overriding
    # `save_related` as soon as we don't depend on Django<1.4 any more.
    def response_add(self, request, obj, *args, **kwargs):
        r = super(ItemEditor, self).response_add(request, obj, *args, **kwargs)
        itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=True)
        return r

    def response_change(self, request, obj, *args, **kwargs):
        r = super(ItemEditor, self).response_change(request, obj, *args, **kwargs)
        itemeditor_post_save_related.send(sender=obj.__class__, instance=obj, created=False)
        return r

    @property
    def change_form_template(self):
        return self.get_template_list()

    def get_template_list(self):
        # retained for backwards-compatibility, change_form_template wraps it
        opts = self.model._meta
        return [
            'admin/feincms/%s/%s/item_editor.html' % (
                opts.app_label, opts.object_name.lower()),
            'admin/feincms/%s/item_editor.html' % opts.app_label,
            'admin/feincms/item_editor.html',
            ]

    def get_fieldsets(self, request, obj=None):
        """
        Insert FEINCMS_CONTENT_FIELDSET it not present.
        Is it reasonable to assume this should always be included?
        """

        fieldsets = copy.deepcopy(
            super(ItemEditor, self).get_fieldsets(request, obj))

        if not FEINCMS_CONTENT_FIELDSET_NAME in dict(fieldsets).keys():
            fieldsets.append(FEINCMS_CONTENT_FIELDSET)

        return fieldsets

    # These next are only used if later we use a subclass of this class
    # which also inherits from VersionAdmin.
    revision_form_template = "admin/feincms/revision_form.html"

    recover_form_template = "admin/feincms/recover_form.html"

    def render_revision_form(self, request, obj, version, context, revert=False, recover=False):
        context.update(self.get_extra_context(request))
        return super(ItemEditor, self).render_revision_form(request, obj, version, context, revert, recover)
<|endoftext|>


<commit_before>"""
sinecosine.py
Author: Ryan Kynor
Credit: <list sources used, if any>

Assignment:

In this assignment you must use *list comprehensions* to generate sprites that show the behavior
of certain mathematical functions: sine and cosine.

The sine and cosine functions are provided in the Python math library. These functions are used
to relate *angles* to *rectangular* (x,y) coordinate systems and can be very useful in computer
game design.

Unlike the last assignment using ggame`, this one will not provide any "skeleton" code to fill
in. You should use your submission for the Picture assignment
(https://github.com/HHS-IntroProgramming/Picture) as a reference for starting this assignment.

See:
https://github.com/HHS-IntroProgramming/Sine-Cosine/blob/master/README.md
for a detailed list of requirements for this assignment.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Displaying-Graphics
for general information on how to use ggame.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Programmed-Graphics
for general information on using list comprehensions to generate graphics.

http://brythonserver.github.io/ggame/
for detailed information on ggame.
"""

from ggame import App, Color, LineStyle, Sprite, CircleAsset
from math import sin, cos, radians

red = Color(0xff0000, 1.0)
green = Color(0x00ff00, 1.0)
blue = Color(0x0000ff, 1.0)
black = Color(0x000000, 1.0)
purple = Color(0x551A8B, 1.0)

thinline = LineStyle(1, black)

bluecircle = CircleAsset(50, thinline, blue)
redcircle = CircleAsset(50, thinline, red)
purplecircle = CircleAsset(50, thinline, purple)

x = range(0, 361, 10)
xcoordinatesblue = range(0, 361, 10)
xcoordinatesred = range(0, 361, 10)
xcoordinatespurple = 100+100*cos(radians(x))

ycoordinatesblue = 100+100*sin(radians(x))
ycoordinatesred = 100+100*cos(radians(x))
ycoordinatespurple = 400+100*sin(radians(x))

# Generate a list of sprites that form a line!
[Sprite = (bluecircle, (xcoordinatesblue , (100+100*sin(radians(x))))) for x in range(0, 361, 10)]
Sprite1 = (redcircle, (xcoordinatesred, ycoordinatesred))
Sprite2 = (purplecircle, (xcoordinatespurple, ycoordinatespurple))

myapp = App()
myapp.run()<commit_msg>Updated from Brython Server: September 21, 2015 2:47:35 PM EDT<commit_after>"""
sinecosine.py
Author: Ryan Kynor
Credit: <list sources used, if any>

Assignment:

In this assignment you must use *list comprehensions* to generate sprites that show the behavior
of certain mathematical functions: sine and cosine.

The sine and cosine functions are provided in the Python math library. These functions are used
to relate *angles* to *rectangular* (x,y) coordinate systems and can be very useful in computer
game design.

Unlike the last assignment using ggame`, this one will not provide any "skeleton" code to fill
in. You should use your submission for the Picture assignment
(https://github.com/HHS-IntroProgramming/Picture) as a reference for starting this assignment.

See:
https://github.com/HHS-IntroProgramming/Sine-Cosine/blob/master/README.md
for a detailed list of requirements for this assignment.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Displaying-Graphics
for general information on how to use ggame.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Programmed-Graphics
for general information on using list comprehensions to generate graphics.

http://brythonserver.github.io/ggame/
for detailed information on ggame.
"""

from ggame import App, Color, LineStyle, Sprite, CircleAsset
from math import sin, cos, radians

red = Color(0xff0000, 1.0)
green = Color(0x00ff00, 1.0)
blue = Color(0x0000ff, 1.0)
black = Color(0x000000, 1.0)
purple = Color(0x551A8B, 1.0)

thinline = LineStyle(1, black)

bluecircle = CircleAsset(50, thinline, blue)
redcircle = CircleAsset(50, thinline, red)
purplecircle = CircleAsset(50, thinline, purple)

x = range(0, 361, 10)
xcoordinatesblue = range(0, 361, 10)
xcoordinatesred = range(0, 361, 10)
xcoordinatespurple = 100+100*cos(radians(x))

ycoordinatesblue = 100+100*sin(radians(x))
ycoordinatesred = 100+100*cos(radians(x))
ycoordinatespurple = 400+100*sin(radians(x))

# Generate a list of sprites that form a line!

[Sprite = (bluecircle, (xcoordinatesblue , (100+100*sin(radians(x))))) for x in range(0, 361, 10)]
Sprite1 = (redcircle, (xcoordinatesred, ycoordinatesred))
Sprite2 = (purplecircle, (xcoordinatespurple, ycoordinatespurple))

myapp = App()
myapp.run()<|endoftext|>


<commit_before>from collections import defaultdict
from datetime import *

class InvalidFieldError(Exception):
    pass

class CronParser(object):
    """
        Space delimited cron string:

        minute[0..59] hour[0..23] dom[1..31] moy[1..12] dow[1..7](1=Monday) year

        allows "*", "-", "/", and "[0-9]"
    """
    fields = ["minute", "hour", "dom", "moy", "dow", "year"]

    def __init__(self, rules, exceptions=list(), in_utc=False):
        """
            rules and
        """

        self.rules = defaultdict(list)
        self.exceptions = defaultdict(list)

        for rname, rule in rules:
            #Two rule definitions for same name will be merged
            self.rules[rname].append(self.parse(rule))

        for ename, exception in exceptions:
            #Two exception definitions for same name will be merged
            self.exceptions[ename].append(self.parse(exception))

        # self.apply_exceptions();

    #Can't do this!  Think about case where we merge holidays e.g. "* * 25 12 * *" and "* * 1 1 * *", then we match december 1st and january 25th too
    def merge_rules(self, r1, r2):
        """
            merge rulesets (union)

            Can handle an empty dictionary as a ruleset
        """
        return {f: r1.get(f, set()).union( r2.get(f, set()) ) for f in CronParser.fields}

    def diff_rules(self, r1, r2):
        """
            remove r2 from r1 (difference)

            Can handle an empty dictionary as a ruleset
        """
        return {f: r1.get(f, set()) - r2.get(f, set()) for f in CronParser.fields}

    # def apply_exceptions(self):
    #     for ename, exc in self.exceptions.items():

    #         for rname, rule in self.rules.items():

    #             #Add exceptions to the same-named rule set
    #             if ename == rname:
    #                 self.rules[rname] = self.merge_rules(rule, exc)

    #             #Remove exceptions from other rule sets
    #             else:
    #                 self.rules[rname] = self.diff_rules(rule, exc)

    def parse_field(self, f, minimum=0, maximum=0):
        """
            Returns a set containing the right elements
            minimum and maximum define the range of values used for wildcards
            minimum and maximum as passed should be inclusive integers.
            All +1s will be added here.
                e.g. 0,1 -> set([0,1])

        """
        digits = {i for i in xrange(10)}

        final_range = []
        try:
            #Handle clauses containing '/'
            divisor = 1
            div_splits = f.split("/")

            if len(div_splits) > 1 and div_splits[1].isdigit():
                divisor = int(div_splits[1])

            # *, */x
            if f[0] == "*":
                return set(xrange(minimum, maximum + 1, divisor))

            # Don't parse the divisor again
            f = div_splits[0]

            # i, j-k, j-k/x
            hyphen_splits = f.split("-")

            if hyphen_splits[0].isdigit():
                #j-k, j-k/x
                if len(hyphen_splits) > 1:
                    return set( xrange(int(hyphen_splits[0]), int(hyphen_splits[1]) + 1, divisor) )

                # i
                return {int(f)}

            #If no rule matches, the string isn't valid
            raise InvalidFieldError(f)

        #Saves boilerplate checking length of string,
        # Intead, assume it's valid, and otherwise throw error
        except IndexError:
            raise InvalidFieldError(f)

    def parse(self, cron_string):
        """
            Parses a cron_string that looks like "m h dom moy dow year"
            return is a dictionary of sets holding integers contained by that field
        """
        fields = cron_string.split(" ")
        return {
            "minutes": self.parse_field(fields[0], 0, 59),
            "hours": self.parse_field(fields[1], 0, 23),
            "dom": self.parse_field(fields[2], 1, 31),
            "moy": self.parse_field(fields[3], 1, 12),
            "dow": self.parse_field(fields[4], 1, 7),
            "year": self.parse_field(fields[5], 2000, 2025)  #What is a sensible year here?  EOT?
        }

    def check_ruleset(self, ruleset, time_obj):
        """
            Returns True/False if time_obj is contained in ruleset
        """

        #If all checks pass, the time_obj belongs to this ruleset
        #Reverse order to ensure holidays are fast
        if time_obj.year not in ruleset["year"]:
            return False

        if time_obj.month not in ruleset["moy"]:
            return False

        if time_obj.day not in ruleset["dom"]:
            return False

        if time_obj.isoweekday() not in ruleset["dow"]:
            return False

        if time_obj.hour not in ruleset["hours"]:
            return False

        if time_obj.minute not in ruleset["minutes"]:
            return False

        return True

    def pick_rules(self, time_obj):
        """
            Picks the rules that time_obj belongs to.

            time_obj is a datetime object.

            if self.in_utc is defined, then the time_obj will be converted to UTC

            If rules overlap, the time_obj will belong to multiple rules.
            Therefore return is a list:
                e.g.
                [], ["2001"], ["weekday_afternoons", "every_thursday"],
        """

        rule_list = []

        #Check exceptions first
        for ename, exceptions in self.exceptions.items():
            for exception in exceptions:
                if self.check_ruleset(exception, time_obj):
                    return [ename]

        #No exceptions match, so all rules are available
        for rname, rulesets in self.rules.items():
            for ruleset in rulesets:
                if self.check_ruleset(ruleset, time_obj):
                    rule_list.append(rname)

        return rule_list

def test_parse_field():
    cp = CronParser()
    strs = {
        "5": {5},
        "2-9": set(xrange(2,10)),
        "20-30": set(xrange(20,31)),
        "20-30/2": set(xrange(20,31,2)),
        "*": set(xrange(0,60)),
        "*/3": set(xrange(0,60,3))
    }
    for f, v in strs.items():
        print f, cp.parse_field(f, 0, 59), cp.parse_field(f, 0, 59) == v

def test_parse():
    cp = CronParser()
    print cp.parse("* 7-19 * * 1-5 * ")

def test_pick_rules():
    cp = CronParser(
        [("open", "* 7-19 * * * *"), ("closed", "* 0-6 * * * *"), ("closed", "* 20-23 * * * *")],
        [("closed", "* 0-8 * * 6-7 *"), ("closed", "* 17-23 * * 6-7 *"),
         ("closed", "* * 25 12 * *"), ("closed", "* * 4 7 * *"),
         ("closed", "* * 31 12 * 2001"),
         ("closed", "* * 31 12 * 2002"),
         ("closed", "* * 31 12 * 2003"),
         ("closed", "* * 31 12 * 2004"),
         ("closed", "* * 31 12 * 2005"),
         ("closed", "* * 31 12 * 2006"),
         ("closed", "* * 31 12 * 2007"),
         ("closed", "* * 31 12 * 2008"),
         ("closed", "* * 31 12 * 2009"),
         ("closed", "* * 31 12 * 2010"),
         ("closed", "* * 31 12 * 2011"),
         ("closed", "* * 31 12 * 2012"),
         ("closed", "* * 31 12 * 2013"),
         ("closed", "* * 31 12 * 2014"),
         ("closed", "* * 31 12 * 2015"),
         ("closed", "* * 31 11 * 2001"),
         ("closed", "* * 31 11 * 2002"),
         ("closed", "* * 31 11 * 2003"),
         ("closed", "* * 31 11 * 2004"),
         ("closed", "* * 31 11 * 2005"),
         ("closed", "* * 31 11 * 2006"),
         ("closed", "* * 31 11 * 2007"),
         ("closed", "* * 31 11 * 2008"),
         ("closed", "* * 31 11 * 2009"),
         ("closed", "* * 31 11 * 2010"),
         ("closed", "* * 31 11 * 2011"),
         ("closed", "* * 31 11 * 2012"),
         ("closed", "* * 31 11 * 2013"),
         ("closed", "* * 31 11 * 2014"),
         ("closed", "* * 31 11 * 2015"),
         ("closed", "* * 31 10 * 2001"),
         ("closed", "* * 31 10 * 2002"),
         ("closed", "* * 31 10 * 2003"),
         ("closed", "* * 31 10 * 2004"),
         ("closed", "* * 31 10 * 2005"),
         ("closed", "* * 31 10 * 2006"),
         ("closed", "* * 31 10 * 2007"),
         ("closed", "* * 31 10 * 2008"),
         ("closed", "* * 31 10 * 2009"),
         ("closed", "* * 31 10 * 2010"),
         ("closed", "* * 31 10 * 2011"),
         ("closed", "* * 31 10 * 2012"),
         ("closed", "* * 31 10 * 2013"),
         ("closed", "* * 31 10 * 2014"),
         ("closed", "* * 31 10 * 2015"),
         ("closed", "* * 31 9 * 2001"),
         ("closed", "* * 31 9 * 2002"),
         ("closed", "* * 31 9 * 2003"),
         ("closed", "* * 31 9 * 2004"),
         ("closed", "* * 31 9 * 2005"),
         ("closed", "* * 31 9 * 2006"),
         ("closed", "* * 31 9 * 2007"),
         ("closed", "* * 31 9 * 2008"),
         ("closed", "* * 31 9 * 2009"),
         ("closed", "* * 31 9 * 2010"),
         ("closed", "* * 31 9 * 2011"),
         ("closed", "* * 31 9 * 2012"),
         ("closed", "* * 31 9 * 2013"),
         ("closed", "* * 31 9 * 2014"),
         ("closed", "* * 31 9 * 2015"),
         ("closed", "* * 31 8 * 2001"),
         ("closed", "* * 31 8 * 2002"),
         ("closed", "* * 31 8 * 2003"),
         ("closed", "* * 31 8 * 2004"),
         ("closed", "* * 31 8 * 2005"),
         ("closed", "* * 31 8 * 2006"),
         ("closed", "* * 31 8 * 2007"),
         ("closed", "* * 31 8 * 2008"),
         ("closed", "* * 31 8 * 2009"),
         ("closed", "* * 31 8 * 2010"),
         ("closed", "* * 31 8 * 2011"),
         ("closed", "* * 31 8 * 2012"),
         ("closed", "* * 31 8 * 2013"),
         ("closed", "* * 31 8 * 2014"),
         ("closed", "* * 31 8 * 2015"),
         ("closed", "* * 31 7 * 2001"),
         ("closed", "* * 31 7 * 2002"),
         ("closed", "* * 31 7 * 2003"),
         ("closed", "* * 31 7 * 2004"),
         ("closed", "* * 31 7 * 2005"),
         ("closed", "* * 31 7 * 2006"),
         ("closed", "* * 31 7 * 2007"),
         ("closed", "* * 31 7 * 2008"),
         ("closed", "* * 31 7 * 2009"),
         ("closed", "* * 31 7 * 2010"),
         ("closed", "* * 31 7 * 2011"),
         ("closed", "* * 31 7 * 2012"),
         ("closed", "* * 31 7 * 2013"),
         ("closed", "* * 31 7 * 2014"),
         ("closed", "* * 31 7 * 2015"),
         ("closed", "* * 31 6 * 2001"),
         ("closed", "* * 31 6 * 2002"),
         ("closed", "* * 31 6 * 2003"),
         ("closed", "* * 31 6 * 2004"),
         ("closed", "* * 31 6 * 2005"),
         ("closed", "* * 31 6 * 2006"),
         ("closed", "* * 31 6 * 2007"),
         ("closed", "* * 31 6 * 2008"),
         ("closed", "* * 31 6 * 2009"),
         ("closed", "* * 31 6 * 2010"),
         ("closed", "* * 31 6 * 2011"),
         ("closed", "* * 31 6 * 2012"),
         ("closed", "* * 31 6 * 2013"),
         ("closed", "* * 31 6 * 2014"),
         ("closed", "* * 31 6 * 2015"),
         ("closed", "* * 31 5 * 2001"),
         ("closed", "* * 31 5 * 2002"),
         ("closed", "* * 31 5 * 2003"),
         ("closed", "* * 31 5 * 2004"),
         ("closed", "* * 31 5 * 2005"),
         ("closed", "* * 31 5 * 2006"),
         ("closed", "* * 31 5 * 2007"),
         ("closed", "* * 31 5 * 2008"),
         ("closed", "* * 31 5 * 2009"),
         ("closed", "* * 31 5 * 2010"),
         ("closed", "* * 31 5 * 2011"),
         ("closed", "* * 31 5 * 2012"),
         ("closed", "* * 31 5 * 2013"),
         ("closed", "* * 31 5 * 2014"),
         ("closed", "* * 31 5 * 2015"),
         ("closed", "* * 31 4 * 2001"),
         ("closed", "* * 31 4 * 2002"),
         ("closed", "* * 31 4 * 2003"),
         ("closed", "* * 31 4 * 2004"),
         ("closed", "* * 31 4 * 2005"),
         ("closed", "* * 31 4 * 2006"),
         ("closed", "* * 31 4 * 2007"),
         ("closed", "* * 31 4 * 2008"),
         ("closed", "* * 31 4 * 2009"),
         ("closed", "* * 31 4 * 2010"),
         ("closed", "* * 31 4 * 2011"),
         ("closed", "* * 31 4 * 2012"),
         ("closed", "* * 31 4 * 2013"),
         ("closed", "* * 31 4 * 2014"),
         ("closed", "* * 31 4 * 2015"),
         ("closed", "* * 31 3 * 2001"),
         ("closed", "* * 31 3 * 2002"),
         ("closed", "* * 31 3 * 2003"),
         ("closed", "* * 31 3 * 2004"),
         ("closed", "* * 31 3 * 2005"),
         ("closed", "* * 31 3 * 2006"),
         ("closed", "* * 31 3 * 2007"),
         ("closed", "* * 31 3 * 2008"),
         ("closed", "* * 31 3 * 2009"),
         ("closed", "* * 31 3 * 2010"),
         ("closed", "* * 31 3 * 2011"),
         ("closed", "* * 31 3 * 2012"),
         ("closed", "* * 31 3 * 2013"),
         ("closed", "* * 31 3 * 2014"),
         ("closed", "* * 31 3 * 2015"),
         ("closed", "* * 31 2 * 2001"),
         ("closed", "* * 31 2 * 2002"),
         ("closed", "* * 31 2 * 2003"),
         ("closed", "* * 31 2 * 2004"),
         ("closed", "* * 31 2 * 2005"),
         ("closed", "* * 31 2 * 2006"),
         ("closed", "* * 31 2 * 2007"),
         ("closed", "* * 31 2 * 2008"),
         ("closed", "* * 31 2 * 2009"),
         ("closed", "* * 31 2 * 2010"),
         ("closed", "* * 31 2 * 2011"),
         ("closed", "* * 31 2 * 2012"),
         ("closed", "* * 31 2 * 2013"),
         ("closed", "* * 31 2 * 2014"),
         ("closed", "* * 31 2 * 2015"),
         ("closed", "* * 31 1 * 2001"),
         ("closed", "* * 31 1 * 2002"),
         ("closed", "* * 31 1 * 2003"),
         ("closed", "* * 31 1 * 2004"),
         ("closed", "* * 31 1 * 2005"),
         ("closed", "* * 31 1 * 2006"),
         ("closed", "* * 31 1 * 2007"),
         ("closed", "* * 31 1 * 2008"),
         ("closed", "* * 31 1 * 2009"),
         ("closed", "* * 31 1 * 2010"),
         ("closed", "* * 31 1 * 2011"),
         ("closed", "* * 31 1 * 2012"),
         ("closed", "* * 31 1 * 2013"),
         ("closed", "* * 31 1 * 2014"),
         ("closed", "* * 31 1 * 2015"),
         ]
    )

    import time
    start = time.time()

    for d in xrange(1,12):
        for h in xrange(0,24):
            cp.pick_rules(datetime(2014,12,d,h,0))

    print  time.time() - start
    print len(cp.exceptions['closed'])
    print "Christmas", cp.pick_rules(datetime(2014,12,25,8,0))

if __name__ == "__main__":
    test_pick_rules()<commit_msg>Adding holiday parsing code<commit_after>from collections import defaultdict
from datetime import *
import re

class InvalidFieldError(Exception):
    pass

class CronParser(object):
    """
        Space delimited cron string:

        minute[0..59] hour[0..23] dom[1..31] moy[1..12] dow[1..7](1=Monday) year

        allows "*", "-", "/", and "[0-9]"
    """
    fields = ["minute", "hour", "dom", "moy", "dow", "year"]
    holiday_re = "[\*]\s[\*]\s(\d{1,2})\s(\d{1,2})\s[\*]\s(\d{4})"

    def __init__(self, rules=list(), exceptions=list(), in_utc=False):
        """
            rules and exeptions should look like:
            [("name", "* * * * * *"), ...]
        """

        self.rules = defaultdict(list)
        self.exceptions = defaultdict(list)
        self.holiday_exceptions = defaultdict(list)  #Optimization to reduce the effect of one day exceptions on the runtime

        for rname, rule in rules:
            #Two rule definitions for same name will be merged
            self.rules[rname].append(self.parse(rule))

        for ename, exception in exceptions:
            #Two exception definitions for same name will be merged
            if self.is_holiday(exception):
                self.holiday_exceptions[ename].append(self.parse(exception))
            else:
                self.exceptions[ename].append(self.parse(exception))

    def diff_rules(self, r1, r2):
        """
            remove r2 from r1 (difference)

            Can handle an empty dictionary as a ruleset
        """
        return {f: r1.get(f, set()) - r2.get(f, set()) for f in CronParser.fields}

    def parse_field(self, f, minimum=0, maximum=0):
        """
            Returns a set containing the right elements
            minimum and maximum define the range of values used for wildcards
            minimum and maximum as passed should be inclusive integers.
            All +1s will be added here.
                e.g. 0,1 -> set([0,1])

        """
        digits = {i for i in xrange(10)}

        final_range = []
        try:
            #Handle clauses containing '/'
            divisor = 1
            div_splits = f.split("/")

            if len(div_splits) > 1 and div_splits[1].isdigit():
                divisor = int(div_splits[1])

            # *, */x
            if f[0] == "*":
                return set(xrange(minimum, maximum + 1, divisor))

            # Don't parse the divisor again
            f = div_splits[0]

            # i, j-k, j-k/x
            hyphen_splits = f.split("-")

            if hyphen_splits[0].isdigit():
                #j-k, j-k/x
                if len(hyphen_splits) > 1:
                    return set( xrange(int(hyphen_splits[0]), int(hyphen_splits[1]) + 1, divisor) )

                # i
                return {int(f)}

            #If no rule matches, the string isn't valid
            raise InvalidFieldError(f)

        #Saves boilerplate checking length of string,
        # Intead, assume it's valid, and otherwise throw error
        except IndexError:
            raise InvalidFieldError(f)

    def parse(self, cron_string):
        """
            Parses a cron_string that looks like "m h dom moy dow year"
            return is a dictionary of sets holding integers contained by that field
        """
        fields = cron_string.split(" ")
        return {
            "minutes": self.parse_field(fields[0], 0, 59),
            "hours": self.parse_field(fields[1], 0, 23),
            "dom": self.parse_field(fields[2], 1, 31),
            "moy": self.parse_field(fields[3], 1, 12),
            "dow": self.parse_field(fields[4], 1, 7),
            "year": self.parse_field(fields[5], 2000, 2025)  #What is a sensible year here?  EOT?
        }

    def is_holiday(self, rule):
        """
            Holiday is defined as one day, one month, one year:

            e.g. Easter: "* * 5 4 * 2015"
        """
        return re.compile(CronParser.holiday_re).match(rule.strip()) is not None

    def holiday_tuple(self, hrule):
        """
            assumes hrule is a holiday
            returns tuple: (dd, mm, yyyy)
        """
        return tuple([ int(d) for d in re.findall(CronParser.holiday_re, hrule.strip())[0] ])

    def check_ruleset(self, ruleset, time_obj):
        """
            Returns True/False if time_obj is contained in ruleset
        """

        #If all checks pass, the time_obj belongs to this ruleset
        #Reverse order to ensure holidays are fast
        if time_obj.year not in ruleset["year"]:
            return False

        if time_obj.month not in ruleset["moy"]:
            return False

        if time_obj.day not in ruleset["dom"]:
            return False

        if time_obj.isoweekday() not in ruleset["dow"]:
            return False

        if time_obj.hour not in ruleset["hours"]:
            return False

        if time_obj.minute not in ruleset["minutes"]:
            return False

        return True

    def pick_rules(self, time_obj):
        """
            Picks the rules that time_obj belongs to.

            time_obj is a datetime object.

            if self.in_utc is defined, then the time_obj will be converted to UTC

            If rules overlap, the time_obj will belong to multiple rules.
            Therefore return is a list:
                e.g.
                [], ["2001"], ["weekday_afternoons", "every_thursday"],
        """

        rule_list = []

        #Check exceptions first
        for ename, exceptions in self.exceptions.items():
            for exception in exceptions:
                if self.check_ruleset(exception, time_obj):
                    return [ename]

        #No exceptions match, so all rules are available
        for rname, rulesets in self.rules.items():
            for ruleset in rulesets:
                if self.check_ruleset(ruleset, time_obj):
                    rule_list.append(rname)

        return rule_list

def test_parse_field():
    cp = CronParser()
    strs = {
        "5": {5},
        "2-9": set(xrange(2,10)),
        "20-30": set(xrange(20,31)),
        "20-30/2": set(xrange(20,31,2)),
        "*": set(xrange(0,60)),
        "*/3": set(xrange(0,60,3))
    }
    for f, v in strs.items():
        print f, cp.parse_field(f, 0, 59), cp.parse_field(f, 0, 59) == v

def test_parse():
    cp = CronParser()
    print cp.parse("* 7-19 * * 1-5 * ")

def test_holiday_parse():
    cp = CronParser()
    print cp.is_holiday("* * * * * *")
    print cp.is_holiday("* * 12 25 * *")
    print cp.is_holiday("* * 4 7 * 2015")
    print cp.is_holiday("* * 4-6 7 * 2015")
    print cp.is_holiday("*/2 * 4 7 * 2015")

    print cp.holiday_tuple("* * 4 7 * 2015")

def test_pick_rules():
    cp = CronParser(
        [("open", "* 7-19 * * * *"), ("closed", "* 0-6 * * * *"), ("closed", "* 20-23 * * * *")],
        [("closed", "* 0-8 * * 6-7 *"), ("closed", "* 17-23 * * 6-7 *"),
         ("closed", "* * 25 12 * *"), ("closed", "* * 4 7 * *")]
    )

    import time
    start = time.time()

    for d in xrange(1,12):
        for h in xrange(0,24):
            cp.pick_rules(datetime(2014,12,d,h,0))

    print  time.time() - start
    print len(cp.exceptions['closed'])
    print "Christmas", cp.pick_rules(datetime(2014,12,25,8,0))

if __name__ == "__main__":
    test_holiday_parse()<|endoftext|>