datalab-platform 0.0.1.dev0__py3-none-any.whl → 1.0.0__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.0.dist-info/METADATA +121 -0
  490. datalab_platform-1.0.0.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.0.dist-info}/WHEEL +0 -0
  494. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.0.dist-info}/entry_points.txt +0 -0
  495. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.0.dist-info}/licenses/LICENSE +0 -0
  496. {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,766 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Object view
5
+ ===========
6
+
7
+ The :mod:`datalab.gui.objectview` module provides widgets to display object
8
+ (signal/image) trees.
9
+
10
+ .. note::
11
+
12
+ This module provides tree widgets to display signals, images and groups. It
13
+ is important to note that, by design, the user can only select either individual
14
+ signals/images or groups, but not both at the same time. This is an important
15
+ design choice, as it allows to simplify the user experience, and to avoid
16
+ potential confusion between the two types of selection.
17
+
18
+ Simple object tree
19
+ ------------------
20
+
21
+ .. autoclass:: SimpleObjectTree
22
+
23
+ Get object dialog
24
+ -----------------
25
+
26
+ .. autoclass:: GetObjectsDialog
27
+
28
+ Object view
29
+ -----------
30
+
31
+ .. autoclass:: ObjectView
32
+ """
33
+
34
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
35
+
36
+ from __future__ import annotations
37
+
38
+ import os
39
+ from collections.abc import Iterator
40
+ from typing import TYPE_CHECKING
41
+
42
+ from guidata.configtools import get_icon
43
+ from qtpy import QtCore as QC
44
+ from qtpy import QtGui as QG
45
+ from qtpy import QtWidgets as QW
46
+ from sigima.objects import ImageObj, SignalObj
47
+
48
+ from datalab.config import _
49
+ from datalab.objectmodel import ObjectGroup, get_short_id, get_uuid
50
+ from datalab.utils.qthelpers import block_signals
51
+
52
+ if TYPE_CHECKING:
53
+ from typing import Any
54
+
55
+ from datalab.gui.panel.base import BaseDataPanel
56
+ from datalab.objectmodel import ObjectModel
57
+
58
+
59
+ def metadata_to_html(metadata: dict[str, Any]) -> str:
60
+ """Convert metadata to human-readable string.
61
+
62
+ Returns:
63
+ HTML string
64
+ """
65
+ textlines = []
66
+ for key, value in metadata.items():
67
+ if len(textlines) > 5:
68
+ textlines.append("[...]")
69
+ break
70
+ if not key.startswith("_"):
71
+ vlines = str(value).splitlines()
72
+ if vlines:
73
+ text = f"<b>{key}:</b> {vlines[0][:100]}"
74
+ if len(vlines) > 1:
75
+ text += " [...]"
76
+ textlines.append(text)
77
+ if textlines:
78
+ ptit = _("Object metadata")
79
+ psub = _("(click on Metadata button for more details)")
80
+ prefix = f"<i><u>{ptit}:</u> {psub}</i><br>"
81
+ return f"<p style='white-space:pre'>{prefix}{'<br>'.join(textlines)}</p>"
82
+ return ""
83
+
84
+
85
+ class SimpleObjectTree(QW.QTreeWidget):
86
+ """Base object handling panel list widget, object (sig/ima) lists"""
87
+
88
+ SIG_ITEM_DOUBLECLICKED = QC.Signal(str)
89
+ SIG_CONTEXT_MENU = QC.Signal(QC.QPoint)
90
+
91
+ def __init__(self, parent: QW.QWidget, objmodel: ObjectModel) -> None:
92
+ self.objmodel: ObjectModel = objmodel
93
+ super().__init__(parent)
94
+ self.setHeaderHidden(True)
95
+ self.setColumnCount(1)
96
+ self.setAlternatingRowColors(True)
97
+ self.itemDoubleClicked.connect(self.item_double_clicked)
98
+ self.header().setSectionResizeMode(QW.QHeaderView.Interactive)
99
+ self.itemChanged.connect(lambda item: self.resizeColumnToContents(0))
100
+
101
+ def __str__(self) -> str:
102
+ """Return string representation"""
103
+ textlist = []
104
+ for tl_index in range(self.topLevelItemCount()):
105
+ tl_item = self.topLevelItem(tl_index)
106
+ textlist.append(tl_item.text(0))
107
+ for index in range(tl_item.childCount()):
108
+ textlist.append(" " + tl_item.child(index).text(0))
109
+ return os.linesep.join(textlist)
110
+
111
+ def initialize_from(self, sobjlist: SimpleObjectTree) -> None:
112
+ """Init from another SimpleObjectList, without making copies of objects"""
113
+ self.objmodel = sobjlist.objmodel
114
+ self.populate_tree()
115
+ self.set_current_item_id(sobjlist.get_current_item_id())
116
+
117
+ def iter_items(
118
+ self, item: QW.QTreeWidgetItem | None = None
119
+ ) -> Iterator[QW.QTreeWidgetItem]:
120
+ """Recursively iterate over all items"""
121
+ if item is None:
122
+ for index in range(self.topLevelItemCount()):
123
+ yield from self.iter_items(self.topLevelItem(index))
124
+ else:
125
+ yield item
126
+ for index in range(item.childCount()):
127
+ yield from self.iter_items(item.child(index))
128
+
129
+ def get_item_from_id(self, item_id) -> QW.QTreeWidgetItem:
130
+ """Return QTreeWidgetItem from id (stored in item's data)"""
131
+ for item in self.iter_items():
132
+ if item.data(0, QC.Qt.UserRole) == item_id:
133
+ return item
134
+ return None
135
+
136
+ def get_current_item_id(self, object_only: bool = False) -> str | None:
137
+ """Return current item id"""
138
+ item = self.currentItem()
139
+ if item is not None and (not object_only or item.parent() is not None):
140
+ return item.data(0, QC.Qt.UserRole)
141
+ return None
142
+
143
+ def set_current_item_id(self, uuid: str, extend: bool = False) -> None:
144
+ """Set current item by id"""
145
+ item = self.get_item_from_id(uuid)
146
+ if extend:
147
+ self.setCurrentItem(item, 0, QC.QItemSelectionModel.Select)
148
+ else:
149
+ self.setCurrentItem(item)
150
+
151
+ def get_current_group_id(self) -> str:
152
+ """Return current group ID"""
153
+ selected_item = self.currentItem()
154
+ if selected_item is None:
155
+ return None
156
+ if selected_item.parent() is None:
157
+ return selected_item.data(0, QC.Qt.UserRole)
158
+ return selected_item.parent().data(0, QC.Qt.UserRole)
159
+
160
+ def get_sel_group_items(self) -> list[QW.QTreeWidgetItem]:
161
+ """Return selected group items"""
162
+ return [item for item in self.selectedItems() if item.parent() is None]
163
+
164
+ def get_sel_group_uuids(self) -> list[str]:
165
+ """Return selected group uuids"""
166
+ return [item.data(0, QC.Qt.UserRole) for item in self.get_sel_group_items()]
167
+
168
+ def get_sel_object_items(self) -> list[QW.QTreeWidgetItem]:
169
+ """Return selected object items"""
170
+ return [item for item in self.selectedItems() if item.parent() is not None]
171
+
172
+ def get_sel_object_uuids(self, include_groups: bool = False) -> list[str]:
173
+ """Return selected objects uuids.
174
+
175
+ Args:
176
+ include_groups: If True, also return objects from selected groups.
177
+
178
+ Returns:
179
+ List of selected objects uuids.
180
+ """
181
+ sel_items = self.get_sel_object_items()
182
+ if not sel_items:
183
+ cur_item = self.currentItem()
184
+ if cur_item is not None and cur_item.parent() is not None:
185
+ sel_items = [cur_item]
186
+ uuids = [item.data(0, QC.Qt.UserRole) for item in sel_items]
187
+ if include_groups:
188
+ for group_id in self.get_sel_group_uuids():
189
+ uuids.extend(self.objmodel.get_group_object_ids(group_id))
190
+ return uuids
191
+
192
+ def get_sel_objects(
193
+ self, include_groups: bool = False
194
+ ) -> list[SignalObj | ImageObj]:
195
+ """Return selected objects.
196
+
197
+ If include_groups is True, also return objects from selected groups."""
198
+ return [self.objmodel[oid] for oid in self.get_sel_object_uuids(include_groups)]
199
+
200
+ def get_sel_groups(self) -> list[ObjectGroup]:
201
+ """Return selected groups"""
202
+ return self.objmodel.get_groups(self.get_sel_group_uuids())
203
+
204
+ @staticmethod
205
+ def __update_item(
206
+ item: QW.QTreeWidgetItem, obj: SignalObj | ImageObj | ObjectGroup
207
+ ) -> None:
208
+ """Update item"""
209
+ item.setText(0, f"{get_short_id(obj)}: {obj.title}")
210
+ if isinstance(obj, (SignalObj, ImageObj)):
211
+ item.setToolTip(0, metadata_to_html(obj.metadata))
212
+ item.setData(0, QC.Qt.UserRole, get_uuid(obj))
213
+
214
+ def populate_tree(self) -> None:
215
+ """Populate tree with objects"""
216
+ uuid = self.get_current_item_id()
217
+ with block_signals(widget=self):
218
+ self.clear()
219
+ for group in self.objmodel.get_groups():
220
+ self.add_group_item(group)
221
+ if uuid is not None:
222
+ self.set_current_item_id(uuid)
223
+
224
+ def update_tree(self) -> None:
225
+ """Update tree"""
226
+ for group in self.objmodel.get_groups():
227
+ self.__update_item(self.get_item_from_id(get_uuid(group)), group)
228
+ for obj in group:
229
+ self.__update_item(self.get_item_from_id(get_uuid(obj)), obj)
230
+
231
+ def __add_to_group_item(
232
+ self, obj: SignalObj | ImageObj, group_item: QW.QTreeWidgetItem
233
+ ) -> None:
234
+ """Add object to group item"""
235
+ item = QW.QTreeWidgetItem()
236
+ icon = "signal.svg" if isinstance(obj, SignalObj) else "image.svg"
237
+ item.setIcon(0, get_icon(icon))
238
+ self.__update_item(item, obj)
239
+ group_item.addChild(item)
240
+
241
+ def add_group_item(self, group: ObjectGroup) -> None:
242
+ """Add group item"""
243
+ group_item = QW.QTreeWidgetItem()
244
+ group_item.setIcon(0, get_icon("group.svg"))
245
+ self.__update_item(group_item, group)
246
+ self.addTopLevelItem(group_item)
247
+ group_item.setExpanded(True)
248
+ for obj in group:
249
+ self.__add_to_group_item(obj, group_item)
250
+ self.resizeColumnToContents(0)
251
+
252
+ def add_object_item(
253
+ self, obj: SignalObj | ImageObj, group_id: str, set_current: bool = True
254
+ ) -> None:
255
+ """Add item"""
256
+ group_item = self.get_item_from_id(group_id)
257
+ self.__add_to_group_item(obj, group_item)
258
+ if set_current:
259
+ self.set_current_item_id(get_uuid(obj))
260
+ self.resizeColumnToContents(0)
261
+
262
+ def update_item(self, uuid: str) -> None:
263
+ """Update item"""
264
+ obj_or_group = self.objmodel.get_object_or_group(uuid)
265
+ item = self.get_item_from_id(uuid)
266
+ self.__update_item(item, obj_or_group)
267
+
268
+ def remove_item(self, oid: str, refresh: bool = True) -> None:
269
+ """Remove item"""
270
+ item = self.get_item_from_id(oid)
271
+ if item is not None:
272
+ with block_signals(widget=self, enable=not refresh):
273
+ if item.parent() is None:
274
+ # Group item: remove from tree
275
+ self.takeTopLevelItem(self.indexOfTopLevelItem(item))
276
+ else:
277
+ # Object item: remove from parent
278
+ item.parent().removeChild(item)
279
+
280
+ def item_double_clicked(self, item: QW.QTreeWidgetItem) -> None:
281
+ """Item was double-clicked: open a pop-up plot dialog"""
282
+ if item.parent() is not None:
283
+ oid = item.data(0, QC.Qt.UserRole)
284
+ self.SIG_ITEM_DOUBLECLICKED.emit(oid)
285
+
286
+ def contextMenuEvent(self, event: QG.QContextMenuEvent) -> None: # pylint: disable=C0103
287
+ """Override Qt method"""
288
+ self.SIG_CONTEXT_MENU.emit(event.globalPos())
289
+
290
+
291
+ class GetObjectsDialog(QW.QDialog):
292
+ """Dialog box showing groups and objects (signals or images) to select one, or more.
293
+
294
+ Args:
295
+ parent: parent widget
296
+ panel: data panel
297
+ title: dialog title
298
+ comment: optional dialog comment
299
+ nb_objects: number of objects to select (default: 1)
300
+ minimum_size: minimum size (width, height)
301
+ """
302
+
303
+ def __init__(
304
+ self,
305
+ parent: QW.QWidget,
306
+ panel: BaseDataPanel,
307
+ title: str,
308
+ comment: str = "",
309
+ nb_objects: int = 1,
310
+ minimum_size: tuple[int, int] | None = None,
311
+ ) -> None:
312
+ super().__init__(parent)
313
+ self.__nb_objects = nb_objects
314
+ self.__selected_objects: list[SignalObj | ImageObj] = []
315
+ self.setWindowTitle(title)
316
+ vlayout = QW.QVBoxLayout()
317
+ self.setLayout(vlayout)
318
+
319
+ self.tree = SimpleObjectTree(parent, panel.objmodel)
320
+ self.tree.initialize_from(panel.objview)
321
+ self.tree.SIG_ITEM_DOUBLECLICKED.connect(lambda oid: self.accept())
322
+ self.tree.itemSelectionChanged.connect(self.__item_selection_changed)
323
+ if nb_objects > 1:
324
+ self.tree.setSelectionMode(QW.QAbstractItemView.ExtendedSelection)
325
+ vlayout.addWidget(self.tree)
326
+
327
+ if comment:
328
+ lbl = QW.QLabel(comment)
329
+ lbl.setWordWrap(True)
330
+ vlayout.addSpacing(10)
331
+ vlayout.addWidget(lbl)
332
+
333
+ bbox = QW.QDialogButtonBox(QW.QDialogButtonBox.Ok | QW.QDialogButtonBox.Cancel)
334
+ bbox.accepted.connect(self.accept)
335
+ bbox.rejected.connect(self.reject)
336
+ self.ok_btn = bbox.button(QW.QDialogButtonBox.Ok)
337
+ vlayout.addSpacing(10)
338
+ vlayout.addWidget(bbox)
339
+ # Update OK button state:
340
+ self.__item_selection_changed()
341
+
342
+ if minimum_size is not None:
343
+ self.setMinimumSize(*minimum_size)
344
+ else:
345
+ self.setMinimumWidth(400)
346
+
347
+ def __item_selection_changed(self) -> None:
348
+ """Item selection has changed"""
349
+ nobj = self.__nb_objects
350
+ self.__selected_objects = self.tree.get_sel_objects(include_groups=nobj > 1)
351
+ self.ok_btn.setEnabled(len(self.__selected_objects) == nobj)
352
+
353
+ def get_selected_objects(self) -> list[SignalObj | ImageObj]:
354
+ """Return selected objects"""
355
+ return self.__selected_objects
356
+
357
+
358
+ class ObjectView(SimpleObjectTree):
359
+ """Object handling panel list widget, object (sig/ima) lists"""
360
+
361
+ SIG_SELECTION_CHANGED = QC.Signal()
362
+ SIG_IMPORT_FILES = QC.Signal(list)
363
+
364
+ def __init__(self, parent: BaseDataPanel, objmodel: ObjectModel) -> None:
365
+ super().__init__(parent, objmodel)
366
+ self.setSelectionMode(QW.QAbstractItemView.ExtendedSelection)
367
+ self.setAcceptDrops(True)
368
+ self.setDragEnabled(True)
369
+ self.setDragDropMode(QW.QAbstractItemView.InternalMove)
370
+ self.itemSelectionChanged.connect(self.item_selection_changed)
371
+ self.__dragged_objects: list[QW.QListWidgetItem] = []
372
+ self.__dragged_groups: list[QW.QListWidgetItem] = []
373
+ self.__dragged_expanded_states: dict[QW.QListWidgetItem, bool] = {}
374
+
375
+ def paintEvent(self, event): # pylint: disable=C0103
376
+ """Reimplement Qt method"""
377
+ super().paintEvent(event)
378
+ if len(self.objmodel) > 0:
379
+ return
380
+
381
+ # Draw empty state message with contextual hints
382
+ painter = QG.QPainter(self.viewport())
383
+ rect = self.rect()
384
+
385
+ # Set font for main message
386
+ font = painter.font()
387
+ font.setPointSize(11)
388
+ painter.setFont(font)
389
+
390
+ # Main message
391
+ main_msg = _("Drag files here to open")
392
+ main_rect = QC.QRect(rect)
393
+ main_rect.setHeight(rect.height() // 2)
394
+ painter.drawText(main_rect, QC.Qt.AlignCenter, main_msg)
395
+
396
+ # Contextual hint
397
+ hint_rect = QC.QRect(rect)
398
+ hint_rect.setTop(rect.height() // 2)
399
+ font.setPointSize(9)
400
+ font.setItalic(True)
401
+ painter.setFont(font)
402
+ painter.setPen(QG.QColor(128, 128, 128)) # Gray color
403
+
404
+ panel: BaseDataPanel = self.parent()
405
+ if panel.PANEL_STR_ID == "signal":
406
+ hint = _(
407
+ "Working with 2D images?\n"
408
+ "Switch to the Image Panel using the tab above."
409
+ )
410
+ else:
411
+ hint = _(
412
+ "Working with 1D signals?\n"
413
+ "Switch to the Signal Panel using the tab above."
414
+ )
415
+ painter.drawText(hint_rect, QC.Qt.AlignCenter, hint)
416
+
417
+ # pylint: disable=unused-argument
418
+ def dragEnterEvent(self, event: QG.QDragEnterEvent) -> None:
419
+ """Reimplement Qt method"""
420
+ if event.mimeData().hasUrls():
421
+ event.acceptProposedAction()
422
+ else:
423
+ super().dragEnterEvent(event)
424
+ self.__dragged_groups = self.get_sel_group_items()
425
+ self.__dragged_objects = self.get_sel_object_items()
426
+ self.__dragged_expanded_states = {
427
+ item.data(0, QC.Qt.UserRole): item.isExpanded()
428
+ for item in self.__dragged_groups
429
+ }
430
+
431
+ def dragLeaveEvent(self, event: QG.QDragLeaveEvent) -> None:
432
+ """Reimplement Qt method"""
433
+ super().dragLeaveEvent(event)
434
+ self.__dragged_groups = []
435
+ self.__dragged_objects = []
436
+ self.__dragged_expanded_states = {}
437
+
438
+ # pylint: disable=unused-argument
439
+ def dragMoveEvent(self, event: QG.QDragMoveEvent) -> None:
440
+ """Reimplement Qt method"""
441
+ self.setDropIndicatorShown(True)
442
+ if event.mimeData().hasUrls():
443
+ event.setDropAction(QC.Qt.CopyAction)
444
+ event.accept()
445
+ else:
446
+ super().dragMoveEvent(event)
447
+ self.setDropIndicatorShown(self.__is_drop_allowed(event))
448
+
449
+ def __is_drop_allowed(self, event: QG.QDropEvent | QG.QDragMoveEvent) -> bool:
450
+ """Return True if drop is allowed"""
451
+ # Yes, this method has too many return statements.
452
+ # But it's still quite readable, so let's focus on other things and just disable
453
+ # the pylint warning.
454
+ #
455
+ # pylint: disable=too-many-return-statements
456
+
457
+ if event.mimeData().hasUrls():
458
+ return True
459
+ drop_pos = self.dropIndicatorPosition()
460
+ on_item = drop_pos == QW.QAbstractItemView.OnItem
461
+ above_item = drop_pos == QW.QAbstractItemView.AboveItem
462
+ below_item = drop_pos == QW.QAbstractItemView.BelowItem
463
+ on_viewport = drop_pos == QW.QAbstractItemView.OnViewport
464
+ target_item = self.itemAt(event.pos())
465
+ # If moved items are objects, refuse the drop on the viewport
466
+ if self.__dragged_objects and on_viewport:
467
+ return False
468
+ # If drop indicator is on an item, refuse the drop if the target item
469
+ # is anything but a group
470
+ if on_item and (target_item is None or target_item.parent() is not None):
471
+ return False
472
+ # If drop indicator is on an item, refuse the drop if the moved items
473
+ # are groups
474
+ if on_item and self.__dragged_groups:
475
+ return False
476
+ # If target item is None, it means that the drop position is
477
+ # outside of the tree. In this case, we accept the drop and move
478
+ # the objects to the end of the list.
479
+ if target_item is None or on_viewport:
480
+ return True
481
+ # If moved items are groups, refuse the drop if the target item is
482
+ # not a group
483
+ if self.__dragged_groups and target_item.parent() is not None:
484
+ return False
485
+ # If moved items are groups, refuse the drop if the target item is
486
+ # a group but the target position is below the target instead of above
487
+ if self.__dragged_groups and below_item:
488
+ return False
489
+ # If moved items are objects, refuse the drop if the target item is
490
+ # a group and the drop indicator is anything but on the target item
491
+ if self.__dragged_objects and target_item.parent() is None and not on_item:
492
+ return False
493
+ # If moved items are objects, refuse the drop if the target item is
494
+ # the first group item and the drop position is above the target item
495
+ if (
496
+ self.__dragged_objects
497
+ and target_item.parent() is None
498
+ and self.indexFromItem(target_item).row() == 0
499
+ and above_item
500
+ ):
501
+ return False
502
+ return True
503
+
504
+ def get_all_group_uuids(self) -> list[str]:
505
+ """Return all group uuids, in a list ordered by group position in the tree"""
506
+ return [
507
+ self.topLevelItem(index).data(0, QC.Qt.UserRole)
508
+ for index in range(self.topLevelItemCount())
509
+ ]
510
+
511
+ def get_all_object_uuids(self) -> dict[str, list[str]]:
512
+ """Return all object uuids, in a dictionary that maps group uuids to the
513
+ list of object uuids in each group, in the correct order"""
514
+ return {
515
+ group_id: [
516
+ self.topLevelItem(index).child(idx).data(0, QC.Qt.UserRole)
517
+ for idx in range(self.topLevelItem(index).childCount())
518
+ ]
519
+ for index, group_id in enumerate(self.get_all_group_uuids())
520
+ }
521
+
522
+ def dropEvent(self, event: QG.QDropEvent) -> None: # pylint: disable=C0103
523
+ """Reimplement Qt method"""
524
+ if event.mimeData().hasUrls():
525
+ fnames = [url.toLocalFile() for url in event.mimeData().urls()]
526
+ self.SIG_IMPORT_FILES.emit(fnames)
527
+ event.setDropAction(QC.Qt.CopyAction)
528
+ event.accept()
529
+ else:
530
+ is_allowed = self.__is_drop_allowed(event)
531
+ if not is_allowed:
532
+ event.ignore()
533
+ else:
534
+ drop_pos = self.dropIndicatorPosition()
535
+ on_viewport = drop_pos == QW.QAbstractItemView.OnViewport
536
+ target_item = self.itemAt(event.pos())
537
+ # If target item is None, it means that the drop position is
538
+ # outside of the tree. In this case, we accept the drop and move
539
+ # the objects to the end of the list.
540
+ if target_item is None or on_viewport:
541
+ # If moved items are groups, move them to the end of the list
542
+ if self.__dragged_groups:
543
+ for item in self.__dragged_groups:
544
+ self.takeTopLevelItem(self.indexOfTopLevelItem(item))
545
+ self.addTopLevelItem(item)
546
+ # If moved items are objects, move them to the last group
547
+ if self.__dragged_objects:
548
+ lastgrp_item = self.topLevelItem(self.topLevelItemCount() - 1)
549
+ for item in self.__dragged_objects:
550
+ item.parent().removeChild(item)
551
+ lastgrp_item.addChild(item)
552
+
553
+ event.accept()
554
+ else:
555
+ super().dropEvent(event)
556
+
557
+ if event.isAccepted():
558
+ # Ok, the drop was accepted, so we need to update the model accordingly
559
+ # (at this stage, the model has not been updated yet but the tree has
560
+ # been updated already, e.g. by the super().dropEvent(event) calls).
561
+ # Thus, we have to loop over all tree items and reproduce the tree
562
+ # structure in the model, by reordering the groups and objects.
563
+ # We have two cases to consider (that mutually exclude each other):
564
+ # 1. Groups are moved: we need to reorder the groups in the model
565
+ # 2. Objects are moved: we need to reorder the objects in all groups
566
+ # in the model
567
+ # Let's start with case 1:
568
+ if self.__dragged_groups:
569
+ # First, we need to get the list of all groups in the model
570
+ # (in the correct order)
571
+ gids = self.get_all_group_uuids()
572
+ # Then, we need to reorder the groups in the model
573
+ self.objmodel.reorder_groups(gids)
574
+ # Now, let's consider case 2:
575
+ if self.__dragged_objects:
576
+ # First, we need to get a dictionary that maps group ids to
577
+ # the list of objects in each group (in the correct order)
578
+ oids = self.get_all_object_uuids()
579
+ # Then, we need to reorder the objects in all groups in the model
580
+ self.objmodel.reorder_objects(oids)
581
+ # Finally, we need to update tree
582
+ self.update_tree()
583
+ # Restore expanded states of moved groups
584
+ for item in self.__dragged_groups:
585
+ item.setExpanded(
586
+ self.__dragged_expanded_states[item.data(0, QC.Qt.UserRole)]
587
+ )
588
+ # Restore selection, either of groups or objects
589
+ sel_items = self.__dragged_groups or self.__dragged_objects
590
+ extend = len(sel_items) > 1
591
+ for item in sel_items:
592
+ if extend:
593
+ self.setCurrentItem(item, 0, QC.QItemSelectionModel.Select)
594
+ else:
595
+ self.setCurrentItem(item)
596
+
597
+ def get_current_object(self) -> SignalObj | ImageObj | None:
598
+ """Return current object"""
599
+ oid = self.get_current_item_id(object_only=True)
600
+ if oid is not None:
601
+ return self.objmodel[oid]
602
+ return None
603
+
604
+ def set_current_object(self, obj: SignalObj | ImageObj) -> None:
605
+ """Set current object"""
606
+ self.set_current_item_id(get_uuid(obj))
607
+
608
+ def item_selection_changed(self) -> None:
609
+ """Refreshing the selection of objects and groups, emitting the
610
+ SIG_SELECTION_CHANGED signal which triggers the update of the
611
+ object properties panel, the plot items and the actions of the
612
+ toolbar and menu bar.
613
+
614
+ This method is called when the user selects or deselects items in the tree.
615
+ It is also called when the user clicks on an item that was already selected.
616
+
617
+ This method emits the SIG_SELECTION_CHANGED signal.
618
+ """
619
+ # ==> This is a very important design choice <==
620
+ # When a group is selected, all individual objects are deselected, even if
621
+ # they belong to other groups. This is intended to simplify the user experience.
622
+ # In other words, the user may either select groups or individual objects, but
623
+ # not both at the same time.
624
+ sel_groups = self.get_sel_group_items()
625
+ if sel_groups:
626
+ for item in self.get_sel_object_items():
627
+ item.setSelected(False)
628
+ if self.currentItem().parent() is not None:
629
+ self.setCurrentItem(sel_groups[0])
630
+
631
+ self.SIG_SELECTION_CHANGED.emit()
632
+
633
+ def select_objects(
634
+ self,
635
+ selection: list[SignalObj | ImageObj | int | str],
636
+ ) -> None:
637
+ """Select multiple objects
638
+
639
+ Args:
640
+ selection: list of objects, object numbers (1 to N) or object uuids
641
+ """
642
+ if all(isinstance(obj, int) for obj in selection):
643
+ all_uuids = self.objmodel.get_object_ids()
644
+ uuids = [all_uuids[num - 1] for num in selection]
645
+ elif all(isinstance(obj, str) for obj in selection):
646
+ uuids = selection
647
+ else:
648
+ assert all(isinstance(obj, (SignalObj, ImageObj)) for obj in selection)
649
+ uuids = [get_uuid(obj) for obj in selection]
650
+ for idx, uuid in enumerate(uuids):
651
+ self.set_current_item_id(uuid, extend=idx > 0)
652
+
653
+ def select_groups(
654
+ self, groups: list[ObjectGroup | int | str] | None = None
655
+ ) -> None:
656
+ """Select multiple groups
657
+
658
+ Args:
659
+ groups: list of groups, group numbers (1 to N), group names or None
660
+ (select all groups). Defaults to None.
661
+ """
662
+ if groups is None:
663
+ groups = self.objmodel.get_groups()
664
+ elif all(isinstance(group, int) for group in groups):
665
+ groups = [self.objmodel.get_groups()[grp_num - 1] for grp_num in groups]
666
+ elif all(isinstance(group, str) for group in groups):
667
+ groups = self.objmodel.get_groups(groups)
668
+ assert all(isinstance(group, ObjectGroup) for group in groups)
669
+ for idx, group in enumerate(groups):
670
+ self.set_current_item_id(get_uuid(group), extend=idx > 0)
671
+
672
+ def __reorder_model(self) -> None:
673
+ """Reorder model"""
674
+ self.objmodel.reorder_groups(self.get_all_group_uuids())
675
+ self.objmodel.reorder_objects(self.get_all_object_uuids())
676
+ self.update_tree()
677
+
678
+ def move_up(self):
679
+ """Move selected objects/groups up"""
680
+ sel_objs = self.get_sel_object_items()
681
+ sel_groups = self.get_sel_group_items()
682
+ # Sort selected objects/groups by their position in the tree
683
+ sel_objs.sort(key=lambda item: self.indexFromItem(item).row())
684
+ sel_groups.sort(key=lambda item: self.indexFromItem(item).row())
685
+ if not sel_objs and not sel_groups:
686
+ return
687
+ if sel_objs:
688
+ for item in sel_objs:
689
+ parent = item.parent()
690
+ idx_item = parent.indexOfChild(item)
691
+ idx_parent = self.indexOfTopLevelItem(parent)
692
+ if idx_item > 0:
693
+ parent.takeChild(idx_item)
694
+ parent.insertChild(idx_item - 1, item)
695
+ elif idx_parent > 0:
696
+ # If the object is the first child of its parent, we check if
697
+ # there is a group above the parent. If so, we move the object
698
+ # to the end of the group above.
699
+ parent.takeChild(idx_item)
700
+ self.topLevelItem(idx_parent - 1).addChild(item)
701
+ else:
702
+ return
703
+ else:
704
+ # Store groups expanded state
705
+ expstates = {
706
+ item.data(0, QC.Qt.UserRole): item.isExpanded() for item in sel_groups
707
+ }
708
+ for item in sel_groups:
709
+ idx_item = self.indexOfTopLevelItem(item)
710
+ if idx_item > 0:
711
+ self.takeTopLevelItem(idx_item)
712
+ self.insertTopLevelItem(idx_item - 1, item)
713
+ else:
714
+ return
715
+ # Restore groups expanded state
716
+ for item in sel_groups:
717
+ item.setExpanded(expstates[item.data(0, QC.Qt.UserRole)])
718
+ self.__reorder_model()
719
+ # Restore selection
720
+ for item in sel_objs + sel_groups:
721
+ item.setSelected(True)
722
+
723
+ def move_down(self):
724
+ """Move selected objects/groups down"""
725
+ sel_objs = self.get_sel_object_items()
726
+ sel_groups = self.get_sel_group_items()
727
+ # Sort selected objects/groups by their position in the tree
728
+ sel_objs.sort(key=lambda item: self.indexFromItem(item).row(), reverse=True)
729
+ sel_groups.sort(key=lambda item: self.indexFromItem(item).row(), reverse=True)
730
+ if not sel_objs and not sel_groups:
731
+ return
732
+ if sel_objs:
733
+ for item in sel_objs:
734
+ parent = item.parent()
735
+ idx_item = parent.indexOfChild(item)
736
+ idx_parent = self.indexOfTopLevelItem(parent)
737
+ if idx_item < parent.childCount() - 1:
738
+ parent.takeChild(idx_item)
739
+ parent.insertChild(idx_item + 1, item)
740
+ elif idx_parent < self.topLevelItemCount() - 1:
741
+ # If the object is the last child of its parent, we check if
742
+ # there is a group below the parent. If so, we move the object
743
+ # to the beginning of the group below.
744
+ parent.takeChild(idx_item)
745
+ self.topLevelItem(idx_parent + 1).insertChild(0, item)
746
+ else:
747
+ return
748
+ else:
749
+ # Store groups expanded state
750
+ expstates = {
751
+ item.data(0, QC.Qt.UserRole): item.isExpanded() for item in sel_groups
752
+ }
753
+ for item in sel_groups:
754
+ idx_item = self.indexOfTopLevelItem(item)
755
+ if idx_item < self.topLevelItemCount() - 1:
756
+ self.takeTopLevelItem(idx_item)
757
+ self.insertTopLevelItem(idx_item + 1, item)
758
+ else:
759
+ return
760
+ # Restore groups expanded state
761
+ for item in sel_groups:
762
+ item.setExpanded(expstates[item.data(0, QC.Qt.UserRole)])
763
+ self.__reorder_model()
764
+ # Restore selection
765
+ for item in sel_objs + sel_groups:
766
+ item.setSelected(True)