<commit_before>"""Filters for h1ds data
"""
from urlparse import urlparse, urlunparse
import urllib2, json
import numpy as np

excluded_filters = set()

def exclude_filter(original_class):
    """Decorator to mark subclasses of BaseFilter which aren't useable filters.

    Use, for example, with  ScalarNumericBaseFilter which is designed to
    be subclassed to form usable filters.
    
    """
    #original_class.__exclude_filter = True
    global excluded_filters
    excluded_filters.add(original_class)
    return original_class

def is_numeric(cls, obj):
    attrs = ['__add__', '__sub__', '__mul__', '__div__', '__pow__']
    # numpy string_ have these attrs, do any others?
    is_numpy_str = type(obj) in [np.string_,]
    return all(hasattr(obj, attr) for attr in attrs) and not is_numpy_str

is_string = lambda cls, d: isinstance(d, basestring)
    
def http_arg(arg):
    if arg.startswith("http://"):
        # make sure we get the JSON  format, in case the user didn't add
        # format=json
        # Split URL into [scheme, netloc, path, params, query, fragments]
        parsed_url = urlparse(arg)

        # parsed_url is an immutable ParseResult  instance, copy it to a
        # (mutable) list
        parsed_url_list = [i for i in parsed_url]

        # Now we  can update the  URL query  string to enforce  the JSON
        # format.
        parsed_url_list[4] = '&'.join([parsed_url[4], 'format=json'])

        # And here is our original URL with format=json query added
        attr_url_json = urlunparse(parsed_url_list)
        request = urllib2.Request(attr_url_json)
        response = json.loads(urllib2.urlopen(request).read())

        return response['data']
    else:
        return arg

class BaseFilterMetaclass(type):
    def __new__(cls, name, bases, dct):
        # assert  some classmethods  which  simplify the  API, so  users
        # don't  have  to  put  @classmethod in  front  of  each  filter
        # subclass
        classmethods = ["valid_dtype", ]
        for cm in classmethods:
            if dct.has_key(cm):
                dct[cm] = classmethod(dct[cm])
        return type.__new__(cls, name, bases, dct)

@exclude_filter
class BaseFilter:

    __metaclass__ = BaseFilterMetaclass

    ndim = 0
    
    def __init__(self, **kwargs):
        self.kwargs = dict((k, http_arg(v)) for k, v in kwargs.iteritems())

    @classmethod
    def valid_ndim(cls, data):
        data_ndim = data.ndim if hasattr(data, 'ndim') else 0
        return (data_ndim == cls.ndim or cls.ndim == "any")
        
    @classmethod
    def is_filterable(cls, data):
        return cls.valid_ndim(data) and cls.valid_dtype(data)

    @classmethod
    def get_slug(cls):
        return cls.slug

@exclude_filter
class ScalarNumericBaseFilter(BaseFilter):

    ndim = 0
    valid_dtype = is_numeric

@exclude_filter
class ScalarStringBaseFilter(BaseFilter):

    ndim = 0
    valid_dtype = is_string

@exclude_filter
class Array1DimNumericBaseFilter(BaseFilter):
    
    ndim = 1
    valid_dtype = is_numeric

@exclude_filter
class Array2DimNumericBaseFilter(BaseFilter):
    
    ndim = 2
    valid_dtype = is_numeric
    

def float_or_array(data):
    """Cast data to float if string, or array if list."""
    if isinstance(data, list):
        return np.array(data)
    else:
        return float(data)


binary_powers = 2**np.arange(30)

########################################################################
## signal -> scalar                                                   ##
########################################################################

class FirstPulse(Array1DimNumericBaseFilter):
    """Return value of dim when the signal is greater than threshold.

    threshold can be a number or 'mid'.
    threshold = 'mid' will use (max(signal)+min(signal))/2
    """

    
    slug = "first_pulse"
    kwarg_names = ["threshold"]
    
    def apply(self, node):
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        first_element = np.where(node.data>_threshold)[0][0]
        node.data = node.dim[first_element]
        node.dim = None
        node.labels = ('first_pulse(%s, %s)' %(node.labels[0],
                                               self.kwargs["threshold"]), )
        

class PulseWidth(Array1DimNumericBaseFilter):
    """
    pulse width...

    """
    slug = "pulse_width"
    kwarg_names = ["threshold"]

    def apply(self, node):
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        t = node.dim[node.data > _threshold]
        end1 = node.dim[(node.data[:-1]-node.data[1:])>_threshold]

        use_size = min([len(t), len(end1)])

        node.data = np.min(end1[:use_size]-t[:use_size])
        node.dim = None
        node.labels = ('pulse_width(%s, %s)' %(node.labels[0],
                                               self.kwargs["threshold"]), )
    

class PulseNumber(Array1DimNumericBaseFilter):

    slug = "pulse_number"
    kwarg_names = ["threshold"]
            
    def apply(self, node):
        """
        number of pulses...??

        """
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        t = node.dim[node.data > _threshold]
        end1 = node.dim[(node.data[:-1]-node.data[1:])>_threshold]

        # TODO: should no need to cast  this as int32, but there is some
        # bizarre problem with dtype_mapping  key... without casting the
        # result of np.min, type(node.data)  says it is numpy.int32, but
        # it   is  somehow   different   to  the   numpy.int32  in   the
        # dtype_mapping key.
        node.data = np.int32(np.min([t.shape[0], end1.shape[0]]))
        node.dim = None
        node.labels = ('pulse_number(%s, %s)' %(node.labels[0],
                                                self.kwargs["threshold"]), )


class Max(Array1DimNumericBaseFilter):
    
    slug = "max"
    kwarg_names = []
    
    def apply(self, node):
        node.data = np.max(node.data)
        node.dim = None
        node.labels = ('max(%s)' %node.labels[0],)

class MaxOf(Array1DimNumericBaseFilter):

    slug = "max_of"
    kwarg_names = ["value"]

        
    def apply(self, node):
        """Returns max(data, value).

        if the data is an array,  an array is returned with each element
        having max(data[element], value) value should be a float

        """
        _value = float(self.kwargs["value"])
        if isinstance(node.data, np.ndarray):
            node.data[node.data < _value] = _value
        else:
            node.data =  np.max([node.data, _value])
        node.labels = ('max_of(%s, %s)' %(node.labels[0],
                                          self.kwargs["values"]),
                        node.labels[1])

class DimOfMaxVal(Array1DimNumericBaseFilter):
    """Returns dim at signal peak.

    """

    slug = "dim_of_max"
    kwarg_names = []
    
    def apply(self, node):
        node.data = node.dim[np.argmax(node.data)]
        node.dim = None
        node.labels = ('dim_of_max(%s)' %node.labels[0],)

class Mean(Array1DimNumericBaseFilter):

    slug = "mean"
    kwarg_names = []
    
    def apply(self, node):
        if len(node.data) > 0:
            node.data = np.mean(node.data)
        else:
            node.data = None
        node.dim = None
        node.labels = ('mean(%s)' %node.labels[0],)

class Element(Array1DimNumericBaseFilter):
    """Get an element of an array.

    The first element has index 0.
    """

    slug = "element"
    kwarg_names = ["index"]

    def apply(self, node):
        _index = int(self.kwargs["index"])
        node.data = node.data[_index]
        node.dim = None
        node.labels = ('%s[%s]' %(node.labels[0], self.kwargs["index"]),)

class PeakToPeak(Array1DimNumericBaseFilter):
    """Max(signal) - min(signal)."""

    slug = "peak_to_peak"
    kwarg_names = []

    def apply(self, node):
        node.data = max(node.data) - min(node.data)
        node.dim = None
        node.labels = ('max(%(lab)s)-min(%(lab)s)' %{'lab':node.labels[0]},)

class NSignals(Array1DimNumericBaseFilter):
    """Single trace returns 1, images return > 1

    Deprecated...
    """

    slug = "n_signals"
    kwarg_names = []
    
    def apply(self, node):
        """Single trace returns 1, images return > 1"""
        node.data = np.int16(node.data.ndim)
        node.dim = None
        node.labels = ('n_signals(%(lab)s)' %{'lab':node.labels[0]},)

########################################################################
## signal -> signal                                                   ##
########################################################################

class SlantedBaseline(Array1DimNumericBaseFilter):
    """Remove linear baseline.

    window must be an integer.

    endpoints are computed at the first (window) and last (window) samples.

    """

    slug = "slanted_baseline"
    kwarg_names = ["window"]

    
    def apply(self, node):
        _window = int(self.kwargs["window"])
        start = np.mean(node.data[:_window])
        end = np.mean(node.data[-_window:])

        dim_len = node.data.shape[0]
        norm_dim = np.arange(dim_len, dtype=float)/(dim_len-1)
        baseline = start + (end-start)*norm_dim
        node.data -= baseline
        node.labels = ('slanted_baseline({}, {})'.format(
            node.labels[0], self.kwargs["window"]))

class PrlLpn(Array1DimNumericBaseFilter):
    """prl_lpn

    TODO: only working for order = 1
    """
    slug = "prl_lpn"
    kwarg_names = ["f0", "order"]
    
    def _do_prl_lpn(self, signal, dim, f0, order):
        """This  function is required  to handle  the recursion  in prl_lpn.

        Handle  only the  signal,  not  the data  wrapper.  Also, we  assume
        arguments have already been cast to numeric types.
        """
        N = int(0.5 + 0.5/(dim[1]-dim[0])/f0)
        a = np.cumsum(signal)
        if order > 1:
            return self._do_prl_lpn(
                self._do_prl_lpn(signal, dim, f0, order-1), dim, f0, 1)
        else:
            return (a[N:]-a[:-N])/float(N)
    
    def apply(self, node):
        _f0 = float(self.kwargs["f0"])
        _order = int(self.kwargs["order"])
        node.data = self._do_prl_lpn(node.data, node.dim, _f0, _order)
        node.labels = ('prl_lpn(%s, %s, %s)' %(node.labels[0],
                                               self.kwargs["f0"],
                                               self.kwargs["order"]),)

class Resample(Array1DimNumericBaseFilter):
    slug = "resample"
    kwarg_names = ["max_samples"]
   
    def apply(self, node):
        _max_samples = int(self.kwargs["max_samples"])
        signal_length = node.data.T.shape[0]
        delta_sample = signal_length/_max_samples

        # put trailing [:max_samples] in case we get an extra one at the end
        node.data = node.data[::delta_sample][:_max_samples]
        node.dim = node.dim[::delta_sample][:_max_samples]
        node.labels = ('resample(%s, %s)' %(node.labels[0],
                                            self.kwargs["max_samples"]),)

class ResampleMinMax(Array1DimNumericBaseFilter):
    """TODO: only works for 1D array..."""
    slug = "resample_minmax"
    kwarg_names = ["n_bins"]
    
    def apply(self, node):

        _n_bins = int(self.kwargs["n_bins"])

        signal_length = node.data.T.shape[0]
        if signal_length >= 2*_n_bins:
            delta_sample = signal_length/_n_bins
            node.dim = node.dim[::delta_sample][:_n_bins]
            max_data = []
            min_data = []

            for i in range(_n_bins):
                tmp = node.data[i*delta_sample:(i+1)*delta_sample]
                max_data.append(max(tmp))
                min_data.append(min(tmp))

            node.labels = ('min', 'max',)
            node.data = np.array([min_data, max_data])

class NormDimRange(Array1DimNumericBaseFilter):
    """Reduce range of signal."""

    slug = "norm_dim_range"
    kwarg_names = ["min", "max"]
    
    def apply(self, node):
        _min = float(self.kwargs["min"])
        _max = float(self.kwargs["max"])
        min_e, max_e = int(_min*len(node.dim)), int(_max*len(node.dim))
        node.data = node.data[min_e:max_e]
        node.dim = node.dim[min_e:max_e]
        node.labels = ('normdim_range(%s, %s, %s)' %(node.labels[0],
                                                     self.kwargs["min"],
                                                     self.kwargs["max"]),)

class DimRange(Array1DimNumericBaseFilter):
    """Reduce range of signal."""

    slug = "dim_range"<commit_msg>ResampleMinMax filter now works with new data classes<commit_after>"""Filters for h1ds data

"""
from urlparse import urlparse, urlunparse
import urllib2, json
import numpy as np

excluded_filters = set()

def exclude_filter(original_class):
    """Decorator to mark subclasses of BaseFilter which aren't useable filters.

    Use, for example, with  ScalarNumericBaseFilter which is designed to
    be subclassed to form usable filters.
    
    """
    #original_class.__exclude_filter = True
    global excluded_filters
    excluded_filters.add(original_class)
    return original_class

def is_numeric(cls, obj):
    attrs = ['__add__', '__sub__', '__mul__', '__div__', '__pow__']
    # numpy string_ have these attrs, do any others?
    is_numpy_str = type(obj) in [np.string_,]
    return all(hasattr(obj, attr) for attr in attrs) and not is_numpy_str

is_string = lambda cls, d: isinstance(d, basestring)
    
def http_arg(arg):
    if arg.startswith("http://"):
        # make sure we get the JSON  format, in case the user didn't add
        # format=json
        # Split URL into [scheme, netloc, path, params, query, fragments]
        parsed_url = urlparse(arg)

        # parsed_url is an immutable ParseResult  instance, copy it to a
        # (mutable) list
        parsed_url_list = [i for i in parsed_url]

        # Now we  can update the  URL query  string to enforce  the JSON
        # format.
        parsed_url_list[4] = '&'.join([parsed_url[4], 'format=json'])

        # And here is our original URL with format=json query added
        attr_url_json = urlunparse(parsed_url_list)
        request = urllib2.Request(attr_url_json)
        response = json.loads(urllib2.urlopen(request).read())

        return response['data']
    else:
        return arg

class BaseFilterMetaclass(type):
    def __new__(cls, name, bases, dct):
        # assert  some classmethods  which  simplify the  API, so  users
        # don't  have  to  put  @classmethod in  front  of  each  filter
        # subclass
        classmethods = ["valid_dtype", ]
        for cm in classmethods:
            if dct.has_key(cm):
                dct[cm] = classmethod(dct[cm])
        return type.__new__(cls, name, bases, dct)

@exclude_filter
class BaseFilter:

    __metaclass__ = BaseFilterMetaclass

    ndim = 0
    
    def __init__(self, **kwargs):
        self.kwargs = dict((k, http_arg(v)) for k, v in kwargs.iteritems())

    @classmethod
    def valid_ndim(cls, data):
        data_ndim = data.ndim if hasattr(data, 'ndim') else 0
        return (data_ndim == cls.ndim or cls.ndim == "any")
        
    @classmethod
    def is_filterable(cls, data):
        return cls.valid_ndim(data) and cls.valid_dtype(data)

    @classmethod
    def get_slug(cls):
        return cls.slug

@exclude_filter
class ScalarNumericBaseFilter(BaseFilter):

    ndim = 0
    valid_dtype = is_numeric

@exclude_filter
class ScalarStringBaseFilter(BaseFilter):

    ndim = 0
    valid_dtype = is_string

@exclude_filter
class Array1DimNumericBaseFilter(BaseFilter):
    
    ndim = 1
    valid_dtype = is_numeric

@exclude_filter
class Array2DimNumericBaseFilter(BaseFilter):
    
    ndim = 2
    valid_dtype = is_numeric
    

def float_or_array(data):
    """Cast data to float if string, or array if list."""
    if isinstance(data, list):
        return np.array(data)
    else:
        return float(data)


binary_powers = 2**np.arange(30)

########################################################################
## signal -> scalar                                                   ##
########################################################################

class FirstPulse(Array1DimNumericBaseFilter):
    """Return value of dim when the signal is greater than threshold.

    threshold can be a number or 'mid'.
    threshold = 'mid' will use (max(signal)+min(signal))/2
    """

    
    slug = "first_pulse"
    kwarg_names = ["threshold"]
    
    def apply(self, node):
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        first_element = np.where(node.data>_threshold)[0][0]
        node.data = node.dim[first_element]
        node.dim = None
        node.labels = ('first_pulse(%s, %s)' %(node.labels[0],
                                               self.kwargs["threshold"]), )
        

class PulseWidth(Array1DimNumericBaseFilter):
    """
    pulse width...

    """
    slug = "pulse_width"
    kwarg_names = ["threshold"]

    def apply(self, node):
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        t = node.dim[node.data > _threshold]
        end1 = node.dim[(node.data[:-1]-node.data[1:])>_threshold]

        use_size = min([len(t), len(end1)])

        node.data = np.min(end1[:use_size]-t[:use_size])
        node.dim = None
        node.labels = ('pulse_width(%s, %s)' %(node.labels[0],
                                               self.kwargs["threshold"]), )
    

class PulseNumber(Array1DimNumericBaseFilter):

    slug = "pulse_number"
    kwarg_names = ["threshold"]
            
    def apply(self, node):
        """
        number of pulses...??

        """
        _threshold = self.kwargs["threshold"]
        if _threshold.lower() == 'mid':
            _threshold = (max(node.data)+min(node.data))/2
        else:
            _threshold = float(_threshold)

        t = node.dim[node.data > _threshold]
        end1 = node.dim[(node.data[:-1]-node.data[1:])>_threshold]

        # TODO: should no need to cast  this as int32, but there is some
        # bizarre problem with dtype_mapping  key... without casting the
        # result of np.min, type(node.data)  says it is numpy.int32, but
        # it   is  somehow   different   to  the   numpy.int32  in   the
        # dtype_mapping key.
        node.data = np.int32(np.min([t.shape[0], end1.shape[0]]))
        node.dim = None
        node.labels = ('pulse_number(%s, %s)' %(node.labels[0],
                                                self.kwargs["threshold"]), )


class Max(Array1DimNumericBaseFilter):
    
    slug = "max"
    kwarg_names = []
    
    def apply(self, node):
        node.data = np.max(node.data)
        node.dim = None
        node.labels = ('max(%s)' %node.labels[0],)

class MaxOf(Array1DimNumericBaseFilter):

    slug = "max_of"
    kwarg_names = ["value"]

        
    def apply(self, node):
        """Returns max(data, value).

        if the data is an array,  an array is returned with each element
        having max(data[element], value) value should be a float

        """
        _value = float(self.kwargs["value"])
        if isinstance(node.data, np.ndarray):
            node.data[node.data < _value] = _value
        else:
            node.data =  np.max([node.data, _value])
        node.labels = ('max_of(%s, %s)' %(node.labels[0],
                                          self.kwargs["values"]),
                        node.labels[1])

class DimOfMaxVal(Array1DimNumericBaseFilter):
    """Returns dim at signal peak.

    """

    slug = "dim_of_max"
    kwarg_names = []
    
    def apply(self, node):
        node.data = node.dim[np.argmax(node.data)]
        node.dim = None
        node.labels = ('dim_of_max(%s)' %node.labels[0],)

class Mean(Array1DimNumericBaseFilter):

    slug = "mean"
    kwarg_names = []
    
    def apply(self, node):
        if len(node.data) > 0:
            node.data = np.mean(node.data)
        else:
            node.data = None
        node.dim = None
        node.labels = ('mean(%s)' %node.labels[0],)

class Element(Array1DimNumericBaseFilter):
    """Get an element of an array.

    The first element has index 0.
    """

    slug = "element"
    kwarg_names = ["index"]

    def apply(self, node):
        _index = int(self.kwargs["index"])
        node.data = node.data[_index]
        node.dim = None
        node.labels = ('%s[%s]' %(node.labels[0], self.kwargs["index"]),)

class PeakToPeak(Array1DimNumericBaseFilter):
    """Max(signal) - min(signal)."""

    slug = "peak_to_peak"
    kwarg_names = []

    def apply(self, node):
        node.data = max(node.data) - min(node.data)
        node.dim = None
        node.labels = ('max(%(lab)s)-min(%(lab)s)' %{'lab':node.labels[0]},)

class NSignals(Array1DimNumericBaseFilter):
    """Single trace returns 1, images return > 1

    Deprecated...
    """

    slug = "n_signals"
    kwarg_names = []
    
    def apply(self, node):
        """Single trace returns 1, images return > 1"""
        node.data = np.int16(node.data.ndim)
        node.dim = None
        node.labels = ('n_signals(%(lab)s)' %{'lab':node.labels[0]},)

########################################################################
## signal -> signal                                                   ##
########################################################################

class SlantedBaseline(Array1DimNumericBaseFilter):
    """Remove linear baseline.

    window must be an integer.

    endpoints are computed at the first (window) and last (window) samples.

    """

    slug = "slanted_baseline"
    kwarg_names = ["window"]

    
    def apply(self, node):
        _window = int(self.kwargs["window"])
        start = np.mean(node.data[:_window])
        end = np.mean(node.data[-_window:])

        dim_len = node.data.shape[0]
        norm_dim = np.arange(dim_len, dtype=float)/(dim_len-1)
        baseline = start + (end-start)*norm_dim
        node.data -= baseline
        node.labels = ('slanted_baseline({}, {})'.format(
            node.labels[0], self.kwargs["window"]))

class PrlLpn(Array1DimNumericBaseFilter):
    """prl_lpn

    TODO: only working for order = 1
    """
    slug = "prl_lpn"
    kwarg_names = ["f0", "order"]
    
    def _do_prl_lpn(self, signal, dim, f0, order):
        """This  function is required  to handle  the recursion  in prl_lpn.

        Handle  only the  signal,  not  the data  wrapper.  Also, we  assume
        arguments have already been cast to numeric types.
        """
        N = int(0.5 + 0.5/(dim[1]-dim[0])/f0)
        a = np.cumsum(signal)
        if order > 1:
            return self._do_prl_lpn(
                self._do_prl_lpn(signal, dim, f0, order-1), dim, f0, 1)
        else:
            return (a[N:]-a[:-N])/float(N)
    
    def apply(self, node):
        _f0 = float(self.kwargs["f0"])
        _order = int(self.kwargs["order"])
        node.data = self._do_prl_lpn(node.data, node.dim, _f0, _order)
        node.labels = ('prl_lpn(%s, %s, %s)' %(node.labels[0],
                                               self.kwargs["f0"],
                                               self.kwargs["order"]),)

class Resample(Array1DimNumericBaseFilter):
    slug = "resample"
    kwarg_names = ["max_samples"]
   
    def apply(self, node):
        _max_samples = int(self.kwargs["max_samples"])
        signal_length = node.data.T.shape[0]
        delta_sample = signal_length/_max_samples

        # put trailing [:max_samples] in case we get an extra one at the end
        node.data = node.data[::delta_sample][:_max_samples]
        node.dim = node.dim[::delta_sample][:_max_samples]
        node.labels = ('resample(%s, %s)' %(node.labels[0],
                                            self.kwargs["max_samples"]),)

class ResampleMinMax(Array1DimNumericBaseFilter):
    """TODO: only works for 1D array..."""
    slug = "resample_minmax"
    kwarg_names = ["n_bins"]
    
    def apply(self, node):
        from h1ds_core.base import Data
        _n_bins = int(self.kwargs["n_bins"])
        new_data_list = []
        for data in node.data:
            signal_length = data.value.T.shape[0]
            if signal_length >= 2*_n_bins:
                delta_sample = signal_length/_n_bins
                new_dimension = data.dimension[::delta_sample][:_n_bins]
                max_data = []
                min_data = []
                for i in range(_n_bins):
                    tmp = data.value[i*delta_sample:(i+1)*delta_sample]
                    max_data.append(max(tmp))
                    min_data.append(min(tmp))
                min_metadata = data.metadata.copy()
                min_metadata['is_minmax'] = True
                min_metadata['minmax_value'] = 'min'
                min_metadata['original_name'] = data.name

                min_data = Data(name="min_resampled("+data.name+")",
                                value=np.array(min_data),
                                dimension=new_dimension,
                                value_units = data.value_units,
                                dimension_units = data.dimension_units,
                                value_dtype = data.value_dtype,
                                dimension_dtype = data.dimension_dtype,
                                metadata = min_metadata
                                )
                new_data_list.append(min_data)

                max_metadata = data.metadata.copy()
                max_metadata['is_minmax'] = True
                max_metadata['minmax_value'] = 'max'
                max_metadata['original_name'] = data.name

                max_data = Data(name="max_resampled("+data.name+")",
                                value=np.array(max_data),
                                dimension=new_dimension,
                                value_units = data.value_units,
                                dimension_units = data.dimension_units,
                                value_dtype = data.value_dtype,
                                dimension_dtype = data.dimension_dtype,
                                metadata = max_metadata
                                )
                new_data_list.append(max_data)

                #node.labels = ('min', 'max',)
                #node.data = np.array([min_data, max_data])
        node.data = new_data_list
        
class NormDimRange(Array1DimNumericBaseFilter):
    """Reduce range of signal."""

    slug = "norm_dim_range"
    kwarg_names = ["min", "max"]
    
    def apply(self, node):
        _min = float(self.kwargs["min"])
        _max = float(self.kwargs["max"])
        min_e, max_e = int(_min*len(node.dim)), int(_max*len(node.dim))
        node.data = node.data[min_e:max_e]
        node.dim = node.dim[min_e:max_e]
        node.labels = ('normdim_range(%s, %s, %s)' %(node.labels[0],
                                                     self.kwargs["min"],
                                                     self.kwargs["max"]),)

class DimRange(Array1DimNumericBaseFilter):
    """Reduce range of signal."""

    slug = "dim_range"
{'fraction_non_alphanumeric': 0.14264663599769983, 'fraction_numerical': 0.007727142035652674, 'mean_word_length': 2.1337988512219845, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 44, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1263890', 'n_tokens_mistral': 8715, 'n_tokens_neox': 8457, 'n_words': 1921}