Miroslav Simko <ms@iolabs.ch> 2026-09-02T08:59:21+02:00
Commit #54 ยท 5 snippets
README.md | 3 ++- src/iolabs_point_cloud_surface_mesh/_config.py | 6 ++++++ tests/test_surface_mesh_config.py | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-)
| 57 | file_naming: SurfaceMeshFileNamingConfig = SurfaceMeshFileNamingConfig() | 57 | file_naming: SurfaceMeshFileNamingConfig = SurfaceMeshFileNamingConfig() |
| 58 | surface_mesh_parameters: SurfaceMeshParametersConfig = SurfaceMeshParametersConfig() | 58 | surface_mesh_parameters: SurfaceMeshParametersConfig = SurfaceMeshParametersConfig() |
| 59 | npz_blacklist_by_segment: dict[int, list[str]] = pydantic.Field(default_factory=dict) | 59 | npz_blacklist_by_segment: dict[int, list[str]] = pydantic.Field(default_factory=dict) |
| 60 | 60 | ||
| 61 | @pydantic.field_validator("file_naming", "surface_mesh_parameters", mode="before") | ||
| 62 | @classmethod | ||
| 63 | def _none_section_is_default(cls, value: object) -> object: | ||
| 64 | """Treat an explicit ``null`` section as "use the packaged defaults".""" | ||
| 65 | return {} if value is None else value | ||
| 66 | |||
| 61 | @pydantic.field_validator("npz_blacklist_by_segment", mode="before") | 67 | @pydantic.field_validator("npz_blacklist_by_segment", mode="before") |
| 62 | @classmethod | 68 | @classmethod |
| 63 | def _normalize_blacklist(cls, value: object) -> dict[int, list[str]]: | 69 | def _normalize_blacklist(cls, value: object) -> dict[int, list[str]]: |
| 64 | """Accept ``segment_<idx>`` keys and string-or-list pattern values.""" | 70 | """Accept ``segment_<idx>`` keys and string-or-list pattern values.""" |
| 116 | ) | 116 | ) |
| 117 | def test_normalize_rejects_invalid_parameter_values(key, value): | 117 | def test_normalize_rejects_invalid_parameter_values(key, value): |
| 118 | with pytest.raises(SurfaceMeshConfigError, match=key): | 118 | with pytest.raises(SurfaceMeshConfigError, match=key): |
| 119 | normalize_surface_mesh_config({"surface_mesh_parameters": {key: value}}) | 119 | normalize_surface_mesh_config({"surface_mesh_parameters": {key: value}}) |
| 120 | |||
| 121 | |||
| 122 | def test_normalize_treats_null_sections_as_defaults(): | ||
| 123 | config = normalize_surface_mesh_config( | ||
| 124 | {"file_naming": None, "surface_mesh_parameters": None, "npz_blacklist_by_segment": None} | ||
| 125 | ) | ||
| 126 | assert config == load_surface_mesh_config() |
| 31 | ## Configuration | 31 | ## Configuration |
| 32 | 32 | ||
| 33 | Packaged defaults in `surface_mesh.default.json` are validated by the pydantic | 33 | Packaged defaults in `surface_mesh.default.json` are validated by the pydantic |
| 34 | `SurfaceMeshConfig` tree; unknown keys raise `SurfaceMeshConfigError`. To add a | 34 | `SurfaceMeshConfig` tree; unknown keys raise `SurfaceMeshConfigError`. To add a |
| 35 | key, add the field to the model and the JSON default. Key tunables: | 35 | key, add the field to the model, the JSON default, and (for tunables) |
| 36 | `params.SurfaceMeshParameters`. Key tunables: | ||
| 36 | `cells_across` (5), `row_spacing_m` (3.0), `device` ("CUDA:0"), | 37 | `cells_across` (5), `row_spacing_m` (3.0), `device` ("CUDA:0"), |
| 37 | `min_points_per_cell`, `z_trim_mad_factor`, `max_extrapolation_m`, | 38 | `min_points_per_cell`, `z_trim_mad_factor`, `max_extrapolation_m`, |
| 38 | `min_measured_edge_fraction`, `edge_smoothing_window_m`. File naming lives | 39 | `min_measured_edge_fraction`, `edge_smoothing_window_m`. File naming lives |
| 39 | under `file_naming`; per-segment NPZ exclusion under | 40 | under `file_naming`; per-segment NPZ exclusion under |
| 57 | file_naming: SurfaceMeshFileNamingConfig = SurfaceMeshFileNamingConfig() | 57 | file_naming: SurfaceMeshFileNamingConfig = SurfaceMeshFileNamingConfig() |
| 58 | surface_mesh_parameters: SurfaceMeshParametersConfig = SurfaceMeshParametersConfig() | 58 | surface_mesh_parameters: SurfaceMeshParametersConfig = SurfaceMeshParametersConfig() |
| 59 | npz_blacklist_by_segment: dict[int, list[str]] = pydantic.Field(default_factory=dict) | 59 | npz_blacklist_by_segment: dict[int, list[str]] = pydantic.Field(default_factory=dict) |
| 60 | 60 | ||
| 61 | @pydantic.field_validator("file_naming", "surface_mesh_parameters", mode="before") | ||
| 62 | @classmethod | ||
| 63 | def _none_section_is_default(cls, value: object) -> object: | ||
| 64 | """Treat an explicit ``null`` section as "use the packaged defaults".""" | ||
| 65 | return {} if value is None else value | ||
| 66 | |||
| 61 | @pydantic.field_validator("npz_blacklist_by_segment", mode="before") | 67 | @pydantic.field_validator("npz_blacklist_by_segment", mode="before") |
| 62 | @classmethod | 68 | @classmethod |
| 63 | def _normalize_blacklist(cls, value: object) -> dict[int, list[str]]: | 69 | def _normalize_blacklist(cls, value: object) -> dict[int, list[str]]: |
| 64 | """Accept ``segment_<idx>`` keys and string-or-list pattern values.""" | 70 | """Accept ``segment_<idx>`` keys and string-or-list pattern values.""" |
| 116 | ) | 116 | ) |
| 117 | def test_normalize_rejects_invalid_parameter_values(key, value): | 117 | def test_normalize_rejects_invalid_parameter_values(key, value): |
| 118 | with pytest.raises(SurfaceMeshConfigError, match=key): | 118 | with pytest.raises(SurfaceMeshConfigError, match=key): |
| 119 | normalize_surface_mesh_config({"surface_mesh_parameters": {key: value}}) | 119 | normalize_surface_mesh_config({"surface_mesh_parameters": {key: value}}) |
| 120 | |||
| 121 | |||
| 122 | def test_normalize_treats_null_sections_as_defaults(): | ||
| 123 | config = normalize_surface_mesh_config( | ||
| 124 | {"file_naming": None, "surface_mesh_parameters": None, "npz_blacklist_by_segment": None} | ||
| 125 | ) | ||
| 126 | assert config == load_surface_mesh_config() |
nullconfig sections fall back to section defaults (pre-migration behaviour), re-validation instead ofmodel_copy(update=), doc/test parity with the packaged JSON.