diff --git a/CHANGELOG b/CHANGELOG index 114019e..ffc26d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,11 @@ -2.0.0b1 +2.0.0a25 - feat: implement data export + - feat: implement simple computation of basic statistics - fix: pipeline modified plain dataset filter - fix: allow to remove all datasets or filters + - fix: failed to load some datasets b/c of zero-valued meta data + - fix: toggle buttons in Block Matrix not functional + - enh: update font sizes - docs: minor update 2.0.0a24 - implemented polygon filters diff --git a/docs/sec_interface.rst b/docs/sec_interface.rst index 4b394af..b868639 100644 --- a/docs/sec_interface.rst +++ b/docs/sec_interface.rst @@ -42,14 +42,14 @@ your data analysis from Shape-Out 1 to Shape-Out 2: could be possible to convert sessions (including the corresponding .tdms files), but the effort in doing so would probably exceed the effort required to just rebuild a clean analysis session in Shape-Out 2. -- Shape-Out 2 currently does not provide a linear mixed effects models - (LMM) analysis. The reason behind that is quite pragmatic: LMM analysis - in Shape-Out 1 is done using +- Shape-Out 2 does not provide a linear mixed effects models + (LMM) analysis. LMM analysis in Shape-Out 1 is done using `R/lme4 `_ and thus - requires a full R distribution shipped with Shape-Out 1. While this - blows up the download and installation size, it is also not clear - whether it would work just like that on macOS. However, if many users - need this feature, then we can think of a workaround. + requires a full R distribution shipped with Shape-Out 1. This + blows up the installation size and makes it more difficult to deploy. + Furthermore (and we are not saying that LMM Analysis is "bad") we are + also looking into other methods for determining statistical significance + which might be more intuitive to understand. Basic usage diff --git a/docs/sec_qg_mixed_effects.rst b/docs/sec_qg_mixed_effects.rst deleted file mode 100644 index cebf166..0000000 --- a/docs/sec_qg_mixed_effects.rst +++ /dev/null @@ -1,79 +0,0 @@ -.. _sec_qg_mixed_effects: - -=========================== -Comparing datasets with LMM -=========================== - -.. warning:: This section is outdated. - -Consider the following datasets. A treatment is applied three times at different -time points. For each treatment, a control measurement is performed. -For each measurement day, a reservoir measurement is performed additionally -for treatment and control. - -- Day1: - - - one sample, called "Treatment I", measured at flow rates of 0.04, - 0.08 and 0.12 µl/s and one measurement in the reservoir - - one control, called "Control I", measured at flow rates 0.04, - 0.08 and 0.12 µl/s and one measurement in the reservoir - -- Day2: - - - two samples, called "Treatment II" and "Treatment III", measured - at flow rates 0.04, 0.08 and 0.12 µl/s and one measurement in the reservoir - - two controls, called "Control II" and "Control III", measured at - flow rates 0.04, 0.08 and 0.12 µl/s and one measurement in the reservoir - -Linear mixed models (LMM) allow to assign a significance to a treatment (fixed effect) -while considering the systematic bias in-between the measurement repetitions -(random effect). - -We will assume that the datasets are loaded into Shape-Out and that -invalid events have been filtered (see e.g. :ref:`sec_qg_filtering`). -The *Analyze* configuration tab enables the comparison of an experiment -(control and treatment) and repetitions of the experiment using -LMM :cite:`Herbig2017`, :cite:`Herbig2018`. - -- **Basic analysis:** - - Assign which measurement is a control and which is a treatment by choosing - the option in the dropdown lists under Interpretation. Group the pairs of - control and treatment done in one experiment, by choosing an index number, - called Repetition. Here, Treatment I and Control I are one experiment – - called Repetition 1, Treatment II and Control II are a repetition of the - experiment – called Repetition 2, Treatment III and Control III are another - repetition of the experiment – called Repetition 3. - - Press Apply to start the calculations. A text file will open to show the results. - - The most important numbers are: - - - **Fixed effects:** - - (Intercept)-Estimate - The mean of the parameter chosen for all controls. - - treatment-Estimate - The effect size of the parameter chosen between the mean - of all controls and the mean of all treatments. - - - **Full coefficient table:** - Shows the effect size of the parameter chosen between control and - treatment for every single experiment. - - - **Model-Pr(>Chisq):** - Shows the p-value and the significance of the test. - - -- **Differential feature analysis:** - - The LMM analysis is only applicable if the respective measurements - show little difference in the reservoir for the feature chosen. - For instance, if a treatment results in non-spherical cells in the reservoir, - then the deformation recorded for the treatment might be biased towards - higher values. - In this case, the information of the reservoir measurement has to be - included by means of the differential deformation :cite:`Herbig2018`. - This can be achieved by selecting the respective reservoir measurements - in the dropdown menu. diff --git a/shapeout2/gui/analysis/ana_meta.py b/shapeout2/gui/analysis/ana_meta.py index 8082167..c2c034d 100644 --- a/shapeout2/gui/analysis/ana_meta.py +++ b/shapeout2/gui/analysis/ana_meta.py @@ -109,11 +109,14 @@ def format_config_key_value(section, key, value): tip = "" # Value formatting if dctype == float: # pretty-print floats - # determine number of decimals - dec = int(np.ceil(np.log10(1/np.abs(value)))) - if dec < 0: - dec = 0 - string = ("{:." + "{}".format(dec + 2) + "f}").format(value) + if value == 0: + string = "0.0" + else: + # determine number of decimals + dec = int(np.ceil(np.log10(1/np.abs(value)))) + if dec < 0: + dec = 0 + string = ("{:." + "{}".format(dec + 2) + "f}").format(value) else: string = str(value) diff --git a/shapeout2/gui/compute/__init__.py b/shapeout2/gui/compute/__init__.py new file mode 100644 index 0000000..9e42a4a --- /dev/null +++ b/shapeout2/gui/compute/__init__.py @@ -0,0 +1 @@ +from .comp_stats import ComputeStatistics # noqa: F401 diff --git a/shapeout2/gui/compute/comp_stats.py b/shapeout2/gui/compute/comp_stats.py new file mode 100644 index 0000000..b046826 --- /dev/null +++ b/shapeout2/gui/compute/comp_stats.py @@ -0,0 +1,176 @@ +import codecs +import numbers +import pathlib +import pkg_resources +import time + +import dclab +from PyQt5 import uic, QtWidgets + +from ...pipeline import Pipeline +from ..._version import version + +STAT_METHODS = sorted(dclab.statistics.Statistics.available_methods.keys()) +STAT_METHODS.remove("%-gated") # This does not make sense with Pipeline + + +class ComputeStatistics(QtWidgets.QDialog): + def __init__(self, parent, pipeline, *args, **kwargs): + QtWidgets.QWidget.__init__(self, parent, *args, **kwargs) + path_ui = pkg_resources.resource_filename( + "shapeout2.gui.compute", "comp_stats.ui") + uic.loadUi(path_ui, self) + # for external statistics + self.path = None + # set pipeline + self.pipeline = pipeline + # Signals + self.pushButton_path.clicked.connect(self.on_browse) + self.comboBox.currentIndexChanged.connect(self.on_combobox) + # Populate statistics methods + self.listWidget_stats.clear() + for meth in STAT_METHODS: + wid = QtWidgets.QListWidgetItem(meth) + wid.setCheckState(2) + self.listWidget_stats.addItem(wid) + # initialize rest + if len(self.pipeline.slots) == 0: + self.comboBox.setCurrentIndex(1) + else: + self.comboBox.setCurrentIndex(0) + self.on_combobox() # computes self.features + + def done(self, r): + if r: + success = self.export_statistics() + else: + success = True + if success: + super(ComputeStatistics, self).done(r) + + def export_statistics(self): + """Export statistics to .tsv""" + # get features + features = [] + for ii in range(self.listWidget_features.count()): + if self.listWidget_features.item(ii).checkState() == 2: + features.append(self.features[ii]) + # get methods + methods = [] + for ii in range(self.listWidget_stats.count()): + if self.listWidget_stats.item(ii).checkState() == 2: + methods.append(STAT_METHODS[ii]) + + prog = QtWidgets.QProgressDialog("Computing statistics...", "Abort", 1, + 1, self) + prog.setMinimumDuration(0) + time.sleep(0.01) + prog.setValue(0) + # compute statistics + values = [] + if self.comboBox.currentIndex() == 0: + # from pipeline + datasets = self.pipeline.get_datasets() + prog.setMaximum(len(datasets)) + for ii, ds in enumerate(datasets): + h, v = dclab.statistics.get_statistics(ds, + methods=methods, + features=features) + h = ["Path", "Slot", "Name"] + h + v = ["{}".format(ds.path), ii, ds.title] + v + values.append(v) + if prog.wasCanceled(): + break + prog.setValue(ii + 1) + QtWidgets.QApplication.processEvents() + else: + # from path + path = pathlib.Path(self.path) + files = sorted(path.rglob("*.rtdc")) + prog.setMaximum(len(files)) + for ii, pp in enumerate(files): + ds = dclab.new_dataset(pp) + h, v = dclab.statistics.get_statistics(ds, + methods=methods, + features=features) + h = ["Path", "Name"] + h + v = ["{}".format(ds.path), ds.title] + v + values.append(v) + prog.setValue(ii + 1) + QtWidgets.QApplication.processEvents() + path, _ = QtWidgets.QFileDialog.getSaveFileName( + self, 'Save statistics', '', 'tab-separated values (*.tsv)') + if not path: + # Abort export + return False + elif not path.endswith(".tsv"): + path += ".tsv" + + # Header + header = ["Statistics Output", + "Shape-Out {}".format(version), + "", + "\t".join(h), + ] + # Data + data = [] + for v in values: + line = [] + for vi in v: + if (isinstance(vi, numbers.Real) + and not isinstance(vi, numbers.Integral)): + line.append("{:.5e}".format(vi)) + else: + line.append("{}".format(vi)) + data.append("\t".join(line)) + # Write BOM + with codecs.open(path, "wb") as fd: + fd.write(codecs.BOM_UTF8) + # Write rest + with codecs.open(path, "a", encoding="utf-8") as fd: + for line in header: + fd.write("# " + line + "\r\n") + for line in data: + fd.write(line + "\r\n") + return True # True means success + + def on_browse(self): + out = QtWidgets.QFileDialog.getExistingDirectory(self, + 'Export directory') + if out: + self.path = out + self.lineEdit_path.setText(self.path) + self.comboBox.setCurrentIndex(1) + else: + self.path = None + self.comboBox.setCurrentIndex(0) + + def on_combobox(self): + if self.comboBox.currentIndex() == 1: + self.widget_path.show() + if self.path is None: + self.on_browse() + if self.path: + self.update_feature_list(use_pipeline=False) + # else, on_combobox is triggered again + else: + self.widget_path.hide() + self.update_feature_list(use_pipeline=True) + + def update_feature_list(self, use_pipeline=True): + if use_pipeline: + self.features = self.pipeline.get_features(scalar=True, + union=True, + label_sort=True) + else: + # This is just a cheap way of getting a label-sorted list + # of all scalar features. + empty_pipeline = Pipeline() + self.features = empty_pipeline.get_features(scalar=True, + label_sort=True) + + self.listWidget_features.clear() + for feat in self.features: + wid = QtWidgets.QListWidgetItem(dclab.dfn.feature_name2label[feat]) + wid.setCheckState(0) + self.listWidget_features.addItem(wid) diff --git a/shapeout2/gui/compute/comp_stats.ui b/shapeout2/gui/compute/comp_stats.ui new file mode 100644 index 0000000..37be45b --- /dev/null +++ b/shapeout2/gui/compute/comp_stats.ui @@ -0,0 +1,127 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + Compute statistics for multiple datasets. + + + + + + + + All datasets of the current session + + + + + All datasets in a directory + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + PATH + + + + + + + Browse + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/shapeout2/gui/main.py b/shapeout2/gui/main.py index 12a2aab..16a068b 100644 --- a/shapeout2/gui/main.py +++ b/shapeout2/gui/main.py @@ -15,8 +15,9 @@ import pyqtgraph as pg from . import analysis -from .matrix import BlockMatrix +from . import compute from . import export +from .matrix import BlockMatrix from . import pipeline_plot from . import quick_view @@ -56,6 +57,9 @@ def __init__(self): self.actionAbout.triggered.connect(self.on_action_about) # Export menu self.actionExportData.triggered.connect(self.on_action_export_data) + # Comput menu + self.actionComputeStatistics.triggered.connect( + self.on_action_compute_statistics) # Initially hide buttons self.pushButton_preset_load.hide() self.pushButton_preset_save.hide() @@ -319,6 +323,10 @@ def on_action_about(self): "Shape-Out {}".format(__version__), about_text) + def on_action_compute_statistics(self): + dlg = compute.ComputeStatistics(self, pipeline=self.pipeline) + dlg.exec() + def on_action_docs(self): webbrowser.open("https://shapeout2.readthedocs.io") diff --git a/shapeout2/gui/matrix/data_matrix.py b/shapeout2/gui/matrix/data_matrix.py index 3cc98db..cfe77a5 100644 --- a/shapeout2/gui/matrix/data_matrix.py +++ b/shapeout2/gui/matrix/data_matrix.py @@ -285,8 +285,6 @@ def enable_quickview(self, b=True): def clear(self): """Reset layout""" self._reset_layout() - self.semi_states_dataset = {} - self.semi_states_filter = {} def dragEnterEvent(self, event): print("drag enter event on data matrix") @@ -469,38 +467,38 @@ def toggle_dataset_active(self): which is defined by the signal sender :class:`MatrixDataset`. Cyclic toggling order: semi -> all -> none """ - self.semi_states_filter = {} + self.semi_states_filter = {} # sic sender = self.sender() slot_id = sender.identifier - state = self.get_slot_widget_state(slot_id) - num_actives = sum([s["active"] for s in state.values()]) + state = self.__getstate__()["elements"][slot_id] + num_actives = sum([s for s in state.values()]) # update state according to the scheme in the docstring if num_actives == 0: if slot_id in self.semi_states_dataset: # use semi state oldstate = self.semi_states_dataset[slot_id] - for key in oldstate: - if key in state: - state[key] = oldstate[key] + for filt_id in oldstate: + if filt_id in state: + state[filt_id] = oldstate[filt_id] else: # toggle all to active - for key in state: - state[key]["active"] = True + for filt_id in state: + state[filt_id] = True elif num_actives == len(state): # toggle all to inactive - for key in state: - state[key]["active"] = False + for filt_id in state: + state[filt_id] = False else: # save semi state self.semi_states_dataset[slot_id] = copy.deepcopy(state) # toggle all to active - for key in state: - state[key]["active"] = True + for filt_id in state: + state[filt_id] = True for filt_id in state: me = self.get_matrix_element(slot_id, filt_id) - me.__setstate__(state[filt_id]) + me.set_active(state[filt_id]) self.publish_matrix() @QtCore.pyqtSlot(bool) @@ -527,43 +525,43 @@ def toggle_filter_active(self): which is defined by the signal sender :class:`MatrixFilter`. Cyclic toggling order: semi -> all -> none """ - self.semi_states_dataset = {} + self.semi_states_dataset = {} # sic sender = self.sender() filt_id = sender.identifier states = self.__getstate__()["elements"] state = {} - for key in states: - state[key] = states[key][filt_id] + for slot_id in states: + state[slot_id] = states[slot_id][filt_id] - num_actives = sum([s["active"] for s in state.values()]) + num_actives = sum(list(state.values())) # update state according to the scheme in the docstring if num_actives == 0: if filt_id in self.semi_states_filter: # use semi state oldstate = self.semi_states_filter[filt_id] - for key in oldstate: - if key in state: - state[key] = oldstate[key] + for slot_id in oldstate: + if slot_id in state: + state[slot_id] = oldstate[slot_id] else: # toggle all to active - for key in state: - state[key]["active"] = True + for slot_id in state: + state[slot_id] = True elif num_actives == len(state): # toggle all to inactive - for key in state: - state[key]["active"] = False + for slot_id in state: + state[slot_id] = False else: # save semi state self.semi_states_filter[filt_id] = copy.deepcopy(state) # toggle all to active - for key in state: - state[key]["active"] = True + for slot_id in state: + state[slot_id] = True for slot_id in state: me = self.get_matrix_element(slot_id, filt_id) - me.__setstate__(state[slot_id]) + me.set_active(state[slot_id]) self.publish_matrix() @QtCore.pyqtSlot(bool) diff --git a/shapeout2/gui/matrix/dm_dataset.py b/shapeout2/gui/matrix/dm_dataset.py index 3d86b3f..bdb7325 100644 --- a/shapeout2/gui/matrix/dm_dataset.py +++ b/shapeout2/gui/matrix/dm_dataset.py @@ -79,6 +79,6 @@ def update_content(self): title = meta_tool.get_repr(self.path, append_path=True) self.setToolTip(title) self.label.setToolTip(title) - if len(title) > 8: - title = title[:5] + "..." + if len(title) > 12: + title = title[:9] + "..." self.label.setText(title) diff --git a/shapeout2/gui/matrix/dm_dataset.ui b/shapeout2/gui/matrix/dm_dataset.ui index 9e3ed84..59daed7 100644 --- a/shapeout2/gui/matrix/dm_dataset.ui +++ b/shapeout2/gui/matrix/dm_dataset.ui @@ -103,6 +103,11 @@ 0 + + + 9 + + Name diff --git a/shapeout2/gui/matrix/dm_element.py b/shapeout2/gui/matrix/dm_element.py index 7c8f523..f503824 100644 --- a/shapeout2/gui/matrix/dm_element.py +++ b/shapeout2/gui/matrix/dm_element.py @@ -52,6 +52,11 @@ def mousePressEvent(self, event): self.update_content(quickview) event.accept() + def set_active(self, b=True): + state = self.__getstate__() + state["active"] = b + self.__setstate__(state) + def update_content(self, quickview=False): if self.invalid: color = "#DCDCDC" # gray diff --git a/shapeout2/gui/matrix/dm_filter.py b/shapeout2/gui/matrix/dm_filter.py index f0fdb64..f8513d0 100644 --- a/shapeout2/gui/matrix/dm_filter.py +++ b/shapeout2/gui/matrix/dm_filter.py @@ -94,8 +94,8 @@ def on_modify(self): def update_content(self): """Reset tool tips and title""" self.label.setToolTip(self.name) - if len(self.name) > 8: - title = self.name[:5]+"..." + if len(self.name) > 12: + title = self.name[:9]+"..." else: title = self.name self.checkBox.blockSignals(True) diff --git a/shapeout2/gui/matrix/dm_filter.ui b/shapeout2/gui/matrix/dm_filter.ui index 5edcbe3..a11bf75 100644 --- a/shapeout2/gui/matrix/dm_filter.ui +++ b/shapeout2/gui/matrix/dm_filter.ui @@ -103,6 +103,11 @@ 0 + + + 9 + + Name diff --git a/shapeout2/gui/matrix/plot_matrix.py b/shapeout2/gui/matrix/plot_matrix.py index 3d6987b..eb41429 100644 --- a/shapeout2/gui/matrix/plot_matrix.py +++ b/shapeout2/gui/matrix/plot_matrix.py @@ -276,43 +276,42 @@ def toggle_plot_active(self): which is defined by the signal sender :class:`MatrixPlot`. Cyclic toggling order: semi -> all -> none """ - self.semi_states_dataset = {} sender = self.sender() - sid = sender.identifier + plot_id = sender.identifier states = self.__getstate__()["elements"] state = {} - for key in states: - state[key] = states[key][sid] + for slot_id in states: + state[slot_id] = states[slot_id][plot_id] - num_actives = sum([s["active"] for s in state.values()]) + num_actives = sum(list(state.values())) # update state according to the scheme in the docstring if num_actives == 0: - if sid in self.semi_states_plot: + if plot_id in self.semi_states_plot: # use semi state - oldstate = self.semi_states_plot[sid] - for key in oldstate: - if key in state: - state[key] = oldstate[key] + oldstate = self.semi_states_plot[plot_id] + for slot_id in oldstate: + if slot_id in state: + state[slot_id] = oldstate[slot_id] else: # toggle all to active - for key in state: - state[key]["active"] = True + for slot_id in state: + state[slot_id] = True elif num_actives == len(state): # toggle all to inactive - for key in state: - state[key]["active"] = False + for slot_id in state: + state[slot_id] = False else: # save semi state - self.semi_states_plot[sid] = copy.deepcopy(state) + self.semi_states_plot[plot_id] = copy.deepcopy(state) # toggle all to active - for key in state: - state[key]["active"] = True + for slot_id in state: + state[slot_id] = True - for dsid in state: - me = self.get_matrix_element(dsid, sid) - me.__setstate__(state[dsid]) + for slot_id in state: + me = self.get_matrix_element(slot_id, plot_id) + me.set_active(state[slot_id]) self.publish_matrix() def update_content(self): diff --git a/shapeout2/gui/matrix/pm_plot.py b/shapeout2/gui/matrix/pm_plot.py index c02f438..f18f5b8 100644 --- a/shapeout2/gui/matrix/pm_plot.py +++ b/shapeout2/gui/matrix/pm_plot.py @@ -69,8 +69,8 @@ def on_modify(self): def update_content(self): """Reset tool tips and title""" self.label.setToolTip(self.name) - if len(self.name) > 8: - title = self.name[:5]+"..." + if len(self.name) > 12: + title = self.name[:9]+"..." else: title = self.name self.label.setText(title) diff --git a/shapeout2/gui/matrix/pm_plot.ui b/shapeout2/gui/matrix/pm_plot.ui index 9146f23..ff64380 100644 --- a/shapeout2/gui/matrix/pm_plot.ui +++ b/shapeout2/gui/matrix/pm_plot.ui @@ -103,6 +103,11 @@ 0 + + + 9 + + Name diff --git a/shapeout2/gui/pipeline_plot.py b/shapeout2/gui/pipeline_plot.py index d5aff32..efdb1ad 100644 --- a/shapeout2/gui/pipeline_plot.py +++ b/shapeout2/gui/pipeline_plot.py @@ -4,7 +4,7 @@ import dclab from dclab import kde_contours import numpy as np -from PyQt5 import uic, QtCore, QtWidgets +from PyQt5 import uic, QtCore, QtGui, QtWidgets import pyqtgraph as pg from ..pipeline import Plot @@ -13,6 +13,7 @@ class PipelinePlot(QtWidgets.QWidget): + """Implements the plotting pipeline using pyqtgraph""" def __init__(self, parent, pipeline, plot_id, *args, **kwargs): QtWidgets.QWidget.__init__(self, parent=parent, *args, **kwargs) path_ui = pkg_resources.resource_filename( @@ -46,7 +47,9 @@ def update_content(self): labelx, labely = get_axes_labels(plot_state, slot_states) - self.plot_layout.addLabel(lay["name"], colspan=2) + self.plot_layout.addLabel(lay["name"], colspan=2, + # default size + 2 + size="{}pt".format(QtGui.QFont().pointSize() + 2)) self.plot_layout.nextRow() self.plot_layout.addLabel(labely, angle=-90) @@ -178,7 +181,9 @@ def redraw(self, dslist, slot_states, plot_state): self.setTitle(title) if plot_state["scatter"]["show event count"]: # set event count - chtml = "{} events" + chtml = "".format( + # default font size - 1 + QtGui.QFont().pointSize() - 1) + "{} events" label = QtWidgets.QGraphicsTextItem( "", # This is kind of hackish: set the parent to the right diff --git a/shapeout2/gui/rangecontrol.py b/shapeout2/gui/rangecontrol.py index 8ea4218..ae04268 100644 --- a/shapeout2/gui/rangecontrol.py +++ b/shapeout2/gui/rangecontrol.py @@ -195,10 +195,13 @@ def setSpinLimits(self, vmin, vmax): # decimals if not self.is_integer: - # two significant digits - dec = -int(np.ceil(np.log10(vmax-vmin))) + 4 - if dec <= 0: + if vmax == vmin: dec = 1 + else: + # two significant digits + dec = int(np.ceil(np.log10(1/np.abs(vmax-vmin)))) + 5 + if dec <= 0: + dec = 1 self.doubleSpinBox_min.setDecimals(dec) self.doubleSpinBox_max.setDecimals(dec) self.doubleSpinBox_min.setSingleStep(10**-dec) diff --git a/shapeout2/pipeline/core.py b/shapeout2/pipeline/core.py index f8e397c..a0ea8b3 100644 --- a/shapeout2/pipeline/core.py +++ b/shapeout2/pipeline/core.py @@ -312,6 +312,10 @@ def get_dataset(self, slot_index, filt_index=-1, apply_filter=True): dsend.apply_filter() return dsend + def get_datasets(self): + """Return all datasets with filters applied""" + return [self.get_dataset(ii) for ii in range(len(self.slots))] + def get_features(self, scalar=False, label_sort=False, union=False, plot_id=None): """Return a list of features in the pipeline diff --git a/shapeout2/pipeline/dataslot.py b/shapeout2/pipeline/dataslot.py index 9d90765..67d6b76 100644 --- a/shapeout2/pipeline/dataslot.py +++ b/shapeout2/pipeline/dataslot.py @@ -1,6 +1,8 @@ import dclab import numpy as np +from .. import meta_tool + class Dataslot(object): """Handles datasets in a pipeline""" @@ -18,7 +20,7 @@ def __init__(self, path, identifier=None, name=None): identifier = "Dataslot_{}".format(Dataslot._instance_counter) if name is None: - name = identifier + name = meta_tool.get_rtdc_config(path)["experiment"]["sample"] #: unique identifier of the filter self.identifier = identifier #: user-defined name of the filter