pycat-napari 1.6.332__tar.gz → 1.6.334__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.
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/CHANGELOG.md +51 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/PKG-INFO +1 -1
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/pyproject.toml +1 -1
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/central_manager.py +1 -1
- pycat_napari-1.6.334/src/pycat/navigator/executor.py +231 -0
- pycat_napari-1.6.334/src/pycat/navigator/parameters.py +201 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/navigator_dock.py +84 -5
- pycat_napari-1.6.332/src/pycat/navigator/executor.py +0 -163
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/.gitignore +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/CONTRIBUTING.md +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/LICENSE +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/README.md +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/THIRD_PARTY_LICENSES.txt +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/benchmarks/README.md +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/README.md +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-arm-mac.txt +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-base.txt +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-devbio-napari-arm-mac.txt +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-devbio-napari.txt +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/_common.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/analysis_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/brightfield_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/invitro_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/io_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/preprocessing_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/segmentation_steps.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch_processor.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch_step_registry.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/data/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/data/data_modules.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/czi_seam.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/dialogs.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/file_io.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/frap_io.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_reader.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_source.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_structure.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/lazy_guard.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/lazy_sources.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/load_channel_identity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/local_cache.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/metadata_extract.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/multidim_io.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/naming.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/napari_adapter.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/czi_bioformats.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/image_reader_2d.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/ims_reader.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/mask_reader.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/stack_layer_builders.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/stack_metadata.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/routing.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/scenes.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session_loader.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session_manifest.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/sidecar_discovery.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_access.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_load.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_openers.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/storage_probe.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tagging.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tiff_planes.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tifffile_zarr_shim.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/viewer_load.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/writers.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/zarr_compat.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/fix_tifffile.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/icons/pycat_logo_512.png +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/icons/pycat_mark.png +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/adapters.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/canonical.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/capabilities.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/context.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/contracts.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_layer_tag_hierarchy_and_module_flow.xlsx +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_module_information_contracts.xlsx +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_question_tree_and_method_mapping.xlsx +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/operation_catalog.json +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/execution.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/gates.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/loader.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/modules.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/op_catalog.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/operation_spec.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/planner.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/quality_gates.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/question_engine.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/registry.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/scientific_tree.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/session.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/tags.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/templates.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/run_pycat.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/advanced_analysis_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/analysis_plots.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/batch_roi_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/benchmark_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/biological_qc_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/brightfield_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/brightfield_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/clean_spot_detection_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_modes.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/coarsening.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/frame_quality.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/intensity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/moduli.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/msd.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/photobleaching.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/relaxation.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/survival.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/contrast_cascade_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/contrast_cascade_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/control_validation.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/correlation_func_analysis_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_qc_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_qc_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_viz_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/drawing_layers.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/dynamic_spatial_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fd_curve_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fd_curve_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/feature_analysis_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/feature_redundancy.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fft_bandpass_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fibril_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fibril_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/frap_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/frap_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fusion_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fusion_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/gaussian_localization_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/general_image_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/gpu_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/_base.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/background.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/deblur.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/filters.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/preprocessing.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/size_estimation.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/upscaling.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/intensity_profile_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/analysis.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/field_summary.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/partition.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/size_distribution.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_bf_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_fluor_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/kymograph_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/label_and_mask_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/layer_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/measurement_stability.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/molecular_counting_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/morphological_complexity_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/nb_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/nb_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/numba_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/obj_based_coloc_analysis_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/organizational_metrics_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/partial_volume_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/partition_enrichment_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pipeline_diagnostic_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pipeline_snr_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pixel_wise_corr_analysis_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/qc_gallery.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/qc_gallery_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ratiometric_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/scan_qc_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/_common.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/cellpose.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/fz.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/intensity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/local_thresholding.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/morphology.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/puncta_refinement.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/subcellular.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/watershed.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation_scale_advisor.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/smlm_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_acf_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_metrology_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_metrology_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_randomness_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spida_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spida_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temperature_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temperature_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temporal_enhancement_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/analysis.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/correlation.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/execution.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/frame_access.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/preprocessing.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_condensate_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_invitro_fluor_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_invitro_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/topology_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/trackmate_bridge.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ts_cache_manager.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ts_cellpose_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/two_channel_coloc_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/unmixing_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/video_export_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/analysis.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/detection.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/drift.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/host.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/msd_adapter.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/napari_adapter.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/panels.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/populations.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/results_dock.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/table_adapter.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/viscosity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/zstack_segmentation_tools.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/zstack_segmentation_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/brushable_table.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/brushable_workspace.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/channel_identity_dialog.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/comparative_figures_ui.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/coordinate_readout.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/feature_explorer_dock.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/field_status.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/home_dock.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/linked_selection_dock.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/menu_manager.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/metadata_contradiction_panel.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/operation_gating.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/preferences_dialog.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/preset_picker.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/scene_switcher.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_analysis_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_diagnostics_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_filtering_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_imageops_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_labels_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_modules.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_segmentation_mixin.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/workflow_checklist.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/__init__.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/analysis_presets.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/app_mode.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/batch_brushing.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/brushing.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/calibration.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_designations.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_modality.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_naming.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/cohort_targets.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/comparative_figures.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/comparative_stats.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/consolidated_table.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/dataset_identity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/dock_space.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/entity_ref.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/entity_registry.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/environment_check.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/errors.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_cards.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_explorer.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_families.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_provenance.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_registry.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/field_registry_hub.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/figure_refine.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/figure_spec.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/filter_store.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/fit_quality.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/frame_interval.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/general_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/intensity_semantics.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_bindings.json +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_tag_hook.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_tags.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/logging_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/marker_logic.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/math_utils.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/measurement.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/measurement_ontology.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/metadata_contradictions.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/metadata_validity.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/notify.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/object_graph.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/object_ref.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/operation_runner.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/pixel_size.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backend_pyqtgraph.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backend_selection.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backends.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/preferences.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/qt_worker.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/quality_gate.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/reliability.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/result_models.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/results_store.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/sample_metadata.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/selection_overlay.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/selection_service.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/tag_registry.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/tag_resolver.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/user_settings.py +0 -0
- {pycat_napari-1.6.332 → pycat_napari-1.6.334}/tests/README.md +0 -0
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
## [1.6.334] - 2026-07-24
|
|
2
|
+
### Fixed + Added — **Navigator execution-adapter layer, Phase 3: adapters fire on REAL plan steps; cell segmentation is the next route-proven workflow.**
|
|
3
|
+
**The finding this closes:** Phase 1 proved the executor mechanism against a *synthetic* step name
|
|
4
|
+
(`background_removal`), but real compiled plans emit toolbox-**module** step names (`segmentation_tools`,
|
|
5
|
+
`image_processing_tools`, …) — so that adapter never fired in production: every real plan step reported
|
|
6
|
+
"run from your panel". Phase 3 re-keys the adapters on the real module names and adds the next proven workflow.
|
|
7
|
+
- **Adapters now key on the real navigator module names** `execution_order` reports. `image_processing_tools`
|
|
8
|
+
(the module whose product is a background-corrected field) maps to the proven `background_removal` batch step
|
|
9
|
+
— Phase 1's bit-for-bit proof is now *live* on real plans.
|
|
10
|
+
- **New route-proven workflow: single-frame cell segmentation.** `segmentation_tools` for a **cell** target maps
|
|
11
|
+
to `cellpose_segmentation` — the batch step `test_route_equivalence` proves computes identically to the manual
|
|
12
|
+
route. A **coarse** module resolves its batch step from the intent: condensate segmentation resolves to `None`
|
|
13
|
+
(its batch route is an unproven documented gap) and is reported, never guessed; time-series cell segmentation
|
|
14
|
+
(`ts_cellpose_tools`, keyframe propagation) is deliberately unmapped — a single-frame stand-in would be wrong
|
|
15
|
+
science. `resolve_batch_step(step, intent)` is the single "will this step run" authority, shared by the
|
|
16
|
+
executor and the parameter review.
|
|
17
|
+
- **Reviewed parameters reach the handler where it actually reads them.** Cellpose reads `cell_diameter` from the
|
|
18
|
+
`data_instance`, not the params dict — so `params_from(intent, ctx, state, reviewed)` now receives the reviewed
|
|
19
|
+
values and applies each where the handler reads it (the params dict for `ball_radius`, the `data_instance` for
|
|
20
|
+
`cell_diameter`); the executor never guesses.
|
|
21
|
+
- **Proof** (`tests/navigator/test_navigator_cellpose_adapter.py`, `base`, 5): a real cell plan's segmentation
|
|
22
|
+
step resolves to a batch handler (the regression guard for the synthetic-name gap); driving
|
|
23
|
+
`cellpose_segmentation` through the adapter with a reviewed diameter equals a direct Cellpose call at that
|
|
24
|
+
diameter, **bit for bit**; condensate/time-series segmentation run nothing and are reported; and every batch
|
|
25
|
+
step an adapter targets is a real registered handler.
|
|
26
|
+
- **Later** (per the `navigator_execution_adapters` spec): more adapters one workflow at a time, then dock
|
|
27
|
+
progress + cancel (Phase 4).
|
|
28
|
+
|
|
29
|
+
## [1.6.333] - 2026-07-24
|
|
30
|
+
### Added — **Navigator execution-adapter layer, Phase 2: an honest parameter review — seeded, editable, provenance-recorded.**
|
|
31
|
+
The navigator asks *what* to do, not *with which parameters* — but a guided run that silently picks a
|
|
32
|
+
rolling-ball radius (or a segmentation method) the user never saw is worse than asking. Phase 2 adds the
|
|
33
|
+
minimal parameter review, Qt-free at its core:
|
|
34
|
+
- New **`navigator/parameters.py`**: each adapter-covered step declares its **material** parameters (the handful
|
|
35
|
+
the science turns on, not every argument). `build_param_review(plan, ctx)` seeds them by a declared
|
|
36
|
+
precedence — **a matching preset → a session value → the grounded function default** — never an invented one.
|
|
37
|
+
`ReviewedStep` generalises the preset `populate-but-never-lock` contract to the no-preset case too: the user
|
|
38
|
+
may edit any value, and the edit is tracked as **provenance in the exact `PresetApplication.record()` shape**
|
|
39
|
+
(`preset_key`, `modified_parameters`, `is_modified`, `parameters`), `preset_key=None` when no preset seeded it.
|
|
40
|
+
- **The reviewed values drive the run.** `run_plan(…, params_by_step=, provenance_by_step=)` merges the reviewed
|
|
41
|
+
values over the adapter's derived params (routing keys like `active_layer` still come from the adapter) and
|
|
42
|
+
records the provenance onto each ran step's `StepOutcome`. `tests/navigator/test_navigator_parameters.py`
|
|
43
|
+
(`base`, 9) pins it: an edited rolling-ball radius makes the guided result equal the **manual operation at
|
|
44
|
+
that radius** — and *not* at the default — so the edit provably reaches the computation.
|
|
45
|
+
- **The dock surfaces it**: the plan view renders an editable "Review parameters" form (a spin/combo per
|
|
46
|
+
material parameter, seeded, tooltip-documented) above Run; the Run passes the `ParamReview` through
|
|
47
|
+
`run_plan_via_central_manager`, which also reports which steps ran with modified parameters. A programmatic
|
|
48
|
+
run with no form still builds a default-seeded review, so provenance is recorded either way.
|
|
49
|
+
- **Later phases** (per the `navigator_execution_adapters` spec): more adapters one workflow at a time (each
|
|
50
|
+
behind its route-equivalence test), then dock progress + cancel.
|
|
51
|
+
|
|
1
52
|
## [1.6.332] - 2026-07-24
|
|
2
53
|
### Added — **Navigator execution-adapter layer, Phase 1: "Run analysis" computes a plan through the batch handlers, gate-respecting (guided == batch == manual).**
|
|
3
54
|
`selection_scale` Part 2 established there is no uniform "run this op" — but the batch `_STEP_MAP` handlers ARE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycat-napari
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.334
|
|
4
4
|
Summary: Python Condensate Analysis Toolbox - A napari-based tool for biomolecular condensate analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/BanerjeeLab-repertoire/pycat-napari
|
|
6
6
|
Project-URL: Repository, https://github.com/BanerjeeLab-repertoire/pycat-napari
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pycat-napari"
|
|
7
|
-
version = "1.6.
|
|
7
|
+
version = "1.6.334"
|
|
8
8
|
description = "Python Condensate Analysis Toolbox - A napari-based tool for biomolecular condensate analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# 3.13 is BLOCKED UPSTREAM, not by PyCAT's own code (which compiles and passes core on 3.13).
|
|
@@ -124,7 +124,7 @@ class CentralManager:
|
|
|
124
124
|
from pycat.ui.navigator_dock import install_navigator_action, run_plan_via_central_manager
|
|
125
125
|
self._navigator_action = install_navigator_action(
|
|
126
126
|
self.viewer, central_manager=self,
|
|
127
|
-
on_run=lambda plan: run_plan_via_central_manager(self, plan))
|
|
127
|
+
on_run=lambda plan, review=None: run_plan_via_central_manager(self, plan, review))
|
|
128
128
|
# Populate the feature registry so any consumer (the beginner home dock, a capability query) sees
|
|
129
129
|
# every surfaced-but-otherwise-hidden feature (navigator inc 4). Guarded: a registration hiccup must
|
|
130
130
|
# never break startup, and the cards' openers import Qt/toolbox lazily so this stays cheap.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""**Execute a compiled navigator plan by driving the BATCH step handlers** (execution-adapter layer, Phase 1).
|
|
2
|
+
|
|
3
|
+
`selection_scale` Part 2 established there is no uniform "run this op" — PyCAT operations have bespoke,
|
|
4
|
+
panel-collected signatures. But the **batch `_STEP_MAP` handlers** are the proven "same computation" route:
|
|
5
|
+
they share one signature — ``(state, image_path, params, output_dir)`` — and `test_route_equivalence` already
|
|
6
|
+
asserts they compute byte-identically to the manual GUI route. So the executor drives *those* handlers, in the
|
|
7
|
+
gate-respecting order (`execution.execution_order`), threading each step's output through a shared ``state``
|
|
8
|
+
dict exactly as a batch replay does.
|
|
9
|
+
|
|
10
|
+
The narrow bridge is per-step: an :class:`ExecAdapter` maps a plan step to a batch handler and derives that
|
|
11
|
+
handler's ``params`` from the answers. A plan step with **no adapter is reported** ("run it from its method
|
|
12
|
+
panel"), never invoked with guessed arguments — a `fn(image)` fallback would pass wrong args and produce wrong
|
|
13
|
+
science silently, which is the whole reason this is an adapter layer and not a loop over ``resolve_operation``.
|
|
14
|
+
|
|
15
|
+
Phase 1 ships the executor + registry + ONE proven adapter (``background_removal`` — the shortest chain
|
|
16
|
+
`test_route_equivalence` already covers), so `guided == batch == manual` is pinned; later phases add adapters
|
|
17
|
+
one workflow at a time, each earning its place with a route-equivalence test. Qt-free; a runner is optional
|
|
18
|
+
(off-thread when given, synchronous otherwise)."""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import dataclasses
|
|
22
|
+
import pathlib
|
|
23
|
+
import tempfile
|
|
24
|
+
from typing import Callable, Optional
|
|
25
|
+
|
|
26
|
+
from .execution import execution_order
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass(frozen=True)
|
|
30
|
+
class ExecAdapter:
|
|
31
|
+
"""Bridges ONE plan step to the batch handler that computes it. ``plan_step`` is the **real navigator module
|
|
32
|
+
name** (as `execution_order` reports it — e.g. ``segmentation_tools``, ``image_processing_tools``), not a
|
|
33
|
+
synthetic label. ``batch_step`` is either the ``_STEP_MAP`` key that computes it, or a callable
|
|
34
|
+
``(intent) -> Optional[str]`` for a coarse module whose batch step depends on the target (e.g.
|
|
35
|
+
``segmentation_tools`` → ``cellpose_segmentation`` for a cell, ``None`` for a condensate whose batch route is
|
|
36
|
+
an unproven gap). ``params_from(intent, ctx, state, reviewed)`` derives the handler's ``params`` from the
|
|
37
|
+
answers and the user-reviewed values, applying each where the handler actually reads it."""
|
|
38
|
+
plan_step: str
|
|
39
|
+
batch_step: object
|
|
40
|
+
params_from: Callable
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _background_removal_params(intent, ctx, state, reviewed):
|
|
44
|
+
radius = reviewed.get("ball_radius") if reviewed else None
|
|
45
|
+
if radius is None and ctx is not None:
|
|
46
|
+
try:
|
|
47
|
+
radius = ctx.get("ball_radius")
|
|
48
|
+
except Exception: # broad-ok: optional_probe — a context miss falls back to the grounded default
|
|
49
|
+
radius = None
|
|
50
|
+
return {"ball_radius": int(radius) if radius else 50, "active_layer": "segmentation image"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cellpose_params(intent, ctx, state, reviewed):
|
|
54
|
+
"""Cellpose reads ``cell_diameter`` from the ``data_instance`` (NOT the params dict), so a reviewed/derived
|
|
55
|
+
diameter is applied THERE — the params dict only carries the method + refine flag the handler reads."""
|
|
56
|
+
diam = reviewed.get("cell_diameter") if reviewed else None
|
|
57
|
+
if diam is None and ctx is not None:
|
|
58
|
+
try:
|
|
59
|
+
diam = ctx.get("cell_diameter")
|
|
60
|
+
except Exception: # broad-ok: optional_probe — no session diameter → the handler's grounded default (100)
|
|
61
|
+
diam = None
|
|
62
|
+
if diam is not None and isinstance(state, dict):
|
|
63
|
+
di = state.get("data_instance")
|
|
64
|
+
if di is not None and hasattr(di, "set_data"):
|
|
65
|
+
try:
|
|
66
|
+
di.set_data("cell_diameter", int(diam))
|
|
67
|
+
except Exception: # broad-ok: optional_probe — a bad diameter falls back to the handler default
|
|
68
|
+
pass
|
|
69
|
+
return {"method": "cellpose", "cellpose_refine": False}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _cellpose_step_if_cell(intent):
|
|
73
|
+
"""``segmentation_tools`` is coarse — for a cell target it is single-frame Cellpose (the ``cellpose_segmentation``
|
|
74
|
+
batch step `test_route_equivalence` proves), but condensate segmentation is an unproven batch route (a
|
|
75
|
+
documented route-equivalence gap), so it resolves to ``None`` (reported, never guessed). Time-series cell
|
|
76
|
+
segmentation (``ts_cellpose_tools``) is deliberately NOT mapped: its real operation is keyframe propagation
|
|
77
|
+
across a stack (``replay_ts_cellpose_keyframe``), which is not route-proven — a single-frame stand-in would
|
|
78
|
+
be wrong science, so it stays 'run from its panel'."""
|
|
79
|
+
return "cellpose_segmentation" if getattr(intent, "target", None) == "cell" else None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
#: The declared adapters, keyed by the REAL navigator module name `execution_order` reports. The ONLY place a
|
|
83
|
+
#: plan step is tied to a computation — a step absent here (or one whose batch step resolves to ``None``) is
|
|
84
|
+
#: reported "run from its panel", never guessed at. Grows one workflow per phase, each behind a
|
|
85
|
+
#: route-equivalence test: ``background_removal`` (rolling-ball) and ``cellpose_segmentation`` are the batch
|
|
86
|
+
#: steps `test_route_equivalence` proves compute identically to the manual route.
|
|
87
|
+
_ADAPTERS: dict = {
|
|
88
|
+
"image_processing_tools": ExecAdapter("image_processing_tools", "background_removal",
|
|
89
|
+
_background_removal_params),
|
|
90
|
+
"segmentation_tools": ExecAdapter("segmentation_tools", _cellpose_step_if_cell, _cellpose_params),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def has_adapter(step_name: str) -> bool:
|
|
95
|
+
return step_name in _ADAPTERS
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def resolve_batch_step(step_name: str, intent=None) -> Optional[str]:
|
|
99
|
+
"""The batch step ``step_name`` will actually run for ``intent``, or ``None`` if it has no adapter or its
|
|
100
|
+
variant is not auto-runnable yet (a coarse module whose target has no proven batch route). The single
|
|
101
|
+
authority for 'will this step run', shared by the executor and the parameter review."""
|
|
102
|
+
adapter = _ADAPTERS.get(step_name)
|
|
103
|
+
if adapter is None:
|
|
104
|
+
return None
|
|
105
|
+
bs = adapter.batch_step
|
|
106
|
+
return bs(intent) if callable(bs) else bs
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclasses.dataclass(frozen=True)
|
|
110
|
+
class StepOutcome:
|
|
111
|
+
"""What happened to one step. ``outcome`` is ``'ran'`` / ``'ran_with_caveat'`` / ``'blocked'`` (the run
|
|
112
|
+
stops here) / ``'skipped'`` (after a blocker) / ``'needs_panel'`` (no adapter yet) / ``'error'``.
|
|
113
|
+
``provenance`` (Phase 2) records how the step was parameterised — the ``PresetApplication.record()`` shape
|
|
114
|
+
(which preset, if any, seeded it and what the user changed) — empty when the step carried no review."""
|
|
115
|
+
name: str
|
|
116
|
+
outcome: str
|
|
117
|
+
detail: str = ""
|
|
118
|
+
provenance: dict = dataclasses.field(default_factory=dict)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclasses.dataclass
|
|
122
|
+
class ExecReport:
|
|
123
|
+
steps: list = dataclasses.field(default_factory=list)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def ran(self) -> list:
|
|
127
|
+
return [s for s in self.steps if s.outcome.startswith("ran")]
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def stopped(self) -> bool:
|
|
131
|
+
return any(s.outcome in ("blocked", "error") for s in self.steps)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def needs_panel(self) -> list:
|
|
135
|
+
return [s for s in self.steps if s.outcome == "needs_panel"]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _build_step_registry() -> dict:
|
|
139
|
+
"""The production batch step registry (name → replay handler), built the way `BatchProcessor` does. Only
|
|
140
|
+
the import-clean `batch_step_registry` is touched (no Qt)."""
|
|
141
|
+
registry: dict = {}
|
|
142
|
+
|
|
143
|
+
class _Recorder:
|
|
144
|
+
def register_step(self, name, fn):
|
|
145
|
+
registry[name] = fn
|
|
146
|
+
|
|
147
|
+
from pycat.batch_step_registry import register_all_steps
|
|
148
|
+
register_all_steps(_Recorder())
|
|
149
|
+
return registry
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def run_plan(plan, state, *, intent=None, ctx=None, image_path=None, output_dir=None, runner=None,
|
|
153
|
+
params_by_step: Optional[dict] = None, provenance_by_step: Optional[dict] = None,
|
|
154
|
+
on_step: Optional[Callable] = None) -> ExecReport:
|
|
155
|
+
"""Execute ``plan``'s steps in gate order by driving the batch handlers, threading ``state`` (a dict the
|
|
156
|
+
handlers read/write, exactly as a batch replay). Returns an :class:`ExecReport`.
|
|
157
|
+
|
|
158
|
+
Gate semantics are READ from :func:`execution.execution_order`, never re-decided: a **blocker** stops the
|
|
159
|
+
run at that step (nothing after it runs); a **caveat** runs with the caveat recorded; **probes** run first.
|
|
160
|
+
A step with no :class:`ExecAdapter` is reported ``'needs_panel'`` — never invoked with guessed arguments.
|
|
161
|
+
A step that raises halts the run (downstream depends on its output). ``runner`` (an ``OperationRunner``)
|
|
162
|
+
runs each handler off the Qt thread when given; otherwise handlers run synchronously (headless / tests).
|
|
163
|
+
|
|
164
|
+
``params_by_step`` (Phase 2) overrides the adapter's derived params per step with the user-reviewed values
|
|
165
|
+
(:mod:`pycat.navigator.parameters`); ``provenance_by_step`` records how each ran step was parameterised
|
|
166
|
+
(the ``PresetApplication.record()`` shape) onto its :class:`StepOutcome`."""
|
|
167
|
+
intent = intent if intent is not None else getattr(plan, "intent", None)
|
|
168
|
+
registry = _build_step_registry()
|
|
169
|
+
report = ExecReport()
|
|
170
|
+
|
|
171
|
+
tmp = None
|
|
172
|
+
if output_dir is None:
|
|
173
|
+
tmp = tempfile.TemporaryDirectory()
|
|
174
|
+
output_dir = pathlib.Path(tmp.name)
|
|
175
|
+
if image_path is None:
|
|
176
|
+
image_path = pathlib.Path(output_dir) / "sample.tif"
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
halted = False
|
|
180
|
+
for es in execution_order(plan):
|
|
181
|
+
if halted or es.status == "skipped":
|
|
182
|
+
report.steps.append(StepOutcome(es.name, "skipped", es.reason))
|
|
183
|
+
continue
|
|
184
|
+
if es.status == "blocked":
|
|
185
|
+
report.steps.append(StepOutcome(es.name, "blocked", es.reason))
|
|
186
|
+
halted = True
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
adapter = _ADAPTERS.get(es.name)
|
|
190
|
+
batch_step = adapter.batch_step(intent) if (adapter and callable(adapter.batch_step)) \
|
|
191
|
+
else (adapter.batch_step if adapter else None)
|
|
192
|
+
if batch_step is None:
|
|
193
|
+
# No adapter, or a coarse module whose variant has no proven batch route yet — report it,
|
|
194
|
+
# never invoke with guessed arguments.
|
|
195
|
+
detail = ("no execution adapter yet — run this step from its method panel" if adapter is None
|
|
196
|
+
else "this variant isn't auto-runnable yet — run this step from its method panel")
|
|
197
|
+
report.steps.append(StepOutcome(es.name, "needs_panel", detail))
|
|
198
|
+
if on_step:
|
|
199
|
+
on_step(report.steps[-1])
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
fn = registry.get(batch_step)
|
|
203
|
+
if fn is None:
|
|
204
|
+
report.steps.append(StepOutcome(es.name, "error",
|
|
205
|
+
f"batch step {batch_step!r} is not registered"))
|
|
206
|
+
halted = True
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Reviewed values reach the adapter, which applies each where the handler actually reads it (the
|
|
210
|
+
# params dict, or the data_instance for cellpose's cell_diameter) — the executor never guesses.
|
|
211
|
+
reviewed = (params_by_step or {}).get(es.name, {})
|
|
212
|
+
params = adapter.params_from(intent, ctx, state, reviewed)
|
|
213
|
+
try:
|
|
214
|
+
if runner is not None:
|
|
215
|
+
runner.execute(fn, state, image_path, params, output_dir)
|
|
216
|
+
else:
|
|
217
|
+
fn(state, image_path, params, output_dir)
|
|
218
|
+
except Exception as exc: # broad-ok: scientific_result — a failed step halts; report it, never silently continue on stale state
|
|
219
|
+
report.steps.append(StepOutcome(es.name, "error", f"{type(exc).__name__}: {exc}"))
|
|
220
|
+
halted = True
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
prov = (provenance_by_step or {}).get(es.name, {})
|
|
224
|
+
report.steps.append(StepOutcome(
|
|
225
|
+
es.name, "ran_with_caveat" if es.status == "caveat" else "ran", es.reason, prov))
|
|
226
|
+
if on_step:
|
|
227
|
+
on_step(report.steps[-1])
|
|
228
|
+
finally:
|
|
229
|
+
if tmp is not None:
|
|
230
|
+
tmp.cleanup()
|
|
231
|
+
return report
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""**The minimal parameter review — surface the handful of parameters that change the result, seeded honestly.**
|
|
2
|
+
|
|
3
|
+
Execution-adapter layer, Phase 2. The navigator asks *what* to do, not *with which parameters* — but a guided
|
|
4
|
+
run that silently picks a rolling-ball radius (or a segmentation method) the user never saw is worse than
|
|
5
|
+
asking. So before a run, each adapter-covered step surfaces its **material** parameters — the handful that
|
|
6
|
+
materially change the result — in a small editable set, seeded by a declared precedence:
|
|
7
|
+
|
|
8
|
+
1. **A matching preset** (:mod:`pycat.utils.analysis_presets`) — if one ``applies_to`` this step's workflow,
|
|
9
|
+
its parameters seed the values (the populate-but-never-lock contract).
|
|
10
|
+
2. **A session value** — a measured/derived value already in the context (e.g. a ``ball_radius`` the user
|
|
11
|
+
measured on their data) seeds an un-preset parameter.
|
|
12
|
+
3. **The grounded function default** — the same default the batch handler falls back to; never an invented one.
|
|
13
|
+
|
|
14
|
+
The user may edit any value; the edit is tracked so the run records **which preset (if any) seeded each step
|
|
15
|
+
and what the user changed** — the exact ``PresetApplication.record()`` shape, so a guided result states how it
|
|
16
|
+
was parameterised and a saved template can carry those params. Qt-free: this is the model the dock's editable
|
|
17
|
+
form and :func:`pycat.navigator.executor.run_plan` both read; the dock only builds widgets over it.
|
|
18
|
+
|
|
19
|
+
Phase 2 ships the model + the one shipped adapter's material set (``background_removal`` → ``ball_radius``);
|
|
20
|
+
later adapters declare their material params here as they land, each still behind a route-equivalence test."""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import dataclasses
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from .execution import execution_order
|
|
27
|
+
from .executor import resolve_batch_step
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclasses.dataclass(frozen=True)
|
|
31
|
+
class StepParam:
|
|
32
|
+
"""One material parameter of a step — declared, so the review shows the same handful the science turns on
|
|
33
|
+
(not every argument the function takes). ``kind`` is ``'int'`` / ``'float'`` / ``'choice'``; ``default`` is
|
|
34
|
+
the grounded function default (what the batch handler falls back to), never an invented value."""
|
|
35
|
+
name: str
|
|
36
|
+
label: str
|
|
37
|
+
kind: str
|
|
38
|
+
default: object
|
|
39
|
+
choices: tuple = ()
|
|
40
|
+
help: str = ""
|
|
41
|
+
minimum: object = None
|
|
42
|
+
maximum: object = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#: The material parameters per plan step, keyed by the REAL navigator module name. The ONLY place a step
|
|
46
|
+
#: declares which of its arguments are surfaced for review. Grows one adapter at a time (each behind its
|
|
47
|
+
#: route-equivalence test), mirroring ``executor._ADAPTERS``.
|
|
48
|
+
_CELL_DIAMETER = StepParam(
|
|
49
|
+
"cell_diameter", "Cell diameter (px)", "int", 100, minimum=1,
|
|
50
|
+
help="The approximate cell diameter in pixels that Cellpose sizes its search to. Measure it on your data — "
|
|
51
|
+
"a wrong diameter is the most common cause of missed or merged cells. Default 100 px.")
|
|
52
|
+
_MATERIAL: dict = {
|
|
53
|
+
"image_processing_tools": (
|
|
54
|
+
StepParam("ball_radius", "Rolling-ball radius (px)", "int", 50, minimum=1,
|
|
55
|
+
help="The rolling-ball structuring radius for background estimation. Larger keeps more "
|
|
56
|
+
"low-frequency structure; too small eats real signal. Default 50 px."),
|
|
57
|
+
),
|
|
58
|
+
"segmentation_tools": (_CELL_DIAMETER,),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
#: Plan step → the ``analysis_presets`` workflow id whose presets seed it (preset precedence, source 1). Empty
|
|
63
|
+
#: for the shipped adapter (no preset applies to ``background_removal``); entries land with the adapters that a
|
|
64
|
+
#: preset actually configures (e.g. ``condensate_puncta_refinement``), one per phase.
|
|
65
|
+
_PRESET_WORKFLOW: dict = {}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def material_params(step_name: str) -> tuple:
|
|
69
|
+
"""The declared material parameters of ``step_name`` (empty if none/unknown)."""
|
|
70
|
+
return _MATERIAL.get(step_name, ())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _coerce(param: StepParam, value):
|
|
74
|
+
"""Coerce a value to the parameter's kind, falling back to the default on a bad value (never raise into a
|
|
75
|
+
run) — an int param must not carry a float that changes ``ceil`` downstream."""
|
|
76
|
+
try:
|
|
77
|
+
if param.kind == "int":
|
|
78
|
+
return int(round(float(value)))
|
|
79
|
+
if param.kind == "float":
|
|
80
|
+
return float(value)
|
|
81
|
+
except (TypeError, ValueError):
|
|
82
|
+
return param.default
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _applicable_preset(step_name: str):
|
|
87
|
+
"""The single preset that applies to this step's workflow, or ``None``. If more than one applies we return
|
|
88
|
+
``None`` (picking one silently would be dishonest — that is a preset-picker choice, not a default)."""
|
|
89
|
+
workflow = _PRESET_WORKFLOW.get(step_name)
|
|
90
|
+
if not workflow:
|
|
91
|
+
return None
|
|
92
|
+
try:
|
|
93
|
+
from pycat.utils.analysis_presets import presets_for
|
|
94
|
+
applicable = presets_for(workflow)
|
|
95
|
+
except Exception: # broad-ok: optional_probe — no preset registry → seed from defaults, not a crash
|
|
96
|
+
return None
|
|
97
|
+
return applicable[0] if len(applicable) == 1 else None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _seed_value(param: StepParam, preset, ctx):
|
|
101
|
+
"""Seed one parameter by the declared precedence: preset → session value → grounded default."""
|
|
102
|
+
if preset is not None and param.name in getattr(preset, "parameters", {}):
|
|
103
|
+
return _coerce(param, preset.parameters[param.name])
|
|
104
|
+
if ctx is not None:
|
|
105
|
+
try:
|
|
106
|
+
cv = ctx.get(param.name)
|
|
107
|
+
except Exception: # broad-ok: optional_probe — a non-mapping context just means no session value
|
|
108
|
+
cv = None
|
|
109
|
+
if cv is not None:
|
|
110
|
+
return _coerce(param, cv)
|
|
111
|
+
return param.default
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ReviewedStep:
|
|
115
|
+
"""One step's reviewable parameters: seeded honestly, editable, tracking the deviation from the seed. The
|
|
116
|
+
populate-but-never-lock contract of :class:`PresetApplication`, generalised to also cover the no-preset
|
|
117
|
+
case (``preset_key`` is ``None`` then) — because provenance is owed whether or not a preset seeded it."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, step_name: str, params, *, preset=None, ctx=None):
|
|
120
|
+
self.step_name = step_name
|
|
121
|
+
self.params = tuple(params)
|
|
122
|
+
self.preset = preset
|
|
123
|
+
self._seed = {p.name: _seed_value(p, preset, ctx) for p in self.params}
|
|
124
|
+
self.values = dict(self._seed)
|
|
125
|
+
self._modified: set = set()
|
|
126
|
+
|
|
127
|
+
def _param(self, name) -> Optional[StepParam]:
|
|
128
|
+
return next((p for p in self.params if p.name == name), None)
|
|
129
|
+
|
|
130
|
+
def set(self, name, value):
|
|
131
|
+
"""Set a value (coerced to its kind); mark it modified iff it differs from the seed. Setting it back to
|
|
132
|
+
the seeded value clears the modification — a result that matches the seed was not 'modified'."""
|
|
133
|
+
param = self._param(name)
|
|
134
|
+
if param is None:
|
|
135
|
+
return self
|
|
136
|
+
v = _coerce(param, value)
|
|
137
|
+
if self._seed.get(name) != v:
|
|
138
|
+
self._modified.add(name)
|
|
139
|
+
else:
|
|
140
|
+
self._modified.discard(name)
|
|
141
|
+
self.values[name] = v
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def is_modified(self) -> bool:
|
|
146
|
+
return bool(self._modified)
|
|
147
|
+
|
|
148
|
+
def provenance(self) -> dict:
|
|
149
|
+
"""The recorded provenance — the exact :meth:`PresetApplication.record` shape, with ``preset_key``
|
|
150
|
+
``None`` when no preset seeded the step. Travels into the run report and a saved template."""
|
|
151
|
+
return {"preset_key": self.preset.key if self.preset is not None else None,
|
|
152
|
+
"modified_parameters": sorted(self._modified),
|
|
153
|
+
"is_modified": self.is_modified,
|
|
154
|
+
"parameters": dict(self.values)}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ParamReview:
|
|
158
|
+
"""The reviewable parameter set for a whole plan — the adapter-covered, runnable steps that declare
|
|
159
|
+
material params, each a :class:`ReviewedStep`. The dock renders an editable form over it; the executor
|
|
160
|
+
reads :meth:`params_by_step` / :meth:`provenance_by_step`."""
|
|
161
|
+
|
|
162
|
+
def __init__(self, steps):
|
|
163
|
+
self.steps = list(steps)
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_empty(self) -> bool:
|
|
167
|
+
return not self.steps
|
|
168
|
+
|
|
169
|
+
def step(self, name) -> Optional[ReviewedStep]:
|
|
170
|
+
return next((s for s in self.steps if s.step_name == name), None)
|
|
171
|
+
|
|
172
|
+
def params_by_step(self) -> dict:
|
|
173
|
+
return {s.step_name: dict(s.values) for s in self.steps}
|
|
174
|
+
|
|
175
|
+
def provenance_by_step(self) -> dict:
|
|
176
|
+
return {s.step_name: s.provenance() for s in self.steps}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_param_review(plan, ctx=None) -> ParamReview:
|
|
180
|
+
"""Build the reviewable parameter set for ``plan``: one :class:`ReviewedStep` per adapter-covered step that
|
|
181
|
+
will actually run (``run`` / ``caveat`` in :func:`execution_order`) and declares material params, seeded by
|
|
182
|
+
the declared precedence. Blocked/skipped steps carry no review (they will not run). Never invents a
|
|
183
|
+
parameter — a step with no declared material params contributes nothing to review."""
|
|
184
|
+
reviewed = []
|
|
185
|
+
intent = getattr(plan, "intent", None)
|
|
186
|
+
try:
|
|
187
|
+
order = execution_order(plan)
|
|
188
|
+
except Exception: # broad-ok: optional_probe — an un-orderable plan yields an empty review, not a crash
|
|
189
|
+
order = []
|
|
190
|
+
for es in order:
|
|
191
|
+
if es.status not in ("run", "caveat"):
|
|
192
|
+
continue
|
|
193
|
+
# Only steps that will ACTUALLY run get a review — a coarse module whose variant is not auto-runnable
|
|
194
|
+
# (its batch step resolves to None) must not surface a parameter for a step that won't execute.
|
|
195
|
+
if resolve_batch_step(es.name, intent) is None:
|
|
196
|
+
continue
|
|
197
|
+
params = material_params(es.name)
|
|
198
|
+
if not params:
|
|
199
|
+
continue
|
|
200
|
+
reviewed.append(ReviewedStep(es.name, params, preset=_applicable_preset(es.name), ctx=ctx))
|
|
201
|
+
return ParamReview(reviewed)
|
|
@@ -128,17 +128,25 @@ def _render_observations(widget, body_layout):
|
|
|
128
128
|
"color: gray; font-size: 10px; margin-bottom: 4px;"))
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def run_plan_via_central_manager(central_manager, plan):
|
|
131
|
+
def run_plan_via_central_manager(central_manager, plan, review=None):
|
|
132
132
|
"""Execute a compiled plan against the session's data through the execution-adapter layer, and report
|
|
133
133
|
per-step outcomes. Adapter-covered steps run through the batch handlers (the proven 'same computation'
|
|
134
|
-
route); a step with no adapter yet is reported as 'run from its
|
|
135
|
-
|
|
134
|
+
route) with the user-reviewed parameters (Phase 2); a step with no adapter yet is reported as 'run from its
|
|
135
|
+
panel' — never invoked with guessed args. ``review`` is the :class:`ParamReview` the dock built from its
|
|
136
|
+
editable form; when ``None`` (a programmatic run), one is built from the plan so parameters are still
|
|
137
|
+
preset/default-seeded and provenance is still recorded. Safe on an uncovered plan (nothing runs);
|
|
138
|
+
best-effort, never crashes the UI. This is the wired ``on_run``."""
|
|
136
139
|
try:
|
|
137
140
|
from pycat.navigator.executor import run_plan
|
|
138
141
|
dr = getattr(getattr(central_manager, "active_data_class", None), "data_repository", None)
|
|
139
142
|
if dr is None:
|
|
140
143
|
return
|
|
141
|
-
|
|
144
|
+
if review is None:
|
|
145
|
+
from pycat.navigator.parameters import build_param_review
|
|
146
|
+
review = build_param_review(plan, ctx=dr)
|
|
147
|
+
report = run_plan(plan, dr, ctx=dr,
|
|
148
|
+
params_by_step=review.params_by_step(),
|
|
149
|
+
provenance_by_step=review.provenance_by_step())
|
|
142
150
|
ran = [s.name for s in report.ran]
|
|
143
151
|
panel = [s.name for s in report.needs_panel]
|
|
144
152
|
blocked = [s for s in report.steps if s.outcome == "blocked"]
|
|
@@ -149,6 +157,9 @@ def run_plan_via_central_manager(central_manager, plan):
|
|
|
149
157
|
parts.append(f"Stopped at {blocked[0].name} — {blocked[0].detail}")
|
|
150
158
|
if panel:
|
|
151
159
|
parts.append("Run these from their method panels, in order: " + ", ".join(panel) + ".")
|
|
160
|
+
modified = [s.name for s in report.ran if s.provenance.get("is_modified")]
|
|
161
|
+
if modified:
|
|
162
|
+
parts.append("Parameters modified for: " + ", ".join(modified) + ".")
|
|
152
163
|
from napari.utils.notifications import show_info
|
|
153
164
|
show_info(" ".join(parts) or "Nothing to run.")
|
|
154
165
|
except Exception as exc: # broad-ok: ui_cleanup — a run attempt must never crash the guided panel
|
|
@@ -199,6 +210,68 @@ def _add_template_save(widget, body_layout):
|
|
|
199
210
|
body_layout.addWidget(btn)
|
|
200
211
|
|
|
201
212
|
|
|
213
|
+
def _param_input(param, value, on_change):
|
|
214
|
+
"""One editable control for a material parameter, seeded at ``value``; ``on_change(new_value)`` fires on
|
|
215
|
+
edit. int → spin box, float → double spin, choice → combo. Returns the widget (or ``None`` on an odd kind)."""
|
|
216
|
+
from PyQt5.QtWidgets import QSpinBox, QDoubleSpinBox, QComboBox
|
|
217
|
+
if param.kind == "int":
|
|
218
|
+
w = QSpinBox()
|
|
219
|
+
w.setMinimum(int(param.minimum) if param.minimum is not None else 0)
|
|
220
|
+
w.setMaximum(int(param.maximum) if param.maximum is not None else 100000)
|
|
221
|
+
w.setValue(int(value))
|
|
222
|
+
w.valueChanged.connect(lambda v: on_change(v))
|
|
223
|
+
return w
|
|
224
|
+
if param.kind == "float":
|
|
225
|
+
w = QDoubleSpinBox()
|
|
226
|
+
w.setDecimals(3)
|
|
227
|
+
w.setMinimum(float(param.minimum) if param.minimum is not None else 0.0)
|
|
228
|
+
w.setMaximum(float(param.maximum) if param.maximum is not None else 1e9)
|
|
229
|
+
w.setValue(float(value))
|
|
230
|
+
w.valueChanged.connect(lambda v: on_change(v))
|
|
231
|
+
return w
|
|
232
|
+
if param.kind == "choice":
|
|
233
|
+
w = QComboBox()
|
|
234
|
+
w.addItems([str(c) for c in param.choices])
|
|
235
|
+
if str(value) in [str(c) for c in param.choices]:
|
|
236
|
+
w.setCurrentText(str(value))
|
|
237
|
+
w.currentTextChanged.connect(lambda v: on_change(v))
|
|
238
|
+
return w
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _add_param_review(widget, body_layout, plan):
|
|
243
|
+
"""Render the editable parameter-review form (Phase 2) and stash the :class:`ParamReview` on the widget as
|
|
244
|
+
``_review`` for the run. Surfaces the handful of material parameters per adapter-covered step, seeded from a
|
|
245
|
+
preset/session value/grounded default (never invented); an edit is tracked so the run records provenance.
|
|
246
|
+
Renders nothing when no adapter-covered step has material params — the honest 'nothing to review' case."""
|
|
247
|
+
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout
|
|
248
|
+
from pycat.navigator.parameters import build_param_review, material_params
|
|
249
|
+
try:
|
|
250
|
+
review = build_param_review(plan, ctx=widget._session.ctx)
|
|
251
|
+
except Exception: # broad-ok: ui_cleanup — a review failure must not break the plan view
|
|
252
|
+
review = None
|
|
253
|
+
widget._review = review
|
|
254
|
+
if review is None or review.is_empty:
|
|
255
|
+
return
|
|
256
|
+
body_layout.addWidget(_label("⚙ Review parameters", "font-weight: bold; font-size: 12px; margin-top: 6px;"))
|
|
257
|
+
body_layout.addWidget(_label("Seeded from your data and grounded defaults — edit before running.",
|
|
258
|
+
"color: gray; font-size: 10px;"))
|
|
259
|
+
for step in review.steps:
|
|
260
|
+
body_layout.addWidget(_label(step.step_name, "font-weight: bold; font-size: 11px; color: #2a7ab0;"))
|
|
261
|
+
for param in material_params(step.step_name):
|
|
262
|
+
frame = QFrame()
|
|
263
|
+
row = QHBoxLayout(frame)
|
|
264
|
+
row.setContentsMargins(0, 0, 0, 0)
|
|
265
|
+
row.addWidget(_label(param.label, "font-size: 11px;"))
|
|
266
|
+
inp = _param_input(param, step.values.get(param.name, param.default),
|
|
267
|
+
lambda v, s=step, n=param.name: s.set(n, v))
|
|
268
|
+
if inp is not None:
|
|
269
|
+
if param.help:
|
|
270
|
+
inp.setToolTip(param.help)
|
|
271
|
+
row.addWidget(inp)
|
|
272
|
+
body_layout.addWidget(frame)
|
|
273
|
+
|
|
274
|
+
|
|
202
275
|
def build_navigator_widget(session, *, on_run=None, central_manager=None, parent=None):
|
|
203
276
|
"""Build (do not mount) the Navigator widget over ``session``, or return ``None`` if Qt is unavailable
|
|
204
277
|
(headless). ``on_run(plan)`` is called when the user runs the compiled plan. When ``central_manager`` is
|
|
@@ -235,6 +308,7 @@ def build_navigator_widget(session, *, on_run=None, central_manager=None, parent
|
|
|
235
308
|
widget._rows = []
|
|
236
309
|
widget._choice_buttons = []
|
|
237
310
|
widget._run_button = None
|
|
311
|
+
widget._review = None # Phase 2: the ParamReview built at plan render, passed to the run
|
|
238
312
|
widget._regate_only = False # a viewer event re-gates; answering/restart recompiles
|
|
239
313
|
widget._reeval_pending = False
|
|
240
314
|
|
|
@@ -267,6 +341,7 @@ def build_navigator_widget(session, *, on_run=None, central_manager=None, parent
|
|
|
267
341
|
body_layout.addWidget(_label(_PLAN_LEGEND, "color: gray; font-size: 10px; margin-bottom: 4px;"))
|
|
268
342
|
for row in widget._rows:
|
|
269
343
|
body_layout.addWidget(_row_frame(row))
|
|
344
|
+
_add_param_review(widget, body_layout, plan) # Phase 2: the editable, seeded material params
|
|
270
345
|
run = QPushButton("▶ Run analysis")
|
|
271
346
|
reason = widget._session.run_blocked_reason()
|
|
272
347
|
run.setEnabled(reason is None and callable(widget._on_run))
|
|
@@ -289,7 +364,11 @@ def build_navigator_widget(session, *, on_run=None, central_manager=None, parent
|
|
|
289
364
|
|
|
290
365
|
def _run():
|
|
291
366
|
if callable(widget._on_run) and widget._plan is not None:
|
|
292
|
-
|
|
367
|
+
review = getattr(widget, "_review", None)
|
|
368
|
+
try:
|
|
369
|
+
widget._on_run(widget._plan, review) # Phase 2: pass the reviewed params
|
|
370
|
+
except TypeError:
|
|
371
|
+
widget._on_run(widget._plan) # a 1-arg on_run (older callers) still works
|
|
293
372
|
|
|
294
373
|
def _restart():
|
|
295
374
|
widget._session = NavigatorSession()
|