Source code for waveorder.cli.settings

import os
import warnings
from pathlib import Path
from typing import List, Literal, Optional, Union

from pydantic.v1 import (
    BaseModel,
    Extra,
    NonNegativeFloat,
    NonNegativeInt,
    PositiveFloat,
    root_validator,
    validator,
)

# This file defines the configuration settings for the CLI.

# Example settings files in `/docs/examples/settings/` are autmatically generated
# by the tests in `/tests/cli_tests/test_settings.py` - `test_generate_example_settings`.

# To keep the example settings up to date, run `pytest` locally when this file changes.


# All settings classes inherit from MyBaseModel, which forbids extra parameters to guard against typos
[docs] class MyBaseModel(BaseModel, extra=Extra.forbid): pass
# Bottom level settings
[docs] class WavelengthIllumination(MyBaseModel): wavelength_illumination: PositiveFloat = 0.532
[docs] class BirefringenceTransferFunctionSettings(MyBaseModel): swing: float = 0.1
[docs] @validator("swing") def swing_range(cls, v): if v <= 0 or v >= 1.0: raise ValueError(f"swing = {v} should be between 0 and 1.") return v
[docs] class BirefringenceApplyInverseSettings(WavelengthIllumination): background_path: Union[str, Path] = "" remove_estimated_background: bool = False flip_orientation: bool = False rotate_orientation: bool = False
[docs] @validator("background_path") def check_background_path(cls, v): if v == "": return v raw_dir = r"{}".format(v) if not os.path.isdir(raw_dir): raise ValueError(f"{v} is not a existing directory") return raw_dir
[docs] class FourierTransferFunctionSettings(MyBaseModel): yx_pixel_size: PositiveFloat = 6.5 / 20 z_pixel_size: PositiveFloat = 2.0 z_padding: NonNegativeInt = 0 z_focus_offset: Union[int, Literal["auto"]] = 0 index_of_refraction_media: PositiveFloat = 1.3 numerical_aperture_detection: PositiveFloat = 1.2
[docs] @validator("numerical_aperture_detection") def na_det(cls, v, values): n = values["index_of_refraction_media"] if v > n: raise ValueError( f"numerical_aperture_detection = {v} must be less than or equal to index_of_refraction_media = {n}" ) return v
[docs] @validator("z_pixel_size") def warn_unit_consistency(cls, v, values): yx_pixel_size = values["yx_pixel_size"] ratio = yx_pixel_size / v if ratio < 1.0 / 20 or ratio > 20: warnings.warn( f"yx_pixel_size ({yx_pixel_size}) / z_pixel_size ({v}) = {ratio}. Did you use consistent units?", UserWarning, ) return v
[docs] class FourierApplyInverseSettings(MyBaseModel): reconstruction_algorithm: Literal["Tikhonov", "TV"] = "Tikhonov" regularization_strength: NonNegativeFloat = 1e-3 TV_rho_strength: PositiveFloat = 1e-3 TV_iterations: NonNegativeInt = 1
[docs] class PhaseTransferFunctionSettings( FourierTransferFunctionSettings, WavelengthIllumination, ): numerical_aperture_illumination: NonNegativeFloat = 0.5 invert_phase_contrast: bool = False
[docs] @validator("numerical_aperture_illumination") def na_ill(cls, v, values): n = values.get("index_of_refraction_media") if v > n: raise ValueError( f"numerical_aperture_illumination = {v} must be less than or equal to index_of_refraction_media = {n}" ) return v
[docs] class FluorescenceTransferFunctionSettings(FourierTransferFunctionSettings): wavelength_emission: PositiveFloat = 0.507
[docs] @validator("wavelength_emission") def warn_unit_consistency(cls, v, values): yx_pixel_size = values.get("yx_pixel_size") ratio = yx_pixel_size / v if ratio < 1.0 / 20 or ratio > 20: warnings.warn( f"yx_pixel_size ({yx_pixel_size}) / wavelength_illumination ({v}) = {ratio}. Did you use consistent units?", UserWarning, ) return v
# Second level settings
[docs] class BirefringenceSettings(MyBaseModel): transfer_function: BirefringenceTransferFunctionSettings = ( BirefringenceTransferFunctionSettings() ) apply_inverse: BirefringenceApplyInverseSettings = ( BirefringenceApplyInverseSettings() )
[docs] class PhaseSettings(MyBaseModel): transfer_function: PhaseTransferFunctionSettings = ( PhaseTransferFunctionSettings() ) apply_inverse: FourierApplyInverseSettings = FourierApplyInverseSettings()
[docs] class FluorescenceSettings(MyBaseModel): transfer_function: FluorescenceTransferFunctionSettings = ( FluorescenceTransferFunctionSettings() ) apply_inverse: FourierApplyInverseSettings = FourierApplyInverseSettings()
# Top level settings
[docs] class ReconstructionSettings(MyBaseModel): input_channel_names: List[str] = [f"State{i}" for i in range(4)] time_indices: Union[ NonNegativeInt, List[NonNegativeInt], Literal["all"] ] = "all" reconstruction_dimension: Literal[2, 3] = 3 birefringence: Optional[BirefringenceSettings] phase: Optional[PhaseSettings] fluorescence: Optional[FluorescenceSettings]
[docs] @root_validator(pre=False) def validate_reconstruction_types(cls, values): if (values.get("birefringence") or values.get("phase")) and values.get( "fluorescence" ) is not None: raise ValueError( '"fluorescence" cannot be present alongside "birefringence" or "phase". Please use one configuration file for a "fluorescence" reconstruction and another configuration file for a "birefringence" and/or "phase" reconstructions.' ) num_channel_names = len(values.get("input_channel_names")) if values.get("birefringence") is None: if ( values.get("phase") is None and values.get("fluorescence") is None ): raise ValueError( "Provide settings for either birefringence, phase, birefringence + phase, or fluorescence." ) if num_channel_names != 1: raise ValueError( f"{num_channel_names} channels names provided. Please provide a single channel for fluorescence/phase reconstructions." ) return values