-
Notifications
You must be signed in to change notification settings - Fork 47
refactor: New design for illumination channel configs and acquisition configs #417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Replace legacy XML/JSON acquisition configs with YAML-based system
- Add Pydantic models for type-safe configuration validation
- Implement hierarchical config structure:
- machine_configs/illumination_channel_config.yaml: Hardware channels
- user_profiles/{profile}/channel_configs/general.yaml: Shared settings
- user_profiles/{profile}/channel_configs/{objective}.yaml: Per-objective overrides
- Add ConfigLoader for YAML config loading/saving
- Add merge logic combining general + objective configs
- Add validation for illumination channel references
- Add migration script (tools/migrate_acquisition_configs.py)
- Add default config generator for new profiles
- Remove legacy channel_definitions.default.json and channel_mappings.json
- Save acquisition configs as YAML instead of XML
Field distribution:
- general.yaml: illumination_channels, display_color, z_offset_um, emission_filter_wheel_position
- objective.yaml: intensity, exposure_time_ms, gain_mode, pixel_format, confocal_override
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a major refactoring of the acquisition configuration system, migrating from XML/JSON to YAML-based configs with Pydantic models for type safety. The new hierarchical structure separates hardware-level illumination configs from user-facing acquisition settings, with support for per-objective overrides and confocal mode.
Key changes:
- Replace legacy XML/JSON configs with YAML + Pydantic models
- Introduce hierarchical config structure: machine_configs, user_profiles with general + objective-specific files
- Add migration script for existing configurations with automatic migration on startup
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
tools/migrate_acquisition_configs.py |
Migration script converting XML/JSON to YAML format with wavelength-based channel matching |
tools/view_laser_af_reference_image.py |
Updated to support both JSON and YAML laser AF settings |
tools/generate_intensity_calibrations.py |
Updated output path to machine_configs directory |
control/models/*.py |
New Pydantic models for type-safe configuration validation |
control/config_loader.py |
YAML loading/saving with Pydantic model integration |
control/default_config_generator.py |
Generates default configs for new profiles from illumination channels |
control/core/channel_configuration_mananger.py |
Refactored to use YAML configs with merge logic |
control/lighting.py |
Updated to load channel mappings from YAML |
control/widgets.py |
Updated illumination channel configurator dialog |
main_hcs.py |
Added auto-migration call on startup |
machine_configs/*.yaml |
Example YAML configuration files |
| Tests | Comprehensive test coverage for models, migration, and config loading |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| and filter wheel associations. | ||
| """ | ||
|
|
||
| from typing import Any, Dict, Optional |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Any' is not used.
| from typing import Any, Dict, Optional | |
| from typing import Dict, Optional |
| CONFOCAL = "confocal" | ||
| WIDEFIELD = "widefield" | ||
| from control.utils_config import ChannelMode | ||
| import control._def |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'control' is not used.
| import control._def |
software/control/config_loader.py
Outdated
|
|
||
| import logging | ||
| from pathlib import Path | ||
| from typing import Dict, List, Optional, Type, TypeVar |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Dict' is not used.
| from typing import Dict, List, Optional, Type, TypeVar | |
| from typing import List, Optional, Type, TypeVar |
| from enum import Enum | ||
| from typing import ClassVar, Dict, List, Optional | ||
|
|
||
| from pydantic import BaseModel, Field, model_validator |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'model_validator' is not used.
| from pydantic import BaseModel, Field, model_validator | |
| from pydantic import BaseModel, Field |
| calibration data and detection parameters. | ||
| """ | ||
|
|
||
| from typing import List, Optional, Tuple |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Tuple' is not used.
| from typing import List, Optional, Tuple | |
| from typing import List, Optional |
| import xml.etree.ElementTree as ET | ||
| from datetime import datetime | ||
| from pathlib import Path | ||
| from typing import Any, Dict, List, Optional, Tuple |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Tuple' is not used.
| from control.models.illumination_config import ( | ||
| DEFAULT_LED_COLOR, | ||
| DEFAULT_WAVELENGTH_COLORS, | ||
| IlluminationType, | ||
| ) |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'DEFAULT_LED_COLOR' is not used.
Import of 'DEFAULT_WAVELENGTH_COLORS' is not used.
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| import yaml |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'yaml' is not used.
| import yaml |
- Remove unused imports flagged by Copilot review: - control/models/camera_config.py: Any - control/core/channel_configuration_mananger.py: control._def - control/config_loader.py: Dict - control/models/illumination_config.py: model_validator - control/models/laser_af_config.py: Tuple - tools/migrate_acquisition_configs.py: Tuple, DEFAULT_LED_COLOR, DEFAULT_WAVELENGTH_COLORS - tests/control/test_config_loader.py: yaml, CameraMappingsConfig - Fix test function names: create_acquisition_channel_from_illumination -> create_general_acquisition_channel / create_objective_acquisition_channel - Apply black formatting to 16 files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement Phase 1 of configuration management consolidation: - ConfigRepository class with profile management and caching - Machine configs (illumination, confocal, camera mappings) - Profile configs (general, objective, laser AF) - Comprehensive unit tests (22 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 2 of configuration management consolidation: - apply_confocal_override(): Apply confocal overrides to channel list - get_effective_channels(): Merge + confocal override in one call - copy_profile_configs(): Copy configs between profiles - Re-export merge_channel_configs, validate_illumination_references, get_illumination_channel_names from models 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 3 partial progress - configuration management consolidation: Microscope: - Add ConfigRepository instance for centralized config access AcquisitionChannel: - Add convenience properties: exposure_time, analog_gain, display_color, illumination_intensity, primary_illumination_channel, z_offset, emission_filter_position - Add get_illumination_source_code() and get_illumination_wavelength() methods that look up from IlluminationChannelConfig LiveController: - Add type annotation for currentConfiguration as AcquisitionChannel - Add helper methods: _get_illumination_config(), _get_illumination_source(), _get_illumination_wavelength(), _is_led_matrix() - Update illumination methods to use structured config instead of name-based parsing for source code and wavelength Note: ChannelMode still used for signals - full migration continues in subsequent commits. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace ChannelMode with AcquisitionChannel throughout: - Update signal types in core.py and gui_hcs.py - Update CaptureInfo.configuration type in job_processing.py - Update multi_point_utils and multi_point_worker type hints - Update utils_acquisition.py save_image function signature - Add id property and setters to AcquisitionChannel for UI compatibility - Update all test files to use AcquisitionChannel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update ChannelConfigurationManager to return AcquisitionChannel directly - Remove _acquisition_channel_to_channel_mode conversion method - Delete control/utils_config.py (ChannelMode and old models) - Delete tests/control/core/test_channel_configuration.py (old tests) - Update LaserAFConfig imports to use control.models - Remove unused utils_config imports from core modules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add set_reference_image() method and reference_image_cropped property that were removed when utils_config.py was deleted. These methods are needed by the laser autofocus controller. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use illumination channel name directly for acquisition channels (e.g., "Fluorescence 405 nm Ex" instead of "405 nm") - Add setAutoDefault(False) to all buttons in IlluminationChannelConfiguratorDialog - Update illumination_channel_config.yaml with all channels in correct order - Remove illumination_channel_config.yaml.example (redundant) - Add illumination_channel_config.yaml to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clean up redundant and vague comments in default_config_generator.py and acquisition_config.py. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 53 out of 53 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """ | ||
|
|
||
| import base64 | ||
| from typing import List, Optional, Tuple |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Tuple' is not used.
| from typing import List, Optional, Tuple | |
| from typing import List, Optional |
| from control.models import ( | ||
| GeneralChannelConfig, | ||
| ObjectiveChannelConfig, | ||
| IlluminationChannelConfig, | ||
| LaserAFConfig, | ||
| AcquisitionChannel, | ||
| IlluminationSettings, | ||
| CameraSettings, | ||
| ) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'IlluminationChannelConfig' is not used.
Import of 'LaserAFConfig' is not used.
| """ | ||
|
|
||
| import shutil | ||
| from pathlib import Path |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Path' is not used.
| from pathlib import Path |
Address PR review comments: - Remove unused Tuple import from laser_af_config.py - Remove unused IlluminationChannelConfig and LaserAFConfig imports from test_repository.py - Remove unused Path import from utils.py - Update test to expect preserved illumination channel names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy YAML files directly from disk instead of from memory cache. This ensures all saved changes are preserved, including objectives not currently loaded in memory. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove the deprecated ConfigLoader class by migrating all functionality to ConfigRepository. This eliminates code duplication and provides a single source of truth for configuration I/O and caching. Changes: - Add missing methods to ConfigRepository (save_illumination_config, save_confocal_config, save_camera_mappings, ensure_machine_configs_directory, profile_has_configs, ensure_profile_directories, get_profile_path) - Add ConfigLoader compatibility aliases (load_* methods) - Update all callers to use ConfigRepository instead of ConfigLoader - Rename test_config_loader.py to test_default_config_generator.py - Remove redundant ConfigRepository tests (covered in test_repository.py) - Remove config_loader.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove load_* compatibility aliases from ConfigRepository - Rename config_loader variables to config_repo throughout codebase - Change all load_* method calls to get_* methods - Use consistent naming: ConfigRepository with get_*/save_* API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AcquisitionChannel model doesn't have illumination_source attribute. Skip emitting image_to_display_multi when USE_NAPARI_FOR_MULTIPOINT is enabled since napari uses channel names instead. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…eration - Change migration to use channel_configurations.xml instead of widefield_configurations.xml (confocal handling removed for now) - Add has_legacy_configs_to_migrate() to detect pending migrations - Block default config generation when legacy XML files exist - Update tests to use channel_configurations.xml This fixes the race condition where default configs were generated before migration could run, causing user settings to be lost. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
d2faa82 to
83b62eb
Compare
…ry (#441) Following the work in #417 ## Summary - Removes `ChannelConfigurationManager` and `ConfigurationManager` classes - Centralizes all configuration management in `ConfigRepository` (pure Python, no Qt dependencies) - Moves `confocal_mode` state from config manager to `LiveController` where it belongs - Updates widgets and controllers to use `LiveController.get_channels()` for channel access - Adds migration script for legacy `acquisition_configurations/` to new `user_profiles/` format ## Key Changes - **ConfigRepository**: Single source of truth for all config I/O and caching - **LiveController.get_channels()**: New method to get acquisition channels for an objective - **ProfileWidget**: Uses ConfigRepository directly instead of ConfigurationManager - **Migration**: Auto-migration runs on startup to convert legacy XML configs to YAML ## Test plan - [x] Run all tests with `pytest tests/` - [x] Run `python3 main_hcs.py --simulation` - verify app starts and loads configs 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add documentation for the YAML-based configuration system introduced in PRs #417 and #441: - configuration-system.md: Main guide covering architecture, machine configs (illumination, confocal, camera mappings), user profiles (general.yaml, objective.yaml), merge logic, and best practices - configuration-api.md: Developer reference for ConfigRepository API, Pydantic models, utility functions, and access patterns - configuration-migration.md: Migration guide from legacy XML/JSON format including auto-migration, manual migration script usage, and troubleshooting - Update automation.md to reference new configuration docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add documentation for the YAML-based configuration system introduced in PRs #417 and #441: - configuration-system.md: Main guide covering architecture, machine configs (illumination, confocal, camera mappings), user profiles (general.yaml, objective.yaml), merge logic, and best practices - configuration-api.md: Developer reference for ConfigRepository API, Pydantic models, utility functions, and access patterns - configuration-migration.md: Migration guide from legacy XML/JSON format including auto-migration, manual migration script usage, and troubleshooting - Update automation.md to reference new configuration docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Changes
New Config Structure
machine_configs/illumination_channel_config.yaml: Hardware-level illumination channelsuser_profiles/{profile}/channel_configs/general.yaml: Shared settings across objectivesuser_profiles/{profile}/channel_configs/{objective}.yaml: Per-objective overridesField Distribution
illumination_channels,display_color,z_offset_um,emission_filter_wheel_positionintensity,exposure_time_ms,gain_mode,pixel_format,confocal_overrideNew Files
control/models/- Pydantic models for acquisition configscontrol/config_loader.py- YAML config loading/savingcontrol/default_config_generator.py- Generate default configs for new profilestools/migrate_acquisition_configs.py- Migration script for legacy configsRemoved
configurations/channel_definitions.default.jsonconfigurations/channel_mappings.jsonRemaining Work (Future PRs)
Test plan
🤖 Generated with Claude Code