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.
Files changed (267) hide show
  1. dcscope/__init__.py +17 -0
  2. dcscope/__main__.py +44 -0
  3. dcscope/_version.py +21 -0
  4. dcscope/extensions.py +250 -0
  5. dcscope/gui/__init__.py +2 -0
  6. dcscope/gui/analysis/__init__.py +10 -0
  7. dcscope/gui/analysis/ana_basins.py +110 -0
  8. dcscope/gui/analysis/ana_basins.ui +143 -0
  9. dcscope/gui/analysis/ana_filter.py +341 -0
  10. dcscope/gui/analysis/ana_filter.ui +364 -0
  11. dcscope/gui/analysis/ana_log.py +122 -0
  12. dcscope/gui/analysis/ana_log.ui +100 -0
  13. dcscope/gui/analysis/ana_meta.py +207 -0
  14. dcscope/gui/analysis/ana_meta.ui +141 -0
  15. dcscope/gui/analysis/ana_plot.py +638 -0
  16. dcscope/gui/analysis/ana_plot.ui +1060 -0
  17. dcscope/gui/analysis/ana_slot.py +504 -0
  18. dcscope/gui/analysis/ana_slot.ui +684 -0
  19. dcscope/gui/analysis/ana_tables.py +227 -0
  20. dcscope/gui/analysis/ana_tables.ui +195 -0
  21. dcscope/gui/analysis/ana_view.py +69 -0
  22. dcscope/gui/analysis/ana_view.ui +269 -0
  23. dcscope/gui/analysis/dlg_slot_reorder.py +55 -0
  24. dcscope/gui/analysis/dlg_slot_reorder.ui +138 -0
  25. dcscope/gui/bulk/__init__.py +2 -0
  26. dcscope/gui/bulk/bulk_emodulus.py +200 -0
  27. dcscope/gui/bulk/bulk_emodulus.ui +245 -0
  28. dcscope/gui/compute/__init__.py +3 -0
  29. dcscope/gui/compute/comp_lme4.py +115 -0
  30. dcscope/gui/compute/comp_lme4.ui +194 -0
  31. dcscope/gui/compute/comp_lme4_dataset.py +51 -0
  32. dcscope/gui/compute/comp_lme4_dataset.ui +92 -0
  33. dcscope/gui/compute/comp_lme4_results.py +120 -0
  34. dcscope/gui/compute/comp_lme4_results.ui +268 -0
  35. dcscope/gui/compute/comp_stats.py +223 -0
  36. dcscope/gui/compute/comp_stats.ui +187 -0
  37. dcscope/gui/dcor/__init__.py +304 -0
  38. dcscope/gui/dcor/dcor.ui +118 -0
  39. dcscope/gui/export/__init__.py +3 -0
  40. dcscope/gui/export/e2data.py +394 -0
  41. dcscope/gui/export/e2data.ui +367 -0
  42. dcscope/gui/export/e2filter.py +76 -0
  43. dcscope/gui/export/e2filter.ui +111 -0
  44. dcscope/gui/export/e2plot.py +94 -0
  45. dcscope/gui/export/e2plot.ui +175 -0
  46. dcscope/gui/main.py +1089 -0
  47. dcscope/gui/main.ui +579 -0
  48. dcscope/gui/matrix/__init__.py +4 -0
  49. dcscope/gui/matrix/block_matrix.py +141 -0
  50. dcscope/gui/matrix/block_matrix.ui +207 -0
  51. dcscope/gui/matrix/data_matrix.py +574 -0
  52. dcscope/gui/matrix/dm_dataset.py +116 -0
  53. dcscope/gui/matrix/dm_dataset.ui +348 -0
  54. dcscope/gui/matrix/dm_element.py +111 -0
  55. dcscope/gui/matrix/dm_element.ui +169 -0
  56. dcscope/gui/matrix/dm_filter.py +115 -0
  57. dcscope/gui/matrix/dm_filter.ui +287 -0
  58. dcscope/gui/matrix/plot_matrix.py +316 -0
  59. dcscope/gui/matrix/pm_element.py +39 -0
  60. dcscope/gui/matrix/pm_plot.py +86 -0
  61. dcscope/gui/matrix/pm_plot.ui +249 -0
  62. dcscope/gui/pipeline_plot.py +797 -0
  63. dcscope/gui/pipeline_plot.ui +66 -0
  64. dcscope/gui/preferences.py +380 -0
  65. dcscope/gui/preferences.ui +554 -0
  66. dcscope/gui/quick_view/__init__.py +1 -0
  67. dcscope/gui/quick_view/qv_main.py +1072 -0
  68. dcscope/gui/quick_view/qv_main.ui +1297 -0
  69. dcscope/gui/quick_view/qv_scatter.py +284 -0
  70. dcscope/gui/quick_view/qv_style.css +10 -0
  71. dcscope/gui/update.py +90 -0
  72. dcscope/gui/widgets/__init__.py +15 -0
  73. dcscope/gui/widgets/bg_thread.py +29 -0
  74. dcscope/gui/widgets/bulk_list.py +79 -0
  75. dcscope/gui/widgets/bulk_list.ui +95 -0
  76. dcscope/gui/widgets/double_spin_box_nan.py +75 -0
  77. dcscope/gui/widgets/feature_combobox.py +140 -0
  78. dcscope/gui/widgets/get_path.py +42 -0
  79. dcscope/gui/widgets/key_value_table_widget.css +12 -0
  80. dcscope/gui/widgets/key_value_table_widget.py +75 -0
  81. dcscope/gui/widgets/mdi_subwindow_wo_close.py +12 -0
  82. dcscope/gui/widgets/qrangeslider.py +262 -0
  83. dcscope/gui/widgets/rangecontrol.py +308 -0
  84. dcscope/gui/widgets/rangecontrol.ui +136 -0
  85. dcscope/gui/widgets/simple_image_view.py +55 -0
  86. dcscope/gui/widgets/simple_plot_widget.py +113 -0
  87. dcscope/gui/widgets/so_colorbaritem.py +29 -0
  88. dcscope/gui/widgets/wait_cursor.py +33 -0
  89. dcscope/idiom.py +26 -0
  90. dcscope/img/__init__.py +2 -0
  91. dcscope/img/icon-theme/breeze/COPYING-ICONS +209 -0
  92. dcscope/img/icon-theme/breeze/COPYING.LIB +510 -0
  93. dcscope/img/icon-theme/breeze/README +5 -0
  94. dcscope/img/icon-theme/breeze/actions/16/application-exit.svg +17 -0
  95. dcscope/img/icon-theme/breeze/actions/16/code-context.svg +13 -0
  96. dcscope/img/icon-theme/breeze/actions/16/dialog-cancel.svg +13 -0
  97. dcscope/img/icon-theme/breeze/actions/16/dialog-close.svg +17 -0
  98. dcscope/img/icon-theme/breeze/actions/16/dialog-messages.svg +13 -0
  99. dcscope/img/icon-theme/breeze/actions/16/dialog-ok-apply.svg +13 -0
  100. dcscope/img/icon-theme/breeze/actions/16/dialog-ok.svg +13 -0
  101. dcscope/img/icon-theme/breeze/actions/16/document-open-folder.svg +13 -0
  102. dcscope/img/icon-theme/breeze/actions/16/document-open.svg +13 -0
  103. dcscope/img/icon-theme/breeze/actions/16/document-save.svg +13 -0
  104. dcscope/img/icon-theme/breeze/actions/16/documentinfo.svg +12 -0
  105. dcscope/img/icon-theme/breeze/actions/16/draw-watercolor.svg +13 -0
  106. dcscope/img/icon-theme/breeze/actions/16/edit-clear-all.svg +13 -0
  107. dcscope/img/icon-theme/breeze/actions/16/edit-clear.svg +13 -0
  108. dcscope/img/icon-theme/breeze/actions/16/edit-paste.svg +13 -0
  109. dcscope/img/icon-theme/breeze/actions/16/globe.svg +16 -0
  110. dcscope/img/icon-theme/breeze/actions/16/gtk-preferences.svg +13 -0
  111. dcscope/img/icon-theme/breeze/actions/16/list-add.svg +13 -0
  112. dcscope/img/icon-theme/breeze/actions/16/messagebox_warning.svg +20 -0
  113. dcscope/img/icon-theme/breeze/actions/16/object-columns.svg +13 -0
  114. dcscope/img/icon-theme/breeze/actions/16/object-order-lower.svg +13 -0
  115. dcscope/img/icon-theme/breeze/actions/16/object-rows.svg +13 -0
  116. dcscope/img/icon-theme/breeze/actions/16/office-chart-line-stacked.svg +13 -0
  117. dcscope/img/icon-theme/breeze/actions/16/office-chart-ring.svg +13 -0
  118. dcscope/img/icon-theme/breeze/actions/16/office-chart-scatter.svg +13 -0
  119. dcscope/img/icon-theme/breeze/actions/16/path-mode-polyline.svg +13 -0
  120. dcscope/img/icon-theme/breeze/actions/16/preferences-activities.svg +13 -0
  121. dcscope/img/icon-theme/breeze/actions/16/remove.svg +10 -0
  122. dcscope/img/icon-theme/breeze/actions/16/search.svg +13 -0
  123. dcscope/img/icon-theme/breeze/actions/16/show-grid.svg +13 -0
  124. dcscope/img/icon-theme/breeze/actions/16/special_paste.svg +13 -0
  125. dcscope/img/icon-theme/breeze/actions/16/tools-wizard.svg +13 -0
  126. dcscope/img/icon-theme/breeze/actions/16/view-calendar-list.svg +13 -0
  127. dcscope/img/icon-theme/breeze/actions/16/view-filter.svg +13 -0
  128. dcscope/img/icon-theme/breeze/actions/16/view-list-tree.svg +13 -0
  129. dcscope/img/icon-theme/breeze/actions/16/view-statistics.svg +13 -0
  130. dcscope/img/icon-theme/breeze/actions/16/visibility.svg +21 -0
  131. dcscope/img/icon-theme/breeze/actions/22/application-exit.svg +13 -0
  132. dcscope/img/icon-theme/breeze/actions/22/code-context.svg +14 -0
  133. dcscope/img/icon-theme/breeze/actions/22/dialog-cancel.svg +14 -0
  134. dcscope/img/icon-theme/breeze/actions/22/dialog-close.svg +10 -0
  135. dcscope/img/icon-theme/breeze/actions/22/dialog-messages.svg +14 -0
  136. dcscope/img/icon-theme/breeze/actions/22/dialog-ok-apply.svg +14 -0
  137. dcscope/img/icon-theme/breeze/actions/22/dialog-ok.svg +14 -0
  138. dcscope/img/icon-theme/breeze/actions/22/document-open-folder.svg +14 -0
  139. dcscope/img/icon-theme/breeze/actions/22/document-open.svg +14 -0
  140. dcscope/img/icon-theme/breeze/actions/22/document-save.svg +14 -0
  141. dcscope/img/icon-theme/breeze/actions/22/documentinfo.svg +14 -0
  142. dcscope/img/icon-theme/breeze/actions/22/draw-watercolor.svg +14 -0
  143. dcscope/img/icon-theme/breeze/actions/22/edit-clear-all.svg +14 -0
  144. dcscope/img/icon-theme/breeze/actions/22/edit-clear.svg +14 -0
  145. dcscope/img/icon-theme/breeze/actions/22/edit-paste.svg +14 -0
  146. dcscope/img/icon-theme/breeze/actions/22/globe.svg +12 -0
  147. dcscope/img/icon-theme/breeze/actions/22/gtk-preferences.svg +13 -0
  148. dcscope/img/icon-theme/breeze/actions/22/list-add.svg +14 -0
  149. dcscope/img/icon-theme/breeze/actions/22/messagebox_warning.svg +17 -0
  150. dcscope/img/icon-theme/breeze/actions/22/object-columns.svg +14 -0
  151. dcscope/img/icon-theme/breeze/actions/22/object-order-lower.svg +17 -0
  152. dcscope/img/icon-theme/breeze/actions/22/object-rows.svg +17 -0
  153. dcscope/img/icon-theme/breeze/actions/22/office-chart-line-stacked.svg +17 -0
  154. dcscope/img/icon-theme/breeze/actions/22/office-chart-ring.svg +17 -0
  155. dcscope/img/icon-theme/breeze/actions/22/office-chart-scatter.svg +17 -0
  156. dcscope/img/icon-theme/breeze/actions/22/path-mode-polyline.svg +17 -0
  157. dcscope/img/icon-theme/breeze/actions/22/preferences-activities.svg +14 -0
  158. dcscope/img/icon-theme/breeze/actions/22/remove.svg +10 -0
  159. dcscope/img/icon-theme/breeze/actions/22/special_paste.svg +14 -0
  160. dcscope/img/icon-theme/breeze/actions/22/tools-wizard.svg +14 -0
  161. dcscope/img/icon-theme/breeze/actions/22/view-calendar-list.svg +14 -0
  162. dcscope/img/icon-theme/breeze/actions/22/view-filter.svg +14 -0
  163. dcscope/img/icon-theme/breeze/actions/22/view-list-tree.svg +14 -0
  164. dcscope/img/icon-theme/breeze/actions/22/view-statistics.svg +14 -0
  165. dcscope/img/icon-theme/breeze/actions/22/visibility.svg +14 -0
  166. dcscope/img/icon-theme/breeze/actions/24/application-exit.svg +12 -0
  167. dcscope/img/icon-theme/breeze/actions/24/code-context.svg +12 -0
  168. dcscope/img/icon-theme/breeze/actions/24/dialog-cancel.svg +12 -0
  169. dcscope/img/icon-theme/breeze/actions/24/dialog-close.svg +10 -0
  170. dcscope/img/icon-theme/breeze/actions/24/dialog-messages.svg +12 -0
  171. dcscope/img/icon-theme/breeze/actions/24/dialog-ok-apply.svg +12 -0
  172. dcscope/img/icon-theme/breeze/actions/24/dialog-ok.svg +14 -0
  173. dcscope/img/icon-theme/breeze/actions/24/document-open-folder.svg +12 -0
  174. dcscope/img/icon-theme/breeze/actions/24/document-open.svg +12 -0
  175. dcscope/img/icon-theme/breeze/actions/24/document-save.svg +12 -0
  176. dcscope/img/icon-theme/breeze/actions/24/documentinfo.svg +14 -0
  177. dcscope/img/icon-theme/breeze/actions/24/draw-watercolor.svg +12 -0
  178. dcscope/img/icon-theme/breeze/actions/24/edit-clear-all.svg +14 -0
  179. dcscope/img/icon-theme/breeze/actions/24/edit-clear.svg +12 -0
  180. dcscope/img/icon-theme/breeze/actions/24/edit-paste.svg +12 -0
  181. dcscope/img/icon-theme/breeze/actions/24/globe.svg +14 -0
  182. dcscope/img/icon-theme/breeze/actions/24/gtk-preferences.svg +13 -0
  183. dcscope/img/icon-theme/breeze/actions/24/list-add.svg +12 -0
  184. dcscope/img/icon-theme/breeze/actions/24/messagebox_warning.svg +13 -0
  185. dcscope/img/icon-theme/breeze/actions/24/object-columns.svg +12 -0
  186. dcscope/img/icon-theme/breeze/actions/24/object-order-lower.svg +15 -0
  187. dcscope/img/icon-theme/breeze/actions/24/object-rows.svg +15 -0
  188. dcscope/img/icon-theme/breeze/actions/24/office-chart-line-stacked.svg +15 -0
  189. dcscope/img/icon-theme/breeze/actions/24/office-chart-ring.svg +15 -0
  190. dcscope/img/icon-theme/breeze/actions/24/office-chart-scatter.svg +15 -0
  191. dcscope/img/icon-theme/breeze/actions/24/path-mode-polyline.svg +15 -0
  192. dcscope/img/icon-theme/breeze/actions/24/preferences-activities.svg +13 -0
  193. dcscope/img/icon-theme/breeze/actions/24/remove.svg +10 -0
  194. dcscope/img/icon-theme/breeze/actions/24/special_paste.svg +12 -0
  195. dcscope/img/icon-theme/breeze/actions/24/tools-wizard.svg +12 -0
  196. dcscope/img/icon-theme/breeze/actions/24/view-calendar-list.svg +12 -0
  197. dcscope/img/icon-theme/breeze/actions/24/view-filter.svg +12 -0
  198. dcscope/img/icon-theme/breeze/actions/24/view-list-tree.svg +12 -0
  199. dcscope/img/icon-theme/breeze/actions/24/view-statistics.svg +12 -0
  200. dcscope/img/icon-theme/breeze/actions/24/visibility.svg +14 -0
  201. dcscope/img/icon-theme/breeze/actions/32/application-exit.svg +8 -0
  202. dcscope/img/icon-theme/breeze/actions/32/dialog-cancel.svg +13 -0
  203. dcscope/img/icon-theme/breeze/actions/32/dialog-messages.svg +13 -0
  204. dcscope/img/icon-theme/breeze/actions/32/dialog-ok-apply.svg +1 -0
  205. dcscope/img/icon-theme/breeze/actions/32/dialog-ok.svg +1 -0
  206. dcscope/img/icon-theme/breeze/actions/32/document-open-folder.svg +13 -0
  207. dcscope/img/icon-theme/breeze/actions/32/document-open.svg +13 -0
  208. dcscope/img/icon-theme/breeze/actions/32/document-save.svg +18 -0
  209. dcscope/img/icon-theme/breeze/actions/32/gtk-preferences.svg +17 -0
  210. dcscope/img/icon-theme/breeze/actions/32/object-order-lower.svg +18 -0
  211. dcscope/img/icon-theme/breeze/actions/32/view-filter.svg +18 -0
  212. dcscope/img/icon-theme/breeze/actions/32/view-list-tree.svg +18 -0
  213. dcscope/img/icon-theme/breeze/places/16/folder-cloud.svg +13 -0
  214. dcscope/img/icon-theme/breeze/places/16/folder.svg +13 -0
  215. dcscope/img/icon-theme/breeze/places/22/folder-cloud.svg +13 -0
  216. dcscope/img/icon-theme/breeze/places/22/folder.svg +13 -0
  217. dcscope/img/icon-theme/breeze/places/24/folder-cloud.svg +12 -0
  218. dcscope/img/icon-theme/breeze/places/24/folder.svg +12 -0
  219. dcscope/img/icon-theme/breeze/places/32/folder-cloud.svg +35 -0
  220. dcscope/img/icon-theme/breeze/places/32/folder.svg +30 -0
  221. dcscope/img/icon-theme/breeze/places/64/folder-cloud.svg +35 -0
  222. dcscope/img/icon-theme/breeze/places/64/folder.svg +30 -0
  223. dcscope/img/icon-theme/breeze/status/16/dialog-error.svg +9 -0
  224. dcscope/img/icon-theme/breeze/status/16/dialog-information.svg +9 -0
  225. dcscope/img/icon-theme/breeze/status/16/dialog-question.svg +7 -0
  226. dcscope/img/icon-theme/breeze/status/16/dialog-warning.svg +9 -0
  227. dcscope/img/icon-theme/breeze/status/22/dialog-error.svg +9 -0
  228. dcscope/img/icon-theme/breeze/status/22/dialog-information.svg +9 -0
  229. dcscope/img/icon-theme/breeze/status/22/dialog-question.svg +7 -0
  230. dcscope/img/icon-theme/breeze/status/22/dialog-warning.svg +9 -0
  231. dcscope/img/icon-theme/breeze/status/24/dialog-error.svg +13 -0
  232. dcscope/img/icon-theme/breeze/status/24/dialog-information.svg +13 -0
  233. dcscope/img/icon-theme/breeze/status/24/dialog-question.svg +9 -0
  234. dcscope/img/icon-theme/breeze/status/24/dialog-warning.svg +1 -0
  235. dcscope/img/icon-theme/breeze/status/64/dialog-error.svg +25 -0
  236. dcscope/img/icon-theme/breeze/status/64/dialog-information.svg +25 -0
  237. dcscope/img/icon-theme/breeze/status/64/dialog-question.svg +25 -0
  238. dcscope/img/icon-theme/breeze/status/64/dialog-warning.svg +25 -0
  239. dcscope/img/icon-theme/collect_icons.py +124 -0
  240. dcscope/img/icon-theme/dcscope/dcor.png +0 -0
  241. dcscope/img/icon-theme/dcscope/edit-copy-anew.svg +13 -0
  242. dcscope/img/icon-theme/dcscope/emodulus.svg +69 -0
  243. dcscope/img/icon-theme/dcscope/filter_ray.svg +54 -0
  244. dcscope/img/icon-theme/dcscope/region_channel.svg +86 -0
  245. dcscope/img/icon-theme/dcscope/region_reservoir.svg +89 -0
  246. dcscope/img/icon-theme/dcscope/rlang.svg +108 -0
  247. dcscope/img/icon-theme/dcscope/statistical_significance.svg +106 -0
  248. dcscope/img/icon-theme/index.theme +81 -0
  249. dcscope/img/icon.png +0 -0
  250. dcscope/img/icon.svg +73 -0
  251. dcscope/img/splash.png +0 -0
  252. dcscope/meta_tool.py +143 -0
  253. dcscope/pipeline/__init__.py +5 -0
  254. dcscope/pipeline/core.py +632 -0
  255. dcscope/pipeline/dataslot.py +270 -0
  256. dcscope/pipeline/filter.py +161 -0
  257. dcscope/pipeline/filter_ray.py +170 -0
  258. dcscope/pipeline/plot.py +187 -0
  259. dcscope/plot_cache.py +79 -0
  260. dcscope/session.py +379 -0
  261. dcscope/util.py +34 -0
  262. dcscope-2.22.1.dist-info/METADATA +95 -0
  263. dcscope-2.22.1.dist-info/RECORD +267 -0
  264. dcscope-2.22.1.dist-info/WHEEL +5 -0
  265. dcscope-2.22.1.dist-info/entry_points.txt +2 -0
  266. dcscope-2.22.1.dist-info/licenses/LICENSE +621 -0
  267. 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)