def test_logging(self): with tempfile.TemporaryDirectory() as tdir: fname = os.path.join(tdir, "dummy") dummy = generate_artificial_data(inmemory=True) ldum = len(dummy._log) save(dummy, filename=fname) # ensure saving is logged correctly assert len(dummy._log) > ldum assert dummy.filename in dummy._log assert dummy.filename + FILE_EXT["info"] in dummy._log assert dummy.cfg["method"] == "save" assert dummy.filename in dummy.cfg["files"] assert dummy.filename + FILE_EXT["info"] in dummy.cfg["files"] # ensure loading is logged correctly dummy2 = load(filename=fname + ".analog") assert len(dummy2._log) > len(dummy._log) assert dummy2.filename in dummy2._log assert dummy2.filename + FILE_EXT["info"] in dummy._log assert dummy2.cfg.cfg["method"] == "load" assert dummy2.filename in dummy2.cfg.cfg["files"] assert dummy2.filename + FILE_EXT["info"] in dummy2.cfg.cfg[ "files"] # Delete all open references to file objects b4 closing tmp dir del dummy, dummy2
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])
class TestAnalogDataPlotting(): nChannels = 16 nTrials = 8 seed = 130810 # To use `selectdata` w/``trials = None``-raw plotting, the trials must not # overlap - construct separate set of `raw*` AnalogData-objects for testing! dataReg = generate_artificial_data(nTrials=nTrials, nChannels=nChannels, seed=seed, equidistant=True, overlapping=True) dataInv = generate_artificial_data(nTrials=nTrials, nChannels=nChannels, seed=seed, equidistant=True, overlapping=True, dimord=dataReg.dimord[::-1]) rawReg = generate_artificial_data(nTrials=nTrials, nChannels=nChannels, seed=seed, equidistant=True, overlapping=False) rawInv = generate_artificial_data(nTrials=nTrials, nChannels=nChannels, seed=seed, equidistant=True, overlapping=False, dimord=rawReg.dimord[::-1]) trials = ["all", [4, 3, 2, 2, 7]] channels = ["all", [14, 13, 12, 12, 15]] toilim = [None, [1.9, 2.5], [2.1, np.inf]] # trlToilim = lambda self, trl, tlim: None if trl is None else tlim def test_singlepanelplot(self): # Lowest possible dpi setting permitting valid png comparisons in `figs_equal` mpl.rcParams["figure.dpi"] = 150 # Test everything except "raw" plotting for trials in self.trials: for channels in self.channels: for toilim in self.toilim: for avg_channels in [True, False]: # Render figure using singlepanelplot mechanics, recreate w/`selectdata`, # results must be identical fig1 = self.dataReg.singlepanelplot( trials=trials, channels=channels, toilim=toilim, avg_channels=avg_channels) selected = self.dataReg.selectdata(trials=trials, channels=channels, toilim=toilim) fig2 = selected.singlepanelplot( trials="all", avg_channels=avg_channels) # Recreate `fig1` and `fig2` in a single sweep by using # `spy.singlepanelplot` w/multiple input objects fig1a, fig2a = singlepanelplot( self.dataReg, self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_channels=avg_channels, overlay=False) # `fig2a` is based on `dataInv` - be more lenient there tol = None if avg_channels: tol = 1e-2 assert figs_equal(fig1, fig2) assert figs_equal(fig1, fig1a) assert figs_equal(fig2, fig2a, tol=tol) assert figs_equal(fig1, fig2a, tol=tol) # Create overlay figures: `fig3` combines `dataReg` and # `dataInv` - must be identical to overlaying `fig1` w/`dataInv` fig3 = singlepanelplot(self.dataReg, self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_channels=avg_channels, overlay=True) fig1 = singlepanelplot(self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_channels=avg_channels, fig=fig1) assert figs_equal(fig1, fig3) # Close figures to avoid memory overflow plt.close("all") # The `selectdata(trials="all")` part requires consecutive trials! for channels in self.channels: for avg_channels in [True, False]: fig1 = self.rawReg.singlepanelplot(trials=None, channels=channels, avg_channels=avg_channels) selected = self.rawReg.selectdata(trials="all", channels=channels) fig2 = selected.singlepanelplot(trials=None, avg_channels=avg_channels) assert figs_equal(fig1, fig2) plt.close("all") # Do not allow selecting time-intervals w/o trial-specification with pytest.raises(SPYValueError): self.dataReg.singlepanelplot(trials=None, toilim=self.toilim[1]) # Do not overlay multi-channel plot w/chan-average and unequal channel-count multiChannelFig = self.dataReg.singlepanelplot( avg_channels=False, channels=self.channels[1]) with pytest.raises(SPYValueError): self.dataReg.singlepanelplot(avg_channels=True, fig=multiChannelFig) with pytest.raises(SPYValueError): self.dataReg.singlepanelplot(channels="all", fig=multiChannelFig) # Do not overlay multi-panel plot w/figure produced by `singlepanelplot` multiChannelFig.nTrialsPanels = 99 with pytest.raises(SPYValueError): self.dataReg.singlepanelplot(fig=multiChannelFig) multiChannelFig.nChanPanels = 99 with pytest.raises(SPYValueError): self.dataReg.singlepanelplot(fig=multiChannelFig) # Ensure grid and title specifications are rendered correctly theTitle = "A title" gridFig = self.dataReg.singlepanelplot(grid=True) assert gridFig.axes[0].get_xgridlines()[0].get_visible() == True titleFig = self.dataReg.singlepanelplot(title=theTitle) assert titleFig.axes[0].get_title() == theTitle plt.close("all") def test_multipanelplot(self): # Lowest possible dpi setting permitting valid png comparisons in `figs_equal` mpl.rcParams["figure.dpi"] = 75 # Test everything except "raw" plotting for trials in self.trials: for channels in self.channels: for toilim in self.toilim: for avg_trials in [True, False]: for avg_channels in [True, False]: # ``avg_trials == avg_channels == True`` yields `SPYWarning` to # use `singlepanelplot` -> no figs to compare here if avg_trials is avg_channels is True: continue # Render figure using multipanelplot mechanics, recreate w/`selectdata`, # results must be identical fig1 = self.dataReg.multipanelplot( trials=trials, channels=channels, toilim=toilim, avg_trials=avg_trials, avg_channels=avg_channels) selected = self.dataReg.selectdata( trials=trials, channels=channels, toilim=toilim) fig2 = selected.multipanelplot( trials="all", avg_trials=avg_trials, avg_channels=avg_channels) # Recreate `fig1` and `fig2` in a single sweep by using # `spy.multipanelplot` w/multiple input objects fig1a, fig2a = multipanelplot( self.dataReg, self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_trials=avg_trials, avg_channels=avg_channels, overlay=False) # `selectdata` preserves trial order but not numbering: ensure # plot titles are correct, but then remove them to allow # comparison of `selected` and `dataReg` figures figTitleLists = [] if avg_trials is False: if trials != "all": for fig in [fig1, fig1a]: titleList = [] for ax in fig.axes: titleList.append(copy(ax.title)) ax.set_title("") titles = [ title.get_text() for title in titleList ] assert titles == [ "Trial #{}".format(trlno) for trlno in trials ] figTitleLists.append(titleList) for fig in [fig2, fig2a]: for ax in fig.axes: ax.set_title("") # After (potential) axes title removal, compare figures; # `fig2a` is based on `dataInv` - be more lenient there tol = None if avg_channels: tol = 1e-2 assert figs_equal(fig1, fig2) assert figs_equal(fig1, fig1a) assert figs_equal(fig2, fig2a, tol=tol) assert figs_equal(fig1, fig2a, tol=tol) # If necessary, restore axes title from `figTitleLists` if figTitleLists: for k, ax in enumerate(fig1.axes): ax.title = figTitleLists[0][k] # Create overlay figures: `fig3` combines `dataReg` and # `dataInv` - must be identical to overlaying `fig1` w/`dataInv` fig3 = multipanelplot(self.dataReg, self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_trials=avg_trials, avg_channels=avg_channels) fig4 = multipanelplot(self.dataInv, trials=trials, channels=channels, toilim=toilim, avg_trials=avg_trials, avg_channels=avg_channels, fig=fig1) assert figs_equal(fig3, fig4) plt.close("all") # The `selectdata(trials="all")` part requires consecutive trials! Add'ly, # `avg_channels` must be `False`, otherwise single-panel plot warning is triggered for channels in self.channels: fig1 = self.rawReg.multipanelplot(trials=None, channels=channels, avg_channels=False, avg_trials=False) selected = self.rawReg.selectdata(trials="all", channels=channels) fig2 = selected.multipanelplot(trials=None, avg_channels=False) assert figs_equal(fig1, fig2) plt.close("all") # Do not allow selecting time-intervals w/o trial-specification with pytest.raises(SPYValueError): self.dataReg.multipanelplot(trials=None, toilim=self.toilim[1]) # Panels = trials, each panel shows single (averaged) channel multiTrialSingleChanFig = self.dataReg.multipanelplot( trials=self.trials[1], avg_trials=False, avg_channels=True) with pytest.raises(SPYValueError): # trial-count does not match up self.dataReg.multipanelplot(trials="all", avg_trials=False, avg_channels=True, fig=multiTrialSingleChanFig) with pytest.raises(SPYValueError): # multi-channel overlay self.dataReg.multipanelplot(trials=self.trials[1], avg_trials=False, avg_channels=False, fig=multiTrialSingleChanFig) # Panels = trials, each panel shows multiple channels multiTrialMultiChanFig = self.dataReg.multipanelplot( trials=self.trials[1], avg_trials=False, avg_channels=False) with pytest.raises(SPYValueError): # no trial specification provided self.dataReg.multipanelplot(trials=None, avg_trials=False, avg_channels=False, fig=multiTrialMultiChanFig) with pytest.raises(SPYValueError): # channel-count does not match up self.dataReg.multipanelplot(trials=self.trials[1], channels=self.channels[1], avg_trials=False, avg_channels=False, fig=multiTrialMultiChanFig) with pytest.raises(SPYValueError): # single-channel overlay self.dataReg.multipanelplot(trials=self.trials[1], avg_trials=False, avg_channels=True, fig=multiTrialMultiChanFig) # Panels = channels multiChannelFig = self.dataReg.multipanelplot(trials=self.trials[1], avg_trials=True, avg_channels=False) with pytest.raises(SPYValueError): # multi-trial overlay self.dataReg.multipanelplot(trials=self.trials[1], avg_trials=False, avg_channels=False, fig=multiChannelFig) with pytest.raises(SPYValueError): # channel-count does not match up self.dataReg.multipanelplot(trials=self.trials[1], channels=self.channels[1], avg_trials=True, avg_channels=False, fig=multiChannelFig) # Do not overlay single-panel plot w/figure produced by `multipanelplot` singlePanelFig = self.dataReg.singlepanelplot() with pytest.raises(SPYValueError): self.dataReg.multipanelplot(fig=singlePanelFig) # Ensure grid and title specifications are rendered correctly theTitle = "A title" gridFig = self.dataReg.multipanelplot(avg_trials=False, avg_channels=True, grid=True) assert all([ gridFig.axes[k].get_xgridlines()[0].get_visible() for k in range(self.nTrials) ]) titleFig = self.dataReg.multipanelplot(avg_trials=False, avg_channels=True, title=theTitle) assert titleFig._suptitle.get_text() == theTitle
class TestSpyCalls(): nChan = 13 nObjs = nChan # Generate `nChan` objects whose channel-labeling scheme obeys: # ob1.channel = ["A", "B", "C", ..., "M"] # ob2.channel = [ "B", "C", ..., "M", "N"] # ob3.channel = [ "C", ..., "M", "N", "O"] # ... # ob13.channel = [ "M", "N", "O", ..., "Z"] # Thus, channel no. 13 ("M") is common across all objects dataObjs = [] for n in range(nObjs): obj = generate_artificial_data(nChannels=nChan, inmemory=False) obj.channel = list(string.ascii_uppercase[n : nChan + n]) dataObjs.append(obj) data = dataObjs[0] def test_validcallstyles(self): # data positional fname, = group_objects(self.data) assert fname == self.data.filename # data as keyword fname, = group_objects(data=self.data) assert fname == self.data.filename # data in cfg cfg = StructDict() cfg.data = self.data fname, = group_objects(cfg) assert fname == self.data.filename # 1. data positional, 2. cfg positional cfg = StructDict() cfg.groupbychan = None fname, = group_objects(self.data, cfg) assert fname == self.data.filename # 1. cfg positional, 2. data positional fname, = group_objects(cfg, self.data) assert fname == self.data.filename # data positional, cfg as keyword fname, = group_objects(self.data, cfg=cfg) assert fname == self.data.filename # cfg positional, data as keyword fname, = group_objects(cfg, data=self.data) assert fname == self.data.filename # both keywords fname, = group_objects(cfg=cfg, data=self.data) assert fname == self.data.filename def test_invalidcallstyles(self): # expected error messages errmsg1 = "expected Syncopy data object(s) provided either via " +\ "`cfg`/keyword or positional arguments, not both" errmsg2 = "expected Syncopy data object(s) provided either via `cfg` " +\ "or as keyword argument, not both" errmsg3 = "expected either 'data' or 'dataset' in `cfg`/keywords, not both" # ensure things break reliably for 'data' as well as 'dataset' for key in ["data", "dataset"]: # data + cfg w/data cfg = StructDict() cfg[key] = self.data with pytest.raises(SPYValueError) as exc: group_objects(self.data, cfg) assert errmsg1 in str(exc.value) # data as positional + kwarg with pytest.raises(SPYValueError) as exc: group_objects(self.data, data=self.data) assert errmsg1 in str(exc.value) with pytest.raises(SPYValueError) as exc: group_objects(self.data, dataset=self.data) assert errmsg1 in str(exc.value) # cfg w/data + kwarg + positional with pytest.raises(SPYValueError) as exc: group_objects(self.data, cfg, data=self.data) assert errmsg1 in str(exc.value) with pytest.raises(SPYValueError) as exc: group_objects(self.data, cfg, dataset=self.data) assert errmsg1 in str(exc.value) # cfg w/data + kwarg with pytest.raises(SPYValueError) as exc: group_objects(cfg, data=self.data) assert errmsg2 in str(exc.value) with pytest.raises(SPYValueError) as exc: group_objects(cfg, dataset=self.data) assert errmsg2 in str(exc.value) # cfg (no data) but double-whammied cfg = StructDict() cfg.groupbychan = None with pytest.raises(SPYValueError)as exc: group_objects(self.data, cfg, cfg=cfg) assert "expected `cfg` either as positional or keyword argument, not both" in str(exc.value) # keyword set via cfg and kwarg with pytest.raises(SPYValueError) as exc: group_objects(self.data, cfg, groupbychan="invalid") assert "'non-default value for groupbychan'; expected no keyword arguments" in str(exc.value) # both data and dataset in cfg/keywords cfg = StructDict() cfg.data = self.data cfg.dataset = self.data with pytest.raises(SPYValueError)as exc: group_objects(cfg) assert errmsg3 in str(exc.value) with pytest.raises(SPYValueError)as exc: group_objects(data=self.data, dataset=self.data) assert errmsg3 in str(exc.value) # data/dataset do not contain Syncopy object with pytest.raises(SPYError)as exc: group_objects(data="invalid") assert "`data` must be Syncopy data object(s)!" in str(exc.value) # cfg is not dict/StructDict with pytest.raises(SPYTypeError)as exc: group_objects(cfg="invalid") assert "Wrong type of cfg: expected dictionary-like" in str(exc.value) # no data input whatsoever with pytest.raises(SPYError)as exc: group_objects("invalid") assert "missing mandatory argument: `data`" in str(exc.value) def test_varargin(self): # data positional allFnames = group_objects(*self.dataObjs) assert allFnames == [obj.filename for obj in self.dataObjs] # data in cfg cfg = StructDict() cfg.data = self.dataObjs fnameList = group_objects(cfg) assert allFnames == fnameList # group objects by single-letter "channels" in various ways for letter in ["L", "E", "I", "A"]: letterIdx = string.ascii_uppercase.index(letter) nOccurences = letterIdx + 1 # data positional + keyword to get "reference" groupList = group_objects(*self.dataObjs, groupbychan=letter) assert len(groupList) == nOccurences # 1. data positional, 2. cfg positional cfg = StructDict() cfg.groupbychan = letter fnameList = group_objects(*self.dataObjs, cfg) assert groupList == fnameList # 1. cfg positional, 2. data positional fnameList = group_objects(cfg, *self.dataObjs) assert groupList == fnameList # data positional, cfg as keyword fnameList = group_objects(*self.dataObjs, cfg=cfg) assert groupList == fnameList # cfg w/data + keyword cfg = StructDict() cfg.dataset = self.dataObjs cfg.groupbychan = letter fnameList = group_objects(cfg) assert groupList == fnameList # data positional + select keyword fnameList = group_objects(*self.dataObjs[:letterIdx + 1], select={"channels": [letter]}) assert groupList == fnameList # data positional + cfg w/select cfg = StructDict() cfg.select = {"channels": [letter]} fnameList = group_objects(*self.dataObjs[:letterIdx + 1], cfg) assert groupList == fnameList # cfg w/data + select cfg = StructDict() cfg.data = self.dataObjs[:letterIdx + 1] cfg.select = {"channels": [letter]} fnameList = group_objects(cfg) assert groupList == fnameList # invalid selection with pytest.raises(SPYValueError) as exc: group_objects(*self.dataObjs, select={"channels": ["Z"]}) assert "expected list/array of channel existing names or indices" in str(exc.value) # data does not only contain Syncopy objects cfg = StructDict() cfg.data = self.dataObjs + ["invalid"] with pytest.raises(SPYError)as exc: group_objects(cfg) assert "`data` must be Syncopy data object(s)!" in str(exc.value)
def test_object_padding(self): # construct AnalogData object w/trials of unequal lengths adata = generate_artificial_data(nTrials=7, nChannels=16, equidistant=False, inmemory=False) timeAxis = adata.dimord.index("time") # test dictionary generation for `create_new = False`: ensure all trials # have padded length of `total_time` seconds (1 sample tolerance) total_time = 30 pad_list = padding(adata, "zero", pad="absolute", padlength=total_time, unit="time", create_new=False) for tk, trl in enumerate(adata.trials): assert "pad_width" in pad_list[tk].keys() assert "constant_values" in pad_list[tk].keys() trl_time = (pad_list[tk]["pad_width"][timeAxis, :].sum() + trl.shape[timeAxis]) / adata.samplerate assert trl_time - total_time < 1 / adata.samplerate # jumble axes of `AnalogData` object and compute max. trial length adata2 = generate_artificial_data(nTrials=7, nChannels=16, equidistant=False, inmemory=False, dimord=adata.dimord[::-1]) timeAxis2 = adata2.dimord.index("time") maxtrllen = 0 for trl in adata2.trials: maxtrllen = max(maxtrllen, trl.shape[timeAxis2]) # symmetric `maxlen` padding: 1 sample tolerance pad_list2 = padding(adata2, "zero", pad="maxlen", create_new=False) for tk, trl in enumerate(adata2.trials): trl_len = pad_list2[tk]["pad_width"][ timeAxis2, :].sum() + trl.shape[timeAxis2] assert (trl_len - maxtrllen) <= 1 pad_list2 = padding(adata2, "zero", pad="maxlen", prepadlength=True, postpadlength=True, create_new=False) for tk, trl in enumerate(adata2.trials): trl_len = pad_list2[tk]["pad_width"][ timeAxis2, :].sum() + trl.shape[timeAxis2] assert (trl_len - maxtrllen) <= 1 # pre- and post- `maxlen` padding: no tolerance pad_list2 = padding(adata2, "zero", pad="maxlen", prepadlength=True, create_new=False) for tk, trl in enumerate(adata2.trials): trl_len = pad_list2[tk]["pad_width"][ timeAxis2, :].sum() + trl.shape[timeAxis2] assert trl_len == maxtrllen pad_list2 = padding(adata2, "zero", pad="maxlen", postpadlength=True, create_new=False) for tk, trl in enumerate(adata2.trials): trl_len = pad_list2[tk]["pad_width"][ timeAxis2, :].sum() + trl.shape[timeAxis2] assert trl_len == maxtrllen # `maxlen'-specific errors: `padlength` wrong type, wrong combo with `prepadlength` with pytest.raises(SPYTypeError): padding(adata, "zero", pad="maxlen", padlength=self.ns, create_new=False) with pytest.raises(SPYTypeError): padding(adata, "zero", pad="maxlen", prepadlength=self.ns, create_new=False) with pytest.raises(SPYTypeError): padding(adata, "zero", pad="maxlen", padlength=self.ns, prepadlength=True, create_new=False)
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()