0.1.22
Loading...
Searching...
No Matches
ULeafOpticsWrapper.py
Go to the documentation of this file.
1"""
2Ctypes wrapper for LeafOptics C++ bindings.
3
4This module provides low-level ctypes bindings to interface with
5the native Helios LeafOptics plugin (PROSPECT model) via the C++ wrapper layer.
6"""
7
8import ctypes
9from typing import List, Tuple
10
11from ..plugins import helios_lib
12from ..exceptions import check_helios_error
13
14# Define the ULeafOptics struct
15class ULeafOptics(ctypes.Structure):
16 """Opaque structure for LeafOptics C++ class"""
17 pass
18
19# Import UContext from main wrapper to avoid type conflicts
20from .UContextWrapper import UContext
21
22# Number of properties in LeafOpticsProperties struct.
23# Order: [numberlayers, brownpigments, chlorophyllcontent, carotenoidcontent,
24# anthocyancontent, watermass, drymass, protein, carbonconstituents, V2Z, fqe]
25LEAF_OPTICS_PROPERTIES_COUNT = 11
26
27# Number of spectral points (400-2500 nm at 1nm resolution)
28LEAF_OPTICS_SPECTRAL_POINTS = 2101
29
30# Error checking callback
31def _check_error(result, func, args):
32 """Automatic error checking for all leaf optics functions"""
33 check_helios_error(helios_lib.getLastErrorCode, helios_lib.getLastErrorMessage)
34 return result
35
36# Try to set up LeafOptics function prototypes
37try:
38 # LeafOptics creation and destruction
39 helios_lib.createLeafOptics.argtypes = [ctypes.POINTER(UContext)]
40 helios_lib.createLeafOptics.restype = ctypes.POINTER(ULeafOptics)
41 helios_lib.createLeafOptics.errcheck = _check_error
43 helios_lib.destroyLeafOptics.argtypes = [ctypes.POINTER(ULeafOptics)]
44 helios_lib.destroyLeafOptics.restype = None
45
46 # Model execution
47 helios_lib.leafOpticsRun.argtypes = [
48 ctypes.POINTER(ULeafOptics),
49 ctypes.POINTER(ctypes.c_uint),
50 ctypes.c_uint,
51 ctypes.POINTER(ctypes.c_float),
52 ctypes.c_char_p
53 ]
54 helios_lib.leafOpticsRun.restype = None
55 helios_lib.leafOpticsRun.errcheck = _check_error
56
57 helios_lib.leafOpticsRunNoUUIDs.argtypes = [
58 ctypes.POINTER(ULeafOptics),
59 ctypes.POINTER(ctypes.c_float),
60 ctypes.c_char_p
61 ]
62 helios_lib.leafOpticsRunNoUUIDs.restype = None
63 helios_lib.leafOpticsRunNoUUIDs.errcheck = _check_error
64
65 # Spectral data retrieval
66 helios_lib.leafOpticsGetLeafSpectra.argtypes = [
67 ctypes.POINTER(ULeafOptics),
68 ctypes.POINTER(ctypes.c_float),
69 ctypes.POINTER(ctypes.c_float),
70 ctypes.POINTER(ctypes.c_float),
71 ctypes.POINTER(ctypes.c_float),
72 ctypes.POINTER(ctypes.c_uint)
73 ]
74 helios_lib.leafOpticsGetLeafSpectra.restype = None
75 helios_lib.leafOpticsGetLeafSpectra.errcheck = _check_error
76
77 # Property management
78 helios_lib.leafOpticsSetProperties.argtypes = [
79 ctypes.POINTER(ULeafOptics),
80 ctypes.POINTER(ctypes.c_uint),
81 ctypes.c_uint,
82 ctypes.POINTER(ctypes.c_float)
83 ]
84 helios_lib.leafOpticsSetProperties.restype = None
85 helios_lib.leafOpticsSetProperties.errcheck = _check_error
86
87 helios_lib.leafOpticsGetPropertiesFromSpectrum.argtypes = [
88 ctypes.POINTER(ULeafOptics),
89 ctypes.POINTER(ctypes.c_uint),
90 ctypes.c_uint
91 ]
92 helios_lib.leafOpticsGetPropertiesFromSpectrum.restype = None
93 helios_lib.leafOpticsGetPropertiesFromSpectrum.errcheck = _check_error
94
95 helios_lib.leafOpticsGetPropertiesFromSpectrumSingle.argtypes = [
96 ctypes.POINTER(ULeafOptics),
97 ctypes.c_uint
98 ]
99 helios_lib.leafOpticsGetPropertiesFromSpectrumSingle.restype = None
100 helios_lib.leafOpticsGetPropertiesFromSpectrumSingle.errcheck = _check_error
101
102 # Species library
103 helios_lib.leafOpticsGetPropertiesFromLibrary.argtypes = [
104 ctypes.POINTER(ULeafOptics),
105 ctypes.c_char_p,
106 ctypes.POINTER(ctypes.c_float)
107 ]
108 helios_lib.leafOpticsGetPropertiesFromLibrary.restype = None
109 helios_lib.leafOpticsGetPropertiesFromLibrary.errcheck = _check_error
110
111 # Message control
112 helios_lib.leafOpticsDisableMessages.argtypes = [ctypes.POINTER(ULeafOptics)]
113 helios_lib.leafOpticsDisableMessages.restype = None
114 helios_lib.leafOpticsDisableMessages.errcheck = _check_error
115
116 helios_lib.leafOpticsEnableMessages.argtypes = [ctypes.POINTER(ULeafOptics)]
117 helios_lib.leafOpticsEnableMessages.restype = None
118 helios_lib.leafOpticsEnableMessages.errcheck = _check_error
119
120 # Optional output primitive data (v1.3.59)
121 helios_lib.leafOpticsOptionalOutputPrimitiveData.argtypes = [ctypes.POINTER(ULeafOptics), ctypes.c_char_p]
122 helios_lib.leafOpticsOptionalOutputPrimitiveData.restype = None
123 helios_lib.leafOpticsOptionalOutputPrimitiveData.errcheck = _check_error
124
125 _LEAFOPTICS_FUNCTIONS_AVAILABLE = True
126
127except AttributeError:
128 _LEAFOPTICS_FUNCTIONS_AVAILABLE = False
129
131# Wrapper functions
132
133def createLeafOptics(context: ctypes.POINTER(UContext)) -> ctypes.POINTER(ULeafOptics):
134 """Create LeafOptics instance
135
136 Args:
137 context: Pointer to the Helios Context
138
139 Returns:
140 Pointer to the created LeafOptics instance
141
142 Raises:
143 NotImplementedError: If LeafOptics functions not available
144 ValueError: If context is None
145 """
146 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
147 raise NotImplementedError(
148 "LeafOptics functions not available in current Helios library. "
149 "Rebuild PyHelios with 'leafoptics' enabled:\n"
150 " build_scripts/build_helios --plugins leafoptics"
151 )
152
153 if not context:
154 raise ValueError("Context instance is None.")
155
156 return helios_lib.createLeafOptics(context)
157
158
159def destroyLeafOptics(leafoptics: ctypes.POINTER(ULeafOptics)) -> None:
160 """Destroy LeafOptics instance"""
161 if leafoptics and _LEAFOPTICS_FUNCTIONS_AVAILABLE:
162 helios_lib.destroyLeafOptics(leafoptics)
163
164
165def leafOpticsRun(leafoptics: ctypes.POINTER(ULeafOptics),
166 uuids: List[int],
167 properties: List[float],
168 label: str) -> None:
169 """Run LeafOptics model and assign spectra to primitives
170
171 Args:
172 leafoptics: Pointer to LeafOptics instance
173 uuids: List of primitive UUIDs
174 properties: List of 9 floats [numberlayers, brownpigments, chlorophyllcontent,
175 carotenoidcontent, anthocyancontent, watermass, drymass, protein, carbonconstituents]
176 label: Label for the spectra (appended to "leaf_reflectivity_" and "leaf_transmissivity_")
177 """
178 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
179 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
180 if not leafoptics:
181 raise ValueError("LeafOptics instance is None.")
182 if not uuids:
183 raise ValueError("UUIDs list cannot be empty.")
184 if len(properties) != LEAF_OPTICS_PROPERTIES_COUNT:
185 raise ValueError(f"Properties must have exactly {LEAF_OPTICS_PROPERTIES_COUNT} elements.")
186 if not label:
187 raise ValueError("Label cannot be empty.")
188
189 uuid_array = (ctypes.c_uint * len(uuids))(*uuids)
190 properties_array = (ctypes.c_float * LEAF_OPTICS_PROPERTIES_COUNT)(*properties)
191 label_bytes = label.encode('utf-8')
192
193 helios_lib.leafOpticsRun(leafoptics, uuid_array, len(uuids), properties_array, label_bytes)
194
195
196def leafOpticsRunNoUUIDs(leafoptics: ctypes.POINTER(ULeafOptics),
197 properties: List[float],
198 label: str) -> None:
199 """Run LeafOptics model without assigning to primitives
200
201 Args:
202 leafoptics: Pointer to LeafOptics instance
203 properties: List of 9 floats [numberlayers, brownpigments, chlorophyllcontent,
204 carotenoidcontent, anthocyancontent, watermass, drymass, protein, carbonconstituents]
205 label: Label for the spectra
206 """
207 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
208 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
209 if not leafoptics:
210 raise ValueError("LeafOptics instance is None.")
211 if len(properties) != LEAF_OPTICS_PROPERTIES_COUNT:
212 raise ValueError(f"Properties must have exactly {LEAF_OPTICS_PROPERTIES_COUNT} elements.")
213 if not label:
214 raise ValueError("Label cannot be empty.")
215
216 properties_array = (ctypes.c_float * LEAF_OPTICS_PROPERTIES_COUNT)(*properties)
217 label_bytes = label.encode('utf-8')
218
219 helios_lib.leafOpticsRunNoUUIDs(leafoptics, properties_array, label_bytes)
220
221
222def leafOpticsGetLeafSpectra(leafoptics: ctypes.POINTER(ULeafOptics),
223 properties: List[float]) -> Tuple[List[float], List[float], List[float]]:
224 """Get leaf reflectance and transmittance spectra
225
226 Args:
227 leafoptics: Pointer to LeafOptics instance
228 properties: List of 9 floats [numberlayers, brownpigments, chlorophyllcontent,
229 carotenoidcontent, anthocyancontent, watermass, drymass, protein, carbonconstituents]
230
231 Returns:
232 Tuple of (wavelengths, reflectivities, transmissivities) as lists of floats
233 - wavelengths: 400-2500 nm at 1nm resolution (2101 points)
234 - reflectivities: reflectance values at each wavelength
235 - transmissivities: transmittance values at each wavelength
236 """
237 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
238 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
239 if not leafoptics:
240 raise ValueError("LeafOptics instance is None.")
241 if len(properties) != LEAF_OPTICS_PROPERTIES_COUNT:
242 raise ValueError(f"Properties must have exactly {LEAF_OPTICS_PROPERTIES_COUNT} elements.")
244 properties_array = (ctypes.c_float * LEAF_OPTICS_PROPERTIES_COUNT)(*properties)
245 reflectivities = (ctypes.c_float * LEAF_OPTICS_SPECTRAL_POINTS)()
246 transmissivities = (ctypes.c_float * LEAF_OPTICS_SPECTRAL_POINTS)()
247 wavelengths = (ctypes.c_float * LEAF_OPTICS_SPECTRAL_POINTS)()
248 size = ctypes.c_uint()
249
250 helios_lib.leafOpticsGetLeafSpectra(
251 leafoptics, properties_array, reflectivities, transmissivities, wavelengths, ctypes.byref(size)
252 )
253
254 # Convert to Python lists
255 n = size.value
256 return (
257 list(wavelengths[:n]),
258 list(reflectivities[:n]),
259 list(transmissivities[:n])
260 )
261
262
263def leafOpticsSetProperties(leafoptics: ctypes.POINTER(ULeafOptics),
264 uuids: List[int],
265 properties: List[float]) -> None:
266 """Set leaf optical properties for primitives
267
268 Args:
269 leafoptics: Pointer to LeafOptics instance
270 uuids: List of primitive UUIDs
271 properties: List of 9 floats [numberlayers, brownpigments, chlorophyllcontent,
272 carotenoidcontent, anthocyancontent, watermass, drymass, protein, carbonconstituents]
273 """
274 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
275 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
276 if not leafoptics:
277 raise ValueError("LeafOptics instance is None.")
278 if not uuids:
279 raise ValueError("UUIDs list cannot be empty.")
280 if len(properties) != LEAF_OPTICS_PROPERTIES_COUNT:
281 raise ValueError(f"Properties must have exactly {LEAF_OPTICS_PROPERTIES_COUNT} elements.")
282
283 uuid_array = (ctypes.c_uint * len(uuids))(*uuids)
284 properties_array = (ctypes.c_float * LEAF_OPTICS_PROPERTIES_COUNT)(*properties)
285
286 helios_lib.leafOpticsSetProperties(leafoptics, uuid_array, len(uuids), properties_array)
287
288
289def leafOpticsGetPropertiesFromSpectrum(leafoptics: ctypes.POINTER(ULeafOptics),
290 uuids: List[int]) -> None:
291 """Get PROSPECT parameters from reflectivity spectrum for primitives
292
293 Args:
294 leafoptics: Pointer to LeafOptics instance
295 uuids: List of primitive UUIDs to query
296
297 Note:
298 Primitives without matching spectra are silently skipped
299 """
300 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
301 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
302 if not leafoptics:
303 raise ValueError("LeafOptics instance is None.")
304 if not uuids:
305 raise ValueError("UUIDs list cannot be empty.")
307 uuid_array = (ctypes.c_uint * len(uuids))(*uuids)
308
309 helios_lib.leafOpticsGetPropertiesFromSpectrum(leafoptics, uuid_array, len(uuids))
310
311
312def leafOpticsGetPropertiesFromSpectrumSingle(leafoptics: ctypes.POINTER(ULeafOptics),
313 uuid: int) -> None:
314 """Get PROSPECT parameters from reflectivity spectrum for a single primitive
315
316 Args:
317 leafoptics: Pointer to LeafOptics instance
318 uuid: Single primitive UUID to query
319
320 Note:
321 If no matching spectrum is found, the primitive is silently skipped
322 """
323 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
324 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
325 if not leafoptics:
326 raise ValueError("LeafOptics instance is None.")
327 if uuid < 0:
328 raise ValueError("UUID must be non-negative.")
330 helios_lib.leafOpticsGetPropertiesFromSpectrumSingle(leafoptics, ctypes.c_uint(uuid))
331
332
333def leafOpticsGetPropertiesFromLibrary(leafoptics: ctypes.POINTER(ULeafOptics),
334 species: str) -> List[float]:
335 """Get leaf optical properties from the built-in species library
336
337 Args:
338 leafoptics: Pointer to LeafOptics instance
339 species: Name of the species (case-insensitive). Available species:
340 "default", "garden_lettuce", "alfalfa", "corn", "sunflower",
341 "english_walnut", "rice", "soybean", "wine_grape", "tomato",
342 "common_bean", "cowpea"
343
344 Returns:
345 List of 9 floats [numberlayers, brownpigments, chlorophyllcontent,
346 carotenoidcontent, anthocyancontent, watermass, drymass, protein, carbonconstituents]
347
348 Note:
349 If species is not found, default properties are used and a warning is issued
350 """
351 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
352 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
353 if not leafoptics:
354 raise ValueError("LeafOptics instance is None.")
355 if not species:
356 raise ValueError("Species name cannot be empty.")
358 properties_array = (ctypes.c_float * LEAF_OPTICS_PROPERTIES_COUNT)()
359 species_bytes = species.encode('utf-8')
360
361 helios_lib.leafOpticsGetPropertiesFromLibrary(leafoptics, species_bytes, properties_array)
362
363 return list(properties_array)
364
365
366def leafOpticsDisableMessages(leafoptics: ctypes.POINTER(ULeafOptics)) -> None:
367 """Disable command-line output messages from LeafOptics"""
368 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
369 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
370 if not leafoptics:
371 raise ValueError("LeafOptics instance is None.")
372
373 helios_lib.leafOpticsDisableMessages(leafoptics)
374
376def leafOpticsEnableMessages(leafoptics: ctypes.POINTER(ULeafOptics)) -> None:
377 """Enable command-line output messages from LeafOptics"""
378 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
379 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
380 if not leafoptics:
381 raise ValueError("LeafOptics instance is None.")
382
383 helios_lib.leafOpticsEnableMessages(leafoptics)
384
386def leafOpticsOptionalOutputPrimitiveData(leafoptics: ctypes.POINTER(ULeafOptics), label: str) -> None:
387 """Selectively output primitive data for specific biochemical properties
388
389 By default, all biochemical properties are written as primitive data. Use this method
390 to select only needed properties for better performance.
391
392 Args:
393 leafoptics: LeafOptics instance pointer
394 label: Property label - one of: "chlorophyll", "carotenoid", "anthocyanin",
395 "brown", "water", "drymass", "protein", "cellulose"
396
397 Note:
398 Added in helios-core v1.3.59
399 """
400 if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
401 raise NotImplementedError("LeafOptics functions not available. Rebuild with leafoptics enabled.")
402 if not leafoptics:
403 raise ValueError("LeafOptics instance is None.")
404 if not label:
405 raise ValueError("Label cannot be empty.")
406
407 label_encoded = label.encode('utf-8')
408 helios_lib.leafOpticsOptionalOutputPrimitiveData(leafoptics, label_encoded)
409
410
411def is_leafoptics_available() -> bool:
412 """Check if LeafOptics functions are available in the current library"""
413 return _LEAFOPTICS_FUNCTIONS_AVAILABLE
414
415
416# Mock mode functions
417if not _LEAFOPTICS_FUNCTIONS_AVAILABLE:
418 def mock_create(*args, **kwargs):
419 raise RuntimeError(
420 "Mock mode: LeafOptics not available. "
421 "This would create a plugin instance with native library."
422 )
423
424 def mock_method(*args, **kwargs):
425 raise RuntimeError(
426 "Mock mode: LeafOptics method not available. "
427 "This would execute plugin computation with native library."
428 )
429
430 # Replace functions with mocks for development
431 createLeafOptics = mock_create
432 leafOpticsRun = mock_method
433 leafOpticsRunNoUUIDs = mock_method
434 leafOpticsGetLeafSpectra = mock_method
435 leafOpticsSetProperties = mock_method
436 leafOpticsGetPropertiesFromSpectrum = mock_method
437 leafOpticsGetPropertiesFromSpectrumSingle = mock_method
438 leafOpticsGetPropertiesFromLibrary = mock_method
Opaque structure for LeafOptics C++ class.
None leafOpticsOptionalOutputPrimitiveData(ctypes.POINTER(ULeafOptics) leafoptics, str label)
Selectively output primitive data for specific biochemical properties.
None destroyLeafOptics(ctypes.POINTER(ULeafOptics) leafoptics)
Destroy LeafOptics instance.
None leafOpticsEnableMessages(ctypes.POINTER(ULeafOptics) leafoptics)
Enable command-line output messages from LeafOptics.
None leafOpticsDisableMessages(ctypes.POINTER(ULeafOptics) leafoptics)
Disable command-line output messages from LeafOptics.
_check_error(result, func, args)
Automatic error checking for all leaf optics functions.
bool is_leafoptics_available()
Check if LeafOptics functions are available in the current library.