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.
Files changed (325) hide show
  1. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/CHANGELOG.md +51 -0
  2. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/PKG-INFO +1 -1
  3. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/pyproject.toml +1 -1
  4. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/central_manager.py +1 -1
  5. pycat_napari-1.6.334/src/pycat/navigator/executor.py +231 -0
  6. pycat_napari-1.6.334/src/pycat/navigator/parameters.py +201 -0
  7. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/navigator_dock.py +84 -5
  8. pycat_napari-1.6.332/src/pycat/navigator/executor.py +0 -163
  9. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/.gitignore +0 -0
  10. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/CONTRIBUTING.md +0 -0
  11. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/LICENSE +0 -0
  12. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/README.md +0 -0
  13. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/THIRD_PARTY_LICENSES.txt +0 -0
  14. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/benchmarks/README.md +0 -0
  15. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/README.md +0 -0
  16. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-arm-mac.txt +0 -0
  17. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-base.txt +0 -0
  18. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-devbio-napari-arm-mac.txt +0 -0
  19. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/config/requirements-devbio-napari.txt +0 -0
  20. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/__init__.py +0 -0
  21. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/__init__.py +0 -0
  22. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/__init__.py +0 -0
  23. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/_common.py +0 -0
  24. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/analysis_steps.py +0 -0
  25. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/brightfield_steps.py +0 -0
  26. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/invitro_steps.py +0 -0
  27. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/io_steps.py +0 -0
  28. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/preprocessing_steps.py +0 -0
  29. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch/steps/segmentation_steps.py +0 -0
  30. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch_processor.py +0 -0
  31. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/batch_step_registry.py +0 -0
  32. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/data/__init__.py +0 -0
  33. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/data/data_modules.py +0 -0
  34. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/__init__.py +0 -0
  35. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/czi_seam.py +0 -0
  36. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/dialogs.py +0 -0
  37. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/file_io.py +0 -0
  38. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/frap_io.py +0 -0
  39. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_reader.py +0 -0
  40. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_source.py +0 -0
  41. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/image_structure.py +0 -0
  42. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/lazy_guard.py +0 -0
  43. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/lazy_sources.py +0 -0
  44. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/load_channel_identity.py +0 -0
  45. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/local_cache.py +0 -0
  46. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/metadata_extract.py +0 -0
  47. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/multidim_io.py +0 -0
  48. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/naming.py +0 -0
  49. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/napari_adapter.py +0 -0
  50. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/__init__.py +0 -0
  51. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/czi_bioformats.py +0 -0
  52. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/image_reader_2d.py +0 -0
  53. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/ims_reader.py +0 -0
  54. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/mask_reader.py +0 -0
  55. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/stack_layer_builders.py +0 -0
  56. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/readers/stack_metadata.py +0 -0
  57. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/routing.py +0 -0
  58. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/scenes.py +0 -0
  59. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session.py +0 -0
  60. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session_loader.py +0 -0
  61. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/session_manifest.py +0 -0
  62. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/sidecar_discovery.py +0 -0
  63. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_access.py +0 -0
  64. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_load.py +0 -0
  65. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/stack_openers.py +0 -0
  66. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/storage_probe.py +0 -0
  67. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tagging.py +0 -0
  68. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tiff_planes.py +0 -0
  69. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/tifffile_zarr_shim.py +0 -0
  70. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/viewer_load.py +0 -0
  71. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/writers.py +0 -0
  72. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/file_io/zarr_compat.py +0 -0
  73. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/fix_tifffile.py +0 -0
  74. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/icons/pycat_logo_512.png +0 -0
  75. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/icons/pycat_mark.png +0 -0
  76. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/__init__.py +0 -0
  77. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/adapters.py +0 -0
  78. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/canonical.py +0 -0
  79. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/capabilities.py +0 -0
  80. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/context.py +0 -0
  81. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/contracts.py +0 -0
  82. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_layer_tag_hierarchy_and_module_flow.xlsx +0 -0
  83. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_module_information_contracts.xlsx +0 -0
  84. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/PyCAT_question_tree_and_method_mapping.xlsx +0 -0
  85. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/data/operation_catalog.json +0 -0
  86. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/execution.py +0 -0
  87. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/gates.py +0 -0
  88. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/loader.py +0 -0
  89. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/modules.py +0 -0
  90. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/op_catalog.py +0 -0
  91. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/operation_spec.py +0 -0
  92. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/planner.py +0 -0
  93. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/quality_gates.py +0 -0
  94. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/question_engine.py +0 -0
  95. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/registry.py +0 -0
  96. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/scientific_tree.py +0 -0
  97. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/session.py +0 -0
  98. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/tags.py +0 -0
  99. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/navigator/templates.py +0 -0
  100. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/run_pycat.py +0 -0
  101. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/__init__.py +0 -0
  102. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/advanced_analysis_ui.py +0 -0
  103. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/analysis_plots.py +0 -0
  104. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/batch_roi_tools.py +0 -0
  105. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/benchmark_tools.py +0 -0
  106. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/biological_qc_tools.py +0 -0
  107. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/brightfield_tools.py +0 -0
  108. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/brightfield_ui.py +0 -0
  109. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/clean_spot_detection_tools.py +0 -0
  110. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_modes.py +0 -0
  111. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/__init__.py +0 -0
  112. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/coarsening.py +0 -0
  113. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/frame_quality.py +0 -0
  114. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/intensity.py +0 -0
  115. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/moduli.py +0 -0
  116. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/msd.py +0 -0
  117. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/photobleaching.py +0 -0
  118. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/relaxation.py +0 -0
  119. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics/survival.py +0 -0
  120. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics_tools.py +0 -0
  121. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/condensate_physics_ui.py +0 -0
  122. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/contrast_cascade_tools.py +0 -0
  123. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/contrast_cascade_ui.py +0 -0
  124. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/control_validation.py +0 -0
  125. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/correlation_func_analysis_tools.py +0 -0
  126. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_qc_tools.py +0 -0
  127. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_qc_ui.py +0 -0
  128. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/data_viz_tools.py +0 -0
  129. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/drawing_layers.py +0 -0
  130. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/dynamic_spatial_tools.py +0 -0
  131. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fd_curve_tools.py +0 -0
  132. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fd_curve_ui.py +0 -0
  133. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/feature_analysis_tools.py +0 -0
  134. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/feature_redundancy.py +0 -0
  135. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fft_bandpass_tools.py +0 -0
  136. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fibril_tools.py +0 -0
  137. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fibril_ui.py +0 -0
  138. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/frap_tools.py +0 -0
  139. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/frap_ui.py +0 -0
  140. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fusion_tools.py +0 -0
  141. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/fusion_ui.py +0 -0
  142. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/gaussian_localization_tools.py +0 -0
  143. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/general_image_tools.py +0 -0
  144. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/gpu_utils.py +0 -0
  145. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/__init__.py +0 -0
  146. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/_base.py +0 -0
  147. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/background.py +0 -0
  148. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/deblur.py +0 -0
  149. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/filters.py +0 -0
  150. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/preprocessing.py +0 -0
  151. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/size_estimation.py +0 -0
  152. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing/upscaling.py +0 -0
  153. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/image_processing_tools.py +0 -0
  154. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/intensity_profile_tools.py +0 -0
  155. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/__init__.py +0 -0
  156. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/analysis.py +0 -0
  157. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/field_summary.py +0 -0
  158. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/partition.py +0 -0
  159. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro/size_distribution.py +0 -0
  160. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_bf_ui.py +0 -0
  161. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_fluor_ui.py +0 -0
  162. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/invitro_tools.py +0 -0
  163. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/kymograph_tools.py +0 -0
  164. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/label_and_mask_tools.py +0 -0
  165. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/layer_tools.py +0 -0
  166. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/measurement_stability.py +0 -0
  167. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/molecular_counting_tools.py +0 -0
  168. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/morphological_complexity_tools.py +0 -0
  169. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/nb_tools.py +0 -0
  170. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/nb_ui.py +0 -0
  171. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/numba_utils.py +0 -0
  172. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/obj_based_coloc_analysis_tools.py +0 -0
  173. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/organizational_metrics_tools.py +0 -0
  174. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/partial_volume_tools.py +0 -0
  175. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/partition_enrichment_tools.py +0 -0
  176. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pipeline_diagnostic_tools.py +0 -0
  177. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pipeline_snr_tools.py +0 -0
  178. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/pixel_wise_corr_analysis_tools.py +0 -0
  179. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/qc_gallery.py +0 -0
  180. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/qc_gallery_ui.py +0 -0
  181. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ratiometric_tools.py +0 -0
  182. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/scan_qc_tools.py +0 -0
  183. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/__init__.py +0 -0
  184. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/_common.py +0 -0
  185. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/cellpose.py +0 -0
  186. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/fz.py +0 -0
  187. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/intensity.py +0 -0
  188. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/local_thresholding.py +0 -0
  189. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/morphology.py +0 -0
  190. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/puncta_refinement.py +0 -0
  191. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/subcellular.py +0 -0
  192. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation/watershed.py +0 -0
  193. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation_scale_advisor.py +0 -0
  194. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/segmentation_tools.py +0 -0
  195. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/smlm_tools.py +0 -0
  196. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_acf_tools.py +0 -0
  197. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_metrology_tools.py +0 -0
  198. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_metrology_ui.py +0 -0
  199. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spatial_randomness_tools.py +0 -0
  200. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spida_tools.py +0 -0
  201. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/spida_ui.py +0 -0
  202. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temperature_tools.py +0 -0
  203. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temperature_ui.py +0 -0
  204. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/temporal_enhancement_tools.py +0 -0
  205. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/__init__.py +0 -0
  206. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/analysis.py +0 -0
  207. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/correlation.py +0 -0
  208. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/execution.py +0 -0
  209. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/frame_access.py +0 -0
  210. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/preprocessing.py +0 -0
  211. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries/ui.py +0 -0
  212. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_condensate_tools.py +0 -0
  213. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_invitro_fluor_ui.py +0 -0
  214. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/timeseries_invitro_tools.py +0 -0
  215. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/topology_tools.py +0 -0
  216. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/trackmate_bridge.py +0 -0
  217. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ts_cache_manager.py +0 -0
  218. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/ts_cellpose_tools.py +0 -0
  219. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/two_channel_coloc_tools.py +0 -0
  220. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/unmixing_tools.py +0 -0
  221. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/video_export_tools.py +0 -0
  222. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/__init__.py +0 -0
  223. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/analysis.py +0 -0
  224. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/detection.py +0 -0
  225. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/drift.py +0 -0
  226. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/host.py +0 -0
  227. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/msd_adapter.py +0 -0
  228. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/napari_adapter.py +0 -0
  229. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/panels.py +0 -0
  230. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/populations.py +0 -0
  231. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/results_dock.py +0 -0
  232. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/table_adapter.py +0 -0
  233. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt/viscosity.py +0 -0
  234. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt_tools.py +0 -0
  235. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/vpt_ui.py +0 -0
  236. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/zstack_segmentation_tools.py +0 -0
  237. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/toolbox/zstack_segmentation_ui.py +0 -0
  238. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/__init__.py +0 -0
  239. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/brushable_table.py +0 -0
  240. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/brushable_workspace.py +0 -0
  241. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/channel_identity_dialog.py +0 -0
  242. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/comparative_figures_ui.py +0 -0
  243. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/coordinate_readout.py +0 -0
  244. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/feature_explorer_dock.py +0 -0
  245. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/field_status.py +0 -0
  246. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/home_dock.py +0 -0
  247. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/linked_selection_dock.py +0 -0
  248. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/menu_manager.py +0 -0
  249. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/metadata_contradiction_panel.py +0 -0
  250. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/operation_gating.py +0 -0
  251. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/preferences_dialog.py +0 -0
  252. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/preset_picker.py +0 -0
  253. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/scene_switcher.py +0 -0
  254. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_analysis_mixin.py +0 -0
  255. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_diagnostics_mixin.py +0 -0
  256. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_filtering_mixin.py +0 -0
  257. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_imageops_mixin.py +0 -0
  258. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_labels_mixin.py +0 -0
  259. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_modules.py +0 -0
  260. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_segmentation_mixin.py +0 -0
  261. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/ui_utils.py +0 -0
  262. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/ui/workflow_checklist.py +0 -0
  263. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/__init__.py +0 -0
  264. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/analysis_presets.py +0 -0
  265. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/app_mode.py +0 -0
  266. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/batch_brushing.py +0 -0
  267. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/brushing.py +0 -0
  268. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/calibration.py +0 -0
  269. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_designations.py +0 -0
  270. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_modality.py +0 -0
  271. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/channel_naming.py +0 -0
  272. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/cohort_targets.py +0 -0
  273. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/comparative_figures.py +0 -0
  274. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/comparative_stats.py +0 -0
  275. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/consolidated_table.py +0 -0
  276. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/dataset_identity.py +0 -0
  277. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/dock_space.py +0 -0
  278. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/entity_ref.py +0 -0
  279. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/entity_registry.py +0 -0
  280. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/environment_check.py +0 -0
  281. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/errors.py +0 -0
  282. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_cards.py +0 -0
  283. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_explorer.py +0 -0
  284. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_families.py +0 -0
  285. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_provenance.py +0 -0
  286. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/feature_registry.py +0 -0
  287. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/field_registry_hub.py +0 -0
  288. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/figure_refine.py +0 -0
  289. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/figure_spec.py +0 -0
  290. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/filter_store.py +0 -0
  291. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/fit_quality.py +0 -0
  292. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/frame_interval.py +0 -0
  293. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/general_utils.py +0 -0
  294. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/intensity_semantics.py +0 -0
  295. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_bindings.json +0 -0
  296. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_tag_hook.py +0 -0
  297. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/layer_tags.py +0 -0
  298. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/logging_utils.py +0 -0
  299. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/marker_logic.py +0 -0
  300. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/math_utils.py +0 -0
  301. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/measurement.py +0 -0
  302. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/measurement_ontology.py +0 -0
  303. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/metadata_contradictions.py +0 -0
  304. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/metadata_validity.py +0 -0
  305. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/notify.py +0 -0
  306. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/object_graph.py +0 -0
  307. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/object_ref.py +0 -0
  308. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/operation_runner.py +0 -0
  309. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/pixel_size.py +0 -0
  310. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backend_pyqtgraph.py +0 -0
  311. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backend_selection.py +0 -0
  312. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/plot_backends.py +0 -0
  313. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/preferences.py +0 -0
  314. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/qt_worker.py +0 -0
  315. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/quality_gate.py +0 -0
  316. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/reliability.py +0 -0
  317. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/result_models.py +0 -0
  318. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/results_store.py +0 -0
  319. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/sample_metadata.py +0 -0
  320. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/selection_overlay.py +0 -0
  321. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/selection_service.py +0 -0
  322. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/tag_registry.py +0 -0
  323. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/tag_resolver.py +0 -0
  324. {pycat_napari-1.6.332 → pycat_napari-1.6.334}/src/pycat/utils/user_settings.py +0 -0
  325. {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.332
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.332"
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 panel' — never invoked with guessed args.
135
- Safe on an uncovered plan (nothing runs); best-effort, never crashes the UI. This is the wired ``on_run``."""
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
- report = run_plan(plan, dr, ctx=dr)
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
- widget._on_run(widget._plan)
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()