datalab-platform 0.0.1.dev0__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datalab/__init__.py +35 -2
- datalab/adapters_metadata/__init__.py +31 -0
- datalab/adapters_metadata/base_adapter.py +316 -0
- datalab/adapters_metadata/common.py +422 -0
- datalab/adapters_metadata/geometry_adapter.py +98 -0
- datalab/adapters_metadata/table_adapter.py +84 -0
- datalab/adapters_plotpy/__init__.py +54 -0
- datalab/adapters_plotpy/annotations.py +124 -0
- datalab/adapters_plotpy/base.py +110 -0
- datalab/adapters_plotpy/converters.py +86 -0
- datalab/adapters_plotpy/factories.py +80 -0
- datalab/adapters_plotpy/objects/__init__.py +0 -0
- datalab/adapters_plotpy/objects/base.py +197 -0
- datalab/adapters_plotpy/objects/image.py +157 -0
- datalab/adapters_plotpy/objects/scalar.py +565 -0
- datalab/adapters_plotpy/objects/signal.py +264 -0
- datalab/adapters_plotpy/roi/__init__.py +0 -0
- datalab/adapters_plotpy/roi/base.py +146 -0
- datalab/adapters_plotpy/roi/factory.py +93 -0
- datalab/adapters_plotpy/roi/image.py +207 -0
- datalab/adapters_plotpy/roi/signal.py +72 -0
- datalab/app.py +98 -0
- datalab/config.py +817 -0
- datalab/control/__init__.py +0 -0
- datalab/control/baseproxy.py +776 -0
- datalab/control/proxy.py +343 -0
- datalab/control/remote.py +1005 -0
- datalab/data/doc/DataLab_en.pdf +0 -0
- datalab/data/doc/DataLab_fr.pdf +0 -0
- datalab/data/icons/analysis/delete_results.svg +109 -0
- datalab/data/icons/analysis/fw1e2.svg +156 -0
- datalab/data/icons/analysis/fwhm.svg +156 -0
- datalab/data/icons/analysis/histogram.svg +49 -0
- datalab/data/icons/analysis/peak_detect.svg +160 -0
- datalab/data/icons/analysis/plot_results.svg +151 -0
- datalab/data/icons/analysis/show_results.svg +83 -0
- datalab/data/icons/analysis/stats.svg +49 -0
- datalab/data/icons/analysis.svg +120 -0
- datalab/data/icons/apply.svg +3 -0
- datalab/data/icons/check_all.svg +15 -0
- datalab/data/icons/collapse.svg +44 -0
- datalab/data/icons/collapse_selection.svg +63 -0
- datalab/data/icons/console.svg +101 -0
- datalab/data/icons/create/1d-normal.svg +8 -0
- datalab/data/icons/create/1d-poisson.svg +9 -0
- datalab/data/icons/create/1d-uniform.svg +8 -0
- datalab/data/icons/create/1d-zero.svg +57 -0
- datalab/data/icons/create/2d-gaussian.svg +56 -0
- datalab/data/icons/create/2d-normal.svg +38 -0
- datalab/data/icons/create/2d-poisson.svg +38 -0
- datalab/data/icons/create/2d-ramp.svg +90 -0
- datalab/data/icons/create/2d-sinc.svg +62 -0
- datalab/data/icons/create/2d-uniform.svg +38 -0
- datalab/data/icons/create/2d-zero.svg +13 -0
- datalab/data/icons/create/checkerboard.svg +39 -0
- datalab/data/icons/create/cosine.svg +12 -0
- datalab/data/icons/create/exponential.svg +55 -0
- datalab/data/icons/create/gaussian.svg +12 -0
- datalab/data/icons/create/grating.svg +29 -0
- datalab/data/icons/create/linear_chirp.svg +7 -0
- datalab/data/icons/create/logistic.svg +7 -0
- datalab/data/icons/create/lorentzian.svg +12 -0
- datalab/data/icons/create/planck.svg +12 -0
- datalab/data/icons/create/polynomial.svg +7 -0
- datalab/data/icons/create/pulse.svg +12 -0
- datalab/data/icons/create/ring.svg +18 -0
- datalab/data/icons/create/sawtooth.svg +7 -0
- datalab/data/icons/create/siemens.svg +35 -0
- datalab/data/icons/create/sinc.svg +12 -0
- datalab/data/icons/create/sine.svg +7 -0
- datalab/data/icons/create/square.svg +7 -0
- datalab/data/icons/create/square_pulse.svg +7 -0
- datalab/data/icons/create/step.svg +7 -0
- datalab/data/icons/create/step_pulse.svg +12 -0
- datalab/data/icons/create/triangle.svg +7 -0
- datalab/data/icons/create/voigt.svg +12 -0
- datalab/data/icons/edit/annotations.svg +72 -0
- datalab/data/icons/edit/annotations_copy.svg +114 -0
- datalab/data/icons/edit/annotations_delete.svg +83 -0
- datalab/data/icons/edit/annotations_edit.svg +98 -0
- datalab/data/icons/edit/annotations_export.svg +85 -0
- datalab/data/icons/edit/annotations_import.svg +85 -0
- datalab/data/icons/edit/annotations_paste.svg +100 -0
- datalab/data/icons/edit/copy_titles.svg +109 -0
- datalab/data/icons/edit/delete.svg +84 -0
- datalab/data/icons/edit/delete_all.svg +214 -0
- datalab/data/icons/edit/duplicate.svg +64 -0
- datalab/data/icons/edit/goto_source.svg +60 -0
- datalab/data/icons/edit/metadata.svg +60 -0
- datalab/data/icons/edit/metadata_add.svg +80 -0
- datalab/data/icons/edit/metadata_copy.svg +96 -0
- datalab/data/icons/edit/metadata_delete.svg +62 -0
- datalab/data/icons/edit/metadata_export.svg +68 -0
- datalab/data/icons/edit/metadata_import.svg +68 -0
- datalab/data/icons/edit/metadata_paste.svg +79 -0
- datalab/data/icons/edit/move_down.svg +55 -0
- datalab/data/icons/edit/move_up.svg +54 -0
- datalab/data/icons/edit/new_group.svg +76 -0
- datalab/data/icons/edit/recompute.svg +60 -0
- datalab/data/icons/edit/rename.svg +49 -0
- datalab/data/icons/edit.svg +16 -0
- datalab/data/icons/expand.svg +44 -0
- datalab/data/icons/expand_selection.svg +63 -0
- datalab/data/icons/fit/cdf_fit.svg +56 -0
- datalab/data/icons/fit/exponential_fit.svg +55 -0
- datalab/data/icons/fit/gaussian_fit.svg +62 -0
- datalab/data/icons/fit/interactive_fit.svg +101 -0
- datalab/data/icons/fit/linear_fit.svg +57 -0
- datalab/data/icons/fit/lorentzian_fit.svg +209 -0
- datalab/data/icons/fit/multigaussian_fit.svg +85 -0
- datalab/data/icons/fit/multilorentzian_fit.svg +85 -0
- datalab/data/icons/fit/piecewiseexponential_fit.svg +209 -0
- datalab/data/icons/fit/planckian_fit.svg +62 -0
- datalab/data/icons/fit/polynomial_fit.svg +59 -0
- datalab/data/icons/fit/sigmoid_fit.svg +56 -0
- datalab/data/icons/fit/sinusoidal_fit.svg +72 -0
- datalab/data/icons/fit/twohalfgaussian_fit.svg +63 -0
- datalab/data/icons/fit/voigt_fit.svg +57 -0
- datalab/data/icons/group.svg +56 -0
- datalab/data/icons/h5/h5array.svg +59 -0
- datalab/data/icons/h5/h5attrs.svg +75 -0
- datalab/data/icons/h5/h5browser.svg +133 -0
- datalab/data/icons/h5/h5file.svg +69 -0
- datalab/data/icons/h5/h5group.svg +49 -0
- datalab/data/icons/h5/h5scalar.svg +1 -0
- datalab/data/icons/help_pdf.svg +46 -0
- datalab/data/icons/history.svg +7 -0
- datalab/data/icons/image.svg +135 -0
- datalab/data/icons/io/fileopen_directory.svg +60 -0
- datalab/data/icons/io/fileopen_h5.svg +84 -0
- datalab/data/icons/io/fileopen_ima.svg +187 -0
- datalab/data/icons/io/fileopen_py.svg +123 -0
- datalab/data/icons/io/fileopen_sig.svg +138 -0
- datalab/data/icons/io/filesave_h5.svg +97 -0
- datalab/data/icons/io/filesave_ima.svg +200 -0
- datalab/data/icons/io/filesave_py.svg +136 -0
- datalab/data/icons/io/filesave_sig.svg +151 -0
- datalab/data/icons/io/import_text.svg +144 -0
- datalab/data/icons/io/save_to_directory.svg +134 -0
- datalab/data/icons/io.svg +84 -0
- datalab/data/icons/libre-camera-flash-off.svg +1 -0
- datalab/data/icons/libre-camera-flash-on.svg +1 -0
- datalab/data/icons/libre-gui-about.svg +1 -0
- datalab/data/icons/libre-gui-action-delete.svg +1 -0
- datalab/data/icons/libre-gui-add.svg +1 -0
- datalab/data/icons/libre-gui-arrow-down.svg +1 -0
- datalab/data/icons/libre-gui-arrow-left.svg +1 -0
- datalab/data/icons/libre-gui-arrow-right.svg +1 -0
- datalab/data/icons/libre-gui-arrow-up.svg +1 -0
- datalab/data/icons/libre-gui-close.svg +40 -0
- datalab/data/icons/libre-gui-cogs.svg +1 -0
- datalab/data/icons/libre-gui-globe.svg +1 -0
- datalab/data/icons/libre-gui-help.svg +1 -0
- datalab/data/icons/libre-gui-link.svg +1 -0
- datalab/data/icons/libre-gui-menu.svg +1 -0
- datalab/data/icons/libre-gui-pencil.svg +1 -0
- datalab/data/icons/libre-gui-plugin.svg +1 -0
- datalab/data/icons/libre-gui-questions.svg +1 -0
- datalab/data/icons/libre-gui-settings.svg +1 -0
- datalab/data/icons/libre-gui-unlink.svg +1 -0
- datalab/data/icons/libre-tech-ram.svg +1 -0
- datalab/data/icons/libre-toolbox.svg +1 -0
- datalab/data/icons/logs.svg +1 -0
- datalab/data/icons/markers.svg +74 -0
- datalab/data/icons/menu.svg +13 -0
- datalab/data/icons/new_ima.svg +148 -0
- datalab/data/icons/new_sig.svg +123 -0
- datalab/data/icons/operations/abs.svg +116 -0
- datalab/data/icons/operations/arithmetic.svg +123 -0
- datalab/data/icons/operations/average.svg +124 -0
- datalab/data/icons/operations/complex_from_magnitude_phase.svg +116 -0
- datalab/data/icons/operations/complex_from_real_imag.svg +124 -0
- datalab/data/icons/operations/constant.svg +116 -0
- datalab/data/icons/operations/constant_add.svg +109 -0
- datalab/data/icons/operations/constant_divide.svg +109 -0
- datalab/data/icons/operations/constant_multiply.svg +109 -0
- datalab/data/icons/operations/constant_subtract.svg +109 -0
- datalab/data/icons/operations/convert_dtype.svg +117 -0
- datalab/data/icons/operations/convolution.svg +46 -0
- datalab/data/icons/operations/deconvolution.svg +57 -0
- datalab/data/icons/operations/derivative.svg +127 -0
- datalab/data/icons/operations/difference.svg +52 -0
- datalab/data/icons/operations/division.svg +139 -0
- datalab/data/icons/operations/exp.svg +116 -0
- datalab/data/icons/operations/flip_horizontally.svg +69 -0
- datalab/data/icons/operations/flip_vertically.svg +74 -0
- datalab/data/icons/operations/im.svg +124 -0
- datalab/data/icons/operations/integral.svg +50 -0
- datalab/data/icons/operations/inverse.svg +143 -0
- datalab/data/icons/operations/log10.svg +109 -0
- datalab/data/icons/operations/phase.svg +116 -0
- datalab/data/icons/operations/power.svg +118 -0
- datalab/data/icons/operations/product.svg +124 -0
- datalab/data/icons/operations/profile.svg +379 -0
- datalab/data/icons/operations/profile_average.svg +399 -0
- datalab/data/icons/operations/profile_radial.svg +261 -0
- datalab/data/icons/operations/profile_segment.svg +262 -0
- datalab/data/icons/operations/quadratic_difference.svg +84 -0
- datalab/data/icons/operations/re.svg +124 -0
- datalab/data/icons/operations/rotate_left.svg +72 -0
- datalab/data/icons/operations/rotate_right.svg +72 -0
- datalab/data/icons/operations/signals_to_image.svg +314 -0
- datalab/data/icons/operations/sqrt.svg +110 -0
- datalab/data/icons/operations/std.svg +124 -0
- datalab/data/icons/operations/sum.svg +102 -0
- datalab/data/icons/play_demo.svg +9 -0
- datalab/data/icons/processing/axis_transform.svg +62 -0
- datalab/data/icons/processing/bandpass.svg +79 -0
- datalab/data/icons/processing/bandstop.svg +71 -0
- datalab/data/icons/processing/binning.svg +126 -0
- datalab/data/icons/processing/clip.svg +119 -0
- datalab/data/icons/processing/detrending.svg +173 -0
- datalab/data/icons/processing/distribute_on_grid.svg +769 -0
- datalab/data/icons/processing/edge_detection.svg +46 -0
- datalab/data/icons/processing/erase.svg +1 -0
- datalab/data/icons/processing/exposure.svg +143 -0
- datalab/data/icons/processing/fourier.svg +104 -0
- datalab/data/icons/processing/highpass.svg +59 -0
- datalab/data/icons/processing/interpolation.svg +71 -0
- datalab/data/icons/processing/level_adjustment.svg +70 -0
- datalab/data/icons/processing/lowpass.svg +60 -0
- datalab/data/icons/processing/morphology.svg +49 -0
- datalab/data/icons/processing/noise_addition.svg +114 -0
- datalab/data/icons/processing/noise_reduction.svg +38 -0
- datalab/data/icons/processing/normalize.svg +84 -0
- datalab/data/icons/processing/offset_correction.svg +131 -0
- datalab/data/icons/processing/resampling1d.svg +101 -0
- datalab/data/icons/processing/resampling2d.svg +240 -0
- datalab/data/icons/processing/reset_positions.svg +185 -0
- datalab/data/icons/processing/resize.svg +9 -0
- datalab/data/icons/processing/reverse_signal_x.svg +171 -0
- datalab/data/icons/processing/stability.svg +11 -0
- datalab/data/icons/processing/swap_x_y.svg +65 -0
- datalab/data/icons/processing/thresholding.svg +63 -0
- datalab/data/icons/processing/windowing.svg +45 -0
- datalab/data/icons/properties.svg +26 -0
- datalab/data/icons/reset.svg +9 -0
- datalab/data/icons/restore.svg +40 -0
- datalab/data/icons/roi/roi.svg +76 -0
- datalab/data/icons/roi/roi_coordinate.svg +78 -0
- datalab/data/icons/roi/roi_copy.svg +112 -0
- datalab/data/icons/roi/roi_delete.svg +81 -0
- datalab/data/icons/roi/roi_export.svg +87 -0
- datalab/data/icons/roi/roi_graphical.svg +78 -0
- datalab/data/icons/roi/roi_grid.svg +67 -0
- datalab/data/icons/roi/roi_ima.svg +188 -0
- datalab/data/icons/roi/roi_import.svg +87 -0
- datalab/data/icons/roi/roi_new.svg +81 -0
- datalab/data/icons/roi/roi_new_circle.svg +95 -0
- datalab/data/icons/roi/roi_new_polygon.svg +110 -0
- datalab/data/icons/roi/roi_new_rectangle.svg +70 -0
- datalab/data/icons/roi/roi_paste.svg +98 -0
- datalab/data/icons/roi/roi_sig.svg +124 -0
- datalab/data/icons/shapes.svg +134 -0
- datalab/data/icons/signal.svg +103 -0
- datalab/data/icons/table.svg +85 -0
- datalab/data/icons/table_unavailable.svg +102 -0
- datalab/data/icons/to_signal.svg +124 -0
- datalab/data/icons/tour/next.svg +44 -0
- datalab/data/icons/tour/previous.svg +44 -0
- datalab/data/icons/tour/rewind.svg +51 -0
- datalab/data/icons/tour/stop.svg +47 -0
- datalab/data/icons/tour/tour.svg +16 -0
- datalab/data/icons/uncheck_all.svg +78 -0
- datalab/data/icons/view/curve_antialiasing.svg +50 -0
- datalab/data/icons/view/new_window.svg +98 -0
- datalab/data/icons/view/refresh-auto.svg +57 -0
- datalab/data/icons/view/refresh-manual.svg +51 -0
- datalab/data/icons/view/reset_curve_styles.svg +96 -0
- datalab/data/icons/view/show_first.svg +55 -0
- datalab/data/icons/view/show_titles.svg +46 -0
- datalab/data/icons/visualization.svg +51 -0
- datalab/data/logo/DataLab-Banner-150.png +0 -0
- datalab/data/logo/DataLab-Banner-200.png +0 -0
- datalab/data/logo/DataLab-Banner2-100.png +0 -0
- datalab/data/logo/DataLab-Splash.png +0 -0
- datalab/data/logo/DataLab-watermark.png +0 -0
- datalab/data/logo/DataLab.svg +83 -0
- datalab/data/tests/reordering_test.h5 +0 -0
- datalab/data/tutorials/fabry_perot/fabry-perot1.jpg +0 -0
- datalab/data/tutorials/fabry_perot/fabry-perot2.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_13.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_18.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_23.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_30.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_35.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_40.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_45.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_50.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_55.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_60.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_65.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_70.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_75.jpg +0 -0
- datalab/data/tutorials/laser_beam/TEM00_z_80.jpg +0 -0
- datalab/env.py +542 -0
- datalab/gui/__init__.py +89 -0
- datalab/gui/actionhandler.py +1701 -0
- datalab/gui/docks.py +473 -0
- datalab/gui/h5io.py +150 -0
- datalab/gui/macroeditor.py +310 -0
- datalab/gui/main.py +2081 -0
- datalab/gui/newobject.py +217 -0
- datalab/gui/objectview.py +766 -0
- datalab/gui/panel/__init__.py +48 -0
- datalab/gui/panel/base.py +3254 -0
- datalab/gui/panel/image.py +157 -0
- datalab/gui/panel/macro.py +607 -0
- datalab/gui/panel/signal.py +164 -0
- datalab/gui/plothandler.py +800 -0
- datalab/gui/processor/__init__.py +84 -0
- datalab/gui/processor/base.py +2456 -0
- datalab/gui/processor/catcher.py +75 -0
- datalab/gui/processor/image.py +1214 -0
- datalab/gui/processor/signal.py +755 -0
- datalab/gui/profiledialog.py +333 -0
- datalab/gui/roieditor.py +633 -0
- datalab/gui/roigrideditor.py +208 -0
- datalab/gui/settings.py +612 -0
- datalab/gui/tour.py +908 -0
- datalab/h5/__init__.py +12 -0
- datalab/h5/common.py +314 -0
- datalab/h5/generic.py +580 -0
- datalab/h5/native.py +39 -0
- datalab/h5/utils.py +95 -0
- datalab/objectmodel.py +640 -0
- datalab/plugins/_readme_.txt +9 -0
- datalab/plugins/datalab_imageformats.py +175 -0
- datalab/plugins/datalab_testdata.py +190 -0
- datalab/plugins.py +355 -0
- datalab/tests/__init__.py +199 -0
- datalab/tests/backbone/__init__.py +1 -0
- datalab/tests/backbone/config_unit_test.py +170 -0
- datalab/tests/backbone/config_versioning_unit_test.py +34 -0
- datalab/tests/backbone/dictlistserial_app_test.py +38 -0
- datalab/tests/backbone/errorcatcher_unit_test.py +69 -0
- datalab/tests/backbone/errormsgbox_unit_test.py +50 -0
- datalab/tests/backbone/execenv_unit.py +262 -0
- datalab/tests/backbone/loadtest_gdi.py +147 -0
- datalab/tests/backbone/long_callback.py +96 -0
- datalab/tests/backbone/main_app_test.py +137 -0
- datalab/tests/backbone/memory_leak.py +43 -0
- datalab/tests/backbone/procisolation1_unit.py +128 -0
- datalab/tests/backbone/procisolation2_unit.py +171 -0
- datalab/tests/backbone/procisolation_unit_test.py +22 -0
- datalab/tests/backbone/profiling_app.py +27 -0
- datalab/tests/backbone/strings_unit_test.py +65 -0
- datalab/tests/backbone/title_formatting_unit_test.py +82 -0
- datalab/tests/conftest.py +131 -0
- datalab/tests/features/__init__.py +1 -0
- datalab/tests/features/applauncher/__init__.py +1 -0
- datalab/tests/features/applauncher/launcher1_app_test.py +28 -0
- datalab/tests/features/applauncher/launcher2_app_test.py +30 -0
- datalab/tests/features/common/__init__.py +1 -0
- datalab/tests/features/common/add_metadata_app_test.py +134 -0
- datalab/tests/features/common/add_metadata_unit_test.py +267 -0
- datalab/tests/features/common/annotations_management_unit_test.py +152 -0
- datalab/tests/features/common/auto_analysis_recompute_unit_test.py +240 -0
- datalab/tests/features/common/createobject_unit_test.py +50 -0
- datalab/tests/features/common/geometry_results_app_test.py +135 -0
- datalab/tests/features/common/interactive_processing_test.py +1109 -0
- datalab/tests/features/common/io_app_test.py +75 -0
- datalab/tests/features/common/large_results_app_test.py +187 -0
- datalab/tests/features/common/metadata_all_patterns_test.py +103 -0
- datalab/tests/features/common/metadata_app_test.py +139 -0
- datalab/tests/features/common/metadata_io_unit_test.py +60 -0
- datalab/tests/features/common/misc_app_test.py +236 -0
- datalab/tests/features/common/multiple_geometry_results_unit_test.py +122 -0
- datalab/tests/features/common/multiple_table_results_unit_test.py +64 -0
- datalab/tests/features/common/operation_modes_app_test.py +392 -0
- datalab/tests/features/common/plot_results_app_test.py +278 -0
- datalab/tests/features/common/reorder_app_test.py +75 -0
- datalab/tests/features/common/result_deletion_unit_test.py +96 -0
- datalab/tests/features/common/result_merged_label_unit_test.py +154 -0
- datalab/tests/features/common/result_shape_settings_unit_test.py +223 -0
- datalab/tests/features/common/roi_plotitem_unit_test.py +64 -0
- datalab/tests/features/common/roieditor_unit_test.py +102 -0
- datalab/tests/features/common/save_to_dir_app_test.py +163 -0
- datalab/tests/features/common/save_to_dir_unit_test.py +474 -0
- datalab/tests/features/common/stat_app_test.py +40 -0
- datalab/tests/features/common/stats_tools_unit_test.py +77 -0
- datalab/tests/features/common/table_results_app_test.py +52 -0
- datalab/tests/features/common/textimport_unit_test.py +131 -0
- datalab/tests/features/common/uuid_preservation_test.py +281 -0
- datalab/tests/features/common/worker_unit_test.py +402 -0
- datalab/tests/features/control/__init__.py +1 -0
- datalab/tests/features/control/connect_dialog.py +28 -0
- datalab/tests/features/control/embedded1_unit_test.py +304 -0
- datalab/tests/features/control/embedded2_unit_test.py +52 -0
- datalab/tests/features/control/remoteclient_app_test.py +219 -0
- datalab/tests/features/control/remoteclient_unit.py +75 -0
- datalab/tests/features/control/simpleclient_unit_test.py +321 -0
- datalab/tests/features/hdf5/__init__.py +1 -0
- datalab/tests/features/hdf5/h5browser1_unit_test.py +31 -0
- datalab/tests/features/hdf5/h5browser2_unit.py +55 -0
- datalab/tests/features/hdf5/h5browser_app_test.py +77 -0
- datalab/tests/features/hdf5/h5import_app_test.py +25 -0
- datalab/tests/features/hdf5/h5importer_app_test.py +34 -0
- datalab/tests/features/image/__init__.py +1 -0
- datalab/tests/features/image/annotations_app_test.py +28 -0
- datalab/tests/features/image/annotations_unit_test.py +80 -0
- datalab/tests/features/image/average_app_test.py +46 -0
- datalab/tests/features/image/background_dialog_test.py +70 -0
- datalab/tests/features/image/blobs_app_test.py +50 -0
- datalab/tests/features/image/contour_app_test.py +42 -0
- datalab/tests/features/image/contour_fabryperot_app_test.py +51 -0
- datalab/tests/features/image/denoise_app_test.py +31 -0
- datalab/tests/features/image/distribute_on_grid_app_test.py +95 -0
- datalab/tests/features/image/edges_app_test.py +31 -0
- datalab/tests/features/image/erase_app_test.py +21 -0
- datalab/tests/features/image/fft2d_app_test.py +27 -0
- datalab/tests/features/image/flatfield_app_test.py +40 -0
- datalab/tests/features/image/geometry_transform_unit_test.py +396 -0
- datalab/tests/features/image/imagetools_app_test.py +51 -0
- datalab/tests/features/image/imagetools_unit_test.py +27 -0
- datalab/tests/features/image/load_app_test.py +73 -0
- datalab/tests/features/image/morph_app_test.py +32 -0
- datalab/tests/features/image/offsetcorrection_app_test.py +30 -0
- datalab/tests/features/image/peak2d_app_test.py +53 -0
- datalab/tests/features/image/profile_app_test.py +73 -0
- datalab/tests/features/image/profile_dialog_test.py +56 -0
- datalab/tests/features/image/roi_app_test.py +98 -0
- datalab/tests/features/image/roi_circ_app_test.py +62 -0
- datalab/tests/features/image/roi_manipulation_app_test.py +268 -0
- datalab/tests/features/image/roigrid_unit_test.py +60 -0
- datalab/tests/features/image/side_by_side_app_test.py +52 -0
- datalab/tests/features/macro/__init__.py +1 -0
- datalab/tests/features/macro/macro_app_test.py +28 -0
- datalab/tests/features/macro/macroeditor_unit_test.py +102 -0
- datalab/tests/features/signal/__init__.py +1 -0
- datalab/tests/features/signal/baseline_dialog_test.py +53 -0
- datalab/tests/features/signal/deltax_dialog_unit_test.py +34 -0
- datalab/tests/features/signal/fft1d_app_test.py +26 -0
- datalab/tests/features/signal/filter_app_test.py +44 -0
- datalab/tests/features/signal/fitdialog_unit_test.py +50 -0
- datalab/tests/features/signal/interpolation_app_test.py +110 -0
- datalab/tests/features/signal/loadbigsignal_app_test.py +80 -0
- datalab/tests/features/signal/multiple_rois_unit_test.py +132 -0
- datalab/tests/features/signal/pulse_features_app_test.py +118 -0
- datalab/tests/features/signal/pulse_features_roi_app_test.py +55 -0
- datalab/tests/features/signal/roi_app_test.py +78 -0
- datalab/tests/features/signal/roi_manipulation_app_test.py +261 -0
- datalab/tests/features/signal/select_xy_cursor_unit_test.py +46 -0
- datalab/tests/features/signal/signalpeakdetection_dialog_test.py +33 -0
- datalab/tests/features/signal/signals_to_image_app_test.py +98 -0
- datalab/tests/features/signal/xarray_compat_app_test.py +128 -0
- datalab/tests/features/tour_unit_test.py +22 -0
- datalab/tests/features/utilities/__init__.py +1 -0
- datalab/tests/features/utilities/installconf_unit_test.py +21 -0
- datalab/tests/features/utilities/logview_app_test.py +21 -0
- datalab/tests/features/utilities/logview_error.py +24 -0
- datalab/tests/features/utilities/logview_unit_test.py +21 -0
- datalab/tests/features/utilities/memstatus_app_test.py +42 -0
- datalab/tests/features/utilities/settings_unit_test.py +88 -0
- datalab/tests/scenarios/__init__.py +1 -0
- datalab/tests/scenarios/beautiful_app.py +121 -0
- datalab/tests/scenarios/common.py +463 -0
- datalab/tests/scenarios/demo.py +212 -0
- datalab/tests/scenarios/example_app_test.py +47 -0
- datalab/tests/scenarios/scenario_h5_app_test.py +75 -0
- datalab/tests/scenarios/scenario_ima1_app_test.py +34 -0
- datalab/tests/scenarios/scenario_ima2_app_test.py +34 -0
- datalab/tests/scenarios/scenario_mac_app_test.py +58 -0
- datalab/tests/scenarios/scenario_sig1_app_test.py +36 -0
- datalab/tests/scenarios/scenario_sig2_app_test.py +35 -0
- datalab/utils/__init__.py +1 -0
- datalab/utils/conf.py +304 -0
- datalab/utils/dephash.py +105 -0
- datalab/utils/qthelpers.py +633 -0
- datalab/utils/strings.py +34 -0
- datalab/utils/tests.py +0 -0
- datalab/widgets/__init__.py +1 -0
- datalab/widgets/connection.py +138 -0
- datalab/widgets/filedialog.py +91 -0
- datalab/widgets/fileviewer.py +84 -0
- datalab/widgets/fitdialog.py +788 -0
- datalab/widgets/h5browser.py +1048 -0
- datalab/widgets/imagebackground.py +111 -0
- datalab/widgets/instconfviewer.py +175 -0
- datalab/widgets/logviewer.py +80 -0
- datalab/widgets/signalbaseline.py +90 -0
- datalab/widgets/signalcursor.py +208 -0
- datalab/widgets/signaldeltax.py +151 -0
- datalab/widgets/signalpeak.py +199 -0
- datalab/widgets/status.py +249 -0
- datalab/widgets/textimport.py +786 -0
- datalab/widgets/warningerror.py +223 -0
- datalab/widgets/wizard.py +286 -0
- datalab_platform-1.0.1.dist-info/METADATA +121 -0
- datalab_platform-1.0.1.dist-info/RECORD +494 -0
- datalab_platform-0.0.1.dev0.dist-info/METADATA +0 -67
- datalab_platform-0.0.1.dev0.dist-info/RECORD +0 -7
- {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/WHEEL +0 -0
- {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/entry_points.txt +0 -0
- {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {datalab_platform-0.0.1.dev0.dist-info → datalab_platform-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
DataLab Qt utilities
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import faulthandler
|
|
10
|
+
import functools
|
|
11
|
+
import inspect
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import os.path as osp
|
|
15
|
+
import shutil
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
import traceback
|
|
19
|
+
from collections.abc import Callable, Generator
|
|
20
|
+
from contextlib import contextmanager
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import guidata
|
|
24
|
+
from guidata.configtools import get_icon
|
|
25
|
+
from guidata.qthelpers import grab_save_window as guidata_grab_save_window
|
|
26
|
+
from guidata.utils.misc import to_string
|
|
27
|
+
from qtpy import QtCore as QC
|
|
28
|
+
from qtpy import QtGui as QG
|
|
29
|
+
from qtpy import QtWidgets as QW
|
|
30
|
+
|
|
31
|
+
from datalab.config import (
|
|
32
|
+
APP_NAME,
|
|
33
|
+
DATETIME_FORMAT,
|
|
34
|
+
SHOTPATH,
|
|
35
|
+
Conf,
|
|
36
|
+
_,
|
|
37
|
+
get_old_log_fname,
|
|
38
|
+
)
|
|
39
|
+
from datalab.env import execenv
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def close_widgets_and_quit(screenshot=False) -> None:
|
|
43
|
+
"""Close Qt top level widgets and quit Qt event loop"""
|
|
44
|
+
for widget in QW.QApplication.instance().topLevelWidgets():
|
|
45
|
+
try:
|
|
46
|
+
wname = widget.objectName()
|
|
47
|
+
except RuntimeError:
|
|
48
|
+
# Object has been deleted
|
|
49
|
+
continue
|
|
50
|
+
if screenshot and wname and widget.isVisible(): # pragma: no cover
|
|
51
|
+
grab_save_window(widget, wname.lower())
|
|
52
|
+
assert widget.close()
|
|
53
|
+
QW.QApplication.instance().quit()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
QAPP_INSTANCE = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_log_contents(fname: str) -> str | None:
|
|
60
|
+
"""Return True if file exists and something was logged in it"""
|
|
61
|
+
if osp.exists(fname):
|
|
62
|
+
with open(fname, "rb") as fdesc:
|
|
63
|
+
return to_string(fdesc.read()).strip()
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def initialize_log_file(fname: str) -> bool:
|
|
68
|
+
"""Eventually keep the previous log file
|
|
69
|
+
Returns True if there was a previous log file"""
|
|
70
|
+
contents = get_log_contents(fname)
|
|
71
|
+
if contents:
|
|
72
|
+
try:
|
|
73
|
+
shutil.move(fname, get_old_log_fname(fname))
|
|
74
|
+
except Exception: # pylint: disable=broad-except
|
|
75
|
+
pass
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def remove_empty_log_file(fname: str) -> None:
|
|
81
|
+
"""Eventually remove empty log files"""
|
|
82
|
+
if not get_log_contents(fname):
|
|
83
|
+
try:
|
|
84
|
+
os.remove(fname)
|
|
85
|
+
except Exception: # pylint: disable=broad-except
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@contextmanager
|
|
90
|
+
def datalab_app_context(
|
|
91
|
+
exec_loop=False, enable_logs=True
|
|
92
|
+
) -> Generator[QW.QApplication, None, None]:
|
|
93
|
+
"""DataLab Qt application context manager, handling Qt application creation
|
|
94
|
+
and persistance, faulthandler/traceback logging features, screenshot mode
|
|
95
|
+
and unattended mode.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
exec_loop: whether to execute Qt event loop (default: False)
|
|
99
|
+
enable_logs: whether to enable logs (default: True)
|
|
100
|
+
"""
|
|
101
|
+
global QAPP_INSTANCE # pylint: disable=global-statement
|
|
102
|
+
if QAPP_INSTANCE is None:
|
|
103
|
+
QAPP_INSTANCE = guidata.qapplication()
|
|
104
|
+
|
|
105
|
+
# === Set application name and version ---------------------------------------------
|
|
106
|
+
# pylint: disable=import-outside-toplevel
|
|
107
|
+
import datalab
|
|
108
|
+
|
|
109
|
+
QAPP_INSTANCE.setApplicationName(APP_NAME)
|
|
110
|
+
QAPP_INSTANCE.setApplicationVersion(datalab.__version__)
|
|
111
|
+
QAPP_INSTANCE.setOrganizationName(APP_NAME + " project")
|
|
112
|
+
|
|
113
|
+
if enable_logs:
|
|
114
|
+
# === Create a logger for standard exceptions ----------------------------------
|
|
115
|
+
tb_log_fname = Conf.main.traceback_log_path.get()
|
|
116
|
+
Conf.main.traceback_log_available.set(initialize_log_file(tb_log_fname))
|
|
117
|
+
logger = logging.getLogger(__name__)
|
|
118
|
+
fmt = "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s"
|
|
119
|
+
logging.basicConfig(
|
|
120
|
+
filename=tb_log_fname,
|
|
121
|
+
filemode="w",
|
|
122
|
+
level=logging.ERROR,
|
|
123
|
+
format=fmt,
|
|
124
|
+
datefmt=DATETIME_FORMAT,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def custom_excepthook(exc_type, exc_value, exc_traceback):
|
|
128
|
+
"Custom exception hook"
|
|
129
|
+
logger.critical(
|
|
130
|
+
"Unhandled exception", exc_info=(exc_type, exc_value, exc_traceback)
|
|
131
|
+
)
|
|
132
|
+
return sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
133
|
+
|
|
134
|
+
sys.excepthook = custom_excepthook
|
|
135
|
+
|
|
136
|
+
# === Use faulthandler for other exceptions ------------------------------------
|
|
137
|
+
fh_log_fname = Conf.main.faulthandler_log_path.get()
|
|
138
|
+
Conf.main.faulthandler_log_available.set(initialize_log_file(fh_log_fname))
|
|
139
|
+
|
|
140
|
+
with open(fh_log_fname, "w", encoding="utf-8") as fh_log_fn:
|
|
141
|
+
if enable_logs and Conf.main.faulthandler_enabled.get(True):
|
|
142
|
+
faulthandler.enable(file=fh_log_fn)
|
|
143
|
+
exception_occured = False
|
|
144
|
+
try:
|
|
145
|
+
yield QAPP_INSTANCE
|
|
146
|
+
except Exception: # pylint: disable=broad-except
|
|
147
|
+
exception_occured = True
|
|
148
|
+
finally:
|
|
149
|
+
if (
|
|
150
|
+
execenv.unattended or execenv.screenshot
|
|
151
|
+
) and not execenv.do_not_quit: # pragma: no cover
|
|
152
|
+
if execenv.delay > 0:
|
|
153
|
+
mode = "Screenshot" if execenv.screenshot else "Unattended"
|
|
154
|
+
message = f"{mode} mode (delay: {execenv.delay}ms)"
|
|
155
|
+
msec = execenv.delay - 200
|
|
156
|
+
for widget in QW.QApplication.instance().topLevelWidgets():
|
|
157
|
+
if isinstance(widget, QW.QMainWindow):
|
|
158
|
+
widget.statusBar().showMessage(message, msec)
|
|
159
|
+
QC.QTimer.singleShot(
|
|
160
|
+
execenv.delay,
|
|
161
|
+
lambda: close_widgets_and_quit(screenshot=execenv.screenshot),
|
|
162
|
+
)
|
|
163
|
+
if exec_loop and not exception_occured:
|
|
164
|
+
QAPP_INSTANCE.exec()
|
|
165
|
+
if exception_occured:
|
|
166
|
+
raise # pylint: disable=misplaced-bare-raise
|
|
167
|
+
|
|
168
|
+
if enable_logs and Conf.main.faulthandler_enabled.get():
|
|
169
|
+
faulthandler.disable()
|
|
170
|
+
remove_empty_log_file(fh_log_fname)
|
|
171
|
+
if enable_logs:
|
|
172
|
+
logging.shutdown()
|
|
173
|
+
remove_empty_log_file(tb_log_fname)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def is_running_tests() -> bool:
|
|
177
|
+
"""Check if code is running during test execution"""
|
|
178
|
+
return "pytest" in sys.modules
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@contextmanager
|
|
182
|
+
def try_or_log_error(context: str) -> Generator[None, None, None]:
|
|
183
|
+
"""Try to execute a function and log an error message if it fails"""
|
|
184
|
+
try:
|
|
185
|
+
yield
|
|
186
|
+
except Exception: # pylint: disable=broad-except
|
|
187
|
+
if is_running_tests():
|
|
188
|
+
# If we are running tests, we want to raise the exception
|
|
189
|
+
raise
|
|
190
|
+
traceback.print_exc()
|
|
191
|
+
logger = logging.getLogger(__name__)
|
|
192
|
+
logger.error("Error in %s", context, exc_info=traceback.format_exc())
|
|
193
|
+
Conf.main.traceback_log_available.set(True)
|
|
194
|
+
finally:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@contextmanager
|
|
199
|
+
def create_progress_bar(
|
|
200
|
+
parent: QW.QWidget, label: str, max_: int, show_after: int = 1000
|
|
201
|
+
) -> Generator[QW.QProgressDialog, None, None]:
|
|
202
|
+
"""Create modal progress bar
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
parent: Parent widget
|
|
206
|
+
label: Progress dialog title
|
|
207
|
+
max_: Maximum progress value
|
|
208
|
+
show_after: Delay before showing the progress dialog (ms, default: 1000)
|
|
209
|
+
"""
|
|
210
|
+
prog = QW.QProgressDialog(label, _("Cancel"), 0, max_, parent, QC.Qt.SplashScreen)
|
|
211
|
+
prog.setWindowModality(QC.Qt.WindowModal)
|
|
212
|
+
prog.setMinimumDuration(show_after)
|
|
213
|
+
try:
|
|
214
|
+
yield prog
|
|
215
|
+
finally:
|
|
216
|
+
prog.close()
|
|
217
|
+
prog.deleteLater()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class CallbackWorker(QC.QThread):
|
|
221
|
+
"""Worker for executing long operations in a separate thread (this must not be
|
|
222
|
+
confused with the :py:class:`datalab.gui.processor.base.Worker` class, which
|
|
223
|
+
handles the execution of computations in a another process)
|
|
224
|
+
|
|
225
|
+
Implements `CallbackWorkerProtocol` from `sigima.worker`, used for computations
|
|
226
|
+
that support cancellation and progress reporting.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
callback: The function to be executed in a separate thread, that takes
|
|
230
|
+
optionnally 'worker' as argument (instance of this class), and any other
|
|
231
|
+
argument passed with **kwargs
|
|
232
|
+
kwargs: Callback keyword arguments
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
SIG_PROGRESS_UPDATE = QC.Signal(int)
|
|
236
|
+
|
|
237
|
+
def __init__(self, callback: Callable, **kwargs) -> None:
|
|
238
|
+
super().__init__()
|
|
239
|
+
self.callback = callback
|
|
240
|
+
if "worker" in inspect.signature(callback).parameters:
|
|
241
|
+
kwargs["worker"] = self
|
|
242
|
+
self.kwargs = kwargs
|
|
243
|
+
self.result: Any | None = None
|
|
244
|
+
self.__canceled = False
|
|
245
|
+
self.__exc = None
|
|
246
|
+
|
|
247
|
+
def run(self) -> None:
|
|
248
|
+
"""Start thread"""
|
|
249
|
+
# Initialize progress bar: setting progress to 0.0 has the effect of
|
|
250
|
+
# showing the progress dialog after the `minimumDuration` time has elapsed.
|
|
251
|
+
# If we don't set the progress to 0.0, the progress dialog will be shown only
|
|
252
|
+
# after the first call to `set_progress` method even if the `minimumDuration`
|
|
253
|
+
# time has elapsed.
|
|
254
|
+
self.set_progress(0.0)
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
self.result = self.callback(**self.kwargs)
|
|
258
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
259
|
+
self.__exc = exc
|
|
260
|
+
|
|
261
|
+
def cancel(self) -> None:
|
|
262
|
+
"""Progress bar was canceled"""
|
|
263
|
+
self.__canceled = True
|
|
264
|
+
|
|
265
|
+
def was_canceled(self) -> bool:
|
|
266
|
+
"""Return whether the progress dialog was canceled by user"""
|
|
267
|
+
return self.__canceled
|
|
268
|
+
|
|
269
|
+
def set_progress(self, value: float) -> None:
|
|
270
|
+
"""Set progress bar value
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
value: float between 0.0 and 1.0
|
|
274
|
+
"""
|
|
275
|
+
self.SIG_PROGRESS_UPDATE.emit(int(100 * value))
|
|
276
|
+
|
|
277
|
+
def get_result(self) -> Any:
|
|
278
|
+
"""Return callback result"""
|
|
279
|
+
if self.__exc is not None:
|
|
280
|
+
raise self.__exc
|
|
281
|
+
return self.result
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def qt_long_callback(
|
|
285
|
+
parent: QW.QWidget,
|
|
286
|
+
label: str,
|
|
287
|
+
worker: CallbackWorker,
|
|
288
|
+
progress: bool,
|
|
289
|
+
show_after: int = 500,
|
|
290
|
+
) -> Any:
|
|
291
|
+
"""Handle long callbacks: run in a separate thread while showing a busy bar
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
parent: Parent widget
|
|
295
|
+
label: Progress dialog title
|
|
296
|
+
worker: Callback worker handling the function execution in a separate thread
|
|
297
|
+
progress: Whether the progress feature is handled or not. If True, a progress
|
|
298
|
+
bar and a 'Cancel' button are shown on the progress dialog. The progress value
|
|
299
|
+
is updated by the `worker.set_progress` method (which takes a float between
|
|
300
|
+
0.0 and 1.0). Moreover, if `progress` is True, we wait for the callback
|
|
301
|
+
function to return (it means that the callback function must implement a
|
|
302
|
+
mechanism to return an intermediate result or `None` if the
|
|
303
|
+
`worker.was_canceled` method returns True).
|
|
304
|
+
show_after: Delay before showing the progress dialog (ms, default: 1000)
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Callback result
|
|
308
|
+
"""
|
|
309
|
+
if progress:
|
|
310
|
+
prog = QW.QProgressDialog(
|
|
311
|
+
label, _("Cancel"), 0, 100, parent, QC.Qt.SplashScreen
|
|
312
|
+
)
|
|
313
|
+
prog.setMinimumDuration(show_after)
|
|
314
|
+
worker.SIG_PROGRESS_UPDATE.connect(prog.setValue)
|
|
315
|
+
prog.canceled.connect(worker.cancel)
|
|
316
|
+
else:
|
|
317
|
+
prog = QW.QProgressDialog(label, None, 0, 0, parent, QC.Qt.SplashScreen)
|
|
318
|
+
prog.setMinimumDuration(0)
|
|
319
|
+
prog.setCancelButton(None)
|
|
320
|
+
prog.setRange(0, 0)
|
|
321
|
+
prog.show()
|
|
322
|
+
prog.setWindowModality(QC.Qt.WindowModal)
|
|
323
|
+
|
|
324
|
+
worker.start()
|
|
325
|
+
while worker.isRunning() and not worker.was_canceled():
|
|
326
|
+
QW.QApplication.processEvents()
|
|
327
|
+
time.sleep(0.005)
|
|
328
|
+
if progress:
|
|
329
|
+
worker.SIG_PROGRESS_UPDATE.disconnect(prog.setValue)
|
|
330
|
+
worker.wait()
|
|
331
|
+
try:
|
|
332
|
+
result = worker.get_result()
|
|
333
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
334
|
+
prog.close()
|
|
335
|
+
prog.deleteLater()
|
|
336
|
+
raise exc
|
|
337
|
+
prog.close()
|
|
338
|
+
prog.deleteLater()
|
|
339
|
+
return result
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def qt_handle_error_message(widget: QW.QWidget, message: str, context: str = None):
|
|
343
|
+
"""Handles application (QWidget) error message"""
|
|
344
|
+
traceback.print_exc()
|
|
345
|
+
txt = str(message)
|
|
346
|
+
msglines = txt.splitlines()
|
|
347
|
+
firstline = _("Error:") if context is None else f"%s: {context}" % _("Context")
|
|
348
|
+
msglines.insert(0, firstline)
|
|
349
|
+
if len(msglines) > 10:
|
|
350
|
+
msglines = msglines[:10] + ["..."]
|
|
351
|
+
title = widget.window().objectName()
|
|
352
|
+
QW.QMessageBox.critical(widget, title, os.linesep.join(msglines))
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def qt_try_except(message=None, context=None):
|
|
356
|
+
"""Try...except Qt widget method decorator"""
|
|
357
|
+
|
|
358
|
+
def qt_try_except_decorator(func):
|
|
359
|
+
"""Try...except Qt widget method decorator"""
|
|
360
|
+
|
|
361
|
+
@functools.wraps(func)
|
|
362
|
+
def method_wrapper(*args, **kwargs):
|
|
363
|
+
"""Decorator wrapper function"""
|
|
364
|
+
self = args[0] # extracting 'self' from method arguments
|
|
365
|
+
# If "self" is a BaseProcessor, then we need to get the panel instance
|
|
366
|
+
panel = getattr(self, "panel", self)
|
|
367
|
+
if message is not None:
|
|
368
|
+
panel.SIG_STATUS_MESSAGE.emit(message, 0)
|
|
369
|
+
QW.QApplication.setOverrideCursor(QG.QCursor(QC.Qt.WaitCursor))
|
|
370
|
+
panel.repaint()
|
|
371
|
+
output = None
|
|
372
|
+
try:
|
|
373
|
+
output = func(*args, **kwargs)
|
|
374
|
+
except Exception as msg: # pylint: disable=broad-except
|
|
375
|
+
if is_running_tests():
|
|
376
|
+
# If we are running tests, we want to raise the exception
|
|
377
|
+
raise
|
|
378
|
+
qt_handle_error_message(panel.parentWidget(), msg, context)
|
|
379
|
+
finally:
|
|
380
|
+
if message is not None:
|
|
381
|
+
panel.SIG_STATUS_MESSAGE.emit("", 0)
|
|
382
|
+
QW.QApplication.restoreOverrideCursor()
|
|
383
|
+
return output
|
|
384
|
+
|
|
385
|
+
return method_wrapper
|
|
386
|
+
|
|
387
|
+
return qt_try_except_decorator
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@contextmanager
|
|
391
|
+
def qt_try_loadsave_file(
|
|
392
|
+
parent: QW.QWidget, filename: str, operation: str
|
|
393
|
+
) -> Generator[str, None, None]:
|
|
394
|
+
"""Try and open file (operation: "load" or "save")"""
|
|
395
|
+
if operation not in ("load", "save"):
|
|
396
|
+
raise ValueError("operation argument must be 'load' or 'save'")
|
|
397
|
+
try:
|
|
398
|
+
yield filename
|
|
399
|
+
except Exception as msg: # pylint: disable=broad-except
|
|
400
|
+
if is_running_tests():
|
|
401
|
+
# If we are running tests, we want to raise the exception
|
|
402
|
+
raise
|
|
403
|
+
traceback.print_exc()
|
|
404
|
+
url = osp.dirname(filename).replace("\\", "/")
|
|
405
|
+
if operation == "load":
|
|
406
|
+
text = _("The file %s could not be read:")
|
|
407
|
+
else:
|
|
408
|
+
text = _("The file %s could not be written:")
|
|
409
|
+
in_folder = _("in this folder")
|
|
410
|
+
message = text % (
|
|
411
|
+
f"<span style='font-weight:bold;color:#555555;'>{osp.basename(filename)}"
|
|
412
|
+
f"</span> (<a href='file:///{url}'>{in_folder}</a>)"
|
|
413
|
+
)
|
|
414
|
+
QW.QMessageBox.critical(parent, APP_NAME, f"{message}<br><br>{str(msg)}")
|
|
415
|
+
finally:
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def grab_save_window(
|
|
420
|
+
widget: QW.QWidget, name: str | None = None, add_timestamp: bool = True
|
|
421
|
+
) -> None: # pragma: no cover
|
|
422
|
+
"""Grab window screenshot and save it.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
widget: Widget to grab
|
|
426
|
+
name: Screenshot name (if None, uses widget.objectName())
|
|
427
|
+
add_timestamp: Whether to add a timestamp to the screenshot name (default: True)
|
|
428
|
+
"""
|
|
429
|
+
if name is None:
|
|
430
|
+
name = widget.objectName()
|
|
431
|
+
|
|
432
|
+
# DataLab-specific logic: determine if timestamp should be added
|
|
433
|
+
# based on name patterns and DataLab conventions
|
|
434
|
+
if name.endswith("_"):
|
|
435
|
+
# Name ending with underscore always gets timestamp
|
|
436
|
+
add_timestamp = True
|
|
437
|
+
elif name[-1].isdigit() or name.startswith(("s_", "i_")):
|
|
438
|
+
# DataLab screenshot names or numbered items don't get timestamp
|
|
439
|
+
add_timestamp = False
|
|
440
|
+
|
|
441
|
+
# Use guidata's grab_save_window with DataLab-specific configuration
|
|
442
|
+
guidata_grab_save_window(
|
|
443
|
+
widget=widget, name=name, save_dir=SHOTPATH, add_timestamp=add_timestamp
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@contextmanager
|
|
448
|
+
def save_restore_stds() -> Generator[None, None, None]:
|
|
449
|
+
"""Save/restore standard I/O before/after doing some things
|
|
450
|
+
(e.g. calling Qt open/save dialogs)"""
|
|
451
|
+
saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
|
|
452
|
+
sys.stdout = None
|
|
453
|
+
try:
|
|
454
|
+
yield
|
|
455
|
+
finally:
|
|
456
|
+
sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@contextmanager
|
|
460
|
+
def block_signals(
|
|
461
|
+
widget: QW.QWidget, enable: bool = True, children: bool = False
|
|
462
|
+
) -> Generator[None, None, None]:
|
|
463
|
+
"""Eventually block/unblock widget Qt signals before/after doing some things
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
widget: Widget to block/unblock signals
|
|
467
|
+
enable: Whether to block/unblock signals (default: True). This is useful
|
|
468
|
+
to avoid blocking signals when not needed without having to handle it by
|
|
469
|
+
adding an `if` statement which would require to duplicate the code that is
|
|
470
|
+
inside the `with` statement in the `else` branch.
|
|
471
|
+
children: Whether to block/unblock signals for child widgets (default: False).
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Context manager
|
|
475
|
+
"""
|
|
476
|
+
if enable:
|
|
477
|
+
widget.blockSignals(True)
|
|
478
|
+
if children:
|
|
479
|
+
for child in widget.findChildren(QW.QWidget):
|
|
480
|
+
child.blockSignals(True)
|
|
481
|
+
try:
|
|
482
|
+
yield
|
|
483
|
+
finally:
|
|
484
|
+
if enable:
|
|
485
|
+
widget.blockSignals(False)
|
|
486
|
+
if children:
|
|
487
|
+
for child in widget.findChildren(QW.QWidget):
|
|
488
|
+
child.blockSignals(False)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def create_menu_button(
|
|
492
|
+
parent: QW.QWidget | None = None, menu: QW.QMenu | None = None
|
|
493
|
+
) -> QW.QPushButton:
|
|
494
|
+
"""Create a menu button
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
parent (QWidget): Parent widget
|
|
498
|
+
menu (QMenu): Menu to attach to the button
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
QW.QPushButton: Menu button
|
|
502
|
+
"""
|
|
503
|
+
button = QW.QPushButton(get_icon("libre-gui-menu.svg"), "", parent)
|
|
504
|
+
button.setFlat(True)
|
|
505
|
+
if menu is not None:
|
|
506
|
+
button.setMenu(menu)
|
|
507
|
+
return button
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def bring_to_front(window: QW.QWidget) -> None:
|
|
511
|
+
"""Bring window to front
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
window: Window to bring to front
|
|
515
|
+
"""
|
|
516
|
+
# Show window on top of others
|
|
517
|
+
eflags = window.windowFlags()
|
|
518
|
+
window.setWindowFlags(eflags | QC.Qt.WindowStaysOnTopHint)
|
|
519
|
+
window.show()
|
|
520
|
+
window.setWindowFlags(eflags)
|
|
521
|
+
window.show()
|
|
522
|
+
# If window is minimized, restore it
|
|
523
|
+
if window.isMinimized():
|
|
524
|
+
window.showNormal()
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def configure_menu_about_to_show(menu: QW.QMenu, slot: Callable) -> None:
|
|
528
|
+
"""Configure menu about to show.
|
|
529
|
+
This method is only used to connect the "aboutToShow" signal of menus,
|
|
530
|
+
and more importantly to fix Issue #15 (Part 2) which is the fact that
|
|
531
|
+
dynamic menus are not supported on MacOS unless an action is added to
|
|
532
|
+
the menu before it is displayed.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
menu: menu
|
|
536
|
+
slot: slot
|
|
537
|
+
"""
|
|
538
|
+
# On MacOS, add an empty action to the menu before connecting the
|
|
539
|
+
# "aboutToShow" signal to the slot. This is required to fix Issue #15 (Part 2)
|
|
540
|
+
if sys.platform == "darwin":
|
|
541
|
+
menu.addAction(QW.QAction(menu))
|
|
542
|
+
menu.aboutToShow.connect(slot)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def resize_widget_to_parent(
|
|
546
|
+
widget: QW.QWidget,
|
|
547
|
+
parent: QW.QWidget | None = None,
|
|
548
|
+
ratio: float = 0.95,
|
|
549
|
+
aspect_ratio: float = 1.0,
|
|
550
|
+
min_size: int = 500,
|
|
551
|
+
) -> None:
|
|
552
|
+
"""Resize widget based on parent widget's dimensions
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
widget: Widget to resize
|
|
556
|
+
parent: Parent widget (if None, uses widget.parentWidget())
|
|
557
|
+
ratio: Ratio of parent size to use (0.0 to 1.0, default: 0.95 for 95%).
|
|
558
|
+
This represents the percentage of the maximum dimension with respect
|
|
559
|
+
to the widget.
|
|
560
|
+
aspect_ratio: Width/height ratio (1.0 for square, >1.0 for landscape,
|
|
561
|
+
<1.0 for portrait, default: 1.0)
|
|
562
|
+
min_size: Minimum size in pixels (default: 500)
|
|
563
|
+
"""
|
|
564
|
+
if parent is None:
|
|
565
|
+
parent = widget.parentWidget()
|
|
566
|
+
|
|
567
|
+
if parent is not None:
|
|
568
|
+
parent_size = parent.size()
|
|
569
|
+
parent_width = parent_size.width()
|
|
570
|
+
parent_height = parent_size.height()
|
|
571
|
+
|
|
572
|
+
# Calculate maximum available dimensions
|
|
573
|
+
max_width = parent_width * ratio
|
|
574
|
+
max_height = parent_height * ratio
|
|
575
|
+
|
|
576
|
+
# Determine which dimension is limiting based on aspect ratio
|
|
577
|
+
# For aspect_ratio = w/h, we have: w = aspect_ratio * h
|
|
578
|
+
# Check which constraint is more restrictive
|
|
579
|
+
width_from_height = max_height * aspect_ratio
|
|
580
|
+
height_from_width = max_width / aspect_ratio
|
|
581
|
+
|
|
582
|
+
if width_from_height <= max_width:
|
|
583
|
+
# Height is the limiting factor
|
|
584
|
+
height = int(max_height)
|
|
585
|
+
width = int(width_from_height)
|
|
586
|
+
else:
|
|
587
|
+
# Width is the limiting factor
|
|
588
|
+
width = int(max_width)
|
|
589
|
+
height = int(height_from_width)
|
|
590
|
+
|
|
591
|
+
# Ensure minimum size while preserving aspect ratio
|
|
592
|
+
if width < min_size or height < min_size:
|
|
593
|
+
if aspect_ratio >= 1.0:
|
|
594
|
+
# Landscape or square: scale up from minimum width
|
|
595
|
+
width = max(width, min_size)
|
|
596
|
+
height = max(height, int(min_size / aspect_ratio))
|
|
597
|
+
else:
|
|
598
|
+
# Portrait: scale up from minimum height
|
|
599
|
+
height = max(height, min_size)
|
|
600
|
+
width = max(width, int(min_size * aspect_ratio))
|
|
601
|
+
|
|
602
|
+
# Final check: ensure we don't exceed parent dimensions
|
|
603
|
+
width = min(width, parent_width)
|
|
604
|
+
height = min(height, parent_height)
|
|
605
|
+
|
|
606
|
+
widget.resize(width, height)
|
|
607
|
+
else:
|
|
608
|
+
# Fallback: use square with min_size if no parent
|
|
609
|
+
widget.resize(min_size, min_size)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def add_corner_menu(
|
|
613
|
+
tabwidget: QW.QTabWidget, corner: QC.Qt.Corner | None = None
|
|
614
|
+
) -> QW.QMenu:
|
|
615
|
+
"""Add menu as corner widget to tab widget
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
tabwidget: Tab widget
|
|
619
|
+
corner: Corner
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Menu
|
|
623
|
+
"""
|
|
624
|
+
if corner is None:
|
|
625
|
+
corner = QC.Qt.TopRightCorner
|
|
626
|
+
menu = QW.QMenu(tabwidget)
|
|
627
|
+
btn = QW.QToolButton(tabwidget)
|
|
628
|
+
btn.setMenu(menu)
|
|
629
|
+
btn.setPopupMode(QW.QToolButton.InstantPopup)
|
|
630
|
+
btn.setIcon(get_icon("menu.svg"))
|
|
631
|
+
btn.setToolTip(_("Open tab menu"))
|
|
632
|
+
tabwidget.setCornerWidget(btn, corner)
|
|
633
|
+
return menu
|
datalab/utils/strings.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
DataLab strings utilities
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import difflib
|
|
10
|
+
import os.path as osp
|
|
11
|
+
import webbrowser
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def save_html_diff(
|
|
15
|
+
text1: str, text2: str, desc1: str, desc2: str, filename: str
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Generates HTML diff between two strings, saves it to a file, and opens it
|
|
18
|
+
in a web browser (Windows only).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
text1 (str): The first string to compare.
|
|
22
|
+
text2 (str): The second string to compare.
|
|
23
|
+
desc1 (str): Description of the first string.
|
|
24
|
+
desc2 (str): Description of the second string.
|
|
25
|
+
filename (str): The name of the file to save the HTML diff to.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
None
|
|
29
|
+
"""
|
|
30
|
+
differ = difflib.HtmlDiff()
|
|
31
|
+
diff_html = differ.make_file(text1.splitlines(), text2.splitlines(), desc1, desc2)
|
|
32
|
+
with open(filename, "w", encoding="utf-8") as file:
|
|
33
|
+
file.write(diff_html)
|
|
34
|
+
webbrowser.open("file://" + osp.realpath(filename))
|
datalab/utils/tests.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|