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,797 @@
1
+ import copy
2
+ import html
3
+ import importlib.resources
4
+
5
+ import dclab
6
+ import numpy as np
7
+ from PyQt6 import uic, QtCore, QtGui, QtWidgets
8
+ import pyqtgraph as pg
9
+ from pyqtgraph import exporters
10
+ from pyqtgraph.graphicsItems.GradientEditorItem import Gradients
11
+
12
+
13
+ from .. import plot_cache
14
+ from .. import util
15
+ from .widgets import DCscopeColorBarItem
16
+
17
+ from .widgets import SimplePlotItem
18
+
19
+
20
+ # Register custom colormaps
21
+ Gradients["grayblue"] = {'ticks': [(0.0, (100, 100, 100, 255)),
22
+ (1.0, (0, 0, 255, 255))],
23
+ 'mode': 'rgb'}
24
+
25
+ Gradients["graygreen"] = {'ticks': [(0.0, (100, 100, 100, 255)),
26
+ (1.0, (0, 180, 0, 255))],
27
+ 'mode': 'rgb'}
28
+
29
+ Gradients["grayorange"] = {'ticks': [(0.0, (100, 100, 100, 255)),
30
+ (1.0, (210, 110, 0, 255))],
31
+ 'mode': 'rgb'}
32
+
33
+ Gradients["grayred"] = {'ticks': [(0.0, (100, 100, 100, 255)),
34
+ (1.0, (200, 0, 0, 255))],
35
+ 'mode': 'rgb'}
36
+
37
+
38
+ class ContourSpacingTooLarge(UserWarning):
39
+ pass
40
+
41
+
42
+ class PipelinePlot(QtWidgets.QWidget):
43
+ """Implements the plotting pipeline using pyqtgraph"""
44
+ instances = {}
45
+
46
+ def __init__(self, parent, pipeline, plot_id, *args, **kwargs):
47
+ super(PipelinePlot, self).__init__(parent=parent, *args, **kwargs)
48
+ ref = importlib.resources.files("dcscope.gui") / "pipeline_plot.ui"
49
+ with importlib.resources.as_file(ref) as path_ui:
50
+ uic.loadUi(path_ui, self)
51
+
52
+ # used to avoid unnecessary plotting
53
+ self._plot_data_hash = "unset"
54
+
55
+ self._window_decoration_size = (None, None)
56
+
57
+ #: Contains the PipelinePlotItems
58
+ self.plot_items = []
59
+ self.pipeline = pipeline
60
+ self.identifier = plot_id
61
+ self.update_content()
62
+ PipelinePlot.instances[plot_id] = self
63
+
64
+ def update_content(self):
65
+ """Update the current plot"""
66
+ parent = self.parent()
67
+ dslist, slot_states = self.pipeline.get_plot_datasets(self.identifier)
68
+ plot = self.pipeline.get_plot(self.identifier)
69
+ plot_state = plot.__getstate__()
70
+ # check whether anything changed
71
+ # 1. plot state and all relevant slot states
72
+ tohash = [slot_states, plot_state]
73
+ # 2. all relevant filter states
74
+ for slot_state in slot_states:
75
+ slot_id = slot_state["identifier"]
76
+ for filt_id in self.pipeline.filter_ids:
77
+ if self.pipeline.is_element_active(slot_id, filt_id):
78
+ filt = self.pipeline.get_filter(filt_id)
79
+ filt_state = filt.__getstate__()
80
+ tohash.append([slot_id, filt_id, filt_state])
81
+ # also check whether the polygon filters changed (#26)
82
+ for pid in filt_state["polygon filters"]:
83
+ pf = dclab.PolygonFilter.get_instance_from_id(pid)
84
+ tohash.append(pf.__getstate__())
85
+ plot_data_hash = util.hashobj(tohash)
86
+ if plot_data_hash == self._plot_data_hash:
87
+ # do nothing
88
+ pass
89
+ else:
90
+ self._plot_data_hash = plot_data_hash
91
+ self.update_content_plot(plot_state, slot_states, dslist)
92
+
93
+ # Set size in the end (after layout is populated)
94
+ lay = plot_state["layout"]
95
+ wsize_x = lay["size x"] + (self._window_decoration_size[0] or 8)
96
+ wsize_y = lay["size y"] + (self._window_decoration_size[1] or 28)
97
+
98
+ parent.resize(QtCore.QSize(wsize_x, wsize_y))
99
+
100
+ if self._window_decoration_size[0] is None:
101
+ psize = self.parent().sizeHint()
102
+ csize = self.sizeHint()
103
+ if (psize.width() == wsize_x
104
+ and psize.height() == wsize_y
105
+ and psize.width() > csize.width()
106
+ and psize.height() > csize.height()):
107
+ # We successfully set the size of the parent window. This
108
+ # means that we can now compute the window decoration size.
109
+ self._window_decoration_size = (
110
+ psize.width() - csize.width(),
111
+ psize.height() - csize.height())
112
+ self.plot_layout.updateGeometry()
113
+ self.update()
114
+
115
+ def update_content_plot(self, plot_state, slot_states, dslist):
116
+ # abbreviations
117
+ gen = plot_state["general"]
118
+ lay = plot_state["layout"]
119
+ sca = plot_state["scatter"]
120
+
121
+ # create a hash set for the dcnum hashes
122
+ hash_set = set()
123
+ for ds in dslist:
124
+ pipe_config = ds.config.get("pipeline", {})
125
+ dcnum_hash = pipe_config.get("dcnum hash", None)
126
+ if dcnum_hash is not None:
127
+ hash_set.add(dcnum_hash)
128
+ else:
129
+ hash_set.add(None)
130
+
131
+ # auto range (overrides stored ranges)
132
+ if gen["auto range"]:
133
+ # default range is limits + 5% margin
134
+ gen["range x"] = self.pipeline.get_min_max(feat=gen["axis x"],
135
+ plot_id=self.identifier,
136
+ margin=.05)
137
+ gen["range y"] = self.pipeline.get_min_max(feat=gen["axis y"],
138
+ plot_id=self.identifier,
139
+ margin=0.05)
140
+
141
+ # title
142
+ self.setWindowTitle(lay["name"])
143
+
144
+ # clear widget
145
+ self.plot_layout.clear()
146
+
147
+ # set background to white
148
+ self.plot_layout.setBackground("w")
149
+
150
+ if not slot_states:
151
+ return
152
+
153
+ labelx, labely = get_axes_labels(plot_state, slot_states)
154
+
155
+ # font size for plot title (default size + 2)
156
+ size = "{}pt".format(QtGui.QFont().pointSize() + 2)
157
+ self.plot_layout.addLabel(html.escape(lay["name"]),
158
+ colspan=3,
159
+ size=size)
160
+ self.plot_layout.nextRow()
161
+
162
+ self.plot_layout.addLabel(labely, angle=-90)
163
+ linner = self.plot_layout.addLayout()
164
+ linner.setContentsMargins(0, 0, 0, 0) # reallocate some space
165
+
166
+ self.plot_items.clear()
167
+
168
+ # limits in case of scatter plot and feature hue
169
+ if lay["division"] == "merge":
170
+ pp = PipelinePlotItem(parent=linner)
171
+ self.plot_items.append(pp)
172
+ linner.addItem(item=pp,
173
+ row=None,
174
+ col=None,
175
+ rowspan=1,
176
+ colspan=1)
177
+ pp.redraw(dslist, slot_states, plot_state)
178
+ elif lay["division"] == "each":
179
+ colcount = 0
180
+ for ds, sl in zip(dslist, slot_states):
181
+ # get the hash flag
182
+ hash_flag = get_hash_flag(hash_set, ds)
183
+
184
+ pp = PipelinePlotItem(parent=linner)
185
+ self.plot_items.append(pp)
186
+ linner.addItem(item=pp,
187
+ row=None,
188
+ col=None,
189
+ rowspan=1,
190
+ colspan=1)
191
+ pp.redraw([ds], [sl], plot_state, hash_flag)
192
+ colcount += 1
193
+ if colcount % lay["column count"] == 0:
194
+ linner.nextRow()
195
+ elif lay["division"] == "multiscatter+contour":
196
+ colcount = 0
197
+ # scatter plots
198
+ plot_state_scatter = copy.deepcopy(plot_state)
199
+ plot_state_scatter["contour"]["enabled"] = False
200
+ for ds, sl in zip(dslist, slot_states):
201
+ # get the hash flag
202
+ hash_flag = get_hash_flag(hash_set, ds)
203
+
204
+ pp = PipelinePlotItem(parent=linner)
205
+ self.plot_items.append(pp)
206
+ linner.addItem(item=pp,
207
+ row=None,
208
+ col=None,
209
+ rowspan=1,
210
+ colspan=1)
211
+ pp.redraw([ds], [sl], plot_state_scatter, hash_flag)
212
+ colcount += 1
213
+ if colcount % lay["column count"] == 0:
214
+ linner.nextRow()
215
+ # contour plot
216
+ plot_state_contour = copy.deepcopy(plot_state)
217
+ plot_state_contour["scatter"]["enabled"] = False
218
+ pp = PipelinePlotItem(parent=linner)
219
+ self.plot_items.append(pp)
220
+ linner.addItem(item=pp,
221
+ row=None,
222
+ col=None,
223
+ rowspan=1,
224
+ colspan=1)
225
+ pp.redraw(dslist, slot_states, plot_state_contour)
226
+
227
+ # colorbar
228
+ colorbar_kwds = {}
229
+
230
+ if sca["marker hue"] == "kde":
231
+ colorbar_kwds["values"] = (0, 1)
232
+ colorbar_kwds["label"] = "density [a.u.]"
233
+ elif sca["marker hue"] == "feature":
234
+ feat = sca["hue feature"]
235
+ label = dclab.dfn.get_feature_label(feat)
236
+ fl_names = slot_states[0]["fl names"]
237
+ if label.count("FL"):
238
+ for key in fl_names:
239
+ if key in label:
240
+ label = label.replace(key, fl_names[key])
241
+ break
242
+ colorbar_kwds["label"] = label
243
+ if label.endswith("[a.u.]"):
244
+ colorbar_kwds["values"] = (0, 1)
245
+ else:
246
+ colorbar_kwds["values"] = (sca["hue min"], sca["hue max"])
247
+
248
+ if colorbar_kwds:
249
+ # add colorbar
250
+ cmap = pg.ColorMap(*zip(*Gradients[sca["colormap"]]["ticks"]))
251
+ colorbar = DCscopeColorBarItem(
252
+ yoffset=31, # this is heuristic
253
+ height=min(300, lay["size y"] // 2),
254
+ colorMap=cmap,
255
+ interactive=False,
256
+ width=15,
257
+ **colorbar_kwds
258
+ )
259
+ self.plot_layout.addItem(colorbar)
260
+
261
+ # x-axis label
262
+ self.plot_layout.nextRow()
263
+ self.plot_layout.addLabel(labelx, col=1)
264
+
265
+
266
+ class PipelinePlotItem(SimplePlotItem):
267
+ def __init__(self, *args, **kwargs):
268
+ super(PipelinePlotItem, self).__init__(*args, **kwargs)
269
+ # circumvent problems with removed plots
270
+ self.setAcceptHoverEvents(False)
271
+ # Disable user interaction
272
+ self.setMouseEnabled(x=False, y=False)
273
+ # bring axes to front
274
+ self.axes_to_front()
275
+ # Keep track of all elements (for redraw)
276
+ self._plot_elements = []
277
+ # Set background to white (for plot export)
278
+ self.vb.setBackgroundColor("w")
279
+
280
+ def perform_export(self, file):
281
+ """Performs export in new layout with axes labels set
282
+
283
+ Overrides the basic functionality of SimplePlotItem.
284
+ See https://github.com/DC-analysis/DCscope/issues/7
285
+ """
286
+ # Create a plot window
287
+ win = pg.GraphicsLayoutWidget(
288
+ size=(int(self.width() + 100), int(self.height() + 100)),
289
+ show=True)
290
+ # fill layout
291
+ labelx, labely = get_axes_labels(self.plot_state, self.slot_states)
292
+ win.addLabel(labely, angle=-90)
293
+ explot = PipelinePlotItem()
294
+ explot.redraw(self.dslist, self.slot_states, self.plot_state)
295
+ win.addItem(explot)
296
+ win.addLabel("") # spacer to avoid cut tick labels on the right(#7)
297
+ win.nextRow()
298
+ win.addLabel(labelx, col=1)
299
+ # Update the UI (do it twice, otherwise the tick labels overlap)
300
+ QtWidgets.QApplication.processEvents(
301
+ QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
302
+ win.hide()
303
+ # perform actual export
304
+ suffix = file[-3:]
305
+ if suffix == "png":
306
+ exp = exporters.ImageExporter(win.scene())
307
+ # translate from screen resolution (80dpi) to 300dpi
308
+ exp.params["width"] = int(exp.params["width"] / 72 * 300)
309
+ elif suffix == "svg":
310
+ exp = exporters.SVGExporter(win.scene())
311
+ exp.export(file)
312
+
313
+ def redraw(self, dslist, slot_states, plot_state, hash_flag=None):
314
+ # Remove everything
315
+ for el in self._plot_elements:
316
+ self.removeItem(el)
317
+
318
+ if not dslist:
319
+ return
320
+
321
+ self.dslist = dslist
322
+ self.slot_states = slot_states
323
+ self.plot_state = plot_state
324
+
325
+ # General
326
+ gen = plot_state["general"]
327
+ # TODO:
328
+ # - test whether all datasets have same channel width / pixel size
329
+ # Isoelastics
330
+ if gen["isoelastics"]:
331
+ cfg = dslist[0].config
332
+ els = add_isoelastics(plot_item=self,
333
+ axis_x=gen["axis x"],
334
+ axis_y=gen["axis y"],
335
+ channel_width=cfg["setup"]["channel width"],
336
+ pixel_size=cfg["imaging"]["pixel size"],
337
+ lut_identifier=gen.get("lut", None))
338
+ self._plot_elements += els
339
+ # Modifications in log mode
340
+ set_viewbox(self,
341
+ range_x=gen["range x"],
342
+ range_y=gen["range y"],
343
+ scale_x=gen["scale x"],
344
+ scale_y=gen["scale y"])
345
+ # Scatter data
346
+ sca = plot_state["scatter"]
347
+ if sca["enabled"]:
348
+ for rtdc_ds, ss in zip(dslist, slot_states):
349
+ sct = add_scatter(plot_item=self,
350
+ rtdc_ds=rtdc_ds,
351
+ plot_state=plot_state,
352
+ slot_state=ss,
353
+ hash_flag=hash_flag
354
+ )
355
+ self._plot_elements += sct
356
+ # Contour data
357
+ if plot_state["contour"]["enabled"]:
358
+ # show legend
359
+ if plot_state["contour"]["legend"]:
360
+ legend = self.addLegend(offset=(-.01, +.01))
361
+ else:
362
+ legend = None
363
+ for rtdc_ds, ss in zip(dslist, slot_states):
364
+ con = add_contour(plot_item=self,
365
+ rtdc_ds=rtdc_ds,
366
+ plot_state=plot_state,
367
+ slot_state=ss,
368
+ legend=legend,
369
+ )
370
+ self._plot_elements += con
371
+
372
+ # Set subplot title and number of events
373
+ if plot_state["layout"]["label plots"]:
374
+ if len(dslist) == 1 and plot_state["scatter"]["enabled"]:
375
+ # only one scatter plot
376
+ ss = slot_states[0]
377
+ self.setTitle("") # fake title
378
+ add_label(text=html.escape(ss["name"]),
379
+ anchor_parent=self.titleLabel.item,
380
+ color=ss["color"],
381
+ text_halign="center",
382
+ text_valign="top",
383
+ dx=4
384
+ )
385
+
386
+ if plot_state["scatter"]["show event count"]:
387
+ if True:
388
+ add_label(text=f"{len(sct[0].data)} events",
389
+ anchor_parent=self.axes["right"]["item"],
390
+ font_size_diff=-1,
391
+ color="black",
392
+ text_halign="right",
393
+ text_valign="top",
394
+ dx=2,
395
+ dy=-5,
396
+ )
397
+
398
+ elif (plot_state["contour"]["enabled"]
399
+ and not plot_state["scatter"]["enabled"]):
400
+ # only a contour plot
401
+ self.setTitle("") # fake title
402
+ add_label(text="Contours",
403
+ color="black",
404
+ anchor_parent=self.titleLabel.item,
405
+ text_halign="center",
406
+ text_valign="top",
407
+ dx=4,
408
+ )
409
+
410
+
411
+ def add_label(text, anchor_parent, text_halign="center", text_valign="center",
412
+ font_size_diff=0, color=None, dx=0, dy=0):
413
+ """Add a graphics label anchored to another item
414
+
415
+ This is a hackish workaround that was made more elaborate
416
+ due to https://github.com/DC-analysis/DCscope/issues/33.
417
+
418
+ Parameters
419
+ ----------
420
+ text: str
421
+ Label text (no HTML!)
422
+ anchor_parent: QGraphicsItem
423
+ Anything in the plot (e.g. axis items or other labels) that can
424
+ be anchored to. This object will be the parent of the label.
425
+ text_halign: str
426
+ Horizontal text alignment relative to anchor point
427
+ ("left", "center", "right")
428
+ text_valign: str
429
+ Vertical text alignment relative to anchor point
430
+ ("left", "center", "right")
431
+ font_size_diff: int
432
+ Change font size of text relative to `QtGui.QFont().pointSize()`
433
+ (is added via css)
434
+ color: str
435
+ Color of the text (is added via css)
436
+ dx: float
437
+ Manual horizontal positioning
438
+ dy: float
439
+ Manual vertical positioning
440
+ """
441
+ assert text_halign in ["left", "center", "right"]
442
+ assert text_valign in ["top", "center", "bottom"]
443
+ font_size = QtGui.QFont().pointSize() + font_size_diff
444
+ css = "font-size:{}pt;".format(font_size)
445
+ if color is not None:
446
+ css += "color:{};".format(color)
447
+ html = "<span style='{}'>{}</span>".format(css, text)
448
+ label = QtWidgets.QGraphicsTextItem(
449
+ "",
450
+ # This is kind of hackish: set the parent to the right
451
+ # axis so that it is always drawn there.
452
+ parent=anchor_parent)
453
+ label.setHtml(html)
454
+
455
+ # move label
456
+ width = label.boundingRect().width()
457
+ height = label.boundingRect().height()
458
+ if text_halign == "center":
459
+ x = -width / 2
460
+ elif text_halign == "left":
461
+ x = 0
462
+ else: # "right"
463
+ x = -width
464
+
465
+ if text_valign == "center":
466
+ y = -height / 2
467
+ elif text_valign == "top":
468
+ y = 0
469
+ else: # "bottom"
470
+ y = -height/2
471
+ label.setPos(x + dx, y + dy)
472
+
473
+
474
+ def add_contour(plot_item, plot_state, rtdc_ds, slot_state, legend=None):
475
+ contours = compute_contours(plot_state=plot_state, rtdc_ds=rtdc_ds)
476
+ con = plot_state["contour"]
477
+ elements = []
478
+ num_unreliable_contours = 0
479
+ for ii in range(len(contours)):
480
+ style = linestyles[con["line styles"][ii]]
481
+ width = con["line widths"][ii]
482
+ for cci in contours[ii]:
483
+ if not compute_contour_reliable(plot_state=plot_state,
484
+ contour=cci):
485
+ num_unreliable_contours += 1
486
+ cline = pg.PlotDataItem(x=cci[:, 0],
487
+ y=cci[:, 1],
488
+ pen=pg.mkPen(color=slot_state["color"],
489
+ width=width,
490
+ style=style,
491
+ ),
492
+ )
493
+ elements.append(cline)
494
+ plot_item.addItem(cline)
495
+ if ii == 0 and legend is not None:
496
+ legend.addItem(cline, slot_state["name"])
497
+ # Always plot higher percentiles above lower percentiles
498
+ # (useful if there are multiple contour plots overlapping)
499
+ cline.setZValue(con["percentiles"][ii])
500
+ if num_unreliable_contours or not elements:
501
+ # Tell the user to refine contour spacing.
502
+ add_label("Please reduce contour spacing.",
503
+ anchor_parent=plot_item.axes["bottom"]["item"],
504
+ font_size_diff=-1,
505
+ color="red",
506
+ text_halign="left",
507
+ text_valign="bottom",
508
+ dy=-12,
509
+ )
510
+ return elements
511
+
512
+
513
+ def add_isoelastics(plot_item, axis_x, axis_y, channel_width, pixel_size,
514
+ lut_identifier=None):
515
+ elements = []
516
+ isodef = dclab.isoelastics.get_default()
517
+ # We do not use isodef.get_with_rtdcbase, because then the
518
+ # isoelastics would be shifted according to flow rate and.
519
+ # viscosity. We could do it, but for visualization there is
520
+ # really no need and also, the plots then look the same as
521
+ # in DCscope 1.
522
+ try:
523
+ iso = isodef.get(
524
+ lut_identifier=lut_identifier if lut_identifier
525
+ else "LE-2D-FEM-19",
526
+ channel_width=channel_width,
527
+ flow_rate=None,
528
+ viscosity=None,
529
+ col1=axis_x,
530
+ col2=axis_y,
531
+ add_px_err=True,
532
+ px_um=pixel_size)
533
+ except KeyError:
534
+ pass
535
+ else:
536
+ for ss in iso:
537
+ iline = pg.PlotDataItem(x=ss[:, 0], y=ss[:, 1])
538
+ plot_item.addItem(iline)
539
+ elements.append(iline)
540
+ # send them to the back
541
+ iline.setZValue(-100)
542
+ return elements
543
+
544
+
545
+ def add_scatter(plot_item, plot_state, rtdc_ds, slot_state, hash_flag):
546
+ gen = plot_state["general"]
547
+ sca = plot_state["scatter"]
548
+ scatter = pg.ScatterPlotItem(size=sca["marker size"],
549
+ pen=pg.mkPen(color=(0, 0, 0, 0)),
550
+ brush=pg.mkBrush("k"),
551
+ symbol="s")
552
+ scatter.setAcceptHoverEvents(False)
553
+ plot_item.addItem(scatter)
554
+
555
+ if sca["marker hue"] == "kde":
556
+ kde_type = gen["kde"]
557
+ else:
558
+ kde_type = "none"
559
+
560
+ x, y, kde, idx = plot_cache.get_scatter_data(
561
+ rtdc_ds=rtdc_ds,
562
+ downsample=sca["downsample"] * sca["downsampling value"],
563
+ xax=gen["axis x"],
564
+ yax=gen["axis y"],
565
+ xscale=gen["scale x"],
566
+ yscale=gen["scale y"],
567
+ kde_type=kde_type,
568
+ )
569
+ # define colormap
570
+ # TODO:
571
+ # - common code base with QuickView
572
+ cmap = pg.ColorMap(*zip(*Gradients[sca["colormap"]]["ticks"]))
573
+ if sca["marker hue"] == "kde":
574
+ brush = []
575
+ # Note: we don't expand the density to [0, 1], because the
576
+ # colorbar will show "density" and because we don want to
577
+ # compute the density in this function and not someplace else.
578
+ for k in kde:
579
+ brush.append(cmap.mapToQColor(k))
580
+ # Note, colors could also be digitized (does not seem to be faster):
581
+ # cbin = np.linspace(0, 1, 1000)
582
+ # dig = np.digitize(kde, cbin)
583
+ # for idx in dig:
584
+ # brush.append(cmap.mapToQColor(cbin[idx]))
585
+ elif sca["marker hue"] == "feature":
586
+ brush = []
587
+ feat = np.asarray(rtdc_ds[sca["hue feature"]][idx], dtype=float)
588
+ feat -= sca["hue min"]
589
+ feat /= sca["hue max"] - sca["hue min"]
590
+ for f in feat:
591
+ if np.isnan(f):
592
+ brush.append(pg.mkColor("#FF0000"))
593
+ else:
594
+ brush.append(cmap.mapToQColor(f))
595
+ elif sca["marker hue"] == "dataset":
596
+ alpha = int(sca["marker alpha"] * 255)
597
+ colord = pg.mkColor(slot_state["color"])
598
+ colord.setAlpha(alpha)
599
+ brush = pg.mkBrush(colord)
600
+ else:
601
+ alpha = int(sca["marker alpha"] * 255)
602
+ colork = pg.mkColor("#000000")
603
+ colork.setAlpha(alpha)
604
+ brush = pg.mkBrush(colork)
605
+
606
+ # convert to log-scale if applicable
607
+ if gen["scale x"] == "log":
608
+ x = np.log10(x)
609
+ if gen["scale y"] == "log":
610
+ y = np.log10(y)
611
+
612
+ # add dcnum hash label
613
+ if hash_flag:
614
+ add_label(
615
+ hash_flag,
616
+ anchor_parent=plot_item.axes["top"]["item"],
617
+ font_size_diff=-1,
618
+ color="red",
619
+ text_halign="left",
620
+ text_valign="top",
621
+ )
622
+
623
+ scatter.setData(x=x, y=y, brush=brush)
624
+ scatter.setZValue(-50)
625
+ return [scatter]
626
+
627
+
628
+ def compute_contours(plot_state, rtdc_ds):
629
+ gen = plot_state["general"]
630
+ con = plot_state["contour"]
631
+ contours = plot_cache.get_contour_data(
632
+ rtdc_ds=rtdc_ds,
633
+ xax=gen["axis x"],
634
+ yax=gen["axis y"],
635
+ xacc=con["spacing x"],
636
+ yacc=con["spacing y"],
637
+ xscale=gen["scale x"],
638
+ yscale=gen["scale y"],
639
+ kde_type=gen["kde"],
640
+ quantiles=[p/100 for p in con["percentiles"]]
641
+ )
642
+ return contours
643
+
644
+
645
+ def compute_contour_opening_angles(plot_state, contour):
646
+ """For each point of the contour, compute the opening angle
647
+
648
+ This takes the visible plot area into account.
649
+ """
650
+ cc = np.array(contour, copy=True)
651
+ if not np.all(cc[0] == cc[-1]):
652
+ cc = np.resize(cc, (len(contour)+1, 2))
653
+ # Normalize contour
654
+ rx = plot_state["general"]["range x"]
655
+ ry = plot_state["general"]["range y"]
656
+ cc[:, 0] = (cc[:, 0] - rx[0]) / (rx[1] - rx[0])
657
+ cc[:, 1] = (cc[:, 1] - ry[0]) / (ry[1] - ry[0])
658
+ # apply scale
659
+ sx = plot_state["general"]["scale x"]
660
+ assert sx in ["log", "linear"]
661
+ if sx == "log":
662
+ cc[:, 0] = np.log10(cc[:, 0])
663
+ sy = plot_state["general"]["scale y"]
664
+ assert sy in ["log", "linear"]
665
+ if sy == "log":
666
+ cc[:, 1] = np.log10(cc[:, 1])
667
+ opang = np.zeros(len(cc)-1, dtype=float)
668
+ for jj, c0 in enumerate(cc[:-1]): # we have a closed contour
669
+ cl = cc[:-1][jj - 1]
670
+ cr = cc[jj + 1]
671
+ # vector a
672
+ a = np.array(cl) - np.array(c0)
673
+ # vector b
674
+ b = np.array(cr) - np.array(c0)
675
+ absa = np.sqrt(np.sum(a ** 2))
676
+ absb = np.sqrt(np.sum(b ** 2))
677
+ denom = absa * absb
678
+ # avoid division by zero warnings
679
+ if isinstance(denom, np.ndarray):
680
+ denom[denom == 0] = np.nan
681
+ elif denom == 0:
682
+ denom = np.nan
683
+ phi = np.arccos(np.sum(a * b) / denom)
684
+ if np.abs(phi) > np.pi/2:
685
+ phi -= np.sign(phi) * np.pi
686
+ opang[jj] = phi
687
+ return opang
688
+
689
+
690
+ def compute_contour_reliable(plot_state, contour, thresh_ang=np.deg2rad(23)):
691
+ """Determine whether contour is reliable or not"""
692
+ # Compute the opening angle for each point of the
693
+ # contour and take the point with the largest opening angle.
694
+ angles = compute_contour_opening_angles(
695
+ plot_state=plot_state, contour=contour)
696
+ if (np.allclose(np.abs(angles[0]), np.pi / 2)
697
+ and np.all(angles[1:6] == 0)):
698
+ # We have probably encountered a contour at the boundary
699
+ # of the image. It looks like this is ok.
700
+ reliable = True
701
+ elif len(angles) > 50:
702
+ # The contour is long enough to be trusted.
703
+ reliable = True
704
+ else:
705
+ reliable = np.max(np.abs(angles)) <= thresh_ang
706
+ return reliable
707
+
708
+
709
+ def get_axes_labels(plot_state, slot_states):
710
+ gen = plot_state["general"]
711
+ # Use slot_states[0] because we only have one x-axis label
712
+ labelx = get_axis_label_from_feature(gen["axis x"], slot_states[0])
713
+ labely = get_axis_label_from_feature(gen["axis y"], slot_states[0])
714
+ return labelx, labely
715
+
716
+
717
+ def get_axis_label_from_feature(feat, slot_state=None):
718
+ """Return the axis label for plotting given a feature name
719
+
720
+ - replace the fluorescence names with user-defined strings
721
+ from `slot_state["fl names"]` if `slot_state` is given
722
+ - html-escape all characters
723
+ """
724
+ label = dclab.dfn.get_feature_label(feat)
725
+ # replace FL-? with user-defined names
726
+ if slot_state is not None and "fl names" in slot_state:
727
+ fl_names = slot_state["fl names"]
728
+ if label.count("FL") and feat.startswith("fl"):
729
+ for key in fl_names:
730
+ if key in label:
731
+ label = label.replace(key, fl_names[key])
732
+ break
733
+ return html.escape(label)
734
+
735
+
736
+ def set_viewbox(plot, range_x, range_y, scale_x="linear", scale_y="linear",
737
+ padding=0):
738
+ # Set Log scale
739
+ plot.setLogMode(x=scale_x == "log",
740
+ y=scale_y == "log")
741
+ range_x = np.array(range_x)
742
+ range_y = np.array(range_y)
743
+ if scale_x == "log":
744
+ if range_x[0] <= 0:
745
+ if range_x[1] > 10:
746
+ range_x[0] = 1e-1
747
+ else:
748
+ range_x[0] = 1e-3
749
+ range_x = np.log10(range_x)
750
+ if scale_y == "log":
751
+ if range_y[0] <= 0:
752
+ if range_y[1] > 10:
753
+ range_y[0] = 1e-1
754
+ else:
755
+ range_y[0] = 1e-3
756
+ range_y = np.log10(range_y)
757
+ # Set Range
758
+ plot.setRange(xRange=range_x,
759
+ yRange=range_y,
760
+ padding=padding,
761
+ )
762
+
763
+
764
+ def get_hash_flag(hash_set, rtdc_ds):
765
+ """Helper function to determine the hash flag based on the dataset and
766
+ hash set."""
767
+ if len(hash_set) == 1:
768
+ # only one hash, no need to show it
769
+ return None
770
+
771
+ req_hash_len = 4
772
+ # get the longest hash from the hash set
773
+ longest_hash = max((h for h in hash_set if h), key=len, default="temphash")
774
+
775
+ # find the minimum and unique hash length dynamically
776
+ for char_len in range(req_hash_len, len(longest_hash)):
777
+ temp_short_hash_set = set(
778
+ h[:char_len] if h is not None else None for h in hash_set
779
+ )
780
+ if len(temp_short_hash_set) != len(hash_set):
781
+ req_hash_len += 1
782
+ else:
783
+ break
784
+
785
+ # get the pipeline hash
786
+ pipe_config = rtdc_ds.config.get("pipeline", {})
787
+ dcnum_hash = pipe_config.get("dcnum hash", None)
788
+ # use the first `req_hash_len` characters of the hash
789
+ short_hash = dcnum_hash[:req_hash_len] if dcnum_hash else None
790
+ return f"Pipeline {short_hash}" if short_hash else None
791
+
792
+
793
+ linestyles = {
794
+ "solid": QtCore.Qt.PenStyle.SolidLine,
795
+ "dashed": QtCore.Qt.PenStyle.DashLine,
796
+ "dotted": QtCore.Qt.PenStyle.DotLine,
797
+ }