From bd646ab14ba64d95aedfa85daab33d3a8fcfdb54 Mon Sep 17 00:00:00 2001 From: Jennifer Pollack Date: Wed, 16 Sep 2026 15:31:04 +0200 Subject: [PATCH 1/2] Replace mask_obscuration with pixel_mask(s) --- config/quality_control.yaml | 10 +++++---- .../{mask_obscuration.py => pixel_masks.py} | 8 +++---- .../quality_control/metrics/registry.py | 4 ++-- .../tests/test_quality_control/config_test.py | 22 +++++++++---------- .../data/invalid/invalid_metric.yaml | 2 +- .../data/invalid/metric_invalid_enabled.yaml | 2 +- .../data/invalid/metric_missing_sections.yaml | 2 +- .../invalid/rejection_metric_not_enabled.yaml | 10 +++++---- .../data/valid/metric_minimal.yaml | 2 +- .../data/valid/quality_control.yaml | 10 +++++---- ...y_control_multiple_rejection_policies.yaml | 10 +++++---- .../test_quality_control/pipeline_test.py | 22 +++++++++---------- .../test_quality_control/resources_test.py | 2 +- .../qc_metrics_registry_test.py | 8 +++---- 14 files changed, 61 insertions(+), 53 deletions(-) rename src/wf_psf/quality_control/metrics/{mask_obscuration.py => pixel_masks.py} (56%) diff --git a/config/quality_control.yaml b/config/quality_control.yaml index f11d04ca..8517e663 100644 --- a/config/quality_control.yaml +++ b/config/quality_control.yaml @@ -8,11 +8,13 @@ resources: metrics: - mask_obscuration: + pixel_mask: enabled: true params: - aperture: gaussian - sigma: 2.5 + aperture: + type: circular + radius: 2.6 + unit: sigma goodness_of_fit: enabled: true @@ -25,7 +27,7 @@ metrics: rejection: - mask_obscuration: + pixel_mask: enabled: true policy: threshold: diff --git a/src/wf_psf/quality_control/metrics/mask_obscuration.py b/src/wf_psf/quality_control/metrics/pixel_masks.py similarity index 56% rename from src/wf_psf/quality_control/metrics/mask_obscuration.py rename to src/wf_psf/quality_control/metrics/pixel_masks.py index 19077f1c..10d67793 100644 --- a/src/wf_psf/quality_control/metrics/mask_obscuration.py +++ b/src/wf_psf/quality_control/metrics/pixel_masks.py @@ -10,11 +10,11 @@ from .base import QualityMetric -class MaskObscurationMetric(QualityMetric): - """Assess masked pixel obscuration for each dataset sample.""" +class PixelMaskMetric(QualityMetric): + """Evaluate pixel-mask metrics for each dataset sample.""" - name = "mask_obscuration" + name = "pixel_mask" def compute(self, dataset): - """Compute the mask obscuration metric for each dataset sample.""" + """Compute pixel-mask metrics for each dataset sample.""" pass diff --git a/src/wf_psf/quality_control/metrics/registry.py b/src/wf_psf/quality_control/metrics/registry.py index f283189f..0f0b9d79 100644 --- a/src/wf_psf/quality_control/metrics/registry.py +++ b/src/wf_psf/quality_control/metrics/registry.py @@ -7,7 +7,7 @@ Jennifer Pollack """ -from .mask_obscuration import MaskObscurationMetric +from .pixel_masks import PixelMaskMetric from .goodness_of_fit import GoodnessOfFitMetric from .base import QualityMetric from wf_psf.utils.registry import Registry @@ -39,6 +39,6 @@ def register_metric(self, metric_cls: type[QualityMetric]) -> None: def build_metrics_registry(): """Create the registry of built-in quality metrics.""" registry = MetricsRegistry() - registry.register_metric(MaskObscurationMetric) + registry.register_metric(PixelMaskMetric) registry.register_metric(GoodnessOfFitMetric) return registry diff --git a/src/wf_psf/tests/test_quality_control/config_test.py b/src/wf_psf/tests/test_quality_control/config_test.py index 53348507..8789b67b 100644 --- a/src/wf_psf/tests/test_quality_control/config_test.py +++ b/src/wf_psf/tests/test_quality_control/config_test.py @@ -47,18 +47,18 @@ def test_quality_control_config_loading(): == "inference_oversampled.yaml" ) - assert "mask_obscuration" in config.metrics - assert isinstance(config.metrics["mask_obscuration"], QualityMetricConfig) - assert config.metrics["mask_obscuration"].enabled is True - assert config.metrics["mask_obscuration"].required_resources == [] + assert "pixel_mask" in config.metrics + assert isinstance(config.metrics["pixel_mask"], QualityMetricConfig) + assert config.metrics["pixel_mask"].enabled is True + assert config.metrics["pixel_mask"].required_resources == [] assert isinstance(config.metrics["goodness_of_fit"], QualityMetricConfig) assert config.metrics["goodness_of_fit"].required_resources == ( [ResourceIdentifier.from_string("psf_models.standard")] ) - assert isinstance(config.rejection["mask_obscuration"], RejectionPolicyConfig) - assert config.rejection["mask_obscuration"].policy == { + assert isinstance(config.rejection["pixel_mask"], RejectionPolicyConfig) + assert config.rejection["pixel_mask"].policy == { "threshold": { "value": 3.0, }, @@ -101,8 +101,8 @@ def test_required_resources_element_must_be_a_str(): def test_metrics_minimal(): config = load_config("valid/metric_minimal.yaml") - assert config.metrics["mask_obscuration"].enabled is True - assert config.metrics["mask_obscuration"].params == {} + assert config.metrics["pixel_mask"].enabled is True + assert config.metrics["pixel_mask"].params == {} assert config.rejection == {} assert config.reporting.save_metrics is False assert config.reporting.log_statistics is False @@ -111,7 +111,7 @@ def test_metrics_minimal(): def test_metric_enabled_must_be_boolean(): with pytest.raises( TypeError, - match="Metric `enabled` flag for 'mask_obscuration' must be boolean", + match="Metric `enabled` flag for 'pixel_mask' must be boolean", ): load_config("invalid/metric_invalid_enabled.yaml") @@ -264,11 +264,11 @@ def test_validate_rejection_policy_metrics_all_valid(qc_config_factory): def test_validate_rejection_policy_metrics_metric_not_found(qc_config_factory): - config = qc_config_factory(rejection_metric="mask_obscuration") + config = qc_config_factory(rejection_metric="pixel_mask") with pytest.raises( ValueError, - match="Rejection policy configured for unknown metric 'mask_obscuration'.", + match="Rejection policy configured for unknown metric 'pixel_mask'.", ): validate_rejection_policy_metrics(config) diff --git a/src/wf_psf/tests/test_quality_control/data/invalid/invalid_metric.yaml b/src/wf_psf/tests/test_quality_control/data/invalid/invalid_metric.yaml index 6d29848a..35ee0947 100644 --- a/src/wf_psf/tests/test_quality_control/data/invalid/invalid_metric.yaml +++ b/src/wf_psf/tests/test_quality_control/data/invalid/invalid_metric.yaml @@ -1,3 +1,3 @@ metrics: - mask_obscuration: true + pixel_mask: true diff --git a/src/wf_psf/tests/test_quality_control/data/invalid/metric_invalid_enabled.yaml b/src/wf_psf/tests/test_quality_control/data/invalid/metric_invalid_enabled.yaml index 5693d88c..4b5dfef4 100644 --- a/src/wf_psf/tests/test_quality_control/data/invalid/metric_invalid_enabled.yaml +++ b/src/wf_psf/tests/test_quality_control/data/invalid/metric_invalid_enabled.yaml @@ -1,3 +1,3 @@ metrics: - mask_obscuration: + pixel_mask: enabled: "yes" \ No newline at end of file diff --git a/src/wf_psf/tests/test_quality_control/data/invalid/metric_missing_sections.yaml b/src/wf_psf/tests/test_quality_control/data/invalid/metric_missing_sections.yaml index 039ae61d..1cb23f13 100644 --- a/src/wf_psf/tests/test_quality_control/data/invalid/metric_missing_sections.yaml +++ b/src/wf_psf/tests/test_quality_control/data/invalid/metric_missing_sections.yaml @@ -1,2 +1,2 @@ metrics: - mask_obscuration: \ No newline at end of file + pixel_mask: \ No newline at end of file diff --git a/src/wf_psf/tests/test_quality_control/data/invalid/rejection_metric_not_enabled.yaml b/src/wf_psf/tests/test_quality_control/data/invalid/rejection_metric_not_enabled.yaml index 1b76b0fe..bdf0b460 100644 --- a/src/wf_psf/tests/test_quality_control/data/invalid/rejection_metric_not_enabled.yaml +++ b/src/wf_psf/tests/test_quality_control/data/invalid/rejection_metric_not_enabled.yaml @@ -8,16 +8,18 @@ resources: metrics: - mask_obscuration: + pixel_mask: enabled: false params: - aperture: gaussian - sigma: 2.5 + aperture: + type: circular + radius: 2.6 + unit: sigma rejection: - mask_obscuration: + pixel_mask: enabled: true policy: threshold: diff --git a/src/wf_psf/tests/test_quality_control/data/valid/metric_minimal.yaml b/src/wf_psf/tests/test_quality_control/data/valid/metric_minimal.yaml index 40f9887c..d7ccacaa 100644 --- a/src/wf_psf/tests/test_quality_control/data/valid/metric_minimal.yaml +++ b/src/wf_psf/tests/test_quality_control/data/valid/metric_minimal.yaml @@ -1,5 +1,5 @@ metrics: - mask_obscuration: + pixel_mask: enabled: true diff --git a/src/wf_psf/tests/test_quality_control/data/valid/quality_control.yaml b/src/wf_psf/tests/test_quality_control/data/valid/quality_control.yaml index 7a5e4636..da49a6d3 100644 --- a/src/wf_psf/tests/test_quality_control/data/valid/quality_control.yaml +++ b/src/wf_psf/tests/test_quality_control/data/valid/quality_control.yaml @@ -8,11 +8,13 @@ resources: metrics: - mask_obscuration: + pixel_mask: enabled: true params: - aperture: gaussian - sigma: 2.5 + aperture: + type: circular + radius: 2.6 + unit: sigma goodness_of_fit: enabled: true @@ -30,7 +32,7 @@ metrics: rejection: - mask_obscuration: + pixel_mask: enabled: true policy: threshold: diff --git a/src/wf_psf/tests/test_quality_control/data/valid/quality_control_multiple_rejection_policies.yaml b/src/wf_psf/tests/test_quality_control/data/valid/quality_control_multiple_rejection_policies.yaml index d94f7e76..555b0c25 100644 --- a/src/wf_psf/tests/test_quality_control/data/valid/quality_control_multiple_rejection_policies.yaml +++ b/src/wf_psf/tests/test_quality_control/data/valid/quality_control_multiple_rejection_policies.yaml @@ -8,11 +8,13 @@ resources: metrics: - mask_obscuration: + pixel_mask: enabled: true params: - aperture: gaussian - sigma: 2.5 + aperture: + type: circular + radius: 2.6 + unit: sigma goodness_of_fit: enabled: true @@ -24,7 +26,7 @@ metrics: rejection: - mask_obscuration: + pixel_mask: enabled: true policy: threshold: diff --git a/src/wf_psf/tests/test_quality_control/pipeline_test.py b/src/wf_psf/tests/test_quality_control/pipeline_test.py index d3cbdd88..1ff3bb44 100644 --- a/src/wf_psf/tests/test_quality_control/pipeline_test.py +++ b/src/wf_psf/tests/test_quality_control/pipeline_test.py @@ -5,7 +5,7 @@ from wf_psf.quality_control.pipeline import QualityControlPipeline from wf_psf.quality_control.config import QualityControlConfig -from wf_psf.quality_control.metrics.mask_obscuration import MaskObscurationMetric +from wf_psf.quality_control.metrics.pixel_masks import PixelMaskMetric from wf_psf.quality_control.metrics.goodness_of_fit import GoodnessOfFitMetric from wf_psf.quality_control.rejection.threshold import ThresholdRejectionPolicy @@ -26,7 +26,7 @@ def test_pipeline_constructor(pipeline_factory): assert isinstance(pipeline.config, QualityControlConfig) # Check metrics registry - assert pipeline.metrics_registry.get("mask_obscuration") is MaskObscurationMetric + assert pipeline.metrics_registry.get("pixel_mask") is PixelMaskMetric assert pipeline.metrics_registry.get("goodness_of_fit") is GoodnessOfFitMetric # Check rejection registry @@ -39,7 +39,7 @@ def test_pipeline_instantiate_metrics_valid(pipeline_factory): metrics = pipeline._instantiate_metrics() assert len(metrics) == 2 - assert isinstance(metrics["mask_obscuration"], MaskObscurationMetric) + assert isinstance(metrics["pixel_mask"], PixelMaskMetric) assert isinstance(metrics["goodness_of_fit"], GoodnessOfFitMetric) @@ -56,8 +56,8 @@ def test_pipeline_instantiate_rejection_policy_valid(pipeline_factory): rejection_policies = pipeline._instantiate_rejection_policies() assert len(rejection_policies) == 1 - assert isinstance(rejection_policies["mask_obscuration"], ThresholdRejectionPolicy) - assert rejection_policies["mask_obscuration"].value == 3.0 + assert isinstance(rejection_policies["pixel_mask"], ThresholdRejectionPolicy) + assert rejection_policies["pixel_mask"].value == 3.0 assert "goodness_of_fit" not in rejection_policies @@ -69,7 +69,7 @@ def test_pipeline_run_single_rejection_policy(pipeline_factory): with ( patch.object( - MaskObscurationMetric, + PixelMaskMetric, "compute", return_value=metric_result, ) as mock_mask_compute, @@ -99,7 +99,7 @@ def test_pipeline_run_single_rejection_policy(pipeline_factory): mock_apply.assert_called_once_with(metric_result) assert np.array_equal( - result.metrics["mask_obscuration"], + result.metrics["pixel_mask"], np.array([1.0, 2.0, 3.0]), ) @@ -109,7 +109,7 @@ def test_pipeline_run_single_rejection_policy(pipeline_factory): ) assert np.array_equal( - result.validity_masks["mask_obscuration"], + result.validity_masks["pixel_mask"], np.array([True, False, True]), ) @@ -132,7 +132,7 @@ def test_pipeline_run_multiple_rejection_policies(pipeline_factory): with ( patch.object( - MaskObscurationMetric, + PixelMaskMetric, "compute", return_value=metric_result, ) as mock_mask_compute, @@ -164,7 +164,7 @@ def test_pipeline_run_multiple_rejection_policies(pipeline_factory): assert mock_apply.call_count == 2 assert np.array_equal( - result.metrics["mask_obscuration"], + result.metrics["pixel_mask"], np.array([1.0, 2.0, 3.0]), ) @@ -174,7 +174,7 @@ def test_pipeline_run_multiple_rejection_policies(pipeline_factory): ) assert np.array_equal( - result.validity_masks["mask_obscuration"], + result.validity_masks["pixel_mask"], np.array([True, True, False]), ) diff --git a/src/wf_psf/tests/test_quality_control/resources_test.py b/src/wf_psf/tests/test_quality_control/resources_test.py index f0869e7f..3cb13425 100644 --- a/src/wf_psf/tests/test_quality_control/resources_test.py +++ b/src/wf_psf/tests/test_quality_control/resources_test.py @@ -73,7 +73,7 @@ def second_preparer(config): def test_get_required(qc_config_factory): config = qc_config_factory( metrics={ - "mask_obscuration": QualityMetricConfig( + "pixel_mask": QualityMetricConfig( enabled=True, required_resources=[], ), diff --git a/src/wf_psf/tests/test_quality_control/test_qc_metrics/qc_metrics_registry_test.py b/src/wf_psf/tests/test_quality_control/test_qc_metrics/qc_metrics_registry_test.py index b4bfdfea..a99159c5 100644 --- a/src/wf_psf/tests/test_quality_control/test_qc_metrics/qc_metrics_registry_test.py +++ b/src/wf_psf/tests/test_quality_control/test_qc_metrics/qc_metrics_registry_test.py @@ -12,7 +12,7 @@ MetricsRegistry, build_metrics_registry, ) -from wf_psf.quality_control.metrics.mask_obscuration import MaskObscurationMetric +from wf_psf.quality_control.metrics.pixel_masks import PixelMaskMetric from wf_psf.quality_control.metrics.goodness_of_fit import GoodnessOfFitMetric @@ -29,16 +29,16 @@ def compute(self, dataset) -> None: def test_duplicate_metric_registration_raises(): registry = MetricsRegistry() - registry.register_metric(MaskObscurationMetric) + registry.register_metric(PixelMaskMetric) with pytest.raises(ValueError): - registry.register_metric(MaskObscurationMetric) + registry.register_metric(PixelMaskMetric) def test_build_metrics_registry(): registry = build_metrics_registry() - assert registry.get("mask_obscuration") is MaskObscurationMetric + assert registry.get("pixel_mask") is PixelMaskMetric assert registry.get("goodness_of_fit") is GoodnessOfFitMetric From 6139271e7b05fa062335b17bd73d0281159691d0 Mon Sep 17 00:00:00 2001 From: Jennifer Pollack Date: Wed, 16 Sep 2026 17:29:46 +0200 Subject: [PATCH 2/2] Change loop variable name to rejection_policy - Replace metric_rejection_policy with rejection_policy for clarity - Clean up formatting and spacing in error messages --- src/wf_psf/quality_control/config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wf_psf/quality_control/config.py b/src/wf_psf/quality_control/config.py index 3924211b..32943780 100644 --- a/src/wf_psf/quality_control/config.py +++ b/src/wf_psf/quality_control/config.py @@ -390,18 +390,19 @@ def validate_rejection_policy_metrics(config: QualityControlConfig) -> None: If an enabled rejection policy references an unknown or disabled quality metric. """ - for metric_name, metric_rejection_policy in config.rejection.items(): - if not metric_rejection_policy.enabled: + for metric_name, rejection_policy in config.rejection.items(): + if not rejection_policy.enabled: continue if metric_name not in config.metrics: raise ValueError( - f"Rejection policy configured for unknown metric '{metric_name}' " + f"Rejection policy configured for unknown metric '{metric_name}'." ) if not config.metrics[metric_name].enabled: raise ValueError( - f"Rejection policy cannot be enabled because metric '{metric_name}' is disabled." + f"Rejection policy cannot be enabled because metric " + f"'{metric_name}' is disabled." )