Пример #1
0
    def _load_array_block(files, shared_array=False, antialias=True):
        Fs = 1
        dtype = 'h' if quantized else 'd'

        # start on 1st index of 0th block
        n = 1
        b_cnt = 0
        b_idx = 1

        ch_record = OE.loadContinuous(files[0], dtype=np.int16, verbose=False)
        d_len = ch_record['data'].shape[-1]
        sub_len = d_len // downsamp
        if sub_len * downsamp < d_len:
            sub_len += 1
        proc_block = shm.shared_ndarray((page_size, d_len), typecode=dtype)
        proc_block[0] = ch_record['data'].astype('d')
        if shared_array:
            saved_array = shm.shared_ndarray((len(files), sub_len),
                                             typecode=dtype)
        else:
            saved_array = np.zeros((len(files), sub_len), dtype=dtype)

        for f in files[1:]:
            ch_record = OE.loadContinuous(f, dtype=np.int16,
                                          verbose=False)  # load data
            Fs = float(ch_record['header']['sampleRate'])
            proc_block[b_idx] = ch_record['data'].astype(dtype)
            b_idx += 1
            n += 1
            if (b_idx == page_size) or (n == len(files)):
                # do dynamic range conversion and downsampling
                # on a block of data
                if not quantized:
                    proc_block *= ch_record['header']['bitVolts']
                if downsamp > 1 and antialias:
                    filtfilt(proc_block, b_lp, a_lp)
                sl = slice(b_cnt * page_size, n)
                saved_array[sl] = proc_block[:b_idx, ::downsamp]
                # update / reset block counters
                b_idx = 0
                b_cnt += 1

        del proc_block
        while gc.collect():
            pass
        return saved_array, Fs, ch_record['header']
Пример #2
0
def upsample(x, fs, appx_fs=None, r=None, interp='sinc'):
    if appx_fs is None and r is None:
        return x
    if appx_fs is not None and r is not None:
        raise ValueError('only specify new fs or resample factor, not both')

    if appx_fs is not None:
        # new sampling interval must be a multiple of old sample interval,
        # so find the closest match that is <= appx_fs
        r = int(np.floor(appx_fs / fs))

    x_up = shm.shared_ndarray(x.shape[:-1] + (r * x.shape[-1],), x.dtype.char)
    x_up[..., ::r] = x

    new_fs = fs * r

    from ecogdata.filt.blocks import BlockedSignal
    from scipy.fftpack import fft, ifft, fftfreq
    from scipy.interpolate import interp1d

    if interp == 'sinc':
        x_up *= r
        op_size = 2**16
        xb = BlockedSignal(x_up, op_size, partial_block=True)
        for block in xb.fwd():
            Xf = fft(block, axis=-1)
            freq = fftfreq(Xf.shape[-1]) * new_fs
            Xf[..., np.abs(freq) > fs/2.0] = 0
            block[:] = ifft(Xf).real
        return x_up, new_fs
    elif interp == 'linear':
        # always pick up the first and last sample when skipping by r
        op_size = r * 10000 + 1
        t = np.arange(op_size)
        xb = BlockedSignal(x_up, op_size, overlap=1, partial_block=True)
        for block in xb.fwd():
            N = block.shape[-1]
            fn = interp1d(t[:N][::r], block[..., ::r], axis=-1,
                          bounds_error=False, fill_value=0,
                          assume_sorted=True)
            block[:] = fn(t[:N])
        return x_up, new_fs

    # design a cheby-1 lowpass filter
    # wp: 0.4 * new_fs
    # ws: 0.5 * new_fs
    # design specs with halved numbers, since filtfilt will be used
    wp = 2 * 0.4 * fs / new_fs
    ws = 2 * 0.5 * fs / new_fs
    ord, wc = signal.cheb1ord(wp, ws, 0.25, 10)
    fdesign = dict(ripple=0.25, hi=0.5 * wc * new_fs, Fs=new_fs, ord=ord)
    filter_array(x_up, ftype='cheby1', inplace=True, design_kwargs=fdesign)
    x_up *= r
    return x_up, new_fs
Пример #3
0
def test_parfilt2():
    from ecogdata.parallel.split_methods import bfilter as bfilter_p
    import ecogdata.parallel.sharedmem as shm
    r = np.random.randn(20, 2000)
    b, a = butter_bp(lo=30, hi=100, Fs=1000)
    zi = lfilter_zi(b, a)

    f1, _ = lfilter(b, a, r, axis=1, zi=zi * r[:, :1])
    f2 = shm.shared_copy(r)
    f3 = shm.shared_ndarray(f2.shape, f2.dtype.char)
    # test w/o blocking
    bfilter_p(b, a, f2, out=f3, axis=1)
    assert (f1 == f3).all()
    # test w/ blocking
    f2 = shm.shared_copy(r)
    bfilter_p(b, a, f2, out=f3, bsize=234, axis=1)
    assert (f1 == f3).all()
Пример #4
0
    def get_output_array(self, slicer, only_shape=False):
        """
        Allocate an array for read outs (or just the output shape).

        Parameters
        ----------
        slicer: slice
            Read slice
        only_shape: bool
            If True, just return the output shape

        Returns
        -------
        output_array: ndarray
            Allocated array for output (unless only_shape is given).

        """
        # Currently this is used to
        # 1) check a slice size
        # 2) create an output array for slice caching
        # **NOTE** that the output needs to respect `transpose_state`
        slicer = _abs_slicer(slicer, self.shape)
        # patch inter-operability from 2.10 to 3.x
        if h5py.version.version_tuple.major >= 3:
            _null_dataset = None
            shape_attr = 'array_shape'
        else:
            _null_dataset = 0
            shape_attr = 'mshape'

        selection = select(self.shape, slicer, _null_dataset)
        out_shape = getattr(selection, shape_attr)
        # if the output will be transposed, then pre-create the array in the right order
        if self._transpose_state:
            out_shape = out_shape[::-1]
        if only_shape:
            return out_shape
        typecode = self.dtype.char
        if self._read_output is None:
            if self._shared_mem_read:
                return shm.shared_ndarray(out_shape, typecode)
            else:
                return np.empty(out_shape, typecode)
        else:
            return self._read_output
Пример #5
0
def _convert(rec,
             from_unit,
             to_unit,
             inverted=False,
             tfs=(),
             Rct=None,
             Cdl=None,
             Rs=None,
             prewarp=False,
             **sm_kwargs):
    from_type = from_unit[-1]
    to_type = to_unit[-1]

    def _override_tf(b, a):
        b, a = signal.normalize(b, a)
        Rs_, Rct_, Cdl_ = tf_to_circuit(b, a)
        b, a = circuit_to_tf((Rs if Rs else Rs_), (Rct if Rct else Rct_),
                             (Cdl if Cdl else Cdl_))
        return b, a

    if not len(tfs):
        from ecogdata.expconfig import params
        session = rec.name.split('.')[0]
        # backwards compatibility
        session = session.split('/')[-1]
        if session in _transform_lookup:
            # XXX: ideally should find the expected unit scale of the
            # transforms in the transform Bunch -- for now assume pico-scale
            to_scale = convert_scale(1, from_unit, 'p' + from_type)
            from_scale = convert_scale(1, 'p' + to_type, to_unit)
            stash_path = pt.sep.join(
                [params.stash_path, 'devices', 'impedance_transfer'])
            transforms = pt.join(stash_path, _transform_lookup[session])
            tfs = load_bunch(transforms, '/')
        else:
            # do an analog integrator --
            # will be converted to 1 + z / (1 - z) later
            # do this on pico scale also (?)
            to_scale = convert_scale(1, from_unit, 'p' + from_type)
            from_scale = convert_scale(1, 'p' + to_type, to_unit)
            tfs = Bunch(aa=np.array([1, 0]), bb=np.array([0, 1]))
    else:
        to_scale = convert_scale(1, from_unit, 'p' + from_type)
        from_scale = convert_scale(1, 'p' + to_type, to_unit)

    if tfs.aa.ndim > 1:
        from ecogdata.filt.time import bfilter
        conv = rec.deepcopy()
        cmap = conv.chan_map
        bb, aa = smooth_transfer_functions(tfs.bb.T, tfs.aa.T, **sm_kwargs)
        for n, ij in enumerate(zip(*cmap.to_mat())):
            b = bb[ij]
            a = aa[ij]
            b, a = _override_tf(b, a)
            if prewarp:
                T = conv.Fs**-1
                z, p, k = signal.tf2zpk(b, a)
                z = 2 / T * np.tan(z * T / 2)
                p = 2 / T * np.tan(p * T / 2)
                b, a = signal.zpk2tf(z, p, k)
            if inverted:
                zb, za = signal.bilinear(a, b, fs=conv.Fs)
            else:
                zb, za = signal.bilinear(b, a, fs=conv.Fs)
            bfilter(zb, za, conv.data[n], bsize=10000)
    else:
        from ecogdata.parallel.split_methods import bfilter
        # avoid needless copy of data array
        rec_data = rec.pop('data')
        conv = rec.deepcopy()
        rec.data = rec_data
        conv_data = shm.shared_ndarray(rec_data.shape, rec_data.dtype.char)
        conv_data[:] = rec_data
        b = tfs.bb
        a = tfs.aa
        if prewarp:
            T = conv.Fs**-1
            z, p, k = signal.tf2zpk(b, a)
            z = 2 / T * np.tan(z * T / 2)
            p = 2 / T * np.tan(p * T / 2)
            b, a = signal.zpk2tf(z, p, k)
        if inverted:
            zb, za = signal.bilinear(a, b, fs=conv.Fs)
        else:
            zb, za = signal.bilinear(b, a, fs=conv.Fs)
        bfilter(zb, za, conv_data, bsize=10000)
        conv.data = conv_data

    conv.data *= (from_scale * to_scale)
    conv.units = to_unit
    return conv
Пример #6
0
def filter_array(
        arr, ftype='butterworth', inplace=True, out=None, block_filter='parallel',
        design_kwargs=dict(), filt_kwargs=dict()
):
    """
    Filter an ND array timeseries on the last dimension. For computational
    efficiency, the timeseries are blocked into partitions (10000 points
    by default) and split over multiple threads (not supported on Windoze).

    Parameters
    ----------
    arr: ndarray
        Timeseries in the last dimension (can be 1D).
    ftype: str
        Filter type to design.
    inplace: bool
        If True, then arr must be a shared memory array. Otherwise a
        shared copy will be made from the input. This is a shortcut for using "out=arr".
    out: ndarray
        If not None, place filter output here (if inplace is specified, any output array is ignored).
    block_filter: str or callable
        Specify the run-time block filter to apply. Can be "parallel" or
        "serial", or can be a callable that follows the basic signature of
        `ecogdata.filt.time.block_filter.bfilter`.
    design_kwargs: dict
        Design parameters for the filter (e.g. lo, hi, Fs, ord)
    filt_kwargs: dict
        Processing parameters (e.g. filtfilt, bsize)

    Returns
    -------
    arr_f: ndarray
        Filtered timeseries, same shape as input.

    """
    b, a = _get_poles_zeros(ftype, **design_kwargs)
    check_shm = False
    if isinstance(block_filter, str):
        if block_filter.lower() == 'parallel':
            from ecogdata.parallel.split_methods import bfilter
            block_filter = bfilter
            check_shm = True
        elif block_filter.lower() == 'serial':
            from .blocked_filter import bfilter
            block_filter = bfilter
        else:
            raise ValueError('Block filter type not known: {}'.format(block_filter))
    if not callable(block_filter):
        raise ValueError('Provided block filter is not callable: {}'.format(block_filter))
    def_args = get_default_args(block_filter)
    # Set some defaults and then update with filt_kwargs
    def_args['bsize'] = 10000
    def_args['filtfilt'] = True
    def_args.update(filt_kwargs)
    def_args['out'] = out
    if inplace:
        # enforce that def_args['out'] is arr?
        def_args['out'] = arr
        block_filter(b, a, arr, **def_args)
        return arr
    else:
        # still use bfilter for memory efficiency
        # Work in progress to use this syntax
        # use_shm = hasattr(block_filter, 'uses_parallel') and block_filter.uses_parallel(b, a, arr)
        if check_shm:
            # signal shared memory usage if check_shm remains true
            try:
                check_shm = block_filter(b, a, arr, check_parallel=True, **def_args)
            except TypeError:
                check_shm = False
        if def_args['out'] is None:
            if check_shm:
                arr_f = shm.shared_ndarray(arr.shape, arr.dtype.char)
            else:
                arr_f = np.empty_like(arr)
            def_args['out'] = arr_f
        block_filter(b, a, arr, **def_args)
        return def_args['out']
Пример #7
0
def load_mux(exp_path,
             test,
             electrode,
             headstage,
             ni_daq_variant='',
             mux_connectors=(),
             bandpass=(),
             notches=(),
             trigger=0,
             bnc=(),
             mux_notches=(),
             save=False,
             snip_transient=True,
             units='uV'):
    """
    Load data from the MUX style headstage acquisition. Data is expected 
    to be organized along columns corresponding to the MUX units. The
    columns following sensor data columns are assumed to be a stimulus
    trigger followed by other BNC channels.

    The electrode information must be provided to determine the
    arrangement of recorded and grounded channels within the sensor
    data column.
    
    This preprocessing routine returns a Bunch container with the
    following items
    
    dset.data : nchan x ntime data array
    dset.ground_chans : m x ntime data array of grounded ADC channels
    dset.bnc : un-MUXed readout of the BNC channel(s)
    dset.chan_map : the channel-to-electrode mapping vector
    dset.Fs : sampling frequency
    dset.name : path + expID for the given data set
    dset.bandpass : bandpass filtering applied (if any)
    dset.trig : the logical value of the trigger channel (at MUX'd Fs)

    * If saving, then a table of the Bunch is written.
    * If snip_transient, then advance the timeseries past the bandpass
      filtering onset transient.
    
    """

    try:
        dset = try_saved(exp_path, test, bandpass)
        return dset
    except DataPathError:
        pass

    # say no to shared memory since it's created later on in this method
    loaded = rawload_mux(exp_path,
                         test,
                         headstage,
                         daq_variant=ni_daq_variant,
                         shm=False)
    channels, Fs, dshape, info = loaded
    nrow, ncol_data = dshape
    if channels.shape[0] >= nrow * ncol_data:
        ncol = channels.shape[0] // nrow
        channels = channels.reshape(ncol, nrow, -1)
    else:
        ncol = channels.shape[0]
        channels.shape = (ncol, -1, nrow)
        channels = channels.transpose(0, 2, 1)

    ## Grab BNC data

    if bnc:
        bnc_chans = [ncol_data + int(b) for b in bnc]
        bnc = np.zeros((len(bnc), nrow * channels.shape[-1]))
        for bc, col in zip(bnc, bnc_chans):
            bc[:] = channels[col].transpose().ravel()
        bnc = bnc.squeeze()

    try:
        trig_chans = channels[ncol_data + trigger].copy()
        pos_edge, trig = process_trigger(trig_chans)
    except IndexError:
        pos_edge = ()
        trig = ()

    ## Realize channel mapping

    chan_map, disconnected, reference = epins.get_electrode_map(
        electrode, connectors=mux_connectors)

    ## Data channels

    # if any pre-processing of multiplexed channels, do it here first
    if mux_notches:

        mux_chans = shm.shared_ndarray((ncol_data, channels.shape[-1], nrow))
        mux_chans[:] = channels[:ncol_data].transpose(0, 2, 1)
        mux_chans.shape = (ncol_data, -1)
        ft.notch_all(mux_chans, Fs, lines=mux_notches, filtfilt=True)
        mux_chans.shape = (ncol_data, channels.shape[-1], nrow)
        channels[:ncol_data] = mux_chans.transpose(0, 2, 1)
        del mux_chans

    rec_chans = channels[:ncol_data].reshape(nrow * ncol_data, -1)

    if units.lower() != 'v':
        convert_scale(rec_chans, 'v', units)

    g_chans = disconnected
    r_chans = reference
    d_chans = np.setdiff1d(np.arange(ncol_data * nrow),
                           np.union1d(g_chans, r_chans))

    data_chans = shm.shared_copy(rec_chans[d_chans])
    if len(g_chans):
        gnd_data = rec_chans[g_chans]
    else:
        gnd_data = ()
    if len(r_chans):
        ref_data = rec_chans[r_chans]
    else:
        ref_data = ()
    del rec_chans
    del channels

    # do highpass filtering for stationarity
    if bandpass:
        # manually remove DC from channels before filtering
        if bandpass[0] > 0:
            data_chans -= data_chans.mean(1)[:, None]
            # do a high order highpass to really crush the crappy
            # low frequency noise
            b, a = ft.butter_bp(lo=bandpass[0], Fs=Fs, ord=5)
            # b, a = ft.cheby1_bp(0.5, lo=bandpass[0], Fs=Fs, ord=5)
        else:
            b = [1]
            a = [1]
        if bandpass[1] > 0:
            b_lp, a_lp = ft.butter_bp(hi=bandpass[1], Fs=Fs, ord=3)
            b = np.convolve(b, b_lp)
            a = np.convolve(a, a_lp)

        filtfilt(data_chans, b, a)
        if len(ref_data):
            with parallel_controller(False):
                ref_data = np.atleast_2d(ref_data)
                filtfilt(ref_data, b, a)
                ref_data = ref_data.squeeze()

    if notches:
        ft.notch_all(data_chans,
                     Fs,
                     lines=notches,
                     inplace=True,
                     filtfilt=True)
        if len(ref_data):
            with parallel_controller(False):
                ref_data = np.atleast_2d(ref_data)
                ft.notch_all(ref_data,
                             Fs,
                             lines=notches,
                             inplace=True,
                             filtfilt=True)
                ref_data = ref_data.squeeze()

    if snip_transient:
        if isinstance(snip_transient, bool):
            snip_len = int(Fs * 5)
        else:
            snip_len = int(Fs * snip_transient)
        if len(pos_edge):
            snip_len = max(0, min(snip_len, pos_edge[0] - int(Fs)))
            pos_edge = pos_edge - snip_len
            trig = trig[..., snip_len:].copy()
        data_chans = data_chans[..., snip_len:].copy()
        gnd_data = gnd_data[..., snip_len:].copy()
        if len(ref_data):
            ref_data = ref_data[..., snip_len:].copy()
        if len(bnc):
            bnc = bnc[..., snip_len * nrow:].copy()

    # do blockwise detrending for stationarity
    ## detrend_window = int(round(0.750*Fs))
    ## ft.bdetrend(data_chans, bsize=detrend_window, type='linear', axis=-1)
    dset = ut.Bunch()
    dset.pos_edge = pos_edge
    dset.data = data_chans
    dset.ground_chans = gnd_data
    dset.ref_chans = ref_data
    dset.bnc = bnc
    dset.chan_map = chan_map
    dset.Fs = Fs
    #dset.name = os.path.join(exp_path, test)
    dset.bandpass = bandpass
    dset.trig = trig
    dset.transient_snipped = snip_transient
    dset.units = units
    dset.notches = notches
    dset.info = info

    if save:
        hf = os.path.join(exp_path, test + '_proc.h5')
        save_bunch(hf, '/', dset, mode='w')
    return dset
Пример #8
0
    def map_raw_data(self, data_file, open_mode, electrode_chans, ground_chans,
                     ref_chans, downsample_ratio):
        """
        Unlike the parent method, this method will directly load and downsample .continuous files to in-memory arrays
        without creating an intermediary MappedSource.

        Parameters
        ----------
        data_file: str
            Open Ephys data path or HDF5 file
        open_mode:
            File mode (for HDF5 only)
        electrode_chans: sequence
        ground_chans: sequence
        ref_chans: sequence
        downsample_ratio: int

        Returns
        -------
        datasource: ElectrodeArraySource
            Datasource for electrodes.
        ground_chans: ElectrodeArraySource
            Datasource for ground channels. May be an empty list
        ref_chans: ElectrodeArraySource:
            Datasource for reference channels. May be an empty list

        """

        # downsample_ratio is 1 if a pre-computed file exists or if a full resolution HDF5 needs to be created here.
        # In these cases, revert to parent method
        if downsample_ratio == 1:
            if os.path.isdir(data_file):
                # mapped_file = data_file + '.h5'
                with TempFilePool(mode='ab') as mf:
                    mapped_file = mf
                print(
                    'Take note!! Creating full resolution map file {}'.format(
                        mapped_file))
                # Get a little tricky and see if this source should be writeable. If no, then leave it quantized with
                # a scaling factor. If yes, then convert the source to microvolts.
                quantized = not (self.ensure_writeable or self.bandpass
                                 or self.notches)
                hdf5_open_ephys_channels(self.experiment_path,
                                         self.recording,
                                         str(mapped_file),
                                         data_chans='all',
                                         quantized=quantized)
                if quantized:
                    open_mode = 'r'
                else:
                    open_mode = 'r+'
                    self.units_scale = convert_scale(1, 'uv', self.units)
                # Note: pass file "name" directly as a TempFilePool object so that file-closing can be registered
                # downstream!!
                data_file = mapped_file
            else:
                # the data_file is possibly an HDF file that should be treated as a read-only source
                with h5py.File(data_file, 'r') as fw:
                    if fw['chdata'].dtype in np.sctypes['float']:
                        self.units_scale = convert_scale(1, 'uv', self.units)
                open_mode = 'r'
            print('Calling super to map/load {} in mode {} scaling units {}'.
                  format(data_file, open_mode, self.units_scale))
            return super(OpenEphysLoader,
                         self).map_raw_data(data_file, open_mode,
                                            electrode_chans, ground_chans,
                                            ref_chans, downsample_ratio)

        # If downsample_ratio > 1 then we are downsampling straight to memory. Invoke a custom loader that handles
        # continuous files
        loaded = load_open_ephys_channels(self.experiment_path,
                                          self.recording,
                                          shared_array=False,
                                          downsamp=downsample_ratio,
                                          save_downsamp=False,
                                          use_stored=False,
                                          quantized=False)
        chdata = loaded.chdata
        electrode_data = shm.shared_ndarray(
            (len(electrode_chans), chdata.shape[1]), chdata.dtype.char)
        np.take(chdata, electrode_chans, axis=0, out=electrode_data)
        datasource = PlainArraySource(electrode_data,
                                      use_shared_mem=False,
                                      adc=loaded.adc,
                                      aux=loaded.aux)
        if ground_chans:
            ground_chans = PlainArraySource(chdata[ground_chans],
                                            use_shared_mem=False)
        if ref_chans:
            ref_chans = PlainArraySource(chdata[ref_chans],
                                         use_shared_mem=False)
        return datasource, ground_chans, ref_chans
Пример #9
0
    def mirror(self, new_rate_ratio=None, writeable=True, mapped=True, channel_compatible=False, filename='',
               copy='', new_sources=dict(), **map_args):
        # TODO: channel order permutation in mirrored source
        """
        Create an empty ElectrodeDataSource based on the current source, possibly with a new sampling rate and new
        access permissions.

        Parameters
        ----------
        new_rate_ratio: int or None
            Ratio of old to new sample rate for the mirrored array (> 1).
        writeable: bool
            Make any new MappedSource arrays writeable. This implies 1) datatype casting to floats, and 2) there is
            no more units conversion on the primary array.
        mapped: bool
            If False, mirror to a PlainArraySource (in memory). Else mirror into a new MappedSource.
        channel_compatible: bool
            If True, preserve the same number of raw data channels in a MappedSource. Otherwise, reduce the channels
            to just the set of active channels. Currently not supported for non-mapped mirrors.
        filename: str
            Name of the new MappedSource. If empty, use a self-destroying temporary file.
        copy: str
            Code whether to copy any arrays, which is only valid when new_rate_ratio is None or 1. 'aligned' copies
            aligned arrays. 'electrode' copies electrode data: only valid if channel_compatible is False.
            'all' copies all arrays. By default, nothing is copied.
        new_sources: dict
            If mapped=False, then pre-allocated arrays can be provided for each source (i.e. 'data_buffer' and any
            aligned arrays).
        map_args: dict
            Any other MappedSource arguments

        Returns
        -------
        datasource: ElectrodeDataSource subtype

        """

        T = self.shape[1]
        if new_rate_ratio:
            T = calc_new_samples(T, new_rate_ratio)

        # if casting to floating point, check for preferred precision
        fp_precision = load_params().floating_point.lower()
        fp_dtype = 'f' if fp_precision == 'single' else 'd'
        fp_dtype = np.dtype(fp_dtype)

        # unpack copy mode
        copy_electrodes_coded = copy.lower() in ('all', 'electrode')
        copy_aligned_coded = copy.lower() in ('all', 'aligned')
        diff_rate = T != self.shape[1]
        if copy_electrodes_coded:
            if diff_rate or channel_compatible:
                copy_electrodes = False
                print('Not copying electrode channels. Diff rate '
                      '({}) or indirect channel map ({})'.format(diff_rate, channel_compatible))
            else:
                copy_electrodes = True
        else:
            copy_electrodes = False
        if copy_aligned_coded:
            if diff_rate:
                copy_aligned = False
                print('Not copying aligned arrays: different sample rate')
            else:
                copy_aligned = True
        else:
            copy_aligned = False

        if mapped:
            if channel_compatible:
                electrode_channels = self._electrode_channels
                C = self.data_buffer.shape[1] if self._transpose else self.data_buffer.shape[0]
                channel_mask = self.binary_channel_mask
            else:
                C = self.shape[0]
                electrode_channels = None
                channel_mask = None
            if writeable:
                new_dtype = fp_dtype
                reopen_mode = 'r+'
                units_scale = None
            else:
                new_dtype = self.data_buffer.map_dtype
                reopen_mode = 'r'
                units_scale = self.data_buffer.units_scale
            tempfile = not filename
            if tempfile:
                with TempFilePool(mode='ab') as f:
                    # punt on the unlink-on-close issue for now with "delete=False"
                    # f.file.close()
                    filename = f
            # open as string just in case its a TempFilePool
            # TODO: (need to figure out how to make it work seamless as a string)
            with h5py.File(str(filename), 'w', libver='latest') as fw:
                # Create all new datasets as non-transposed
                fw.create_dataset(self._electrode_field, shape=(C, T), dtype=new_dtype, chunks=True)
                if copy_electrodes:
                    for block, sl in self.iter_blocks(return_slice=True, sharedmem=False):
                        fw[self._electrode_field][sl] = block
                for name in self.aligned_arrays:
                    arr = getattr(self, name)
                    if len(arr.shape) > 1:
                        dims = (arr.shape[1], T) if self._transpose else (arr.shape[0], T)
                    # is this correct ???
                    dtype = fp_dtype if writeable else arr.dtype
                    fw.create_dataset(name, shape=dims, dtype=dtype, chunks=True)
                    if copy_aligned:
                        aligned = getattr(self, name)[:]
                        fw[name][:] = aligned.T if self._transpose else aligned
                # set single write, multiple read mode AFTER datasets are created
                # Skip for now -- SWMR issue pending
                # fw.swmr_mode = True
            print(str(filename), 'reopen mode', reopen_mode)
            f_mapped = h5py.File(str(filename), reopen_mode, libver='latest')
            # Skip for now -- SWMR issue pending
            # if writeable:
            #     f_mapped.swmr_mode = True
            if isinstance(filename, TempFilePool):
                filename.register_to_close(f_mapped)
            # If the original buffer's unit scaling is 1.0 then we should keep real slices on
            real_slices = units_scale == 1.0
            return MappedSource.from_hdf_sources(f_mapped, self._electrode_field, units_scale=units_scale,
                                                 real_slices=real_slices, aligned_arrays=self.aligned_arrays,
                                                 transpose=False, electrode_channels=electrode_channels,
                                                 channel_mask=channel_mask, **map_args)
            # return MappedSource(f_mapped, self._electrode_field, electrode_channels=electrode_channels,
            #                     channel_mask=channel_mask, aligned_arrays=self.aligned_arrays,
            #                     transpose=False, **map_args)
        else:
            if channel_compatible:
                print('RAM mirrors are not channel compatible--use mapped=True')
            self._check_slice_size(np.s_[:, :T])
            C = self.shape[0]
            if 'data_buffer' in new_sources:
                new_source = new_sources['data_buffer']
            else:
                new_source = shm.shared_ndarray((C, T), fp_dtype.char)
            if copy_electrodes:
                for block, sl in self.iter_blocks(return_slice=True, sharedmem=False):
                    new_source[sl] = block
            # Kind of tricky with aligned fields -- assume that transpose means the same thing for them?
            # But also un-transpose them on this mirroring step
            aligned_arrays = dict()
            for name in self.aligned_arrays:
                arr = getattr(self, name)
                if len(arr.shape) > 1:
                    dims = (arr.shape[1], T) if self._transpose else (arr.shape[0], T)
                else:
                    dims = (T,)
                if name in new_sources:
                    aligned_arrays[name] = new_sources[name]
                else:
                    aligned_arrays[name] = shm.shared_ndarray(dims, fp_dtype.char)
                if copy_aligned:
                    aligned = getattr(self, name)[:]
                    aligned_arrays[name][:] = aligned.T if self._transpose else aligned
            return PlainArraySource(new_source, **aligned_arrays)
Пример #10
0
def load_afe(exp_pth,
             test,
             electrode,
             n_data,
             range_code,
             cycle_rate,
             units='nA',
             bandpass=(),
             save=True,
             notches=(),
             snip_transient=True,
             **extra):

    h5 = tables.open_file(os.path.join(exp_pth, test + '.h5'))

    data = h5.root.data[:]
    Fs = h5.root.Fs[0, 0]

    if data.shape[1] > n_data:
        trig_chans = data[:, n_data:]
        trig = np.any(trig_chans > 1, axis=1).astype('i')
        pos_edge = np.where(np.diff(trig) > 0)[0] + 1
    else:
        trig = None
        pos_edge = ()

    data_chans = data[:, :n_data].T.copy(order='C')

    # convert dynamic range to charge or current
    if 'v' not in units.lower():
        pico_coulombs = range_lookup[range_code]
        convert_dyn_range(data_chans, (-1.4, 1.4),
                          pico_coulombs,
                          out=data_chans)
        if 'a' in units.lower():
            # To convert to amps, need to divide coulombs by the
            # integration period. This is found approximately by
            # finding out how many cycles in a scan period were spent
            # integrating. A scan period is now hard coded to be 500
            # cycles. The cycling rate is given as an argument.
            # The integration period for channel i should be:
            # 500 - 2*(n_data - i)
            # That is, the 1st channel is clocked out over two cycles
            # immediately after the integration period. Meanwhile other
            # channels still acquire until they are clocked out.
            n_cycles = 500
            #i_cycles = n_cycles - 2*(n_data - np.arange(n_data))
            i_cycles = n_cycles - 2 * n_data
            i_period = i_cycles / cycle_rate
            data_chans /= i_period  #[:,None]
            convert_scale(data_chans, 'pa', units)
    elif units.lower() != 'v':
        convert_scale(data, 'v', units)

    # only use this one electrode (for now)
    chan_map, disconnected = epins.get_electrode_map('psv_61_afe')[:2]
    connected = np.setdiff1d(np.arange(n_data), disconnected)
    disconnected = disconnected[disconnected < n_data]

    chan_map = chan_map.subset(list(range(len(connected))))

    data = shm.shared_ndarray((len(connected), data_chans.shape[-1]))
    data[:, :] = data_chans[connected]
    ground_chans = data_chans[disconnected].copy()
    del data_chans

    if bandpass:
        # do a little extra to kill DC
        data -= data.mean(axis=1)[:, None]
        (b, a) = ft.butter_bp(lo=bandpass[0], hi=bandpass[1], Fs=Fs)
        filtfilt(data, b, a)
    if notches:
        for freq in notches:
            (b, a) = ft.notch(freq, Fs=Fs, ftype='cheby2')
            filtfilt(data, b, a)

    ## detrend_window = int(round(0.750*Fs))
    ## ft.bdetrend(data, bsize=detrend_window, type='linear', axis=-1)
    else:
        data -= data.mean(axis=1)[:, None]

    if snip_transient:
        snip_len = min(10000, pos_edge[0]) if len(pos_edge) else 10000
        data = data[..., snip_len:].copy()
        ground_chans = ground_chans[..., snip_len:].copy()
        if len(pos_edge):
            trig = trig[..., snip_len:]
            pos_edge -= snip_len

    dset = Bunch()

    dset.data = data
    dset.ground_chans = ground_chans
    dset.chan_map = chan_map
    dset.Fs = Fs
    dset.pos_edge = pos_edge
    dset.bandpass = bandpass
    dset.trig = trig
    dset.transient_snipped = snip_transient
    dset.units = units
    dset.notches = notches
    return dset
Пример #11
0
def load_afe_aug21(exp_pth,
                   test,
                   electrode,
                   n_data,
                   range_code,
                   cycle_rate,
                   units='nA',
                   bandpass=(),
                   save=False,
                   notches=(),
                   snip_transient=True,
                   **extra):
    h5 = tables.open_file(os.path.join(exp_pth, test + '.h5'))
    Fs = h5.root.Fs.read()

    n_row = h5.root.numRow.read()
    n_data_col = h5.root.numCol.read()
    n_col = h5.root.numChan.read()
    # data rows are 4:67 -- acquiring AFE chans 31:0 and 63:32 on two columns
    data_rows = slice(4, 67, 2)

    full_data = h5.root.data[:].reshape(n_col, n_row, -1)

    #data_chans = shm.shared_ndarray( (32*n_data_col, full_data.shape[-1]) )
    data_chans = full_data[:n_data_col,
                           data_rows].reshape(-1, full_data.shape[-1])
    trig_chans = full_data[-10:, -1]
    del full_data

    trig = np.any(trig_chans > 1, axis=0).astype('i')
    pos_edge = np.where(np.diff(trig) > 0)[0] + 1

    # convert dynamic range to charge or current
    if 'v' not in units.lower():
        pico_coulombs = range_lookup[range_code]
        convert_dyn_range(data_chans, (-1.4, 1.4),
                          pico_coulombs,
                          out=data_chans)
        if 'a' in units.lower():
            i_period = 563e-6
            data_chans /= i_period
            convert_scale(data_chans, 'pa', units)
    elif units.lower() != 'v':
        convert_scale(data_chans, 'v', units)

    ## # only use this one electrode (for now)
    chan_map, disconnected = epins.get_electrode_map('psv_61_afe')[:2]
    connected = np.setdiff1d(np.arange(n_data), disconnected)
    disconnected = disconnected[disconnected < n_data]

    data = shm.shared_ndarray((len(connected), data_chans.shape[-1]))
    data[:, :] = data_chans[connected]
    ground_chans = data_chans[disconnected]

    del data_chans
    # do a little extra to kill DC
    data -= data.mean(axis=1)[:, None]
    if bandpass:
        (b, a) = ft.butter_bp(lo=bandpass[0], hi=bandpass[1], Fs=Fs)
        filtfilt(data, b, a)
    if notches:
        ft.notch_all(data, Fs, lines=notches, inplace=True, filtfilt=True)

    if snip_transient:
        snip_len = min(10000, pos_edge[0]) if len(pos_edge) else 10000
        data = data[..., snip_len:].copy()
        if ground_chans is not None:
            ground_chans = ground_chans[..., snip_len:].copy()
        if len(pos_edge):
            trig = trig[..., snip_len:]
            pos_edge -= snip_len

    dset = ut.Bunch()

    dset.data = data
    dset.ground_chans = ground_chans
    dset.chan_map = chan_map
    dset.Fs = Fs
    dset.pos_edge = pos_edge
    dset.bandpass = bandpass
    dset.trig = trig
    dset.transient_snipped = snip_transient
    dset.units = units
    dset.notches = notches
    return dset
Пример #12
0
def traverse_table(f,
                   path='/',
                   load=True,
                   scan=False,
                   shared_paths=(),
                   skip_stale_pickles=True,
                   attempt_reload=False):
    # Walk nodes and stuff arrays into the bunch.
    # If we encounter a group, then loop back into this method
    from ecogdata.devices.load.file2data import FileLoader
    if not isinstance(f, tables.file.File):
        if load or scan:
            # If scan is True, load should be forced False here
            load = not scan
            with closing(tables.open_file(f, mode='r')) as f:
                return traverse_table(f,
                                      path=path,
                                      load=load,
                                      scan=scan,
                                      shared_paths=shared_paths,
                                      skip_stale_pickles=skip_stale_pickles,
                                      attempt_reload=attempt_reload)
        else:
            f = tables.open_file(f, mode='r')
            try:
                return traverse_table(f,
                                      path=path,
                                      load=load,
                                      scan=scan,
                                      shared_paths=shared_paths,
                                      skip_stale_pickles=skip_stale_pickles,
                                      attempt_reload=attempt_reload)
            except:
                f.close()
                raise
    if load or scan:
        gbunch = Bunch()
    else:
        gbunch = HDF5Bunch(f)
    (p, g) = os.path.split(path)
    if g == '':
        g = p
    nlist = f.list_nodes(path)
    #for n in f.walk_nodes(where=path):
    for n in nlist:
        if isinstance(n, tables.Array):
            if load:
                if n.dtype.char == 'O':
                    arr = 'Not loaded: ' + n.name
                elif '/'.join([path, n.name]) in shared_paths:
                    arr = shm.shared_ndarray(n.shape)
                    arr[:] = n.read()
                else:
                    arr = n.read()
                if isinstance(arr, np.ndarray) and n.shape:
                    if arr.shape == (1, 1):
                        arr = arr[0, 0]
                        if arr == 0:
                            arr = None
                    else:
                        arr = arr.squeeze()
            else:
                arr = n
            gbunch[n.name] = arr
        elif isinstance(n, tables.VLArray):
            if load:
                try:
                    obj = n.read()[0]
                except (ModuleNotFoundError, PickleError, PicklingError):
                    if not skip_stale_pickles:
                        raise
                    gbunch[n.name] = 'unloadable pickle'
                    continue
                # if it's a generic Bunch Pickle, then update the bunch
                if n.name == 'b_pickle':
                    gbunch.update(obj)
                else:
                    gbunch[n.name] = obj
            else:
                # ignore the empty pickle
                if n.name == 'b_pickle' and n.size_in_memory > 32:
                    gbunch[n.name] = 'unloaded pickle'
        elif isinstance(n, tables.Group):
            gname = n._v_name
            # walk_nodes() includes the current group:
            # don't try to descend into this node!
            if gname == g:
                continue
            if gname == '#refs#':
                continue
            subbunch = traverse_table(f,
                                      path='/'.join([path, gname]),
                                      load=load,
                                      scan=scan,
                                      shared_paths=shared_paths,
                                      skip_stale_pickles=skip_stale_pickles,
                                      attempt_reload=attempt_reload)
            gbunch[gname] = subbunch

        else:
            gbunch[n.name] = 'Not Loaded!'

    this_node = f.get_node(path)
    for attr in this_node._v_attrs._f_list():
        gbunch[attr] = this_node._v_attrs[attr]

    loaders = [v for v in gbunch.values() if isinstance(v, FileLoader)]
    if attempt_reload and loaders:
        for loader in loaders:
            print('Attempting load from {}'.format(loader.primary_data_file))
            dataset = loader.create_dataset()
            new_keys = set(dataset.keys()) - set(gbunch.keys())
            for k in new_keys:
                gbunch[k] = dataset.pop(k)
    return gbunch