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,574 @@
1
+ import copy
2
+
3
+ import numpy as np
4
+ from PyQt6 import QtCore, QtWidgets
5
+
6
+ from ... import pipeline
7
+
8
+ from .dm_dataset import MatrixDataset
9
+ from .dm_filter import MatrixFilter
10
+ from .dm_element import MatrixElement
11
+
12
+
13
+ class DataMatrix(QtWidgets.QWidget):
14
+ quickviewed = QtCore.pyqtSignal(int, int)
15
+ matrix_changed = QtCore.pyqtSignal()
16
+ filter_modify_clicked = QtCore.pyqtSignal(str)
17
+ slot_modify_clicked = QtCore.pyqtSignal(str)
18
+
19
+ def __init__(self, *args, **kwargs):
20
+ super(DataMatrix, self).__init__(*args, **kwargs)
21
+
22
+ self.glo = None
23
+ self._reset_layout()
24
+
25
+ self.setAcceptDrops(True)
26
+
27
+ # used for toggling between all active, all inactive and semi state
28
+ self.semi_states_dataset = {}
29
+ self.semi_states_filter = {}
30
+
31
+ # used for remembering quickview element
32
+ self._old_quickview_instance = None
33
+
34
+ def read_pipeline_state(self):
35
+ """State of the current data matrix"""
36
+ # slots
37
+ slot_states = []
38
+ slots_used = []
39
+ for dw in self.dataset_widgets:
40
+ dw_state = dw.read_pipeline_state()
41
+ slot = pipeline.Dataslot.get_slot(dw_state["identifier"])
42
+ slot.slot_used = dw_state["enabled"]
43
+ slot_states.append(slot.__getstate__())
44
+ if dw_state["enabled"]:
45
+ slots_used.append(dw_state["identifier"])
46
+
47
+ # filters
48
+ filter_states = []
49
+ filters_used = []
50
+ for fw in self.filter_widgets:
51
+ fw_state = fw.read_pipeline_state()
52
+ filt = pipeline.Filter.get_filter(fw_state["identifier"])
53
+ filter_states.append(filt.__getstate__())
54
+ if fw_state["enabled"]:
55
+ filters_used.append(fw_state["identifier"])
56
+ # elements
57
+ mestates = {}
58
+ for dw in self.dataset_widgets:
59
+ idict = {}
60
+ for fw in self.filter_widgets:
61
+ me = self.get_matrix_element(dw.identifier, fw.identifier)
62
+ # We only store the information about whether the user
63
+ # clicked this element. The state about "enabled" is stored
64
+ # in `slots_used` and `filters_used`.
65
+ idict[fw.identifier] = me.read_pipeline_state()["active"]
66
+ mestates[dw.identifier] = idict
67
+ state = {"elements": mestates,
68
+ "filters": filter_states,
69
+ "filters used": filters_used,
70
+ "slots": slot_states,
71
+ "slots used": slots_used,
72
+ }
73
+ return state
74
+
75
+ def write_pipeline_state(self, state):
76
+ # remember current QuickView identifiers
77
+ qv_slot_id, qv_filt_id = self.get_quickview_ids()
78
+ self.blockSignals(True)
79
+ self.clear()
80
+ # dataset states
81
+ for ii in range(len(state["slots"])):
82
+ slot_id = state["slots"][ii]["identifier"]
83
+ dw_state = {"path": state["slots"][ii]["path"],
84
+ "identifier": slot_id,
85
+ "enabled": slot_id in state["slots used"],
86
+ }
87
+ self.add_dataset(state=dw_state)
88
+ # filter states
89
+ for jj in range(len(state["filters"])):
90
+ filt_id = state["filters"][jj]["identifier"]
91
+ fw_state = {"identifier": filt_id,
92
+ "enabled": filt_id in state["filters used"],
93
+ "name": state["filters"][jj]["name"]
94
+ }
95
+ self.add_filter(state=fw_state)
96
+ # make sure elements exist
97
+ # (this also sets enabled/disabled state)
98
+ self.fill_elements()
99
+ # element states
100
+ MatrixElement._quick_view_instance = None
101
+ for slot_id in state["elements"]:
102
+ ds_state = state["elements"][slot_id]
103
+ for filt_id in ds_state:
104
+ me = self.get_matrix_element(slot_id, filt_id)
105
+ me_state = me.read_pipeline_state()
106
+ me_state["active"] = ds_state[filt_id]
107
+ me.write_pipeline_state(me_state)
108
+ # re-apply current quickview ids
109
+ try:
110
+ meqv = self.get_matrix_element(qv_slot_id, qv_filt_id)
111
+ except KeyError:
112
+ pass
113
+ else:
114
+ MatrixElement._quick_view_instance = meqv
115
+ self.update_content()
116
+ self.blockSignals(False)
117
+
118
+ def _reset_layout(self):
119
+ if self.glo is not None:
120
+ # send old layout to Nirvana eventually
121
+ layout = self.glo
122
+ self.glo = None
123
+ self.old_layout = QtWidgets.QWidget()
124
+ self.old_layout.setLayout(layout)
125
+ self.old_layout.hide()
126
+ self.old_layout.deleteLater()
127
+ # add new layout
128
+ self.glo = QtWidgets.QGridLayout()
129
+ self.glo.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
130
+ self.glo.setSpacing(2)
131
+ self.glo.setContentsMargins(0, 0, 0, 0)
132
+ # add dummy corner element
133
+ cl = QtWidgets.QLabel("Block\nMatrix")
134
+ cl.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
135
+ self.glo.addWidget(cl, 0, 0)
136
+ self.setLayout(self.glo)
137
+
138
+ @property
139
+ def dataset_widgets(self):
140
+ datasets = []
141
+ for ii in range(self.glo.rowCount()):
142
+ item = self.glo.itemAtPosition(ii+1, 0)
143
+ if item is not None:
144
+ ds = item.widget()
145
+ datasets.append(ds)
146
+ return datasets
147
+
148
+ @property
149
+ def element_width(self):
150
+ """Data matrix element width (without 2px spacing)"""
151
+ for jj in range(1, self.glo.columnCount()-1):
152
+ item = self.glo.itemAtPosition(0, jj)
153
+ if item is not None:
154
+ width = item.geometry().width()
155
+ break
156
+ else:
157
+ width = 67
158
+ return width
159
+
160
+ @property
161
+ def element_widget_dict(self):
162
+ els = {}
163
+ for ii, ws in enumerate(self.dataset_widgets):
164
+ elsd = {}
165
+ for jj, wf in enumerate(self.filter_widgets):
166
+ it = self.glo.itemAtPosition(ii+1, jj+1)
167
+ elsd[wf.identifier] = it.widget()
168
+ els[ws.identifier] = elsd
169
+ return els
170
+
171
+ @property
172
+ def filter_widgets(self):
173
+ filters = []
174
+ for jj in range(self.glo.columnCount()):
175
+ item = self.glo.itemAtPosition(0, jj+1)
176
+ if item is not None:
177
+ fs = item.widget()
178
+ filters.append(fs)
179
+ return filters
180
+
181
+ @property
182
+ def num_datasets(self):
183
+ count = 0
184
+ for ii in range(1, self.glo.rowCount()):
185
+ if self.glo.itemAtPosition(ii, 0) is not None:
186
+ count += 1
187
+ return count
188
+
189
+ @property
190
+ def num_filters(self):
191
+ count = 0
192
+ for jj in range(1, self.glo.columnCount()):
193
+ if self.glo.itemAtPosition(0, jj) is not None:
194
+ count += 1
195
+ return count
196
+
197
+ @property
198
+ def plot_matrix(self):
199
+ for ch in self.parent().children():
200
+ if ch.__class__.__name__ == "PlotMatrix":
201
+ break
202
+ else:
203
+ raise KeyError("PlotMatrix not found!")
204
+ return ch
205
+
206
+ def add_dataset(self, slot_id=None, state=None):
207
+ """Add a dataset to the DataMatrix"""
208
+ md = MatrixDataset(identifier=slot_id, state=state)
209
+ self.glo.addWidget(md, self.num_datasets+1, 0)
210
+ md.active_toggled.connect(self.toggle_dataset_active)
211
+ md.enabled_toggled.connect(self.toggle_dataset_enable)
212
+ md.enabled_toggled.connect(self.plot_matrix.toggle_dataset_enable)
213
+ md.option_action.connect(self.on_option_dataset)
214
+ md.modify_clicked.connect(self.slot_modify_clicked.emit)
215
+ self.fill_elements()
216
+ self.plot_matrix.fill_elements()
217
+ self.adjust_size()
218
+ self.plot_matrix.adjust_size() # important when opt/removing slots
219
+ self.publish_matrix()
220
+ return md
221
+
222
+ def add_filter(self, identifier=None, state=None):
223
+ mf = MatrixFilter(identifier=identifier, state=state)
224
+ mf.active_toggled.connect(self.toggle_filter_active)
225
+ mf.enabled_toggled.connect(self.toggle_filter_enable)
226
+ mf.option_action.connect(self.on_option_filter)
227
+ mf.modify_clicked.connect(self.filter_modify_clicked.emit)
228
+ self.glo.addWidget(mf, 0, self.num_filters+1)
229
+ self.fill_elements()
230
+ self.adjust_size()
231
+ self.plot_matrix.adjust_size() # important when opt/removing filters
232
+ self.publish_matrix()
233
+ return mf
234
+
235
+ def adjust_size(self):
236
+ QtWidgets.QApplication.processEvents(
237
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
238
+ self.setMinimumSize(self.sizeHint())
239
+ self.setFixedSize(self.sizeHint())
240
+ QtWidgets.QApplication.processEvents(
241
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
242
+ self.setMinimumSize(self.sizeHint())
243
+ self.setFixedSize(self.sizeHint())
244
+
245
+ def changed_element(self):
246
+ self.publish_matrix()
247
+
248
+ def changed_quickview(self):
249
+ slot_index_qv, filt_index_qv = self.get_quickview_indices()
250
+ # events must be integer, use -1 to indicate None
251
+ if slot_index_qv is None:
252
+ slot_index_qv = -1
253
+ if filt_index_qv is None:
254
+ filt_index_qv = -1
255
+ self.quickviewed.emit(slot_index_qv, filt_index_qv)
256
+
257
+ def enable_quickview(self, b=True):
258
+ if b:
259
+ MatrixElement._quick_view_instance = self._old_quickview_instance
260
+ else:
261
+ self._old_quickview_instance = MatrixElement._quick_view_instance
262
+ MatrixElement._quick_view_instance = None
263
+ self.update_content()
264
+
265
+ def clear(self):
266
+ """Reset layout"""
267
+ self._reset_layout()
268
+
269
+ def dragEnterEvent(self, event):
270
+ # drag enter event on data matrix
271
+ event.ignore()
272
+
273
+ def dropEvent(self, event):
274
+ # drag drop event on data matrix
275
+ event.ignore()
276
+
277
+ def fill_elements(self):
278
+ # add widgets
279
+ for ii in range(self.num_datasets):
280
+ for jj in range(self.num_filters):
281
+ if self.glo.itemAtPosition(ii+1, jj+1) is None:
282
+ me = MatrixElement()
283
+ me.quickview_selected.connect(self.changed_quickview)
284
+ me.element_changed.connect(self.changed_element)
285
+ self.glo.addWidget(me, ii+1, jj+1)
286
+ # make sure enabled/disabled is honored
287
+ state = self.read_pipeline_state()
288
+ for slot_sate in state["slots"]:
289
+ slot_id = slot_sate["identifier"]
290
+ for filt_state in state["filters"]:
291
+ filt_id = filt_state["identifier"]
292
+ if not (slot_id in state["slots used"]
293
+ and filt_id in state["filters used"]):
294
+ me = self.get_matrix_element(slot_id, filt_id)
295
+ mstate = me.read_pipeline_state()
296
+ mstate["enabled"] = False
297
+ me.write_pipeline_state(mstate)
298
+
299
+ def get_filter_index(self, filter_id):
300
+ for ii, fs in enumerate(self.filter_widgets):
301
+ if fs.identifier == filter_id:
302
+ break
303
+ else:
304
+ raise KeyError("Filter '{}' not found!".format(filter_id))
305
+ return ii
306
+
307
+ def get_filter_widget_state(self, filter_id):
308
+ ii = self.get_filter_index(filter_id)
309
+ fw = self.filter_widgets[ii]
310
+ return fw.read_pipeline_state()
311
+
312
+ def get_slot_index(self, slot_id):
313
+ for ii, dw in enumerate(self.dataset_widgets):
314
+ if dw.identifier == slot_id:
315
+ break
316
+ else:
317
+ raise KeyError("Dataset '{}' not found!".format(slot_id))
318
+ return ii
319
+
320
+ def get_slot_widget_state(self, slot_id, ret_index=False):
321
+ ii = self.get_slot_index(slot_id)
322
+ dw = self.dataset_widgets[ii]
323
+ if ret_index:
324
+ return dw.read_pipeline_state(), ii
325
+ else:
326
+ return dw.read_pipeline_state()
327
+
328
+ def get_matrix_element(self, slot_id, filt_id):
329
+ """Return matrix element matching dataset and filter identifiers"""
330
+ ii, jj = self.get_matrix_indices(slot_id, filt_id)
331
+ return self.glo.itemAtPosition(ii, jj).widget()
332
+
333
+ def get_matrix_indices(self, slot_id, filt_id):
334
+ ncols = self.glo.columnCount()
335
+ nrows = self.glo.rowCount()
336
+ for ii in range(1, nrows):
337
+ ds = self.glo.itemAtPosition(ii, 0).widget()
338
+ if ds.identifier == slot_id:
339
+ for jj in range(1, ncols):
340
+ f = self.glo.itemAtPosition(0, jj).widget()
341
+ if f.identifier == filt_id:
342
+ break
343
+ else:
344
+ raise KeyError("Filter '{}' not found!".format(filt_id))
345
+ break
346
+ else:
347
+ raise KeyError("Dataset '{}' not found!".format(slot_id))
348
+ return ii, jj
349
+
350
+ def get_quickview_ids(self):
351
+ current = MatrixElement._quick_view_instance
352
+ if current is not None:
353
+ try:
354
+ state = self.read_pipeline_state()
355
+ except KeyError:
356
+ # the state is not valid (issue #25)
357
+ return None, None
358
+ for slot_id in state["elements"]:
359
+ ds_state = state["elements"][slot_id]
360
+ for filt_id in ds_state:
361
+ me = self.get_matrix_element(slot_id, filt_id)
362
+ if current == me:
363
+ return slot_id, filt_id
364
+ else:
365
+ # no valid QuickView selection (issue #38)
366
+ return None, None
367
+ else:
368
+ return None, None
369
+
370
+ def get_quickview_indices(self):
371
+ slot_id, filt_id = self.get_quickview_ids()
372
+ if slot_id is not None:
373
+ ii, jj = self.get_matrix_indices(slot_id, filt_id)
374
+ return ii - 1, jj - 1
375
+ else:
376
+ return None, None
377
+
378
+ @QtCore.pyqtSlot(str)
379
+ def on_option_dataset(self, option):
380
+ """Dataset option logic (remove, insert_anew, duplicate)"""
381
+ dw_state = self.sender().read_pipeline_state()
382
+ slot_id = dw_state["identifier"]
383
+ slot_index = self.get_slot_index(slot_id)
384
+ state = self.read_pipeline_state()
385
+ pstate = self.plot_matrix.read_pipeline_state()
386
+ if option == "remove":
387
+ state["slots"].pop(slot_index)
388
+ if slot_id in state["slots used"]:
389
+ state["slots used"].remove(slot_id)
390
+ state["elements"].pop(slot_id)
391
+ pstate["elements"].pop(slot_id)
392
+ else:
393
+ # create a new slot
394
+ slot = pipeline.Dataslot(path=dw_state["path"])
395
+ new_id = slot.identifier
396
+ if option == "duplicate":
397
+ # use original state
398
+ new_state = copy.deepcopy(state["slots"][slot_index])
399
+ # only set the new identifier (issue #96)
400
+ new_state["identifier"] = new_id
401
+ else: # insert_anew
402
+ new_state = slot.__getstate__()
403
+ slot.__setstate__(new_state)
404
+ state["slots"].insert(slot_index+1, new_state)
405
+ state["slots used"].append(new_id)
406
+ # this also takes care of filling up matrix elements
407
+ self.write_pipeline_state(state)
408
+ # this correctly assigns elements in plot matrix
409
+ # (also when option is not remove)
410
+ self.plot_matrix.write_pipeline_state(pstate)
411
+ self.plot_matrix.fill_elements()
412
+ self.publish_matrix()
413
+
414
+ @QtCore.pyqtSlot(str)
415
+ def on_option_filter(self, option):
416
+ """Filter option logic (remove, duplicate)"""
417
+ fw_state = self.sender().read_pipeline_state()
418
+ filt_id = fw_state["identifier"]
419
+ filt_index = self.get_filter_index(filt_id)
420
+ state = self.read_pipeline_state()
421
+ if option == "remove":
422
+ state["filters"].pop(filt_index)
423
+ # remove matrix elements
424
+ for ds_key in state["elements"]:
425
+ state["elements"][ds_key].pop(filt_id)
426
+ else: # duplicate
427
+ filt = pipeline.Filter()
428
+ new_state = copy.deepcopy(state["filters"][filt_index])
429
+ new_state["identifier"] = filt.identifier
430
+ new_state["name"] = filt.name
431
+ state["filters"].insert(filt_index+1, new_state)
432
+ state["filters used"].append(filt.identifier)
433
+ filt.__setstate__(new_state)
434
+ self.write_pipeline_state(state)
435
+ self.publish_matrix()
436
+
437
+ def publish_matrix(self):
438
+ """Publish state via self.matrix_changed signal for Pipeline"""
439
+ if not self.signalsBlocked():
440
+ self.matrix_changed.emit()
441
+ self.changed_quickview()
442
+
443
+ @QtCore.pyqtSlot()
444
+ def toggle_dataset_active(self):
445
+ """Switch between all active, all inactive, previous state
446
+
447
+ Modifies the matrix elements for a dataset/row,
448
+ which is defined by the signal sender :class:`MatrixDataset`.
449
+ Cyclic toggling order: semi -> all -> none
450
+ """
451
+ self.semi_states_filter = {} # sic
452
+ sender = self.sender()
453
+ slot_id = sender.identifier
454
+ state = self.read_pipeline_state()["elements"][slot_id]
455
+ num_actives = sum([s for s in state.values()])
456
+
457
+ # update state according to the scheme in the docstring
458
+ if num_actives == 0:
459
+ if slot_id in self.semi_states_dataset:
460
+ # use semi state
461
+ oldstate = self.semi_states_dataset[slot_id]
462
+ for filt_id in oldstate:
463
+ if filt_id in state:
464
+ state[filt_id] = oldstate[filt_id]
465
+ else:
466
+ # toggle all to active
467
+ for filt_id in state:
468
+ state[filt_id] = True
469
+ elif num_actives == len(state):
470
+ # toggle all to inactive
471
+ for filt_id in state:
472
+ state[filt_id] = False
473
+ else:
474
+ # save semi state
475
+ self.semi_states_dataset[slot_id] = copy.deepcopy(state)
476
+ # toggle all to active
477
+ for filt_id in state:
478
+ state[filt_id] = True
479
+
480
+ for filt_id in state:
481
+ me = self.get_matrix_element(slot_id, filt_id)
482
+ me.set_active(state[filt_id])
483
+ self.publish_matrix()
484
+
485
+ @QtCore.pyqtSlot(bool)
486
+ def toggle_dataset_enable(self, enabled):
487
+ sender = self.sender()
488
+ slot_id = sender.identifier
489
+ state = self.read_pipeline_state()
490
+ for filt_id in state["elements"][slot_id]:
491
+ # make sure that disabled filters are honored
492
+ fstate = self.get_filter_widget_state(filt_id)
493
+ fenabled = fstate["enabled"]
494
+ # update element widget
495
+ me = self.get_matrix_element(slot_id, filt_id)
496
+ mstate = me.read_pipeline_state()
497
+ mstate["enabled"] = np.logical_and(enabled, fenabled)
498
+ me.write_pipeline_state(mstate)
499
+ self.publish_matrix()
500
+
501
+ @QtCore.pyqtSlot()
502
+ def toggle_filter_active(self):
503
+ """Switch between all active, all inactive, previous state
504
+
505
+ Modifies the matrix elements for a filter/column,
506
+ which is defined by the signal sender :class:`MatrixFilter`.
507
+ Cyclic toggling order: semi -> all -> none
508
+ """
509
+ self.semi_states_dataset = {} # sic
510
+ sender = self.sender()
511
+ filt_id = sender.identifier
512
+
513
+ states = self.read_pipeline_state()["elements"]
514
+ state = {}
515
+ for slot_id in states:
516
+ state[slot_id] = states[slot_id][filt_id]
517
+
518
+ num_actives = sum(list(state.values()))
519
+
520
+ # update state according to the scheme in the docstring
521
+ if num_actives == 0:
522
+ if filt_id in self.semi_states_filter:
523
+ # use semi state
524
+ oldstate = self.semi_states_filter[filt_id]
525
+ for slot_id in oldstate:
526
+ if slot_id in state:
527
+ state[slot_id] = oldstate[slot_id]
528
+ else:
529
+ # toggle all to active
530
+ for slot_id in state:
531
+ state[slot_id] = True
532
+ elif num_actives == len(state):
533
+ # toggle all to inactive
534
+ for slot_id in state:
535
+ state[slot_id] = False
536
+ else:
537
+ # save semi state
538
+ self.semi_states_filter[filt_id] = copy.deepcopy(state)
539
+ # toggle all to active
540
+ for slot_id in state:
541
+ state[slot_id] = True
542
+
543
+ for slot_id in state:
544
+ me = self.get_matrix_element(slot_id, filt_id)
545
+ me.set_active(state[slot_id])
546
+ self.publish_matrix()
547
+
548
+ @QtCore.pyqtSlot(bool)
549
+ def toggle_filter_enable(self, enabled):
550
+ sender = self.sender()
551
+ sid = sender.identifier
552
+ state = self.read_pipeline_state()
553
+ for slot_id in state["elements"]:
554
+ # make sure that disabled filters are honored
555
+ dstate = self.get_slot_widget_state(slot_id)
556
+ denabled = dstate["enabled"]
557
+ # update element widget
558
+ me = self.get_matrix_element(slot_id, sid)
559
+ mstate = me.read_pipeline_state()
560
+ mstate["enabled"] = np.logical_and(enabled, denabled)
561
+ me.write_pipeline_state(mstate)
562
+ self.publish_matrix()
563
+
564
+ def update_content(self):
565
+ ncols = self.glo.columnCount()
566
+ nrows = self.glo.rowCount()
567
+ for ii in range(nrows):
568
+ for jj in range(ncols):
569
+ if ii == 0 and jj == 0:
570
+ # block matrix label
571
+ continue
572
+ item = self.glo.itemAtPosition(ii, jj)
573
+ if item is not None:
574
+ item.widget().update_content()
@@ -0,0 +1,116 @@
1
+ import importlib.resources
2
+
3
+ from PyQt6 import uic, QtWidgets, QtCore, QtGui
4
+
5
+ from ... import meta_tool
6
+ from ... import pipeline
7
+
8
+
9
+ class MatrixDataset(QtWidgets.QWidget):
10
+ active_toggled = QtCore.pyqtSignal()
11
+ enabled_toggled = QtCore.pyqtSignal(bool)
12
+ option_action = QtCore.pyqtSignal(str)
13
+ modify_clicked = QtCore.pyqtSignal(str)
14
+
15
+ def __init__(self, identifier=None, state=None, *args, **kwargs):
16
+ """Create a new dataset matrix element
17
+
18
+ Specify either an existing Dataslot identifier or a
19
+ Dataslot state
20
+ """
21
+ super(MatrixDataset, self).__init__(*args, **kwargs)
22
+ ref = importlib.resources.files(
23
+ "dcscope.gui.matrix") / "dm_dataset.ui"
24
+ with importlib.resources.as_file(ref) as path_ui:
25
+ uic.loadUi(path_ui, self)
26
+
27
+ # options button
28
+ menu = QtWidgets.QMenu()
29
+ menu.addAction('insert anew', self.action_insert_anew)
30
+ menu.addAction('duplicate', self.action_duplicate)
31
+ menu.addAction('remove', self.action_remove)
32
+ self.toolButton_opt.setMenu(menu)
33
+
34
+ # toggle all active, all inactive, semi state
35
+ self.toolButton_toggle.clicked.connect(self.active_toggled.emit)
36
+
37
+ # toggle enabled/disabled state
38
+ self.checkBox.clicked.connect(self.enabled_toggled.emit)
39
+
40
+ # modify slot button
41
+ self.toolButton_modify.clicked.connect(self.on_modify)
42
+
43
+ if state is None:
44
+ slot = pipeline.Dataslot._instances[identifier]
45
+ self.identifier = identifier
46
+ self.path = slot.path
47
+ # set tooltip/label
48
+ self.update_content()
49
+ else:
50
+ self.write_pipeline_state(state)
51
+
52
+ def read_pipeline_state(self):
53
+ state = {"path": self.path,
54
+ "identifier": self.identifier,
55
+ "enabled": self.checkBox.isChecked(),
56
+ }
57
+ return state
58
+
59
+ def write_pipeline_state(self, state):
60
+ self.identifier = state["identifier"]
61
+ self.path = state["path"]
62
+ self.checkBox.setChecked(state["enabled"])
63
+ self.update_content()
64
+
65
+ def action_duplicate(self):
66
+ self.option_action.emit("duplicate")
67
+
68
+ def action_insert_anew(self):
69
+ self.option_action.emit("insert_anew")
70
+
71
+ def action_remove(self):
72
+ self.option_action.emit("remove")
73
+
74
+ def on_modify(self):
75
+ self.modify_clicked.emit(self.identifier)
76
+
77
+ def set_label_string(self, string):
78
+ if self.label.fontMetrics().boundingRect(string).width() < 65:
79
+ nstring = string
80
+ else:
81
+ nstring = string + "..."
82
+ while True:
83
+ width = self.label.fontMetrics().boundingRect(nstring).width()
84
+ if width > 65:
85
+ nstring = nstring[:-4] + "..."
86
+ else:
87
+ break
88
+ self.label.setText(nstring)
89
+
90
+ def update_content(self):
91
+ """Reset tool tips and title"""
92
+ if self.path is not None:
93
+ tip = meta_tool.get_repr(self.path, append_path=True)
94
+ self.setToolTip(tip)
95
+ self.label.setToolTip(tip)
96
+ slot = pipeline.Dataslot._instances[self.identifier]
97
+ name = slot.name
98
+ self.set_label_string(name)
99
+ # Set region image
100
+ region = meta_tool.get_info(self.path,
101
+ section="setup",
102
+ key="chip region")
103
+ icon = QtGui.QIcon.fromTheme("region_{}".format(region))
104
+ pixmap = icon.pixmap(16)
105
+ self.label_region.setPixmap(pixmap)
106
+ self.label_region.setToolTip(region)
107
+ if region == "channel":
108
+ # Set flow rate
109
+ flow_rate = meta_tool.get_info(self.path,
110
+ section="setup",
111
+ key="flow rate")
112
+ self.label_flowrate.setText("{:.4g}".format(flow_rate))
113
+ self.label_flowrate.setToolTip("{:.4g} µL/s".format(flow_rate))
114
+ else:
115
+ self.label_flowrate.setText(region[:3])
116
+ self.label_flowrate.setToolTip(region)