PyHelios 0.1.11
Loading...
Searching...
No Matches
plugin_decorators.py
Go to the documentation of this file.
1"""
2Validation decorators for PyHelios plugin methods.
3
4This module provides validation decorators that ensure parameter validation
5for all plugin methods, maintaining consistency across the PyHelios API.
6"""
7
8from functools import wraps
9from typing import Any, Callable, List, Optional, Union
10from .plugins import (
11 validate_band_label, validate_source_id, validate_source_id_list,
12 validate_flux_value, validate_ray_count, validate_direction_vector,
13 validate_angle_degrees, validate_scaling_factor, validate_uuid_list,
14 validate_tree_id, validate_segment_resolution, validate_filename,
15 validate_recursion_level, validate_subdivision_count, validate_time_value,
16 # Photosynthesis validation functions
17 validate_species_name, validate_temperature, validate_co2_concentration,
18 validate_photosynthetic_rate, validate_conductance, validate_par_flux,
19 validate_empirical_coefficients, validate_farquhar_coefficients,
20 validate_vcmax, validate_jmax, validate_quantum_efficiency, validate_dark_respiration,
21 validate_oxygen_concentration, validate_temperature_response_params,
22 # Camera validation functions
23 validate_camera_label, validate_band_labels_list, validate_antialiasing_samples
24)
25from .datatypes import validate_vec3, validate_rgb_color
26from .core import validate_positive_value, validate_non_negative_value
27
28
29# RadiationModel decorators
30def validate_radiation_band_params(func: Callable) -> Callable:
31 """Validate parameters for radiation band operations."""
32 @wraps(func)
33 def wrapper(self, old_label: str, new_label: str, *args, **kwargs):
34 validate_band_label(old_label, "old_label", func.__name__)
35 validate_band_label(new_label, "new_label", func.__name__)
36 return func(self, old_label, new_label, *args, **kwargs)
37 return wrapper
38
39
40def validate_collimated_source_params(func: Callable) -> Callable:
41 """Validate parameters for collimated radiation source creation."""
42 @wraps(func)
43 def wrapper(self, direction=None, *args, **kwargs):
44 if direction is not None:
45 # RadiationModel handles multiple types (vec3, SphericalCoord, tuple)
46 # Let the method handle type conversion, but validate if it's vec3-like
47 if hasattr(direction, 'x') and hasattr(direction, 'y') and hasattr(direction, 'z'):
48 validate_direction_vector(direction, "direction", func.__name__)
49 return func(self, direction, *args, **kwargs)
50 return wrapper
51
52
53def validate_sphere_source_params(func: Callable) -> Callable:
54 """Validate parameters for sphere radiation source creation."""
55 @wraps(func)
56 def wrapper(self, position, radius: float, *args, **kwargs):
57 validate_vec3(position, "position", func.__name__)
58 validate_positive_value(radius, "radius", func.__name__)
59 return func(self, position, radius, *args, **kwargs)
60 return wrapper
61
62
63def validate_sun_sphere_params(func: Callable) -> Callable:
64 """Validate parameters for sun sphere radiation source."""
65 @wraps(func)
66 def wrapper(self, radius: float, zenith: float, azimuth: float,
67 position_scaling: float = 1.0, angular_width: float = 0.53,
68 flux_scaling: float = 1.0, *args, **kwargs):
69 validate_positive_value(radius, "radius", func.__name__)
70 validate_angle_degrees(zenith, "zenith", func.__name__)
71 validate_angle_degrees(azimuth, "azimuth", func.__name__)
72 validate_scaling_factor(position_scaling, param_name="position_scaling", function_name=func.__name__)
73 validate_positive_value(angular_width, "angular_width", func.__name__)
74 validate_scaling_factor(flux_scaling, param_name="flux_scaling", function_name=func.__name__)
75 return func(self, radius, zenith, azimuth, position_scaling, angular_width, flux_scaling, *args, **kwargs)
76 return wrapper
77
78
79def validate_source_flux_multiple_params(func: Callable) -> Callable:
80 """Validate parameters for setting multiple source flux."""
81 @wraps(func)
82 def wrapper(self, source_ids: List[int], label: str, flux: float, *args, **kwargs):
83 validate_source_id_list(source_ids, "source_ids", func.__name__)
84 validate_band_label(label, "label", func.__name__)
85 validate_flux_value(flux, "flux", func.__name__)
86 return func(self, source_ids, label, flux, *args, **kwargs)
87 return wrapper
88
89
90def validate_get_source_flux_params(func: Callable) -> Callable:
91 """Validate parameters for getting source flux."""
92 @wraps(func)
93 def wrapper(self, source_id: int, label: str, *args, **kwargs):
94 validate_source_id(source_id, "source_id", func.__name__)
95 validate_band_label(label, "label", func.__name__)
96 return func(self, source_id, label, *args, **kwargs)
97 return wrapper
98
99
100def validate_update_geometry_params(func: Callable) -> Callable:
101 """Validate parameters for geometry updates."""
102 @wraps(func)
103 def wrapper(self, uuids: Optional[List[int]] = None, *args, **kwargs):
104 if uuids is not None:
105 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=True)
106 return func(self, uuids, *args, **kwargs)
107 return wrapper
108
109
110def validate_run_band_params(func: Callable) -> Callable:
111 """Validate parameters for running radiation bands."""
112 @wraps(func)
113 def wrapper(self, band_label: Union[str, List[str]], *args, **kwargs):
114 if isinstance(band_label, str):
115 validate_band_label(band_label, "band_label", func.__name__)
116 elif isinstance(band_label, list):
117 if not band_label:
118 from .exceptions import create_validation_error
119 raise create_validation_error(
120 f"Parameter cannot be empty",
121 param_name="band_label",
122 function_name=func.__name__,
123 expected_type="non-empty list",
124 actual_value=band_label,
125 suggestion="Provide at least one band label."
126 )
127 for i, label in enumerate(band_label):
128 validate_band_label(label, f"band_label[{i}]", func.__name__)
129 else:
130 from .exceptions import create_validation_error
131 raise create_validation_error(
132 f"Parameter must be a string or list of strings, got {type(band_label).__name__}",
133 param_name="band_label",
134 function_name=func.__name__,
135 expected_type="string or list of strings",
136 actual_value=band_label,
137 suggestion="Provide a band label string or list of band labels."
138 )
139 return func(self, band_label, *args, **kwargs)
140 return wrapper
141
142
143def validate_scattering_depth_params(func: Callable) -> Callable:
144 """Validate parameters for scattering depth."""
145 @wraps(func)
146 def wrapper(self, label: str, depth: int, *args, **kwargs):
147 validate_band_label(label, "label", func.__name__)
148 if not isinstance(depth, int):
149 from .exceptions import create_validation_error
150 raise create_validation_error(
151 f"Parameter must be an integer, got {type(depth).__name__}",
152 param_name="depth",
153 function_name=func.__name__,
154 expected_type="integer",
155 actual_value=depth,
156 suggestion="Scattering depth must be an integer."
157 )
158 validate_non_negative_value(depth, "depth", func.__name__)
159 return func(self, label, depth, *args, **kwargs)
160 return wrapper
161
162
163def validate_min_scatter_energy_params(func: Callable) -> Callable:
164 """Validate parameters for minimum scatter energy."""
165 @wraps(func)
166 def wrapper(self, label: str, energy: float, *args, **kwargs):
167 validate_band_label(label, "label", func.__name__)
168 validate_positive_value(energy, "energy", func.__name__)
169 return func(self, label, energy, *args, **kwargs)
170 return wrapper
171
172
173# WeberPennTree decorators
174def validate_tree_uuid_params(func: Callable) -> Callable:
175 """Validate tree ID parameters for WeberPennTree methods."""
176 @wraps(func)
177 def wrapper(self, tree_id: int, *args, **kwargs):
178 validate_tree_id(tree_id, "tree_id", func.__name__)
179 return func(self, tree_id, *args, **kwargs)
180 return wrapper
181
182
183def validate_recursion_params(func: Callable) -> Callable:
184 """Validate recursion level parameters."""
185 @wraps(func)
186 def wrapper(self, level: int, *args, **kwargs):
187 validate_recursion_level(level, "level", func.__name__)
188 return func(self, level, *args, **kwargs)
189 return wrapper
190
191
192def validate_trunk_segment_params(func: Callable) -> Callable:
193 """Validate trunk segment resolution parameters."""
194 @wraps(func)
195 def wrapper(self, trunk_segs: int, *args, **kwargs):
196 validate_segment_resolution(trunk_segs, min_val=3, max_val=100, param_name="trunk_segs", function_name=func.__name__)
197 return func(self, trunk_segs, *args, **kwargs)
198 return wrapper
199
200
201def validate_branch_segment_params(func: Callable) -> Callable:
202 """Validate branch segment resolution parameters."""
203 @wraps(func)
204 def wrapper(self, branch_segs: int, *args, **kwargs):
205 validate_segment_resolution(branch_segs, min_val=3, max_val=100, param_name="branch_segs", function_name=func.__name__)
206 return func(self, branch_segs, *args, **kwargs)
207 return wrapper
208
209
210def validate_leaf_subdivisions_params(func: Callable) -> Callable:
211 """Validate leaf subdivision parameters."""
212 @wraps(func)
213 def wrapper(self, leaf_segs_x: int, leaf_segs_y: int, *args, **kwargs):
214 validate_subdivision_count(leaf_segs_x, "leaf_segs_x", func.__name__)
215 validate_subdivision_count(leaf_segs_y, "leaf_segs_y", func.__name__)
216 return func(self, leaf_segs_x, leaf_segs_y, *args, **kwargs)
217 return wrapper
218
219
220def validate_xml_file_params(func: Callable) -> Callable:
221 """Validate XML file path parameters for WeberPennTree."""
222 @wraps(func)
223 def wrapper(self, filename: str, silent: bool = False, *args, **kwargs):
224 from pathlib import Path
225 from .exceptions import create_validation_error
226
227 # Validate filename parameter
228 validate_filename(filename, "filename", func.__name__, allowed_extensions=['.xml'])
229
230 # Convert to Path for existence checking
231 xml_path = Path(filename)
232
233 # Check if file exists
234 if not xml_path.exists():
235 raise create_validation_error(
236 f"XML file not found: {filename}",
237 param_name="filename",
238 function_name=func.__name__,
239 expected_type="path to existing .xml file",
240 actual_value=filename,
241 suggestion=f"Ensure the file exists at: {xml_path.resolve()}"
242 )
243
244 # Validate silent parameter
245 if not isinstance(silent, bool):
246 raise create_validation_error(
247 f"Parameter must be a boolean, got {type(silent).__name__}",
248 param_name="silent",
249 function_name=func.__name__,
250 expected_type="bool",
251 actual_value=silent,
252 suggestion="Use True or False for silent parameter."
253 )
254
255 return func(self, filename, silent, *args, **kwargs)
256 return wrapper
257
258
259# EnergyBalance decorators
260def validate_energy_run_params(func: Callable) -> Callable:
261 """Validate parameters for energy balance run method."""
262 @wraps(func)
263 def wrapper(self, uuids: Optional[List[int]] = None, dt: Optional[float] = None, *args, **kwargs):
264 if uuids is not None:
265 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=False)
266 if dt is not None:
267 validate_positive_value(dt, "dt", func.__name__)
268 return func(self, uuids, dt, *args, **kwargs)
269 return wrapper
270
271
272def validate_energy_band_params(func: Callable) -> Callable:
273 """Validate parameters for energy balance band operations."""
274 @wraps(func)
275 def wrapper(self, band: Union[str, List[str]], *args, **kwargs):
276 if isinstance(band, str):
277 if not band.strip():
278 from .exceptions import create_validation_error
279 raise create_validation_error(
280 f"Parameter must be a non-empty string",
281 param_name="band",
282 function_name=func.__name__,
283 expected_type="non-empty string",
284 actual_value=repr(band),
285 suggestion="Provide a non-empty band name."
286 )
287 elif isinstance(band, list):
288 if not band:
289 from .exceptions import create_validation_error
290 raise create_validation_error(
291 f"Parameter cannot be empty",
292 param_name="band",
293 function_name=func.__name__,
294 expected_type="non-empty list",
295 actual_value=band,
296 suggestion="Provide at least one band name."
297 )
298 for i, b in enumerate(band):
299 if not isinstance(b, str) or not b.strip():
300 from .exceptions import create_validation_error
301 raise create_validation_error(
302 f"All band names must be non-empty strings, got {type(b).__name__} at index {i}",
303 param_name=f"band[{i}]",
304 function_name=func.__name__,
305 expected_type="non-empty string",
306 actual_value=b,
307 suggestion="All band names must be non-empty strings."
308 )
309 else:
310 from .exceptions import create_validation_error
311 raise create_validation_error(
312 f"Parameter must be a string or list of strings, got {type(band).__name__}",
313 param_name="band",
314 function_name=func.__name__,
315 expected_type="string or list of strings",
316 actual_value=band,
317 suggestion="Provide a band name string or list of band names."
318 )
319 return func(self, band, *args, **kwargs)
320 return wrapper
321
322
323def validate_air_energy_params(func: Callable) -> Callable:
324 """Validate parameters for air energy balance."""
325 @wraps(func)
326 def wrapper(self, canopy_height_m: Optional[float] = None, reference_height_m: Optional[float] = None, *args, **kwargs):
327 if canopy_height_m is not None:
328 validate_positive_value(canopy_height_m, "canopy_height_m", func.__name__)
329 if reference_height_m is not None:
330 validate_positive_value(reference_height_m, "reference_height_m", func.__name__)
331
332 # Check that both parameters are provided together or neither
333 if (canopy_height_m is None) != (reference_height_m is None):
334 from .exceptions import create_validation_error
335 raise create_validation_error(
336 f"Canopy height and reference height must be provided together or omitted together",
337 param_name="canopy_height_m, reference_height_m",
338 function_name=func.__name__,
339 expected_type="both parameters provided or both None",
340 actual_value=f"canopy_height_m={canopy_height_m}, reference_height_m={reference_height_m}",
341 suggestion="Provide both canopy_height_m and reference_height_m, or omit both for automatic detection."
342 )
343
344 return func(self, canopy_height_m, reference_height_m, *args, **kwargs)
345 return wrapper
346
347
348def validate_evaluate_air_energy_params(func: Callable) -> Callable:
349 """Validate parameters for evaluating air energy balance."""
350 @wraps(func)
351 def wrapper(self, dt_sec: float, time_advance_sec: float, UUIDs: Optional[List[int]] = None, *args, **kwargs):
352 validate_time_value(dt_sec, "dt_sec", func.__name__)
353 validate_time_value(time_advance_sec, "time_advance_sec", func.__name__)
354
355 if time_advance_sec < dt_sec:
356 from .exceptions import create_validation_error
357 raise create_validation_error(
358 f"Time advance ({time_advance_sec}) must be greater than or equal to time step ({dt_sec})",
359 param_name="time_advance_sec",
360 function_name=func.__name__,
361 expected_type=f"number >= {dt_sec}",
362 actual_value=time_advance_sec,
363 suggestion=f"Use time_advance_sec >= {dt_sec}."
364 )
365
366 if UUIDs is not None:
367 validate_uuid_list(UUIDs, "UUIDs", func.__name__, allow_empty=False)
368
369 return func(self, dt_sec, time_advance_sec, UUIDs, *args, **kwargs)
370 return wrapper
371
372
373def validate_output_data_params(func: Callable) -> Callable:
374 """Validate parameters for optional output data."""
375 @wraps(func)
376 def wrapper(self, label: str, *args, **kwargs):
377 if not isinstance(label, str):
378 from .exceptions import create_validation_error
379 raise create_validation_error(
380 f"Parameter must be a string, got {type(label).__name__}",
381 param_name="label",
382 function_name=func.__name__,
383 expected_type="string",
384 actual_value=label,
385 suggestion="Provide a string label for the output data."
386 )
387 if not label.strip():
388 from .exceptions import create_validation_error
389 raise create_validation_error(
390 f"Parameter cannot be empty or whitespace-only",
391 param_name="label",
392 function_name=func.__name__,
393 expected_type="non-empty string",
394 actual_value=repr(label),
395 suggestion="Provide a non-empty label for the output data."
396 )
397 return func(self, label, *args, **kwargs)
398 return wrapper
399
400
401def validate_print_report_params(func: Callable) -> Callable:
402 """Validate parameters for print report methods."""
403 @wraps(func)
404 def wrapper(self, UUIDs: Optional[List[int]] = None, *args, **kwargs):
405 if UUIDs is not None:
406 validate_uuid_list(UUIDs, "UUIDs", func.__name__, allow_empty=False)
407 return func(self, UUIDs, *args, **kwargs)
408 return wrapper
409
410
411# Visualizer decorators
412def validate_build_geometry_params(func: Callable) -> Callable:
413 """Validate parameters for building visualizer geometry."""
414 @wraps(func)
415 def wrapper(self, context, uuids: Optional[List[int]] = None, *args, **kwargs):
416 # Context validation - check it's the right type
417 # Use both isinstance and string-based checking to handle import ordering issues
418 from ..Context import Context
419 is_valid_context = (
420 isinstance(context, Context) or
421 (hasattr(context, '__class__') and
422 context.__class__.__name__ == 'Context' and
423 'pyhelios.Context' in context.__class__.__module__)
424 )
425
426 if not is_valid_context:
427 from .exceptions import create_validation_error
428 raise create_validation_error(
429 f"Parameter must be a Context instance, got {type(context).__name__}",
430 param_name="context",
431 function_name=func.__name__,
432 expected_type="Context",
433 actual_value=context,
434 suggestion="Provide a valid PyHelios Context instance."
435 )
436
437 if uuids is not None:
438 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=True)
439 return func(self, context, uuids, *args, **kwargs)
440 return wrapper
441
442
443def validate_print_window_params(func: Callable) -> Callable:
444 """Validate parameters for printing window to file."""
445 @wraps(func)
446 def wrapper(self, filename: str, *args, **kwargs):
447 validate_filename(filename, "filename", func.__name__,
448 allowed_extensions=['.png', '.jpg', '.jpeg', '.bmp', '.tga'])
449 return func(self, filename, *args, **kwargs)
450 return wrapper
451
452
453# Photosynthesis decorators
454def validate_photosynthesis_species_params(func: Callable) -> Callable:
455 """Validate photosynthesis species parameters."""
456 @wraps(func)
457 def wrapper(self, species: str, *args, **kwargs):
458 validate_species_name(species, "species", func.__name__)
459 return func(self, species, *args, **kwargs)
460 return wrapper
461
462
463def validate_photosynthesis_temperature_params(func: Callable) -> Callable:
464 """Validate temperature parameters for photosynthesis."""
465 @wraps(func)
466 def wrapper(self, temperature: float, *args, **kwargs):
467 validate_temperature(temperature, "temperature", func.__name__)
468 return func(self, temperature, *args, **kwargs)
469 return wrapper
470
471
472def validate_photosynthesis_co2_params(func: Callable) -> Callable:
473 """Validate CO2 concentration parameters."""
474 @wraps(func)
475 def wrapper(self, co2_concentration: float, *args, **kwargs):
476 validate_co2_concentration(co2_concentration, "co2_concentration", func.__name__)
477 return func(self, co2_concentration, *args, **kwargs)
478 return wrapper
479
480
481def validate_photosynthesis_par_params(func: Callable) -> Callable:
482 """Validate PAR flux parameters."""
483 @wraps(func)
484 def wrapper(self, par_flux: float, *args, **kwargs):
485 validate_par_flux(par_flux, "par_flux", func.__name__)
486 return func(self, par_flux, *args, **kwargs)
487 return wrapper
488
489
490def validate_photosynthesis_conductance_params(func: Callable) -> Callable:
491 """Validate conductance parameters."""
492 @wraps(func)
493 def wrapper(self, conductance: float, *args, **kwargs):
494 validate_conductance(conductance, "conductance", func.__name__)
495 return func(self, conductance, *args, **kwargs)
496 return wrapper
497
498
499def validate_empirical_model_params(func: Callable) -> Callable:
500 """Validate empirical model coefficient parameters."""
501 @wraps(func)
502 def wrapper(self, coefficients, *args, **kwargs):
503 validate_empirical_coefficients(coefficients, "coefficients", func.__name__)
504 return func(self, coefficients, *args, **kwargs)
505 return wrapper
506
507
508def validate_farquhar_model_params(func: Callable) -> Callable:
509 """Validate Farquhar model coefficient parameters."""
510 @wraps(func)
511 def wrapper(self, coefficients, *args, **kwargs):
512 validate_farquhar_coefficients(coefficients, "coefficients", func.__name__)
513 return func(self, coefficients, *args, **kwargs)
514 return wrapper
515
516
517def validate_vcmax_params(func: Callable) -> Callable:
518 """Validate Vcmax parameters for Farquhar model."""
519 @wraps(func)
520 def wrapper(self, vcmax: float, *args, **kwargs):
521 validate_vcmax(vcmax, "vcmax", func.__name__)
522 return func(self, vcmax, *args, **kwargs)
523 return wrapper
524
525
526def validate_jmax_params(func: Callable) -> Callable:
527 """Validate Jmax parameters for Farquhar model."""
528 @wraps(func)
529 def wrapper(self, jmax: float, *args, **kwargs):
530 validate_jmax(jmax, "jmax", func.__name__)
531 return func(self, jmax, *args, **kwargs)
532 return wrapper
533
534
535def validate_quantum_efficiency_params(func: Callable) -> Callable:
536 """Validate quantum efficiency parameters."""
537 @wraps(func)
538 def wrapper(self, efficiency: float, *args, **kwargs):
539 validate_quantum_efficiency(efficiency, "efficiency", func.__name__)
540 return func(self, efficiency, *args, **kwargs)
541 return wrapper
542
543
544def validate_dark_respiration_params(func: Callable) -> Callable:
545 """Validate dark respiration parameters."""
546 @wraps(func)
547 def wrapper(self, respiration: float, *args, **kwargs):
548 validate_dark_respiration(respiration, "respiration", func.__name__)
549 return func(self, respiration, *args, **kwargs)
550 return wrapper
551
552
553def validate_oxygen_concentration_params(func: Callable) -> Callable:
554 """Validate oxygen concentration parameters."""
555 @wraps(func)
556 def wrapper(self, o2_concentration: float, *args, **kwargs):
557 validate_oxygen_concentration(o2_concentration, "o2_concentration", func.__name__)
558 return func(self, o2_concentration, *args, **kwargs)
559 return wrapper
560
561
562def validate_temperature_response_params(func: Callable) -> Callable:
563 """Validate temperature response parameters."""
564 @wraps(func)
565 def wrapper(self, params, *args, **kwargs):
566 validate_temperature_response_params(params, "params", func.__name__)
567 return func(self, params, *args, **kwargs)
568 return wrapper
569
570
571def validate_photosynthesis_rate_params(func: Callable) -> Callable:
572 """Validate photosynthetic rate parameters."""
573 @wraps(func)
574 def wrapper(self, rate: float, *args, **kwargs):
575 validate_photosynthetic_rate(rate, "rate", func.__name__)
576 return func(self, rate, *args, **kwargs)
577 return wrapper
578
579
580def validate_photosynthesis_uuid_params(func: Callable) -> Callable:
581 """Validate UUID parameters for photosynthesis methods."""
582 @wraps(func)
583 def wrapper(self, uuids: Union[List[int], int], *args, **kwargs):
584 if isinstance(uuids, int):
585 # Single UUID - validate as non-negative integer (UUIDs start from 0)
586 validate_non_negative_value(uuids, "uuids", func.__name__)
587 elif isinstance(uuids, list):
588 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=False)
589 else:
590 from .exceptions import create_validation_error
591 raise create_validation_error(
592 f"Parameter must be an integer or list of integers, got {type(uuids).__name__}",
593 param_name="uuids",
594 function_name=func.__name__,
595 expected_type="integer or list of integers",
596 actual_value=uuids,
597 suggestion="Provide a UUID (integer) or list of UUIDs."
598 )
599 return func(self, uuids, *args, **kwargs)
600 return wrapper
601
602
603def validate_radiation_camera_params(func: Callable) -> Callable:
604 """
605 Validate parameters for addRadiationCamera method.
606
607 Handles validation and type conversion for camera creation parameters:
608 - camera_label: string validation
609 - band_labels: list of strings validation
610 - position: converts lists/tuples to vec3 if needed
611 - lookat_or_direction: handles vec3 or SphericalCoord
612 - antialiasing_samples: positive integer validation
613 """
614 @wraps(func)
615 def wrapper(self, camera_label, band_labels, position, lookat_or_direction,
616 camera_properties=None, antialiasing_samples: int = 100, *args, **kwargs):
617 from ..wrappers.DataTypes import vec3, SphericalCoord, make_vec3
618
619 # Validate basic parameters
620 validated_label = validate_camera_label(camera_label, "camera_label", func.__name__)
621 validated_bands = validate_band_labels_list(band_labels, "band_labels", func.__name__)
622 validated_samples = validate_antialiasing_samples(antialiasing_samples, "antialiasing_samples", func.__name__)
623
624 # Validate and convert position to vec3
625 validated_position = validate_vec3(position, "position", func.__name__)
626
627 # Validate lookat_or_direction (can be vec3, list/tuple, or SphericalCoord)
628 validated_direction = None
629 if hasattr(lookat_or_direction, 'radius') and hasattr(lookat_or_direction, 'elevation'):
630 # SphericalCoord - validate directly (already proper type)
631 validated_direction = lookat_or_direction
632 else:
633 # Assume vec3 or convertible to vec3
634 validated_direction = validate_vec3(lookat_or_direction, "lookat_or_direction", func.__name__)
635
636 # Validate camera properties if provided
637 if camera_properties is not None:
638 if not hasattr(camera_properties, 'to_array'):
639 from ..validation.exceptions import create_validation_error
640 raise create_validation_error(
641 f"camera_properties must be a CameraProperties instance or None, got {type(camera_properties).__name__}",
642 param_name="camera_properties",
643 function_name=func.__name__,
644 expected_type="CameraProperties or None",
645 actual_value=camera_properties,
646 suggestion="Use a CameraProperties instance or None for default properties."
647 )
648
649 return func(self, validated_label, validated_bands, validated_position, validated_direction,
650 camera_properties, validated_samples, *args, **kwargs)
651 return wrapper
Callable validate_branch_segment_params(Callable func)
Validate branch segment resolution parameters.
Callable validate_jmax_params(Callable func)
Validate Jmax parameters for Farquhar model.
Callable validate_radiation_camera_params(Callable func)
Validate parameters for addRadiationCamera method.
Callable validate_photosynthesis_co2_params(Callable func)
Validate CO2 concentration parameters.
Callable validate_recursion_params(Callable func)
Validate recursion level parameters.
Callable validate_quantum_efficiency_params(Callable func)
Validate quantum efficiency parameters.
Callable validate_collimated_source_params(Callable func)
Validate parameters for collimated radiation source creation.
Callable validate_print_window_params(Callable func)
Validate parameters for printing window to file.
Callable validate_min_scatter_energy_params(Callable func)
Validate parameters for minimum scatter energy.
Callable validate_update_geometry_params(Callable func)
Validate parameters for geometry updates.
Callable validate_tree_uuid_params(Callable func)
Validate tree ID parameters for WeberPennTree methods.
Callable validate_get_source_flux_params(Callable func)
Validate parameters for getting source flux.
Callable validate_energy_band_params(Callable func)
Validate parameters for energy balance band operations.
Callable validate_photosynthesis_temperature_params(Callable func)
Validate temperature parameters for photosynthesis.
Callable validate_evaluate_air_energy_params(Callable func)
Validate parameters for evaluating air energy balance.
Callable validate_sphere_source_params(Callable func)
Validate parameters for sphere radiation source creation.
Callable validate_trunk_segment_params(Callable func)
Validate trunk segment resolution parameters.
Callable validate_empirical_model_params(Callable func)
Validate empirical model coefficient parameters.
Callable validate_radiation_band_params(Callable func)
Validate parameters for radiation band operations.
Callable validate_dark_respiration_params(Callable func)
Validate dark respiration parameters.
Callable validate_photosynthesis_rate_params(Callable func)
Validate photosynthetic rate parameters.
Callable validate_leaf_subdivisions_params(Callable func)
Validate leaf subdivision parameters.
Callable validate_temperature_response_params(Callable func)
Validate temperature response parameters.
Callable validate_build_geometry_params(Callable func)
Validate parameters for building visualizer geometry.
Callable validate_photosynthesis_uuid_params(Callable func)
Validate UUID parameters for photosynthesis methods.
Callable validate_farquhar_model_params(Callable func)
Validate Farquhar model coefficient parameters.
Callable validate_oxygen_concentration_params(Callable func)
Validate oxygen concentration parameters.
Callable validate_print_report_params(Callable func)
Validate parameters for print report methods.
Callable validate_xml_file_params(Callable func)
Validate XML file path parameters for WeberPennTree.
Callable validate_vcmax_params(Callable func)
Validate Vcmax parameters for Farquhar model.
Callable validate_photosynthesis_species_params(Callable func)
Validate photosynthesis species parameters.
Callable validate_energy_run_params(Callable func)
Validate parameters for energy balance run method.
Callable validate_run_band_params(Callable func)
Validate parameters for running radiation bands.
Callable validate_photosynthesis_conductance_params(Callable func)
Validate conductance parameters.
Callable validate_sun_sphere_params(Callable func)
Validate parameters for sun sphere radiation source.
Callable validate_air_energy_params(Callable func)
Validate parameters for air energy balance.
Callable validate_scattering_depth_params(Callable func)
Validate parameters for scattering depth.
Callable validate_photosynthesis_par_params(Callable func)
Validate PAR flux parameters.
Callable validate_output_data_params(Callable func)
Validate parameters for optional output data.
Callable validate_source_flux_multiple_params(Callable func)
Validate parameters for setting multiple source flux.