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,1109 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Interactive re-processing feature tests
5
+
6
+ Tests the end-to-end interactive processing workflow where users can
7
+ modify processing parameters and re-apply them to regenerate the result.
8
+
9
+ This includes:
10
+ - Parameter modification and re-application
11
+ - Parameter serialization/deserialization
12
+ - Processing metadata storage for 1-to-1 operations
13
+ - Handling of operations with and without parameters
14
+
15
+ Note: metadata_all_patterns_test.py verifies metadata storage for all
16
+ processing patterns (1-to-1, 2-to-1, n-to-1).
17
+ """
18
+
19
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
20
+
21
+ from __future__ import annotations
22
+
23
+ import numpy as np
24
+ from guidata.dataset import json_to_dataset
25
+ from guidata.qthelpers import qt_app_context, qt_wait
26
+ from sigima.objects import Gauss2DParam, GaussParam, create_image_roi
27
+ from sigima.params import (
28
+ BinningParam,
29
+ ConstantParam,
30
+ GaussianParam,
31
+ MovingAverageParam,
32
+ SignalsToImageParam,
33
+ )
34
+ from sigima.proc.image import RadialProfileParam
35
+
36
+ from datalab.gui.newobject import CREATION_PARAMETERS_OPTION
37
+ from datalab.gui.processor.base import PROCESSING_PARAMETERS_OPTION
38
+ from datalab.objectmodel import get_uuid
39
+ from datalab.tests import datalab_test_app_context
40
+
41
+
42
+ def test_signal_interactive_processing():
43
+ """Test interactive processing for signals"""
44
+ with qt_app_context():
45
+ with datalab_test_app_context() as win:
46
+ panel = win.signalpanel
47
+ processor = panel.processor
48
+
49
+ # Create a test signal
50
+ panel.new_object()
51
+ signal = panel.objview.get_current_object()
52
+ assert signal is not None
53
+
54
+ # Apply a Gaussian filter (which has parameters)
55
+ param = GaussianParam.create(sigma=2.0)
56
+ processor.compute_1_to_1(
57
+ processor.get_feature("gaussian_filter").function,
58
+ param=param,
59
+ title="Gaussian filter",
60
+ )
61
+
62
+ # Get the filtered signal
63
+ filtered_sig = panel.objview.get_current_object()
64
+ assert filtered_sig is not None
65
+ assert filtered_sig != signal
66
+
67
+ # Check that processing metadata was stored
68
+ assert PROCESSING_PARAMETERS_OPTION in filtered_sig.get_metadata_options()
69
+ option_dict = filtered_sig.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
70
+
71
+ # Verify metadata content
72
+ assert option_dict["source_uuid"] == get_uuid(signal)
73
+ assert option_dict["func_name"] == "gaussian_filter"
74
+
75
+ # Verify the parameter can be deserialized
76
+ stored_param = json_to_dataset(option_dict["param_json"])
77
+ assert stored_param.sigma == 2.0
78
+
79
+
80
+ def test_image_interactive_processing():
81
+ """Test interactive processing for images"""
82
+ with qt_app_context():
83
+ with datalab_test_app_context() as win:
84
+ panel = win.imagepanel
85
+ processor = panel.processor
86
+
87
+ # Create a test image
88
+ panel.new_object()
89
+ image = panel.objview.get_current_object()
90
+ assert image is not None
91
+
92
+ # Apply a moving average filter (which has parameters)
93
+ param = MovingAverageParam.create(n=5)
94
+ processor.compute_1_to_1(
95
+ processor.get_feature("moving_average").function,
96
+ param=param,
97
+ title="Moving average",
98
+ )
99
+
100
+ # Get the filtered image
101
+ filtered_ima = panel.objview.get_current_object()
102
+ assert filtered_ima is not None
103
+ assert filtered_ima != image
104
+
105
+ # Check that processing metadata was stored
106
+ assert PROCESSING_PARAMETERS_OPTION in filtered_ima.get_metadata_options()
107
+ option_dict = filtered_ima.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
108
+
109
+ # Verify metadata content
110
+ assert option_dict["source_uuid"] == get_uuid(image)
111
+ assert option_dict["func_name"] == "moving_average"
112
+
113
+ # Verify the parameter can be deserialized
114
+ stored_param = json_to_dataset(option_dict["param_json"])
115
+ assert stored_param.n == 5
116
+
117
+
118
+ def test_processing_without_parameters():
119
+ """Test that processing without parameters doesn't store metadata"""
120
+ with qt_app_context():
121
+ with datalab_test_app_context() as win:
122
+ panel = win.signalpanel
123
+ processor = panel.processor
124
+
125
+ # Create a test signal
126
+ panel.new_object()
127
+ signal = panel.objview.get_current_object()
128
+ assert signal is not None
129
+
130
+ # Apply absolute value (which has NO parameters)
131
+ processor.compute_1_to_1(
132
+ processor.get_feature("absolute").function,
133
+ title="Absolute value",
134
+ )
135
+
136
+ # Get the result signal
137
+ result_signal = panel.objview.get_current_object()
138
+ assert result_signal is not None
139
+ assert result_signal != signal
140
+
141
+ # Check that processing metadata was NOT stored
142
+ assert PROCESSING_PARAMETERS_OPTION in result_signal.get_metadata_options()
143
+ option_dict = result_signal.get_metadata_option(
144
+ PROCESSING_PARAMETERS_OPTION
145
+ )
146
+ assert "param_json" not in option_dict
147
+
148
+
149
+ def test_recompute():
150
+ """Test recompute feature for signals"""
151
+ with qt_app_context():
152
+ with datalab_test_app_context() as win:
153
+ panel = win.signalpanel
154
+ processor = panel.processor
155
+
156
+ # Create a test signal
157
+ panel.new_object()
158
+ signal = panel.objview.get_current_object()
159
+ signal_uuid = get_uuid(signal)
160
+
161
+ # Apply a Gaussian filter with initial parameters
162
+ param = GaussianParam.create(sigma=2.0)
163
+ processor.run_feature("gaussian_filter", param=param)
164
+ filtered_sig = panel.objview.get_current_object()
165
+ original_data = filtered_sig.y.copy()
166
+
167
+ # Recompute with different input signal data
168
+ constant = 1.23098765
169
+ signal.y += constant
170
+ panel.recompute_processing()
171
+
172
+ assert np.allclose(filtered_sig.y, original_data + constant)
173
+
174
+ # Verify metadata is correct
175
+ assert PROCESSING_PARAMETERS_OPTION in filtered_sig.get_metadata_options()
176
+ option_dict = filtered_sig.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
177
+ assert option_dict["source_uuid"] == signal_uuid
178
+ assert option_dict["func_name"] == "gaussian_filter"
179
+
180
+
181
+ def test_apply_creation_parameters_signal():
182
+ """Test apply_creation_parameters for signals"""
183
+ with qt_app_context():
184
+ with datalab_test_app_context() as win:
185
+ panel = win.signalpanel
186
+ objprop = panel.objprop
187
+
188
+ # Create a signal with specific parameters
189
+ param = GaussParam.create(mu=250.0, sigma=20.0, a=100.0, y0=0.0, size=500)
190
+ panel.new_object(param=param, edit=False)
191
+ signal = panel.objview.get_current_object()
192
+ assert signal is not None
193
+ signal_uuid = get_uuid(signal)
194
+
195
+ # Verify the Creation tab was set up
196
+ assert objprop.creation_param_editor is not None
197
+ original_data = signal.y.copy()
198
+
199
+ # Modify the creation parameters in the editor
200
+ editor = objprop.creation_param_editor
201
+ # Change the Gaussian parameters to get a predictable result
202
+ editor.dataset.a = 200.0 # Double the amplitude from 100.0 to 200.0
203
+
204
+ # Apply the new creation parameters
205
+ objprop.apply_creation_parameters()
206
+
207
+ # Verify the signal was updated in-place (same UUID)
208
+ updated_signal = panel.objview.get_current_object()
209
+ assert get_uuid(updated_signal) == signal_uuid
210
+
211
+ # Get the updated creation parameters from metadata
212
+ creation_param_json = updated_signal.get_metadata_option(
213
+ CREATION_PARAMETERS_OPTION
214
+ )
215
+ updated_param = json_to_dataset(creation_param_json)
216
+
217
+ # Verify the parameter was actually updated in metadata
218
+ assert updated_param.a == 200.0
219
+
220
+ # Verify the data has changed
221
+ # Since we're working with very small Gaussian values,
222
+ # just verify they're different
223
+ assert not np.array_equal(updated_signal.y, original_data)
224
+
225
+
226
+ def test_apply_creation_parameters_image():
227
+ """Test apply_creation_parameters for images"""
228
+ with qt_app_context():
229
+ with datalab_test_app_context() as win:
230
+ panel = win.imagepanel
231
+ objprop = panel.objprop
232
+
233
+ # Create an image with specific parameters (using a derived class
234
+ # of NewImageParam) to ensure creation parameters are stored in metadata
235
+ param = Gauss2DParam.create(x0=50.0, y0=50.0, sigma=10.0, a=100.0)
236
+ panel.new_object(param=param, edit=False)
237
+ image = panel.objview.get_current_object()
238
+ assert image is not None
239
+
240
+ # Verify the Creation tab was set up
241
+ assert objprop.creation_param_editor is not None
242
+ original_data = image.data.copy()
243
+
244
+ # Modify the parameters in the editor to create a visibly different image
245
+ editor = objprop.creation_param_editor
246
+ # Change the amplitude to make it clearly different
247
+ editor.dataset.a = 200.0 # Double the amplitude from 100.0 to 200.0
248
+
249
+ # Apply the new parameters
250
+ objprop.apply_creation_parameters()
251
+
252
+ # Verify the image was updated in-place (same UUID)
253
+ updated_image = panel.objview.get_current_object()
254
+ assert get_uuid(updated_image) == get_uuid(image)
255
+
256
+ # Verify the data has changed (amplitude doubled)
257
+ assert not np.array_equal(updated_image.data, original_data)
258
+
259
+
260
+ def test_no_duplicate_creation_tabs():
261
+ """Test that applying creation parameters multiple times doesn't create
262
+ duplicate tabs.
263
+
264
+ This test verifies the fix for the bug where clicking "Apply" in the
265
+ Creation tab would create a new Creation tab instead of reusing the
266
+ existing one. It also verifies that the Creation tab remains current
267
+ after applying changes.
268
+ """
269
+ with qt_app_context():
270
+ with datalab_test_app_context() as win:
271
+ panel = win.imagepanel
272
+ objprop = panel.objprop
273
+
274
+ # Create an image with creation parameters
275
+ param = Gauss2DParam.create(x0=50.0, y0=50.0, sigma=10.0, a=100.0)
276
+ panel.new_object(param=param, edit=False)
277
+ image = panel.objview.get_current_object()
278
+ assert image is not None
279
+
280
+ # Verify Creation tab was set up
281
+ assert objprop.creation_param_editor is not None
282
+ assert objprop.creation_scroll is not None
283
+
284
+ # Count how many Creation tabs exist initially using the widget reference
285
+ initial_index = objprop.tabwidget.indexOf(objprop.creation_scroll)
286
+ assert initial_index >= 0, "Creation tab should be present"
287
+
288
+ # Count tabs by checking if they reference the same scroll widget
289
+ initial_count = sum(
290
+ 1
291
+ for i in range(objprop.tabwidget.count())
292
+ if objprop.tabwidget.widget(i) is objprop.creation_scroll
293
+ )
294
+ assert initial_count == 1, "Should have exactly one Creation tab initially"
295
+
296
+ # Apply creation parameters multiple times
297
+ editor = objprop.creation_param_editor
298
+ for amplitude in [150.0, 200.0, 250.0]:
299
+ editor.dataset.a = amplitude
300
+ objprop.apply_creation_parameters()
301
+
302
+ # Wait for the deferred setup_creation_tab to complete
303
+ qt_wait(0.1)
304
+
305
+ # Verify that creation_scroll reference still exists
306
+ assert objprop.creation_scroll is not None
307
+
308
+ # Count Creation tabs again - should still be just one
309
+ creation_count = sum(
310
+ 1
311
+ for i in range(objprop.tabwidget.count())
312
+ if objprop.tabwidget.widget(i) is objprop.creation_scroll
313
+ )
314
+ assert creation_count == 1, (
315
+ f"Should still have exactly one Creation tab after "
316
+ f"applying amplitude={amplitude}"
317
+ )
318
+
319
+ # Verify that the Creation tab is the current tab
320
+ assert objprop.tabwidget.currentWidget() is objprop.creation_scroll, (
321
+ f"Creation tab should remain current after "
322
+ f"applying amplitude={amplitude}"
323
+ )
324
+
325
+
326
+ def test_no_creation_parameters_for_base_classes():
327
+ """Test that creation parameters are NOT stored for base classes
328
+
329
+ This test verifies the behavior introduced by the patch that only stores
330
+ creation parameters for derived classes of NewSignalParam/NewImageParam,
331
+ not for the base classes themselves.
332
+ """
333
+ with qt_app_context():
334
+ with datalab_test_app_context() as win:
335
+ # Test with signals
336
+ signal_panel = win.signalpanel
337
+ signal_objprop = signal_panel.objprop
338
+
339
+ # Create a signal using default new_object() (uses base NewSignalParam)
340
+ signal_panel.new_object(edit=False)
341
+ signal = signal_panel.objview.get_current_object()
342
+ assert signal is not None
343
+
344
+ # Verify the Creation tab was NOT set up (no creation parameters stored)
345
+ assert signal_objprop.creation_param_editor is None
346
+
347
+ # Verify that CREATION_PARAMETERS_OPTION is not in metadata
348
+ assert CREATION_PARAMETERS_OPTION not in signal.get_metadata_options()
349
+
350
+ # Test with images
351
+ image_panel = win.imagepanel
352
+ image_objprop = image_panel.objprop
353
+
354
+ # Create an image using default new_object() (uses base NewImageParam)
355
+ image_panel.new_object(edit=False)
356
+ image = image_panel.objview.get_current_object()
357
+ assert image is not None
358
+
359
+ # Verify the Creation tab was NOT set up (no creation parameters stored)
360
+ assert image_objprop.creation_param_editor is None
361
+
362
+ # Verify that CREATION_PARAMETERS_OPTION is not in metadata
363
+ assert CREATION_PARAMETERS_OPTION not in image.get_metadata_options()
364
+
365
+
366
+ def test_apply_processing_parameters_signal():
367
+ """Test apply_processing_parameters for signals"""
368
+ with qt_app_context():
369
+ with datalab_test_app_context() as win:
370
+ panel = win.signalpanel
371
+ processor = panel.processor
372
+ objprop = panel.objprop
373
+
374
+ # Create a test signal with some structure
375
+ param = GaussParam.create(mu=250.0, sigma=20.0, a=100.0, y0=10.0, size=500)
376
+ panel.new_object(param=param, edit=False)
377
+ signal = panel.objview.get_current_object()
378
+ assert signal is not None
379
+ signal_uuid = get_uuid(signal)
380
+ original_signal_data = signal.y.copy()
381
+
382
+ # Apply addition_constant with initial value
383
+ v0 = 5.0
384
+ processor.run_feature("addition_constant", ConstantParam.create(value=v0))
385
+
386
+ # Get the processed signal
387
+ processed_sig = panel.objview.get_current_object()
388
+ assert processed_sig is not None
389
+ processed_uuid = get_uuid(processed_sig)
390
+
391
+ # Verify initial constant was applied: data should be original + 5.0
392
+ assert np.allclose(processed_sig.y, original_signal_data + v0)
393
+
394
+ # Select the processed signal to trigger setup_processing_tab
395
+ panel.objview.set_current_object(processed_sig)
396
+
397
+ # Verify the Processing tab was set up
398
+ assert objprop.processing_param_editor is not None
399
+
400
+ # Modify the processing parameters
401
+ editor = objprop.processing_param_editor
402
+ # Change constant from 5.0 to 15.0
403
+ editor.dataset.value = v1 = 15.0
404
+
405
+ # Apply the new processing parameters
406
+ report = objprop.apply_processing_parameters()
407
+
408
+ # Verify the operation succeeded
409
+ assert report.success, f"Reprocessing failed: {report.message}"
410
+ assert report.obj_uuid == processed_uuid
411
+
412
+ # Verify the object UUID didn't change (in-place update)
413
+ assert get_uuid(processed_sig) == processed_uuid
414
+
415
+ # Verify the new constant was applied: data should now be original + 15.0
416
+ assert np.allclose(processed_sig.y, original_signal_data + v1)
417
+
418
+ # Verify metadata still points to the same source
419
+ pp_dict = processed_sig.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
420
+ assert pp_dict["source_uuid"] == signal_uuid
421
+ assert pp_dict["func_name"] == "addition_constant"
422
+
423
+ # Verify the parameter was updated
424
+ stored_param = json_to_dataset(pp_dict["param_json"])
425
+ assert stored_param.value == v1
426
+
427
+
428
+ def test_apply_processing_parameters_image():
429
+ """Test apply_processing_parameters for images"""
430
+ with qt_app_context():
431
+ with datalab_test_app_context() as win:
432
+ panel = win.imagepanel
433
+ processor = panel.processor
434
+ objprop = panel.objprop
435
+
436
+ # Create a default test image
437
+ panel.new_object(edit=False)
438
+ image = panel.objview.get_current_object()
439
+ assert image is not None
440
+ image_uuid = get_uuid(image)
441
+ original_image_data = image.data.copy()
442
+
443
+ # Apply addition_constant with initial value
444
+ v0 = 7.0
445
+ processor.run_feature("addition_constant", ConstantParam.create(value=v0))
446
+
447
+ # Get the processed image
448
+ processed_ima = panel.objview.get_current_object()
449
+ assert processed_ima is not None
450
+ processed_uuid = get_uuid(processed_ima)
451
+
452
+ # Verify initial constant was applied: data should be original + 7.0
453
+ assert np.allclose(processed_ima.data, original_image_data + v0)
454
+
455
+ # Select the processed image to trigger setup_processing_tab
456
+ panel.objview.set_current_object(processed_ima)
457
+
458
+ # Verify the Processing tab was set up
459
+ assert objprop.processing_param_editor is not None
460
+
461
+ # Modify the processing parameters
462
+ editor = objprop.processing_param_editor
463
+ # Change constant from 7.0 to 20.0
464
+ editor.dataset.value = v1 = 20.0
465
+
466
+ # Apply the new processing parameters
467
+ report = objprop.apply_processing_parameters()
468
+
469
+ # Verify the operation succeeded
470
+ assert report.success, f"Reprocessing failed: {report.message}"
471
+ assert report.obj_uuid == processed_uuid
472
+
473
+ # Verify the object UUID didn't change (in-place update)
474
+ assert get_uuid(processed_ima) == processed_uuid
475
+
476
+ # Verify the new constant was applied: data should now be original + 20.0
477
+ assert np.allclose(processed_ima.data, original_image_data + v1)
478
+
479
+ # Verify metadata still points to the same source
480
+ pp_dict = processed_ima.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
481
+ assert pp_dict["source_uuid"] == image_uuid
482
+ assert pp_dict["func_name"] == "addition_constant"
483
+
484
+ # Verify the parameter was updated
485
+ stored_param = json_to_dataset(pp_dict["param_json"])
486
+ assert stored_param.value == v1
487
+
488
+
489
+ def test_no_duplicate_processing_tabs():
490
+ """Test that applying processing parameters multiple times doesn't create
491
+ duplicate tabs.
492
+
493
+ This test verifies the fix for the bug where clicking "Apply" in the
494
+ Processing tab would create a new Processing tab instead of reusing the
495
+ existing one. It also verifies that the Processing tab remains current
496
+ after applying changes.
497
+ """
498
+ with qt_app_context():
499
+ with datalab_test_app_context() as win:
500
+ panel = win.imagepanel
501
+ processor = panel.processor
502
+ objprop = panel.objprop
503
+
504
+ # Create a default test image
505
+ panel.new_object(edit=False)
506
+ image = panel.objview.get_current_object()
507
+ assert image is not None
508
+
509
+ # Apply addition_constant with initial value
510
+ v0 = 7.0
511
+ processor.run_feature("addition_constant", ConstantParam.create(value=v0))
512
+
513
+ # Get the processed image
514
+ processed_ima = panel.objview.get_current_object()
515
+ assert processed_ima is not None
516
+
517
+ # Select the processed image to trigger setup_processing_tab
518
+ panel.objview.set_current_object(processed_ima)
519
+
520
+ # Verify Processing tab was set up
521
+ assert objprop.processing_param_editor is not None
522
+ assert objprop.processing_scroll is not None
523
+
524
+ # Count how many Processing tabs exist initially
525
+ initial_index = objprop.tabwidget.indexOf(objprop.processing_scroll)
526
+ assert initial_index >= 0, "Processing tab should be present"
527
+
528
+ # Count tabs by checking if they reference the same scroll widget
529
+ initial_count = sum(
530
+ 1
531
+ for i in range(objprop.tabwidget.count())
532
+ if objprop.tabwidget.widget(i) is objprop.processing_scroll
533
+ )
534
+ assert initial_count == 1, (
535
+ "Should have exactly one Processing tab initially"
536
+ )
537
+
538
+ # Apply processing parameters multiple times
539
+ editor = objprop.processing_param_editor
540
+ for value in [10.0, 15.0, 20.0]:
541
+ editor.dataset.value = value
542
+ report = objprop.apply_processing_parameters()
543
+ assert report.success
544
+
545
+ # Wait for the deferred setup_processing_tab to complete
546
+ qt_wait(0.1)
547
+
548
+ # Verify that processing_scroll reference still exists
549
+ assert objprop.processing_scroll is not None
550
+
551
+ # Count Processing tabs again - should still be just one
552
+ processing_count = sum(
553
+ 1
554
+ for i in range(objprop.tabwidget.count())
555
+ if objprop.tabwidget.widget(i) is objprop.processing_scroll
556
+ )
557
+ assert processing_count == 1, (
558
+ f"Should still have exactly one Processing tab after "
559
+ f"applying value={value}"
560
+ )
561
+
562
+ # Verify that the Processing tab is the current tab
563
+ assert objprop.tabwidget.currentWidget() is objprop.processing_scroll, (
564
+ f"Processing tab should remain current after applying value={value}"
565
+ )
566
+
567
+
568
+ def test_apply_processing_parameters_missing_source():
569
+ """Test apply_processing_parameters when source object is missing"""
570
+ with qt_app_context():
571
+ with datalab_test_app_context() as win:
572
+ panel = win.signalpanel
573
+ processor = panel.processor
574
+ objprop = panel.objprop
575
+
576
+ # Create a test signal with actual data
577
+ param = GaussParam.create(mu=250.0, sigma=20.0, a=100.0, y0=10.0, size=500)
578
+ panel.new_object(param=param, edit=False)
579
+ signal = panel.objview.get_current_object()
580
+
581
+ # Apply a Gaussian filter
582
+ filter_param = GaussianParam.create(sigma=2.0)
583
+ processor.compute_1_to_1(
584
+ processor.get_feature("gaussian_filter").function,
585
+ param=filter_param,
586
+ title="Gaussian filter",
587
+ )
588
+
589
+ # Get the filtered signal
590
+ filtered_sig = panel.objview.get_current_object()
591
+
592
+ # Delete the source signal
593
+ panel.objview.set_current_object(signal)
594
+ panel.remove_object(force=True)
595
+
596
+ # Select the filtered signal
597
+ panel.objview.set_current_object(filtered_sig)
598
+
599
+ # Try to apply processing parameters
600
+ report = objprop.apply_processing_parameters(interactive=False)
601
+
602
+ # Verify the operation failed with appropriate message
603
+ assert not report.success
604
+ # Check for English or French message
605
+ assert (
606
+ "no longer exists" in report.message.lower()
607
+ or "n'existe plus" in report.message.lower()
608
+ )
609
+
610
+
611
+ def test_cross_panel_image_to_signal():
612
+ """Test cross-panel processing: Image → Signal (radial profile)"""
613
+ with qt_app_context():
614
+ with datalab_test_app_context() as win:
615
+ image_panel = win.imagepanel
616
+ signal_panel = win.signalpanel
617
+ image_processor = image_panel.processor
618
+
619
+ # Create a test image with a Gaussian peak
620
+ image_param = Gauss2DParam.create(
621
+ x0=50.0, y0=50.0, sigma=10.0, a=100.0, height=100, width=100
622
+ )
623
+ image_panel.new_object(param=image_param, edit=False)
624
+ image = image_panel.objview.get_current_object()
625
+ assert image is not None
626
+ image_uuid = get_uuid(image)
627
+
628
+ # Apply radial_profile (Image → Signal cross-panel computation)
629
+ profile_param = RadialProfileParam.create(x0=50, y0=50)
630
+ image_processor.run_feature("radial_profile", param=profile_param)
631
+
632
+ # Verify the result is now in the signal panel
633
+ signal = signal_panel.objview.get_current_object()
634
+ assert signal is not None
635
+
636
+ # Check that processing metadata was stored
637
+ assert PROCESSING_PARAMETERS_OPTION in signal.get_metadata_options()
638
+ option_dict = signal.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
639
+
640
+ # Verify metadata content
641
+ assert option_dict["source_uuid"] == image_uuid
642
+ assert option_dict["func_name"] == "radial_profile"
643
+ assert option_dict["pattern"] == "1-to-1"
644
+
645
+ # Verify the parameter can be deserialized
646
+ stored_param = json_to_dataset(option_dict["param_json"])
647
+ assert stored_param.x0 == 50
648
+ assert stored_param.y0 == 50
649
+
650
+ # Test that the Processing tab is set up correctly
651
+ # (even though source is in a different panel)
652
+ signal_panel.objview.set_current_object(signal)
653
+ assert signal_panel.objprop.processing_param_editor is not None
654
+
655
+ # Test modifying processing parameters
656
+ editor = signal_panel.objprop.processing_param_editor
657
+ # Change the center position
658
+ editor.dataset.x0 = 40
659
+ editor.dataset.y0 = 40
660
+
661
+ # Apply the new processing parameters
662
+ report = signal_panel.objprop.apply_processing_parameters()
663
+
664
+ # Verify the operation succeeded
665
+ assert report.success, f"Cross-panel reprocessing failed: {report.message}"
666
+
667
+ # Verify the parameter was updated in metadata
668
+ updated_dict = signal.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
669
+ updated_param = json_to_dataset(updated_dict["param_json"])
670
+ assert updated_param.x0 == 40
671
+ assert updated_param.y0 == 40
672
+
673
+ # Test recompute feature with modified source image
674
+ image.data *= 2.0 # Double the image intensity
675
+ original_signal_data = signal.y.copy()
676
+
677
+ # Recompute the radial profile
678
+ signal_panel.recompute_processing()
679
+
680
+ # The signal should have changed (doubled intensity)
681
+ assert not np.allclose(signal.y, original_signal_data)
682
+
683
+
684
+ def test_cross_panel_image_to_signal_group():
685
+ """Test cross-panel processing with groups: Image group → Signal group"""
686
+ with qt_app_context():
687
+ with datalab_test_app_context() as win:
688
+ image_panel = win.imagepanel
689
+ signal_panel = win.signalpanel
690
+ image_processor = image_panel.processor
691
+
692
+ # Create a group of test images
693
+ group_name = "Test Images"
694
+ group = image_panel.add_group(group_name)
695
+ group_id = get_uuid(group)
696
+
697
+ # Create multiple test images in the group
698
+ n_images = 3
699
+ for i in range(n_images):
700
+ image_param = Gauss2DParam.create(
701
+ x0=50.0 + i * 5,
702
+ y0=50.0 + i * 5,
703
+ sigma=10.0,
704
+ a=100.0,
705
+ height=100,
706
+ width=100,
707
+ )
708
+ image_panel.new_object(param=image_param, edit=False)
709
+ # The new objects are automatically added to the current group
710
+
711
+ # Select the entire group
712
+ image_panel.objview.set_current_item_id(group_id, extend=False)
713
+
714
+ # Get the initial number of groups in signal panel
715
+ signal_groups_before = len(signal_panel.objmodel.get_groups())
716
+
717
+ # Apply radial_profile to the entire group (Image → Signal cross-panel)
718
+ profile_param = RadialProfileParam.create(x0=50, y0=50)
719
+ image_processor.run_feature("radial_profile", param=profile_param)
720
+
721
+ # Verify a NEW group was created in the signal panel
722
+ signal_groups_after = len(signal_panel.objmodel.get_groups())
723
+ assert signal_groups_after == signal_groups_before + 1, (
724
+ "Expected a new group to be created for cross-panel computation results"
725
+ )
726
+
727
+ # Verify that the new group contains the correct number of signals
728
+ signal_groups = signal_panel.objmodel.get_groups()
729
+ new_group = signal_groups[-1] # Get the last (newly created) group
730
+ signals_in_group = new_group.get_objects()
731
+ assert len(signals_in_group) == n_images, (
732
+ f"Expected {n_images} signals in new group, got {len(signals_in_group)}"
733
+ )
734
+
735
+ # Verify the group name follows the expected pattern
736
+ assert "radial_profile" in new_group.title.lower(), (
737
+ f"Expected group name to contain 'radial_profile', "
738
+ f"got '{new_group.title}'"
739
+ )
740
+
741
+
742
+ def test_cross_panel_signal_to_image():
743
+ """Test cross-panel processing: Signal → Image (combine signals into image)"""
744
+ with qt_app_context():
745
+ with datalab_test_app_context() as win:
746
+ signal_panel = win.signalpanel
747
+ image_panel = win.imagepanel
748
+ signal_processor = signal_panel.processor
749
+
750
+ # Create multiple test signals
751
+ n_signals = 5
752
+ signal_uuids = []
753
+ for i in range(n_signals):
754
+ signal_param = GaussParam.create(
755
+ mu=250.0 + i * 10, sigma=20.0, a=100.0, y0=float(i), size=500
756
+ )
757
+ signal_panel.new_object(param=signal_param, edit=False)
758
+ signal = signal_panel.objview.get_current_object()
759
+ signal_uuids.append(get_uuid(signal))
760
+
761
+ # Select all signals
762
+ for uuid in signal_uuids:
763
+ signal_panel.objview.set_current_item_id(uuid, extend=True)
764
+
765
+ # Combine signals into image (Signal → Image cross-panel computation)
766
+ sti_param = SignalsToImageParam.create(
767
+ orientation="columns", normalize=True
768
+ )
769
+ signal_processor.run_feature("signals_to_image", param=sti_param)
770
+
771
+ # Verify the result is now in the image panel
772
+ image = image_panel.objview.get_current_object()
773
+ assert image is not None
774
+
775
+ # Check that processing metadata was stored
776
+ assert PROCESSING_PARAMETERS_OPTION in image.get_metadata_options()
777
+ option_dict = image.get_metadata_option(PROCESSING_PARAMETERS_OPTION)
778
+
779
+ # Verify metadata content for n-to-1 pattern
780
+ assert option_dict["func_name"] == "signals_to_image"
781
+ assert option_dict["pattern"] == "n-to-1"
782
+ assert len(option_dict["source_uuids"]) == n_signals
783
+ assert all(uuid in signal_uuids for uuid in option_dict["source_uuids"])
784
+
785
+ # Verify the parameter can be deserialized
786
+ stored_param = json_to_dataset(option_dict["param_json"])
787
+ assert stored_param.orientation == "columns"
788
+ assert stored_param.normalize is True
789
+
790
+ # Test that the Processing tab is NOT set up for n-to-1 pattern
791
+ # (interactive processing only works for 1-to-1 pattern)
792
+ image_panel.objview.set_current_object(image)
793
+ assert image_panel.objprop.processing_param_editor is None
794
+
795
+
796
+ def test_select_source_objects_same_panel():
797
+ """Test selecting source objects within the same panel"""
798
+ with qt_app_context():
799
+ with datalab_test_app_context() as win:
800
+ panel = win.signalpanel
801
+ processor = panel.processor
802
+
803
+ # Create a source signal
804
+ panel.new_object(edit=False)
805
+ source_signal = panel.objview.get_current_object()
806
+ source_uuid = get_uuid(source_signal)
807
+
808
+ # Apply a filter to create a processed signal
809
+ param = GaussianParam.create(sigma=2.0)
810
+ processor.run_feature("gaussian_filter", param=param)
811
+
812
+ # Get the filtered signal
813
+ filtered_signal = panel.objview.get_current_object()
814
+ assert filtered_signal is not None
815
+
816
+ # Clear selection
817
+ panel.objview.clearSelection()
818
+
819
+ # Select only the filtered signal
820
+ panel.objview.set_current_item_id(get_uuid(filtered_signal), extend=False)
821
+
822
+ # Call select_source_objects
823
+ panel.select_source_objects()
824
+
825
+ # Verify that the source signal is now selected
826
+ selected_uuids = panel.objview.get_sel_object_uuids()
827
+ assert source_uuid in selected_uuids
828
+ assert len(selected_uuids) == 1
829
+
830
+
831
+ def test_select_source_objects_cross_panel():
832
+ """Test selecting source objects across panels (cross-panel computation)"""
833
+ with qt_app_context():
834
+ with datalab_test_app_context() as win:
835
+ image_panel = win.imagepanel
836
+ signal_panel = win.signalpanel
837
+ image_processor = image_panel.processor
838
+
839
+ # Create a source image
840
+ image_param = Gauss2DParam.create(
841
+ x0=50.0, y0=50.0, sigma=10.0, a=100.0, height=100, width=100
842
+ )
843
+ image_panel.new_object(param=image_param, edit=False)
844
+ image = image_panel.objview.get_current_object()
845
+ image_uuid = get_uuid(image)
846
+
847
+ # Apply radial_profile (Image → Signal)
848
+ profile_param = RadialProfileParam.create(x0=50, y0=50)
849
+ image_processor.run_feature("radial_profile", param=profile_param)
850
+
851
+ # Verify we're now in the signal panel
852
+ signal = signal_panel.objview.get_current_object()
853
+ assert signal is not None
854
+
855
+ # Clear selection in signal panel
856
+ signal_panel.objview.clearSelection()
857
+
858
+ # Select only the radial profile signal
859
+ signal_panel.objview.set_current_item_id(get_uuid(signal), extend=False)
860
+
861
+ # Call select_source_objects - should switch to image panel
862
+ signal_panel.select_source_objects()
863
+
864
+ # Verify that we switched to the image panel
865
+ # (the current panel in the main window should now be image panel)
866
+ assert win.get_current_panel() == "image"
867
+
868
+ # Verify that the source image is now selected in the image panel
869
+ selected_uuids = image_panel.objview.get_sel_object_uuids()
870
+ assert image_uuid in selected_uuids
871
+ assert len(selected_uuids) == 1
872
+
873
+
874
+ def test_select_source_objects_multiple_sources():
875
+ """Test selecting multiple source objects (n-to-1 processing)"""
876
+ with qt_app_context():
877
+ with datalab_test_app_context() as win:
878
+ panel = win.signalpanel
879
+ processor = panel.processor
880
+
881
+ # Create multiple source signals
882
+ n_signals = 3
883
+ source_uuids = []
884
+ for _i in range(n_signals):
885
+ panel.new_object(edit=False)
886
+ signal = panel.objview.get_current_object()
887
+ source_uuids.append(get_uuid(signal))
888
+
889
+ # Select all source signals
890
+ for uuid in source_uuids:
891
+ panel.objview.set_current_item_id(uuid, extend=True)
892
+
893
+ # Apply addition operation (n-to-1)
894
+ processor.run_feature("addition")
895
+
896
+ # Get the result signal
897
+ result_signal = panel.objview.get_current_object()
898
+ assert result_signal is not None
899
+
900
+ # Clear selection
901
+ panel.objview.clearSelection()
902
+
903
+ # Select only the result signal
904
+ panel.objview.set_current_item_id(get_uuid(result_signal), extend=False)
905
+
906
+ # Call select_source_objects
907
+ panel.select_source_objects()
908
+
909
+ # Verify that all source signals are now selected
910
+ selected_uuids = panel.objview.get_sel_object_uuids()
911
+ assert len(selected_uuids) == n_signals
912
+ assert all(uuid in selected_uuids for uuid in source_uuids)
913
+
914
+
915
+ def test_select_source_objects_deleted_source():
916
+ """Test selecting source objects when source has been deleted"""
917
+ with qt_app_context():
918
+ with datalab_test_app_context() as win:
919
+ panel = win.signalpanel
920
+ processor = panel.processor
921
+
922
+ # Create a source signal
923
+ panel.new_object(edit=False)
924
+ source_signal = panel.objview.get_current_object()
925
+
926
+ # Apply a filter
927
+ param = GaussianParam.create(sigma=2.0)
928
+ processor.run_feature("gaussian_filter", param=param)
929
+
930
+ # Get the filtered signal
931
+ filtered_signal = panel.objview.get_current_object()
932
+
933
+ # Delete the source signal
934
+ panel.objview.set_current_object(source_signal)
935
+ panel.remove_object(force=True)
936
+
937
+ # Select the filtered signal
938
+ panel.objview.clearSelection()
939
+ panel.objview.set_current_item_id(get_uuid(filtered_signal), extend=False)
940
+
941
+ # Call select_source_objects - should show warning
942
+ # (This won't raise an exception, just show a message box in GUI)
943
+ panel.select_source_objects()
944
+
945
+ # Verify that no objects are selected (source was deleted)
946
+ selected_uuids = panel.objview.get_sel_object_uuids()
947
+ # The filtered signal should still be selected
948
+ assert get_uuid(filtered_signal) in selected_uuids
949
+
950
+
951
+ def test_roi_mask_invalidation_on_size_change():
952
+ """Test that ROI masks are invalidated when image size changes.
953
+
954
+ This test verifies the fix for the bug where modifying creation parameters
955
+ that change the image dimensions doesn't invalidate the ROI mask cache,
956
+ resulting in corrupted ROI display.
957
+
958
+ Reproduction steps:
959
+ 1. Create a 2D gaussian image
960
+ 2. Add a rectangular ROI
961
+ 3. Change the size of the image (increase width by 50%)
962
+ 4. Verify that the ROI mask is properly invalidated and recomputed
963
+ """
964
+ with qt_app_context():
965
+ with datalab_test_app_context() as win:
966
+ panel = win.imagepanel
967
+ objprop = panel.objprop
968
+
969
+ # Step 1: Create a 2D gaussian image with specific dimensions
970
+ param = Gauss2DParam.create(
971
+ height=100, width=100, x0=50.0, y0=50.0, sigma=10.0, a=100.0
972
+ )
973
+ panel.new_object(param=param, edit=False)
974
+ image = panel.objview.get_current_object()
975
+ assert image is not None
976
+ assert image.data.shape == (100, 100)
977
+
978
+ # Step 2: Add a rectangular ROI
979
+ roi = create_image_roi("rectangle", [20, 20, 40, 40])
980
+ image.roi = roi
981
+
982
+ # Verify the ROI mask is created and cached
983
+ mask_before = image.maskdata
984
+ assert mask_before is not None
985
+ assert mask_before.shape == (100, 100)
986
+
987
+ # Step 3: Change the image dimensions (increase width by 50%)
988
+ editor = objprop.creation_param_editor
989
+ assert editor is not None
990
+ editor.dataset.width = 150 # Change from 100 to 150
991
+
992
+ # Apply the new parameters
993
+ objprop.apply_creation_parameters()
994
+
995
+ # Step 4: Verify the image was resized
996
+ updated_image = panel.objview.get_current_object()
997
+ assert get_uuid(updated_image) == get_uuid(image)
998
+ assert updated_image.data.shape == (100, 150)
999
+
1000
+ # Step 5: Verify the ROI mask was invalidated and will be recomputed
1001
+ # with the new dimensions
1002
+ mask_after = updated_image.maskdata
1003
+ assert mask_after is not None
1004
+ assert mask_after.shape == (100, 150), (
1005
+ f"ROI mask shape {mask_after.shape} doesn't match "
1006
+ f"new image shape {updated_image.data.shape}"
1007
+ )
1008
+
1009
+ # The mask should be different from before (different shape)
1010
+ assert mask_before.shape != mask_after.shape
1011
+
1012
+
1013
+ def test_roi_mask_invalidation_on_processing_change():
1014
+ """Test that ROI masks are invalidated when processing changes image dimensions.
1015
+
1016
+ This test verifies the fix for the bug where reprocessing with parameters
1017
+ that change image dimensions doesn't invalidate the ROI mask cache,
1018
+ resulting in corrupted ROI display.
1019
+
1020
+ Scenario:
1021
+ 1. Create a source image
1022
+ 2. Apply binning (reduces dimensions)
1023
+ 3. Add ROI to the binned image
1024
+ 4. Change binning factor (changes dimensions again)
1025
+ 5. Verify ROI mask is properly recomputed
1026
+ """
1027
+ with qt_app_context():
1028
+ with datalab_test_app_context() as win:
1029
+ panel = win.imagepanel
1030
+ objprop = panel.objprop
1031
+
1032
+ # Step 1: Create a source image
1033
+ param = Gauss2DParam.create(
1034
+ height=100, width=100, x0=50.0, y0=50.0, sigma=10.0, a=100.0
1035
+ )
1036
+ panel.new_object(param=param, edit=False)
1037
+ source_image = panel.objview.get_current_object()
1038
+ assert source_image is not None
1039
+
1040
+ # Step 2: Apply binning to reduce dimensions
1041
+ binning_param = BinningParam.create(sx=2, sy=2) # 100x100 -> 50x50
1042
+
1043
+ # Use the processor's run_feature method with edit=False
1044
+ panel.processor.run_feature("binning", binning_param, edit=False)
1045
+
1046
+ # Get the binned result (last object in the list)
1047
+ binned = panel.objview.get_sel_objects()[-1]
1048
+ assert binned.data.shape == (50, 50)
1049
+
1050
+ # Step 3: Add a rectangular ROI to the binned image
1051
+ roi = create_image_roi("rectangle", [10, 10, 20, 20])
1052
+ binned.roi = roi
1053
+
1054
+ # Verify the ROI mask is created and cached
1055
+ mask_before = binned.maskdata
1056
+ assert mask_before is not None
1057
+ assert mask_before.shape == (50, 50)
1058
+
1059
+ # Step 4: Change binning factor via Processing tab
1060
+ assert objprop.setup_processing_tab(binned)
1061
+ editor = objprop.processing_param_editor
1062
+ assert editor is not None
1063
+
1064
+ # Change binning factor from 2x2 to 4x4 (50x50 -> 25x25)
1065
+ editor.dataset.sx = 4
1066
+ editor.dataset.sy = 4
1067
+
1068
+ # Apply the new processing parameters
1069
+ report = objprop.apply_processing_parameters(binned)
1070
+ assert report.success
1071
+
1072
+ # Step 5: Verify the image was resized
1073
+ assert binned.data.shape == (25, 25)
1074
+
1075
+ # Step 6: Verify the ROI mask was invalidated and will be recomputed
1076
+ # with the new dimensions
1077
+ mask_after = binned.maskdata
1078
+ assert mask_after is not None
1079
+ assert mask_after.shape == (25, 25), (
1080
+ f"ROI mask shape {mask_after.shape} doesn't match "
1081
+ f"new image shape {binned.data.shape}"
1082
+ )
1083
+
1084
+ # The mask should be different from before (different shape)
1085
+ assert mask_before.shape != mask_after.shape
1086
+
1087
+
1088
+ if __name__ == "__main__":
1089
+ test_signal_interactive_processing()
1090
+ test_image_interactive_processing()
1091
+ test_processing_without_parameters()
1092
+ test_recompute()
1093
+ test_apply_creation_parameters_signal()
1094
+ test_apply_creation_parameters_image()
1095
+ test_no_duplicate_creation_tabs()
1096
+ test_no_creation_parameters_for_base_classes()
1097
+ test_apply_processing_parameters_signal()
1098
+ test_apply_processing_parameters_image()
1099
+ test_no_duplicate_processing_tabs()
1100
+ test_apply_processing_parameters_missing_source()
1101
+ test_cross_panel_image_to_signal()
1102
+ test_cross_panel_image_to_signal_group()
1103
+ test_cross_panel_signal_to_image()
1104
+ test_select_source_objects_same_panel()
1105
+ test_select_source_objects_cross_panel()
1106
+ test_select_source_objects_multiple_sources()
1107
+ test_select_source_objects_deleted_source()
1108
+ test_roi_mask_invalidation_on_size_change()
1109
+ test_roi_mask_invalidation_on_processing_change()