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