datalab-platform 0.0.1.dev0__py3-none-any.whl → 1.0.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 (496) hide show
  1. datalab/__init__.py +35 -2
  2. datalab/adapters_metadata/__init__.py +31 -0
  3. datalab/adapters_metadata/base_adapter.py +316 -0
  4. datalab/adapters_metadata/common.py +422 -0
  5. datalab/adapters_metadata/geometry_adapter.py +98 -0
  6. datalab/adapters_metadata/table_adapter.py +84 -0
  7. datalab/adapters_plotpy/__init__.py +54 -0
  8. datalab/adapters_plotpy/annotations.py +124 -0
  9. datalab/adapters_plotpy/base.py +110 -0
  10. datalab/adapters_plotpy/converters.py +86 -0
  11. datalab/adapters_plotpy/factories.py +80 -0
  12. datalab/adapters_plotpy/objects/__init__.py +0 -0
  13. datalab/adapters_plotpy/objects/base.py +197 -0
  14. datalab/adapters_plotpy/objects/image.py +157 -0
  15. datalab/adapters_plotpy/objects/scalar.py +565 -0
  16. datalab/adapters_plotpy/objects/signal.py +264 -0
  17. datalab/adapters_plotpy/roi/__init__.py +0 -0
  18. datalab/adapters_plotpy/roi/base.py +146 -0
  19. datalab/adapters_plotpy/roi/factory.py +93 -0
  20. datalab/adapters_plotpy/roi/image.py +207 -0
  21. datalab/adapters_plotpy/roi/signal.py +72 -0
  22. datalab/app.py +98 -0
  23. datalab/config.py +817 -0
  24. datalab/control/__init__.py +0 -0
  25. datalab/control/baseproxy.py +776 -0
  26. datalab/control/proxy.py +343 -0
  27. datalab/control/remote.py +1005 -0
  28. datalab/data/doc/DataLab_en.pdf +0 -0
  29. datalab/data/doc/DataLab_fr.pdf +0 -0
  30. datalab/data/icons/analysis/delete_results.svg +109 -0
  31. datalab/data/icons/analysis/fw1e2.svg +156 -0
  32. datalab/data/icons/analysis/fwhm.svg +156 -0
  33. datalab/data/icons/analysis/histogram.svg +49 -0
  34. datalab/data/icons/analysis/peak_detect.svg +160 -0
  35. datalab/data/icons/analysis/plot_results.svg +151 -0
  36. datalab/data/icons/analysis/show_results.svg +83 -0
  37. datalab/data/icons/analysis/stats.svg +49 -0
  38. datalab/data/icons/analysis.svg +120 -0
  39. datalab/data/icons/apply.svg +3 -0
  40. datalab/data/icons/check_all.svg +15 -0
  41. datalab/data/icons/collapse.svg +44 -0
  42. datalab/data/icons/collapse_selection.svg +63 -0
  43. datalab/data/icons/console.svg +101 -0
  44. datalab/data/icons/create/1d-normal.svg +8 -0
  45. datalab/data/icons/create/1d-poisson.svg +9 -0
  46. datalab/data/icons/create/1d-uniform.svg +8 -0
  47. datalab/data/icons/create/1d-zero.svg +57 -0
  48. datalab/data/icons/create/2d-gaussian.svg +56 -0
  49. datalab/data/icons/create/2d-normal.svg +38 -0
  50. datalab/data/icons/create/2d-poisson.svg +38 -0
  51. datalab/data/icons/create/2d-ramp.svg +90 -0
  52. datalab/data/icons/create/2d-sinc.svg +62 -0
  53. datalab/data/icons/create/2d-uniform.svg +38 -0
  54. datalab/data/icons/create/2d-zero.svg +13 -0
  55. datalab/data/icons/create/checkerboard.svg +39 -0
  56. datalab/data/icons/create/cosine.svg +12 -0
  57. datalab/data/icons/create/exponential.svg +55 -0
  58. datalab/data/icons/create/gaussian.svg +12 -0
  59. datalab/data/icons/create/grating.svg +29 -0
  60. datalab/data/icons/create/linear_chirp.svg +7 -0
  61. datalab/data/icons/create/logistic.svg +7 -0
  62. datalab/data/icons/create/lorentzian.svg +12 -0
  63. datalab/data/icons/create/planck.svg +12 -0
  64. datalab/data/icons/create/polynomial.svg +7 -0
  65. datalab/data/icons/create/pulse.svg +12 -0
  66. datalab/data/icons/create/ring.svg +18 -0
  67. datalab/data/icons/create/sawtooth.svg +7 -0
  68. datalab/data/icons/create/siemens.svg +35 -0
  69. datalab/data/icons/create/sinc.svg +12 -0
  70. datalab/data/icons/create/sine.svg +7 -0
  71. datalab/data/icons/create/square.svg +7 -0
  72. datalab/data/icons/create/square_pulse.svg +7 -0
  73. datalab/data/icons/create/step.svg +7 -0
  74. datalab/data/icons/create/step_pulse.svg +12 -0
  75. datalab/data/icons/create/triangle.svg +7 -0
  76. datalab/data/icons/create/voigt.svg +12 -0
  77. datalab/data/icons/edit/annotations.svg +72 -0
  78. datalab/data/icons/edit/annotations_copy.svg +114 -0
  79. datalab/data/icons/edit/annotations_delete.svg +83 -0
  80. datalab/data/icons/edit/annotations_edit.svg +98 -0
  81. datalab/data/icons/edit/annotations_export.svg +85 -0
  82. datalab/data/icons/edit/annotations_import.svg +85 -0
  83. datalab/data/icons/edit/annotations_paste.svg +100 -0
  84. datalab/data/icons/edit/copy_titles.svg +109 -0
  85. datalab/data/icons/edit/delete.svg +84 -0
  86. datalab/data/icons/edit/delete_all.svg +214 -0
  87. datalab/data/icons/edit/duplicate.svg +64 -0
  88. datalab/data/icons/edit/goto_source.svg +60 -0
  89. datalab/data/icons/edit/metadata.svg +60 -0
  90. datalab/data/icons/edit/metadata_add.svg +80 -0
  91. datalab/data/icons/edit/metadata_copy.svg +96 -0
  92. datalab/data/icons/edit/metadata_delete.svg +62 -0
  93. datalab/data/icons/edit/metadata_export.svg +68 -0
  94. datalab/data/icons/edit/metadata_import.svg +68 -0
  95. datalab/data/icons/edit/metadata_paste.svg +79 -0
  96. datalab/data/icons/edit/move_down.svg +55 -0
  97. datalab/data/icons/edit/move_up.svg +54 -0
  98. datalab/data/icons/edit/new_group.svg +76 -0
  99. datalab/data/icons/edit/recompute.svg +60 -0
  100. datalab/data/icons/edit/rename.svg +49 -0
  101. datalab/data/icons/edit.svg +16 -0
  102. datalab/data/icons/expand.svg +44 -0
  103. datalab/data/icons/expand_selection.svg +63 -0
  104. datalab/data/icons/fit/cdf_fit.svg +56 -0
  105. datalab/data/icons/fit/exponential_fit.svg +55 -0
  106. datalab/data/icons/fit/gaussian_fit.svg +62 -0
  107. datalab/data/icons/fit/interactive_fit.svg +101 -0
  108. datalab/data/icons/fit/linear_fit.svg +57 -0
  109. datalab/data/icons/fit/lorentzian_fit.svg +209 -0
  110. datalab/data/icons/fit/multigaussian_fit.svg +85 -0
  111. datalab/data/icons/fit/multilorentzian_fit.svg +85 -0
  112. datalab/data/icons/fit/piecewiseexponential_fit.svg +209 -0
  113. datalab/data/icons/fit/planckian_fit.svg +62 -0
  114. datalab/data/icons/fit/polynomial_fit.svg +59 -0
  115. datalab/data/icons/fit/sigmoid_fit.svg +56 -0
  116. datalab/data/icons/fit/sinusoidal_fit.svg +72 -0
  117. datalab/data/icons/fit/twohalfgaussian_fit.svg +63 -0
  118. datalab/data/icons/fit/voigt_fit.svg +57 -0
  119. datalab/data/icons/group.svg +56 -0
  120. datalab/data/icons/h5/h5array.svg +59 -0
  121. datalab/data/icons/h5/h5attrs.svg +75 -0
  122. datalab/data/icons/h5/h5browser.svg +133 -0
  123. datalab/data/icons/h5/h5file.svg +69 -0
  124. datalab/data/icons/h5/h5group.svg +49 -0
  125. datalab/data/icons/h5/h5scalar.svg +1 -0
  126. datalab/data/icons/help_pdf.svg +46 -0
  127. datalab/data/icons/history.svg +7 -0
  128. datalab/data/icons/image.svg +135 -0
  129. datalab/data/icons/io/fileopen_directory.svg +60 -0
  130. datalab/data/icons/io/fileopen_h5.svg +84 -0
  131. datalab/data/icons/io/fileopen_ima.svg +187 -0
  132. datalab/data/icons/io/fileopen_py.svg +123 -0
  133. datalab/data/icons/io/fileopen_sig.svg +138 -0
  134. datalab/data/icons/io/filesave_h5.svg +97 -0
  135. datalab/data/icons/io/filesave_ima.svg +200 -0
  136. datalab/data/icons/io/filesave_py.svg +136 -0
  137. datalab/data/icons/io/filesave_sig.svg +151 -0
  138. datalab/data/icons/io/import_text.svg +144 -0
  139. datalab/data/icons/io/save_to_directory.svg +134 -0
  140. datalab/data/icons/io.svg +84 -0
  141. datalab/data/icons/libre-camera-flash-off.svg +1 -0
  142. datalab/data/icons/libre-camera-flash-on.svg +1 -0
  143. datalab/data/icons/libre-gui-about.svg +1 -0
  144. datalab/data/icons/libre-gui-action-delete.svg +1 -0
  145. datalab/data/icons/libre-gui-add.svg +1 -0
  146. datalab/data/icons/libre-gui-arrow-down.svg +1 -0
  147. datalab/data/icons/libre-gui-arrow-left.svg +1 -0
  148. datalab/data/icons/libre-gui-arrow-right.svg +1 -0
  149. datalab/data/icons/libre-gui-arrow-up.svg +1 -0
  150. datalab/data/icons/libre-gui-close.svg +40 -0
  151. datalab/data/icons/libre-gui-cogs.svg +1 -0
  152. datalab/data/icons/libre-gui-globe.svg +1 -0
  153. datalab/data/icons/libre-gui-help.svg +1 -0
  154. datalab/data/icons/libre-gui-link.svg +1 -0
  155. datalab/data/icons/libre-gui-menu.svg +1 -0
  156. datalab/data/icons/libre-gui-pencil.svg +1 -0
  157. datalab/data/icons/libre-gui-plugin.svg +1 -0
  158. datalab/data/icons/libre-gui-questions.svg +1 -0
  159. datalab/data/icons/libre-gui-settings.svg +1 -0
  160. datalab/data/icons/libre-gui-unlink.svg +1 -0
  161. datalab/data/icons/libre-tech-ram.svg +1 -0
  162. datalab/data/icons/libre-toolbox.svg +1 -0
  163. datalab/data/icons/logs.svg +1 -0
  164. datalab/data/icons/markers.svg +74 -0
  165. datalab/data/icons/menu.svg +13 -0
  166. datalab/data/icons/new_ima.svg +148 -0
  167. datalab/data/icons/new_sig.svg +123 -0
  168. datalab/data/icons/operations/abs.svg +116 -0
  169. datalab/data/icons/operations/arithmetic.svg +123 -0
  170. datalab/data/icons/operations/average.svg +124 -0
  171. datalab/data/icons/operations/complex_from_magnitude_phase.svg +116 -0
  172. datalab/data/icons/operations/complex_from_real_imag.svg +124 -0
  173. datalab/data/icons/operations/constant.svg +116 -0
  174. datalab/data/icons/operations/constant_add.svg +109 -0
  175. datalab/data/icons/operations/constant_divide.svg +109 -0
  176. datalab/data/icons/operations/constant_multiply.svg +109 -0
  177. datalab/data/icons/operations/constant_subtract.svg +109 -0
  178. datalab/data/icons/operations/convert_dtype.svg +117 -0
  179. datalab/data/icons/operations/convolution.svg +46 -0
  180. datalab/data/icons/operations/deconvolution.svg +57 -0
  181. datalab/data/icons/operations/derivative.svg +127 -0
  182. datalab/data/icons/operations/difference.svg +52 -0
  183. datalab/data/icons/operations/division.svg +139 -0
  184. datalab/data/icons/operations/exp.svg +116 -0
  185. datalab/data/icons/operations/flip_horizontally.svg +69 -0
  186. datalab/data/icons/operations/flip_vertically.svg +74 -0
  187. datalab/data/icons/operations/im.svg +124 -0
  188. datalab/data/icons/operations/integral.svg +50 -0
  189. datalab/data/icons/operations/inverse.svg +143 -0
  190. datalab/data/icons/operations/log10.svg +109 -0
  191. datalab/data/icons/operations/phase.svg +116 -0
  192. datalab/data/icons/operations/power.svg +118 -0
  193. datalab/data/icons/operations/product.svg +124 -0
  194. datalab/data/icons/operations/profile.svg +379 -0
  195. datalab/data/icons/operations/profile_average.svg +399 -0
  196. datalab/data/icons/operations/profile_radial.svg +261 -0
  197. datalab/data/icons/operations/profile_segment.svg +262 -0
  198. datalab/data/icons/operations/quadratic_difference.svg +84 -0
  199. datalab/data/icons/operations/re.svg +124 -0
  200. datalab/data/icons/operations/rotate_left.svg +72 -0
  201. datalab/data/icons/operations/rotate_right.svg +72 -0
  202. datalab/data/icons/operations/signals_to_image.svg +314 -0
  203. datalab/data/icons/operations/sqrt.svg +110 -0
  204. datalab/data/icons/operations/std.svg +124 -0
  205. datalab/data/icons/operations/sum.svg +102 -0
  206. datalab/data/icons/play_demo.svg +9 -0
  207. datalab/data/icons/processing/axis_transform.svg +62 -0
  208. datalab/data/icons/processing/bandpass.svg +79 -0
  209. datalab/data/icons/processing/bandstop.svg +71 -0
  210. datalab/data/icons/processing/binning.svg +126 -0
  211. datalab/data/icons/processing/clip.svg +119 -0
  212. datalab/data/icons/processing/detrending.svg +173 -0
  213. datalab/data/icons/processing/distribute_on_grid.svg +769 -0
  214. datalab/data/icons/processing/edge_detection.svg +46 -0
  215. datalab/data/icons/processing/erase.svg +1 -0
  216. datalab/data/icons/processing/exposure.svg +143 -0
  217. datalab/data/icons/processing/fourier.svg +104 -0
  218. datalab/data/icons/processing/highpass.svg +59 -0
  219. datalab/data/icons/processing/interpolation.svg +71 -0
  220. datalab/data/icons/processing/level_adjustment.svg +70 -0
  221. datalab/data/icons/processing/lowpass.svg +60 -0
  222. datalab/data/icons/processing/morphology.svg +49 -0
  223. datalab/data/icons/processing/noise_addition.svg +114 -0
  224. datalab/data/icons/processing/noise_reduction.svg +38 -0
  225. datalab/data/icons/processing/normalize.svg +84 -0
  226. datalab/data/icons/processing/offset_correction.svg +131 -0
  227. datalab/data/icons/processing/resampling1d.svg +101 -0
  228. datalab/data/icons/processing/resampling2d.svg +240 -0
  229. datalab/data/icons/processing/reset_positions.svg +185 -0
  230. datalab/data/icons/processing/resize.svg +9 -0
  231. datalab/data/icons/processing/reverse_signal_x.svg +171 -0
  232. datalab/data/icons/processing/stability.svg +11 -0
  233. datalab/data/icons/processing/swap_x_y.svg +65 -0
  234. datalab/data/icons/processing/thresholding.svg +63 -0
  235. datalab/data/icons/processing/windowing.svg +45 -0
  236. datalab/data/icons/properties.svg +26 -0
  237. datalab/data/icons/reset.svg +9 -0
  238. datalab/data/icons/restore.svg +40 -0
  239. datalab/data/icons/roi/roi.svg +76 -0
  240. datalab/data/icons/roi/roi_coordinate.svg +78 -0
  241. datalab/data/icons/roi/roi_copy.svg +112 -0
  242. datalab/data/icons/roi/roi_delete.svg +81 -0
  243. datalab/data/icons/roi/roi_export.svg +87 -0
  244. datalab/data/icons/roi/roi_graphical.svg +78 -0
  245. datalab/data/icons/roi/roi_grid.svg +67 -0
  246. datalab/data/icons/roi/roi_ima.svg +188 -0
  247. datalab/data/icons/roi/roi_import.svg +87 -0
  248. datalab/data/icons/roi/roi_new.svg +81 -0
  249. datalab/data/icons/roi/roi_new_circle.svg +95 -0
  250. datalab/data/icons/roi/roi_new_polygon.svg +110 -0
  251. datalab/data/icons/roi/roi_new_rectangle.svg +70 -0
  252. datalab/data/icons/roi/roi_paste.svg +98 -0
  253. datalab/data/icons/roi/roi_sig.svg +124 -0
  254. datalab/data/icons/shapes.svg +134 -0
  255. datalab/data/icons/signal.svg +103 -0
  256. datalab/data/icons/table.svg +85 -0
  257. datalab/data/icons/table_unavailable.svg +102 -0
  258. datalab/data/icons/to_signal.svg +124 -0
  259. datalab/data/icons/tour/next.svg +44 -0
  260. datalab/data/icons/tour/previous.svg +44 -0
  261. datalab/data/icons/tour/rewind.svg +51 -0
  262. datalab/data/icons/tour/stop.svg +47 -0
  263. datalab/data/icons/tour/tour.svg +16 -0
  264. datalab/data/icons/uncheck_all.svg +78 -0
  265. datalab/data/icons/view/curve_antialiasing.svg +50 -0
  266. datalab/data/icons/view/new_window.svg +98 -0
  267. datalab/data/icons/view/refresh-auto.svg +57 -0
  268. datalab/data/icons/view/refresh-manual.svg +51 -0
  269. datalab/data/icons/view/reset_curve_styles.svg +96 -0
  270. datalab/data/icons/view/show_first.svg +55 -0
  271. datalab/data/icons/view/show_titles.svg +46 -0
  272. datalab/data/icons/visualization.svg +51 -0
  273. datalab/data/logo/DataLab-Banner-150.png +0 -0
  274. datalab/data/logo/DataLab-Banner-200.png +0 -0
  275. datalab/data/logo/DataLab-Banner2-100.png +0 -0
  276. datalab/data/logo/DataLab-Splash.png +0 -0
  277. datalab/data/logo/DataLab-watermark.png +0 -0
  278. datalab/data/logo/DataLab.svg +83 -0
  279. datalab/data/tests/reordering_test.h5 +0 -0
  280. datalab/data/tutorials/fabry_perot/fabry-perot1.jpg +0 -0
  281. datalab/data/tutorials/fabry_perot/fabry-perot2.jpg +0 -0
  282. datalab/data/tutorials/laser_beam/TEM00_z_13.jpg +0 -0
  283. datalab/data/tutorials/laser_beam/TEM00_z_18.jpg +0 -0
  284. datalab/data/tutorials/laser_beam/TEM00_z_23.jpg +0 -0
  285. datalab/data/tutorials/laser_beam/TEM00_z_30.jpg +0 -0
  286. datalab/data/tutorials/laser_beam/TEM00_z_35.jpg +0 -0
  287. datalab/data/tutorials/laser_beam/TEM00_z_40.jpg +0 -0
  288. datalab/data/tutorials/laser_beam/TEM00_z_45.jpg +0 -0
  289. datalab/data/tutorials/laser_beam/TEM00_z_50.jpg +0 -0
  290. datalab/data/tutorials/laser_beam/TEM00_z_55.jpg +0 -0
  291. datalab/data/tutorials/laser_beam/TEM00_z_60.jpg +0 -0
  292. datalab/data/tutorials/laser_beam/TEM00_z_65.jpg +0 -0
  293. datalab/data/tutorials/laser_beam/TEM00_z_70.jpg +0 -0
  294. datalab/data/tutorials/laser_beam/TEM00_z_75.jpg +0 -0
  295. datalab/data/tutorials/laser_beam/TEM00_z_80.jpg +0 -0
  296. datalab/env.py +542 -0
  297. datalab/gui/__init__.py +89 -0
  298. datalab/gui/actionhandler.py +1701 -0
  299. datalab/gui/docks.py +473 -0
  300. datalab/gui/h5io.py +150 -0
  301. datalab/gui/macroeditor.py +310 -0
  302. datalab/gui/main.py +2081 -0
  303. datalab/gui/newobject.py +217 -0
  304. datalab/gui/objectview.py +766 -0
  305. datalab/gui/panel/__init__.py +48 -0
  306. datalab/gui/panel/base.py +3254 -0
  307. datalab/gui/panel/image.py +157 -0
  308. datalab/gui/panel/macro.py +607 -0
  309. datalab/gui/panel/signal.py +164 -0
  310. datalab/gui/plothandler.py +800 -0
  311. datalab/gui/processor/__init__.py +84 -0
  312. datalab/gui/processor/base.py +2456 -0
  313. datalab/gui/processor/catcher.py +75 -0
  314. datalab/gui/processor/image.py +1214 -0
  315. datalab/gui/processor/signal.py +755 -0
  316. datalab/gui/profiledialog.py +333 -0
  317. datalab/gui/roieditor.py +633 -0
  318. datalab/gui/roigrideditor.py +208 -0
  319. datalab/gui/settings.py +612 -0
  320. datalab/gui/tour.py +908 -0
  321. datalab/h5/__init__.py +12 -0
  322. datalab/h5/common.py +314 -0
  323. datalab/h5/generic.py +580 -0
  324. datalab/h5/native.py +39 -0
  325. datalab/h5/utils.py +95 -0
  326. datalab/objectmodel.py +640 -0
  327. datalab/plugins/_readme_.txt +9 -0
  328. datalab/plugins/datalab_imageformats.py +175 -0
  329. datalab/plugins/datalab_testdata.py +190 -0
  330. datalab/plugins.py +355 -0
  331. datalab/tests/__init__.py +199 -0
  332. datalab/tests/backbone/__init__.py +1 -0
  333. datalab/tests/backbone/config_unit_test.py +170 -0
  334. datalab/tests/backbone/config_versioning_unit_test.py +34 -0
  335. datalab/tests/backbone/dictlistserial_app_test.py +38 -0
  336. datalab/tests/backbone/errorcatcher_unit_test.py +69 -0
  337. datalab/tests/backbone/errormsgbox_unit_test.py +50 -0
  338. datalab/tests/backbone/execenv_unit.py +262 -0
  339. datalab/tests/backbone/loadtest_gdi.py +147 -0
  340. datalab/tests/backbone/long_callback.py +96 -0
  341. datalab/tests/backbone/main_app_test.py +137 -0
  342. datalab/tests/backbone/memory_leak.py +43 -0
  343. datalab/tests/backbone/procisolation1_unit.py +128 -0
  344. datalab/tests/backbone/procisolation2_unit.py +171 -0
  345. datalab/tests/backbone/procisolation_unit_test.py +22 -0
  346. datalab/tests/backbone/profiling_app.py +27 -0
  347. datalab/tests/backbone/strings_unit_test.py +65 -0
  348. datalab/tests/backbone/title_formatting_unit_test.py +82 -0
  349. datalab/tests/conftest.py +131 -0
  350. datalab/tests/features/__init__.py +1 -0
  351. datalab/tests/features/applauncher/__init__.py +1 -0
  352. datalab/tests/features/applauncher/launcher1_app_test.py +28 -0
  353. datalab/tests/features/applauncher/launcher2_app_test.py +30 -0
  354. datalab/tests/features/common/__init__.py +1 -0
  355. datalab/tests/features/common/add_metadata_app_test.py +134 -0
  356. datalab/tests/features/common/add_metadata_unit_test.py +267 -0
  357. datalab/tests/features/common/annotations_management_unit_test.py +152 -0
  358. datalab/tests/features/common/auto_analysis_recompute_unit_test.py +240 -0
  359. datalab/tests/features/common/createobject_unit_test.py +50 -0
  360. datalab/tests/features/common/geometry_results_app_test.py +135 -0
  361. datalab/tests/features/common/interactive_processing_test.py +1109 -0
  362. datalab/tests/features/common/io_app_test.py +75 -0
  363. datalab/tests/features/common/large_results_app_test.py +187 -0
  364. datalab/tests/features/common/metadata_all_patterns_test.py +103 -0
  365. datalab/tests/features/common/metadata_app_test.py +139 -0
  366. datalab/tests/features/common/metadata_io_unit_test.py +60 -0
  367. datalab/tests/features/common/misc_app_test.py +236 -0
  368. datalab/tests/features/common/multiple_geometry_results_unit_test.py +122 -0
  369. datalab/tests/features/common/multiple_table_results_unit_test.py +64 -0
  370. datalab/tests/features/common/operation_modes_app_test.py +392 -0
  371. datalab/tests/features/common/plot_results_app_test.py +278 -0
  372. datalab/tests/features/common/reorder_app_test.py +75 -0
  373. datalab/tests/features/common/result_deletion_unit_test.py +96 -0
  374. datalab/tests/features/common/result_merged_label_unit_test.py +154 -0
  375. datalab/tests/features/common/result_shape_settings_unit_test.py +223 -0
  376. datalab/tests/features/common/roi_plotitem_unit_test.py +64 -0
  377. datalab/tests/features/common/roieditor_unit_test.py +102 -0
  378. datalab/tests/features/common/save_to_dir_app_test.py +163 -0
  379. datalab/tests/features/common/save_to_dir_unit_test.py +474 -0
  380. datalab/tests/features/common/stat_app_test.py +40 -0
  381. datalab/tests/features/common/stats_tools_unit_test.py +77 -0
  382. datalab/tests/features/common/table_results_app_test.py +52 -0
  383. datalab/tests/features/common/textimport_unit_test.py +131 -0
  384. datalab/tests/features/common/uuid_preservation_test.py +281 -0
  385. datalab/tests/features/common/worker_unit_test.py +402 -0
  386. datalab/tests/features/control/__init__.py +1 -0
  387. datalab/tests/features/control/connect_dialog.py +28 -0
  388. datalab/tests/features/control/embedded1_unit_test.py +304 -0
  389. datalab/tests/features/control/embedded2_unit_test.py +52 -0
  390. datalab/tests/features/control/remoteclient_app_test.py +219 -0
  391. datalab/tests/features/control/remoteclient_unit.py +75 -0
  392. datalab/tests/features/control/simpleclient_unit_test.py +321 -0
  393. datalab/tests/features/hdf5/__init__.py +1 -0
  394. datalab/tests/features/hdf5/h5browser1_unit_test.py +31 -0
  395. datalab/tests/features/hdf5/h5browser2_unit.py +55 -0
  396. datalab/tests/features/hdf5/h5browser_app_test.py +77 -0
  397. datalab/tests/features/hdf5/h5import_app_test.py +25 -0
  398. datalab/tests/features/hdf5/h5importer_app_test.py +34 -0
  399. datalab/tests/features/image/__init__.py +1 -0
  400. datalab/tests/features/image/annotations_app_test.py +28 -0
  401. datalab/tests/features/image/annotations_unit_test.py +80 -0
  402. datalab/tests/features/image/average_app_test.py +46 -0
  403. datalab/tests/features/image/background_dialog_test.py +70 -0
  404. datalab/tests/features/image/blobs_app_test.py +50 -0
  405. datalab/tests/features/image/contour_app_test.py +42 -0
  406. datalab/tests/features/image/contour_fabryperot_app_test.py +51 -0
  407. datalab/tests/features/image/denoise_app_test.py +31 -0
  408. datalab/tests/features/image/distribute_on_grid_app_test.py +95 -0
  409. datalab/tests/features/image/edges_app_test.py +31 -0
  410. datalab/tests/features/image/erase_app_test.py +21 -0
  411. datalab/tests/features/image/fft2d_app_test.py +27 -0
  412. datalab/tests/features/image/flatfield_app_test.py +40 -0
  413. datalab/tests/features/image/geometry_transform_unit_test.py +396 -0
  414. datalab/tests/features/image/imagetools_app_test.py +51 -0
  415. datalab/tests/features/image/imagetools_unit_test.py +27 -0
  416. datalab/tests/features/image/load_app_test.py +73 -0
  417. datalab/tests/features/image/morph_app_test.py +32 -0
  418. datalab/tests/features/image/offsetcorrection_app_test.py +30 -0
  419. datalab/tests/features/image/peak2d_app_test.py +53 -0
  420. datalab/tests/features/image/profile_app_test.py +73 -0
  421. datalab/tests/features/image/profile_dialog_test.py +56 -0
  422. datalab/tests/features/image/roi_app_test.py +98 -0
  423. datalab/tests/features/image/roi_circ_app_test.py +62 -0
  424. datalab/tests/features/image/roi_manipulation_app_test.py +268 -0
  425. datalab/tests/features/image/roigrid_unit_test.py +60 -0
  426. datalab/tests/features/image/side_by_side_app_test.py +52 -0
  427. datalab/tests/features/macro/__init__.py +1 -0
  428. datalab/tests/features/macro/macro_app_test.py +28 -0
  429. datalab/tests/features/macro/macroeditor_unit_test.py +102 -0
  430. datalab/tests/features/signal/__init__.py +1 -0
  431. datalab/tests/features/signal/baseline_dialog_test.py +53 -0
  432. datalab/tests/features/signal/deltax_dialog_unit_test.py +34 -0
  433. datalab/tests/features/signal/fft1d_app_test.py +26 -0
  434. datalab/tests/features/signal/filter_app_test.py +44 -0
  435. datalab/tests/features/signal/fitdialog_unit_test.py +50 -0
  436. datalab/tests/features/signal/interpolation_app_test.py +110 -0
  437. datalab/tests/features/signal/loadbigsignal_app_test.py +80 -0
  438. datalab/tests/features/signal/multiple_rois_unit_test.py +132 -0
  439. datalab/tests/features/signal/pulse_features_app_test.py +118 -0
  440. datalab/tests/features/signal/pulse_features_roi_app_test.py +55 -0
  441. datalab/tests/features/signal/roi_app_test.py +78 -0
  442. datalab/tests/features/signal/roi_manipulation_app_test.py +261 -0
  443. datalab/tests/features/signal/select_xy_cursor_unit_test.py +46 -0
  444. datalab/tests/features/signal/signalpeakdetection_dialog_test.py +33 -0
  445. datalab/tests/features/signal/signals_to_image_app_test.py +98 -0
  446. datalab/tests/features/signal/xarray_compat_app_test.py +128 -0
  447. datalab/tests/features/tour_unit_test.py +22 -0
  448. datalab/tests/features/utilities/__init__.py +1 -0
  449. datalab/tests/features/utilities/installconf_unit_test.py +21 -0
  450. datalab/tests/features/utilities/logview_app_test.py +21 -0
  451. datalab/tests/features/utilities/logview_error.py +24 -0
  452. datalab/tests/features/utilities/logview_unit_test.py +21 -0
  453. datalab/tests/features/utilities/memstatus_app_test.py +42 -0
  454. datalab/tests/features/utilities/settings_unit_test.py +88 -0
  455. datalab/tests/scenarios/__init__.py +1 -0
  456. datalab/tests/scenarios/beautiful_app.py +121 -0
  457. datalab/tests/scenarios/common.py +463 -0
  458. datalab/tests/scenarios/demo.py +212 -0
  459. datalab/tests/scenarios/example_app_test.py +47 -0
  460. datalab/tests/scenarios/scenario_h5_app_test.py +75 -0
  461. datalab/tests/scenarios/scenario_ima1_app_test.py +34 -0
  462. datalab/tests/scenarios/scenario_ima2_app_test.py +34 -0
  463. datalab/tests/scenarios/scenario_mac_app_test.py +58 -0
  464. datalab/tests/scenarios/scenario_sig1_app_test.py +36 -0
  465. datalab/tests/scenarios/scenario_sig2_app_test.py +35 -0
  466. datalab/utils/__init__.py +1 -0
  467. datalab/utils/conf.py +304 -0
  468. datalab/utils/dephash.py +105 -0
  469. datalab/utils/qthelpers.py +633 -0
  470. datalab/utils/strings.py +34 -0
  471. datalab/utils/tests.py +0 -0
  472. datalab/widgets/__init__.py +1 -0
  473. datalab/widgets/connection.py +138 -0
  474. datalab/widgets/filedialog.py +91 -0
  475. datalab/widgets/fileviewer.py +84 -0
  476. datalab/widgets/fitdialog.py +788 -0
  477. datalab/widgets/h5browser.py +1048 -0
  478. datalab/widgets/imagebackground.py +111 -0
  479. datalab/widgets/instconfviewer.py +175 -0
  480. datalab/widgets/logviewer.py +80 -0
  481. datalab/widgets/signalbaseline.py +90 -0
  482. datalab/widgets/signalcursor.py +208 -0
  483. datalab/widgets/signaldeltax.py +151 -0
  484. datalab/widgets/signalpeak.py +199 -0
  485. datalab/widgets/status.py +249 -0
  486. datalab/widgets/textimport.py +786 -0
  487. datalab/widgets/warningerror.py +223 -0
  488. datalab/widgets/wizard.py +286 -0
  489. datalab_platform-1.0.1.dist-info/METADATA +121 -0
  490. datalab_platform-1.0.1.dist-info/RECORD +494 -0
  491. datalab_platform-0.0.1.dev0.dist-info/METADATA +0 -67
  492. datalab_platform-0.0.1.dev0.dist-info/RECORD +0 -7
  493. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/WHEEL +0 -0
  494. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/entry_points.txt +0 -0
  495. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/licenses/LICENSE +0 -0
  496. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1701 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Action handler
5
+ ==============
6
+
7
+ The :mod:`datalab.gui.actionhandler` module handles all application actions
8
+ (menus, toolbars, context menu). These actions point to DataLab panels, processors,
9
+ objecthandler, ...
10
+
11
+ Utility classes
12
+ ---------------
13
+
14
+ .. autoclass:: SelectCond
15
+ :members:
16
+
17
+ .. autoclass:: ActionCategory
18
+ :members:
19
+
20
+ Handler classes
21
+ ---------------
22
+
23
+ .. autoclass:: SignalActionHandler
24
+ :members:
25
+ :inherited-members:
26
+
27
+ .. autoclass:: ImageActionHandler
28
+ :members:
29
+ :inherited-members:
30
+ """
31
+
32
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
33
+
34
+ from __future__ import annotations
35
+
36
+ import abc
37
+ import enum
38
+ from collections.abc import Callable, Generator
39
+ from contextlib import contextmanager
40
+ from functools import partial
41
+ from typing import TYPE_CHECKING
42
+
43
+ import sigima.objects as sio
44
+ from guidata.configtools import get_icon
45
+ from guidata.qthelpers import add_actions, create_action
46
+ from qtpy import QtCore as QC
47
+ from qtpy import QtGui as QG
48
+ from qtpy import QtWidgets as QW
49
+
50
+ from datalab.adapters_metadata import GeometryAdapter, TableAdapter, have_results
51
+ from datalab.config import Conf, _
52
+ from datalab.gui import newobject
53
+ from datalab.widgets import fitdialog
54
+
55
+ if TYPE_CHECKING:
56
+ from sigima.objects import ImageObj, SignalObj
57
+
58
+ from datalab.gui.panel.image import ImagePanel
59
+ from datalab.gui.panel.signal import SignalPanel
60
+ from datalab.objectmodel import ObjectGroup
61
+
62
+
63
+ class SelectCond:
64
+ """Signal or image select conditions"""
65
+
66
+ @staticmethod
67
+ def __compat_groups(selected_groups: list[ObjectGroup], min_len: int = 1) -> bool:
68
+ """Check if groups are compatible"""
69
+ return (
70
+ len(selected_groups) >= min_len
71
+ and all(len(group) == len(selected_groups[0]) for group in selected_groups)
72
+ and all(len(group) > 0 for group in selected_groups)
73
+ )
74
+
75
+ @staticmethod
76
+ # pylint: disable=unused-argument
77
+ def always(
78
+ selected_groups: list[ObjectGroup],
79
+ selected_objects: list[SignalObj | ImageObj],
80
+ ) -> bool:
81
+ """Always true"""
82
+ return True
83
+
84
+ @staticmethod
85
+ def exactly_one(
86
+ selected_groups: list[ObjectGroup],
87
+ selected_objects: list[SignalObj | ImageObj],
88
+ ) -> bool:
89
+ """Exactly one signal or image is selected"""
90
+ return len(selected_groups) == 0 and len(selected_objects) == 1
91
+
92
+ @staticmethod
93
+ # pylint: disable=unused-argument
94
+ def exactly_one_group(
95
+ selected_groups: list[ObjectGroup],
96
+ selected_objects: list[SignalObj | ImageObj],
97
+ ) -> bool:
98
+ """Exactly one group is selected"""
99
+ return len(selected_groups) == 1
100
+
101
+ @staticmethod
102
+ def exactly_one_group_or_one_object(
103
+ selected_groups: list[ObjectGroup],
104
+ selected_objects: list[SignalObj | ImageObj],
105
+ ) -> bool:
106
+ """Exactly one group or one signal or image is selected"""
107
+ return len(selected_groups) == 1 or len(selected_objects) == 1
108
+
109
+ @staticmethod
110
+ # pylint: disable=unused-argument
111
+ def at_least_one_group_or_one_object(
112
+ sel_groups: list[ObjectGroup],
113
+ sel_objects: list[SignalObj | ImageObj],
114
+ ) -> bool:
115
+ """At least one group or one signal or image is selected"""
116
+ return len(sel_objects) >= 1 or len(sel_groups) >= 1
117
+
118
+ @staticmethod
119
+ # pylint: disable=unused-argument
120
+ def at_least_one(
121
+ sel_groups: list[ObjectGroup],
122
+ sel_objects: list[SignalObj | ImageObj],
123
+ ) -> bool:
124
+ """At least one signal or image is selected"""
125
+ return len(sel_objects) >= 1 or SelectCond.__compat_groups(sel_groups, 1)
126
+
127
+ @staticmethod
128
+ def at_least_two(
129
+ sel_groups: list[ObjectGroup],
130
+ sel_objects: list[SignalObj | ImageObj],
131
+ ) -> bool:
132
+ """At least two signals or images are selected"""
133
+ return len(sel_objects) >= 2 or SelectCond.__compat_groups(sel_groups, 2)
134
+
135
+ @staticmethod
136
+ # pylint: disable=unused-argument
137
+ def with_roi(
138
+ selected_groups: list[ObjectGroup],
139
+ selected_objects: list[SignalObj | ImageObj],
140
+ ) -> bool:
141
+ """At least one signal or image has a ROI"""
142
+ return any(obj.roi is not None for obj in selected_objects)
143
+
144
+ @staticmethod
145
+ # pylint: disable=unused-argument
146
+ def exactly_one_with_roi(
147
+ selected_groups: list[ObjectGroup],
148
+ selected_objects: list[SignalObj | ImageObj],
149
+ ) -> bool:
150
+ """Exactly one signal or image has a ROI"""
151
+ return (
152
+ len(selected_groups) == 0
153
+ and len(selected_objects) == 1
154
+ and selected_objects[0].roi is not None
155
+ )
156
+
157
+ @staticmethod
158
+ # pylint: disable=unused-argument
159
+ def exactly_one_with_annotations(
160
+ selected_groups: list[ObjectGroup],
161
+ selected_objects: list[SignalObj | ImageObj],
162
+ ) -> bool:
163
+ """Exactly one signal or image has annotations"""
164
+ return (
165
+ len(selected_groups) == 0
166
+ and len(selected_objects) == 1
167
+ and selected_objects[0].has_annotations()
168
+ )
169
+
170
+ @staticmethod
171
+ # pylint: disable=unused-argument
172
+ def with_annotations(
173
+ selected_groups: list[ObjectGroup],
174
+ selected_objects: list[SignalObj | ImageObj],
175
+ ) -> bool:
176
+ """At least one signal or image has annotations"""
177
+ return any(obj.has_annotations() for obj in selected_objects)
178
+
179
+ @staticmethod
180
+ # pylint: disable=unused-argument
181
+ def with_results(
182
+ selected_groups: list[ObjectGroup],
183
+ selected_objects: list[SignalObj | ImageObj],
184
+ ) -> bool:
185
+ """At least one signal or image has results"""
186
+ return have_results(selected_objects)
187
+
188
+
189
+ class ActionCategory(enum.Enum):
190
+ """Action categories"""
191
+
192
+ FILE = enum.auto()
193
+ CREATE = enum.auto()
194
+ EDIT = enum.auto()
195
+ VIEW = enum.auto()
196
+ ROI = enum.auto()
197
+ OPERATION = enum.auto()
198
+ PROCESSING = enum.auto()
199
+ ANALYSIS = enum.auto()
200
+ CONTEXT_MENU = enum.auto()
201
+ PANEL_TOOLBAR = enum.auto()
202
+ VIEW_TOOLBAR = enum.auto()
203
+ SUBMENU = enum.auto() # temporary
204
+ PLUGINS = enum.auto() # for plugins actions
205
+
206
+
207
+ class BaseActionHandler(metaclass=abc.ABCMeta):
208
+ """Object handling panel GUI interactions: actions, menus, ...
209
+
210
+ Args:
211
+ panel: Panel to handle
212
+ panel_toolbar: Panel toolbar (actions related to the panel objects management)
213
+ view_toolbar: View toolbar (actions related to the panel view, i.e. plot)
214
+ """
215
+
216
+ OBJECT_STR = "" # e.g. "signal"
217
+ OBJECT_STR_PLURAL = "" # e.g. "signals"
218
+
219
+ def __init__(
220
+ self,
221
+ panel: SignalPanel | ImagePanel,
222
+ panel_toolbar: QW.QToolBar,
223
+ view_toolbar: QW.QToolBar,
224
+ ):
225
+ self.panel = panel
226
+ self.panel_toolbar = panel_toolbar
227
+ self.view_toolbar = view_toolbar
228
+ self.feature_actions = {}
229
+ self.operation_end_actions = None
230
+ self.__category_in_progress: ActionCategory = None
231
+ self.__submenu_in_progress = False
232
+ self.__submenu_stack: list[dict[str, any]] = [] # Stack for nested submenus
233
+ self.__actions: dict[Callable, list[QW.QAction]] = {}
234
+ self.__submenus: dict[str, QW.QMenu] = {}
235
+ # Store reference to ROI remove submenu
236
+ self.roi_remove_submenu: QW.QMenu | None = None
237
+ # Store reference to results delete submenu
238
+ self.results_delete_submenu: QW.QMenu | None = None
239
+ # Store reference to metadata and annotations submenus (for screenshots)
240
+ self.metadata_submenu: QW.QMenu | None = None
241
+ self.annotations_submenu: QW.QMenu | None = None
242
+ # Store reference to show label action (for settings dialog)
243
+ self.show_label_action: QW.QAction | None = None
244
+
245
+ @property
246
+ def object_suffix(self) -> str:
247
+ """Object suffix (e.g. "sig" for signal, "ima" for image)"""
248
+ return self.__class__.__name__[:3].lower()
249
+
250
+ def populate_roi_remove_submenu(self) -> None:
251
+ """Populate the ROI Remove submenu dynamically based on current selection"""
252
+ submenu = self.roi_remove_submenu
253
+ if submenu is None:
254
+ return
255
+
256
+ # Clear existing actions
257
+ submenu.clear()
258
+
259
+ # Get current selected object
260
+ selected_objects = self.panel.objview.get_sel_objects()
261
+ if not selected_objects:
262
+ return
263
+
264
+ obj = selected_objects[0]
265
+ if obj.roi is None or obj.roi.is_empty():
266
+ return
267
+
268
+ # Add individual ROI removal actions
269
+ for i in range(len(obj.roi.single_rois)):
270
+ roi_title = obj.roi.get_single_roi_title(i)
271
+ action = QW.QAction(roi_title, submenu)
272
+ # Use partial to avoid lambda closure issues
273
+ action.triggered.connect(partial(self._remove_single_roi_by_index, i))
274
+ submenu.addAction(action)
275
+
276
+ # Add separator and "Remove all" action
277
+ if len(obj.roi.single_rois) > 0:
278
+ submenu.addSeparator()
279
+ remove_all_action = QW.QAction(_("Remove all"), submenu)
280
+ remove_all_action.triggered.connect(
281
+ self.panel.processor.delete_regions_of_interest
282
+ )
283
+ submenu.addAction(remove_all_action)
284
+
285
+ def _remove_single_roi_by_index(self, roi_index: int) -> None:
286
+ """Helper method to remove a single ROI by index"""
287
+ self.panel.processor.delete_single_roi(roi_index)
288
+
289
+ def has_metadata_in_clipboard(
290
+ self,
291
+ selected_groups: list[ObjectGroup],
292
+ selected_objects: list[SignalObj | ImageObj],
293
+ ) -> bool:
294
+ """Check if metadata clipboard is not empty"""
295
+ # pylint: disable=unused-argument
296
+ return bool(self.panel.metadata_clipboard)
297
+
298
+ def has_annotations_in_clipboard(
299
+ self,
300
+ selected_groups: list[ObjectGroup],
301
+ selected_objects: list[SignalObj | ImageObj],
302
+ ) -> bool:
303
+ """Check if annotations clipboard is not empty"""
304
+ # pylint: disable=unused-argument
305
+ return bool(self.panel.annotations_clipboard)
306
+
307
+ def populate_results_delete_submenu(self) -> None:
308
+ """Populate the Results Delete submenu dynamically based on current selection"""
309
+ submenu = self.results_delete_submenu
310
+ if submenu is None:
311
+ return
312
+
313
+ # Clear existing actions
314
+ submenu.clear()
315
+
316
+ # Get current selected objects (including groups)
317
+ objs = self.panel.objview.get_sel_objects(include_groups=True)
318
+ if not objs:
319
+ submenu.setEnabled(False)
320
+ return
321
+
322
+ # Collect all results with their metadata keys and titles
323
+ result_items = [] # List of (metadata_key, title, obj, adapter)
324
+
325
+ for obj in objs:
326
+ # Get all adapters for this object
327
+ for adapter in list(GeometryAdapter.iterate_from_obj(obj)) + list(
328
+ TableAdapter.iterate_from_obj(obj)
329
+ ):
330
+ metadata_key = adapter.metadata_key
331
+ title = adapter.title
332
+ result_items.append((metadata_key, title, obj, adapter))
333
+
334
+ if not result_items:
335
+ submenu.setEnabled(False)
336
+ return
337
+
338
+ # Enable submenu since we have results
339
+ submenu.setEnabled(True)
340
+
341
+ # Add individual result deletion actions
342
+ for metadata_key, title, obj, adapter in result_items:
343
+ action = QW.QAction(title, submenu)
344
+ # Use partial to avoid lambda closure issues
345
+ action.triggered.connect(partial(self._delete_single_result, obj, adapter))
346
+ submenu.addAction(action)
347
+
348
+ # Add separator and "Delete all results..." action
349
+ if result_items:
350
+ submenu.addSeparator()
351
+ delete_all_action = QW.QAction(_("Delete all results") + "...", submenu)
352
+ delete_all_action.triggered.connect(self.panel.delete_results)
353
+ submenu.addAction(delete_all_action)
354
+
355
+ def _delete_single_result(
356
+ self, obj: SignalObj | ImageObj, adapter: GeometryAdapter | TableAdapter
357
+ ) -> None:
358
+ """Helper method to delete a single result
359
+
360
+ Args:
361
+ obj: Object containing the result
362
+ adapter: Adapter for the result to delete
363
+ """
364
+ adapter.remove_from(obj)
365
+ # Update properties panel to reflect the removal
366
+ if obj is self.panel.objview.get_current_object():
367
+ self.panel.objprop.update_properties_from(obj)
368
+ # Update action states to reflect the removal
369
+ selected_groups = self.panel.objview.get_sel_groups()
370
+ selected_objects = self.panel.objview.get_sel_objects(include_groups=True)
371
+ self.selected_objects_changed(selected_groups, selected_objects)
372
+ # Refresh the plot to update the display
373
+ # Use the same refresh pattern as delete_results() method
374
+ self.panel.refresh_plot("selected", True, False)
375
+
376
+ @contextmanager
377
+ def new_category(self, category: ActionCategory) -> Generator[None, None, None]:
378
+ """Context manager for creating a new menu.
379
+
380
+ Args:
381
+ category: Action category
382
+
383
+ Yields:
384
+ None
385
+ """
386
+ self.__category_in_progress = category
387
+ try:
388
+ yield
389
+ finally:
390
+ self.__category_in_progress = None
391
+
392
+ @contextmanager
393
+ def new_menu(
394
+ self,
395
+ title: str,
396
+ icon_name: str | None = None,
397
+ store_ref: str | None = None,
398
+ ) -> Generator[None, None, None]:
399
+ """Context manager for creating a new menu.
400
+
401
+ Args:
402
+ title: Menu title
403
+ icon_name: Menu icon name. Defaults to None.
404
+ store_ref: Optional attribute name to store menu reference.
405
+ Defaults to None.
406
+
407
+ Yields:
408
+ None
409
+ """
410
+ # Create a unique key for this submenu level
411
+ parent_key = ""
412
+ if self.__submenu_stack:
413
+ parent_key = self.__submenu_stack[-1]["key"] + "/"
414
+ elif self.__category_in_progress:
415
+ parent_key = self.__category_in_progress.name + "/"
416
+
417
+ key = parent_key + title
418
+ is_new = key not in self.__submenus
419
+
420
+ if is_new:
421
+ self.__submenus[key] = menu = QW.QMenu(title)
422
+ if icon_name:
423
+ menu.setIcon(get_icon(icon_name))
424
+ # Store reference to menu if requested
425
+ if store_ref is not None:
426
+ setattr(self, store_ref, menu)
427
+ else:
428
+ menu = self.__submenus[key]
429
+
430
+ # Save current submenu state and push new submenu onto stack
431
+ submenu_state = {
432
+ "key": key,
433
+ "menu": menu,
434
+ "is_new": is_new,
435
+ "actions": [], # Actions for this submenu level
436
+ }
437
+ self.__submenu_stack.append(submenu_state)
438
+ self.__submenu_in_progress = True
439
+
440
+ try:
441
+ yield
442
+ finally:
443
+ # Pop the current submenu from stack
444
+ current_submenu = self.__submenu_stack.pop()
445
+
446
+ # Get actions for this specific submenu level
447
+ submenu_actions = current_submenu.get("actions", [])
448
+
449
+ # Also get any actions that were added to the generic SUBMENU category
450
+ # while this submenu was the active one
451
+ generic_submenu_actions = self.feature_actions.pop(
452
+ ActionCategory.SUBMENU, []
453
+ )
454
+ submenu_actions.extend(generic_submenu_actions)
455
+
456
+ add_actions(current_submenu["menu"], submenu_actions)
457
+
458
+ # Update submenu in progress status BEFORE adding to parent
459
+ self.__submenu_in_progress = len(self.__submenu_stack) > 0
460
+
461
+ if current_submenu["is_new"]:
462
+ # Add this submenu to its parent (either category or parent submenu)
463
+ if self.__submenu_stack:
464
+ # We're in a nested submenu, add to parent submenu's actions
465
+ parent_submenu = self.__submenu_stack[-1]
466
+ parent_submenu["actions"].append(current_submenu["menu"])
467
+ else:
468
+ # We're at the top level, add to category actions
469
+ # Force using the current category, not SUBMENU
470
+ self.add_to_action_list(
471
+ current_submenu["menu"], category=self.__category_in_progress
472
+ )
473
+
474
+ # pylint: disable=too-many-positional-arguments
475
+ def new_action(
476
+ self,
477
+ title: str,
478
+ position: int | None = None,
479
+ separator: bool = False,
480
+ triggered: Callable | None = None,
481
+ toggled: Callable | None = None,
482
+ shortcut: QW.QShortcut | None = None,
483
+ icon_name: str | None = None,
484
+ tip: str | None = None,
485
+ select_condition: Callable | str | None = None,
486
+ context_menu_pos: int | None = None,
487
+ context_menu_sep: bool = False,
488
+ toolbar_pos: int | None = None,
489
+ toolbar_sep: bool = False,
490
+ toolbar_category: ActionCategory | None = None,
491
+ ) -> QW.QAction:
492
+ """Create new action and add it to list of actions.
493
+
494
+ Args:
495
+ title: action title
496
+ position: add action to menu at this position. Defaults to None.
497
+ separator: add separator before action in menu
498
+ (or after if pos is positive). Defaults to False.
499
+ triggered: triggered callback. Defaults to None.
500
+ toggled: toggled callback. Defaults to None.
501
+ shortcut: shortcut. Defaults to None.
502
+ icon_name: icon name. Defaults to None.
503
+ tip: tooltip. Defaults to None.
504
+ select_condition: selection condition. Defaults to None.
505
+ If str, must be the name of a method of SelectCond, i.e. one of
506
+ "always", "exactly_one", "exactly_one_group",
507
+ "at_least_one_group_or_one_object", "at_least_one",
508
+ "at_least_two", "with_roi".
509
+ context_menu_pos: add action to context menu at this position.
510
+ Defaults to None.
511
+ context_menu_sep: add separator before action in context menu
512
+ (or after if context_menu_pos is positive). Defaults to False.
513
+ toolbar_pos: add action to toolbar at this position. Defaults to None.
514
+ toolbar_sep: add separator before action in toolbar
515
+ (or after if toolbar_pos is positive). Defaults to False.
516
+ toolbar_category: toolbar category. Defaults to None.
517
+ If toolbar_pos is not None, this specifies the category of the toolbar.
518
+ If None, defaults to ActionCategory.VIEW_TOOLBAR if the current category
519
+ is ActionCategory.VIEW, else to ActionCategory.PANEL_TOOLBAR.
520
+
521
+ Returns:
522
+ New action
523
+ """
524
+ if isinstance(select_condition, str):
525
+ assert select_condition in SelectCond.__dict__
526
+ select_condition = getattr(SelectCond, select_condition)
527
+
528
+ action = create_action(
529
+ parent=self.panel,
530
+ title=title,
531
+ triggered=triggered,
532
+ toggled=toggled,
533
+ shortcut=shortcut,
534
+ icon=get_icon(icon_name) if icon_name else None,
535
+ tip=tip,
536
+ context=QC.Qt.WidgetWithChildrenShortcut, # [1]
537
+ )
538
+ self.panel.addAction(action) # [1]
539
+ # [1] This is needed to make actions work with shortcuts for active panel,
540
+ # because some of the shortcuts are using the same keybindings for both panels.
541
+ # (Fixes #10)
542
+
543
+ self.add_action(action, select_condition)
544
+ self.add_to_action_list(action, None, position, separator)
545
+ if context_menu_pos is not None:
546
+ self.add_to_action_list(
547
+ action, ActionCategory.CONTEXT_MENU, context_menu_pos, context_menu_sep
548
+ )
549
+ if toolbar_pos is not None:
550
+ if toolbar_category is None:
551
+ if self.__category_in_progress is ActionCategory.VIEW:
552
+ toolbar_category = ActionCategory.VIEW_TOOLBAR
553
+ else:
554
+ toolbar_category = ActionCategory.PANEL_TOOLBAR
555
+ self.add_to_action_list(action, toolbar_category, toolbar_pos, toolbar_sep)
556
+ return action
557
+
558
+ def action_for(
559
+ self,
560
+ function_or_name: Callable | str,
561
+ position: int | None = None,
562
+ separator: bool = False,
563
+ context_menu_pos: int | None = None,
564
+ context_menu_sep: bool = False,
565
+ toolbar_pos: int | None = None,
566
+ toolbar_sep: bool = False,
567
+ toolbar_category: ActionCategory | None = None,
568
+ ) -> QW.QAction:
569
+ """Create action for a feature.
570
+
571
+ Args:
572
+ function_or_name: function or name of the feature
573
+ position: add action to menu at this position. Defaults to None.
574
+ separator: add separator before action in menu
575
+ context_menu_pos: add action to context menu at this position.
576
+ context_menu_pos: add action to context menu at this position.
577
+ Defaults to None.
578
+ context_menu_sep: add separator before action in context menu
579
+ (or after if context_menu_pos is positive). Defaults to False.
580
+ toolbar_pos: add action to toolbar at this position. Defaults to None.
581
+ toolbar_sep: add separator before action in toolbar
582
+ (or after if toolbar_pos is positive). Defaults to False.
583
+ toolbar_category: toolbar category. Defaults to None.
584
+ If toolbar_pos is not None, this specifies the category of the toolbar.
585
+ If None, defaults to ActionCategory.VIEW_TOOLBAR if the current category
586
+ is ActionCategory.VIEW, else to ActionCategory.PANEL_TOOLBAR.
587
+
588
+ Returns:
589
+ New action
590
+ """
591
+ feature = self.panel.processor.get_feature(function_or_name)
592
+ if feature.pattern == "n_to_1":
593
+ condition = SelectCond.at_least_two
594
+ else:
595
+ condition = SelectCond.at_least_one
596
+ return self.new_action(
597
+ feature.action_title,
598
+ position=position,
599
+ separator=separator,
600
+ triggered=lambda: self.panel.processor.run_feature(feature.function),
601
+ select_condition=condition,
602
+ icon_name=feature.icon_name,
603
+ tip=feature.comment,
604
+ context_menu_pos=context_menu_pos,
605
+ context_menu_sep=context_menu_sep,
606
+ toolbar_pos=toolbar_pos,
607
+ toolbar_sep=toolbar_sep,
608
+ toolbar_category=toolbar_category,
609
+ )
610
+
611
+ def add_to_action_list(
612
+ self,
613
+ action: QW.QAction,
614
+ category: ActionCategory | None = None,
615
+ pos: int | None = None,
616
+ sep: bool = False,
617
+ ) -> None:
618
+ """Add action to list of actions.
619
+
620
+ Args:
621
+ action: action to add
622
+ category: action category. Defaults to None.
623
+ If None, action is added to the current category.
624
+ pos: add action to menu at this position. Defaults to None.
625
+ If None, action is added at the end of the list.
626
+ sep: add separator before action in menu
627
+ (or after if pos is positive). Defaults to False.
628
+ """
629
+ if category is None:
630
+ if self.__submenu_in_progress and self.__submenu_stack:
631
+ # Add directly to the current submenu's action list
632
+ current_submenu = self.__submenu_stack[-1]
633
+ actionlist = current_submenu["actions"]
634
+ if pos is None:
635
+ pos = -1
636
+ add_separator_after = pos >= 0
637
+ if pos < 0:
638
+ pos = len(actionlist) + pos + 1
639
+ actionlist.insert(pos, action)
640
+ if sep:
641
+ if add_separator_after:
642
+ pos += 1
643
+ actionlist.insert(pos, None)
644
+ return
645
+ if self.__category_in_progress is not None:
646
+ category = self.__category_in_progress
647
+ else:
648
+ raise ValueError("No category specified")
649
+ if pos is None:
650
+ pos = -1
651
+ actionlist = self.feature_actions.setdefault(category, [])
652
+ add_separator_after = pos >= 0
653
+ if pos < 0:
654
+ pos = len(actionlist) + pos + 1
655
+ actionlist.insert(pos, action)
656
+ if sep:
657
+ if add_separator_after:
658
+ pos += 1
659
+ actionlist.insert(pos, None)
660
+
661
+ def add_action(
662
+ self, action: QW.QAction, select_condition: Callable | None = None
663
+ ) -> None:
664
+ """Add action to list of actions.
665
+
666
+ Args:
667
+ action: action to add
668
+ select_condition: condition to enable action. Defaults to None.
669
+ If None, action is enabled if at least one object is selected.
670
+ """
671
+ if select_condition is None:
672
+ select_condition = SelectCond.at_least_one
673
+ self.__actions.setdefault(select_condition, []).append(action)
674
+
675
+ def selected_objects_changed(
676
+ self,
677
+ selected_groups: list[ObjectGroup],
678
+ selected_objects: list[SignalObj | ImageObj],
679
+ ) -> None:
680
+ """Update actions based on selected objects.
681
+
682
+ Args:
683
+ selected_groups: selected groups
684
+ selected_objects: selected objects
685
+ """
686
+ for cond, actlist in self.__actions.items():
687
+ if cond is not None:
688
+ for act in actlist:
689
+ act.setEnabled(cond(selected_groups, selected_objects))
690
+
691
+ def create_all_actions(self):
692
+ """Create all actions"""
693
+ self.create_first_actions()
694
+ self.create_last_actions()
695
+
696
+ # Connect ROI remove submenu signal after all actions are created
697
+ if self.roi_remove_submenu is not None:
698
+ self.roi_remove_submenu.aboutToShow.connect(
699
+ self.populate_roi_remove_submenu
700
+ )
701
+ # Add the submenu to the action management system with ROI condition
702
+ self.add_action(self.roi_remove_submenu, SelectCond.with_roi)
703
+
704
+ # Connect results delete submenu signal after all actions are created
705
+ if self.results_delete_submenu is not None:
706
+ self.results_delete_submenu.aboutToShow.connect(
707
+ self.populate_results_delete_submenu
708
+ )
709
+ # Add the submenu to the action management system
710
+ self.add_action(
711
+ self.results_delete_submenu,
712
+ SelectCond.at_least_one_group_or_one_object,
713
+ )
714
+
715
+ add_actions(
716
+ self.panel_toolbar, self.feature_actions.pop(ActionCategory.PANEL_TOOLBAR)
717
+ )
718
+ # For the view toolbar, we add the actions to the beginning of the toolbar:
719
+ before = self.view_toolbar.actions()[0]
720
+ for action in self.feature_actions.pop(ActionCategory.VIEW_TOOLBAR):
721
+ if action is None:
722
+ self.view_toolbar.insertSeparator(before)
723
+ else:
724
+ self.view_toolbar.insertAction(before, action)
725
+ self.view_toolbar.insertSeparator(before)
726
+
727
+ def create_first_actions(self):
728
+ """Create actions that are added to the menus in the first place"""
729
+ # MARK: FILE
730
+ with self.new_category(ActionCategory.FILE):
731
+ self.new_action(
732
+ _("Open %s...") % self.OBJECT_STR,
733
+ # Icon name is 'fileopen_sig.svg' or 'fileopen_ima.svg':
734
+ icon_name=f"fileopen_{self.object_suffix}.svg",
735
+ tip=_("Open one or more %s files") % self.OBJECT_STR,
736
+ triggered=self.panel.load_from_files,
737
+ shortcut=QG.QKeySequence(QG.QKeySequence.Open),
738
+ select_condition=SelectCond.always,
739
+ toolbar_pos=-1,
740
+ )
741
+ self.new_action(
742
+ _("Open from directory..."),
743
+ icon_name="fileopen_directory.svg",
744
+ tip=_("Open all %s files from directory") % self.OBJECT_STR,
745
+ triggered=self.panel.load_from_directory,
746
+ select_condition=SelectCond.always,
747
+ toolbar_pos=-1,
748
+ )
749
+ self.new_action(
750
+ _("Save %s...") % self.OBJECT_STR,
751
+ # Icon name is 'filesave_sig.svg' or 'filesave_ima.svg'
752
+ icon_name=f"filesave_{self.object_suffix}.svg",
753
+ tip=_("Save selected %s") % self.OBJECT_STR_PLURAL,
754
+ triggered=self.panel.save_to_files,
755
+ shortcut=QG.QKeySequence(QG.QKeySequence.Save),
756
+ select_condition=SelectCond.at_least_one,
757
+ context_menu_pos=-1,
758
+ toolbar_pos=-1,
759
+ )
760
+ self.new_action(
761
+ _("Save to directory..."),
762
+ icon_name="save_to_directory.svg",
763
+ tip=_("Save selected %s using a filename pattern")
764
+ % self.OBJECT_STR_PLURAL,
765
+ triggered=self.panel.save_to_directory,
766
+ select_condition=SelectCond.at_least_two,
767
+ context_menu_pos=-1,
768
+ toolbar_pos=-1,
769
+ )
770
+ self.new_action(
771
+ _("Import text file..."),
772
+ icon_name="import_text.svg",
773
+ triggered=self.panel.exec_import_wizard,
774
+ select_condition=SelectCond.always,
775
+ )
776
+
777
+ # MARK: EDIT
778
+ with self.new_category(ActionCategory.EDIT):
779
+ self.new_action(
780
+ _("Recompute"),
781
+ icon_name="recompute.svg",
782
+ shortcut="Ctrl+R",
783
+ tip=_("Recompute selected %s with its processing parameters")
784
+ % self.OBJECT_STR,
785
+ triggered=self.panel.recompute_processing,
786
+ select_condition=SelectCond.at_least_one_group_or_one_object,
787
+ context_menu_pos=-1,
788
+ toolbar_pos=-1,
789
+ )
790
+ self.new_action(
791
+ _("Select source objects"),
792
+ icon_name="goto_source.svg",
793
+ tip=_("Select source object(s) used to create the selected %s")
794
+ % self.OBJECT_STR,
795
+ triggered=self.panel.select_source_objects,
796
+ select_condition=SelectCond.exactly_one,
797
+ context_menu_pos=-1,
798
+ toolbar_pos=-1,
799
+ )
800
+ self.new_action(
801
+ _("Rename"),
802
+ icon_name="rename.svg",
803
+ shortcut="F2",
804
+ tip=_("Edit title of selected %s or group") % self.OBJECT_STR,
805
+ triggered=self.panel.rename_selected_object_or_group,
806
+ select_condition=SelectCond.exactly_one_group_or_one_object,
807
+ context_menu_pos=-1,
808
+ separator=True,
809
+ )
810
+ self.new_action(
811
+ _("New group..."),
812
+ icon_name="new_group.svg",
813
+ tip=_("Create a new group"),
814
+ triggered=self.panel.new_group,
815
+ select_condition=SelectCond.always,
816
+ context_menu_pos=-1,
817
+ )
818
+ self.new_action(
819
+ _("Move up"),
820
+ icon_name="move_up.svg",
821
+ tip=_("Move up selection (groups or objects)"),
822
+ triggered=self.panel.objview.move_up,
823
+ select_condition=SelectCond.at_least_one_group_or_one_object,
824
+ context_menu_pos=-1,
825
+ toolbar_pos=-1,
826
+ )
827
+ self.new_action(
828
+ _("Move down"),
829
+ icon_name="move_down.svg",
830
+ tip=_("Move down selection (groups or objects)"),
831
+ triggered=self.panel.objview.move_down,
832
+ select_condition=SelectCond.at_least_one_group_or_one_object,
833
+ context_menu_pos=-1,
834
+ toolbar_pos=-1,
835
+ )
836
+ self.new_action(
837
+ _("Duplicate"),
838
+ icon_name="duplicate.svg",
839
+ tip=_("Duplicate selected %s") % self.OBJECT_STR,
840
+ separator=True,
841
+ triggered=self.panel.duplicate_object,
842
+ shortcut=QG.QKeySequence(QG.QKeySequence.Copy),
843
+ select_condition=SelectCond.at_least_one_group_or_one_object,
844
+ context_menu_pos=-1,
845
+ toolbar_pos=-1,
846
+ )
847
+ self.new_action(
848
+ _("Remove"),
849
+ icon_name="delete.svg",
850
+ tip=_("Remove selected %s") % self.OBJECT_STR,
851
+ triggered=self.panel.remove_object,
852
+ shortcut=QG.QKeySequence(QG.QKeySequence.Delete),
853
+ select_condition=SelectCond.at_least_one_group_or_one_object,
854
+ context_menu_pos=-1,
855
+ toolbar_pos=-1,
856
+ )
857
+ self.new_action(
858
+ _("Delete all"),
859
+ select_condition=SelectCond.always,
860
+ shortcut="Shift+Ctrl+Suppr",
861
+ tip=_("Delete all groups and objects"),
862
+ icon_name="delete_all.svg",
863
+ triggered=self.panel.delete_all_objects,
864
+ toolbar_pos=-1,
865
+ )
866
+ with self.new_menu(
867
+ _("Metadata"), icon_name="metadata.svg", store_ref="metadata_submenu"
868
+ ):
869
+ self.new_action(
870
+ _("Copy metadata"),
871
+ icon_name="metadata_copy.svg",
872
+ tip=_("Copy metadata from selected %s") % self.OBJECT_STR,
873
+ triggered=self.panel.copy_metadata,
874
+ select_condition=SelectCond.exactly_one,
875
+ toolbar_pos=-1,
876
+ )
877
+ self.new_action(
878
+ _("Paste metadata"),
879
+ icon_name="metadata_paste.svg",
880
+ tip=_("Paste metadata into selected %s") % self.OBJECT_STR,
881
+ triggered=self.panel.paste_metadata,
882
+ select_condition=self.has_metadata_in_clipboard,
883
+ toolbar_pos=-1,
884
+ )
885
+ self.new_action(
886
+ _("Add metadata") + "...",
887
+ separator=True,
888
+ icon_name="metadata_add.svg",
889
+ tip=_("Add a metadata item to selected %s") % self.OBJECT_STR,
890
+ triggered=self.panel.add_metadata,
891
+ select_condition=SelectCond.at_least_one,
892
+ toolbar_pos=-1,
893
+ )
894
+ self.new_action(
895
+ _("Import metadata") + "...",
896
+ icon_name="metadata_import.svg",
897
+ tip=_("Import metadata into %s") % self.OBJECT_STR,
898
+ triggered=self.panel.import_metadata_from_file,
899
+ select_condition=SelectCond.exactly_one,
900
+ toolbar_pos=-1,
901
+ )
902
+ self.new_action(
903
+ _("Export metadata") + "...",
904
+ icon_name="metadata_export.svg",
905
+ tip=_("Export selected %s metadata") % self.OBJECT_STR,
906
+ triggered=self.panel.export_metadata_from_file,
907
+ select_condition=SelectCond.exactly_one,
908
+ toolbar_pos=-1,
909
+ )
910
+ self.new_action(
911
+ _("Delete object metadata"),
912
+ separator=True,
913
+ icon_name="metadata_delete.svg",
914
+ tip=_("Delete all that is contained in object metadata"),
915
+ triggered=self.panel.delete_metadata,
916
+ toolbar_pos=-1,
917
+ )
918
+ with self.new_menu(
919
+ _("Annotations"),
920
+ icon_name="annotations.svg",
921
+ store_ref="annotations_submenu",
922
+ ):
923
+ self.new_action(
924
+ _("Copy annotations"),
925
+ icon_name="annotations_copy.svg",
926
+ tip=_("Copy annotations from selected %s") % self.OBJECT_STR,
927
+ triggered=self.panel.copy_annotations,
928
+ select_condition=SelectCond.exactly_one_with_annotations,
929
+ )
930
+ self.new_action(
931
+ _("Paste annotations"),
932
+ icon_name="annotations_paste.svg",
933
+ tip=_("Paste annotations into selected %s") % self.OBJECT_STR,
934
+ triggered=self.panel.paste_annotations,
935
+ select_condition=self.has_annotations_in_clipboard,
936
+ )
937
+ self.new_action(
938
+ _("Edit annotations") + "...",
939
+ separator=True,
940
+ icon_name="annotations_edit.svg",
941
+ tip=_("Edit annotations of selected %s") % self.OBJECT_STR,
942
+ triggered=lambda: self.panel.open_separate_view(
943
+ edit_annotations=True
944
+ ),
945
+ select_condition=SelectCond.exactly_one,
946
+ )
947
+ self.new_action(
948
+ _("Import annotations") + "...",
949
+ icon_name="annotations_import.svg",
950
+ tip=_("Import annotations into %s") % self.OBJECT_STR,
951
+ triggered=self.panel.import_annotations_from_file,
952
+ select_condition=SelectCond.exactly_one,
953
+ )
954
+ self.new_action(
955
+ _("Export annotations") + "...",
956
+ icon_name="annotations_export.svg",
957
+ tip=_("Export selected %s annotations") % self.OBJECT_STR,
958
+ triggered=self.panel.export_annotations_from_file,
959
+ select_condition=SelectCond.exactly_one_with_annotations,
960
+ )
961
+ self.new_action(
962
+ _("Delete annotations"),
963
+ separator=True,
964
+ icon_name="annotations_delete.svg",
965
+ tip=_("Delete all annotations from selected %s") % self.OBJECT_STR,
966
+ triggered=self.panel.delete_annotations,
967
+ select_condition=SelectCond.with_annotations,
968
+ )
969
+ self.new_action(
970
+ _("Insert object title as annotation label"),
971
+ separator=True,
972
+ triggered=lambda: self.panel.add_label_with_title(ignore_msg=False),
973
+ tip=_(
974
+ "Add the selected object's title as a label to the plot annotations"
975
+ ),
976
+ )
977
+ self.new_action(
978
+ _("Copy titles to clipboard"),
979
+ icon_name="copy_titles.svg",
980
+ tip=_("Copy titles of selected objects to clipboard"),
981
+ triggered=self.panel.copy_titles_to_clipboard,
982
+ )
983
+
984
+ # MARK: ROI
985
+ with self.new_category(ActionCategory.ROI):
986
+ self.new_action(
987
+ _("Edit graphically") + "...",
988
+ triggered=self.panel.processor.edit_roi_graphically,
989
+ icon_name="roi.svg",
990
+ context_menu_pos=-1,
991
+ context_menu_sep=True,
992
+ toolbar_pos=-1,
993
+ toolbar_category=ActionCategory.VIEW_TOOLBAR,
994
+ tip=_("Edit regions of interest graphically"),
995
+ )
996
+ self.new_action(
997
+ _("Edit numerically") + "...",
998
+ triggered=self.panel.processor.edit_roi_numerically,
999
+ select_condition=SelectCond.exactly_one_with_roi,
1000
+ tip=_("Edit regions of interest numerically"),
1001
+ )
1002
+
1003
+ # MARK: VIEW
1004
+ with self.new_category(ActionCategory.VIEW):
1005
+ self.new_action(
1006
+ _("View in a new window") + "...",
1007
+ icon_name="new_window.svg",
1008
+ tip=_("View selected %s in a new window") % self.OBJECT_STR,
1009
+ triggered=self.panel.open_separate_view,
1010
+ context_menu_pos=0,
1011
+ context_menu_sep=True,
1012
+ toolbar_pos=0,
1013
+ )
1014
+
1015
+ # MARK: OPERATION
1016
+ with self.new_category(ActionCategory.OPERATION):
1017
+ self.action_for("arithmetic")
1018
+ with self.new_menu(_("Constant Operations"), icon_name="constant.svg"):
1019
+ self.action_for("addition_constant")
1020
+ self.action_for("difference_constant")
1021
+ self.action_for("product_constant")
1022
+ self.action_for("division_constant")
1023
+ self.action_for("addition")
1024
+ self.action_for("difference")
1025
+ self.action_for("product")
1026
+ self.action_for("division")
1027
+ self.action_for("inverse", separator=True)
1028
+ self.action_for("exp")
1029
+ self.action_for("log10")
1030
+
1031
+ # MARK: PROCESSING
1032
+ with self.new_category(ActionCategory.PROCESSING):
1033
+ with self.new_menu(
1034
+ _("Axis transformation"), icon_name="axis_transform.svg"
1035
+ ):
1036
+ self.action_for("calibration")
1037
+ with self.new_menu(_("Level adjustment"), icon_name="level_adjustment.svg"):
1038
+ self.action_for("normalize")
1039
+ self.action_for("clip")
1040
+ self.new_action(
1041
+ _("Offset correction"),
1042
+ triggered=self.panel.processor.compute_offset_correction,
1043
+ icon_name="offset_correction.svg",
1044
+ tip=_("Evaluate and subtract the offset value from the data"),
1045
+ )
1046
+ with self.new_menu(_("Noise addition"), icon_name="noise_addition.svg"):
1047
+ self.action_for("add_gaussian_noise")
1048
+ self.action_for("add_poisson_noise")
1049
+ self.action_for("add_uniform_noise")
1050
+ with self.new_menu(_("Noise reduction"), icon_name="noise_reduction.svg"):
1051
+ self.action_for("gaussian_filter")
1052
+ self.action_for("moving_average")
1053
+ self.action_for("moving_median")
1054
+ self.action_for("wiener")
1055
+ with self.new_menu(_("Fourier analysis"), icon_name="fourier.svg"):
1056
+ self.action_for("zero_padding")
1057
+ self.action_for("fft")
1058
+ self.action_for("ifft")
1059
+ self.action_for("magnitude_spectrum")
1060
+ self.action_for("phase_spectrum")
1061
+ self.action_for("psd")
1062
+
1063
+ # MARK: ANALYSIS
1064
+ with self.new_category(ActionCategory.ANALYSIS):
1065
+ self.action_for("stats", context_menu_pos=-1, context_menu_sep=True)
1066
+ self.action_for("histogram", context_menu_pos=-1)
1067
+
1068
+ def create_last_actions(self):
1069
+ """Create actions that are added to the menus in the end"""
1070
+ # MARK: ROI
1071
+ with self.new_category(ActionCategory.ROI):
1072
+ self.new_action(
1073
+ _("Extract") + "...",
1074
+ triggered=self.panel.processor.compute_roi_extraction,
1075
+ # Icon name is 'roi_sig.svg' or 'roi_ima.svg':
1076
+ icon_name=f"roi_{self.object_suffix}.svg",
1077
+ separator=True,
1078
+ )
1079
+ self.new_action(
1080
+ _("Copy"),
1081
+ separator=True,
1082
+ icon_name="roi_copy.svg",
1083
+ tip=_("Copy regions of interest from selected %s") % self.OBJECT_STR,
1084
+ triggered=self.panel.copy_roi,
1085
+ select_condition=SelectCond.exactly_one_with_roi,
1086
+ toolbar_pos=-1,
1087
+ )
1088
+ self.new_action(
1089
+ _("Paste"),
1090
+ icon_name="roi_paste.svg",
1091
+ tip=_("Paste regions of interest into selected %s") % self.OBJECT_STR,
1092
+ triggered=self.panel.paste_roi,
1093
+ toolbar_pos=-1,
1094
+ )
1095
+ self.new_action(
1096
+ _("Import") + "...",
1097
+ icon_name="roi_import.svg",
1098
+ tip=_("Import regions of interest into %s") % self.OBJECT_STR,
1099
+ triggered=self.panel.import_roi_from_file,
1100
+ select_condition=SelectCond.exactly_one,
1101
+ toolbar_pos=-1,
1102
+ )
1103
+ self.new_action(
1104
+ _("Export") + "...",
1105
+ icon_name="roi_export.svg",
1106
+ tip=_("Export selected %s regions of interest") % self.OBJECT_STR,
1107
+ triggered=self.panel.export_roi_to_file,
1108
+ select_condition=SelectCond.exactly_one_with_roi,
1109
+ toolbar_pos=-1,
1110
+ )
1111
+
1112
+ # Create dynamic "Remove" submenu
1113
+ with self.new_menu(_("Remove"), icon_name="roi_delete.svg"):
1114
+ # Store reference to the submenu for dynamic population
1115
+ if self.__submenu_stack:
1116
+ current_submenu = self.__submenu_stack[-1]
1117
+ self.roi_remove_submenu = current_submenu["menu"]
1118
+
1119
+ # MARK: OPERATION
1120
+ with self.new_category(ActionCategory.OPERATION):
1121
+ self.action_for("absolute", separator=True)
1122
+ self.action_for("phase")
1123
+ self.action_for("complex_from_magnitude_phase")
1124
+ self.action_for("real", separator=True)
1125
+ self.action_for("imag")
1126
+ self.action_for("complex_from_real_imag")
1127
+ self.action_for("astype", separator=True)
1128
+ self.action_for("average", separator=True)
1129
+ self.action_for("standard_deviation")
1130
+ self.action_for("quadratic_difference")
1131
+ self.action_for("convolution", separator=True)
1132
+ self.action_for("deconvolution")
1133
+
1134
+ # MARK: ANALYSIS
1135
+ with self.new_category(ActionCategory.ANALYSIS):
1136
+ self.new_action(
1137
+ _("Show results") + "...",
1138
+ triggered=self.panel.show_results,
1139
+ icon_name="show_results.svg",
1140
+ separator=True,
1141
+ select_condition=SelectCond.at_least_one_group_or_one_object,
1142
+ )
1143
+ self.show_label_action = self.new_action(
1144
+ _("Results label"),
1145
+ toggled=self.panel.toggle_result_label_visibility,
1146
+ tip=_("Show or hide the merged result label on the plot"),
1147
+ select_condition=SelectCond.with_results,
1148
+ )
1149
+ self.show_label_action.setCheckable(True)
1150
+ self.show_label_action.setChecked(Conf.view.show_result_label.get())
1151
+ self.new_action(
1152
+ _("Plot results") + "...",
1153
+ triggered=self.panel.plot_results,
1154
+ icon_name="plot_results.svg",
1155
+ select_condition=SelectCond.at_least_one_group_or_one_object,
1156
+ )
1157
+
1158
+ # Create dynamic "Delete results" submenu
1159
+ with self.new_menu(_("Delete results"), icon_name="delete_results.svg"):
1160
+ # Store reference to the submenu for dynamic population
1161
+ if self.__submenu_stack:
1162
+ current_submenu = self.__submenu_stack[-1]
1163
+ self.results_delete_submenu = current_submenu["menu"]
1164
+
1165
+ # MARK: VIEW
1166
+ with self.new_category(ActionCategory.VIEW):
1167
+ self.new_action(
1168
+ _("Edit annotations") + "...",
1169
+ icon_name="annotations.svg",
1170
+ tip=_("Edit annotations of selected %s") % self.OBJECT_STR,
1171
+ triggered=lambda: self.panel.open_separate_view(edit_annotations=True),
1172
+ context_menu_pos=1,
1173
+ toolbar_pos=-1,
1174
+ )
1175
+ main = self.panel.mainwindow
1176
+ for cat in (ActionCategory.VIEW, ActionCategory.VIEW_TOOLBAR):
1177
+ for act in (main.autorefresh_action, main.showfirstonly_action):
1178
+ self.add_to_action_list(act, cat, -1)
1179
+ self.new_action(
1180
+ _("Refresh manually"),
1181
+ icon_name="refresh-manual.svg",
1182
+ tip=_("Refresh plot, even if auto-refresh is enabled"),
1183
+ shortcut=QG.QKeySequence(QG.QKeySequence.Refresh),
1184
+ triggered=self.panel.manual_refresh,
1185
+ select_condition=SelectCond.always,
1186
+ toolbar_pos=-1,
1187
+ )
1188
+ for cat in (ActionCategory.VIEW, ActionCategory.VIEW_TOOLBAR):
1189
+ self.add_to_action_list(main.showlabel_action, cat, -1)
1190
+
1191
+
1192
+ class SignalActionHandler(BaseActionHandler):
1193
+ """Object handling signal panel GUI interactions: actions, menus, ..."""
1194
+
1195
+ OBJECT_STR = _("signal")
1196
+ OBJECT_STR_PLURAL = _("signals")
1197
+
1198
+ def create_first_actions(self):
1199
+ """Create actions that are added to the menus in the first place"""
1200
+ super().create_first_actions()
1201
+
1202
+ # MARK: CREATE
1203
+ with self.new_category(ActionCategory.CREATE):
1204
+ for label, pclass, icon_name, separator in (
1205
+ (_("Zero"), sio.ZeroParam, "1d-zero.svg", False),
1206
+ (
1207
+ _("Normal distribution"),
1208
+ sio.NormalDistribution1DParam,
1209
+ "1d-normal.svg",
1210
+ False,
1211
+ ),
1212
+ (
1213
+ _("Poisson distribution"),
1214
+ sio.PoissonDistribution1DParam,
1215
+ "1d-poisson.svg",
1216
+ False,
1217
+ ),
1218
+ (
1219
+ _("Uniform distribution"),
1220
+ sio.UniformDistribution1DParam,
1221
+ "1d-uniform.svg",
1222
+ False,
1223
+ ),
1224
+ (_("Gaussian"), sio.GaussParam, "gaussian.svg", True),
1225
+ (_("Lorentzian"), sio.LorentzParam, "lorentzian.svg", False),
1226
+ (_("Voigt"), sio.VoigtParam, "voigt.svg", False),
1227
+ (_("Blackbody (Planck's law)"), sio.PlanckParam, "planck.svg", False),
1228
+ (_("Sine"), sio.SineParam, "sine.svg", True),
1229
+ (_("Cosine"), sio.CosineParam, "cosine.svg", False),
1230
+ (_("Sawtooth"), sio.SawtoothParam, "sawtooth.svg", False),
1231
+ (_("Triangle"), sio.TriangleParam, "triangle.svg", False),
1232
+ (_("Square"), sio.SquareParam, "square.svg", False),
1233
+ (_("Cardinal sine"), sio.SincParam, "sinc.svg", False),
1234
+ (_("Linear chirp"), sio.LinearChirpParam, "linear_chirp.svg", False),
1235
+ (_("Step"), sio.StepParam, "step.svg", True),
1236
+ (_("Exponential"), sio.ExponentialParam, "exponential.svg", False),
1237
+ (_("Logistic"), sio.LogisticParam, "logistic.svg", False),
1238
+ (_("Pulse"), sio.PulseParam, "pulse.svg", False),
1239
+ (_("Step pulse"), sio.StepPulseParam, "step_pulse.svg", False),
1240
+ (_("Square pulse"), sio.SquarePulseParam, "square_pulse.svg", False),
1241
+ (_("Polynomial"), sio.PolyParam, "polynomial.svg", True),
1242
+ (_("Custom"), newobject.CustomSignalParam, None, False),
1243
+ ):
1244
+ self.new_action(
1245
+ label,
1246
+ tip=_("Create new %s") % label,
1247
+ triggered=lambda pclass=pclass: self.panel.new_object(pclass()),
1248
+ icon_name=icon_name,
1249
+ select_condition=SelectCond.always,
1250
+ separator=separator,
1251
+ )
1252
+
1253
+ # MARK: OPERATION
1254
+ with self.new_category(ActionCategory.OPERATION):
1255
+ self.action_for("power", separator=True)
1256
+ self.action_for("sqrt")
1257
+
1258
+ def cra_fit(title, fitdlgfunc, tip: str | None = None):
1259
+ """Create curve fitting action"""
1260
+ return self.new_action(
1261
+ title,
1262
+ triggered=lambda: self.panel.processor.compute_fit(title, fitdlgfunc),
1263
+ icon_name=fitdlgfunc.__name__ + ".svg",
1264
+ tip=tip,
1265
+ )
1266
+
1267
+ # MARK: PROCESSING
1268
+ with self.new_category(ActionCategory.PROCESSING):
1269
+ with self.new_menu(_("Axis transformation")):
1270
+ self.action_for("transpose")
1271
+ self.action_for("reverse_x")
1272
+ self.action_for("to_cartesian")
1273
+ self.action_for("to_polar")
1274
+ with self.new_menu(_("Frequency filters"), icon_name="highpass.svg"):
1275
+ self.action_for("lowpass")
1276
+ self.action_for("highpass")
1277
+ self.action_for("bandpass")
1278
+ self.action_for("bandstop")
1279
+ with self.new_menu(_("Fitting"), icon_name="exponential_fit.svg"):
1280
+ with self.new_menu(
1281
+ _("Interactive fitting"), icon_name="interactive_fit.svg"
1282
+ ):
1283
+ cra_fit(_("Linear fit"), fitdialog.linear_fit)
1284
+ self.new_action(
1285
+ _("Polynomial fit"),
1286
+ triggered=self.panel.processor.compute_polyfit,
1287
+ icon_name="polynomial_fit.svg",
1288
+ )
1289
+ cra_fit(_("Gaussian fit"), fitdialog.gaussian_fit)
1290
+ cra_fit(_("Lorentzian fit"), fitdialog.lorentzian_fit)
1291
+ cra_fit(_("Voigt fit"), fitdialog.voigt_fit)
1292
+ self.new_action(
1293
+ _("Multi-Gaussian fit"),
1294
+ triggered=self.panel.processor.compute_multigaussianfit,
1295
+ icon_name="multigaussian_fit.svg",
1296
+ )
1297
+ self.new_action(
1298
+ _("Multi-Lorentzian fit"),
1299
+ triggered=self.panel.processor.compute_multilorentzianfit,
1300
+ icon_name="multilorentzian_fit.svg",
1301
+ )
1302
+ cra_fit(
1303
+ _("Planckian fit"),
1304
+ fitdialog.planckian_fit,
1305
+ tip=_("Planckian (blackbody radiation) fitting"),
1306
+ )
1307
+ cra_fit(
1308
+ _("Two half-Gaussian fit"),
1309
+ fitdialog.twohalfgaussian_fit,
1310
+ tip=_("Asymmetric peak fitting with two half-Gaussians"),
1311
+ )
1312
+ cra_fit(
1313
+ _("Piecewise exponential (raise-decay) fit"),
1314
+ fitdialog.piecewiseexponential_fit,
1315
+ tip=_(
1316
+ "Piecewise exponential fitting with raise and decay "
1317
+ "components"
1318
+ ),
1319
+ )
1320
+ cra_fit(_("Exponential fit"), fitdialog.exponential_fit)
1321
+ cra_fit(_("Sinusoidal fit"), fitdialog.sinusoidal_fit)
1322
+ cra_fit(
1323
+ _("CDF fit"),
1324
+ fitdialog.cdf_fit,
1325
+ tip=_(
1326
+ "Cumulative distribution function fit, "
1327
+ "related to Error function (erf)"
1328
+ ),
1329
+ )
1330
+ separator_needed = True
1331
+ for fit_name in (
1332
+ "linear_fit",
1333
+ "polynomial_fit",
1334
+ "gaussian_fit",
1335
+ "lorentzian_fit",
1336
+ "voigt_fit",
1337
+ "planckian_fit",
1338
+ "twohalfgaussian_fit",
1339
+ "piecewiseexponential_fit",
1340
+ "exponential_fit",
1341
+ "sinusoidal_fit",
1342
+ "cdf_fit",
1343
+ "sigmoid_fit",
1344
+ ):
1345
+ self.action_for(fit_name, separator=separator_needed)
1346
+ separator_needed = False
1347
+ self.action_for("evaluate_fit", separator=True)
1348
+ self.action_for("derivative", separator=True)
1349
+ self.action_for("integral")
1350
+ self.action_for("apply_window", separator=True)
1351
+ self.action_for("detrending")
1352
+ self.action_for("interpolate")
1353
+ self.action_for("resampling")
1354
+ with self.new_menu(_("Stability analysis"), icon_name="stability.svg"):
1355
+ self.action_for("allan_variance")
1356
+ self.action_for("allan_deviation")
1357
+ self.action_for("modified_allan_variance")
1358
+ self.action_for("hadamard_variance")
1359
+ self.action_for("total_variance")
1360
+ self.action_for("time_deviation")
1361
+ self.new_action(
1362
+ _("All stability features") + "...",
1363
+ triggered=self.panel.processor.compute_all_stability,
1364
+ separator=True,
1365
+ tip=_("Compute all stability features"),
1366
+ )
1367
+ self.action_for("xy_mode", separator=True)
1368
+
1369
+ # MARK: ANALYSIS
1370
+ with self.new_category(ActionCategory.ANALYSIS):
1371
+ self.action_for("fwhm")
1372
+ self.action_for("fw1e2")
1373
+ self.new_action(
1374
+ _("Full width at y=..."),
1375
+ triggered=self.panel.processor.compute_full_width_at_y,
1376
+ tip=_("Compute the full width at a given y value"),
1377
+ )
1378
+ self.action_for("x_at_minmax")
1379
+ self.new_action(
1380
+ _("First abscissa at y=..."),
1381
+ triggered=self.panel.processor.compute_x_at_y,
1382
+ tip=_(
1383
+ "Compute the first abscissa at a given y value "
1384
+ "(linear interpolation)"
1385
+ ),
1386
+ )
1387
+ self.new_action(
1388
+ _("Ordinate at x=..."),
1389
+ triggered=self.panel.processor.compute_y_at_x,
1390
+ tip=_("Compute the ordinate at a given x value (linear interpolation)"),
1391
+ )
1392
+ self.action_for("extract_pulse_features")
1393
+ self.new_action(
1394
+ _("Peak detection"),
1395
+ separator=True,
1396
+ triggered=self.panel.processor.compute_peak_detection,
1397
+ icon_name="peak_detect.svg",
1398
+ )
1399
+ self.action_for("sampling_rate_period", separator=True)
1400
+ self.action_for("dynamic_parameters", context_menu_pos=-1)
1401
+ self.action_for("bandwidth_3db", context_menu_pos=-1)
1402
+ self.action_for("contrast")
1403
+
1404
+ def create_last_actions(self):
1405
+ """Create actions that are added to the menus in the end"""
1406
+ super().create_last_actions()
1407
+ with self.new_category(ActionCategory.OPERATION):
1408
+ self.action_for("signals_to_image", separator=True)
1409
+
1410
+ with self.new_category(ActionCategory.VIEW):
1411
+ antialiasing_action = self.new_action(
1412
+ _("Curve anti-aliasing"),
1413
+ icon_name="curve_antialiasing.svg",
1414
+ toggled=self.panel.toggle_anti_aliasing,
1415
+ tip=_("Toggle curve anti-aliasing on/off (may slow down plotting)"),
1416
+ toolbar_pos=-1,
1417
+ )
1418
+ antialiasing_action.setChecked(Conf.view.sig_antialiasing.get(True))
1419
+ self.new_action(
1420
+ _("Reset curve styles"),
1421
+ select_condition=SelectCond.always,
1422
+ icon_name="reset_curve_styles.svg",
1423
+ triggered=self.panel.reset_curve_styles,
1424
+ tip=_(
1425
+ "Curve styles are looped over a list of predefined styles.\n"
1426
+ "This action resets the list to its initial state."
1427
+ ),
1428
+ toolbar_pos=-1,
1429
+ )
1430
+
1431
+
1432
+ class ImageActionHandler(BaseActionHandler):
1433
+ """Object handling image panel GUI interactions: actions, menus, ..."""
1434
+
1435
+ OBJECT_STR = _("image")
1436
+ OBJECT_STR_PLURAL = _("images")
1437
+
1438
+ def create_first_actions(self):
1439
+ """Create actions that are added to the menus in the first place"""
1440
+ # MARK: PROCESSING (1/2)
1441
+ with self.new_category(ActionCategory.PROCESSING):
1442
+ with self.new_menu(_("Geometry"), icon_name="rotate_right.svg"):
1443
+ self.action_for("fliph", context_menu_pos=-1, context_menu_sep=True)
1444
+ self.action_for("transpose", context_menu_pos=-1)
1445
+ self.action_for("flipv", context_menu_pos=-1)
1446
+ self.action_for("rotate270", context_menu_pos=-1)
1447
+ self.action_for("rotate90", context_menu_pos=-1)
1448
+ self.action_for("rotate")
1449
+ self.new_action(
1450
+ _("Distribute on a grid..."),
1451
+ triggered=self.panel.processor.distribute_on_grid,
1452
+ icon_name="distribute_on_grid.svg",
1453
+ select_condition=SelectCond.at_least_two,
1454
+ separator=True,
1455
+ )
1456
+ self.new_action(
1457
+ _("Reset image positions"),
1458
+ triggered=self.panel.processor.reset_positions,
1459
+ icon_name="reset_positions.svg",
1460
+ select_condition=SelectCond.at_least_two,
1461
+ )
1462
+
1463
+ with self.new_menu(
1464
+ _("Axis transformation"), icon_name="axis_transform.svg"
1465
+ ):
1466
+ self.action_for("set_uniform_coords")
1467
+
1468
+ super().create_first_actions()
1469
+
1470
+ # MARK: CREATE
1471
+ with self.new_category(ActionCategory.CREATE):
1472
+ for label, pclass, icon_name, separator in (
1473
+ (_("Zero"), sio.Zero2DParam, "2d-zero.svg", False),
1474
+ (
1475
+ _("Normal distribution"),
1476
+ sio.NormalDistribution2DParam,
1477
+ "2d-normal.svg",
1478
+ False,
1479
+ ),
1480
+ (
1481
+ _("Poisson distribution"),
1482
+ sio.PoissonDistribution2DParam,
1483
+ "2d-poisson.svg",
1484
+ False,
1485
+ ),
1486
+ (
1487
+ _("Uniform distribution"),
1488
+ sio.UniformDistribution2DParam,
1489
+ "2d-uniform.svg",
1490
+ False,
1491
+ ),
1492
+ (_("Gaussian"), sio.Gauss2DParam, "2d-gaussian.svg", True),
1493
+ (_("2D sinc"), sio.Sinc2DParam, "2d-sinc.svg", False),
1494
+ (_("Ring pattern"), sio.Ring2DParam, "ring.svg", True),
1495
+ (_("Ramp"), sio.Ramp2DParam, "2d-ramp.svg", False),
1496
+ (_("Checkerboard"), sio.Checkerboard2DParam, "checkerboard.svg", False),
1497
+ (
1498
+ _("Sinusoidal grating"),
1499
+ sio.SinusoidalGrating2DParam,
1500
+ "grating.svg",
1501
+ False,
1502
+ ),
1503
+ (_("Siemens star"), sio.SiemensStar2DParam, "siemens.svg", False),
1504
+ ):
1505
+ self.new_action(
1506
+ label,
1507
+ tip=_("Create new %s") % label,
1508
+ triggered=lambda pclass=pclass: self.panel.new_object(pclass()),
1509
+ icon_name=icon_name,
1510
+ select_condition=SelectCond.always,
1511
+ separator=separator,
1512
+ )
1513
+
1514
+ # MARK: ROI
1515
+ with self.new_category(ActionCategory.ROI):
1516
+ self.new_action(
1517
+ _("Create ROI grid") + "...",
1518
+ triggered=self.panel.processor.create_roi_grid,
1519
+ icon_name="roi_grid.svg",
1520
+ tip=_("Create a grid of regions of interest"),
1521
+ )
1522
+
1523
+ # MARK: OPERATION
1524
+ with self.new_category(ActionCategory.OPERATION):
1525
+ self.action_for("log10_z_plus_n")
1526
+
1527
+ # MARK: PROCESSING (2/2)
1528
+ with self.new_category(ActionCategory.PROCESSING):
1529
+ with self.new_menu(_("Frequency filters"), icon_name="noise_reduction.svg"):
1530
+ self.action_for("butterworth")
1531
+ self.action_for("gaussian_freq_filter")
1532
+ with self.new_menu(_("Thresholding"), icon_name="thresholding.svg"):
1533
+ self.action_for("threshold")
1534
+ self.action_for("threshold_isodata")
1535
+ self.action_for("threshold_li")
1536
+ self.action_for("threshold_mean")
1537
+ self.action_for("threshold_minimum")
1538
+ self.action_for("threshold_otsu")
1539
+ self.action_for("threshold_triangle")
1540
+ self.action_for("threshold_yen")
1541
+ self.new_action(
1542
+ _("All thresholding methods") + "...",
1543
+ triggered=self.panel.processor.compute_all_threshold,
1544
+ separator=True,
1545
+ tip=_("Apply all thresholding methods"),
1546
+ )
1547
+ with self.new_menu(_("Exposure"), icon_name="exposure.svg"):
1548
+ self.action_for("adjust_gamma")
1549
+ self.action_for("adjust_log")
1550
+ self.action_for("adjust_sigmoid")
1551
+ self.action_for("equalize_hist")
1552
+ self.action_for("equalize_adapthist")
1553
+ self.action_for("rescale_intensity")
1554
+ with self.new_menu(_("Restoration"), icon_name="noise_reduction.svg"):
1555
+ self.action_for("denoise_tv")
1556
+ self.action_for("denoise_bilateral")
1557
+ self.action_for("denoise_wavelet")
1558
+ self.action_for("denoise_tophat")
1559
+ self.new_action(
1560
+ _("All denoising methods") + "...",
1561
+ triggered=self.panel.processor.compute_all_denoise,
1562
+ separator=True,
1563
+ tip=_("Apply all denoising methods"),
1564
+ )
1565
+ with self.new_menu(_("Morphology"), icon_name="morphology.svg"):
1566
+ self.action_for("white_tophat")
1567
+ self.action_for("black_tophat")
1568
+ self.action_for("erosion")
1569
+ self.action_for("dilation")
1570
+ self.action_for("opening")
1571
+ self.action_for("closing")
1572
+ self.new_action(
1573
+ _("All morphological operations") + "...",
1574
+ triggered=self.panel.processor.compute_all_morphology,
1575
+ separator=True,
1576
+ tip=_("Apply all morphological operations"),
1577
+ )
1578
+ with self.new_menu(_("Edge detection"), icon_name="edge_detection.svg"):
1579
+ self.action_for("canny")
1580
+ self.action_for("farid", separator=True)
1581
+ self.action_for("farid_h")
1582
+ self.action_for("farid_v")
1583
+ self.action_for("laplace", separator=True)
1584
+ self.action_for("prewitt", separator=True)
1585
+ self.action_for("prewitt_h")
1586
+ self.action_for("prewitt_v")
1587
+ self.action_for("roberts", separator=True)
1588
+ self.action_for("scharr", separator=True)
1589
+ self.action_for("scharr_h")
1590
+ self.action_for("scharr_v")
1591
+ self.action_for("sobel", separator=True)
1592
+ self.action_for("sobel_h")
1593
+ self.action_for("sobel_v")
1594
+ self.new_action(
1595
+ _("All edge detection filters..."),
1596
+ triggered=self.panel.processor.compute_all_edges,
1597
+ separator=True,
1598
+ tip=_("Compute all edge detection filters"),
1599
+ )
1600
+ self.new_action(
1601
+ _("Erase area") + "...",
1602
+ triggered=self.panel.processor.compute_erase,
1603
+ icon_name="erase.svg",
1604
+ separator=True,
1605
+ tip=_("Erase area in the image as defined by a region of interest"),
1606
+ )
1607
+
1608
+ # MARK: ANALYSIS
1609
+ with self.new_category(ActionCategory.ANALYSIS):
1610
+ with self.new_menu(_("Intensity profiles"), icon_name="profile.svg"):
1611
+ self.new_action(
1612
+ _("Line profile..."),
1613
+ triggered=self.panel.processor.compute_line_profile,
1614
+ icon_name="profile.svg",
1615
+ tip=_("Extract horizontal or vertical profile"),
1616
+ context_menu_pos=-1,
1617
+ context_menu_sep=True,
1618
+ )
1619
+ self.new_action(
1620
+ _("Segment profile..."),
1621
+ triggered=self.panel.processor.compute_segment_profile,
1622
+ icon_name="profile_segment.svg",
1623
+ tip=_("Extract profile along a segment"),
1624
+ context_menu_pos=-1,
1625
+ )
1626
+ self.new_action(
1627
+ _("Average profile..."),
1628
+ triggered=self.panel.processor.compute_average_profile,
1629
+ icon_name="profile_average.svg",
1630
+ tip=_("Extract average horizontal or vertical profile"),
1631
+ context_menu_pos=-1,
1632
+ )
1633
+ self.new_action(
1634
+ _("Radial profile extraction..."),
1635
+ triggered=self.panel.processor.compute_radial_profile,
1636
+ icon_name="profile_radial.svg",
1637
+ tip=_("Radial profile extraction around image centroid"),
1638
+ )
1639
+ self.action_for("horizontal_projection", separator=True)
1640
+ self.action_for("vertical_projection")
1641
+ self.action_for("centroid", separator=True)
1642
+ self.action_for("enclosing_circle")
1643
+ self.new_action(
1644
+ _("2D peak detection"),
1645
+ separator=True,
1646
+ triggered=self.panel.processor.compute_peak_detection,
1647
+ tip=_("Compute automatic 2D peak detection"),
1648
+ )
1649
+ self.action_for("contour_shape")
1650
+ self.action_for("hough_circle_peaks")
1651
+ with self.new_menu(_("Blob detection")):
1652
+ self.action_for("blob_dog")
1653
+ self.action_for("blob_doh")
1654
+ self.action_for("blob_log")
1655
+ self.action_for("blob_opencv")
1656
+
1657
+ def create_last_actions(self):
1658
+ """Create actions that are added to the menus in the end"""
1659
+ # MARK: PROCESSING
1660
+ with self.new_category(ActionCategory.PROCESSING):
1661
+ self.new_action(
1662
+ _("Resize") + "...",
1663
+ triggered=self.panel.processor.compute_resize,
1664
+ icon_name="resize.svg",
1665
+ separator=True,
1666
+ )
1667
+ self.new_action(
1668
+ _("Pixel binning") + "...",
1669
+ triggered=self.panel.processor.compute_binning,
1670
+ icon_name="binning.svg",
1671
+ )
1672
+ self.action_for("resampling")
1673
+
1674
+ # MARK: VIEW
1675
+ with self.new_category(ActionCategory.VIEW):
1676
+ self.new_action(
1677
+ _("View images side-by-side") + "...",
1678
+ icon_name="new_window.svg",
1679
+ tip=_("View selected images side-by-side in a new window"),
1680
+ triggered=self.panel.view_images_side_by_side,
1681
+ select_condition=SelectCond.at_least_two,
1682
+ context_menu_pos=-1,
1683
+ )
1684
+
1685
+ super().create_last_actions()
1686
+
1687
+ # MARK: OPERATION
1688
+ with self.new_category(ActionCategory.OPERATION):
1689
+ self.action_for("flatfield", separator=True)
1690
+
1691
+ # MARK: VIEW
1692
+ with self.new_category(ActionCategory.VIEW):
1693
+ showcontrast_action = self.new_action(
1694
+ _("Show contrast panel"),
1695
+ icon_name="contrast.png",
1696
+ tip=_("Show or hide contrast adjustment panel"),
1697
+ select_condition=SelectCond.always,
1698
+ toggled=self.panel.toggle_show_contrast,
1699
+ toolbar_pos=-1,
1700
+ )
1701
+ showcontrast_action.setChecked(Conf.view.show_contrast.get(True))