Skip to content

Commit

Permalink
Rework of the validation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
dokempf committed Feb 28, 2025
1 parent c7351ef commit ef8df3a
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 365 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- macos-latest
- windows-latest
python:
- "3.9"
- "3.10"
- "3.13"

defaults:
Expand Down
10 changes: 3 additions & 7 deletions python/helios/leg.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
from helios.platform import PlatformSettingsBase, PlatformSettings
from helios.scanner import ScannerSettings, ScannerSettingsBase
from helios.validation import Model, Property
from helios.validation import Model

import _helios


class Leg(Model, cpp_class=_helios.Leg):
platform_settings: PlatformSettingsBase = Property(
cpp="platform_settings", wraptype=PlatformSettings, default=PlatformSettings()
)
scanner_settings: ScannerSettingsBase = Property(
cpp="scanner_settings", wraptype=ScannerSettings, default=ScannerSettings()
)
platform_settings: PlatformSettingsBase = PlatformSettings()
scanner_settings: ScannerSettingsBase = ScannerSettings()
15 changes: 7 additions & 8 deletions python/helios/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from helios.validation import (
AssetPath,
Model,
Property,
UpdateableMixin,
validate_xml_file,
)
Expand All @@ -16,15 +15,15 @@ class PlatformSettingsBase(Model, UpdateableMixin, cpp_class=_helios.PlatformSet


class PlatformSettings(PlatformSettingsBase):
x: float = Property(cpp="x", default=0)
y: float = Property(cpp="y", default=0)
z: float = Property(cpp="z", default=0)
x: float = 0
y: float = 0
z: float = 0


class StaticPlatformSettings(PlatformSettingsBase):
x: float = Property(cpp="x", default=0)
y: float = Property(cpp="y", default=0)
z: float = Property(cpp="z", default=0)
x: float = 0
y: float = 0
z: float = 0


class Platform(Model, cpp_class=_helios.Platform):
Expand All @@ -38,7 +37,7 @@ def from_xml(cls, platform_file: AssetPath, platform_id: str = ""):
_cpp_platform = _helios.read_platform_from_xml(
str(platform_file), [str(p) for p in get_asset_directories()], platform_id
)
return cls.__new__(cls, _cpp_object=_cpp_platform)
return cls._from_cpp(_cpp_platform)


#
Expand Down
39 changes: 18 additions & 21 deletions python/helios/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from helios.validation import (
AssetPath,
Model,
Property,
UpdateableMixin,
validate_xml_file,
)
Expand All @@ -17,29 +16,27 @@ class ScannerSettingsBase(Model, UpdateableMixin, cpp_class=_helios.ScannerSetti


class ScannerSettings(ScannerSettingsBase):
is_active: bool = Property(cpp="is_active", default=True)
head_rotation: float = Property(cpp="head_rotation", default=0)
rotation_start_angle: float = Property(cpp="rotation_start_angle", default=0)
rotation_stop_angle: float = Property(cpp="rotation_stop_angle", default=0)
pulse_frequency: int = Property(cpp="pulse_frequency", default=300000)
scan_angle: float = Property(cpp="scan_angle", default=0.349066)
min_vertical_angle: float = Property(cpp="min_vertical_angle", default=np.nan)
max_vertical_angle: float = Property(cpp="max_vertical_angle", default=np.nan)
scan_frequency: float = Property(cpp="scan_frequency", default=200)
beam_divergence_angle: float = Property(cpp="beam_divergence_angle", default=0.0003)
trajectory_time_interval: float = Property(
cpp="trajectory_time_interval", default=0.01
)
vertical_resolution: float = Property(cpp="vertical_resolution", default=0)
horizontal_resolution: float = Property(cpp="horizontal_resolution", default=0)
is_active: bool = True
head_rotation: float = 0
rotation_start_angle: float = 0
rotation_stop_angle: float = 0
pulse_frequency: int = 300000
scan_angle: float = 0.349066
min_vertical_angle: float = np.nan
max_vertical_angle: float = np.nan
scan_frequency: float = 200
beam_divergence_angle: float = 0.0003
trajectory_time_interval: float = 0.01
vertical_resolution: float = 0
horizontal_resolution: float = 0


# TODO: Requires expert input
class RotatingOpticsScannerSettings(ScannerSettingsBase):
is_active: bool = Property(cpp="is_active", default=True)
head_rotation: float = Property(cpp="head_rotation", default=0)
rotation_start_angle: float = Property(cpp="rotation_start_angle", default=0)
rotation_stop_angle: float = Property(cpp="rotation_stop_angle", default=0)
is_active: bool = True
head_rotation: float = 0
rotation_start_angle: float = 0
rotation_stop_angle: float = 0


# TODO: Requires expert input
Expand Down Expand Up @@ -73,7 +70,7 @@ def from_xml(cls, scanner_file: AssetPath, scanner_id: str = ""):
_cpp_scanner = _helios.read_scanner_from_xml(
str(scanner_file), [str(p) for p in get_asset_directories()], scanner_id
)
return cls.__new__(cls, _cpp_object=_cpp_scanner)
return cls._from_cpp(_cpp_scanner)


#
Expand Down
22 changes: 10 additions & 12 deletions python/helios/scene.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from helios.settings import ExecutionSettings, compose_execution_settings
from helios.util import get_asset_directories
from helios.validation import AssetPath, Model, Property, validate_xml_file
from helios.validation import AssetPath, Model, validate_xml_file

from numpydantic import NDArray, Shape
from pydantic import PositiveFloat, validate_call
Expand Down Expand Up @@ -92,7 +92,7 @@ def from_xml(cls, scene_part_file: AssetPath, id: int):
_cpp_scene_part = _helios.read_scene_part_from_xml(
str(scene_part_file), [str(p) for p in get_asset_directories()], id
)
return cls.__new__(cls, _cpp_object=_cpp_scene_part)
return cls._from_cpp(_cpp_scene_part)

@classmethod
@validate_call
Expand All @@ -103,17 +103,11 @@ def from_obj(cls, obj_file: AssetPath, up_axis: Literal["y", "z"] = "z"):
str(obj_file), [str(p) for p in get_asset_directories()], up_axis
)

return cls.__new__(cls, _cpp_object=_cpp_part)
return cls._from_cpp(_cpp_part)


class StaticScene(Model, cpp_class=_helios.StaticScene):
scene_parts: list[ScenePart] = Property(
cpp="scene_parts",
wraptype=ScenePart,
iterable=True,
default=[],
unique_across_instances=True,
)
scene_parts: list[ScenePart] = []

def _finalize(
self, execution_settings: Optional[ExecutionSettings] = None, **parameters
Expand All @@ -139,7 +133,11 @@ def _set_reflectances(self, wavelength: float):
self._cpp_object, [str(p) for p in get_asset_directories()], wavelength
)

def _update_hook(self):
def _pre_set(self, field, value):
if field == "scene_parts":
self._enforce_uniqueness_across_instances(field, value)

def _post_set(self, field):
# When the Scene changes, we want to invalidate the KDTree etc.

_helios.invalidate_static_scene(self._cpp_object)
Expand All @@ -154,4 +152,4 @@ def from_xml(cls, scene_file: AssetPath):
_cpp_scene = _helios.read_scene_from_xml(
str(scene_file), [str(p) for p in get_asset_directories()], True, True
)
return cls.__new__(cls, _cpp_object=_cpp_scene)
return cls._from_cpp(_cpp_scene)
39 changes: 17 additions & 22 deletions python/helios/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from helios.validation import (
CreatedDirectory,
Model,
Property,
ThreadCount,
UpdateableMixin,
)
Expand Down Expand Up @@ -53,30 +52,26 @@ class OutputFormat(StrEnum):


class ExecutionSettings(Model, UpdateableMixin):
parallelization: ParallelizationStrategy = Property(
default=ParallelizationStrategy.CHUNK
)
num_threads: ThreadCount = Property(default=None)
chunk_size: PositiveInt = Property(default=32)
warehouse_factor: PositiveInt = Property(default=4)
log_file: Optional[Path] = Property(default="helios.log")
log_file_only: bool = Property(default=False)
verbosity: LogVerbosity = Property(default=LogVerbosity.DEFAULT)
factory_type: KDTreeFactoryType = Property(
default=KDTreeFactoryType.SAH_APPROXIMATION
)
kdt_num_threads: ThreadCount = Property(default=None)
kdt_geom_num_threads: ThreadCount = Property(default=None)
sah_nodes: PositiveInt = Property(default=32)
parallelization: ParallelizationStrategy = ParallelizationStrategy.CHUNK
num_threads: ThreadCount = None
chunk_size: PositiveInt = 32
warehouse_factor: PositiveInt = 4
log_file: Optional[Path] = "helios.log"
log_file_only: bool = False
verbosity: LogVerbosity = LogVerbosity.DEFAULT
factory_type: KDTreeFactoryType = KDTreeFactoryType.SAH_APPROXIMATION
kdt_num_threads: ThreadCount = None
kdt_geom_num_threads: ThreadCount = None
sah_nodes: PositiveInt = 32


class OutputSettings(Model, UpdateableMixin):
format: OutputFormat = Property(default=OutputFormat.NPY)
split_by_channel: bool = Property(default=False)
output_dir: CreatedDirectory = Property(default="output")
write_waveform: bool = Property(default=False)
write_pulse: bool = Property(default=False)
las_scale: PositiveFloat = Property(default=0.0001)
format: OutputFormat = OutputFormat.NPY
split_by_channel: bool = False
output_dir: CreatedDirectory = "output"
write_waveform: bool = False
write_pulse: bool = False
las_scale: PositiveFloat = 0.0001


# Storage for global settings
Expand Down
23 changes: 13 additions & 10 deletions python/helios/survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
compose_output_settings,
)
from helios.util import get_asset_directories, meas_dtype, traj_dtype
from helios.validation import AssetPath, Model, Property, validate_xml_file
from helios.validation import AssetPath, Model, validate_xml_file

from datetime import datetime, timezone
from pathlib import Path
Expand All @@ -24,14 +24,12 @@


class Survey(Model, cpp_class=_helios.Survey):
scanner: Scanner = Property(
cpp="scanner", wraptype=Scanner, unique_across_instances=True
)
platform: Platform = Property(cpp="platform", wraptype=Platform)
scene: StaticScene = Property(cpp="scene", wraptype=StaticScene)
legs: list[Leg] = Property(cpp="legs", wraptype=Leg, iterable=True, default=[])
name: str = Property(cpp="name", default="")
gps_time: datetime = Property(default=datetime.now(timezone.utc))
scanner: Scanner
platform: Platform
scene: StaticScene
legs: list[Leg] = []
name: str = ""
gps_time: datetime = datetime.now(timezone.utc)

@validate_call
def run(
Expand Down Expand Up @@ -216,4 +214,9 @@ def from_xml(cls, survey_file: AssetPath):
_cpp_survey = _helios.read_survey_from_xml(
str(survey_file), [str(p) for p in get_asset_directories()], True, True
)
return cls.__new__(cls, _cpp_object=_cpp_survey)

return cls._from_cpp(_cpp_survey)

def _pre_set(self, field, value):
if field == "scanner":
self._enforce_uniqueness_across_instances(field, value)
13 changes: 6 additions & 7 deletions python/helios/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def set_rng_seed(seed: Union[int, datetime] = datetime.now(timezone.utc)) -> Non
_helios.default_rand_generator_seed(str(seed))


def is_real_iterable(value):
return isinstance(value, Iterable) and not isinstance(value, str)


@validate_call
def combine_parameters(groups: Union[None, list[list[str]]] = None, **parameters):
"""Combine parameter spaces for parameter studies.
Expand Down Expand Up @@ -138,20 +142,15 @@ def combine_parameters(groups: Union[None, list[list[str]]] = None, **parameters
iterables within a group varies.
"""

def _is_real_iterable(value):
return isinstance(value, Iterable) and not isinstance(value, str)

# Define default for groups: Each parameter that was passed an iterable
# of possible values forms its own group.
if groups is None:
groups = [
[key] for key, value in parameters.items() if _is_real_iterable(value)
]
groups = [[key] for key, value in parameters.items() if is_real_iterable(value)]

# Ensure that all parameters within a group are lists of the same length
for group in groups:
for key in group:
if not _is_real_iterable(parameters[key]):
if not is_real_iterable(parameters[key]):
raise ValueError("All parameters within a group must be iterable.")
if len(set(len(parameters[key]) for key in group)) > 1:
raise ValueError("All parameters within a group must have the same length.")
Expand Down
Loading

0 comments on commit ef8df3a

Please sign in to comment.