0.1.39
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 # 0 (the native default) disables early termination; only negative values are invalid.
169 validate_non_negative_value(energy, "energy", func.__name__)
170 return func(self, label, energy, *args, **kwargs)
171 return wrapper
172
173
174# WeberPennTree decorators
175def validate_tree_uuid_params(func: Callable) -> Callable:
176 """Validate tree ID parameters for WeberPennTree methods."""
177 @wraps(func)
178 def wrapper(self, tree_id: int, *args, **kwargs):
179 validate_tree_id(tree_id, "tree_id", func.__name__)
180 return func(self, tree_id, *args, **kwargs)
181 return wrapper
182
183
184def validate_recursion_params(func: Callable) -> Callable:
185 """Validate recursion level parameters."""
186 @wraps(func)
187 def wrapper(self, level: int, *args, **kwargs):
188 validate_recursion_level(level, "level", func.__name__)
189 return func(self, level, *args, **kwargs)
190 return wrapper
191
192
193def validate_trunk_segment_params(func: Callable) -> Callable:
194 """Validate trunk segment resolution parameters."""
195 @wraps(func)
196 def wrapper(self, trunk_segs: int, *args, **kwargs):
197 validate_segment_resolution(trunk_segs, min_val=3, max_val=100, param_name="trunk_segs", function_name=func.__name__)
198 return func(self, trunk_segs, *args, **kwargs)
199 return wrapper
200
201
202def validate_branch_segment_params(func: Callable) -> Callable:
203 """Validate branch segment resolution parameters."""
204 @wraps(func)
205 def wrapper(self, branch_segs: int, *args, **kwargs):
206 validate_segment_resolution(branch_segs, min_val=3, max_val=100, param_name="branch_segs", function_name=func.__name__)
207 return func(self, branch_segs, *args, **kwargs)
208 return wrapper
209
210
211def validate_leaf_subdivisions_params(func: Callable) -> Callable:
212 """Validate leaf subdivision parameters."""
213 @wraps(func)
214 def wrapper(self, leaf_segs_x: int, leaf_segs_y: int, *args, **kwargs):
215 validate_subdivision_count(leaf_segs_x, "leaf_segs_x", func.__name__)
216 validate_subdivision_count(leaf_segs_y, "leaf_segs_y", func.__name__)
217 return func(self, leaf_segs_x, leaf_segs_y, *args, **kwargs)
218 return wrapper
219
220
221def validate_xml_file_params(func: Callable) -> Callable:
222 """Validate XML file path parameters for WeberPennTree."""
223 @wraps(func)
224 def wrapper(self, filename: str, silent: bool = False, *args, **kwargs):
225 from pathlib import Path
226 from .exceptions import create_validation_error
227
228 # Validate filename parameter
229 validate_filename(filename, "filename", func.__name__, allowed_extensions=['.xml'])
230
231 # Convert to Path for existence checking
232 xml_path = Path(filename)
233
234 # Check if file exists
235 if not xml_path.exists():
236 raise create_validation_error(
237 f"XML file not found: {filename}",
238 param_name="filename",
239 function_name=func.__name__,
240 expected_type="path to existing .xml file",
241 actual_value=filename,
242 suggestion=f"Ensure the file exists at: {xml_path.resolve()}"
243 )
244
245 # Validate silent parameter
246 if not isinstance(silent, bool):
247 raise create_validation_error(
248 f"Parameter must be a boolean, got {type(silent).__name__}",
249 param_name="silent",
250 function_name=func.__name__,
251 expected_type="bool",
252 actual_value=silent,
253 suggestion="Use True or False for silent parameter."
254 )
255
256 return func(self, filename, silent, *args, **kwargs)
257 return wrapper
258
259
260# EnergyBalance decorators
261def validate_energy_run_params(func: Callable) -> Callable:
262 """Validate parameters for energy balance run method."""
263 @wraps(func)
264 def wrapper(self, uuids: Optional[List[int]] = None, dt: Optional[float] = None, *args, **kwargs):
265 if uuids is not None:
266 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=False)
267 if dt is not None:
268 validate_positive_value(dt, "dt", func.__name__)
269 return func(self, uuids, dt, *args, **kwargs)
270 return wrapper
271
272
273def validate_energy_band_params(func: Callable) -> Callable:
274 """Validate parameters for energy balance band operations."""
275 @wraps(func)
276 def wrapper(self, band: Union[str, List[str]], *args, **kwargs):
277 if isinstance(band, str):
278 if not band.strip():
279 from .exceptions import create_validation_error
280 raise create_validation_error(
281 f"Parameter must be a non-empty string",
282 param_name="band",
283 function_name=func.__name__,
284 expected_type="non-empty string",
285 actual_value=repr(band),
286 suggestion="Provide a non-empty band name."
287 )
288 elif isinstance(band, list):
289 if not band:
290 from .exceptions import create_validation_error
291 raise create_validation_error(
292 f"Parameter cannot be empty",
293 param_name="band",
294 function_name=func.__name__,
295 expected_type="non-empty list",
296 actual_value=band,
297 suggestion="Provide at least one band name."
298 )
299 for i, b in enumerate(band):
300 if not isinstance(b, str) or not b.strip():
301 from .exceptions import create_validation_error
302 raise create_validation_error(
303 f"All band names must be non-empty strings, got {type(b).__name__} at index {i}",
304 param_name=f"band[{i}]",
305 function_name=func.__name__,
306 expected_type="non-empty string",
307 actual_value=b,
308 suggestion="All band names must be non-empty strings."
309 )
310 else:
311 from .exceptions import create_validation_error
312 raise create_validation_error(
313 f"Parameter must be a string or list of strings, got {type(band).__name__}",
314 param_name="band",
315 function_name=func.__name__,
316 expected_type="string or list of strings",
317 actual_value=band,
318 suggestion="Provide a band name string or list of band names."
319 )
320 return func(self, band, *args, **kwargs)
321 return wrapper
322
323
324def validate_air_energy_params(func: Callable) -> Callable:
325 """Validate parameters for air energy balance."""
326 @wraps(func)
327 def wrapper(self, canopy_height_m: Optional[float] = None, reference_height_m: Optional[float] = None, *args, **kwargs):
328 if canopy_height_m is not None:
329 validate_positive_value(canopy_height_m, "canopy_height_m", func.__name__)
330 if reference_height_m is not None:
331 validate_positive_value(reference_height_m, "reference_height_m", func.__name__)
332
333 # Check that both parameters are provided together or neither
334 if (canopy_height_m is None) != (reference_height_m is None):
335 from .exceptions import create_validation_error
336 raise create_validation_error(
337 f"Canopy height and reference height must be provided together or omitted together",
338 param_name="canopy_height_m, reference_height_m",
339 function_name=func.__name__,
340 expected_type="both parameters provided or both None",
341 actual_value=f"canopy_height_m={canopy_height_m}, reference_height_m={reference_height_m}",
342 suggestion="Provide both canopy_height_m and reference_height_m, or omit both for automatic detection."
343 )
344
345 return func(self, canopy_height_m, reference_height_m, *args, **kwargs)
346 return wrapper
347
348
349def validate_evaluate_air_energy_params(func: Callable) -> Callable:
350 """Validate parameters for evaluating air energy balance."""
351 @wraps(func)
352 def wrapper(self, dt_sec: float, time_advance_sec: float, UUIDs: Optional[List[int]] = None, *args, **kwargs):
353 validate_time_value(dt_sec, "dt_sec", func.__name__)
354 validate_time_value(time_advance_sec, "time_advance_sec", func.__name__)
355
356 if time_advance_sec < dt_sec:
357 from .exceptions import create_validation_error
358 raise create_validation_error(
359 f"Time advance ({time_advance_sec}) must be greater than or equal to time step ({dt_sec})",
360 param_name="time_advance_sec",
361 function_name=func.__name__,
362 expected_type=f"number >= {dt_sec}",
363 actual_value=time_advance_sec,
364 suggestion=f"Use time_advance_sec >= {dt_sec}."
365 )
366
367 if UUIDs is not None:
368 validate_uuid_list(UUIDs, "UUIDs", func.__name__, allow_empty=False)
369
370 return func(self, dt_sec, time_advance_sec, UUIDs, *args, **kwargs)
371 return wrapper
372
373
374def validate_output_data_params(func: Callable) -> Callable:
375 """Validate parameters for optional output data."""
376 @wraps(func)
377 def wrapper(self, label: str, *args, **kwargs):
378 if not isinstance(label, str):
379 from .exceptions import create_validation_error
380 raise create_validation_error(
381 f"Parameter must be a string, got {type(label).__name__}",
382 param_name="label",
383 function_name=func.__name__,
384 expected_type="string",
385 actual_value=label,
386 suggestion="Provide a string label for the output data."
387 )
388 if not label.strip():
389 from .exceptions import create_validation_error
390 raise create_validation_error(
391 f"Parameter cannot be empty or whitespace-only",
392 param_name="label",
393 function_name=func.__name__,
394 expected_type="non-empty string",
395 actual_value=repr(label),
396 suggestion="Provide a non-empty label for the output data."
397 )
398 return func(self, label, *args, **kwargs)
399 return wrapper
400
401
402def validate_print_report_params(func: Callable) -> Callable:
403 """Validate parameters for print report methods."""
404 @wraps(func)
405 def wrapper(self, UUIDs: Optional[List[int]] = None, *args, **kwargs):
406 if UUIDs is not None:
407 validate_uuid_list(UUIDs, "UUIDs", func.__name__, allow_empty=False)
408 return func(self, UUIDs, *args, **kwargs)
409 return wrapper
410
411
412# Visualizer decorators
413def validate_build_geometry_params(func: Callable) -> Callable:
414 """Validate parameters for building visualizer geometry."""
415 @wraps(func)
416 def wrapper(self, context, uuids: Optional[List[int]] = None, *args, **kwargs):
417 # Context validation - check it's the right type
418 # Use both isinstance and string-based checking to handle import ordering issues
419 from ..Context import Context
420 is_valid_context = (
421 isinstance(context, Context) or
422 (hasattr(context, '__class__') and
423 context.__class__.__name__ == 'Context' and
424 'pyhelios.Context' in context.__class__.__module__)
425 )
426
427 if not is_valid_context:
428 from .exceptions import create_validation_error
429 raise create_validation_error(
430 f"Parameter must be a Context instance, got {type(context).__name__}",
431 param_name="context",
432 function_name=func.__name__,
433 expected_type="Context",
434 actual_value=context,
435 suggestion="Provide a valid PyHelios Context instance."
436 )
437
438 if uuids is not None:
439 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=True)
440 return func(self, context, uuids, *args, **kwargs)
441 return wrapper
442
443
444def validate_print_window_params(func: Callable) -> Callable:
445 """Validate parameters for printing window to file."""
446 @wraps(func)
447 def wrapper(self, filename: str, *args, **kwargs):
448 validate_filename(filename, "filename", func.__name__,
449 allowed_extensions=['.png', '.jpg', '.jpeg', '.bmp', '.tga'])
450 return func(self, filename, *args, **kwargs)
451 return wrapper
452
453
454# Photosynthesis decorators
455def validate_photosynthesis_species_params(func: Callable) -> Callable:
456 """Validate photosynthesis species parameters."""
457 @wraps(func)
458 def wrapper(self, species: str, *args, **kwargs):
459 validate_species_name(species, "species", func.__name__)
460 return func(self, species, *args, **kwargs)
461 return wrapper
462
463
464def validate_photosynthesis_temperature_params(func: Callable) -> Callable:
465 """Validate temperature parameters for photosynthesis."""
466 @wraps(func)
467 def wrapper(self, temperature: float, *args, **kwargs):
468 validate_temperature(temperature, "temperature", func.__name__)
469 return func(self, temperature, *args, **kwargs)
470 return wrapper
471
472
473def validate_photosynthesis_co2_params(func: Callable) -> Callable:
474 """Validate CO2 concentration parameters."""
475 @wraps(func)
476 def wrapper(self, co2_concentration: float, *args, **kwargs):
477 validate_co2_concentration(co2_concentration, "co2_concentration", func.__name__)
478 return func(self, co2_concentration, *args, **kwargs)
479 return wrapper
480
481
482def validate_photosynthesis_par_params(func: Callable) -> Callable:
483 """Validate PAR flux parameters."""
484 @wraps(func)
485 def wrapper(self, par_flux: float, *args, **kwargs):
486 validate_par_flux(par_flux, "par_flux", func.__name__)
487 return func(self, par_flux, *args, **kwargs)
488 return wrapper
489
490
491def validate_photosynthesis_conductance_params(func: Callable) -> Callable:
492 """Validate conductance parameters."""
493 @wraps(func)
494 def wrapper(self, conductance: float, *args, **kwargs):
495 validate_conductance(conductance, "conductance", func.__name__)
496 return func(self, conductance, *args, **kwargs)
497 return wrapper
498
499
500def validate_empirical_model_params(func: Callable) -> Callable:
501 """Validate empirical model coefficient parameters."""
502 @wraps(func)
503 def wrapper(self, coefficients, *args, **kwargs):
504 validate_empirical_coefficients(coefficients, "coefficients", func.__name__)
505 return func(self, coefficients, *args, **kwargs)
506 return wrapper
507
508
509def validate_farquhar_model_params(func: Callable) -> Callable:
510 """Validate Farquhar model coefficient parameters."""
511 @wraps(func)
512 def wrapper(self, coefficients, *args, **kwargs):
513 validate_farquhar_coefficients(coefficients, "coefficients", func.__name__)
514 return func(self, coefficients, *args, **kwargs)
515 return wrapper
516
517
518def validate_vcmax_params(func: Callable) -> Callable:
519 """Validate Vcmax parameters for Farquhar model."""
520 @wraps(func)
521 def wrapper(self, vcmax: float, *args, **kwargs):
522 validate_vcmax(vcmax, "vcmax", func.__name__)
523 return func(self, vcmax, *args, **kwargs)
524 return wrapper
525
526
527def validate_jmax_params(func: Callable) -> Callable:
528 """Validate Jmax parameters for Farquhar model."""
529 @wraps(func)
530 def wrapper(self, jmax: float, *args, **kwargs):
531 validate_jmax(jmax, "jmax", func.__name__)
532 return func(self, jmax, *args, **kwargs)
533 return wrapper
534
535
536def validate_quantum_efficiency_params(func: Callable) -> Callable:
537 """Validate quantum efficiency parameters."""
538 @wraps(func)
539 def wrapper(self, efficiency: float, *args, **kwargs):
540 validate_quantum_efficiency(efficiency, "efficiency", func.__name__)
541 return func(self, efficiency, *args, **kwargs)
542 return wrapper
543
544
545def validate_dark_respiration_params(func: Callable) -> Callable:
546 """Validate dark respiration parameters."""
547 @wraps(func)
548 def wrapper(self, respiration: float, *args, **kwargs):
549 validate_dark_respiration(respiration, "respiration", func.__name__)
550 return func(self, respiration, *args, **kwargs)
551 return wrapper
552
553
554def validate_oxygen_concentration_params(func: Callable) -> Callable:
555 """Validate oxygen concentration parameters."""
556 @wraps(func)
557 def wrapper(self, o2_concentration: float, *args, **kwargs):
558 validate_oxygen_concentration(o2_concentration, "o2_concentration", func.__name__)
559 return func(self, o2_concentration, *args, **kwargs)
560 return wrapper
561
562
563def validate_temperature_response_params(func: Callable) -> Callable:
564 """Validate temperature response parameters."""
565 @wraps(func)
566 def wrapper(self, params, *args, **kwargs):
567 validate_temperature_response_params(params, "params", func.__name__)
568 return func(self, params, *args, **kwargs)
569 return wrapper
570
571
572def validate_photosynthesis_rate_params(func: Callable) -> Callable:
573 """Validate photosynthetic rate parameters."""
574 @wraps(func)
575 def wrapper(self, rate: float, *args, **kwargs):
576 validate_photosynthetic_rate(rate, "rate", func.__name__)
577 return func(self, rate, *args, **kwargs)
578 return wrapper
579
580
581def validate_photosynthesis_uuid_params(func: Callable) -> Callable:
582 """Validate UUID parameters for photosynthesis methods."""
583 @wraps(func)
584 def wrapper(self, uuids: Union[List[int], int], *args, **kwargs):
585 if isinstance(uuids, int):
586 # Single UUID - validate as non-negative integer (UUIDs start from 0)
587 validate_non_negative_value(uuids, "uuids", func.__name__)
588 elif isinstance(uuids, list):
589 validate_uuid_list(uuids, "uuids", func.__name__, allow_empty=False)
590 else:
591 from .exceptions import create_validation_error
592 raise create_validation_error(
593 f"Parameter must be an integer or list of integers, got {type(uuids).__name__}",
594 param_name="uuids",
595 function_name=func.__name__,
596 expected_type="integer or list of integers",
597 actual_value=uuids,
598 suggestion="Provide a UUID (integer) or list of UUIDs."
599 )
600 return func(self, uuids, *args, **kwargs)
601 return wrapper
602
603
604def validate_radiation_camera_params(func: Callable) -> Callable:
605 """
606 Validate parameters for addRadiationCamera method.
607
608 Handles validation and type conversion for camera creation parameters:
609 - camera_label: string validation
610 - band_labels: list of strings validation
611 - position: converts lists/tuples to vec3 if needed
612 - lookat_or_direction: handles vec3 or SphericalCoord
613 - antialiasing_samples: positive integer validation
614 """
615 @wraps(func)
616 def wrapper(self, camera_label, band_labels, position, lookat_or_direction,
617 camera_properties=None, antialiasing_samples: int = 100, *args, **kwargs):
618 from ..wrappers.DataTypes import vec3, SphericalCoord, make_vec3
619
620 # Validate basic parameters
621 validated_label = validate_camera_label(camera_label, "camera_label", func.__name__)
622 validated_bands = validate_band_labels_list(band_labels, "band_labels", func.__name__)
623 validated_samples = validate_antialiasing_samples(antialiasing_samples, "antialiasing_samples", func.__name__)
624
625 # Validate and convert position to vec3
626 validated_position = validate_vec3(position, "position", func.__name__)
627
628 # Validate lookat_or_direction (can be vec3, list/tuple, or SphericalCoord)
629 validated_direction = None
630 if hasattr(lookat_or_direction, 'radius') and hasattr(lookat_or_direction, 'elevation'):
631 # SphericalCoord - validate directly (already proper type)
632 validated_direction = lookat_or_direction
633 else:
634 # Assume vec3 or convertible to vec3
635 validated_direction = validate_vec3(lookat_or_direction, "lookat_or_direction", func.__name__)
636
637 # Validate camera properties if provided
638 if camera_properties is not None:
639 if not hasattr(camera_properties, 'to_array'):
640 from ..validation.exceptions import create_validation_error
641 raise create_validation_error(
642 f"camera_properties must be a CameraProperties instance or None, got {type(camera_properties).__name__}",
643 param_name="camera_properties",
644 function_name=func.__name__,
645 expected_type="CameraProperties or None",
646 actual_value=camera_properties,
647 suggestion="Use a CameraProperties instance or None for default properties."
648 )
649
650 return func(self, validated_label, validated_bands, validated_position, validated_direction,
651 camera_properties, validated_samples, *args, **kwargs)
652 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.