exhale-xrf 1.5.0__tar.gz
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.
- exhale_xrf-1.5.0/.ruff.toml +22 -0
- exhale_xrf-1.5.0/LICENSE +21 -0
- exhale_xrf-1.5.0/MANIFEST.in +21 -0
- exhale_xrf-1.5.0/PKG-INFO +229 -0
- exhale_xrf-1.5.0/README.md +189 -0
- exhale_xrf-1.5.0/artwork/generate/57-mask-y.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/57-masks.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/extract_xrf_icon.py +190 -0
- exhale_xrf-1.5.0/artwork/generate/generate_xrf_guide.py +680 -0
- exhale_xrf-1.5.0/artwork/generate/install_xrf_splash.py +58 -0
- exhale_xrf-1.5.0/artwork/generate/sprinkle_mask_points.py +144 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_cells_x_imagegen_prompt.md +143 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_guide_57.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_icon_x_dark.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_icon_x_mask.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_icon_x_region.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_icon_x_transparent.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_imagegen_57.png +0 -0
- exhale_xrf-1.5.0/artwork/generate/xrf_reference.png +0 -0
- exhale_xrf-1.5.0/artwork/screenshots/segmentation_20261023.png +0 -0
- exhale_xrf-1.5.0/artwork/unused-icons/LICENSE-icons.txt +21 -0
- exhale_xrf-1.5.0/artwork/unused-icons/atom-checkmark.svg +13 -0
- exhale_xrf-1.5.0/artwork/unused-icons/atom-square.svg +9 -0
- exhale_xrf-1.5.0/artwork/unused-icons/atom-x-mark.svg +12 -0
- exhale_xrf-1.5.0/artwork/unused-icons/checkbox-denied.svg +8 -0
- exhale_xrf-1.5.0/artwork/unused-icons/floppy-pen.svg +6 -0
- exhale_xrf-1.5.0/artwork/unused-icons/photo-plus.svg +10 -0
- exhale_xrf-1.5.0/artwork/unused-icons/photo.svg +2 -0
- exhale_xrf-1.5.0/docs/README.md +13 -0
- exhale_xrf-1.5.0/docs/architecture.md +619 -0
- exhale_xrf-1.5.0/docs/boundary-fitting.md +121 -0
- exhale_xrf-1.5.0/docs/code-overview.md +673 -0
- exhale_xrf-1.5.0/docs/development.md +561 -0
- exhale_xrf-1.5.0/exhale/__init__.py +55 -0
- exhale_xrf-1.5.0/exhale/__main__.py +315 -0
- exhale_xrf-1.5.0/exhale/analysis/__init__.py +1 -0
- exhale_xrf-1.5.0/exhale/analysis/analysis_settings_dialog.py +13 -0
- exhale_xrf-1.5.0/exhale/analysis/analysis_settings_dialog_ui.py +93 -0
- exhale_xrf-1.5.0/exhale/analysis/analysisworker.py +264 -0
- exhale_xrf-1.5.0/exhale/analysis/boundarycalibration.py +189 -0
- exhale_xrf-1.5.0/exhale/analysis/boundaryfitting.py +296 -0
- exhale_xrf-1.5.0/exhale/analysis/cluster_distances.py +230 -0
- exhale_xrf-1.5.0/exhale/analysis/clustering_controller.py +380 -0
- exhale_xrf-1.5.0/exhale/analysis/controller.py +1860 -0
- exhale_xrf-1.5.0/exhale/analysis/correlation_controller.py +904 -0
- exhale_xrf-1.5.0/exhale/analysis/correlations.py +188 -0
- exhale_xrf-1.5.0/exhale/analysis/correlationwidgets.py +908 -0
- exhale_xrf-1.5.0/exhale/analysis/layerwidgets.py +668 -0
- exhale_xrf-1.5.0/exhale/analysis/matrix_export_dialog.py +114 -0
- exhale_xrf-1.5.0/exhale/analysis/matrix_export_dialog_ui.py +145 -0
- exhale_xrf-1.5.0/exhale/analysis/matrixpdf.py +499 -0
- exhale_xrf-1.5.0/exhale/analysis/matrixplots.py +247 -0
- exhale_xrf-1.5.0/exhale/analysis/model_store.py +162 -0
- exhale_xrf-1.5.0/exhale/analysis/naparihelper.py +957 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_backends.py +192 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_batch_dialog.py +110 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_batch_dialog_ui.py +59 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_help_dialog.py +72 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_monai.py +142 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_options_dialog.py +86 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_options_dialog_ui.py +120 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentation_viewer.py +401 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentationbatch.py +211 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentationcursor.py +334 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentationio.py +312 -0
- exhale_xrf-1.5.0/exhale/analysis/segmentationmethods.py +86 -0
- exhale_xrf-1.5.0/exhale/analysis/trainingio.py +129 -0
- exhale_xrf-1.5.0/exhale/appearance.py +156 -0
- exhale_xrf-1.5.0/exhale/data/__init__.py +1 -0
- exhale_xrf-1.5.0/exhale/data/channeldata.py +105 -0
- exhale_xrf-1.5.0/exhale/data/constants.py +12 -0
- exhale_xrf-1.5.0/exhale/data/elementsettings.py +211 -0
- exhale_xrf-1.5.0/exhale/data/imagesettings.py +295 -0
- exhale_xrf-1.5.0/exhale/data/matrixpalettes.py +143 -0
- exhale_xrf-1.5.0/exhale/data/plotsettings.py +162 -0
- exhale_xrf-1.5.0/exhale/data/projectdata.py +932 -0
- exhale_xrf-1.5.0/exhale/data/segmentation.py +98 -0
- exhale_xrf-1.5.0/exhale/data/segmentationbatch.py +47 -0
- exhale_xrf-1.5.0/exhale/data/segmentationediting.py +555 -0
- exhale_xrf-1.5.0/exhale/data/segmentationhistory.py +435 -0
- exhale_xrf-1.5.0/exhale/data/segmentationref.py +96 -0
- exhale_xrf-1.5.0/exhale/data/source_refs.py +287 -0
- exhale_xrf-1.5.0/exhale/data/tiffimport.py +64 -0
- exhale_xrf-1.5.0/exhale/exceptiondialog.py +73 -0
- exhale_xrf-1.5.0/exhale/fileio.py +35 -0
- exhale_xrf-1.5.0/exhale/icons.py +207 -0
- exhale_xrf-1.5.0/exhale/imaging/__init__.py +1 -0
- exhale_xrf-1.5.0/exhale/imaging/element_controller.py +635 -0
- exhale_xrf-1.5.0/exhale/imaging/histogramwidget.py +280 -0
- exhale_xrf-1.5.0/exhale/imaging/image_controller.py +601 -0
- exhale_xrf-1.5.0/exhale/imaging/image_settings_dialog.py +13 -0
- exhale_xrf-1.5.0/exhale/imaging/image_settings_dialog_ui.py +161 -0
- exhale_xrf-1.5.0/exhale/imaging/imagecanvas.py +467 -0
- exhale_xrf-1.5.0/exhale/imaging/imagecomposer.py +685 -0
- exhale_xrf-1.5.0/exhale/imaging/listwidgets.py +353 -0
- exhale_xrf-1.5.0/exhale/imaging/pdfexport.py +127 -0
- exhale_xrf-1.5.0/exhale/main_window.py +518 -0
- exhale_xrf-1.5.0/exhale/main_window_ui.py +1548 -0
- exhale_xrf-1.5.0/exhale/model_settings.py +23 -0
- exhale_xrf-1.5.0/exhale/overridecursor.py +18 -0
- exhale_xrf-1.5.0/exhale/project/__init__.py +1 -0
- exhale_xrf-1.5.0/exhale/project/project_tab.py +593 -0
- exhale_xrf-1.5.0/exhale/project/projectio.py +926 -0
- exhale_xrf-1.5.0/exhale/project/projectmanager.py +297 -0
- exhale_xrf-1.5.0/exhale/project/projectstatemanager.py +286 -0
- exhale_xrf-1.5.0/exhale/project/sourcemanager.py +197 -0
- exhale_xrf-1.5.0/exhale/resources/icons/LICENSE-icons.txt +21 -0
- exhale_xrf-1.5.0/exhale/resources/icons/adjustments.svg +13 -0
- exhale_xrf-1.5.0/exhale/resources/icons/arrow-back-up.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/arrow-forward-up.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/arrows-join.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/arrows-maximize.svg +12 -0
- exhale_xrf-1.5.0/exhale/resources/icons/atom.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/bolt.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/brush.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/checkbox-default-marked.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/checkbox-denied-extended.svg +8 -0
- exhale_xrf-1.5.0/exhale/resources/icons/checkbox-marked.svg +8 -0
- exhale_xrf-1.5.0/exhale/resources/icons/checkbox.svg +4 -0
- exhale_xrf-1.5.0/exhale/resources/icons/checkmark.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/clipboard-copy.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/cut.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/eraser.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/file-dots.svg +9 -0
- exhale_xrf-1.5.0/exhale/resources/icons/floppy.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/folder-open.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/help.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/info-circle.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/lasso.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/layer-one.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/layer-plus.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/layers-atom.svg +13 -0
- exhale_xrf-1.5.0/exhale/resources/icons/layers-plus.svg +9 -0
- exhale_xrf-1.5.0/exhale/resources/icons/layers.svg +9 -0
- exhale_xrf-1.5.0/exhale/resources/icons/lock.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/open.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/player-stop.svg +5 -0
- exhale_xrf-1.5.0/exhale/resources/icons/pointer.svg +4 -0
- exhale_xrf-1.5.0/exhale/resources/icons/rays.svg +12 -0
- exhale_xrf-1.5.0/exhale/resources/icons/refresh.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/screenshot.svg +13 -0
- exhale_xrf-1.5.0/exhale/resources/icons/settings.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/squares-composed-plus.svg +12 -0
- exhale_xrf-1.5.0/exhale/resources/icons/squares-composed.svg +11 -0
- exhale_xrf-1.5.0/exhale/resources/icons/stack-2.svg +7 -0
- exhale_xrf-1.5.0/exhale/resources/icons/stack-bolt.svg +9 -0
- exhale_xrf-1.5.0/exhale/resources/icons/trash.svg +9 -0
- exhale_xrf-1.5.0/exhale/resources/icons/x-mark.svg +6 -0
- exhale_xrf-1.5.0/exhale/resources/icons/xrf.png +0 -0
- exhale_xrf-1.5.0/exhale/resources/licenses/DINOv3-LICENSE.md +66 -0
- exhale_xrf-1.5.0/exhale/resources/linux/io.github.ctroein.Exhale.desktop +12 -0
- exhale_xrf-1.5.0/exhale/resources/models/2D_versatile_fluo_copy/config.json +1 -0
- exhale_xrf-1.5.0/exhale/resources/models/2D_versatile_fluo_copy/thresholds.json +1 -0
- exhale_xrf-1.5.0/exhale/resources/models/2D_versatile_fluo_copy/weights_best.h5 +0 -0
- exhale_xrf-1.5.0/exhale/resources/models/monai_unet/metadata.json +35 -0
- exhale_xrf-1.5.0/exhale/resources/models/monai_unet/model.pt +0 -0
- exhale_xrf-1.5.0/exhale/resources/xrf_splash.jpg +0 -0
- exhale_xrf-1.5.0/exhale/runtimeinfo.py +65 -0
- exhale_xrf-1.5.0/exhale/settings_dialog.py +84 -0
- exhale_xrf-1.5.0/exhale/settings_dialog_ui.py +94 -0
- exhale_xrf-1.5.0/exhale/tooltips.py +228 -0
- exhale_xrf-1.5.0/exhale/update_controller.py +189 -0
- exhale_xrf-1.5.0/exhale/updates.py +304 -0
- exhale_xrf-1.5.0/exhale/widgets.py +189 -0
- exhale_xrf-1.5.0/exhale/xrf_refcopy/xrf_element_channel.py +123 -0
- exhale_xrf-1.5.0/exhale/xrf_refcopy/xrf_other_channel.py +108 -0
- exhale_xrf-1.5.0/exhale/xrf_refcopy/xrf_sample_class.py +579 -0
- exhale_xrf-1.5.0/exhale/xrf_refcopy/xrf_utils.py +454 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/PKG-INFO +229 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/SOURCES.txt +245 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/dependency_links.txt +1 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/entry_points.txt +2 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/requires.txt +35 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/scm_file_list.json +263 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/scm_version.json +8 -0
- exhale_xrf-1.5.0/exhale_xrf.egg-info/top_level.txt +7 -0
- exhale_xrf-1.5.0/pyproject.toml +85 -0
- exhale_xrf-1.5.0/requirements-cellpose.txt +3 -0
- exhale_xrf-1.5.0/run_exhale.py +6 -0
- exhale_xrf-1.5.0/setup.cfg +4 -0
- exhale_xrf-1.5.0/tests/test_analysis_stages.py +110 -0
- exhale_xrf-1.5.0/tests/test_analysisworker.py +40 -0
- exhale_xrf-1.5.0/tests/test_application_update_trial.py +123 -0
- exhale_xrf-1.5.0/tests/test_artwork_generation.py +169 -0
- exhale_xrf-1.5.0/tests/test_boundary_calibration_cli.py +100 -0
- exhale_xrf-1.5.0/tests/test_boundaryfitting.py +132 -0
- exhale_xrf-1.5.0/tests/test_cluster_distance_worker.py +220 -0
- exhale_xrf-1.5.0/tests/test_cluster_distances.py +179 -0
- exhale_xrf-1.5.0/tests/test_correlations.py +252 -0
- exhale_xrf-1.5.0/tests/test_correlationwidgets.py +435 -0
- exhale_xrf-1.5.0/tests/test_elementsettings_defaults.py +84 -0
- exhale_xrf-1.5.0/tests/test_entrypoints.py +109 -0
- exhale_xrf-1.5.0/tests/test_histogramwidget.py +183 -0
- exhale_xrf-1.5.0/tests/test_icon_check_delegate.py +142 -0
- exhale_xrf-1.5.0/tests/test_imagecanvas.py +234 -0
- exhale_xrf-1.5.0/tests/test_imagecomposer_mapping.py +239 -0
- exhale_xrf-1.5.0/tests/test_intensity_partition.py +94 -0
- exhale_xrf-1.5.0/tests/test_layer_solo.py +231 -0
- exhale_xrf-1.5.0/tests/test_macos_runtime.py +263 -0
- exhale_xrf-1.5.0/tests/test_matrix_export_dialog.py +87 -0
- exhale_xrf-1.5.0/tests/test_matrix_export_routing.py +111 -0
- exhale_xrf-1.5.0/tests/test_matrixpdf.py +218 -0
- exhale_xrf-1.5.0/tests/test_model_settings.py +34 -0
- exhale_xrf-1.5.0/tests/test_model_store.py +224 -0
- exhale_xrf-1.5.0/tests/test_napari_initial_fit.py +94 -0
- exhale_xrf-1.5.0/tests/test_naparihelper.py +473 -0
- exhale_xrf-1.5.0/tests/test_native_updates.py +57 -0
- exhale_xrf-1.5.0/tests/test_outlinefitting.py +112 -0
- exhale_xrf-1.5.0/tests/test_output_permissions.py +82 -0
- exhale_xrf-1.5.0/tests/test_plot_integration.py +263 -0
- exhale_xrf-1.5.0/tests/test_plotsettings.py +89 -0
- exhale_xrf-1.5.0/tests/test_project_paths.py +71 -0
- exhale_xrf-1.5.0/tests/test_projectdata.py +251 -0
- exhale_xrf-1.5.0/tests/test_qt_compat.py +95 -0
- exhale_xrf-1.5.0/tests/test_samples.py +212 -0
- exhale_xrf-1.5.0/tests/test_segmentation_backends.py +139 -0
- exhale_xrf-1.5.0/tests/test_segmentation_core.py +197 -0
- exhale_xrf-1.5.0/tests/test_segmentation_editing_controller.py +92 -0
- exhale_xrf-1.5.0/tests/test_segmentation_history_io.py +373 -0
- exhale_xrf-1.5.0/tests/test_segmentation_lifecycle.py +107 -0
- exhale_xrf-1.5.0/tests/test_segmentation_monai.py +230 -0
- exhale_xrf-1.5.0/tests/test_segmentation_postprocessing.py +139 -0
- exhale_xrf-1.5.0/tests/test_segmentation_preferences.py +97 -0
- exhale_xrf-1.5.0/tests/test_segmentation_selection_controller.py +80 -0
- exhale_xrf-1.5.0/tests/test_segmentation_viewer.py +552 -0
- exhale_xrf-1.5.0/tests/test_segmentationbatch.py +373 -0
- exhale_xrf-1.5.0/tests/test_segmentationcursor.py +155 -0
- exhale_xrf-1.5.0/tests/test_segmentationediting.py +577 -0
- exhale_xrf-1.5.0/tests/test_segmentationhistory.py +346 -0
- exhale_xrf-1.5.0/tests/test_segmentationio.py +75 -0
- exhale_xrf-1.5.0/tests/test_segmentationref.py +156 -0
- exhale_xrf-1.5.0/tests/test_smartoutline.py +129 -0
- exhale_xrf-1.5.0/tests/test_source_lifecycle.py +1994 -0
- exhale_xrf-1.5.0/tests/test_source_readers.py +179 -0
- exhale_xrf-1.5.0/tests/test_tensorflow_cuda.py +30 -0
- exhale_xrf-1.5.0/tests/test_training_review_controller.py +63 -0
- exhale_xrf-1.5.0/tests/test_trainingio.py +149 -0
- exhale_xrf-1.5.0/tests/test_update_controller.py +90 -0
- exhale_xrf-1.5.0/tests/test_updates.py +202 -0
- exhale_xrf-1.5.0/tests/test_version.py +38 -0
- exhale_xrf-1.5.0/tests/test_widgets.py +65 -0
- exhale_xrf-1.5.0/tests/test_windows_installer.py +74 -0
- exhale_xrf-1.5.0/tools/fit_boundary_parameters.py +278 -0
- exhale_xrf-1.5.0/tools/prepare_model_release.py +51 -0
- exhale_xrf-1.5.0/tools/try_application_update.py +208 -0
- exhale_xrf-1.5.0/tools/try_dependency_updates.py +129 -0
- exhale_xrf-1.5.0/version_bump.sh +93 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
target-version = "py310"
|
|
2
|
+
line-length = 100
|
|
3
|
+
extend-exclude = [
|
|
4
|
+
"exhale/*_ui.py",
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[lint]
|
|
8
|
+
select = [
|
|
9
|
+
"E301", "E302", "E303", "E304", "E305", "E306",
|
|
10
|
+
"E4", "E7", "E9", "F", "W",
|
|
11
|
+
"I", "B", "UP", "RUF100",
|
|
12
|
+
]
|
|
13
|
+
ignore = ["E501"]
|
|
14
|
+
preview = true
|
|
15
|
+
explicit-preview-rules = true
|
|
16
|
+
|
|
17
|
+
[format]
|
|
18
|
+
quote-style = "double"
|
|
19
|
+
indent-style = "space"
|
|
20
|
+
line-ending = "auto"
|
|
21
|
+
skip-magic-trailing-comma = false
|
|
22
|
+
docstring-code-format = false
|
exhale_xrf-1.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carl Troein, Tom Delaire, Bryan Falcones, Emanuel Larsson, Karina Thånell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include pyproject.toml
|
|
3
|
+
include MANIFEST.in
|
|
4
|
+
include run_exhale.py
|
|
5
|
+
|
|
6
|
+
recursive-include exhale *.py
|
|
7
|
+
recursive-include exhale/resources/icons *.png *.svg *.txt
|
|
8
|
+
recursive-include exhale/resources/linux *.desktop
|
|
9
|
+
recursive-include exhale/resources/licenses *
|
|
10
|
+
include exhale/resources/*.jpg
|
|
11
|
+
recursive-include exhale/resources/models *.json *.h5 *.pt
|
|
12
|
+
|
|
13
|
+
exclude .gitignore
|
|
14
|
+
|
|
15
|
+
prune .github
|
|
16
|
+
prune build
|
|
17
|
+
prune dist
|
|
18
|
+
prune packaging
|
|
19
|
+
prune exhale/resources/ui
|
|
20
|
+
|
|
21
|
+
global-exclude __pycache__ *.py[cod]
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exhale-xrf
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Exhale, Efficient X-ray Hub Aiding Lung Explorations
|
|
5
|
+
Author: Tom Delaire, Emanuel Larsson, Bryan Falcones, Karina Thånell
|
|
6
|
+
Author-email: Carl Troein <carl.troein@mgeo.lu.se>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/ctroein/exhale
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
10
|
+
Requires-Python: <3.14,>=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: packaging<27
|
|
14
|
+
Requires-Dist: numpy<2.6
|
|
15
|
+
Requires-Dist: scipy<1.19
|
|
16
|
+
Requires-Dist: scikit-learn<1.10
|
|
17
|
+
Requires-Dist: pandas<3.2
|
|
18
|
+
Requires-Dist: h5py<3.17
|
|
19
|
+
Requires-Dist: tifffile<2026.11
|
|
20
|
+
Requires-Dist: imagecodecs<2026.11
|
|
21
|
+
Requires-Dist: napari<0.11,>=0.10
|
|
22
|
+
Requires-Dist: imageio<2.39
|
|
23
|
+
Requires-Dist: pyqt6<6.13,>=6.7
|
|
24
|
+
Requires-Dist: qtpy<2.5
|
|
25
|
+
Requires-Dist: pyqtgraph<0.15,>=0.13.7
|
|
26
|
+
Provides-Extra: stardist
|
|
27
|
+
Requires-Dist: csbdeep<0.9; extra == "stardist"
|
|
28
|
+
Requires-Dist: tensorflow==2.15.*; sys_platform == "win32" and extra == "stardist"
|
|
29
|
+
Requires-Dist: tensorflow<2.21; sys_platform == "linux" and extra == "stardist"
|
|
30
|
+
Requires-Dist: tensorflow<2.21; sys_platform == "darwin" and extra == "stardist"
|
|
31
|
+
Requires-Dist: stardist<0.10; extra == "stardist"
|
|
32
|
+
Provides-Extra: cellpose
|
|
33
|
+
Requires-Dist: torch<2.16,>=2.8; extra == "cellpose"
|
|
34
|
+
Requires-Dist: torchvision<0.30,>=0.23; extra == "cellpose"
|
|
35
|
+
Requires-Dist: cellpose==4.2.1.1; extra == "cellpose"
|
|
36
|
+
Provides-Extra: monai
|
|
37
|
+
Requires-Dist: torch<2.16,>=2.8; extra == "monai"
|
|
38
|
+
Requires-Dist: monai<1.7,>=1.6.1; extra == "monai"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# Exhale
|
|
42
|
+
## Efficient X-ray Hub Aiding Lung Explorations
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<img src="artwork/screenshots/segmentation_20261023.png"
|
|
46
|
+
alt="EXHALE segmentation results"
|
|
47
|
+
width="900">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
This project aims to develop a specialized tool for investigating elemental aspects of chronic lung diseases.
|
|
51
|
+
|
|
52
|
+
From the [VINNOVA project
|
|
53
|
+
description](https://www.vinnova.se/en/p/exhale--efficient-x-ray-hub-aiding-lung-explorations/):
|
|
54
|
+
The purpose of this project is to advance understanding and treatment of
|
|
55
|
+
chronic lung diseases, leading causes of mortality worldwide, by
|
|
56
|
+
characterizing elements in lung tissue suspected to impact their
|
|
57
|
+
pathogenesis. The industrial partners TrulyLabs and AnaMar in collaboration
|
|
58
|
+
with MAX IV, Lung Biology and Correlative Image Processing and Analysis
|
|
59
|
+
(CIPA) fom Lund University, aim to streamline a workflow of X-ray imaging
|
|
60
|
+
studies for lung diseases, making these methods more accessible for
|
|
61
|
+
biomedical companies and clinical researchers.
|
|
62
|
+
|
|
63
|
+
## Currect functionality
|
|
64
|
+
|
|
65
|
+
* Loading HDF5 files with XRF data from the NanoMAX beamline at MAX IV.
|
|
66
|
+
* Composing element maps into publication-ready figures.
|
|
67
|
+
* Segmentation of cells and clustering of areas in element maps.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
### Linux
|
|
72
|
+
|
|
73
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if needed,
|
|
74
|
+
then choose the segmentation backends you want.
|
|
75
|
+
|
|
76
|
+
For StarDist (TensorFlow):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv tool install --python 3.12 'exhale-xrf[stardist]'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For Cellpose and MONAI U-Net (PyTorch; Cellpose also needs DINOv3):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv tool install --python 3.12 --with-requirements https://raw.githubusercontent.com/ctroein/exhale/main/requirements-cellpose.txt 'exhale-xrf[cellpose,monai]'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For MONAI U-Net alone:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv tool install --python 3.12 'exhale-xrf[monai]'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
MONAI U-Net's trained weights are bundled; no model download is needed.
|
|
95
|
+
|
|
96
|
+
To also include StarDist, use `'exhale-xrf[stardist,cellpose,monai]'` with the same additional
|
|
97
|
+
requirements argument. Plain `exhale-xrf` installs viewing, composition and
|
|
98
|
+
analysis of existing results without neural segmentation backends. Unavailable
|
|
99
|
+
backends are disabled in the application.
|
|
100
|
+
|
|
101
|
+
Run `exhale` to start the application. To add it to the current user's desktop
|
|
102
|
+
environment and application menu:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
exhale --install-desktop
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
To update the application and its selected backends:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv tool upgrade exhale-xrf
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
On Linux, Settings also offers daily update checks and a Check now button.
|
|
115
|
+
Checks notify you of newer stable releases without installing anything. For a
|
|
116
|
+
writable uv tool installation owned by your account, Update now closes Exhale,
|
|
117
|
+
runs the upgrade, checks startup, and reopens it. Normal project saving applies
|
|
118
|
+
before closing. Other Exhale instances must be closed first. Update results and
|
|
119
|
+
the log location are shown after restarting. Automatic checks can be disabled
|
|
120
|
+
in Settings.
|
|
121
|
+
|
|
122
|
+
uv retains the installation settings, including extras and additional
|
|
123
|
+
requirements. To change backend selection, rerun `uv tool install` with the
|
|
124
|
+
wanted extras and, for Cellpose, the requirements argument.
|
|
125
|
+
|
|
126
|
+
To uninstall, remove the desktop/menu entry before removing the application:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
exhale --remove-desktop
|
|
130
|
+
uv tool uninstall exhale-xrf
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Windows
|
|
134
|
+
|
|
135
|
+
Windows 10 or later with 64-bit compatibility is required. Download the
|
|
136
|
+
`exhale-…-setup.exe` installer from the
|
|
137
|
+
[GitHub releases page](https://github.com/ctroein/exhale/releases) and run it.
|
|
138
|
+
|
|
139
|
+
The installer offers StarDist, Cellpose, both, or neither before setup. It
|
|
140
|
+
downloads a private Python environment and the selected packages; internet
|
|
141
|
+
access is required, but an existing Python or uv installation is not.
|
|
142
|
+
Use the Start menu or optional desktop shortcut to launch EXHALE.
|
|
143
|
+
|
|
144
|
+
Settings offers daily update checks, Check now, and Update now. Application
|
|
145
|
+
updates preserve the selected backends and validate a new runtime before
|
|
146
|
+
switching to it. You can also download and run a newer installer; use that to
|
|
147
|
+
update the launcher/setup tools, change backends, or repair the installation.
|
|
148
|
+
Uninstall through Windows Settings; the private runtime and shortcuts are
|
|
149
|
+
removed while projects, trained models and application settings are retained.
|
|
150
|
+
|
|
151
|
+
### macOS
|
|
152
|
+
|
|
153
|
+
Download the Apple Silicon (`arm64`) or Intel (`x86_64`) DMG from the
|
|
154
|
+
[GitHub releases page](https://github.com/ctroein/exhale/releases), drag
|
|
155
|
+
EXHALE.app into Applications, and open it there. Setup downloads Python and the
|
|
156
|
+
selected segmentation backends; neither Python nor uv needs to be installed
|
|
157
|
+
beforehand. Intel Macs support StarDist but not the current Cellpose backend.
|
|
158
|
+
The launcher requires macOS 13 or newer; Cellpose's PyTorch requires at least
|
|
159
|
+
macOS 14 on Apple Silicon, and dependencies may require newer versions.
|
|
160
|
+
|
|
161
|
+
The Mac app is not Developer ID signed or notarized. macOS may block its first
|
|
162
|
+
launch; see [Apple's explanation and opening options](https://support.apple.com/102445).
|
|
163
|
+
|
|
164
|
+
Subsequent launches open the installed application. Hold Option while opening
|
|
165
|
+
EXHALE.app to change backends, repair the installation, or remove the private
|
|
166
|
+
runtime. Settings offers daily update checks and Update now, which preserves
|
|
167
|
+
the backends and validates a new runtime before switching to it. To update the
|
|
168
|
+
launcher/setup tools, quit EXHALE and replace the app with a newer release; setup
|
|
169
|
+
will install its bundled release with your previous backend choices. For removal, use
|
|
170
|
+
**Remove Runtime** in setup, then move EXHALE.app to Trash. Projects, model files,
|
|
171
|
+
and application settings are retained.
|
|
172
|
+
|
|
173
|
+
Alternatively, use the same uv installation, backend selection and update
|
|
174
|
+
commands as on Linux. Start the application with `exhale`. The desktop/menu
|
|
175
|
+
registration commands are Linux-only; to uninstall a Mac uv installation, run:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
uv tool uninstall exhale-xrf
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
App-managed environments are separate from installations made with
|
|
182
|
+
`uv tool install`.
|
|
183
|
+
|
|
184
|
+
### pip and source installations
|
|
185
|
+
|
|
186
|
+
Use a virtual environment with a supported Python version (3.11–3.13).
|
|
187
|
+
**Windows StarDist requires Python 3.11** because of its TensorFlow dependency.
|
|
188
|
+
|
|
189
|
+
For StarDist:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
python -m pip install 'exhale-xrf[stardist]'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
For Cellpose and MONAI U-Net (Apple Silicon required on macOS):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
python -m pip install 'exhale-xrf[cellpose,monai]' -r https://raw.githubusercontent.com/ctroein/exhale/main/requirements-cellpose.txt
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
For MONAI U-Net alone, use `python -m pip install 'exhale-xrf[monai]'`; no
|
|
202
|
+
DINOv3 requirements file is needed. Use `'exhale-xrf[stardist,cellpose,monai]'` with
|
|
203
|
+
the same requirements argument to include all three methods. For a Git checkout:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
python -m pip install -e '.[stardist,cellpose,monai]' -r requirements-cellpose.txt
|
|
207
|
+
./run_exhale.py
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Omit unwanted extras and the requirements file when Cellpose is not needed.
|
|
211
|
+
On Windows, launch the checkout with `python run_exhale.py`.
|
|
212
|
+
|
|
213
|
+
### DINOv3 and trained models
|
|
214
|
+
|
|
215
|
+
The additional requirements file pins DINOv3 to a tested source archive; Git is
|
|
216
|
+
not required. For a particular EXHALE release, replace `main` in the requirements
|
|
217
|
+
URL with its `vVERSION` tag and append `==VERSION` to the package specification.
|
|
218
|
+
A version-pinned uv installation must be reinstalled with a newer version
|
|
219
|
+
specification to upgrade.
|
|
220
|
+
|
|
221
|
+
Trained Cellpose weights are downloaded on first use to the model directory in
|
|
222
|
+
application settings. The same mechanism is used for installed applications and
|
|
223
|
+
Git checkouts; the large weights are not included in the application installer.
|
|
224
|
+
DINOv3 code and derived model weights retain the
|
|
225
|
+
[DINOv3 license](exhale/resources/licenses/DINOv3-LICENSE.md).
|
|
226
|
+
|
|
227
|
+
## Developer documentation
|
|
228
|
+
|
|
229
|
+
Developer documentation is located in [`docs/`](docs/README.md).
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Exhale
|
|
2
|
+
## Efficient X-ray Hub Aiding Lung Explorations
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<img src="artwork/screenshots/segmentation_20261023.png"
|
|
6
|
+
alt="EXHALE segmentation results"
|
|
7
|
+
width="900">
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
This project aims to develop a specialized tool for investigating elemental aspects of chronic lung diseases.
|
|
11
|
+
|
|
12
|
+
From the [VINNOVA project
|
|
13
|
+
description](https://www.vinnova.se/en/p/exhale--efficient-x-ray-hub-aiding-lung-explorations/):
|
|
14
|
+
The purpose of this project is to advance understanding and treatment of
|
|
15
|
+
chronic lung diseases, leading causes of mortality worldwide, by
|
|
16
|
+
characterizing elements in lung tissue suspected to impact their
|
|
17
|
+
pathogenesis. The industrial partners TrulyLabs and AnaMar in collaboration
|
|
18
|
+
with MAX IV, Lung Biology and Correlative Image Processing and Analysis
|
|
19
|
+
(CIPA) fom Lund University, aim to streamline a workflow of X-ray imaging
|
|
20
|
+
studies for lung diseases, making these methods more accessible for
|
|
21
|
+
biomedical companies and clinical researchers.
|
|
22
|
+
|
|
23
|
+
## Currect functionality
|
|
24
|
+
|
|
25
|
+
* Loading HDF5 files with XRF data from the NanoMAX beamline at MAX IV.
|
|
26
|
+
* Composing element maps into publication-ready figures.
|
|
27
|
+
* Segmentation of cells and clustering of areas in element maps.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### Linux
|
|
32
|
+
|
|
33
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if needed,
|
|
34
|
+
then choose the segmentation backends you want.
|
|
35
|
+
|
|
36
|
+
For StarDist (TensorFlow):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install --python 3.12 'exhale-xrf[stardist]'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For Cellpose and MONAI U-Net (PyTorch; Cellpose also needs DINOv3):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv tool install --python 3.12 --with-requirements https://raw.githubusercontent.com/ctroein/exhale/main/requirements-cellpose.txt 'exhale-xrf[cellpose,monai]'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For MONAI U-Net alone:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv tool install --python 3.12 'exhale-xrf[monai]'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
MONAI U-Net's trained weights are bundled; no model download is needed.
|
|
55
|
+
|
|
56
|
+
To also include StarDist, use `'exhale-xrf[stardist,cellpose,monai]'` with the same additional
|
|
57
|
+
requirements argument. Plain `exhale-xrf` installs viewing, composition and
|
|
58
|
+
analysis of existing results without neural segmentation backends. Unavailable
|
|
59
|
+
backends are disabled in the application.
|
|
60
|
+
|
|
61
|
+
Run `exhale` to start the application. To add it to the current user's desktop
|
|
62
|
+
environment and application menu:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
exhale --install-desktop
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
To update the application and its selected backends:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv tool upgrade exhale-xrf
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
On Linux, Settings also offers daily update checks and a Check now button.
|
|
75
|
+
Checks notify you of newer stable releases without installing anything. For a
|
|
76
|
+
writable uv tool installation owned by your account, Update now closes Exhale,
|
|
77
|
+
runs the upgrade, checks startup, and reopens it. Normal project saving applies
|
|
78
|
+
before closing. Other Exhale instances must be closed first. Update results and
|
|
79
|
+
the log location are shown after restarting. Automatic checks can be disabled
|
|
80
|
+
in Settings.
|
|
81
|
+
|
|
82
|
+
uv retains the installation settings, including extras and additional
|
|
83
|
+
requirements. To change backend selection, rerun `uv tool install` with the
|
|
84
|
+
wanted extras and, for Cellpose, the requirements argument.
|
|
85
|
+
|
|
86
|
+
To uninstall, remove the desktop/menu entry before removing the application:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
exhale --remove-desktop
|
|
90
|
+
uv tool uninstall exhale-xrf
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Windows
|
|
94
|
+
|
|
95
|
+
Windows 10 or later with 64-bit compatibility is required. Download the
|
|
96
|
+
`exhale-…-setup.exe` installer from the
|
|
97
|
+
[GitHub releases page](https://github.com/ctroein/exhale/releases) and run it.
|
|
98
|
+
|
|
99
|
+
The installer offers StarDist, Cellpose, both, or neither before setup. It
|
|
100
|
+
downloads a private Python environment and the selected packages; internet
|
|
101
|
+
access is required, but an existing Python or uv installation is not.
|
|
102
|
+
Use the Start menu or optional desktop shortcut to launch EXHALE.
|
|
103
|
+
|
|
104
|
+
Settings offers daily update checks, Check now, and Update now. Application
|
|
105
|
+
updates preserve the selected backends and validate a new runtime before
|
|
106
|
+
switching to it. You can also download and run a newer installer; use that to
|
|
107
|
+
update the launcher/setup tools, change backends, or repair the installation.
|
|
108
|
+
Uninstall through Windows Settings; the private runtime and shortcuts are
|
|
109
|
+
removed while projects, trained models and application settings are retained.
|
|
110
|
+
|
|
111
|
+
### macOS
|
|
112
|
+
|
|
113
|
+
Download the Apple Silicon (`arm64`) or Intel (`x86_64`) DMG from the
|
|
114
|
+
[GitHub releases page](https://github.com/ctroein/exhale/releases), drag
|
|
115
|
+
EXHALE.app into Applications, and open it there. Setup downloads Python and the
|
|
116
|
+
selected segmentation backends; neither Python nor uv needs to be installed
|
|
117
|
+
beforehand. Intel Macs support StarDist but not the current Cellpose backend.
|
|
118
|
+
The launcher requires macOS 13 or newer; Cellpose's PyTorch requires at least
|
|
119
|
+
macOS 14 on Apple Silicon, and dependencies may require newer versions.
|
|
120
|
+
|
|
121
|
+
The Mac app is not Developer ID signed or notarized. macOS may block its first
|
|
122
|
+
launch; see [Apple's explanation and opening options](https://support.apple.com/102445).
|
|
123
|
+
|
|
124
|
+
Subsequent launches open the installed application. Hold Option while opening
|
|
125
|
+
EXHALE.app to change backends, repair the installation, or remove the private
|
|
126
|
+
runtime. Settings offers daily update checks and Update now, which preserves
|
|
127
|
+
the backends and validates a new runtime before switching to it. To update the
|
|
128
|
+
launcher/setup tools, quit EXHALE and replace the app with a newer release; setup
|
|
129
|
+
will install its bundled release with your previous backend choices. For removal, use
|
|
130
|
+
**Remove Runtime** in setup, then move EXHALE.app to Trash. Projects, model files,
|
|
131
|
+
and application settings are retained.
|
|
132
|
+
|
|
133
|
+
Alternatively, use the same uv installation, backend selection and update
|
|
134
|
+
commands as on Linux. Start the application with `exhale`. The desktop/menu
|
|
135
|
+
registration commands are Linux-only; to uninstall a Mac uv installation, run:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv tool uninstall exhale-xrf
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
App-managed environments are separate from installations made with
|
|
142
|
+
`uv tool install`.
|
|
143
|
+
|
|
144
|
+
### pip and source installations
|
|
145
|
+
|
|
146
|
+
Use a virtual environment with a supported Python version (3.11–3.13).
|
|
147
|
+
**Windows StarDist requires Python 3.11** because of its TensorFlow dependency.
|
|
148
|
+
|
|
149
|
+
For StarDist:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python -m pip install 'exhale-xrf[stardist]'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For Cellpose and MONAI U-Net (Apple Silicon required on macOS):
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python -m pip install 'exhale-xrf[cellpose,monai]' -r https://raw.githubusercontent.com/ctroein/exhale/main/requirements-cellpose.txt
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For MONAI U-Net alone, use `python -m pip install 'exhale-xrf[monai]'`; no
|
|
162
|
+
DINOv3 requirements file is needed. Use `'exhale-xrf[stardist,cellpose,monai]'` with
|
|
163
|
+
the same requirements argument to include all three methods. For a Git checkout:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python -m pip install -e '.[stardist,cellpose,monai]' -r requirements-cellpose.txt
|
|
167
|
+
./run_exhale.py
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Omit unwanted extras and the requirements file when Cellpose is not needed.
|
|
171
|
+
On Windows, launch the checkout with `python run_exhale.py`.
|
|
172
|
+
|
|
173
|
+
### DINOv3 and trained models
|
|
174
|
+
|
|
175
|
+
The additional requirements file pins DINOv3 to a tested source archive; Git is
|
|
176
|
+
not required. For a particular EXHALE release, replace `main` in the requirements
|
|
177
|
+
URL with its `vVERSION` tag and append `==VERSION` to the package specification.
|
|
178
|
+
A version-pinned uv installation must be reinstalled with a newer version
|
|
179
|
+
specification to upgrade.
|
|
180
|
+
|
|
181
|
+
Trained Cellpose weights are downloaded on first use to the model directory in
|
|
182
|
+
application settings. The same mechanism is used for installed applications and
|
|
183
|
+
Git checkouts; the large weights are not included in the application installer.
|
|
184
|
+
DINOv3 code and derived model weights retain the
|
|
185
|
+
[DINOv3 license](exhale/resources/licenses/DINOv3-LICENSE.md).
|
|
186
|
+
|
|
187
|
+
## Developer documentation
|
|
188
|
+
|
|
189
|
+
Developer documentation is located in [`docs/`](docs/README.md).
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Extract the generated X artwork into reproducible icon-source variants."""
|
|
3
|
+
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from PIL import Image
|
|
9
|
+
from scipy.ndimage import distance_transform_edt, label
|
|
10
|
+
|
|
11
|
+
HERE = Path(__file__).resolve().parent
|
|
12
|
+
ROOT = HERE.parents[1]
|
|
13
|
+
|
|
14
|
+
SOURCE_X_RECT = (11, 6, 20, 20) # Inclusive x1, y1, x2, y2 in the 57-pixel mask.
|
|
15
|
+
GUIDE_RG_COLORS = ((230, 35, 0), (0, 190, 0), (218, 151, 18))
|
|
16
|
+
DARK_GREEN = (7, 31, 16)
|
|
17
|
+
RUNTIME_ICON = ROOT / "exhale" / "resources" / "icons" / "xrf.png"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def source_x_mask(source: np.ndarray) -> np.ndarray:
|
|
21
|
+
"""Return red-or-green X pixels from its clean source rectangle."""
|
|
22
|
+
red_or_green = (source & 0b011) != 0
|
|
23
|
+
x1, y1, x2, y2 = SOURCE_X_RECT
|
|
24
|
+
mask = np.zeros_like(red_or_green)
|
|
25
|
+
mask[y1 : y2 + 1, x1 : x2 + 1] = red_or_green[y1 : y2 + 1, x1 : x2 + 1]
|
|
26
|
+
if not mask.any():
|
|
27
|
+
raise ValueError(f"no source X pixels found in {SOURCE_X_RECT}")
|
|
28
|
+
return mask
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def fitted_x_mask(source: np.ndarray, guide: np.ndarray) -> np.ndarray:
|
|
32
|
+
"""Match the source X to its fitted red, green, and yellow guide components."""
|
|
33
|
+
source_mask = source_x_mask(source)
|
|
34
|
+
if guide.shape[0] != guide.shape[1] or source.shape[0] != source.shape[1]:
|
|
35
|
+
raise ValueError("source mask and guide must be square")
|
|
36
|
+
scaled_source = (
|
|
37
|
+
np.asarray(
|
|
38
|
+
Image.fromarray(source_mask.astype(np.uint8) * 255).resize(
|
|
39
|
+
(guide.shape[1], guide.shape[0]),
|
|
40
|
+
Image.Resampling.NEAREST,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
!= 0
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
candidate = np.zeros(guide.shape[:2], dtype=bool)
|
|
47
|
+
for color in GUIDE_RG_COLORS:
|
|
48
|
+
candidate |= np.all(guide == color, axis=2)
|
|
49
|
+
components, _ = label(candidate, structure=np.ones((3, 3), dtype=np.uint8))
|
|
50
|
+
component_ids = np.unique(components[scaled_source & candidate])
|
|
51
|
+
component_ids = component_ids[component_ids != 0]
|
|
52
|
+
if not len(component_ids):
|
|
53
|
+
raise ValueError("no fitted guide components overlap the source X")
|
|
54
|
+
return np.isin(components, component_ids)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def expanded_masks(mask: np.ndarray, margin: float, feather: float) -> tuple[np.ndarray, ...]:
|
|
58
|
+
"""Return round-cornered core and outer masks plus the exterior blend weight."""
|
|
59
|
+
if margin < 0 or feather < 0:
|
|
60
|
+
raise ValueError("margin and feather must not be negative")
|
|
61
|
+
distance = distance_transform_edt(~mask)
|
|
62
|
+
core = distance <= margin
|
|
63
|
+
outer = distance <= margin + feather
|
|
64
|
+
blend = np.clip((margin + feather - distance) / max(feather, 1.0), 0.0, 1.0)
|
|
65
|
+
blend[core] = 1.0
|
|
66
|
+
blend = blend * blend * (3.0 - 2.0 * blend)
|
|
67
|
+
return core, outer, blend
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def square_crop_bounds(mask: np.ndarray, size: int) -> tuple[int, int, int, int]:
|
|
71
|
+
"""Return a centered fixed-size crop around the mask."""
|
|
72
|
+
rows, columns = np.nonzero(mask)
|
|
73
|
+
if not len(rows):
|
|
74
|
+
raise ValueError("cannot crop an empty mask")
|
|
75
|
+
center_x = int(columns.min() + columns.max() + 1) // 2
|
|
76
|
+
center_y = int(rows.min() + rows.max() + 1) // 2
|
|
77
|
+
left = center_x - size // 2
|
|
78
|
+
upper = center_y - size // 2
|
|
79
|
+
if left < 0 or upper < 0 or left + size > mask.shape[1] or upper + size > mask.shape[0]:
|
|
80
|
+
raise ValueError("the centered icon crop extends beyond the image")
|
|
81
|
+
return left, upper, left + size, upper + size
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def extract_icon_sources(
|
|
85
|
+
source_path: Path,
|
|
86
|
+
guide_path: Path,
|
|
87
|
+
image_path: Path,
|
|
88
|
+
output_directory: Path,
|
|
89
|
+
crop_size: int = 360,
|
|
90
|
+
margin: float = 12.0,
|
|
91
|
+
feather: float = 4.0,
|
|
92
|
+
install_runtime: bool = False,
|
|
93
|
+
) -> tuple[Path, ...]:
|
|
94
|
+
"""Write the mask, diagnostic, transparent, and opaque icon sources."""
|
|
95
|
+
with Image.open(source_path) as image:
|
|
96
|
+
source = np.asarray(image).copy()
|
|
97
|
+
with Image.open(guide_path) as image:
|
|
98
|
+
guide = np.asarray(image.convert("RGB"))
|
|
99
|
+
with Image.open(image_path) as image:
|
|
100
|
+
generated = np.asarray(image.convert("RGB"))
|
|
101
|
+
if generated.shape != guide.shape:
|
|
102
|
+
raise ValueError("guide and generated image dimensions differ")
|
|
103
|
+
|
|
104
|
+
fitted_mask = fitted_x_mask(source, guide)
|
|
105
|
+
core_mask, _outer_mask, blend = expanded_masks(fitted_mask, margin, feather)
|
|
106
|
+
bounds = square_crop_bounds(core_mask, crop_size)
|
|
107
|
+
left, upper, right, lower = bounds
|
|
108
|
+
cropped_mask = core_mask[upper:lower, left:right].copy()
|
|
109
|
+
cropped_mask[[0, -1], :] = False
|
|
110
|
+
cropped_mask[:, [0, -1]] = False
|
|
111
|
+
cropped_blend = blend[upper:lower, left:right, None]
|
|
112
|
+
cropped_image = generated[upper:lower, left:right]
|
|
113
|
+
|
|
114
|
+
output_directory.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
mask_path = output_directory / "xrf_icon_x_mask.png"
|
|
116
|
+
diagnostic_path = output_directory / "xrf_icon_x_region.png"
|
|
117
|
+
transparent_path = output_directory / "xrf_icon_x_transparent.png"
|
|
118
|
+
dark_path = output_directory / "xrf_icon_x_dark.png"
|
|
119
|
+
|
|
120
|
+
Image.fromarray(cropped_mask.astype(np.uint8) * 255, "L").save(mask_path)
|
|
121
|
+
|
|
122
|
+
dark_color = np.asarray(DARK_GREEN, dtype=np.float32)
|
|
123
|
+
composited = np.round(
|
|
124
|
+
cropped_image.astype(np.float32) * cropped_blend + dark_color * (1.0 - cropped_blend)
|
|
125
|
+
).astype(np.uint8)
|
|
126
|
+
|
|
127
|
+
luminance = np.clip(
|
|
128
|
+
0.2126 * cropped_image[..., 0]
|
|
129
|
+
+ 0.7152 * cropped_image[..., 1]
|
|
130
|
+
+ 0.0722 * cropped_image[..., 2],
|
|
131
|
+
0,
|
|
132
|
+
255,
|
|
133
|
+
).astype(np.uint8)
|
|
134
|
+
diagnostic = np.repeat(luminance[..., None], 3, axis=2)
|
|
135
|
+
diagnostic[cropped_mask] = composited[cropped_mask]
|
|
136
|
+
Image.fromarray(diagnostic, "RGB").save(diagnostic_path)
|
|
137
|
+
|
|
138
|
+
transparent = np.empty((*cropped_mask.shape, 4), dtype=np.uint8)
|
|
139
|
+
transparent[..., :3] = composited
|
|
140
|
+
transparent[..., 3] = cropped_mask.astype(np.uint8) * 255
|
|
141
|
+
Image.fromarray(transparent, "RGBA").save(transparent_path)
|
|
142
|
+
|
|
143
|
+
dark = np.empty_like(composited)
|
|
144
|
+
dark[:] = DARK_GREEN
|
|
145
|
+
dark[cropped_mask] = composited[cropped_mask]
|
|
146
|
+
Image.fromarray(dark, "RGB").save(dark_path)
|
|
147
|
+
|
|
148
|
+
if install_runtime:
|
|
149
|
+
RUNTIME_ICON.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
with Image.open(transparent_path) as image:
|
|
151
|
+
image.resize((512, 512), Image.Resampling.LANCZOS).save(RUNTIME_ICON)
|
|
152
|
+
|
|
153
|
+
return mask_path, diagnostic_path, transparent_path, dark_path
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def parse_args() -> ArgumentParser:
|
|
157
|
+
parser = ArgumentParser(description=__doc__)
|
|
158
|
+
parser.add_argument("--source", type=Path, default=HERE / "57-masks.png")
|
|
159
|
+
parser.add_argument("--guide", type=Path, default=HERE / "xrf_guide_57.png")
|
|
160
|
+
parser.add_argument("--image", type=Path, default=HERE / "xrf_imagegen_57.png")
|
|
161
|
+
parser.add_argument("--output-directory", type=Path, default=HERE)
|
|
162
|
+
parser.add_argument("--crop-size", type=int, default=360)
|
|
163
|
+
parser.add_argument("--margin", type=float, default=12.0)
|
|
164
|
+
parser.add_argument("--feather", type=float, default=4.0)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"--install-runtime",
|
|
167
|
+
action="store_true",
|
|
168
|
+
help="install the transparent 512-pixel application and Linux icon",
|
|
169
|
+
)
|
|
170
|
+
return parser
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main() -> None:
|
|
174
|
+
args = parse_args().parse_args()
|
|
175
|
+
paths = extract_icon_sources(
|
|
176
|
+
args.source,
|
|
177
|
+
args.guide,
|
|
178
|
+
args.image,
|
|
179
|
+
args.output_directory,
|
|
180
|
+
args.crop_size,
|
|
181
|
+
args.margin,
|
|
182
|
+
args.feather,
|
|
183
|
+
args.install_runtime,
|
|
184
|
+
)
|
|
185
|
+
for path in paths:
|
|
186
|
+
print(path)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|