225 lines
7.0 KiB
Python
225 lines
7.0 KiB
Python
import numpy as np
|
|
from scipy.interpolate import interp1d
|
|
|
|
from tbwk.RawOpener import Block
|
|
from tbwk.Properties import PropertyBag
|
|
|
|
|
|
"""
|
|
Block structure
|
|
(all is little endian)
|
|
|
|
The header of each block consists of block type (4 bytes), block size (4 bytes) and 4 empty bytes.
|
|
|
|
A measurement block (151) contains 3 nested blocks. The blocks content start at 520.
|
|
- 152, Contains a description and the measurement name:
|
|
12 bytes (?)
|
|
1 int8 (length), usually 28
|
|
n char, usually "Thermo Scientific DataCarton"
|
|
8 bytes, usually 00 00 00 00 00 f0 3f. Might be float64 for "1.0"
|
|
1 int8 (length)
|
|
n char, contains the measurement name
|
|
12 bytes, usually 3 int32: [1, 1, 0]
|
|
- 920, a wrapper for another block.
|
|
12 bytes (?)
|
|
Then another subblock starts with FE FF FF FF, with the content starting at offset 520 again.
|
|
- 921
|
|
12 bytes
|
|
1 int8 (length), usually 29
|
|
n char, usually "Thermo Scientific UV Spectrum"
|
|
8 bytes, usually 00 00 00 00 00 f0 3f.
|
|
- 922
|
|
12 bytes
|
|
\n
|
|
4 bytes
|
|
- 930
|
|
12 bytes
|
|
Then another subblock starts with FE FF FF FF, offset at 520
|
|
- 931
|
|
12 bytes
|
|
1 int8 (length), usually 30
|
|
n char, usually "Thermo Scientific Data-Vector " (sic)
|
|
1 int8 (length), usually 29
|
|
n char, usually "SpectrumFileFormat.UVSpectrum"
|
|
1 int8 (length)
|
|
n char, measurement name again.
|
|
8 bytes, windows filetime, eg 97 dc af d7 2c 46 d6 01 = 2020-06-19 13:29:06.5140375 (+2)
|
|
- 932
|
|
y values.
|
|
y label at offset 59, containing axis label in long and short:
|
|
1 int8
|
|
n char
|
|
1 int8
|
|
n char
|
|
8 bytes
|
|
21 bytes
|
|
1 int32 (4 bytes) indicating number of floats
|
|
n float64 containing y values
|
|
- 932
|
|
x values
|
|
y label at offset 59, containing axis label in long and short:
|
|
1 int8
|
|
n char
|
|
1 int8 (usually 0)
|
|
n char (empty)
|
|
8 bytes
|
|
21 bytes
|
|
1 int32 (4 bytes) indicating number of floats
|
|
n float64 containing y values
|
|
- 990 (10X?)
|
|
- All contain XML data.
|
|
|
|
- 62, content starts commonly at 520 after the block header.
|
|
12 bytes (?)
|
|
n char, XML content
|
|
"""
|
|
|
|
|
|
class Measurement:
|
|
"""
|
|
Represents a single measurement on a NanoDrop 2000.
|
|
|
|
Use the methods of this object for the most often used parameters, or
|
|
access the PropertyBag to access the tabular values as set by the measurement method.
|
|
"""
|
|
title: str = None
|
|
|
|
x_values: np.ndarray = None
|
|
x_label: str = None
|
|
|
|
y_values: np.ndarray = None
|
|
y_label: str = None
|
|
|
|
properties: PropertyBag = None
|
|
|
|
def __init__(self,
|
|
title: str,
|
|
x_values: np.ndarray,
|
|
x_label: str,
|
|
y_values: np.ndarray,
|
|
y_label: str,
|
|
properties: PropertyBag = None,
|
|
):
|
|
"""
|
|
|
|
:param title: Title of the measurement
|
|
:param x_values: numpy array containing x values
|
|
:param x_label: label for x values
|
|
:param y_values: numpy array containing y values (must be equal size)
|
|
:param y_label: label for y values
|
|
:param properties: A property bag
|
|
"""
|
|
assert len(x_values) == len(y_values)
|
|
|
|
self.title = title
|
|
|
|
self.x_values = x_values
|
|
self.x_label = x_label
|
|
|
|
self.y_values = y_values
|
|
self.y_label = y_label
|
|
|
|
self.properties = properties
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Measurement[{self.title}], {self.properties.get_method_title()}>"
|
|
|
|
def get_title(self) -> str:
|
|
"""
|
|
Returns the given sample name (title) of the measurement
|
|
"""
|
|
return self.title
|
|
|
|
def get_method_title(self) -> str:
|
|
"""
|
|
Returns the title of the method
|
|
"""
|
|
return self.properties.get_method_title()
|
|
|
|
def get_method_description(self) -> str:
|
|
"""
|
|
Returns the description of the method.
|
|
"""
|
|
return self.properties.get_method_description()
|
|
|
|
def get_x(self) -> np.ndarray:
|
|
"""
|
|
Returns x values as a numpy array, float64
|
|
|
|
Should usually contain the measured wavelengths.
|
|
"""
|
|
return self.x_values
|
|
|
|
def get_x_label(self) -> str:
|
|
"""
|
|
Returns the saved label for the x-axis.
|
|
"""
|
|
return self.x_label
|
|
|
|
def get_y(self) -> np.ndarray:
|
|
"""
|
|
Returns y values as a numpy array, float64
|
|
|
|
Should usually contain the measured absorption.
|
|
:return:
|
|
"""
|
|
return self.y_values
|
|
|
|
def get_y_label(self) -> str:
|
|
"""
|
|
Returns the saved label for the y-axis.
|
|
"""
|
|
return self.y_label
|
|
|
|
def get_property_bag(self) -> PropertyBag:
|
|
"""
|
|
Returns the property bag.
|
|
"""
|
|
return self.properties
|
|
|
|
def get_absorption_at(self, wavelength: float, from_spectrum=False) -> float:
|
|
""" Returns the absorption at a given wavelength.
|
|
|
|
If from_spectrum is set to true, the value comes always from the spectrum. If set to False, the measured
|
|
values are tried first. """
|
|
|
|
if from_spectrum is False:
|
|
wavelength_id = f"A{wavelength:.0f}"
|
|
|
|
if self.properties.has_property(wavelength_id):
|
|
value = self.properties.get_property(wavelength_id)
|
|
return value.get_value().get_value()
|
|
|
|
# Try if we find the measured value exactly
|
|
f = np.isin(self.x_values, wavelength, assume_unique=True)
|
|
result = self.y_values[f]
|
|
|
|
if len(result) == 1:
|
|
return result.item()
|
|
|
|
# If this does not work, we need to intrapolate
|
|
f = interp1d(self.x_values, self.y_values, kind="cubic")
|
|
|
|
return f(wavelength)
|
|
|
|
@classmethod
|
|
def from_block(cls, block):
|
|
assert block.type == Block.Measurement
|
|
|
|
# Create a property bag
|
|
properties = PropertyBag.from_xml(block.parsed_content[2].parsed_content)
|
|
|
|
# Create new measurement classes
|
|
ret = cls(
|
|
title=block.parsed_content[0].parsed_content[1].decode("utf8"),
|
|
x_values=block.parsed_content[1].parsed_content[2].parsed_content[2].parsed_content[3],
|
|
x_label=block.parsed_content[1].parsed_content[2].parsed_content[2].parsed_content[0].decode("utf8"),
|
|
y_values=block.parsed_content[1].parsed_content[2].parsed_content[1].parsed_content[3],
|
|
y_label=block.parsed_content[1].parsed_content[2].parsed_content[1].parsed_content[0].decode("utf8"),
|
|
properties=properties,
|
|
)
|
|
|
|
return ret
|
|
|
|
|