dcscope 2.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dcscope/__init__.py +17 -0
- dcscope/__main__.py +44 -0
- dcscope/_version.py +21 -0
- dcscope/extensions.py +250 -0
- dcscope/gui/__init__.py +2 -0
- dcscope/gui/analysis/__init__.py +10 -0
- dcscope/gui/analysis/ana_basins.py +110 -0
- dcscope/gui/analysis/ana_basins.ui +143 -0
- dcscope/gui/analysis/ana_filter.py +341 -0
- dcscope/gui/analysis/ana_filter.ui +364 -0
- dcscope/gui/analysis/ana_log.py +122 -0
- dcscope/gui/analysis/ana_log.ui +100 -0
- dcscope/gui/analysis/ana_meta.py +207 -0
- dcscope/gui/analysis/ana_meta.ui +141 -0
- dcscope/gui/analysis/ana_plot.py +638 -0
- dcscope/gui/analysis/ana_plot.ui +1060 -0
- dcscope/gui/analysis/ana_slot.py +504 -0
- dcscope/gui/analysis/ana_slot.ui +684 -0
- dcscope/gui/analysis/ana_tables.py +227 -0
- dcscope/gui/analysis/ana_tables.ui +195 -0
- dcscope/gui/analysis/ana_view.py +69 -0
- dcscope/gui/analysis/ana_view.ui +269 -0
- dcscope/gui/analysis/dlg_slot_reorder.py +55 -0
- dcscope/gui/analysis/dlg_slot_reorder.ui +138 -0
- dcscope/gui/bulk/__init__.py +2 -0
- dcscope/gui/bulk/bulk_emodulus.py +200 -0
- dcscope/gui/bulk/bulk_emodulus.ui +245 -0
- dcscope/gui/compute/__init__.py +3 -0
- dcscope/gui/compute/comp_lme4.py +115 -0
- dcscope/gui/compute/comp_lme4.ui +194 -0
- dcscope/gui/compute/comp_lme4_dataset.py +51 -0
- dcscope/gui/compute/comp_lme4_dataset.ui +92 -0
- dcscope/gui/compute/comp_lme4_results.py +120 -0
- dcscope/gui/compute/comp_lme4_results.ui +268 -0
- dcscope/gui/compute/comp_stats.py +223 -0
- dcscope/gui/compute/comp_stats.ui +187 -0
- dcscope/gui/dcor/__init__.py +304 -0
- dcscope/gui/dcor/dcor.ui +118 -0
- dcscope/gui/export/__init__.py +3 -0
- dcscope/gui/export/e2data.py +394 -0
- dcscope/gui/export/e2data.ui +367 -0
- dcscope/gui/export/e2filter.py +76 -0
- dcscope/gui/export/e2filter.ui +111 -0
- dcscope/gui/export/e2plot.py +94 -0
- dcscope/gui/export/e2plot.ui +175 -0
- dcscope/gui/main.py +1089 -0
- dcscope/gui/main.ui +579 -0
- dcscope/gui/matrix/__init__.py +4 -0
- dcscope/gui/matrix/block_matrix.py +141 -0
- dcscope/gui/matrix/block_matrix.ui +207 -0
- dcscope/gui/matrix/data_matrix.py +574 -0
- dcscope/gui/matrix/dm_dataset.py +116 -0
- dcscope/gui/matrix/dm_dataset.ui +348 -0
- dcscope/gui/matrix/dm_element.py +111 -0
- dcscope/gui/matrix/dm_element.ui +169 -0
- dcscope/gui/matrix/dm_filter.py +115 -0
- dcscope/gui/matrix/dm_filter.ui +287 -0
- dcscope/gui/matrix/plot_matrix.py +316 -0
- dcscope/gui/matrix/pm_element.py +39 -0
- dcscope/gui/matrix/pm_plot.py +86 -0
- dcscope/gui/matrix/pm_plot.ui +249 -0
- dcscope/gui/pipeline_plot.py +797 -0
- dcscope/gui/pipeline_plot.ui +66 -0
- dcscope/gui/preferences.py +380 -0
- dcscope/gui/preferences.ui +554 -0
- dcscope/gui/quick_view/__init__.py +1 -0
- dcscope/gui/quick_view/qv_main.py +1072 -0
- dcscope/gui/quick_view/qv_main.ui +1297 -0
- dcscope/gui/quick_view/qv_scatter.py +284 -0
- dcscope/gui/quick_view/qv_style.css +10 -0
- dcscope/gui/update.py +90 -0
- dcscope/gui/widgets/__init__.py +15 -0
- dcscope/gui/widgets/bg_thread.py +29 -0
- dcscope/gui/widgets/bulk_list.py +79 -0
- dcscope/gui/widgets/bulk_list.ui +95 -0
- dcscope/gui/widgets/double_spin_box_nan.py +75 -0
- dcscope/gui/widgets/feature_combobox.py +140 -0
- dcscope/gui/widgets/get_path.py +42 -0
- dcscope/gui/widgets/key_value_table_widget.css +12 -0
- dcscope/gui/widgets/key_value_table_widget.py +75 -0
- dcscope/gui/widgets/mdi_subwindow_wo_close.py +12 -0
- dcscope/gui/widgets/qrangeslider.py +262 -0
- dcscope/gui/widgets/rangecontrol.py +308 -0
- dcscope/gui/widgets/rangecontrol.ui +136 -0
- dcscope/gui/widgets/simple_image_view.py +55 -0
- dcscope/gui/widgets/simple_plot_widget.py +113 -0
- dcscope/gui/widgets/so_colorbaritem.py +29 -0
- dcscope/gui/widgets/wait_cursor.py +33 -0
- dcscope/idiom.py +26 -0
- dcscope/img/__init__.py +2 -0
- dcscope/img/icon-theme/breeze/COPYING-ICONS +209 -0
- dcscope/img/icon-theme/breeze/COPYING.LIB +510 -0
- dcscope/img/icon-theme/breeze/README +5 -0
- dcscope/img/icon-theme/breeze/actions/16/application-exit.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/16/code-context.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-cancel.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-close.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-messages.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-ok-apply.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-ok.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-open-folder.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-open.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-save.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/documentinfo.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/16/draw-watercolor.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-clear-all.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-clear.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-paste.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/globe.svg +16 -0
- dcscope/img/icon-theme/breeze/actions/16/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/list-add.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/messagebox_warning.svg +20 -0
- dcscope/img/icon-theme/breeze/actions/16/object-columns.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/object-order-lower.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/object-rows.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-line-stacked.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-ring.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-scatter.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/path-mode-polyline.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/preferences-activities.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/16/search.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/show-grid.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/special_paste.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/tools-wizard.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-calendar-list.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-filter.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-list-tree.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-statistics.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/visibility.svg +21 -0
- dcscope/img/icon-theme/breeze/actions/22/application-exit.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/22/code-context.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-cancel.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-close.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-messages.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-ok-apply.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-ok.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-open-folder.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-open.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-save.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/documentinfo.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/draw-watercolor.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-clear-all.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-clear.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-paste.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/globe.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/22/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/22/list-add.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/messagebox_warning.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/object-columns.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/object-order-lower.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/object-rows.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-line-stacked.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-ring.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-scatter.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/path-mode-polyline.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/preferences-activities.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/22/special_paste.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/tools-wizard.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-calendar-list.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-filter.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-list-tree.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-statistics.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/visibility.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/application-exit.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/code-context.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-cancel.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-close.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-messages.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-ok-apply.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-ok.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/document-open-folder.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/document-open.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/document-save.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/documentinfo.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/draw-watercolor.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-clear-all.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-clear.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-paste.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/globe.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/list-add.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/messagebox_warning.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/object-columns.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/object-order-lower.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/object-rows.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-line-stacked.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-ring.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-scatter.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/path-mode-polyline.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/preferences-activities.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/24/special_paste.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/tools-wizard.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-calendar-list.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-filter.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-list-tree.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-statistics.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/visibility.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/32/application-exit.svg +8 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-cancel.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-messages.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-ok-apply.svg +1 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-ok.svg +1 -0
- dcscope/img/icon-theme/breeze/actions/32/document-open-folder.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/document-open.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/document-save.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/gtk-preferences.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/32/object-order-lower.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/view-filter.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/view-list-tree.svg +18 -0
- dcscope/img/icon-theme/breeze/places/16/folder-cloud.svg +13 -0
- dcscope/img/icon-theme/breeze/places/16/folder.svg +13 -0
- dcscope/img/icon-theme/breeze/places/22/folder-cloud.svg +13 -0
- dcscope/img/icon-theme/breeze/places/22/folder.svg +13 -0
- dcscope/img/icon-theme/breeze/places/24/folder-cloud.svg +12 -0
- dcscope/img/icon-theme/breeze/places/24/folder.svg +12 -0
- dcscope/img/icon-theme/breeze/places/32/folder-cloud.svg +35 -0
- dcscope/img/icon-theme/breeze/places/32/folder.svg +30 -0
- dcscope/img/icon-theme/breeze/places/64/folder-cloud.svg +35 -0
- dcscope/img/icon-theme/breeze/places/64/folder.svg +30 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-error.svg +9 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-information.svg +9 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-question.svg +7 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-warning.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-error.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-information.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-question.svg +7 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-warning.svg +9 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-error.svg +13 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-information.svg +13 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-question.svg +9 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-warning.svg +1 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-error.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-information.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-question.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-warning.svg +25 -0
- dcscope/img/icon-theme/collect_icons.py +124 -0
- dcscope/img/icon-theme/dcscope/dcor.png +0 -0
- dcscope/img/icon-theme/dcscope/edit-copy-anew.svg +13 -0
- dcscope/img/icon-theme/dcscope/emodulus.svg +69 -0
- dcscope/img/icon-theme/dcscope/filter_ray.svg +54 -0
- dcscope/img/icon-theme/dcscope/region_channel.svg +86 -0
- dcscope/img/icon-theme/dcscope/region_reservoir.svg +89 -0
- dcscope/img/icon-theme/dcscope/rlang.svg +108 -0
- dcscope/img/icon-theme/dcscope/statistical_significance.svg +106 -0
- dcscope/img/icon-theme/index.theme +81 -0
- dcscope/img/icon.png +0 -0
- dcscope/img/icon.svg +73 -0
- dcscope/img/splash.png +0 -0
- dcscope/meta_tool.py +143 -0
- dcscope/pipeline/__init__.py +5 -0
- dcscope/pipeline/core.py +632 -0
- dcscope/pipeline/dataslot.py +270 -0
- dcscope/pipeline/filter.py +161 -0
- dcscope/pipeline/filter_ray.py +170 -0
- dcscope/pipeline/plot.py +187 -0
- dcscope/plot_cache.py +79 -0
- dcscope/session.py +379 -0
- dcscope/util.py +34 -0
- dcscope-2.22.1.dist-info/METADATA +95 -0
- dcscope-2.22.1.dist-info/RECORD +267 -0
- dcscope-2.22.1.dist-info/WHEEL +5 -0
- dcscope-2.22.1.dist-info/entry_points.txt +2 -0
- dcscope-2.22.1.dist-info/licenses/LICENSE +621 -0
- dcscope-2.22.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import importlib.resources
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Dict, Literal, Tuple
|
|
5
|
+
|
|
6
|
+
import dclab
|
|
7
|
+
import numpy as np
|
|
8
|
+
from PyQt6 import uic, QtCore, QtWidgets
|
|
9
|
+
import pyqtgraph as pg
|
|
10
|
+
from scipy.ndimage import binary_erosion
|
|
11
|
+
|
|
12
|
+
from ..compute.comp_stats import STAT_METHODS
|
|
13
|
+
from ... import idiom
|
|
14
|
+
from ..widgets import show_wait_cursor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#: default choices for x-axis in plots in descending order
|
|
18
|
+
AXES_DEFAULT_CHOICES_X = [
|
|
19
|
+
"area_um", "index", "frame", "index_online", "time",
|
|
20
|
+
]
|
|
21
|
+
#: default choices for y-axis in plots in descending order
|
|
22
|
+
AXES_DEFAULT_CHOICES_Y = [
|
|
23
|
+
"deform", "bright_avg", "bright_bc_avg", "bg_med", "index",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QuickView(QtWidgets.QWidget):
|
|
28
|
+
polygon_filter_created = QtCore.pyqtSignal()
|
|
29
|
+
polygon_filter_modified = QtCore.pyqtSignal()
|
|
30
|
+
polygon_filter_about_to_be_deleted = QtCore.pyqtSignal(int)
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args, **kwargs):
|
|
33
|
+
self._hover_ds_id = None
|
|
34
|
+
self._hover_event_idx = None
|
|
35
|
+
super(QuickView, self).__init__(*args, **kwargs)
|
|
36
|
+
ref = importlib.resources.files(
|
|
37
|
+
"dcscope.gui.quick_view") / "qv_main.ui"
|
|
38
|
+
with importlib.resources.as_file(ref) as path_ui:
|
|
39
|
+
uic.loadUi(path_ui, self)
|
|
40
|
+
|
|
41
|
+
ref = importlib.resources.files(
|
|
42
|
+
"dcscope.gui.quick_view") / "qv_style.css"
|
|
43
|
+
with importlib.resources.as_file(ref) as path_css:
|
|
44
|
+
stylesheet = pathlib.Path(path_css).read_text()
|
|
45
|
+
self.groupBox_image.setStyleSheet(stylesheet)
|
|
46
|
+
self.groupBox_trace.setStyleSheet(stylesheet)
|
|
47
|
+
self.comboBox_x.default_choices = AXES_DEFAULT_CHOICES_X
|
|
48
|
+
self.comboBox_y.default_choices = AXES_DEFAULT_CHOICES_Y
|
|
49
|
+
|
|
50
|
+
self.setWindowTitle("Quick View")
|
|
51
|
+
|
|
52
|
+
self._set_initial_ui()
|
|
53
|
+
|
|
54
|
+
# Set scale options (with data)
|
|
55
|
+
for cb in [self.comboBox_xscale, self.comboBox_yscale]:
|
|
56
|
+
cb.clear()
|
|
57
|
+
cb.addItem("linear", "linear")
|
|
58
|
+
cb.addItem("logarithmic", "log")
|
|
59
|
+
|
|
60
|
+
# Set marker hue options (with data)
|
|
61
|
+
self.comboBox_hue.clear()
|
|
62
|
+
self.comboBox_hue.addItem("KDE", "kde")
|
|
63
|
+
self.comboBox_hue.addItem("feature", "feature")
|
|
64
|
+
|
|
65
|
+
# Set look-up table options for isoelasticity lines
|
|
66
|
+
self.comboBox_lut.clear()
|
|
67
|
+
lut_dict = dclab.features.emodulus.load.get_internal_lut_names_dict()
|
|
68
|
+
for lut_id in lut_dict.keys():
|
|
69
|
+
self.comboBox_lut.addItem(lut_id, lut_id)
|
|
70
|
+
# Set LE-2D-FEM-19 as a default
|
|
71
|
+
idx = self.comboBox_lut.findData("LE-2D-FEM-19")
|
|
72
|
+
self.comboBox_lut.setCurrentIndex(idx)
|
|
73
|
+
|
|
74
|
+
# settings button
|
|
75
|
+
self.toolButton_event.toggled.connect(self.on_tool)
|
|
76
|
+
self.toolButton_poly.toggled.connect(self.on_tool)
|
|
77
|
+
self.toolButton_settings.toggled.connect(self.on_tool)
|
|
78
|
+
|
|
79
|
+
# polygon filter signals
|
|
80
|
+
self.label_poly_create.setVisible(False)
|
|
81
|
+
self.label_poly_modify.setVisible(False)
|
|
82
|
+
self.pushButton_poly_save.setVisible(False)
|
|
83
|
+
self.pushButton_poly_cancel.setVisible(False)
|
|
84
|
+
self.pushButton_poly_delete.setVisible(False)
|
|
85
|
+
self.pushButton_poly_create.clicked.connect(self.on_poly_create)
|
|
86
|
+
self.pushButton_poly_save.clicked.connect(self.on_poly_done_save)
|
|
87
|
+
self.pushButton_poly_cancel.clicked.connect(self.on_poly_done_cancel)
|
|
88
|
+
self.pushButton_poly_delete.clicked.connect(self.on_poly_done_delete)
|
|
89
|
+
self.comboBox_poly.currentIndexChanged.connect(self.on_poly_modify)
|
|
90
|
+
self.update_polygon_panel()
|
|
91
|
+
|
|
92
|
+
# event changed signal
|
|
93
|
+
self.widget_scatter.scatter.sigClicked.connect(
|
|
94
|
+
self.on_event_scatter_clicked)
|
|
95
|
+
self.widget_scatter.update_hover_pos.connect(
|
|
96
|
+
self.on_event_scatter_hover)
|
|
97
|
+
self.spinBox_event.valueChanged.connect(self.on_event_scatter_spin)
|
|
98
|
+
self.checkBox_image_contour.stateChanged.connect(
|
|
99
|
+
self.on_event_scatter_update)
|
|
100
|
+
self.checkBox_image_contrast.stateChanged.connect(
|
|
101
|
+
self.on_event_scatter_update)
|
|
102
|
+
self.checkBox_image_zoom.stateChanged.connect(
|
|
103
|
+
self.on_event_scatter_update)
|
|
104
|
+
self.checkBox_image_background.stateChanged.connect(
|
|
105
|
+
self.on_event_scatter_update)
|
|
106
|
+
self.checkBox_trace_raw.stateChanged.connect(
|
|
107
|
+
self.on_event_scatter_update)
|
|
108
|
+
self.checkBox_trace_legend.stateChanged.connect(
|
|
109
|
+
self.on_event_scatter_update)
|
|
110
|
+
self.checkBox_trace_zoom.stateChanged.connect(
|
|
111
|
+
self.on_event_scatter_update)
|
|
112
|
+
self.tabWidget_event.currentChanged.connect(
|
|
113
|
+
self.on_event_scatter_update)
|
|
114
|
+
|
|
115
|
+
# apply button
|
|
116
|
+
self.toolButton_apply.clicked.connect(self.plot)
|
|
117
|
+
# value changed signals for plot
|
|
118
|
+
self.signal_widgets = [self.checkBox_downsample,
|
|
119
|
+
self.spinBox_downsample,
|
|
120
|
+
self.comboBox_x,
|
|
121
|
+
self.comboBox_y,
|
|
122
|
+
self.comboBox_xscale,
|
|
123
|
+
self.comboBox_yscale,
|
|
124
|
+
self.checkBox_isoelastics,
|
|
125
|
+
self.comboBox_z_hue,
|
|
126
|
+
self.comboBox_hue,
|
|
127
|
+
self.checkBox_hue,
|
|
128
|
+
self.comboBox_lut
|
|
129
|
+
]
|
|
130
|
+
for w in self.signal_widgets:
|
|
131
|
+
if hasattr(w, "currentIndexChanged"):
|
|
132
|
+
w.currentIndexChanged.connect(self.plot_auto)
|
|
133
|
+
elif hasattr(w, "stateChanged"):
|
|
134
|
+
w.stateChanged.connect(self.plot_auto)
|
|
135
|
+
elif hasattr(w, "valueChanged"):
|
|
136
|
+
w.valueChanged.connect(self.plot_auto)
|
|
137
|
+
# copy statistics to clipboard
|
|
138
|
+
self.toolButton_stats2clipboard.clicked.connect(
|
|
139
|
+
self.on_stats2clipboard)
|
|
140
|
+
|
|
141
|
+
# Set individual plots
|
|
142
|
+
kw0 = dict(x=np.arange(10), y=np.arange(10))
|
|
143
|
+
self.trace_plots = {
|
|
144
|
+
"fl1_raw": pg.PlotDataItem(pen="#6EA068", **kw0), # green
|
|
145
|
+
"fl2_raw": pg.PlotDataItem(pen="#8E7A45", **kw0), # orange
|
|
146
|
+
"fl3_raw": pg.PlotDataItem(pen="#8F4D48", **kw0), # red
|
|
147
|
+
"fl1_median": pg.PlotDataItem(pen="#15BF00", **kw0), # green
|
|
148
|
+
"fl2_median": pg.PlotDataItem(pen="#BF8A00", **kw0), # orange
|
|
149
|
+
"fl3_median": pg.PlotDataItem(pen="#BF0C00", **kw0), # red
|
|
150
|
+
}
|
|
151
|
+
for key in self.trace_plots:
|
|
152
|
+
self.graphicsView_trace.addItem(self.trace_plots[key])
|
|
153
|
+
self.trace_plots[key].setVisible(False)
|
|
154
|
+
|
|
155
|
+
self.graphicsView_trace.plotItem.setLabels(
|
|
156
|
+
left="Fluorescence [a.u.]", bottom="Event time [µs]")
|
|
157
|
+
self.legend_trace = self.graphicsView_trace.addLegend(
|
|
158
|
+
offset=(-.01, +.01))
|
|
159
|
+
|
|
160
|
+
# qpi_pha cmaps
|
|
161
|
+
self.cmap_pha = pg.colormap.get('CET-D1A', skipCache=True)
|
|
162
|
+
self.cmap_pha_with_black = pg.colormap.get('CET-D1A', skipCache=True)
|
|
163
|
+
self.cmap_pha_with_black.color[0] = [0, 0, 0, 1]
|
|
164
|
+
|
|
165
|
+
# image display default range of values that the cmap will cover
|
|
166
|
+
self.levels_image = (0, 255)
|
|
167
|
+
self.levels_qpi_pha = (-3.14, 3.14)
|
|
168
|
+
self.levels_qpi_amp = (0, 2)
|
|
169
|
+
|
|
170
|
+
#: default parameters for the event image
|
|
171
|
+
self.img_info = {
|
|
172
|
+
"image": {
|
|
173
|
+
"view_event": self.imageView_image,
|
|
174
|
+
"view_poly": self.imageView_image_poly,
|
|
175
|
+
"cmap": None,
|
|
176
|
+
"cmap_changed": {"view_event": False,
|
|
177
|
+
"view_poly": False},
|
|
178
|
+
"kwargs": dict(autoLevels=False, levels=self.levels_image),
|
|
179
|
+
},
|
|
180
|
+
"qpi_pha": {
|
|
181
|
+
"view_event": self.imageView_image_pha,
|
|
182
|
+
"view_poly": self.imageView_image_poly_pha,
|
|
183
|
+
"cmap": self.cmap_pha,
|
|
184
|
+
"cmap_changed": {"view_event": False,
|
|
185
|
+
"view_poly": False},
|
|
186
|
+
"kwargs": dict(autoLevels=False, levels=self.levels_qpi_pha),
|
|
187
|
+
},
|
|
188
|
+
"qpi_amp": {
|
|
189
|
+
"view_event": self.imageView_image_amp,
|
|
190
|
+
"view_poly": self.imageView_image_poly_amp,
|
|
191
|
+
"cmap": None,
|
|
192
|
+
"cmap_changed": {"view_event": False,
|
|
193
|
+
"view_poly": False},
|
|
194
|
+
"kwargs": dict(autoLevels=False, levels=self.levels_qpi_amp),
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# set initial empty dataset
|
|
199
|
+
self._rtdc_ds = None
|
|
200
|
+
#: A cache for the event index plotted for a dataset
|
|
201
|
+
self._dataset_event_plot_indices_cache = {}
|
|
202
|
+
self.slot = None
|
|
203
|
+
|
|
204
|
+
self._statistics_cache = collections.OrderedDict()
|
|
205
|
+
|
|
206
|
+
def read_pipeline_state(self):
|
|
207
|
+
plot = {
|
|
208
|
+
"downsampling": self.checkBox_downsample.isChecked(),
|
|
209
|
+
"downsampling value": self.spinBox_downsample.value(),
|
|
210
|
+
"axis x": self.comboBox_x.currentData(),
|
|
211
|
+
"axis y": self.comboBox_y.currentData(),
|
|
212
|
+
"scale x": self.comboBox_xscale.currentData(),
|
|
213
|
+
"scale y": self.comboBox_yscale.currentData(),
|
|
214
|
+
"isoelastics": self.checkBox_isoelastics.isChecked(),
|
|
215
|
+
"lut": self.comboBox_lut.currentData(),
|
|
216
|
+
"marker hue": self.checkBox_hue.isChecked(),
|
|
217
|
+
"marker hue value": self.comboBox_hue.currentData(),
|
|
218
|
+
"marker hue feature": self.comboBox_z_hue.currentData(),
|
|
219
|
+
}
|
|
220
|
+
event = {
|
|
221
|
+
"index": self.spinBox_event.value(),
|
|
222
|
+
"image auto contrast": self.checkBox_image_contrast.isChecked(),
|
|
223
|
+
"image contour": self.checkBox_image_contour.isChecked(),
|
|
224
|
+
"image zoom": self.checkBox_image_zoom.isChecked(),
|
|
225
|
+
"image background": self.checkBox_image_background.isChecked(),
|
|
226
|
+
"trace legend": self.checkBox_trace_legend.isChecked(),
|
|
227
|
+
"trace raw": self.checkBox_trace_raw.isChecked(),
|
|
228
|
+
"trace zoom": self.checkBox_trace_zoom.isChecked(),
|
|
229
|
+
}
|
|
230
|
+
state = {
|
|
231
|
+
"plot": plot,
|
|
232
|
+
"event": event,
|
|
233
|
+
}
|
|
234
|
+
return state
|
|
235
|
+
|
|
236
|
+
def write_pipeline_state(self, state):
|
|
237
|
+
plot = state["plot"]
|
|
238
|
+
for tb in self.signal_widgets:
|
|
239
|
+
tb.blockSignals(True)
|
|
240
|
+
# downsampling
|
|
241
|
+
self.checkBox_downsample.setChecked(plot["downsampling"])
|
|
242
|
+
self.spinBox_downsample.setValue(plot["downsampling value"])
|
|
243
|
+
self.checkBox_hue.setChecked(plot["marker hue"])
|
|
244
|
+
# combo box key selection
|
|
245
|
+
self.update_feature_choices()
|
|
246
|
+
for key, cb in [
|
|
247
|
+
# axes labels
|
|
248
|
+
("axis x", self.comboBox_x),
|
|
249
|
+
("axis y", self.comboBox_y),
|
|
250
|
+
# scaling
|
|
251
|
+
("scale x", self.comboBox_xscale),
|
|
252
|
+
("scale y", self.comboBox_yscale),
|
|
253
|
+
# look up table
|
|
254
|
+
("lut", self.comboBox_lut),
|
|
255
|
+
# marker hue
|
|
256
|
+
("marker hue value", self.comboBox_hue),
|
|
257
|
+
("marker hue feature", self.comboBox_z_hue),
|
|
258
|
+
]:
|
|
259
|
+
idx = cb.findData(plot[key])
|
|
260
|
+
idx = idx if idx > 0 else 0
|
|
261
|
+
cb.setCurrentIndex(idx)
|
|
262
|
+
|
|
263
|
+
# isoelastics
|
|
264
|
+
self.checkBox_isoelastics.setChecked(plot["isoelastics"])
|
|
265
|
+
for tb in self.signal_widgets:
|
|
266
|
+
tb.blockSignals(False)
|
|
267
|
+
if "event" in state:
|
|
268
|
+
event = state["event"]
|
|
269
|
+
self.checkBox_image_contrast.setChecked(
|
|
270
|
+
event["image auto contrast"])
|
|
271
|
+
self.checkBox_image_contour.setChecked(event["image contour"])
|
|
272
|
+
self.checkBox_image_zoom.setChecked(event["image zoom"])
|
|
273
|
+
self.checkBox_image_background.setChecked(
|
|
274
|
+
event["image background"])
|
|
275
|
+
self.spinBox_event.setValue(event["index"])
|
|
276
|
+
self.checkBox_trace_raw.setChecked(event["trace raw"])
|
|
277
|
+
self.checkBox_trace_legend.setChecked(event["trace legend"])
|
|
278
|
+
|
|
279
|
+
def _check_file_open(self, rtdc_ds):
|
|
280
|
+
"""Check whether a dataset is still open"""
|
|
281
|
+
if isinstance(rtdc_ds, dclab.rtdc_dataset.RTDC_HDF5):
|
|
282
|
+
if rtdc_ds.h5file:
|
|
283
|
+
# the file is open
|
|
284
|
+
isopen = True
|
|
285
|
+
else:
|
|
286
|
+
isopen = False
|
|
287
|
+
elif isinstance(rtdc_ds, dclab.rtdc_dataset.RTDC_Hierarchy):
|
|
288
|
+
isopen = self._check_file_open(rtdc_ds.get_root_parent())
|
|
289
|
+
else:
|
|
290
|
+
# DCOR
|
|
291
|
+
isopen = True
|
|
292
|
+
return isopen
|
|
293
|
+
|
|
294
|
+
def _set_initial_ui(self):
|
|
295
|
+
self._hover_ds_id = None
|
|
296
|
+
self._hover_event_idx = None
|
|
297
|
+
# events label
|
|
298
|
+
self.label_noevents.setVisible(False)
|
|
299
|
+
self.enable_interface(False)
|
|
300
|
+
|
|
301
|
+
def enable_interface(self, value):
|
|
302
|
+
# Initially, only show the info about how QuickView works
|
|
303
|
+
self.widget_tool.setEnabled(value)
|
|
304
|
+
self.widget_scatter.setVisible(value)
|
|
305
|
+
# stacked widget
|
|
306
|
+
self.stackedWidget.setEnabled(value)
|
|
307
|
+
# how-to label
|
|
308
|
+
self.label_howto.setVisible(not value)
|
|
309
|
+
|
|
310
|
+
if not value:
|
|
311
|
+
self.imageView_image.setImage(np.full((10, 10), 200))
|
|
312
|
+
self.imageView_image_amp.setImage(np.full((10, 10), 200))
|
|
313
|
+
self.imageView_image_pha.setImage(np.full((10, 10), 200))
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def rtdc_ds(self):
|
|
317
|
+
"""Dataset to plot; set to None initially and if the file is closed"""
|
|
318
|
+
if self._rtdc_ds is not None:
|
|
319
|
+
if not self._check_file_open(self._rtdc_ds):
|
|
320
|
+
self._rtdc_ds = None
|
|
321
|
+
# now check again
|
|
322
|
+
if self._rtdc_ds is None:
|
|
323
|
+
self._set_initial_ui()
|
|
324
|
+
return self._rtdc_ds
|
|
325
|
+
|
|
326
|
+
@rtdc_ds.setter
|
|
327
|
+
def rtdc_ds(self, rtdc_ds):
|
|
328
|
+
if self._rtdc_ds is not rtdc_ds:
|
|
329
|
+
self._hover_ds_id = None
|
|
330
|
+
self._hover_event_idx = None
|
|
331
|
+
|
|
332
|
+
self._rtdc_ds = rtdc_ds
|
|
333
|
+
|
|
334
|
+
# Hide "Subtract Background"-Checkbox if feature
|
|
335
|
+
# "image_bg" not in dataset
|
|
336
|
+
contains_bg_feat = "image_bg" in rtdc_ds
|
|
337
|
+
self.checkBox_image_background.setVisible(contains_bg_feat)
|
|
338
|
+
|
|
339
|
+
# set the dataset for the FeatureComboBoxes
|
|
340
|
+
self.comboBox_x.set_dataset(rtdc_ds)
|
|
341
|
+
self.comboBox_y.set_dataset(rtdc_ds)
|
|
342
|
+
self.comboBox_z_hue.set_dataset(rtdc_ds)
|
|
343
|
+
|
|
344
|
+
# Showing image data
|
|
345
|
+
####################
|
|
346
|
+
def get_event_image(self, ds, event, feat="image"):
|
|
347
|
+
"""Handle the image processing and contour processing for the event"""
|
|
348
|
+
state = self.read_pipeline_state()
|
|
349
|
+
if feat == "image":
|
|
350
|
+
cell_img = self._prepare_event_image_image(ds, event, state)
|
|
351
|
+
elif feat == "qpi_pha":
|
|
352
|
+
cell_img = self._prepare_event_image_qpi_pha(ds, event, state)
|
|
353
|
+
elif feat == "qpi_amp":
|
|
354
|
+
cell_img = self._prepare_event_image_qpi_amp(ds, event, state)
|
|
355
|
+
else:
|
|
356
|
+
raise NotImplementedError(f"Image feature {feat} not implemented")
|
|
357
|
+
|
|
358
|
+
return cell_img
|
|
359
|
+
|
|
360
|
+
def _prepare_event_image_image(self, ds, event, state):
|
|
361
|
+
cell_img = ds["image"][event]
|
|
362
|
+
# apply background correction
|
|
363
|
+
if "image_bg" in ds:
|
|
364
|
+
if state["event"]["image background"]:
|
|
365
|
+
bgimg = ds["image_bg"][event].astype(np.int16)
|
|
366
|
+
cell_img = cell_img.astype(np.int16)
|
|
367
|
+
cell_img = cell_img - bgimg + int(np.mean(bgimg))
|
|
368
|
+
# automatic contrast
|
|
369
|
+
if state["event"]["image auto contrast"]:
|
|
370
|
+
vmin, vmax = cell_img.min(), cell_img.max()
|
|
371
|
+
cell_img = (cell_img - vmin) / (vmax - vmin) * 255
|
|
372
|
+
cell_img = self._convert_to_rgb(cell_img)
|
|
373
|
+
# clip and convert to int
|
|
374
|
+
cell_img = np.clip(cell_img, 0, 255)
|
|
375
|
+
cell_img = np.require(cell_img, np.uint8, 'C')
|
|
376
|
+
|
|
377
|
+
cell_img = self._insert_contour_and_zoom(
|
|
378
|
+
cell_img,
|
|
379
|
+
cmap_levels=self.img_info["image"]["kwargs"]["levels"],
|
|
380
|
+
contour_style="red",
|
|
381
|
+
ds=ds,
|
|
382
|
+
event=event,
|
|
383
|
+
state=state)
|
|
384
|
+
|
|
385
|
+
return cell_img
|
|
386
|
+
|
|
387
|
+
def _prepare_event_image_qpi_amp(self, ds, event, state):
|
|
388
|
+
cell_img = ds["qpi_amp"][event]
|
|
389
|
+
if state["event"]["image auto contrast"]:
|
|
390
|
+
vmin, vmax = cell_img.min(), cell_img.max()
|
|
391
|
+
else:
|
|
392
|
+
vmin, vmax = self.levels_qpi_amp
|
|
393
|
+
self.img_info["qpi_amp"]["kwargs"]["levels"] = (vmin, vmax)
|
|
394
|
+
# to get the correct contour colour it is easier to view the
|
|
395
|
+
# amplitude as an RGB image
|
|
396
|
+
cell_img = self._convert_to_rgb(cell_img)
|
|
397
|
+
|
|
398
|
+
cell_img = self._insert_contour_and_zoom(
|
|
399
|
+
cell_img,
|
|
400
|
+
cmap_levels=self.img_info["qpi_amp"]["kwargs"]["levels"],
|
|
401
|
+
contour_style="red",
|
|
402
|
+
ds=ds,
|
|
403
|
+
event=event,
|
|
404
|
+
state=state)
|
|
405
|
+
|
|
406
|
+
return cell_img
|
|
407
|
+
|
|
408
|
+
@staticmethod
|
|
409
|
+
def _convert_to_rgb(cell_img):
|
|
410
|
+
cell_img = cell_img.reshape(
|
|
411
|
+
cell_img.shape[0], cell_img.shape[1], 1)
|
|
412
|
+
return np.repeat(cell_img, 3, axis=2)
|
|
413
|
+
|
|
414
|
+
def _prepare_event_image_qpi_pha(self, ds, event, state):
|
|
415
|
+
cell_img = ds["qpi_pha"][event]
|
|
416
|
+
# colormap levels
|
|
417
|
+
if state["event"]["image auto contrast"]:
|
|
418
|
+
vmin, vmax = self._vmin_max_around_zero(cell_img)
|
|
419
|
+
if state["event"]["image contour"]:
|
|
420
|
+
# offset required for auto-contrast with contour
|
|
421
|
+
# two times the contrast range, divided by the cmap length
|
|
422
|
+
# this essentially adds a cmap point for our contour
|
|
423
|
+
offset = 2 * ((vmax - vmin) / len(self.cmap_pha.color))
|
|
424
|
+
vmin = vmin - offset
|
|
425
|
+
else:
|
|
426
|
+
vmin, vmax = self.levels_qpi_pha
|
|
427
|
+
self.img_info["qpi_pha"]["kwargs"]["levels"] = (vmin, vmax)
|
|
428
|
+
|
|
429
|
+
# update colormap
|
|
430
|
+
if state["event"]["image contour"]:
|
|
431
|
+
new_cmap = self.cmap_pha_with_black
|
|
432
|
+
else:
|
|
433
|
+
new_cmap = self.cmap_pha
|
|
434
|
+
if self.img_info["qpi_pha"]["cmap"] != new_cmap:
|
|
435
|
+
self.img_info["qpi_pha"]["cmap"] = new_cmap
|
|
436
|
+
# performance
|
|
437
|
+
self.img_info["qpi_pha"]["cmap_changed"]["view_poly"] = True
|
|
438
|
+
self.img_info["qpi_pha"]["cmap_changed"]["view_event"] = True
|
|
439
|
+
|
|
440
|
+
cell_img = self._insert_contour_and_zoom(
|
|
441
|
+
cell_img,
|
|
442
|
+
cmap_levels=self.img_info["qpi_pha"]["kwargs"]["levels"],
|
|
443
|
+
contour_style="lowest-level",
|
|
444
|
+
ds=ds,
|
|
445
|
+
event=event,
|
|
446
|
+
state=state)
|
|
447
|
+
|
|
448
|
+
return cell_img
|
|
449
|
+
|
|
450
|
+
def _vmin_max_around_zero(self, cell_img):
|
|
451
|
+
vmin_abs, vmax_abs = np.abs(cell_img.min()), np.abs(cell_img.max())
|
|
452
|
+
v_largest = max(vmax_abs, vmin_abs)
|
|
453
|
+
vmin, vmax = -v_largest, v_largest
|
|
454
|
+
return vmin, vmax
|
|
455
|
+
|
|
456
|
+
def _insert_contour_and_zoom(self,
|
|
457
|
+
cell_img: np.ndarray,
|
|
458
|
+
cmap_levels: Tuple[float, float],
|
|
459
|
+
contour_style: Literal["red", "lowest-level"],
|
|
460
|
+
ds: dclab.rtdc_dataset.RTDCBase,
|
|
461
|
+
event: int,
|
|
462
|
+
state: Dict):
|
|
463
|
+
if "mask" in ds and len(ds["mask"]) > event:
|
|
464
|
+
mask = ds["mask"][event]
|
|
465
|
+
if state["event"]["image contour"]:
|
|
466
|
+
# Compute contour image from mask. If you are wondering
|
|
467
|
+
# whether this is kosher, please take a look at issue #76:
|
|
468
|
+
# https://github.com/DC-analysis/dclab/issues/76
|
|
469
|
+
cont = mask ^ binary_erosion(mask)
|
|
470
|
+
if contour_style == "red":
|
|
471
|
+
# draw red contour for grayscale images
|
|
472
|
+
ch_red = cmap_levels[1] * 0.7
|
|
473
|
+
ch_other = int(cmap_levels[0]) if \
|
|
474
|
+
cmap_levels[1] == 255 else cmap_levels[0]
|
|
475
|
+
# assign channel values for contour
|
|
476
|
+
cell_img[cont, 0] = int(
|
|
477
|
+
ch_red) if cmap_levels[1] == 255 else ch_red
|
|
478
|
+
cell_img[cont, 1] = ch_other
|
|
479
|
+
cell_img[cont, 2] = ch_other
|
|
480
|
+
elif contour_style == "lowest-level":
|
|
481
|
+
# use the lowest value from the colormap
|
|
482
|
+
# (used for e.g. phase images)
|
|
483
|
+
cell_img[cont] = cmap_levels[0]
|
|
484
|
+
|
|
485
|
+
if state["event"]["image zoom"]:
|
|
486
|
+
cell_img = self.image_zoom(cell_img, mask)
|
|
487
|
+
|
|
488
|
+
return cell_img
|
|
489
|
+
|
|
490
|
+
def get_event_image_and_show(self, ds, event, feat, view):
|
|
491
|
+
"""Convenience method for getting and showing event image"""
|
|
492
|
+
cell_img = self.get_event_image(ds, event, feat)
|
|
493
|
+
self.show_image(feat, view, cell_img)
|
|
494
|
+
|
|
495
|
+
def show_image(self, feat, view, cell_img):
|
|
496
|
+
self.img_info[feat][view].setImage(cell_img,
|
|
497
|
+
**self.img_info[feat]["kwargs"])
|
|
498
|
+
|
|
499
|
+
if (self.img_info[feat]["cmap"] is not None
|
|
500
|
+
# performance
|
|
501
|
+
and self.img_info[feat]["cmap_changed"][view]):
|
|
502
|
+
self.img_info[feat]["cmap_changed"][view] = False
|
|
503
|
+
self.img_info[feat][view].setColorMap(self.img_info[feat]["cmap"])
|
|
504
|
+
self.img_info[feat][view].setVisible(True)
|
|
505
|
+
|
|
506
|
+
@staticmethod
|
|
507
|
+
def image_zoom(cell_img, mask):
|
|
508
|
+
xv, yv = np.where(mask)
|
|
509
|
+
idminx = xv.min() - 5
|
|
510
|
+
idminy = yv.min() - 5
|
|
511
|
+
idmaxx = xv.max() + 5
|
|
512
|
+
idmaxy = yv.max() + 5
|
|
513
|
+
idminx = idminx if idminx >= 0 else 0
|
|
514
|
+
idminy = idminy if idminy >= 0 else 0
|
|
515
|
+
shx, shy = mask.shape
|
|
516
|
+
idmaxx = idmaxx if idmaxx < shx else shx
|
|
517
|
+
idmaxy = idmaxy if idmaxy < shy else shy
|
|
518
|
+
return cell_img[idminx:idmaxx, idminy:idmaxy]
|
|
519
|
+
|
|
520
|
+
# Statistics
|
|
521
|
+
############
|
|
522
|
+
def get_statistics(self):
|
|
523
|
+
if self.rtdc_ds is not None:
|
|
524
|
+
features = [self.comboBox_x.currentData(),
|
|
525
|
+
self.comboBox_y.currentData()]
|
|
526
|
+
# cache statistics from
|
|
527
|
+
dsid = "-".join(features
|
|
528
|
+
+ [self.rtdc_ds.identifier,
|
|
529
|
+
self.rtdc_ds.filter._parent_hash]
|
|
530
|
+
)
|
|
531
|
+
if dsid not in self._statistics_cache:
|
|
532
|
+
stats = dclab.statistics.get_statistics(ds=self.rtdc_ds,
|
|
533
|
+
features=features,
|
|
534
|
+
methods=STAT_METHODS)
|
|
535
|
+
self._statistics_cache[dsid] = stats
|
|
536
|
+
if len(self._statistics_cache) > 1000:
|
|
537
|
+
# avoid a memory leak
|
|
538
|
+
self._statistics_cache.popitem(last=False)
|
|
539
|
+
return self._statistics_cache[dsid]
|
|
540
|
+
else:
|
|
541
|
+
return None, None
|
|
542
|
+
|
|
543
|
+
# Scatter Plot
|
|
544
|
+
##############
|
|
545
|
+
@QtCore.pyqtSlot(object, object)
|
|
546
|
+
def on_event_scatter_clicked(self, plot, point):
|
|
547
|
+
"""User clicked on scatter plot
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
plot: pg.PlotItem
|
|
552
|
+
Active plot
|
|
553
|
+
point: QPoint
|
|
554
|
+
Selected point (determined by scatter plot widget)
|
|
555
|
+
"""
|
|
556
|
+
if self.widget_scatter.events_plotted is not None:
|
|
557
|
+
# plotted events
|
|
558
|
+
plotted = self.widget_scatter.events_plotted
|
|
559
|
+
# get corrected index
|
|
560
|
+
ds_idx = np.where(plotted)[0][point.index()]
|
|
561
|
+
self.show_event(ds_idx)
|
|
562
|
+
# Note that triggering the toolButton_event must be done after
|
|
563
|
+
# calling show_event, otherwise the first event is shown and
|
|
564
|
+
# only after that the desired one. This would be a drawback when
|
|
565
|
+
# events come from remote locations.
|
|
566
|
+
#
|
|
567
|
+
# `self.on_tool` (`self.toolButton_event`) takes care of this:
|
|
568
|
+
# self.widget_scatter.select.setVisible(True)
|
|
569
|
+
if not self.toolButton_event.isChecked():
|
|
570
|
+
# emulate mouse toggle
|
|
571
|
+
self.toolButton_event.setChecked(True)
|
|
572
|
+
self.toolButton_event.toggled.emit(True)
|
|
573
|
+
|
|
574
|
+
@QtCore.pyqtSlot(QtCore.QPointF)
|
|
575
|
+
def on_event_scatter_hover(self, pos):
|
|
576
|
+
"""Update the image view in the polygon widget """
|
|
577
|
+
if self.rtdc_ds is not None and self.toolButton_poly.isChecked():
|
|
578
|
+
ds = self.rtdc_ds
|
|
579
|
+
# plotted events
|
|
580
|
+
plotted = self.widget_scatter.events_plotted
|
|
581
|
+
spos = self.widget_scatter.scatter.mapFromView(pos)
|
|
582
|
+
point = self.widget_scatter.scatter.pointAt(spos)
|
|
583
|
+
# get corrected index
|
|
584
|
+
event = np.where(plotted)[0][point.index()]
|
|
585
|
+
|
|
586
|
+
# Only plot if we have not plotted this event before
|
|
587
|
+
if (self._hover_ds_id != id(ds)
|
|
588
|
+
or self._hover_event_idx != event):
|
|
589
|
+
# remember where we were
|
|
590
|
+
self._hover_ds_id = id(ds)
|
|
591
|
+
self._hover_event_idx = event
|
|
592
|
+
view = "view_poly"
|
|
593
|
+
for key in self.img_info.keys():
|
|
594
|
+
self.img_info[key][view].setVisible(False)
|
|
595
|
+
|
|
596
|
+
try:
|
|
597
|
+
# if we have qpi data, image might be a different shape
|
|
598
|
+
if "qpi_pha" in ds:
|
|
599
|
+
self.get_event_image_and_show(
|
|
600
|
+
ds, event, "qpi_pha", view)
|
|
601
|
+
if "qpi_amp" in ds:
|
|
602
|
+
self.get_event_image_and_show(
|
|
603
|
+
ds, event, "qpi_amp", view)
|
|
604
|
+
elif "image" in ds:
|
|
605
|
+
self.get_event_image_and_show(
|
|
606
|
+
ds, event, "image", view)
|
|
607
|
+
except IndexError:
|
|
608
|
+
# the plot got updated, and we still have the old data
|
|
609
|
+
self.get_event_image_and_show(ds, 0, "image", view)
|
|
610
|
+
|
|
611
|
+
@QtCore.pyqtSlot(int)
|
|
612
|
+
def on_event_scatter_spin(self, event):
|
|
613
|
+
"""Sping control for event selection changed"""
|
|
614
|
+
self.show_event(event - 1)
|
|
615
|
+
|
|
616
|
+
@QtCore.pyqtSlot()
|
|
617
|
+
def on_event_scatter_update(self):
|
|
618
|
+
"""Just update the event shown"""
|
|
619
|
+
event = self.spinBox_event.value()
|
|
620
|
+
self.show_event(event - 1)
|
|
621
|
+
|
|
622
|
+
# Polygon Selection
|
|
623
|
+
###################
|
|
624
|
+
@QtCore.pyqtSlot()
|
|
625
|
+
def on_poly_create(self):
|
|
626
|
+
"""User wants to create a polygon filter"""
|
|
627
|
+
self.pushButton_poly_create.setEnabled(False)
|
|
628
|
+
if not self.toolButton_poly.isChecked():
|
|
629
|
+
# emulate mouse toggle
|
|
630
|
+
self.toolButton_poly.setChecked(True)
|
|
631
|
+
self.toolButton_poly.toggled.emit(True)
|
|
632
|
+
self.comboBox_poly.setEnabled(False)
|
|
633
|
+
self.groupBox_poly.setEnabled(True)
|
|
634
|
+
self.label_poly_create.setVisible(True)
|
|
635
|
+
self.pushButton_poly_save.setVisible(True)
|
|
636
|
+
self.pushButton_poly_cancel.setVisible(True)
|
|
637
|
+
# defaults
|
|
638
|
+
self.lineEdit_poly.setText("Polygon Filter {}".format(
|
|
639
|
+
dclab.PolygonFilter._instance_counter + 1))
|
|
640
|
+
self.checkBox_poly.setChecked(False)
|
|
641
|
+
self.widget_scatter.activate_poly_mode()
|
|
642
|
+
# trigger resize and redraw
|
|
643
|
+
mdiwin = self.parent()
|
|
644
|
+
mdiwin.adjustSize()
|
|
645
|
+
mdiwin.update()
|
|
646
|
+
self.update()
|
|
647
|
+
|
|
648
|
+
@QtCore.pyqtSlot()
|
|
649
|
+
def on_poly_done(self, mode="none"):
|
|
650
|
+
"""User is done creating or modifying a polygon filter"""
|
|
651
|
+
self.pushButton_poly_create.setEnabled(True)
|
|
652
|
+
self.label_poly_create.setVisible(False)
|
|
653
|
+
self.label_poly_modify.setVisible(False)
|
|
654
|
+
self.pushButton_poly_save.setVisible(False)
|
|
655
|
+
self.pushButton_poly_cancel.setVisible(False)
|
|
656
|
+
self.pushButton_poly_delete.setVisible(False)
|
|
657
|
+
# remove the PolyLineRoi
|
|
658
|
+
self.widget_scatter.activate_scatter_mode()
|
|
659
|
+
self.update_polygon_panel()
|
|
660
|
+
if mode == "create":
|
|
661
|
+
self.polygon_filter_created.emit()
|
|
662
|
+
elif mode == "modify":
|
|
663
|
+
self.polygon_filter_modified.emit()
|
|
664
|
+
|
|
665
|
+
@QtCore.pyqtSlot()
|
|
666
|
+
def on_poly_done_delete(self):
|
|
667
|
+
# delete the polygon filter
|
|
668
|
+
idp = self.comboBox_poly.currentData()
|
|
669
|
+
if idp is not None:
|
|
670
|
+
# There is a polygon filter that we want to delete
|
|
671
|
+
self.polygon_filter_about_to_be_deleted.emit(idp)
|
|
672
|
+
dclab.PolygonFilter.remove(idp)
|
|
673
|
+
mode = "modify"
|
|
674
|
+
else:
|
|
675
|
+
mode = "none"
|
|
676
|
+
self.on_poly_done(mode)
|
|
677
|
+
|
|
678
|
+
@QtCore.pyqtSlot()
|
|
679
|
+
def on_poly_done_cancel(self):
|
|
680
|
+
self.on_poly_done()
|
|
681
|
+
|
|
682
|
+
@QtCore.pyqtSlot()
|
|
683
|
+
def on_poly_done_save(self):
|
|
684
|
+
# save the polygon filter
|
|
685
|
+
points = self.widget_scatter.get_poly_points()
|
|
686
|
+
name = self.lineEdit_poly.text()
|
|
687
|
+
inverted = self.checkBox_poly.isChecked()
|
|
688
|
+
axes = self.widget_scatter.xax, self.widget_scatter.yax
|
|
689
|
+
# determine whether to create a new polygon filter or whether
|
|
690
|
+
# to update an existing one.
|
|
691
|
+
idp = self.comboBox_poly.currentData()
|
|
692
|
+
if idp is None:
|
|
693
|
+
dclab.PolygonFilter(axes=axes, points=points, name=name,
|
|
694
|
+
inverted=inverted)
|
|
695
|
+
mode = "create"
|
|
696
|
+
else:
|
|
697
|
+
pf = dclab.PolygonFilter.get_instance_from_id(idp)
|
|
698
|
+
pf.name = name
|
|
699
|
+
pf.inverted = inverted
|
|
700
|
+
pf.points = points
|
|
701
|
+
mode = "modify"
|
|
702
|
+
self.on_poly_done(mode)
|
|
703
|
+
|
|
704
|
+
@QtCore.pyqtSlot()
|
|
705
|
+
def on_poly_modify(self):
|
|
706
|
+
"""User wants to modify a polygon filter"""
|
|
707
|
+
self.pushButton_poly_create.setEnabled(False)
|
|
708
|
+
self.comboBox_poly.setEnabled(False)
|
|
709
|
+
self.groupBox_poly.setEnabled(True)
|
|
710
|
+
self.label_poly_modify.setVisible(True)
|
|
711
|
+
self.pushButton_poly_save.setVisible(True)
|
|
712
|
+
self.pushButton_poly_cancel.setVisible(True)
|
|
713
|
+
self.pushButton_poly_delete.setVisible(True)
|
|
714
|
+
# get the polygon filter id
|
|
715
|
+
idp = self.comboBox_poly.currentData()
|
|
716
|
+
pf = dclab.PolygonFilter.get_instance_from_id(idp)
|
|
717
|
+
# set UI information
|
|
718
|
+
self.lineEdit_poly.setText(pf.name)
|
|
719
|
+
self.checkBox_poly.setChecked(pf.inverted)
|
|
720
|
+
# set axes
|
|
721
|
+
state = self.read_pipeline_state()
|
|
722
|
+
state["plot"]["axis x"] = pf.axes[0]
|
|
723
|
+
state["plot"]["axis y"] = pf.axes[1]
|
|
724
|
+
self.write_pipeline_state(state)
|
|
725
|
+
self.plot()
|
|
726
|
+
# add ROI
|
|
727
|
+
self.widget_scatter.activate_poly_mode(pf.points)
|
|
728
|
+
|
|
729
|
+
# Buttons
|
|
730
|
+
#########
|
|
731
|
+
@QtCore.pyqtSlot()
|
|
732
|
+
def on_stats2clipboard(self):
|
|
733
|
+
"""Copy the statistics as tsv data to the clipboard"""
|
|
734
|
+
h, v = self.get_statistics()
|
|
735
|
+
if h is not None:
|
|
736
|
+
# assemble tsv data
|
|
737
|
+
tsv = ""
|
|
738
|
+
for hi, vi in zip(h, v):
|
|
739
|
+
tsv += "{}\t{:.7g}\n".format(hi, vi)
|
|
740
|
+
QtWidgets.qApp.clipboard().setText(tsv)
|
|
741
|
+
|
|
742
|
+
@show_wait_cursor
|
|
743
|
+
@QtCore.pyqtSlot()
|
|
744
|
+
def on_tool(self, collapse=False):
|
|
745
|
+
"""Show and hide tools when the user selected a tool button"""
|
|
746
|
+
toblock = [self.toolButton_event,
|
|
747
|
+
self.toolButton_poly,
|
|
748
|
+
self.toolButton_settings,
|
|
749
|
+
]
|
|
750
|
+
for b in toblock:
|
|
751
|
+
b.blockSignals(True)
|
|
752
|
+
# show extra data
|
|
753
|
+
show_event = False
|
|
754
|
+
show_poly = False
|
|
755
|
+
show_settings = False
|
|
756
|
+
sender = self.sender()
|
|
757
|
+
if sender == self.toolButton_event:
|
|
758
|
+
show_event = self.toolButton_event.isChecked()
|
|
759
|
+
elif sender == self.toolButton_poly:
|
|
760
|
+
show_poly = self.toolButton_poly.isChecked()
|
|
761
|
+
elif sender == self.toolButton_settings:
|
|
762
|
+
show_settings = self.toolButton_settings.isChecked()
|
|
763
|
+
elif collapse:
|
|
764
|
+
# show nothing
|
|
765
|
+
pass
|
|
766
|
+
else:
|
|
767
|
+
# keep everything as-is but update the sizes
|
|
768
|
+
show_event = self.stackedWidget.currentWidget() is self.page_event
|
|
769
|
+
show_settings = (
|
|
770
|
+
self.stackedWidget.currentWidget() is self.page_settings)
|
|
771
|
+
show_poly = self.stackedWidget.currentWidget() is self.page_poly
|
|
772
|
+
|
|
773
|
+
# toolbutton checked
|
|
774
|
+
self.toolButton_event.setChecked(show_event)
|
|
775
|
+
self.toolButton_poly.setChecked(show_poly)
|
|
776
|
+
self.toolButton_settings.setChecked(show_settings)
|
|
777
|
+
|
|
778
|
+
# stack widget visibility
|
|
779
|
+
if show_event:
|
|
780
|
+
self.stackedWidget.setCurrentWidget(self.page_event)
|
|
781
|
+
elif show_settings:
|
|
782
|
+
self.stackedWidget.setCurrentWidget(self.page_settings)
|
|
783
|
+
elif show_poly:
|
|
784
|
+
self.stackedWidget.setCurrentWidget(self.page_poly)
|
|
785
|
+
|
|
786
|
+
self.widget_scatter.select.setVisible(show_event) # point in scatter
|
|
787
|
+
|
|
788
|
+
if show_event:
|
|
789
|
+
# update event plot (maybe axes changed)
|
|
790
|
+
self.on_event_scatter_update()
|
|
791
|
+
|
|
792
|
+
for b in toblock:
|
|
793
|
+
b.blockSignals(False)
|
|
794
|
+
|
|
795
|
+
if not show_poly:
|
|
796
|
+
self.on_poly_done()
|
|
797
|
+
|
|
798
|
+
self.update()
|
|
799
|
+
|
|
800
|
+
@show_wait_cursor
|
|
801
|
+
@QtCore.pyqtSlot()
|
|
802
|
+
def plot(self):
|
|
803
|
+
"""Update the plot using the current state of the UI"""
|
|
804
|
+
if self.rtdc_ds is not None:
|
|
805
|
+
plot = self.read_pipeline_state()["plot"]
|
|
806
|
+
downsample = plot["downsampling"] * plot["downsampling value"]
|
|
807
|
+
hue_kwargs = {}
|
|
808
|
+
if self.checkBox_hue.isChecked():
|
|
809
|
+
hue_type = self.comboBox_hue.currentData()
|
|
810
|
+
if hue_type == "kde":
|
|
811
|
+
hue_kwargs = {"kde_type": "histogram"}
|
|
812
|
+
if hue_type == "feature":
|
|
813
|
+
hue_kwargs = {"feat": self.comboBox_z_hue.currentData()}
|
|
814
|
+
else:
|
|
815
|
+
hue_type = "none"
|
|
816
|
+
self.widget_scatter.plot_data(rtdc_ds=self.rtdc_ds,
|
|
817
|
+
slot=self.slot,
|
|
818
|
+
downsample=downsample,
|
|
819
|
+
xax=plot["axis x"],
|
|
820
|
+
yax=plot["axis y"],
|
|
821
|
+
xscale=plot["scale x"],
|
|
822
|
+
yscale=plot["scale y"],
|
|
823
|
+
hue_type=hue_type,
|
|
824
|
+
hue_kwargs=hue_kwargs,
|
|
825
|
+
isoelastics=plot["isoelastics"],
|
|
826
|
+
lut_identifier=plot["lut"])
|
|
827
|
+
# make sure the correct plot items are visible
|
|
828
|
+
# (e.g. scatter select)
|
|
829
|
+
self.on_tool()
|
|
830
|
+
# update polygon filter axis names
|
|
831
|
+
self.label_poly_x.setText(
|
|
832
|
+
dclab.dfn.get_feature_label(plot["axis x"]))
|
|
833
|
+
self.label_poly_y.setText(
|
|
834
|
+
dclab.dfn.get_feature_label(plot["axis y"]))
|
|
835
|
+
self.show_statistics()
|
|
836
|
+
# Make sure features are properly colored in the comboboxes
|
|
837
|
+
self.update_feature_choices()
|
|
838
|
+
|
|
839
|
+
@QtCore.pyqtSlot()
|
|
840
|
+
def plot_auto(self):
|
|
841
|
+
"""Update the plot only if the "Auto-apply" checkbox is checked"""
|
|
842
|
+
if self.checkBox_auto_apply.isChecked():
|
|
843
|
+
sender = self.sender()
|
|
844
|
+
for cb, sen in [
|
|
845
|
+
(self.checkBox_downsample, [self.spinBox_downsample]),
|
|
846
|
+
(self.checkBox_hue, [self.comboBox_hue,
|
|
847
|
+
self.comboBox_z_hue])]:
|
|
848
|
+
# Do not replot if the user changes the options for a
|
|
849
|
+
# disabled settings (e.g. downsampling, hue)
|
|
850
|
+
if sender in sen:
|
|
851
|
+
if not cb.isChecked():
|
|
852
|
+
break
|
|
853
|
+
else:
|
|
854
|
+
self.plot()
|
|
855
|
+
|
|
856
|
+
@show_wait_cursor
|
|
857
|
+
@QtCore.pyqtSlot(int)
|
|
858
|
+
def show_event(self, event):
|
|
859
|
+
"""Display the event data (image, contour, trace)
|
|
860
|
+
|
|
861
|
+
Parameters
|
|
862
|
+
----------
|
|
863
|
+
event: int
|
|
864
|
+
Event index of the dataset; indices start at 0
|
|
865
|
+
If set to None, the index from `self.spinBox_event`
|
|
866
|
+
will be used.
|
|
867
|
+
"""
|
|
868
|
+
if self.rtdc_ds is None:
|
|
869
|
+
return
|
|
870
|
+
|
|
871
|
+
# dataset
|
|
872
|
+
ds = self.rtdc_ds
|
|
873
|
+
self._dataset_event_plot_indices_cache[
|
|
874
|
+
id(self.rtdc_ds.hparent)] = event
|
|
875
|
+
event_count = ds.config["experiment"]["event count"]
|
|
876
|
+
if event_count == 0:
|
|
877
|
+
# nothing to do
|
|
878
|
+
return
|
|
879
|
+
# Update spin box data
|
|
880
|
+
self.spinBox_event.blockSignals(True)
|
|
881
|
+
self.spinBox_event.setValue(event + 1)
|
|
882
|
+
self.spinBox_event.blockSignals(False)
|
|
883
|
+
|
|
884
|
+
# Update selection point in scatter plot
|
|
885
|
+
self.widget_scatter.setSelection(event)
|
|
886
|
+
if self.tabWidget_event.currentIndex() == 0:
|
|
887
|
+
# update image
|
|
888
|
+
state = self.read_pipeline_state()
|
|
889
|
+
self.groupBox_image.setVisible(False)
|
|
890
|
+
|
|
891
|
+
view = "view_event"
|
|
892
|
+
for key in self.img_info.keys():
|
|
893
|
+
self.img_info[key][view].setVisible(False)
|
|
894
|
+
|
|
895
|
+
# if we have qpi data, image might be a different shape
|
|
896
|
+
if "qpi_pha" in ds:
|
|
897
|
+
self.get_event_image_and_show(ds, event, "qpi_pha", view)
|
|
898
|
+
if "qpi_amp" in ds:
|
|
899
|
+
self.get_event_image_and_show(ds, event, "qpi_amp", view)
|
|
900
|
+
elif "image" in ds:
|
|
901
|
+
self.get_event_image_and_show(ds, event, "image", view)
|
|
902
|
+
|
|
903
|
+
self.groupBox_image.setVisible(True)
|
|
904
|
+
|
|
905
|
+
if "trace" in ds:
|
|
906
|
+
# remove legend items
|
|
907
|
+
for item in reversed(self.legend_trace.items):
|
|
908
|
+
self.legend_trace.removeItem(item[1].text)
|
|
909
|
+
self.legend_trace.setVisible(state["event"]["trace legend"])
|
|
910
|
+
# get slot from identifier
|
|
911
|
+
# time axis
|
|
912
|
+
flsamples = ds.config["fluorescence"]["samples per event"]
|
|
913
|
+
flrate = ds.config["fluorescence"]["sample rate"]
|
|
914
|
+
fltime = np.arange(flsamples) / flrate * 1e6
|
|
915
|
+
# temporal range (min, max, fl-peak-maximum)
|
|
916
|
+
range_t = [fltime[0], fltime[-1], 0]
|
|
917
|
+
# fluorescence intensity
|
|
918
|
+
range_fl = [0, 0]
|
|
919
|
+
for key in dclab.dfn.FLUOR_TRACES:
|
|
920
|
+
if key.count("raw") and not state["event"]["trace raw"]:
|
|
921
|
+
# hide raw trace data if user decided so
|
|
922
|
+
show = False
|
|
923
|
+
else:
|
|
924
|
+
show = True
|
|
925
|
+
flid = key.split("_")[0]
|
|
926
|
+
if key in ds["trace"] and show:
|
|
927
|
+
# show the trace information
|
|
928
|
+
tracey = ds["trace"][key][event] # trace data
|
|
929
|
+
range_fl[0] = min(range_fl[0], tracey.min())
|
|
930
|
+
range_fl[1] = max(range_fl[1], tracey.max())
|
|
931
|
+
self.trace_plots[key].setData(fltime, tracey)
|
|
932
|
+
self.trace_plots[key].setVisible(True)
|
|
933
|
+
if state["event"]["trace zoom"]:
|
|
934
|
+
flpos = ds["{}_pos".format(flid)][event]
|
|
935
|
+
flwidth = ds["{}_width".format(flid)][event]
|
|
936
|
+
flmax = ds["{}_max".format(flid)][event]
|
|
937
|
+
# use the peak maximum to decide which range to use
|
|
938
|
+
if flmax > range_t[2]:
|
|
939
|
+
range_t[0] = flpos - 1.5 * flwidth
|
|
940
|
+
range_t[1] = flpos + 1.5 * flwidth
|
|
941
|
+
range_t[2] = flmax
|
|
942
|
+
# set legend name
|
|
943
|
+
ln = "{} {}".format(
|
|
944
|
+
self.slot.fl_name_dict[
|
|
945
|
+
"FL-{}".format(key[2])], key[4:])
|
|
946
|
+
self.legend_trace.addItem(self.trace_plots[key], ln)
|
|
947
|
+
self.legend_trace.update()
|
|
948
|
+
else:
|
|
949
|
+
self.trace_plots[key].setVisible(False)
|
|
950
|
+
self.graphicsView_trace.setXRange(*range_t[:2], padding=0)
|
|
951
|
+
if range_fl[0] != range_fl[1]:
|
|
952
|
+
self.graphicsView_trace.setYRange(*range_fl, padding=.01)
|
|
953
|
+
self.graphicsView_trace.setLimits(xMin=0, xMax=fltime[-1])
|
|
954
|
+
self.groupBox_trace.setVisible(True)
|
|
955
|
+
else:
|
|
956
|
+
self.groupBox_trace.setVisible(False)
|
|
957
|
+
else:
|
|
958
|
+
# only use computed features (speed)
|
|
959
|
+
fcands = ds.features_local
|
|
960
|
+
feats = [f for f in fcands if f in ds.features_scalar]
|
|
961
|
+
lf = sorted([(dclab.dfn.get_feature_label(f), f) for f in feats])
|
|
962
|
+
keys = []
|
|
963
|
+
vals = []
|
|
964
|
+
for lii, fii in lf:
|
|
965
|
+
keys.append(lii)
|
|
966
|
+
val = ds[fii][event]
|
|
967
|
+
if fii in idiom.INTEGER_FEATURES:
|
|
968
|
+
val = int(np.round(val))
|
|
969
|
+
vals.append(val)
|
|
970
|
+
self.tableWidget_feats.set_key_vals(keys, vals)
|
|
971
|
+
|
|
972
|
+
@show_wait_cursor
|
|
973
|
+
@QtCore.pyqtSlot(object, object)
|
|
974
|
+
def show_rtdc(self, rtdc_ds, slot):
|
|
975
|
+
"""Display an RT-DC measurement given by `path` and `filters`"""
|
|
976
|
+
if np.all(rtdc_ds.filter.all) and rtdc_ds.format == "hierarchy":
|
|
977
|
+
# No filers applied, no additional hierarchy child required.
|
|
978
|
+
self.rtdc_ds = rtdc_ds
|
|
979
|
+
else:
|
|
980
|
+
# Create a hierarchy child so that the user can browse
|
|
981
|
+
# comfortably through the data without seeing hidden events.
|
|
982
|
+
self.rtdc_ds = dclab.new_dataset(
|
|
983
|
+
rtdc_ds,
|
|
984
|
+
identifier=f"child-of-{rtdc_ds.identifier}")
|
|
985
|
+
event_count = self.rtdc_ds.config["experiment"]["event count"]
|
|
986
|
+
if event_count == 0:
|
|
987
|
+
self.enable_interface(False)
|
|
988
|
+
self.label_noevents.setVisible(True)
|
|
989
|
+
self.on_tool(collapse=True)
|
|
990
|
+
return
|
|
991
|
+
else:
|
|
992
|
+
# make things visible
|
|
993
|
+
self.enable_interface(True)
|
|
994
|
+
self.label_noevents.setVisible(False)
|
|
995
|
+
|
|
996
|
+
# get the state
|
|
997
|
+
state = self.read_pipeline_state()
|
|
998
|
+
plot = state["plot"]
|
|
999
|
+
# remove event state (ill-defined for different datasets)
|
|
1000
|
+
state.pop("event")
|
|
1001
|
+
|
|
1002
|
+
self.slot = slot
|
|
1003
|
+
|
|
1004
|
+
# check whether axes exist in ds and change them to defaults
|
|
1005
|
+
# if necessary
|
|
1006
|
+
ds_features = sorted(self.rtdc_ds.features_scalar)
|
|
1007
|
+
if plot["axis x"] not in ds_features and ds_features:
|
|
1008
|
+
plot["axis x"] = ds_features[0]
|
|
1009
|
+
if plot["axis y"] not in ds_features and ds_features:
|
|
1010
|
+
if len(ds_features) > 1:
|
|
1011
|
+
plot["axis y"] = ds_features[1]
|
|
1012
|
+
else:
|
|
1013
|
+
# If there is only one feature, at least we
|
|
1014
|
+
# have set the state to a reasonable value.
|
|
1015
|
+
plot["axis y"] = ds_features[0]
|
|
1016
|
+
|
|
1017
|
+
# set control ranges
|
|
1018
|
+
self.spinBox_event.blockSignals(True)
|
|
1019
|
+
self.spinBox_event.setMaximum(event_count)
|
|
1020
|
+
self.spinBox_event.setToolTip("total: {}".format(event_count))
|
|
1021
|
+
cur_value = self._dataset_event_plot_indices_cache.get(
|
|
1022
|
+
id(rtdc_ds), 0) + 1
|
|
1023
|
+
self.spinBox_event.setValue(cur_value)
|
|
1024
|
+
self.spinBox_event.blockSignals(False)
|
|
1025
|
+
|
|
1026
|
+
# set quick view state
|
|
1027
|
+
self.write_pipeline_state(state)
|
|
1028
|
+
# scatter plot
|
|
1029
|
+
self.plot()
|
|
1030
|
+
# reset image view
|
|
1031
|
+
self.groupBox_image.setVisible(False)
|
|
1032
|
+
self.groupBox_trace.setVisible(False)
|
|
1033
|
+
# this only updates the size of the tools (because there is no
|
|
1034
|
+
# sender)
|
|
1035
|
+
self.on_tool()
|
|
1036
|
+
|
|
1037
|
+
def show_statistics(self):
|
|
1038
|
+
h, v = self.get_statistics()
|
|
1039
|
+
if h is not None:
|
|
1040
|
+
self.tableWidget_stat.set_key_vals(keys=h, vals=v)
|
|
1041
|
+
|
|
1042
|
+
def update_feature_choices(self):
|
|
1043
|
+
"""Updates the axes comboboxes choices
|
|
1044
|
+
|
|
1045
|
+
This is used e.g. when emodulus becomes available
|
|
1046
|
+
"""
|
|
1047
|
+
if self.rtdc_ds is not None:
|
|
1048
|
+
# axes combobox choices
|
|
1049
|
+
self.comboBox_x.update_feature_list()
|
|
1050
|
+
self.comboBox_y.update_feature_list()
|
|
1051
|
+
self.comboBox_z_hue.update_feature_list()
|
|
1052
|
+
|
|
1053
|
+
@QtCore.pyqtSlot()
|
|
1054
|
+
def update_polygon_panel(self):
|
|
1055
|
+
"""Update polygon filter combobox etc."""
|
|
1056
|
+
if self.label_poly_modify.isVisible():
|
|
1057
|
+
# User is currently modifying a polygon filter (issue 148).
|
|
1058
|
+
# We discard the user's changes.
|
|
1059
|
+
self.on_poly_done_cancel()
|
|
1060
|
+
|
|
1061
|
+
pfts = dclab.PolygonFilter.instances
|
|
1062
|
+
self.comboBox_poly.blockSignals(True)
|
|
1063
|
+
self.comboBox_poly.clear()
|
|
1064
|
+
self.comboBox_poly.addItem("Choose...", None)
|
|
1065
|
+
for pf in pfts:
|
|
1066
|
+
self.comboBox_poly.addItem(pf.name, pf.unique_id)
|
|
1067
|
+
self.comboBox_poly.blockSignals(False)
|
|
1068
|
+
self.comboBox_poly.setEnabled(True)
|
|
1069
|
+
if not pfts:
|
|
1070
|
+
# disable combo box if there are no filters
|
|
1071
|
+
self.comboBox_poly.setEnabled(False)
|
|
1072
|
+
self.groupBox_poly.setEnabled(False)
|