pyASDReader 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,940 @@
1
+ from __future__ import annotations
2
+
3
+ # !/usr/bin/env python
4
+ # -*- encoding: utf-8 -*-
5
+ '''
6
+ @File : ASD_File_Reader.py
7
+ @Time : 2024/11/19 03:52:34
8
+ @Author : Kai Cao
9
+ @Version : 1.0.0
10
+ @Contact : caokai_cgs@163.com
11
+ @License : (C)Copyright 2024-
12
+ Copyright Statement: Full Copyright
13
+ @Desc : According to "ASD File Format version 8: Revision B"
14
+ '''
15
+
16
+ import os
17
+ import struct
18
+ import re
19
+ import logging
20
+ import numpy as np
21
+ import xml.etree.ElementTree as ET
22
+ from datetime import datetime, timedelta
23
+ from collections import namedtuple
24
+ from enum import Enum
25
+ from .constant import (FileVersion_e, InstrumentType_e, InstrumentModel_e, SpectraType_e, SignatureState_e, AuditLogType_e, DataType_e, DataFormat_e, IT_ms_e, CalibrationType_e, SaturationError_e, ClassiferDataType_e)
26
+ from .logger_setup import setup_logging
27
+ from .file_attributes import FileAttributes
28
+
29
+ # Initialize module-level logger
30
+ logger = logging.getLogger(__name__)
31
+
32
+ class ASDFile(object):
33
+
34
+ DEFAULT_DERIVATIVE_GAP = 5
35
+
36
+ def __init__(self, filepath: str = None):
37
+ """Initialize ASDFile instance.
38
+
39
+ Args:
40
+ filepath: Optional path to ASD file. If provided, file will be read automatically.
41
+ """
42
+ self.asdFileVersion = 0
43
+ self.metadata = None
44
+ self.spectrumData = None
45
+ self.referenceFileHeader = None
46
+ self.referenceData = None
47
+ self.classifierData = None
48
+ self.dependants = None
49
+ self.calibrationHeader = None
50
+ self.calibrationSeriesABS = None
51
+ self.calibrationSeriesBSE = None
52
+ self.calibrationSeriesLMP = None
53
+ self.calibrationSeriesFO = None
54
+ self.auditLog = None
55
+ self.signature = None
56
+ self.__asdFileStream = None
57
+ self.wavelengths = None
58
+
59
+ # Auto-read file if filepath is provided
60
+ if filepath is not None:
61
+ self.read(filepath)
62
+
63
+ def read(self: object, filePath: str) -> bool:
64
+ readSuccess = False
65
+
66
+ # Check if filePath is valid
67
+ if filePath is None or not isinstance(filePath, (str, bytes, os.PathLike)):
68
+ logger.error(f"Invalid file path: {filePath}")
69
+ return False
70
+
71
+ # Check if file exists
72
+ if not (os.path.exists(filePath) and os.path.isfile(filePath)):
73
+ logger.error(f"File does not exist or is not a file: {filePath}")
74
+ return False
75
+
76
+ try:
77
+ # read in file to memory(buffer)
78
+ with open(filePath, 'rb') as fileHandle:
79
+ self.__asdFileStream = fileHandle.read()
80
+ if self.__asdFileStream[-3:] == b'\xFF\xFE\xFD':
81
+ self.__bom = self.__asdFileStream[-3:]
82
+ self.__asdFileStream = self.__asdFileStream[:-3]
83
+ except Exception as e:
84
+ logger.exception(f"Error in reading the file.\nError: {e}")
85
+ return False
86
+
87
+ # refering C# Line 884 to identify the file version
88
+ self.asdFileVersion, offset = self.__validate_fileVersion()
89
+
90
+ # Check if file version is valid
91
+ if self.asdFileVersion.value <= 0:
92
+ logger.error(f"Invalid ASD file version")
93
+ return False
94
+ if self.asdFileVersion.value > 0:
95
+ try:
96
+ offset = self.__parse_metadata(offset)
97
+ self.wavelengths = np.arange(self.metadata.channel1Wavelength, self.metadata.channel1Wavelength + self.metadata.channels * self.metadata.wavelengthStep, self.metadata.wavelengthStep)
98
+ except Exception as e:
99
+ logger.exception(f"Error in parsing the metadata.\nError: {e}")
100
+ else:
101
+ try:
102
+ offset = self.__parse_spectrumData(offset)
103
+ except Exception as e:
104
+ logger.exception(f"Error in parsing the metadata and spectrum data.\nError: {e}")
105
+ if self.asdFileVersion.value >= 2:
106
+ try:
107
+ offset = self.__parse_referenceFileHeader(offset)
108
+ except Exception as e:
109
+ logger.exception(f"Error in parsing the reference file header.\nError: {e}")
110
+ else:
111
+ try:
112
+ offset = self.__parse_referenceData(offset)
113
+ except Exception as e:
114
+ logger.exception(f"Error in parsing the reference data.\nError: {e}")
115
+ if self.asdFileVersion.value >= 6:
116
+ try:
117
+ # Read Classifier Data
118
+ offset = self.__parse_classifierData(offset)
119
+ except Exception as e:
120
+ logger.exception(f"Error in parsing the classifier data.\nError: {e}")
121
+ else:
122
+ try:
123
+ offset = self.__parse_dependentVariables(offset)
124
+ except Exception as e:
125
+ logger.exception(f"Error in parsing the depndant variables.\nError: {e}")
126
+ if self.asdFileVersion.value >= 7:
127
+ try:
128
+ # Read Calibration Header
129
+ offset = self.__parse_calibrationHeader(offset)
130
+ except Exception as e:
131
+ logger.exception(f"Error in parsing the calibration header.\nError: {e}")
132
+ else:
133
+ try:
134
+ if self.calibrationHeader and (self.calibrationHeader.calibrationNum > 0):
135
+ # Parsing the calibration data according to 'ASD File Format version 8: Revision B', through the suquence of 'Absolute Calibration Data', 'Base Calibration Data', 'Lamp Calibration Data', 'Fiber Optic Data' successively.
136
+ for hdr in self.calibrationHeader.calibrationSeries: # Number of calibrationSeries buffers in the file.
137
+ if hdr[0] == CalibrationType_e.cb_ABSOLUTE:
138
+ self.calibrationSeriesABS, _, _, offset = self.__parse_spectra(offset)
139
+ elif hdr[0] == CalibrationType_e.cb_BASE:
140
+ self.calibrationSeriesBSE, _, _, offset = self.__parse_spectra(offset)
141
+ elif hdr[0] == CalibrationType_e.cb_LAMP:
142
+ self.calibrationSeriesLMP, _, _, offset = self.__parse_spectra(offset)
143
+ elif hdr[0] == CalibrationType_e.cb_FIBER:
144
+ self.calibrationSeriesFO, _, _, offset = self.__parse_spectra(offset)
145
+ # else:
146
+ # logger.info(f"Calibration data is not available.")
147
+ except Exception as e:
148
+ logger.exception(f"Error in parsing the calibration data.\nError: {e}")
149
+ if self.asdFileVersion.value >= 8:
150
+ try:
151
+ # Read Audit Log
152
+ offset = self.__parse_auditLog(offset)
153
+ except Exception as e:
154
+ logger.exception(f"Error in parsing the audit log.\nError: {e}")
155
+ # Read Signature
156
+ else:
157
+ try:
158
+ offset = self.__parse_signature(offset)
159
+ except Exception as e:
160
+ logger.exception(f"Error in parsing the signature.\nError: {e}")
161
+ readSuccess = True
162
+ return readSuccess
163
+
164
+ def update(self, field_name: str, new_value):
165
+ pass
166
+
167
+ def write(self: object, file: str) -> bool:
168
+ pass
169
+
170
+ def __check_offset(func):
171
+ def wrapper(self: object, offset: int = None, *args, **kwargs):
172
+ # Check if offset is None or out of range
173
+ # TODO: add 0 <= offset < len(self.__asdFileStream) check
174
+ if isinstance(offset, int) and 0 <= offset:
175
+ if offset < len(self.__asdFileStream):
176
+ return func(self, offset, *args, **kwargs)
177
+ else:
178
+ logger.info("Reached the end of the binary byte stream. offset: {offset}")
179
+ return None, None
180
+ else:
181
+ logger.error(f"Invalid offset: {offset}. It should be a non-negative integer.")
182
+ return None, None
183
+ return wrapper
184
+
185
+ @__check_offset
186
+ def __parse_metadata(self: object, offset) -> int:
187
+
188
+ asdMetadataFormat = '<157s 18s B B b b l b l f f b b b b b H 128s 56s L h h H H f f f f h b 4b H H H b L H H H H f f 27s 5b'
189
+ asdMetadatainfo = namedtuple('metadata', "asdFileVersion comments when_datetime daylighSavingsFlag programVersion fileVersion iTime \
190
+ darkCorrected darkTime dataType referenceTime channel1Wavelength wavelengthStep dataFormat \
191
+ old_darkCurrentCount old_refCount old_sampleCount application channels appData gpsData \
192
+ intergrationTime_ms fo darkCurrentCorrention calibrationSeries instrumentNum yMin yMax xMin xMax \
193
+ ipNumBits xMode flags1 flags2 flags3 flags4 darkCurrentCount refCount sampleCount instrument \
194
+ calBulbID swir1Gain swir2Gain swir1Offset swir2Offset splice1_wavelength splice2_wavelength smartDetectorType \
195
+ spare1 spare2 spare3 spare4 spare5 byteStream byteStreamLength")
196
+ try:
197
+ comments, when, programVersion, fileVersion, iTime, darkCorrected, darkTime, \
198
+ dataType, referenceTime, channel1Wavelength, wavelengthStep, dataFormat, \
199
+ old_darkCurrentCount, old_refCount, old_sampleCount, \
200
+ application, channels, appData, gpsData, intergrationTime_ms, fo, darkCurrentCorrention, \
201
+ calibrationSeries, instrumentNum, yMin, yMax, xMin, xMax, ipNumBits, xMode, \
202
+ flags1, flags2, flags3, flags4, darkCurrentCount, refCount, \
203
+ sampleCount, instrument, calBulbID, swir1Gain, swir2Gain, swir1Offset, swir2Offset, \
204
+ splice1_wavelength, splice2_wavelength, smartDetectorType, \
205
+ spare1, spare2, spare3, spare4, spare5 = struct.unpack_from(asdMetadataFormat, self.__asdFileStream, offset)
206
+ asdFileVersion, _ = self.__validate_fileVersion()
207
+ comments = comments.strip(b'\x00') # remove null bytes
208
+ # Parse the time from the buffer, format is year, month, day, hour, minute, second
209
+ when_datetime, daylighSavingsFlag = self.__parse_ASDFilewhen((struct.unpack_from('9h', when))) # 9 short integers
210
+ programVersion = self.__parseVersion(programVersion)
211
+ fileVersion = self.__parseVersion(fileVersion)
212
+ darkCorrected = bool(darkCorrected)
213
+ darkTime = datetime.fromtimestamp(darkTime)
214
+ dataType = DataType_e(dataType)
215
+ referenceTime = datetime.fromtimestamp(referenceTime)
216
+ dataFormat = DataFormat_e(dataFormat)
217
+ intergrationTime = IT_ms_e(intergrationTime_ms)
218
+ calibrationSeries = CalibrationType_e(calibrationSeries)
219
+ flags2 = self.__parseSaturationError(flags2)
220
+ instrument = InstrumentType_e(instrument)
221
+ ByteStream = self.__asdFileStream[:484]
222
+ ByteStreamLength = len(ByteStream)
223
+ offset += struct.calcsize(asdMetadataFormat)
224
+ self.metadata = asdMetadatainfo._make(
225
+ (asdFileVersion, comments, when_datetime, daylighSavingsFlag, programVersion, fileVersion, iTime, darkCorrected, darkTime, \
226
+ dataType, referenceTime, channel1Wavelength, wavelengthStep, dataFormat, old_darkCurrentCount, old_refCount, old_sampleCount, \
227
+ application, channels, appData, gpsData, intergrationTime, fo, darkCurrentCorrention, calibrationSeries, instrumentNum, \
228
+ yMin, yMax, xMin, xMax, ipNumBits, xMode, flags1, flags2, flags3, flags4, darkCurrentCount, refCount, \
229
+ sampleCount, instrument, calBulbID, swir1Gain, swir2Gain, swir1Offset, swir2Offset, \
230
+ splice1_wavelength, splice2_wavelength, smartDetectorType, \
231
+ spare1, spare2, spare3, spare4, spare5 , ByteStream, ByteStreamLength))
232
+ except Exception as e:
233
+ logger.exception(f"Metadata (ASD File Header) parse error: {e}")
234
+ return None
235
+ # logger.info(f"Read: metadata end offset: {offset}")
236
+ return offset
237
+
238
+ @__check_offset
239
+ def __parse_spectrumData(self: object, offset: int) -> int:
240
+ try:
241
+ spectrumDataInfo = namedtuple('spectrumData', 'spectra byteStream byteStreamLength')
242
+ spectra, spectrumDataStream, spectrumDataStreamLength, offset = self.__parse_spectra(offset)
243
+ self.spectrumData = spectrumDataInfo._make((spectra, spectrumDataStream, spectrumDataStreamLength))
244
+ # logger.info(f"Read: spectrum data end offset: {offset}")
245
+ return offset
246
+ except Exception as e:
247
+ logger.exception(f"Spectrum Data parse error: {e}")
248
+ return None
249
+
250
+ @__check_offset
251
+ def __parse_referenceFileHeader(self: object, offset: int) -> int:
252
+ initOffset = offset
253
+ asdReferenceFormat = 'd d'
254
+ asdreferenceFileHeaderInfo = namedtuple('referenceFileHeader', "referenceFlag referenceTime spectrumTime referenceDescription byteStream byteStreamLength")
255
+ try:
256
+ referenceFlag, offset = self.__parse_Bool(offset)
257
+ referenceTime_doublefloat, spectrumTime_doublefloat = struct.unpack_from(asdReferenceFormat, self.__asdFileStream, offset)
258
+ referenceTime_datetime = self.__parseTimeOLE(referenceTime_doublefloat) # Convert to datetime
259
+ spectrumTime_datetime = self.__parseTimeOLE(spectrumTime_doublefloat) # Convert to datetime
260
+ offset += struct.calcsize(asdReferenceFormat)
261
+ referenceDescription, offset = self.__parse_bstr(offset)
262
+ byteStream = self.__asdFileStream[initOffset:offset]
263
+ byteStreamLength = len(byteStream)
264
+ self.referenceFileHeader = asdreferenceFileHeaderInfo._make((referenceFlag, referenceTime_datetime, spectrumTime_datetime, referenceDescription, byteStream, byteStreamLength))
265
+ # logger.info(f"Read: reference file header end offset: {offset}")
266
+ return offset
267
+ except Exception as e:
268
+ logger.exception(f"Reference File Header parse error: {e}")
269
+ return None
270
+
271
+ @__check_offset
272
+ def __parse_referenceData(self: object, offset: int) -> int:
273
+ try:
274
+ referenceDataInfo = namedtuple('referenceData', 'spectra byteStream byteStreamLength')
275
+ spectra, referenceDataStream, referenceDataStreamLength, offset = self.__parse_spectra(offset)
276
+ self.referenceData = referenceDataInfo._make((spectra, referenceDataStream, referenceDataStreamLength))
277
+ # logger.info(f"Read: reference data end offset: {offset}")
278
+ return offset
279
+ except Exception as e:
280
+ logger.exception(f"Reference Data parse error: {e}")
281
+ return None
282
+
283
+ @__check_offset
284
+ def __parse_classifierData(self: object, offset: int) -> int:
285
+ try:
286
+ initOffset = offset
287
+ yCode, yModelType = struct.unpack_from('bb', self.__asdFileStream, offset)
288
+ offset += struct.calcsize('bb')
289
+ title_str, offset = self.__parse_bstr(offset)
290
+ subtitle_str, offset = self.__parse_bstr(offset)
291
+ productName_str, offset = self.__parse_bstr(offset)
292
+ vendor_str, offset = self.__parse_bstr(offset)
293
+ lotNumber_str, offset = self.__parse_bstr(offset)
294
+ sample__str, offset = self.__parse_bstr(offset)
295
+ modelName_str, offset = self.__parse_bstr(offset)
296
+ operator_str, offset = self.__parse_bstr(offset)
297
+ dateTime_str, offset = self.__parse_bstr(offset)
298
+ instrument_str, offset = self.__parse_bstr(offset)
299
+ serialNumber_str, offset = self.__parse_bstr(offset)
300
+ displayMode_str, offset = self.__parse_bstr(offset)
301
+ comments_str, offset = self.__parse_bstr(offset)
302
+ units_str, offset = self.__parse_bstr(offset)
303
+ filename_str, offset = self.__parse_bstr(offset)
304
+ username_str, offset = self.__parse_bstr(offset)
305
+ reserved1_str, offset = self.__parse_bstr(offset)
306
+ reserved2_str, offset = self.__parse_bstr(offset)
307
+ reserved3_str, offset = self.__parse_bstr(offset)
308
+ reserved4_str, offset = self.__parse_bstr(offset)
309
+ constituantCount_int, = struct.unpack_from('H', self.__asdFileStream, offset)
310
+ offset += struct.calcsize('H')
311
+ asdClassifierDataInfo = namedtuple('classifierData', 'yCode yModelType title subtitle productName vendor lotNumber sample modelName operator dateTime instrument serialNumber displayMode comments units filename username reserved1 reserved2 reserved3 reserved4 constituantCount constituantItems byteStream byteStreamLength')
312
+ # Past the constituants
313
+ if constituantCount_int > 0:
314
+ offset += 10
315
+ # logger.info(f"constituant items ")
316
+ constituantItems = []
317
+ for i in range(constituantCount_int):
318
+ # logger.info(f"constituant items sequence: {i}")
319
+ item, offset = self.__parse_constituantType(offset)
320
+ constituantItems.append(item)
321
+ if constituantCount_int == 0:
322
+ constituantItems = []
323
+ offset += 2
324
+ byteStream = self.__asdFileStream[initOffset:offset]
325
+ byteStreamLength = len(byteStream)
326
+ self.classifierData = asdClassifierDataInfo._make((yCode, yModelType, title_str, subtitle_str, productName_str, vendor_str, lotNumber_str, sample__str, modelName_str, operator_str, dateTime_str, instrument_str, serialNumber_str, displayMode_str, comments_str, units_str, filename_str, username_str, reserved1_str, reserved2_str, reserved3_str, reserved4_str, constituantCount_int, constituantItems, byteStream, byteStreamLength))
327
+ # logger.info(f"Read: classifier Data end offset: {offset}")
328
+ return offset
329
+ except Exception as e:
330
+ logger.exception(f"classifier Data parse error: {e}")
331
+ return None
332
+
333
+ @__check_offset
334
+ def __parse_dependentVariables(self: object, offset: int) -> int:
335
+ try:
336
+ initOffset = offset
337
+ dependantInfo = namedtuple('dependants', 'saveDependentVariables dependentVariableCount dependentVariableLabels dependentVariableValue byteStream byteStreamLength')
338
+ saveDependentVariables, offset = self.__parse_Bool(offset)
339
+ dependant_format = 'h'
340
+ dependentVariableCount, = struct.unpack_from(dependant_format, self.__asdFileStream, offset)
341
+ offset += struct.calcsize(dependant_format)
342
+ if dependentVariableCount > 0:
343
+ offset += 10
344
+ dependantVariableLabels_list = []
345
+ for i in range(dependentVariableCount):
346
+ dependentVariableLabel, offset = self.__parse_bstr(offset)
347
+ dependantVariableLabels_list.append(dependentVariableLabel)
348
+ offset += 10
349
+ dependantVariableValues_list = []
350
+ for i in range(dependentVariableCount):
351
+ dependentVariableValue, = struct.unpack_from('<f', self.__asdFileStream, offset)
352
+ dependantVariableValues_list.append(dependentVariableValue)
353
+ offset += struct.calcsize('<f')
354
+ self.dependants = dependantInfo._make((saveDependentVariables, dependentVariableCount, dependantVariableLabels_list, dependantVariableValues_list, self.__asdFileStream[initOffset:offset], len(self.__asdFileStream[initOffset:offset])))
355
+ # if there are no dependent variables, skip 4 bytes (corresponding to 4 empty byte positions b'\x00')
356
+ if dependentVariableCount == 0:
357
+ offset += 4
358
+ dependantVariableLabels_list = []
359
+ dependantVariableValues_list = []
360
+ self.dependants = dependantInfo._make((saveDependentVariables, dependentVariableCount, dependantVariableLabels_list, dependantVariableValues_list, self.__asdFileStream[initOffset:offset], len(self.__asdFileStream[initOffset:offset])))
361
+ # logger.info(f"Read: dependant variables end offset: {offset}")
362
+ return offset
363
+ except Exception as e:
364
+ logger.exception(f"Dependant variables parse error: {e}")
365
+ return None
366
+
367
+ @__check_offset
368
+ def __parse_calibrationHeader(self: object, offset: int) -> int:
369
+ try:
370
+ calibrationHeaderCountNum_format = 'b'
371
+ calibrationSeries_buffer_format = '<b 20s i h h'
372
+ calibrationHeaderInfo = namedtuple('calibrationHeader', 'calibrationNum calibrationSeries, byteStream byteStreamLength')
373
+ calibrationHeaderCount, = struct.unpack_from(calibrationHeaderCountNum_format, self.__asdFileStream, offset)
374
+ byteStream = self.__asdFileStream[offset:offset + struct.calcsize(calibrationHeaderCountNum_format) + struct.calcsize(calibrationSeries_buffer_format)*calibrationHeaderCount]
375
+ byteStreamLength = len(byteStream)
376
+ offset += struct.calcsize(calibrationHeaderCountNum_format)
377
+ if calibrationHeaderCount > 0:
378
+ calibrationSeries = []
379
+ for i in range(calibrationHeaderCount):
380
+ (cbtype, cbname, cbIntergrationTime_ms, cbSwir1Gain, cbWwir2Gain) = struct.unpack_from(calibrationSeries_buffer_format, self.__asdFileStream, offset)
381
+ cbtype_e = CalibrationType_e(cbtype)
382
+ name = cbname.strip(b'\x00')
383
+ cbIntergrationTime = IT_ms_e(cbIntergrationTime_ms)
384
+ calibrationSeries.append(((cbtype_e, name, cbIntergrationTime, cbSwir1Gain, cbWwir2Gain)))
385
+ offset += struct.calcsize(calibrationSeries_buffer_format)
386
+ self.calibrationHeader = calibrationHeaderInfo._make((calibrationHeaderCount, calibrationSeries, byteStream, byteStreamLength))
387
+ else:
388
+ calibrationSeries = []
389
+ self.calibrationHeader = calibrationHeaderInfo._make((calibrationHeaderCount, calibrationSeries, byteStream, byteStreamLength))
390
+ # logger.info(f"Read: calibration header end offset: {offset}")
391
+ return offset
392
+ except Exception as e:
393
+ logger.exception(f"Calibration Header parse error: {e}")
394
+ return None
395
+
396
+ @__check_offset
397
+ def __parse_auditLog(self: object, offset: int) -> int:
398
+ try:
399
+ initOffset = offset
400
+ auditLogInfo = namedtuple('auditLog', 'auditCount auditEvents byteStream byteStreamLength')
401
+ additCount, = struct.unpack_from('l', self.__asdFileStream, offset)
402
+ offset += struct.calcsize('l')
403
+ if additCount > 0:
404
+ offset += 10
405
+ auditEvents, auditEventsLength = self.__parse_auditEvents(offset)
406
+ offset += auditEventsLength
407
+ self.auditLog = auditLogInfo._make((additCount, auditEvents, self.__asdFileStream[initOffset:offset], len(self.__asdFileStream[initOffset:offset])))
408
+ # logger.info(f"Read: audit log header end offset: {offset}")
409
+ return offset
410
+ except Exception as e:
411
+ logger.exception(f"Audit Log Header parse error: {e}")
412
+ return None
413
+
414
+ @__check_offset
415
+ def __parse_signature(self: object, offset: int) -> int:
416
+ try:
417
+ initOffset = offset
418
+ signatureInfo = namedtuple('signature', 'signed, signatureTime, userDomain, userLogin, userName, source, reason, notes, publicKey, signature, byteStream, byteStreamLength')
419
+ signed_int, = struct.unpack_from('b', self.__asdFileStream, offset)
420
+ # 0 – Unsigned
421
+ # 1 - Signed
422
+ signed_map = {0: SignatureState_e.UN_SIGNED, 1: SignatureState_e.SIGNED}
423
+ if signed_int not in signed_map:
424
+ signed = SignatureState_e.SIGNED_INVALID
425
+ # set the file version based on the version string
426
+ else:
427
+ signed = signed_map[signed_int]
428
+ offset += struct.calcsize('b')
429
+ signatureTime_int, = struct.unpack_from('q', self.__asdFileStream, offset)
430
+ #! The timestamp is to be parsed
431
+ signatureTime = signatureTime_int
432
+ # start_date = datetime(1, 1, 1)
433
+ # signatureTime = start_date + timedelta(seconds=signatureTime_int // 10000000)
434
+ # signatureTime = datetime.fromtimestamp(signatureTime_int, tz=) # Convert to datetime
435
+ offset += struct.calcsize('q')
436
+ userDomain, offset = self.__parse_bstr(offset)
437
+ userLogin, offset = self.__parse_bstr(offset)
438
+ userName, offset = self.__parse_bstr(offset)
439
+ source, offset = self.__parse_bstr(offset)
440
+ reason, offset = self.__parse_bstr(offset)
441
+ notes, offset = self.__parse_bstr(offset)
442
+ publicKey, offset = self.__parse_bstr(offset)
443
+ # signature, offset = self.__parse_bstr(offset)
444
+ signature, = struct.unpack_from('128s', self.__asdFileStream, offset)
445
+ offset += struct.calcsize('128s')
446
+ byteStream = self.__asdFileStream[initOffset:offset]
447
+ byteStreamLength = len(byteStream)
448
+ self.signature = signatureInfo._make((signed, signatureTime, userDomain, userLogin, userName, source, reason, notes, publicKey, signature, byteStream, byteStreamLength))
449
+ # logger.info(f"Read: signature end offset: {offset}")
450
+ except Exception as e:
451
+ logger.exception(f"Signature parse error: {e}")
452
+ return None
453
+ return offset
454
+
455
+ @__check_offset
456
+ def __parse_spectra(self: object, offset: int) -> tuple[np.array, bytes, int, int]:
457
+ try:
458
+ spectra = np.array(struct.unpack_from('<{}d'.format(self.metadata.channels), self.__asdFileStream, offset))
459
+ offset += (self.metadata.channels * 8)
460
+ spectrumDataStream = self.__asdFileStream[offset:offset + self.metadata.channels * 8]
461
+ spectrumDataStreamLength = len(spectrumDataStream)
462
+ return spectra, spectrumDataStream, spectrumDataStreamLength, offset
463
+ except Exception as e:
464
+ logger.exception(f"Spectrum data parse error: {e}")
465
+ return None, None, None, None
466
+
467
+ @__check_offset
468
+ def __parse_constituantType(self: object, offset: int) -> tuple[tuple, int]:
469
+ try:
470
+ constituentName, offset = self.__parse_bstr(offset)
471
+ passFail, offset = self.__parse_bstr(offset)
472
+ fmt = '<d d d d d d d d d l d d'
473
+ mDistance, mDistanceLimit, concentration, concentrationLimit, fRatio, residual, residualLimit, scores, scoresLimit, modelType, reserved1, reserved2 = struct.unpack_from(fmt, self.__asdFileStream, offset)
474
+ merterialReportInfo = namedtuple('itemsInMeterialReport', 'constituentName passFail mDistance mDistanceLimit concentration concentrationLimit fRatio residual residualLimit scores scoresLimit modelType reserved1 reserved2')
475
+ itemsInMeterialReport = merterialReportInfo._make((constituentName, passFail, mDistance, mDistanceLimit, concentration, concentrationLimit, fRatio, residual, residualLimit, scores, scoresLimit, modelType, reserved1, reserved2))
476
+ offset += struct.calcsize(fmt)
477
+ # logger.info(f"Read: constituant type end offset: {offset}")
478
+ return itemsInMeterialReport, offset
479
+ except Exception as e:
480
+ logger.exception(f"Constituant Type parse error {e}")
481
+ return None, None
482
+
483
+ @__check_offset
484
+ def __parse_bstr(self: object, offset: int) -> tuple[str, int]:
485
+ try:
486
+ size, = struct.unpack_from('<h', self.__asdFileStream, offset)
487
+ offset += struct.calcsize('<h')
488
+ bstr_format = '<{}s'.format(size)
489
+ str = ''
490
+ if size >= 0:
491
+ bstr, = struct.unpack_from(bstr_format, self.__asdFileStream, offset)
492
+ str = bstr.decode('utf-8')
493
+ offset += struct.calcsize(bstr_format)
494
+ return str, offset
495
+ except struct.error as err:
496
+ logger.exception(f"Byte string parse error: {err}")
497
+ return None, None
498
+
499
+ @__check_offset
500
+ def __parse_Bool(self: object, offset: int) -> tuple[bool, int]:
501
+ try:
502
+ buffer = self.__asdFileStream[offset:offset + 2]
503
+ if buffer == b'\xFF\xFF':
504
+ return True, offset + 2
505
+ elif buffer == b'\x00\x00':
506
+ return False, offset + 2
507
+ else:
508
+ raise ValueError("Invalid Boolean value")
509
+ except Exception as e:
510
+ return None, None
511
+
512
+ @__check_offset
513
+ def __parse_auditEvents(self: object, offset: int) -> tuple[list, int]:
514
+ try:
515
+ auditEvents_str = self.__asdFileStream[offset:].decode('utf-8', errors='ignore')
516
+ auditPattern = re.compile(r'<Audit_Event>(.*?)</Audit_Event>', re.DOTALL)
517
+ auditEvents = auditPattern.findall(auditEvents_str)
518
+ auditEvents_list = []
519
+ auditEventLength = 0
520
+ for auditEvent in auditEvents:
521
+ auditEvent = "<Audit_Event>" + auditEvent + "</Audit_Event>"
522
+ auditEventLength += len(auditEvent.encode('utf-8')) + 2
523
+ auditEvents_list.append(auditEvent)
524
+ auditEventsTuple_list = []
525
+ for auditEvent in auditEvents_list:
526
+ auditEventtuple = self.__parse_auditLogEvent(auditEvent)
527
+ auditEventsTuple_list.append(auditEventtuple)
528
+ return auditEventsTuple_list, auditEventLength
529
+ except Exception as e:
530
+ logger.exception(f"Audit Event parse error: {e}")
531
+ return None, None
532
+
533
+ def __parse_auditLogEvent(self: object, event: str) -> tuple:
534
+ try:
535
+ auditInfo = namedtuple('event', 'application appVersion name login time source function notes')
536
+ # Security note: xml.etree.ElementTree in Python 3.8+ has XXE protection by default
537
+ # External entities and DTD processing are disabled automatically
538
+ root = ET.fromstring(event)
539
+ application = root.find('Audit_Application').text
540
+ appVersion = root.find('Audit_AppVersion').text
541
+ name = root.find('Audit_Name').text
542
+ login = root.find('Audit_Login').text
543
+ time = root.find('Audit_Time').text
544
+ source = root.find('Audit_Source').text
545
+ function = root.find('Audit_Function').text
546
+ notes = root.find('Audit_Notes').text
547
+ auditEvents = auditInfo._make((application, appVersion, name, login, time, source, function, notes))
548
+ return auditEvents
549
+ except Exception as e:
550
+ logger.exception(f"Audit Log Data parse error: {e}")
551
+ return None
552
+
553
+ def __validate_fileVersion(self: object) -> int:
554
+ try:
555
+ # read the file version from the first 3 bytes of the file
556
+ version_data = self.__asdFileStream[:3]
557
+ version_map = {b'ASD': FileVersion_e.FILE_VERSION_1, b'as2': FileVersion_e.FILE_VERSION_2, b'as3': FileVersion_e.FILE_VERSION_3, b'as4': FileVersion_e.FILE_VERSION_4, b'as5': FileVersion_e.FILE_VERSION_5, b'as6': FileVersion_e.FILE_VERSION_6, b'as7': FileVersion_e.FILE_VERSION_7, b'as8': FileVersion_e.FILE_VERSION_8}
558
+ if version_data not in version_map:
559
+ fileversion = FileVersion_e.FILE_VERSION_INVALID
560
+ # set the file version based on the version string
561
+ else:
562
+ fileversion = version_map[version_data]
563
+ # logger.info(f"File Version: {fileversion}")
564
+ return fileversion, 3
565
+ except Exception as e:
566
+ logger.exception(f"File Version Validation Error:\n{e}")
567
+ return FileVersion_e.FILE_VERSION_INVALID, 3
568
+
569
+ def __parseVersion(self, version: int) -> str:
570
+ major = (version & 0xF0) >> 4
571
+ minor = version & 0x0F
572
+ return f"{major}.{minor}"
573
+
574
+ # Parse the storage time through 9 short integers and store it as a datetime type
575
+ def __parse_ASDFilewhen(self: object, when: bytes) -> tuple:
576
+ seconds = when[0] # seconds [0,61]
577
+ minutes = when[1] # minutes [0,59]
578
+ hour = when[2] # hour [0,23]
579
+ day = when[3] # day of the month [1,31]
580
+ month = when[4] # month of year [0,11]
581
+ year = when[5] # years since 1900
582
+ weekDay = when[6] # day of week [0,6] (Sunday = 0)
583
+ daysInYear = when[7] # day of year [0,365]
584
+ daylighSavingsFlag = when[8] # daylight savings flag
585
+ if year < 1900:
586
+ year = year + 1900
587
+ date_datetime = datetime(year, month + 1, day, hour, minutes, seconds)
588
+ return date_datetime, daylighSavingsFlag
589
+
590
+ def __parse_gps(self: object, gps_field: bytes) -> tuple:
591
+ # Domumentation: ASD File Format Version 8, page 4
592
+ gpsDataInfo = namedtuple('gpsdata', 'trueHeading, speed, latitude, longitude, altitude, lock, hardwareMode, ss, mm, hh, flags1, flags2, satellites, filler1, filler2')
593
+ try:
594
+ gpsDatadFormat = '<d d d d d h b b b b b h 5s b b'
595
+ trueHeading, speed, latitude, longitude, altitude, lock, hardwareMode, ss, mm, hh, flags1, flags2, satellites, filler1, filler2 = struct.unpack(gpsDatadFormat, gps_field)
596
+ gpsData = gpsDataInfo._make((trueHeading, speed, latitude, longitude, altitude, lock, hardwareMode, ss, mm, hh, flags1, flags2, satellites, filler1, filler2))
597
+ return gpsData
598
+ except Exception as e:
599
+ logger.exception(f"GPS parse error: {e}")
600
+ return None
601
+
602
+ def __parse_SmartDetector(self: object, smartDetectorData: bytes) -> tuple:
603
+ try:
604
+ smartDetectorFormat = '<i f f f h b f f'
605
+ smartDetectorInfo = namedtuple('smartDetector', 'serialNumber signal dark ref status avg humid temp')
606
+ serialNumber, signal, dark, ref, status, avg, humid, temp = struct.unpack(smartDetectorFormat, smartDetectorData)
607
+ smartDetector = smartDetectorInfo._make((serialNumber, signal, dark, ref, status, avg, humid, temp))
608
+ return smartDetector
609
+ except Exception as e:
610
+ logger.exception(f"Smart Detector parse error: {e}")
611
+ return None
612
+
613
+ def __parseSaturationError(self, flags2: int) -> list:
614
+ errors = []
615
+ if flags2 & 0x01:
616
+ errors.append(SaturationError_e.VNIR_SATURATION)
617
+ if flags2 & 0x02:
618
+ errors.append(SaturationError_e.SWIR1_SATURATION)
619
+ if flags2 & 0x04:
620
+ errors.append(SaturationError_e.SWIR2_SATURATION)
621
+ if flags2 & 0x08:
622
+ errors.append(SaturationError_e.SWIR1_TEC_ALARM)
623
+ if flags2 & 0x10: # Fixed: was 0x16 (22), should be 0x10 (16)
624
+ errors.append(SaturationError_e.SWIR2_TEC_ALARM)
625
+ return errors
626
+
627
+ def __parseTimeOLE(self: object, timeole: float) -> datetime:
628
+ try:
629
+ ole_base_date = datetime(1899, 12, 30)
630
+ days = int(timeole)
631
+ fraction = timeole - days
632
+ total_hours = fraction * 24
633
+ hours = int(total_hours)
634
+ minutes = int((total_hours - hours) * 60)
635
+ seconds = int(((total_hours - hours) * 60 - minutes) * 60)
636
+ microseconds = int((((total_hours - hours) * 60 - minutes) * 60 - seconds) * 1000000)
637
+ time_delta = timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds)
638
+ result_datetime = ole_base_date + time_delta
639
+ return result_datetime
640
+ except Exception as e:
641
+ logger.exception(f"OLE time parse error: {e}")
642
+ return None
643
+
644
+ #! Need to check the result of the function
645
+ @property
646
+ def digitalNumber(self):
647
+ return self.spectrumData.spectra if self.spectrumData is not None else None
648
+
649
+ @property
650
+ def whiteReference(self):
651
+ if self.referenceData is not None:
652
+ return self.__normalise_spectrum(self.referenceData.spectra)
653
+ else:
654
+ return None
655
+
656
+ @property
657
+ def reflectance(self):
658
+ if self.metadata.asdFileVersion.value >= 2:
659
+ try:
660
+ if self.metadata.referenceTime and self.metadata.dataType == DataType_e.dt_REF_TYPE:
661
+ reflectance = np.divide(self.__normalise_spectrum(self.spectrumData.spectra), self.__normalise_spectrum(self.referenceData.spectra), where=self.__normalise_spectrum(self.referenceData.spectra) != 0)
662
+ return reflectance
663
+ else:
664
+ return None
665
+ except Exception as e:
666
+ logger.info(f"Reflectance calculation error: {e}")
667
+ return None
668
+ else:
669
+ logger.info("Reflectance calculation error: Unsupported file version")
670
+ return None
671
+
672
+ @property
673
+ def reflectanceNoDeriv(self):
674
+ return self.reflectance
675
+
676
+ @property
677
+ def reflectance1stDeriv(self):
678
+ if self.reflectance is not None:
679
+ return self.__derivative(self.reflectance)
680
+ else:
681
+ return None
682
+
683
+ @property
684
+ def reflectance2ndDeriv(self):
685
+ if self.reflectance1stDeriv is not None:
686
+ return self.__derivative(self.reflectance1stDeriv)
687
+ else:
688
+ return None
689
+
690
+ #* Need to check the result of the function, not available in the SpecView
691
+ # 3rd Derivative
692
+ @property
693
+ def reflectance3rdDeriv(self):
694
+ if self.reflectance2ndDeriv is not None:
695
+ return self.__derivative(self.reflectance2ndDeriv)
696
+ else:
697
+ return None
698
+
699
+ #! Need to check the result of the function
700
+ # Reflectance (Transmittance)
701
+ @property
702
+ def transmitance(self):
703
+ pass
704
+
705
+ def __normalise_spectrum(self: object, spectrum) -> np.array:
706
+ # normalise the spectrum data, for VNIR and SWIR1, SWIR2, the data is normalised based on the integration time and gain
707
+ if spectrum is not None:
708
+ spectra = np.array(spectrum)
709
+ splice1_index = int(self.metadata.splice1_wavelength)
710
+ splice2_index = int(self.metadata.splice2_wavelength)
711
+ spectra[:splice1_index] = spectra[:splice1_index] / self.metadata.intergrationTime_ms.value
712
+ #
713
+ spectra[splice1_index:splice2_index] = spectra[splice1_index:splice2_index] * self.metadata.swir1Gain / 2048
714
+ spectra[splice2_index:] = spectra[splice2_index:] * self.metadata.swir2Gain / 2048
715
+ return spectra
716
+ else:
717
+ return None
718
+
719
+ def __derivative(self, data: np.array) -> np.array:
720
+ derivative = np.zeros_like(data)
721
+ D1 = ASDFile.DEFAULT_DERIVATIVE_GAP // 2
722
+ D2 = ASDFile.DEFAULT_DERIVATIVE_GAP - 1
723
+ derivative[D1:-D1] = (data[D1*2:] - data[:-D1*2]) / D2
724
+ # for i in range(D1, len(data) - D1):
725
+ # derivative[i] = (data[i + D1] - data[i - D1]) / D2
726
+ return derivative
727
+
728
+ @property
729
+ def absoluteReflectance(self):
730
+ if self.calibrationSeriesABS is not None:
731
+ return np.multiply(self.reflectance, self.calibrationSeriesABS)
732
+ else:
733
+ return None
734
+
735
+ @property
736
+ def log1R(self):
737
+ if self.reflectance is not None:
738
+ return np.log(1/self.reflectance)/np.log(10)
739
+ else:
740
+ return None
741
+
742
+ #! Need to check the result of the function
743
+ @property
744
+ def log1T(self):
745
+ pass
746
+
747
+ @property
748
+ def log1RNoDeriv(self):
749
+ if self.log1R is not None:
750
+ return self.log1R
751
+ else:
752
+ return None
753
+
754
+ @property
755
+ def log1R1stDeriv(self):
756
+ if self.log1R is not None:
757
+ return self.__derivative(self.log1R)
758
+ else:
759
+ return None
760
+
761
+ @property
762
+ def log1R2ndDeriv(self):
763
+ if self.log1R1stDeriv is not None:
764
+ return self.__derivative(self.log1R1stDeriv)
765
+ else:
766
+ return None
767
+
768
+
769
+ #! Need to check the result of the function
770
+ @property
771
+ def radiance(self, pcc: bool = False):
772
+ if self.calibrationHeader is not None:
773
+ if self.calibrationHeader.calibrationNum >= 3 and (all(x is not None for x in [self.calibrationSeriesABS, self.calibrationSeriesLMP, self.calibrationSeriesBSE]) or all(x is not None for x in [self.calibrationSeriesBSE, self.calibrationSeriesLMP, self.calibrationSeriesFO])):
774
+ for i in range(self.calibrationHeader.calibrationNum):
775
+ if self.calibrationHeader.calibrationSeries[i][0] == CalibrationType_e.cb_FIBER:
776
+ responseCal_info = namedtuple('responseCal', 'cbIT cbS1Gain cbS2Gain')
777
+ cbIT, cbS1Gain, cbS2Gain = self.calibrationHeader.calibrationSeries[i][2:5]
778
+ responseCal = responseCal_info._make((cbIT, cbS1Gain, cbS2Gain))
779
+ if self.metadata.fo >= 180:
780
+ radiance = self.__calc_irradiance(responseCal)
781
+ else:
782
+ radiance = self.__calc_radiance(responseCal)
783
+ if pcc == True:
784
+ radiance = self.__parabolic_correction(radiance)
785
+ else:
786
+ logger.info("Radiance calculation error: Invalid calibration header data")
787
+ return None
788
+ return radiance
789
+ else:
790
+ logger.info("Radiance calculation error: Invalid calibration header data")
791
+ return None
792
+
793
+ def __calc_radiance(self, response_cal) -> np.ndarray:
794
+ radiance = self.__calc_irradiance(response_cal)
795
+ radiance *= (self.calibrationSeriesBSE / np.pi)
796
+ return radiance
797
+
798
+ def __calc_irradiance(self, responseCal) -> np.ndarray:
799
+ DEFAULT_GAIN = 2048.0
800
+ radiance = np.zeros(self.metadata.channels)
801
+ dVnirConstant = 0.0
802
+ dSwir1Constant = 0.0
803
+ dSwir2Constant = 0.0
804
+ dSplice1 = 0.0
805
+ dSplice2 = 0.0
806
+ # Determine the last wavelength
807
+ dLastWavelength = self.metadata.channel1Wavelength + (self.metadata.channels - 1) * self.metadata.wavelengthStep
808
+ # Set the Splice Points
809
+ instrument = self.metadata.instrument
810
+ if instrument in [InstrumentType_e.UNKNOWN_INSTRUMENT, InstrumentType_e.PSII_INSTRUMENT, InstrumentType_e.LSVNIR_INSTRUMENT, InstrumentType_e.FSVNIR_INSTRUMENT, InstrumentType_e.HAND_HELD_INSTRUMENT]:
811
+ dSplice1 = dLastWavelength
812
+ dSplice2 = dLastWavelength
813
+ elif instrument == InstrumentType_e.FSFR_INSTRUMENT:
814
+ dSplice1 = self.metadata.splice1_wavelength
815
+ dSplice2 = self.metadata.splice2_wavelength
816
+ elif instrument == InstrumentType_e.FSNIR_INSTRUMENT:
817
+ dSplice1 = self.metadata.channel1Wavelength
818
+ dSplice2 = self.metadata.splice2_wavelength
819
+ # Set the Starting Wavelength
820
+ dWavelength = self.metadata.channel1Wavelength
821
+ i = 0
822
+ if instrument != InstrumentType_e.FSNIR_INSTRUMENT:
823
+ # VNIR
824
+ dVnirConstant = float(responseCal.cbIT.value) / float(self.metadata.intergrationTime_ms.value)
825
+ while dWavelength <= dSplice1 and i < self.metadata.channels:
826
+ if self.calibrationSeriesFO[i] != 0:
827
+ radiance[i] = float(self.calibrationSeriesLMP[i]) * (float(self.spectrumData.spectra[i]) / float(self.calibrationSeriesFO[i])) * dVnirConstant
828
+ else:
829
+ radiance[i] = 0
830
+ i += 1
831
+ dWavelength += self.metadata.wavelengthStep
832
+ if instrument in [InstrumentType_e.FSFR_INSTRUMENT, InstrumentType_e.FSNIR_INSTRUMENT]:
833
+ # SWiR1
834
+ dSwir1Constant = ((DEFAULT_GAIN / responseCal.cbS1Gain) / (DEFAULT_GAIN / self.metadata.swir1Gain))
835
+ while dWavelength <= dSplice2 and i < self.metadata.channels:
836
+ if self.calibrationSeriesFO[i] != 0:
837
+ radiance[i] = self.calibrationSeriesLMP[i] * (self.spectrumData.spectra[i] / self.calibrationSeriesFO[i]) * dSwir1Constant
838
+ else:
839
+ radiance[i] = 0
840
+ i += 1
841
+ dWavelength += self.metadata.wavelengthStep
842
+ # SWiR2
843
+ dSwir2Constant = ((DEFAULT_GAIN / responseCal.cbS2Gain) / (DEFAULT_GAIN / self.metadata.swir2Gain))
844
+ while dWavelength <= dLastWavelength and i < self.metadata.channels:
845
+ if self.calibrationSeriesFO[i] != 0:
846
+ radiance[i] = self.calibrationSeriesLMP[i] * (self.spectrumData.spectra[i] / self.calibrationSeriesFO[i]) * dSwir2Constant
847
+ else:
848
+ radiance[i] = 0
849
+ i += 1
850
+ dWavelength += self.metadata.wavelengthStep
851
+ return radiance
852
+
853
+ #! Need to check the result of the function
854
+ def __parabolic_correction(self, radiance: np.ndarray) -> np.ndarray:
855
+
856
+ DEFAULT_GAP = 3
857
+ iAvgLen = 0
858
+ iIndex = 0
859
+ dPC = 0.0
860
+
861
+ iStartingWavelength = int(self.metadata.channel1_wavelength)
862
+ iEndingWavelength = int(self.metadata.channels - 1) + int(self.metadata.channel1_wavelength)
863
+
864
+ InstrumentType = self.get_instrument_type(iStartingWavelength, iEndingWavelength)
865
+
866
+ iSplice1 = int(self.metadata.splice1_wavelength)
867
+ iSplice2 = int(self.metadata.splice2_wavelength)
868
+ iVertex1 = 675
869
+ iVertex2 = 1975
870
+
871
+ if (((InstrumentType & InstrumentModel_e.itVnir.value) == InstrumentModel_e.itVnir.value) and
872
+ ((InstrumentType & InstrumentModel_e.itSwir1.value) == InstrumentModel_e.itSwir1.value) and
873
+ ((InstrumentType & InstrumentModel_e.itSwir2.value) == InstrumentModel_e.itSwir2.value)) or \
874
+ (((InstrumentType & InstrumentModel_e.itVnir.value) == InstrumentModel_e.itVnir.value) and
875
+ ((InstrumentType & InstrumentModel_e.itSwir1.value) == InstrumentModel_e.itSwir1.value)) or \
876
+ (((InstrumentType & InstrumentModel_e.itSwir1.value) == InstrumentModel_e.itSwir1.value) and
877
+ ((InstrumentType & InstrumentModel_e.itSwir2.value) == InstrumentModel_e.itSwir2.value)):
878
+
879
+ iAvgLen = iSplice1 - iVertex1
880
+ iIndex = iSplice1 - iStartingWavelength
881
+ # 计算 VNIR 或 SWIR1 的 pfactor
882
+ dPC = radiance[iIndex]
883
+
884
+ if dPC == 0:
885
+ dPC = 1
886
+
887
+ dPCFactor1 = (self.average(radiance, iIndex + 1, DEFAULT_GAP) - radiance[iIndex]) / (dPC * iAvgLen * iAvgLen)
888
+
889
+ nPoint = abs(iVertex1 - iStartingWavelength)
890
+ iWavelength = iVertex1
891
+ dE = len(radiance)
892
+
893
+ while iWavelength <= iSplice1:
894
+ if nPoint <= dE:
895
+ radiance[nPoint] *= (dPCFactor1 * (iWavelength - iVertex1) ** 2 + 1)
896
+ nPoint += 1
897
+ iWavelength += 1
898
+
899
+ if InstrumentType == InstrumentModel_e.itVnirSwir1Swir2.value:
900
+ # 计算 SWIR2 的 PC
901
+ iAvgLen = iSplice2 - iVertex2
902
+
903
+ iIndex = iSplice2 - iStartingWavelength
904
+ # 计算 SWIR2 的 pfactor
905
+ dPC = self.average(radiance, iIndex + 1, DEFAULT_GAP)
906
+
907
+ if dPC == 0:
908
+ dPC = 1
909
+
910
+ dPCFactor2 = (self.average(radiance, iIndex - 2, DEFAULT_GAP) -
911
+ self.average(radiance, iIndex + 1, DEFAULT_GAP)) / (dPC * iAvgLen * iAvgLen)
912
+
913
+ nPoint = (iSplice2 - iStartingWavelength) + 1
914
+ iWavelength = iSplice2 + 1
915
+
916
+ while iWavelength <= iVertex2:
917
+ radiance[nPoint] *= (dPCFactor2 * (iWavelength - iVertex2) ** 2 + 1)
918
+ nPoint += 1
919
+ iWavelength += 1
920
+
921
+ return radiance
922
+
923
+ # TODO: Implement the following functions
924
+ # Radiometric Calculation
925
+ # Parabolic Correction
926
+ # Splice Correction
927
+ # Lambda Integration
928
+ # Quantum lntensity
929
+ # Interpolate
930
+ # Statistics
931
+ # NEDL
932
+ # ASCll Export
933
+ # Import Ascii X,Y
934
+ # JCAMP-DX Export
935
+ # Bran+Luebbe
936
+ # Colorimetry..
937
+ # GPS Log
938
+ # Convex Hull
939
+ # Custom...
940
+