Exemple #1
0
 def test_empty(self):
     dummy = AnalogData()
     assert len(dummy.cfg) == 0
     assert dummy.dimord == None
     for attr in ["channel", "data", "hdr", "sampleinfo", "trialinfo"]:
         assert getattr(dummy, attr) is None
     with pytest.raises(SPYTypeError):
         AnalogData({})
Exemple #2
0
 def test_filename(self):
     # ensure we're salting sufficiently to create at least `numf`
     # distinct pseudo-random filenames in `__storage__`
     numf = 1000
     dummy = AnalogData()
     fnames = []
     for k in range(numf):
         fnames.append(dummy._gen_filename())
     assert np.unique(fnames).size == numf
Exemple #3
0
    def test_dataselection(self):
        dummy = AnalogData(data=self.data,
                           trialdefinition=self.trl,
                           samplerate=self.samplerate)
        trialSelections = [
            "all",  # enforce below selections in all trials of `dummy`
            [3, 1]  # minimally unordered
        ]
        chanSelections = [
            ["channel03", "channel01", "channel01",
             "channel02"],  # string selection w/repetition + unordered
            [4, 2, 2, 5, 5],  # repetition + unorderd
            range(5, 8),  # narrow range
            slice(-2, None)  # negative-start slice
        ]
        toiSelections = [
            "all",  # non-type-conform string
            [0.6],  # single inexact match
            [-0.2, 0.6, 0.9, 1.1, 1.3, 1.6, 1.8, 2.2, 2.45,
             3.]  # unordered, inexact, repetions
        ]
        toilimSelections = [
            [0.5, 1.5],  # regular range
            [1.5, 2.0],  # minimal range (just two-time points)
            [1.0, np.inf]  # unbounded from above
        ]
        timeSelections = list(zip(["toi"] * len(toiSelections), toiSelections)) \
            + list(zip(["toilim"] * len(toilimSelections), toilimSelections))

        idx = [slice(None)] * len(dummy.dimord)
        timeIdx = dummy.dimord.index("time")
        chanIdx = dummy.dimord.index("channel")

        for trialSel in trialSelections:
            for chanSel in chanSelections:
                for timeSel in timeSelections:
                    kwdict = {}
                    kwdict["trials"] = trialSel
                    kwdict["channels"] = chanSel
                    kwdict[timeSel[0]] = timeSel[1]
                    cfg = StructDict(kwdict)
                    # data selection via class-method + `Selector` instance for indexing
                    selected = dummy.selectdata(**kwdict)
                    selector = Selector(dummy, kwdict)
                    idx[chanIdx] = selector.channel
                    for tk, trialno in enumerate(selector.trials):
                        idx[timeIdx] = selector.time[tk]
                        assert np.array_equal(
                            selected.trials[tk].squeeze(),
                            dummy.trials[trialno][idx[0], :][:,
                                                             idx[1]].squeeze())
                    cfg.data = dummy
                    cfg.out = AnalogData(dimord=AnalogData._defaultDimord)
                    # data selection via package function and `cfg`: ensure equality
                    selectdata(cfg)
                    assert np.array_equal(cfg.out.channel, selected.channel)
                    assert np.array_equal(cfg.out.data, selected.data)
Exemple #4
0
    def test_nparray(self):
        dummy = AnalogData(data=self.data)
        assert dummy.dimord == AnalogData._defaultDimord
        assert dummy.channel.size == self.nc
        assert (dummy.sampleinfo == [0, self.ns]).min()
        assert dummy.trialinfo.shape == (1, 0)
        assert np.array_equal(dummy.data, self.data)

        # wrong shape for data-type
        with pytest.raises(SPYValueError):
            AnalogData(np.ones((3, )))
def filter_manager(data,
                   b=None,
                   a=None,
                   out=None,
                   select=None,
                   chan_per_worker=None,
                   keeptrials=True,
                   parallel=False,
                   parallel_store=None,
                   log_dict=None):
    newOut = False
    if out is None:
        newOut = True
        out = AnalogData(dimord=AnalogData._defaultDimord)
    myfilter = LowPassFilter(b, a=a)
    myfilter.initialize(data,
                        out._stackingDim,
                        chan_per_worker=chan_per_worker,
                        keeptrials=keeptrials)
    myfilter.compute(data,
                     out,
                     parallel=parallel,
                     parallel_store=parallel_store,
                     log_dict=log_dict)
    return out if newOut else None
Exemple #6
0
 def test_virtualdata(self):
     with tempfile.TemporaryDirectory() as tdir:
         fname = os.path.join(tdir, "dummy.npy")
         np.save(fname, self.data)
         dmap = open_memmap(fname, mode="r")
         vdata = VirtualData([dmap, dmap])
         dummy = AnalogData(vdata)
         assert dummy.channel.size == 2 * self.nc
         assert len(dummy._filename) == 2
         assert isinstance(dummy.filename, str)
         del dmap, dummy, vdata
Exemple #7
0
    def test_trialretrieval(self):
        # test ``_get_trial`` with NumPy array: regular order
        dummy = AnalogData(data=self.data, trialdefinition=self.trl)
        for trlno, start in enumerate(range(0, self.ns, 5)):
            trl_ref = self.data[start:start + 5, :]
            assert np.array_equal(dummy._get_trial(trlno), trl_ref)

        # test ``_get_trial`` with NumPy array: swapped dimensions
        dummy = AnalogData(self.data.T,
                           trialdefinition=self.trl,
                           dimord=["channel", "time"])
        for trlno, start in enumerate(range(0, self.ns, 5)):
            trl_ref = self.data.T[:, start:start + 5]
            assert np.array_equal(dummy._get_trial(trlno), trl_ref)

        # # test ``_copy_trial`` with memmap'ed data
        # with tempfile.TemporaryDirectory() as tdir:
        #     fname = os.path.join(tdir, "dummy.npy")
        #     np.save(fname, self.data)
        #     mm = open_memmap(fname, mode="r")
        #     dummy = AnalogData(mm, trialdefinition=self.trl)
        #     for trlno, start in enumerate(range(0, self.ns, 5)):
        #         trl_ref = self.data[start:start + 5, :]
        #         trl_tmp = dummy._copy_trial(trlno,
        #                                     dummy.filename,
        #                                     dummy.dimord,
        #                                     dummy.sampleinfo,
        #                                     dummy.hdr)
        #         assert np.array_equal(trl_tmp, trl_ref)
        #
        #    # Delete all open references to file objects b4 closing tmp dir
        #    del mm, dummy
        del dummy
    def test_save_mmap(self):
        with tempfile.TemporaryDirectory() as tdir:
            fname = os.path.join(tdir, "vdat.npy")
            dname = os.path.join(tdir, "dummy")
            vdata = np.ones((1000, 5000))  # ca. 38.2 MB
            np.save(fname, vdata)
            del vdata
            dmap = open_memmap(fname)
            adata = AnalogData(dmap, samplerate=10)

            # Ensure memory consumption stays within provided bounds
            mem = memory_usage()[0]
            save(adata, filename=dname, memuse=60)
            assert (mem - memory_usage()[0]) < 70

            # Delete all open references to file objects b4 closing tmp dir
            del dmap, adata
Exemple #9
0
    def test_clear(self):
        with tempfile.TemporaryDirectory() as tdir:
            fname = os.path.join(tdir, "dummy.npy")
            data = np.ones((5000, 1000))  # ca. 38.2 MB
            np.save(fname, data)
            del data
            dmap = open_memmap(fname)

            # test consistency and efficacy of clear method
            dummy = AnalogData(dmap)
            data = np.array(dummy.data)
            dummy.clear()
            assert np.array_equal(data, dummy.data)
            mem = memory_usage()[0]
            dummy.clear()
            time.sleep(1)
            assert np.abs(mem - memory_usage()[0]) > 30

            # Delete all open references to file objects b4 closing tmp dir
            del dmap, dummy
Exemple #10
0
    def test_trialsetting(self):

        # Create sampleinfo w/ EventData vs. AnalogData samplerate
        sr_e = 2
        sr_a = 1
        pre = 2
        post = 1
        msk = self.data[:, 1] == 1
        sinfo = np.vstack(
            [self.data[msk, 0] / sr_e - pre,
             self.data[msk, 0] / sr_e + post]).T
        sinfo_e = np.round(sinfo * sr_e).astype(int)
        sinfo_a = np.round(sinfo * sr_a).astype(int)

        # Compute sampleinfo w/pre, post and trigger
        evt_dummy = EventData(self.data, samplerate=sr_e)
        evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        assert np.array_equal(evt_dummy.sampleinfo, sinfo_e)

        # Compute sampleinfo w/ start/stop combination
        evt_dummy = EventData(self.data, samplerate=sr_e)
        evt_dummy.definetrial(start=0, stop=1)
        sinfo2 = np.vstack([
            self.data[np.where(self.data[:, 1] == 0)[0], 0],
            self.data[np.where(self.data[:, 1] == 1)[0], 0]
        ]).T
        assert np.array_equal(sinfo2, evt_dummy.sampleinfo)

        # Same w/ more complicated data array
        samples = np.arange(0, int(self.ns / 3), 3)[1:]
        dappend = np.vstack([samples, np.full(samples.shape, 2)]).T
        data3 = np.vstack([self.data, dappend])
        idx = np.argsort(data3[:, 0])
        data3 = data3[idx, :]
        evt_dummy = EventData(data3, samplerate=sr_e)
        evt_dummy.definetrial(start=0, stop=1)
        assert np.array_equal(sinfo2, evt_dummy.sampleinfo)

        # Compute sampleinfo w/start/stop arrays instead of scalars
        starts = [2, 2, 1]
        stops = [1, 2, 0]
        sinfo3 = np.empty((3, 2))
        dsamps = list(data3[:, 0])
        dcodes = list(data3[:, 1])
        for sk, (start, stop) in enumerate(zip(starts, stops)):
            idx = dcodes.index(start)
            start = dsamps[idx]
            dcodes = dcodes[idx + 1:]
            dsamps = dsamps[idx + 1:]
            idx = dcodes.index(stop)
            stop = dsamps[idx]
            dcodes = dcodes[idx + 1:]
            dsamps = dsamps[idx + 1:]
            sinfo3[sk, :] = [start, stop]
        evt_dummy = EventData(data3, samplerate=sr_e)
        evt_dummy.definetrial(start=[2, 2, 1], stop=[1, 2, 0])
        assert np.array_equal(evt_dummy.sampleinfo, sinfo3)

        # Attach computed sampleinfo to AnalogData (data and data3 must yield identical resutls)
        evt_dummy = EventData(data=self.data, samplerate=sr_e)
        evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        ang_dummy = AnalogData(self.adata, samplerate=sr_a)
        ang_dummy.definetrial(evt_dummy)
        assert np.array_equal(ang_dummy.sampleinfo, sinfo_a)
        evt_dummy = EventData(data=data3, samplerate=sr_e)
        evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        ang_dummy.definetrial(evt_dummy)
        assert np.array_equal(ang_dummy.sampleinfo, sinfo_a)

        # Compute and attach sampleinfo on the fly
        evt_dummy = EventData(data=self.data, samplerate=sr_e)
        ang_dummy = AnalogData(self.adata, samplerate=sr_a)
        ang_dummy.definetrial(evt_dummy, pre=pre, post=post, trigger=1)
        assert np.array_equal(ang_dummy.sampleinfo, sinfo_a)
        evt_dummy = EventData(data=data3, samplerate=sr_e)
        ang_dummy = AnalogData(self.adata, samplerate=sr_a)
        ang_dummy.definetrial(evt_dummy, pre=pre, post=post, trigger=1)
        assert np.array_equal(ang_dummy.sampleinfo, sinfo_a)

        # Extend data and provoke an exception due to out of bounds erro
        smp = np.vstack([
            np.arange(self.ns, int(2.5 * self.ns), 5),
            np.zeros((int((1.5 * self.ns) / 5), ))
        ]).T
        smp[1::2, 1] = 1
        data4 = np.vstack([data3, smp])
        evt_dummy = EventData(data=data4, samplerate=sr_e)
        evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        # with pytest.raises(SPYValueError):
        # ang_dummy.definetrial(evt_dummy)

        # Trimming edges produces zero-length trial
        with pytest.raises(SPYValueError):
            ang_dummy.definetrial(evt_dummy, clip_edges=True)

        # We need `clip_edges` to make trial-definition work
        data4 = data4[:-2, :]
        data4[-2, 0] = data4[-1, 0]
        evt_dummy = EventData(data=data4, samplerate=sr_e)
        evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        # with pytest.raises(SPYValueError):
        # ang_dummy.definetrial(evt_dummy)
        ang_dummy.definetrial(evt_dummy, clip_edges=True)
        assert ang_dummy.sampleinfo[-1, 1] == self.ns

        # Check both pre/start and/or post/stop being None
        evt_dummy = EventData(data=self.data, samplerate=sr_e)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(trigger=1, post=post)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(pre=pre, trigger=1)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(start=0)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(stop=1)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(trigger=1)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(pre=pre, post=post)

        # Try to define trials w/o samplerate set
        evt_dummy = EventData(data=self.data)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        evt_dummy = EventData(data=self.data, samplerate=sr_e)
        ang_dummy = AnalogData(self.adata)
        with pytest.raises(SPYValueError):
            ang_dummy.definetrial(evt_dummy, pre=pre, post=post, trigger=1)

        # Try to define trials w/o data
        evt_dummy = EventData(samplerate=sr_e)
        with pytest.raises(SPYValueError):
            evt_dummy.definetrial(pre=pre, post=post, trigger=1)
        ang_dummy = AnalogData(self.adata, samplerate=sr_a)
        with pytest.raises(SPYValueError):
            ang_dummy.definetrial(evt_dummy, pre=pre, post=post, trigger=1)
Exemple #11
0
def generate_artificial_data(nTrials=2,
                             nChannels=2,
                             equidistant=True,
                             seed=None,
                             overlapping=False,
                             inmemory=True,
                             dimord="default"):
    """
    Create :class:`~syncopy.AnalogData` object with synthetic trigonometric signal(s)
    
    Parameters
    ----------
    nTrials : int
        Number of trials to populate synthetic data object with
    nChannels : int
        Number of channels to populate synthetic object with
    equidistant : bool
        If `True`, trials of equal length are defined
    seed : None or int
        If `None`, imposed noise is completely random. If `seed` is an integer, 
        it is used to fix the (initial) state of NumPy's random number generator 
        :func:`numpy.random.default_rng`, i.e., objects created wtih same `seed` 
        will be populated with identical artificial signals. 
    overlapping : bool
        If `True`, constructed trials overlap
    inmemory : bool
        If `True`, the full `data` array (all channels across all trials) is allocated
        in memory (fast but dangerous for large arrays), otherwise the output data 
        object's corresponding backing HDF5 file in `__storage__` is filled with 
        synthetic data in a trial-by-trial manner (slow but safe even for very 
        large datasets). 
    dimord : str or list
        If `dimord` is "default", the constructed output object uses the default
        dimensional layout of a standard :class:`~syncopy.AnalogData` object. 
        If `dimord` is a list (i.e., ``["channel", "time"]``) the provided sequence
        of dimensions is used. 
        
    Returns
    -------
    out : :class:`~syncopy.AnalogData` object
        Syncopy :class:`~syncopy.AnalogData` object with specified properties 
        populated with a synthetic multivariate trigonometric signal. 

    Notes
    -----
    This is an auxiliary method that is intended purely for internal use. Thus, 
    no error checking is performed. 
    
    Examples
    --------
    Generate small artificial :class:`~syncopy.AnalogData` object in memory
    
    .. code-block:: python
    
        >>> iAmSmall = generate_artificial_data(nTrials=5, nChannels=10, inmemory=True)
        >>> iAmSmall
        Syncopy AnalogData object with fields
        
                    cfg : dictionary with keys ''
                channel : [10] element <class 'numpy.ndarray'>
              container : None
                   data : 5 trials of length 3000 defined on [15000 x 10] float32 Dataset of size 0.57 MB
                 dimord : 2 element list
               filename : /Users/pantaray/.spy/spy_158f_4d4153e3.analog
                   mode : r+
             sampleinfo : [5 x 2] element <class 'numpy.ndarray'>
             samplerate : 1000.0
                    tag : None
                   time : 5 element list
              trialinfo : [5 x 0] element <class 'numpy.ndarray'>
                 trials : 5 element iterable
        
        Use `.log` to see object history
        
    Generate artificial :class:`~syncopy.AnalogData` object of more substantial 
    size on disk
        
    .. code-block:: python
    
        >>> iAmBig = generate_artificial_data(nTrials=50, nChannels=1024, inmemory=False)
        >>> iAmBig
        Syncopy AnalogData object with fields
        
                    cfg : dictionary with keys ''
                channel : [1024] element <class 'numpy.ndarray'>
              container : None
                   data : 200 trials of length 3000 defined on [600000 x 1024] float32 Dataset of size 2.29 GB
                 dimord : 2 element list
               filename : /Users/pantaray/.spy/spy_158f_b80715fe.analog
                   mode : r+
             sampleinfo : [200 x 2] element <class 'numpy.ndarray'>
             samplerate : 1000.0
                    tag : None
                   time : 200 element list
              trialinfo : [200 x 0] element <class 'numpy.ndarray'>
                 trials : 200 element iterable
        
        Use `.log` to see object history
        
    """

    # Create dummy 1d signal that will be blown up to fill channels later
    dt = 0.001
    t = np.arange(0, 3, dt, dtype="float32") - 1.0
    sig = np.cos(2 * np.pi * (7 * (np.heaviside(t, 1) * t - 1) + 10) * t)

    # Depending on chosen `dimord` either get default position of time-axis
    # in `AnalogData` objects or use provided `dimord` and reshape signal accordingly
    if dimord == "default":
        dimord = AnalogData._defaultDimord
    timeAxis = dimord.index("time")
    idx = [1, 1]
    idx[timeAxis] = -1
    sig = np.repeat(sig.reshape(*idx), axis=idx.index(1), repeats=nChannels)

    # Initialize random number generator (with possibly user-provided seed-value)
    rng = np.random.default_rng(seed)

    # Either construct the full data array in memory using tiling or create
    # an HDF5 container in `__storage__` and fill it trial-by-trial
    # NOTE: use `swapaxes` here to ensure two objects created w/same seed really
    # are affected w/identical additive noise patterns, no matter their respective
    # `dimord`.
    out = AnalogData(samplerate=1 / dt, dimord=dimord)
    if inmemory:
        idx[timeAxis] = nTrials
        sig = np.tile(sig, idx)
        shp = [slice(None), slice(None)]
        for iTrial in range(nTrials):
            shp[timeAxis] = slice(iTrial * t.size, (iTrial + 1) * t.size)
            noise = rng.standard_normal(
                (t.size, nChannels)).astype(sig.dtype) * 0.5
            sig[tuple(shp)] += np.swapaxes(noise, timeAxis, 0)
        out.data = sig
    else:
        with h5py.File(out.filename, "w") as h5f:
            shp = list(sig.shape)
            shp[timeAxis] *= nTrials
            dset = h5f.create_dataset("data",
                                      shape=tuple(shp),
                                      dtype=sig.dtype)
            shp = [slice(None), slice(None)]
            for iTrial in range(nTrials):
                shp[timeAxis] = slice(iTrial * t.size, (iTrial + 1) * t.size)
                noise = rng.standard_normal(
                    (t.size, nChannels)).astype(sig.dtype) * 0.5
                dset[tuple(shp)] = sig + np.swapaxes(noise, timeAxis, 0)
                dset.flush()
        out.data = h5py.File(out.filename, "r+")["data"]

    # Define by-trial offsets to generate (non-)equidistant/(non-)overlapping trials
    trialdefinition = np.zeros((nTrials, 3), dtype='int')
    if equidistant:
        equiOffset = 0
        if overlapping:
            equiOffset = 100
        offsets = np.full((nTrials, ), equiOffset, dtype=sig.dtype)
    else:
        offsets = rng.integers(low=int(0.1 * t.size),
                               high=int(0.2 * t.size),
                               size=(nTrials, ))

    # Using generated offsets, construct trialdef array and make sure initial
    # and end-samples are within data bounds (only relevant if overlapping
    # trials are built)
    shift = (-1)**(not overlapping)
    for iTrial in range(nTrials):
        trialdefinition[iTrial, :] = np.array([
            iTrial * t.size - shift * offsets[iTrial],
            (iTrial + 1) * t.size + shift * offsets[iTrial], -1000
        ])
    if equidistant:
        trialdefinition[0, :2] += equiOffset
        trialdefinition[-1, :2] -= equiOffset
    else:
        trialdefinition[0, 0] = 0
        trialdefinition[-1, 1] = nTrials * t.size
    out.definetrial(trialdefinition)

    return out
Exemple #12
0
    def test_continuous_toitoilim(self):

        # this only works w/the equidistant trials constructed above!!!
        selDict = {
            "toi": (
                None,  # trivial "selection" of entire contents
                "all",  # trivial "selection" of entire contents
                [0.5],  # single entry lists
                [0.6],  # inexact match
                [1.0, 2.5],  # two disjoint time-points
                [1.2, 2.7],  # inexact from above
                [1.9, 2.4],  # inexact from below
                [0.4, 2.1],  # inexact from below, inexact from above
                [1.6, 1.9],  # inexact from above, inexact from below
                [-0.2, 0.6, 0.9, 1.1, 1.3, 1.6, 1.8, 2.2, 2.45,
                 3.],  # alternating madness
                [2.0, 0.5, 2.5],  # unsorted list
                [1.0, 0.5, 0.5, 1.5],  # repetition
                [0.5, 0.5, 1.0,
                 1.5],  # preserve repetition, don't convert to slice
                [0.5, 1.0,
                 1.5]),  # sorted list (should be converted to slice-selection)
            "toilim": (
                None,  # trivial "selection" of entire contents
                "all",  # trivial "selection" of entire contents
                [0.5, 1.5],  # regular range
                [1.5, 2.0],  # minimal range (just two-time points)
                [1.0, np.inf],  # unbounded from above
                [-np.inf, 1.0])
        }  # unbounded from below

        # all trials have same time-scale: take 1st one as reference
        trlTime = (np.arange(
            0, self.trl["AnalogData"][0, 1] - self.trl["AnalogData"][0, 0]) +
                   self.trl["AnalogData"][0, 2]) / self.samplerate

        ang = AnalogData(data=self.data["AnalogData"],
                         trialdefinition=self.trl["AnalogData"],
                         samplerate=self.samplerate)
        angIdx = [slice(None)] * len(ang.dimord)
        timeIdx = ang.dimord.index("time")

        # the below check only works for equidistant trials!
        for tselect in ["toi", "toilim"]:
            for timeSel in selDict[tselect]:
                sel = Selector(ang, {tselect: timeSel}).time
                if timeSel is None or timeSel == "all":
                    idx = slice(None)
                else:
                    if tselect == "toi":
                        idx = []
                        for tp in timeSel:
                            idx.append(np.abs(trlTime - tp).argmin())
                    else:
                        idx = np.intersect1d(
                            np.where(trlTime >= timeSel[0])[0],
                            np.where(trlTime <= timeSel[1])[0])

                # check that correct data was selected (all trials identical, just take 1st one)
                assert np.array_equal(ang.trials[0][idx, :],
                                      ang.trials[0][sel[0], :])
                if not isinstance(idx, slice) and len(idx) > 1:
                    timeSteps = np.diff(idx)
                    if timeSteps.min() == timeSteps.max() == 1:
                        idx = slice(idx[0], idx[-1] + 1, 1)
                result = [idx] * len(ang.trials)

                # check correct format of selector (list -> slice etc.)
                assert np.array_equal(result, sel)

                # perform actual data-selection and ensure identity of results
                selected = selectdata(ang, {tselect: timeSel})
                for trialno in range(len(ang.trials)):
                    angIdx[timeIdx] = result[trialno]
                    assert np.array_equal(selected.trials[trialno],
                                          ang.trials[trialno][tuple(angIdx)])
Exemple #13
0
    def test_general(self):

        # construct expected results for `DiscreteData` objects defined above
        mapDict = {"unit": "SpikeData", "eventid": "EventData"}
        for prop, dclass in mapDict.items():
            discrete = getattr(spd, dclass)(data=self.data[dclass],
                                            trialdefinition=self.trl[dclass],
                                            samplerate=self.samplerate)
            propIdx = discrete.dimord.index(prop)

            # convert selection from `selectDict` to a usable integer-list
            allResults = []
            for selection in self.selectDict[prop]["valid"]:
                if isinstance(selection, slice):
                    if selection.start is selection.stop is None:
                        selects = [None]
                    else:
                        selects = list(range(getattr(discrete,
                                                     prop).size))[selection]
                elif isinstance(selection, range):
                    selects = list(selection)
                elif isinstance(selection, str) or selection is None:
                    selects = [None]
                else:  # selection is list/ndarray
                    if isinstance(selection[0], str):
                        avail = getattr(discrete, prop)
                    else:
                        avail = np.arange(getattr(discrete, prop).size)
                    selects = []
                    for sel in selection:
                        selects += list(np.where(avail == sel)[0])

                # alternate (expensive) way to get by-trial selection indices
                result = []
                for trial in discrete.trials:
                    if selects[0] is None:
                        res = slice(0, trial.shape[0], 1)
                    else:
                        res = []
                        for sel in selects:
                            res += list(np.where(trial[:, propIdx] == sel)[0])
                        if len(res) > 1:
                            steps = np.diff(res)
                            if steps.min() == steps.max() == 1:
                                res = slice(res[0], res[-1] + 1, 1)
                    result.append(res)
                allResults.append(result)

            self.selectDict[prop]["result"] = tuple(allResults)

        # wrong type of data and/or selector
        with pytest.raises(SPYTypeError):
            Selector(np.empty((3, )), {})
        with pytest.raises(SPYValueError):
            Selector(spd.AnalogData(), {})
        ang = AnalogData(data=self.data["AnalogData"],
                         trialdefinition=self.trl["AnalogData"],
                         samplerate=self.samplerate)
        with pytest.raises(SPYTypeError):
            Selector(ang, ())
        with pytest.raises(SPYValueError):
            Selector(ang, {"wrongkey": [1]})

        # go through all data-classes defined above
        for dclass in self.classes:
            dummy = getattr(spd, dclass)(data=self.data[dclass],
                                         trialdefinition=self.trl[dclass],
                                         samplerate=self.samplerate)

            # test trial selection
            selection = Selector(dummy, {"trials": [3, 1]})
            assert selection.trials == [3, 1]
            selected = selectdata(dummy, trials=[3, 1])
            assert np.array_equal(selected.trials[0], dummy.trials[3])
            assert np.array_equal(selected.trials[1], dummy.trials[1])
            assert selected.trialdefinition.shape == (2, 4)
            assert np.array_equal(selected.trialdefinition[:, -1],
                                  dummy.trialdefinition[[3, 1], -1])

            for trlSec in [None, "all"]:
                selection = Selector(dummy, {"trials": trlSec})
                assert selection.trials == list(range(len(dummy.trials)))
                selected = selectdata(dummy, trials=trlSec)
                for tk, trl in enumerate(selected.trials):
                    assert np.array_equal(trl, dummy.trials[tk])
                assert np.array_equal(selected.trialdefinition,
                                      dummy.trialdefinition)

            with pytest.raises(SPYValueError):
                Selector(dummy, {"trials": [-1, 9]})

            # test "simple" property setters handled by `_selection_setter`
            # for prop in ["eventid"]:
            for prop in ["channel", "taper", "unit", "eventid"]:
                if hasattr(dummy, prop):
                    expected = self.selectDict[prop]["result"]
                    for sk, sel in enumerate(self.selectDict[prop]["valid"]):
                        solution = expected[sk]
                        if dclass == "SpikeData" and prop == "channel":
                            if isinstance(solution, slice):
                                start, stop, step = solution.start, solution.stop, solution.step
                                if start is None:
                                    start = 0
                                elif start < 0:
                                    start = len(dummy.channel) + start
                                if stop is None:
                                    stop = len(dummy.channel)
                                elif stop < 0:
                                    stop = len(dummy.channel) + stop
                                if step not in [None, 1]:
                                    solution = list(range(start,
                                                          stop))[solution]
                                else:
                                    solution = slice(start, stop, step)

                        # once we're sure `Selector` works, actually select data
                        selection = Selector(dummy, {prop + "s": sel})
                        assert getattr(selection, prop) == solution
                        selected = selectdata(dummy, {prop + "s": sel})

                        # process `unit` and `enventid`
                        if prop in selection._byTrialProps:
                            propIdx = selected.dimord.index(prop)
                            propArr = np.unique(
                                selected.data[:, propIdx]).astype(np.intp)
                            assert set(getattr(selected, prop)) == set(
                                getattr(dummy, prop)[propArr])
                            tk = 0
                            for trialno in range(len(dummy.trials)):
                                if solution[
                                        trialno]:  # do not try to compare empty selections
                                    assert np.array_equal(
                                        selected.trials[tk],
                                        dummy.trials[trialno][
                                            solution[trialno], :])
                                    tk += 1

                        # `channel` is a special case for `SpikeData` objects
                        elif dclass == "SpikeData" and prop == "channel":
                            chanIdx = selected.dimord.index("channel")
                            chanArr = np.arange(dummy.channel.size)
                            assert set(selected.data[:, chanIdx]).issubset(
                                chanArr[solution])
                            assert set(selected.channel) == set(
                                dummy.channel[solution])

                        # everything else (that is not a `DiscreteData` child)
                        else:
                            idx = [slice(None)] * len(dummy.dimord)
                            idx[dummy.dimord.index(prop)] = solution
                            assert np.array_equal(
                                np.array(dummy.data)[tuple(idx)],
                                selected.data)
                            assert np.array_equal(
                                getattr(selected, prop),
                                getattr(dummy, prop)[solution])

                    # ensure invalid selection trigger expected errors
                    for ik, isel in enumerate(
                            self.selectDict[prop]["invalid"]):
                        with pytest.raises(
                                self.selectDict[prop]["errors"][ik]):
                            Selector(dummy, {prop + "s": isel})
                else:

                    # ensure objects that don't have a `prop` attribute complain
                    with pytest.raises(SPYValueError):
                        Selector(dummy, {prop + "s": [0]})

            # ensure invalid `toi` + `toilim` specifications trigger expected errors
            if hasattr(dummy, "time") or hasattr(dummy, "trialtime"):
                for selection in ["toi", "toilim"]:
                    for ik, isel in enumerate(
                            self.selectDict[selection]["invalid"]):
                        with pytest.raises(
                                self.selectDict[selection]["errors"][ik]):
                            Selector(dummy, {selection: isel})
                # provide both `toi` and `toilim`
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"toi": [0], "toilim": [0, 1]})
            else:
                # ensure objects that don't have `time` props complain properly
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"toi": [0]})
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"toilim": [0]})

            # ensure invalid `foi` + `foilim` specifications trigger expected errors
            if hasattr(dummy, "freq"):
                for selection in ["foi", "foilim"]:
                    for ik, isel in enumerate(
                            self.selectDict[selection]["invalid"]):
                        with pytest.raises(
                                self.selectDict[selection]["errors"][ik]):
                            Selector(dummy, {selection: isel})
                # provide both `foi` and `foilim`
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"foi": [0], "foilim": [0, 1]})
            else:
                # ensure objects without `freq` property complain properly
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"foi": [0]})
                with pytest.raises(SPYValueError):
                    Selector(dummy, {"foilim": [0]})
Exemple #14
0
    def test_saveload(self):
        with tempfile.TemporaryDirectory() as tdir:
            fname = os.path.join(tdir, "dummy")

            # basic but most important: ensure object integrity is preserved
            checkAttr = [
                "channel", "data", "dimord", "sampleinfo", "samplerate",
                "trialinfo"
            ]
            dummy = AnalogData(data=self.data, samplerate=1000)
            dummy.save(fname)
            filename = construct_spy_filename(fname, dummy)
            # NOTE: We removed support for loading data via the constructor
            # dummy2 = AnalogData(filename)
            # for attr in checkAttr:
            #     assert np.array_equal(getattr(dummy, attr), getattr(dummy2, attr))
            dummy3 = load(fname)
            for attr in checkAttr:
                assert np.array_equal(getattr(dummy3, attr),
                                      getattr(dummy, attr))
            save(dummy3, container=os.path.join(tdir, "ymmud"))
            dummy4 = load(os.path.join(tdir, "ymmud"))
            for attr in checkAttr:
                assert np.array_equal(getattr(dummy4, attr),
                                      getattr(dummy, attr))
            del dummy, dummy3, dummy4  # avoid PermissionError in Windows

            # # FIXME: either remove or repair this
            # # save object hosting VirtualData
            # np.save(fname + ".npy", self.data)
            # dmap = open_memmap(fname + ".npy", mode="r")
            # vdata = VirtualData([dmap, dmap])
            # dummy = AnalogData(vdata, samplerate=1000)
            # dummy.save(fname, overwrite=True)
            # dummy2 = AnalogData(filename)
            # assert dummy2.mode == "r+"
            # assert np.array_equal(dummy2.data, vdata[:, :])
            # del dummy, dummy2  # avoid PermissionError in Windows

            # ensure trialdefinition is saved and loaded correctly
            dummy = AnalogData(data=self.data,
                               trialdefinition=self.trl,
                               samplerate=1000)
            dummy.save(fname + "_trl")
            filename = construct_spy_filename(fname + "_trl", dummy)
            dummy2 = load(filename)
            assert np.array_equal(dummy.trialdefinition,
                                  dummy2.trialdefinition)

            # test getters
            assert np.array_equal(dummy.sampleinfo, dummy2.sampleinfo)
            assert np.array_equal(dummy._t0, dummy2._t0)
            assert np.array_equal(dummy.trialinfo, dummy2.trialinfo)

            del dummy, dummy2  # avoid PermissionError in Windows

            # swap dimensions and ensure `dimord` is preserved
            dummy = AnalogData(data=self.data,
                               dimord=["channel", "time"],
                               samplerate=1000)
            dummy.save(fname + "_dimswap")
            filename = construct_spy_filename(fname + "_dimswap", dummy)
            dummy2 = load(filename)
            assert dummy2.dimord == dummy.dimord
            assert dummy2.channel.size == self.ns  # swapped
            assert dummy2.data.shape == dummy.data.shape

            # Delete all open references to file objects and wait 0.1s for changes
            # to take effect (thanks, Windows!)
            del dummy, dummy2
            time.sleep(0.1)
class TestComputationalRoutine():

    # Construct linear combination of low- and high-frequency sine waves
    # and use an IIR filter to reconstruct the low-frequency component
    nChannels = 32
    nTrials = 8
    fData = 2
    fNoise = 64
    fs = 1000
    t = np.linspace(-1, 1, fs)
    orig = np.sin(2 * np.pi * fData * t)
    sig = orig + np.sin(2 * np.pi * fNoise * t)
    cutoff = 50
    b, a = signal.butter(8, 2 * cutoff / fs)

    # Blow up the signal to have "channels" and "trials" and inflate the low-
    # frequency component accordingly for ad-hoc comparisons later
    sig = np.repeat(sig.reshape(-1, 1), axis=1, repeats=nChannels)
    sig = np.tile(sig, (nTrials, 1))
    orig = np.repeat(orig.reshape(-1, 1), axis=1, repeats=nChannels)
    orig = np.tile(orig, (nTrials, 1))

    # Construct artificial equidistant trial-definition array
    trl = np.zeros((nTrials, 3), dtype="int")
    for ntrial in range(nTrials):
        trl[ntrial, :] = np.array([ntrial * fs, (ntrial + 1) * fs, -500])

    # Create reference AnalogData objects with equidistant trial spacing
    sigdata = AnalogData(data=sig,
                         samplerate=fs,
                         trialdefinition=trl,
                         dimord=["time", "channel"])
    origdata = AnalogData(data=orig,
                          samplerate=fs,
                          trialdefinition=trl,
                          dimord=["time", "channel"])

    # Set by-worker channel-count for channel-parallelization
    chanPerWrkr = 7

    # Data selections to be tested w/`sigdata`
    sigdataSelections = [
        None, {
            "trials": [3, 1, 0],
            "channels": ["channel" + str(i) for i in range(12, 28)][::-1]
        }, {
            "trials": [0, 1, 2],
            "channels": range(0, int(nChannels / 2)),
            "toilim": [-0.25, 0.25]
        }
    ]

    # Data selections to be tested w/`artdata` generated below (use fixed but arbitrary
    # random number seed to randomly select time-points for `toi` (with repetitions)
    seed = np.random.RandomState(13)
    artdataSelections = [
        None, {
            "trials": [3, 1, 0],
            "channels": ["channel" + str(i) for i in range(12, 28)][::-1],
            "toi": None
        }, {
            "trials": [0, 1, 2],
            "channels": range(0, int(nChannels / 2)),
            "toilim": [-0.5, 0.6]
        }
    ]

    # Error tolerances and respective quality metrics (depend on data selection!)
    tols = [1e-6, 1e-6, 1e-2]
    metrix = [np.max, np.max, np.mean]

    def test_sequential_equidistant(self):
        for sk, select in enumerate(self.sigdataSelections):
            sel = Selector(self.sigdata, select)
            out = filter_manager(self.sigdata, self.b, self.a, select=select)

            # check correct signal filtering (especially wrt data-selection)
            if select is None:
                reference = self.orig
            else:
                ref = []
                for tk, trlno in enumerate(sel.trials):
                    ref.append(self.origdata.trials[trlno][sel.time[tk],
                                                           sel.channel])
                    # check for correct time selection
                    assert np.array_equal(
                        out.time[tk], self.sigdata.time[trlno][sel.time[tk]])
                reference = np.vstack(ref)
            assert self.metrix[sk](
                np.abs(out.data - reference)) < self.tols[sk]
            assert np.array_equal(out.channel,
                                  self.sigdata.channel[sel.channel])

            # ensure pre-selection is equivalent to in-place selection
            if select is None:
                selected = self.sigdata.selectdata()
            else:
                selected = self.sigdata.selectdata(**select)
            out_sel = filter_manager(selected, self.b, self.a)
            assert np.array_equal(out.data, out_sel.data)
            assert np.array_equal(out.channel, out_sel.channel)
            assert np.array_equal(out.time, out_sel.time)

            out = filter_manager(self.sigdata,
                                 self.b,
                                 self.a,
                                 select=select,
                                 keeptrials=False)

            # check correct signal filtering (especially wrt data-selection)
            if select is None:
                reference = self.orig[:self.t.size, :]
            else:
                ref = np.zeros(out.trials[0].shape)
                for tk, trlno in enumerate(sel.trials):
                    ref += self.origdata.trials[trlno][sel.time[tk],
                                                       sel.channel]
                    # check for correct time selection (accounting for trial-averaging)
                    assert np.array_equal(out.time[0],
                                          self.sigdata.time[0][sel.time[0]])
                reference = ref / len(sel.trials)
            assert self.metrix[sk](
                np.abs(out.data - reference)) < self.tols[sk]
            assert np.array_equal(out.channel,
                                  self.sigdata.channel[sel.channel])

            # ensure pre-selection is equivalent to in-place selection
            if select is None:
                selected = self.sigdata.selectdata()
            else:
                selected = self.sigdata.selectdata(**select)
            out_sel = filter_manager(selected,
                                     self.b,
                                     self.a,
                                     keeptrials=False)
            assert np.array_equal(out.data, out_sel.data)
            assert np.array_equal(out.channel, out_sel.channel)
            assert np.array_equal(out.time, out_sel.time)

    def test_sequential_nonequidistant(self):
        for overlapping in [False, True]:
            nonequidata = generate_artificial_data(nTrials=self.nTrials,
                                                   nChannels=self.nChannels,
                                                   equidistant=False,
                                                   overlapping=overlapping,
                                                   inmemory=False)

            # unsorted, w/repetitions
            toi = self.seed.choice(nonequidata.time[0],
                                   int(nonequidata.time[0].size))
            self.artdataSelections[1]["toi"] = toi

            for select in self.artdataSelections:
                sel = Selector(nonequidata, select)
                out = filter_manager(nonequidata,
                                     self.b,
                                     self.a,
                                     select=select)

                # compare expected w/actual shape of computed data
                reference = 0
                for tk, trlno in enumerate(sel.trials):
                    reference += nonequidata.trials[trlno][
                        sel.time[tk]].shape[0]
                    # check for correct time selection
                    # FIXME: remove `if` below as soon as `time` prop for lists is fixed
                    if not isinstance(sel.time[0], list):
                        assert np.array_equal(
                            out.time[tk],
                            nonequidata.time[trlno][sel.time[tk]])

                assert out.data.shape[0] == reference
                assert np.array_equal(out.channel,
                                      nonequidata.channel[sel.channel])

                # ensure pre-selection is equivalent to in-place selection
                if select is None:
                    selected = nonequidata.selectdata()
                else:
                    selected = nonequidata.selectdata(**select)
                out_sel = filter_manager(selected, self.b, self.a)
                assert np.array_equal(out.data, out_sel.data)
                assert np.array_equal(out.channel, out_sel.channel)
                for tk in range(len(out.trials)):
                    assert np.array_equal(out.time[tk], out_sel.time[tk])

    def test_sequential_saveload(self):
        for sk, select in enumerate(self.sigdataSelections):
            sel = Selector(self.sigdata, select)
            out = filter_manager(self.sigdata,
                                 self.b,
                                 self.a,
                                 select=select,
                                 log_dict={
                                     "a": "this is a",
                                     "b": "this is b"
                                 })

            # only keyword args (`a` in this case here) are stored in `cfg`
            assert set(["a"]) == set(out.cfg.keys())
            assert np.array_equal(out.cfg["a"], self.a)
            assert len(out.trials) == len(sel.trials)
            # ensure our `log_dict` specification was respected
            assert "lowpass" in out._log
            assert "a = this is a" in out._log
            assert "b = this is b" in out._log

            # ensure pre-selection is equivalent to in-place selection
            if select is None:
                selected = self.sigdata.selectdata()
            else:
                selected = self.sigdata.selectdata(**select)
            out_sel = filter_manager(selected,
                                     self.b,
                                     self.a,
                                     log_dict={
                                         "a": "this is a",
                                         "b": "this is b"
                                     })
            assert set(["a"]) == set(out.cfg.keys())
            assert np.array_equal(out.cfg["a"], self.a)
            assert len(out.trials) == len(out_sel.trials)
            assert "lowpass" in out._log
            assert "a = this is a" in out._log
            assert "b = this is b" in out._log

            # save and re-load result, ensure nothing funky happens
            with tempfile.TemporaryDirectory() as tdir:
                fname = os.path.join(tdir, "dummy")
                out.save(fname)
                dummy = load(fname)
                assert "a" in dummy.cfg.keys()
                assert np.array_equal(dummy.cfg["a"], self.a)
                assert out.filename == dummy.filename
                if select is None:
                    reference = self.orig
                else:
                    ref = []
                    for tk, trlno in enumerate(sel.trials):
                        ref.append(self.origdata.trials[trlno][sel.time[tk],
                                                               sel.channel])
                        assert np.array_equal(
                            dummy.time[tk],
                            self.sigdata.time[trlno][sel.time[tk]])
                    reference = np.vstack(ref)
                assert self.metrix[sk](
                    np.abs(dummy.data - reference)) < self.tols[sk]
                assert np.array_equal(dummy.channel,
                                      self.sigdata.channel[sel.channel])
                time.sleep(0.01)
                del out

                # ensure out_sel is written/read correctly
                fname2 = os.path.join(tdir, "dummy2")
                out_sel.save(fname2)
                dummy2 = load(fname2)
                assert "a" in dummy2.cfg.keys()
                assert np.array_equal(dummy2.cfg["a"], dummy.cfg["a"])
                assert np.array_equal(dummy.data, dummy2.data)
                assert np.array_equal(dummy.channel, dummy2.channel)
                assert np.array_equal(dummy.time, dummy2.time)
                del dummy, dummy2, out_sel

    @skip_without_acme
    def test_parallel_equidistant(self, testcluster):
        client = dd.Client(testcluster)
        for parallel_store in [True, False]:
            for chan_per_worker in [None, self.chanPerWrkr]:
                for sk, select in enumerate(self.sigdataSelections):
                    # FIXME: remove as soon as channel-parallelization works w/channel selectors
                    if chan_per_worker is not None:
                        select = None
                    sel = Selector(self.sigdata, select)
                    out = filter_manager(self.sigdata,
                                         self.b,
                                         self.a,
                                         select=select,
                                         chan_per_worker=chan_per_worker,
                                         parallel=True,
                                         parallel_store=parallel_store)

                    assert out.data.is_virtual == parallel_store

                    # check correct signal filtering (especially wrt data-selection)
                    if select is None:
                        reference = self.orig
                    else:
                        ref = []
                        for tk, trlno in enumerate(sel.trials):
                            ref.append(
                                self.origdata.trials[trlno][sel.time[tk],
                                                            sel.channel])
                            # check for correct time selection
                            assert np.array_equal(
                                out.time[tk],
                                self.sigdata.time[trlno][sel.time[tk]])
                        reference = np.vstack(ref)
                    assert self.metrix[sk](
                        np.abs(out.data - reference)) < self.tols[sk]
                    assert np.array_equal(out.channel,
                                          self.sigdata.channel[sel.channel])

                    # ensure correct no. HDF5 files were generated for virtual data-set
                    if parallel_store:
                        nfiles = len(
                            glob(
                                os.path.join(
                                    os.path.splitext(out.filename)[0],
                                    "*.h5")))
                        if chan_per_worker is None:
                            assert nfiles == len(sel.trials)
                        else:
                            assert nfiles == len(sel.trials) * (
                                int(out.channel.size / chan_per_worker) +
                                int(out.channel.size % chan_per_worker > 0))

                    # ensure pre-selection is equivalent to in-place selection
                    if select is None:
                        selected = self.sigdata.selectdata()
                    else:
                        selected = self.sigdata.selectdata(**select)
                    out_sel = filter_manager(selected,
                                             self.b,
                                             self.a,
                                             chan_per_worker=chan_per_worker,
                                             parallel=True,
                                             parallel_store=parallel_store)
                    assert np.allclose(out.data, out_sel.data)
                    assert np.array_equal(out.channel, out_sel.channel)
                    assert np.array_equal(out.time, out_sel.time)

                    out = filter_manager(self.sigdata,
                                         self.b,
                                         self.a,
                                         select=select,
                                         parallel=True,
                                         parallel_store=parallel_store,
                                         keeptrials=False)

                    # check correct signal filtering (especially wrt data-selection)
                    if select is None:
                        reference = self.orig[:self.t.size, :]
                    else:
                        ref = np.zeros(out.trials[0].shape)
                        for tk, trlno in enumerate(sel.trials):
                            ref += self.origdata.trials[trlno][sel.time[tk],
                                                               sel.channel]
                            # check for correct time selection (accounting for trial-averaging)
                            assert np.array_equal(
                                out.time[0], self.sigdata.time[0][sel.time[0]])
                        reference = ref / len(sel.trials)
                    assert self.metrix[sk](
                        np.abs(out.data - reference)) < self.tols[sk]
                    assert np.array_equal(out.channel,
                                          self.sigdata.channel[sel.channel])
                    assert out.data.is_virtual == False

                    # ensure pre-selection is equivalent to in-place selection
                    out_sel = filter_manager(selected,
                                             self.b,
                                             self.a,
                                             parallel=True,
                                             parallel_store=parallel_store,
                                             keeptrials=False)
                    assert np.allclose(out.data, out_sel.data)
                    assert np.array_equal(out.channel, out_sel.channel)
                    assert np.array_equal(out.time, out_sel.time)

        client.close()

    @skip_without_acme
    def test_parallel_nonequidistant(self, testcluster):
        client = dd.Client(testcluster)
        for overlapping in [False, True]:
            nonequidata = generate_artificial_data(nTrials=self.nTrials,
                                                   nChannels=self.nChannels,
                                                   equidistant=False,
                                                   overlapping=overlapping,
                                                   inmemory=False)

            # unsorted, w/repetitions
            toi = self.seed.choice(nonequidata.time[0],
                                   int(nonequidata.time[0].size))
            self.artdataSelections[1]["toi"] = toi

            for parallel_store in [True, False]:
                for chan_per_worker in [None, self.chanPerWrkr]:
                    for select in self.artdataSelections:
                        # FIXME: remove as soon as channel-parallelization works w/channel selectors
                        if chan_per_worker is not None:
                            select = None
                        sel = Selector(nonequidata, select)
                        out = filter_manager(nonequidata,
                                             self.b,
                                             self.a,
                                             select=select,
                                             chan_per_worker=chan_per_worker,
                                             parallel=True,
                                             parallel_store=parallel_store)

                        # compare expected w/actual shape of computed data
                        reference = 0
                        for tk, trlno in enumerate(sel.trials):
                            reference += nonequidata.trials[trlno][
                                sel.time[tk]].shape[0]
                            # check for correct time selection
                            # FIXME: remove `if` below as soon as `time` prop for lists is fixed
                            if not isinstance(sel.time[0], list):
                                assert np.array_equal(
                                    out.time[tk],
                                    nonequidata.time[trlno][sel.time[tk]])
                        assert out.data.shape[0] == reference
                        assert np.array_equal(out.channel,
                                              nonequidata.channel[sel.channel])
                        assert out.data.is_virtual == parallel_store

                        if parallel_store:
                            nfiles = len(
                                glob(
                                    os.path.join(
                                        os.path.splitext(out.filename)[0],
                                        "*.h5")))
                            if chan_per_worker is None:
                                assert nfiles == len(sel.trials)
                            else:
                                assert nfiles == len(sel.trials) * (
                                    int(out.channel.size / chan_per_worker) +
                                    int(out.channel.size % chan_per_worker > 0)
                                )

                        # ensure pre-selection is equivalent to in-place selection
                        if select is None:
                            selected = nonequidata.selectdata()
                        else:
                            selected = nonequidata.selectdata(**select)
                        out_sel = filter_manager(
                            selected,
                            self.b,
                            self.a,
                            chan_per_worker=chan_per_worker,
                            parallel=True,
                            parallel_store=parallel_store)
                        assert np.allclose(out.data, out_sel.data)
                        assert np.array_equal(out.channel, out_sel.channel)
                        for tk in range(len(out.trials)):
                            assert np.array_equal(out.time[tk],
                                                  out_sel.time[tk])

        client.close()

    @skip_without_acme
    def test_parallel_saveload(self, testcluster):
        client = dd.Client(testcluster)
        for parallel_store in [True, False]:
            for sk, select in enumerate(self.sigdataSelections):
                sel = Selector(self.sigdata, select)
                out = filter_manager(self.sigdata,
                                     self.b,
                                     self.a,
                                     select=select,
                                     log_dict={
                                         "a": "this is a",
                                         "b": "this is b"
                                     },
                                     parallel=True,
                                     parallel_store=parallel_store)

                # only keyword args (`a` in this case here) are stored in `cfg`
                assert set(["a"]) == set(out.cfg.keys())
                assert np.array_equal(out.cfg["a"], self.a)
                assert len(out.trials) == len(sel.trials)
                # ensure our `log_dict` specification was respected
                assert "lowpass" in out._log
                assert "a = this is a" in out._log
                assert "b = this is b" in out._log

                # ensure pre-selection is equivalent to in-place selection
                if select is None:
                    selected = self.sigdata.selectdata()
                else:
                    selected = self.sigdata.selectdata(**select)
                out_sel = filter_manager(selected,
                                         self.b,
                                         self.a,
                                         log_dict={
                                             "a": "this is a",
                                             "b": "this is b"
                                         },
                                         parallel=True,
                                         parallel_store=parallel_store)
                # only keyword args (`a` in this case here) are stored in `cfg`
                assert set(["a"]) == set(out.cfg.keys())
                assert np.array_equal(out.cfg["a"], self.a)
                assert len(out.trials) == len(sel.trials)
                # ensure our `log_dict` specification was respected
                assert "lowpass" in out._log
                assert "a = this is a" in out._log
                assert "b = this is b" in out._log

                # save and re-load result, ensure nothing funky happens
                with tempfile.TemporaryDirectory() as tdir:
                    fname = os.path.join(tdir, "dummy")
                    out.save(fname)
                    dummy = load(fname)
                    assert "a" in dummy.cfg.keys()
                    assert np.array_equal(dummy.cfg["a"], self.a)
                    assert out.filename == dummy.filename
                    assert not out.data.is_virtual
                    if select is None:
                        reference = self.orig
                    else:
                        ref = []
                        for tk, trlno in enumerate(sel.trials):
                            ref.append(
                                self.origdata.trials[trlno][sel.time[tk],
                                                            sel.channel])
                            assert np.array_equal(
                                dummy.time[tk],
                                self.sigdata.time[trlno][sel.time[tk]])
                        reference = np.vstack(ref)
                    assert self.metrix[sk](
                        np.abs(dummy.data - reference)) < self.tols[sk]
                    assert np.array_equal(dummy.channel,
                                          self.sigdata.channel[sel.channel])
                    # del dummy, out

                    # ensure out_sel is written/read correctly
                    fname2 = os.path.join(tdir, "dummy2")
                    out_sel.save(fname2)
                    dummy2 = load(fname2)
                    assert "a" in dummy2.cfg.keys()
                    assert np.array_equal(dummy2.cfg["a"], dummy.cfg["a"])
                    assert np.array_equal(dummy.data, dummy2.data)
                    assert np.array_equal(dummy.channel, dummy2.channel)
                    assert np.array_equal(dummy.time, dummy2.time)
                    assert not dummy2.data.is_virtual
                    del dummy, dummy2, out, out_sel

        client.close()