From 8ecb7d3a2bcdbe9eeff01571c1e15de1c54e0265 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 13:48:20 +0200 Subject: [PATCH 01/24] Add parser and schema --- src/nomad_measurements/transmission/parser.py | 52 + src/nomad_measurements/transmission/schema.py | 1289 +++++++++++++++++ 2 files changed, 1341 insertions(+) create mode 100644 src/nomad_measurements/transmission/parser.py create mode 100644 src/nomad_measurements/transmission/schema.py diff --git a/src/nomad_measurements/transmission/parser.py b/src/nomad_measurements/transmission/parser.py new file mode 100644 index 00000000..d7c6804d --- /dev/null +++ b/src/nomad_measurements/transmission/parser.py @@ -0,0 +1,52 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from typing import TYPE_CHECKING + +from nomad.parsing import MatchingParser + +from nomad_measurements.transmission.schema import ( + ELNUVVisNirTransmission, + RawFileTransmissionData, +) +from nomad_measurements.utils import create_archive + +if TYPE_CHECKING: + from nomad.datamodel.datamodel import ( + EntryArchive, + ) + + +class TransmissionParser(MatchingParser): + """ + Parser for matching files from Transmission Spectrophotometry and + creating instances of ELN. + """ + + def parse( + self, mainfile: str, archive: 'EntryArchive', logger=None, child_archives=None + ) -> None: + data_file = mainfile.split('/')[-1] + entry = ELNUVVisNirTransmission.m_from_dict( + ELNUVVisNirTransmission.m_def.a_template + ) + entry.data_file = data_file + file_name = f'{".".join(data_file.split(".")[:-1])}.archive.json' + archive.data = RawFileTransmissionData( + measurement=create_archive(entry, archive, file_name) + ) + archive.metadata.entry_name = f'{data_file} data file' diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py new file mode 100644 index 00000000..210218b3 --- /dev/null +++ b/src/nomad_measurements/transmission/schema.py @@ -0,0 +1,1289 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +Module for schemas related to Transmission Spectrophotometry. Contains the schema for +spectrophotometer, sample, and measurement. + +To add a new schema for a measurement technique, create a new schema class with the +signature: `Transmission(Measurement)`. +For example, UVVisNirTransmission(Measurement) for UV-Vis-NIR Transmission. + +If you want a corresponding ELN schema, create a new class with the signature: +`ELNTransmission( + Transmission, PlotSection, EntryData +)`. +For example, ELNUVVisNirTransmission(UVVisNirTransmission, PlotSection, EntryData). +""" + +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Union, +) + +import numpy as np +import plotly.express as px +from fairmat_readers_transmission import read_asc +from nomad.datamodel.data import ( + ArchiveSection, + EntryData, +) +from nomad.datamodel.metainfo.annotations import ( + ELNAnnotation, + ELNComponentEnum, + Filter, + SectionProperties, +) +from nomad.datamodel.metainfo.basesections import ( + CompositeSystemReference, + Instrument, + InstrumentReference, + Measurement, + MeasurementResult, + ReadableIdentifiers, +) +from nomad.datamodel.metainfo.plot import ( + PlotlyFigure, + PlotSection, +) +from nomad.metainfo import ( + MEnum, + Quantity, + SchemaPackage, + Section, + SubSection, +) +from nomad.units import ureg +from nomad_material_processing.general import Sample + +from nomad_measurements.utils import create_archive, merge_sections + +if TYPE_CHECKING: + from nomad.datamodel.datamodel import EntryArchive + from structlog.stdlib import BoundLogger + + +m_package = SchemaPackage( + aliases=[ + 'uv_vis_nir_transmission', + 'uv_vis_nir_transmission.schema', + 'uv_vis_nir_transmission.parser', + ], +) + + +class TransmissionSpectrophotometer(Instrument, EntryData): + """ + Entry section for the transmission spectrophotometer. + """ + + m_def = Section() + serial_number = Quantity( + type=str, + description='Instrument serial number.', + a_eln={'component': 'StringEditQuantity'}, + ) + software_version = Quantity( + type=str, + description='Software/firmware version.', + a_eln={'component': 'StringEditQuantity'}, + ) + + +class TransmissionSampleReference(CompositeSystemReference): + """ + Reference to the sample used in the transmission measurement. Additionally, + contains the thickness and orientation of the sample. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'lab_id', + 'reference', + 'thickness', + 'orientation', + ] + ) + ) + ) + reference = Quantity( + type=Sample, + description=""" + A reference to the sample used. + """, + a_eln=ELNAnnotation( + component='ReferenceEditQuantity', + label='sample reference', + ), + ) + thickness = Quantity( + type=np.float64, + description=""" + Thickness of the sample along the direction of the light beam. + Also referred to as path length of the beam.""", + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'millimeter', + }, + unit='meter', + ) + orientation = Quantity( + type=str, + description=""" + Crystallographic orientation of the sample surface on which the light beam is + incident. + """, + a_eln={'component': 'StringEditQuantity'}, + ) + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + """ + The normalizer for the `TransmissionSampleReference` class. + + Args: + archive (EntryArchive): The NOMAD archive. + logger (BoundLogger): A structlog logger. + """ + super().normalize(archive, logger) + # TODO: if the thickness is not mentioned, it should be copied from the + # geometry of the referenced sample. + + +class Accessory(ArchiveSection): + """ + Section for adding setting for a custom accessory. + """ + + m_def = Section( + description='An accessory used in the instrument.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'description', + ], + ), + ), + ) + name = Quantity( + type=str, + description='Name of the accessory.', + a_eln={'component': 'StringEditQuantity'}, + ) + description = Quantity( + type=str, + description='Description of the accessory.', + a_eln={'component': 'RichTextEditQuantity'}, + ) + + +class PolDepol(Accessory): + """ + Optional accessory to polarize or depolarize the light beam entering the sample. + """ + + m_def = Section( + description=( + 'Optional accessory to polarize or depolarize the light beam ' + 'entering the sample.' + ), + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'mode', + 'polarizer_angle', + ], + ), + ), + ) + + mode = Quantity( + type=MEnum(['Polarizer', 'Depolarizer']), + description='Mode of the accessory: either polarizer or depolarizer.', + a_eln={'component': 'RadioEnumEditQuantity'}, + ) + polarizer_angle = Quantity( + type=np.float64, + description='Value of polarization angle when polarizer mode is used.', + a_eln={'component': 'NumberEditQuantity'}, + unit='degrees', + ) + + def normalize(self, archive, logger): + if self.mode is None or self.mode == 'Depolarizer': + if self.polarizer_angle is not None: + logger.warning( + 'Ambiguous polarizer angle: ' + 'PolDepol accessory is not set to "Polarizer" mode, ' + 'but `polarizer_angle` is set.' + ) + super().normalize(archive, logger) + + +class Aperture(Accessory): + """ + Section for adding settings of a custom aperture. + """ + + m_def = Section( + description=""" + Custom aperture placed in front of the sample inside the sample compartment. + """, + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'diameter', + ], + ), + ), + ) + diameter = Quantity( + type=np.float64, + description='Diameter of the aperture.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'mm', + }, + unit='mm', + ) + + +class SettingOverWavelengthRange(ArchiveSection): + """ + An instrument setting set over a range of wavelength. + """ + + m_def = Section( + description='An instrument setting set over a range of wavelength.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_upper_limit', + 'wavelength_lower_limit', + 'value', + ], + ), + ), + ) + name = Quantity( + type=str, + description='Short description containing wavelength range.', + a_eln={'component': 'StringEditQuantity'}, + ) + wavelength_upper_limit = Quantity( + type=np.float64, + description='Upper limit of wavelength range.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + ) + wavelength_lower_limit = Quantity( + type=np.float64, + description='Lower limit of wavelength range.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + ) + value = Quantity( + type=np.float64, + description='Value of the given instrument setting.', + a_eln={'component': 'NumberEditQuantity'}, + unit='dimensionless', + ) + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + """ + The normalizer for the `SettingOverWavelengthRange` class. + + Args: + archive (EntryArchive): The archive containing the section. + logger (BoundLogger): A structlog logger. + """ + super().normalize(archive, logger) + + upper_limit = '-' + lower_limit = '-' + if self.wavelength_upper_limit is not None: + upper_limit = self.wavelength_upper_limit.magnitude + if self.wavelength_lower_limit is not None: + lower_limit = self.wavelength_lower_limit.magnitude + if isinstance(upper_limit, float) and isinstance(lower_limit, float): + if upper_limit < lower_limit: + logger.warning( + f'Upper limit of wavelength "{upper_limit}" should be greater than' + f'lower limit of wavelength "{lower_limit}".' + ) + upper_limit = '-' + lower_limit = '-' + self.wavelength_upper_limit = None + self.wavelength_lower_limit = None + self.name = f'[{lower_limit}, {upper_limit}]' + + +class SlitWidth(SettingOverWavelengthRange): + """ + Slit width setting over a wavelength range. + """ + + m_def = Section( + description='Slit width value over a wavelength range.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_upper_limit', + 'wavelength_lower_limit', + 'value', + ], + ), + ), + ) + value = Quantity( + type=np.float64, + description='Slit width value.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + ) + slit_width_servo = Quantity( + type=bool, + description=""" + True if slit width servo is on, i.e., the system monitors the reference + beam energy and adjusts the slits to avoid over-saturation of the detectors. + """, + a_eln={'component': 'BoolEditQuantity'}, + ) + + +class Monochromator(ArchiveSection): + """ + Monochromator setting over a wavelength range. + """ + + m_def = Section( + description='Monochromator setting over a wavelength range.', + ) + monochromator_change_point = Quantity( + type=np.float64, + description='The wavelength at which the monochromator changes settings.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + shape=['*'], + ) + monochromator_slit_width = SubSection( + section_def=SlitWidth, + repeats=True, + ) + + +class Lamp(ArchiveSection): + """ + Lamp setting over a wavelength range. + """ + + m_def = Section( + description='Lamp setting over a wavelength range.', + ) + d2_lamp = Quantity( + type=bool, + description=( + 'True if the Deuterium (D2) lamp is used ' + '(typically covers the UV range from about 160 nm to 400 nm).' + ), + a_eln={'component': 'BoolEditQuantity'}, + ) + tungsten_lamp = Quantity( + type=bool, + description=( + 'True if the Tungsten lamp is used ' + '(typically covers the visible to near-infrared range from about ' + '320 nm to 2500 nm)' + ), + a_eln={'component': 'BoolEditQuantity'}, + ) + lamp_change_point = Quantity( + type=np.float64, + description='The wavelength at which lamp used for the beam changes.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + shape=['*'], + ) + + +class NIRGain(SettingOverWavelengthRange): + """ + NIR gain factor over a range of wavelength. + """ + + m_def = Section( + description='NIR gain factor over a wavelength range.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_upper_limit', + 'wavelength_lower_limit', + 'value', + ], + ), + ), + ) + value = Quantity( + type=np.float64, + description='NIR gain factor of the detector.', + a_eln={'component': 'NumberEditQuantity'}, + unit='dimensionless', + ) + + +class IntegrationTime(SettingOverWavelengthRange): + """ + Integration time over a wavelength range. + """ + + m_def = Section( + description='Integration time over a wavelength range.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_upper_limit', + 'wavelength_lower_limit', + 'value', + ], + ), + ), + ) + value = Quantity( + type=np.float64, + description='Integration time value.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 's', + }, + unit='s', + ) + + +class Detector(ArchiveSection): + """ + Detector setting over a wavelength range. + """ + + m_def = Section( + description='Detector setting over a wavelength range.', + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'module', + 'detectors', + 'detector_change_point', + 'nir_gain', + 'integration_time', + ], + ), + ), + ) + module = Quantity( + type=MEnum( + [ + 'Three Detector Module', + 'Two Detector Module', + '150-mm Integrating Sphere', + ] + ), + a_eln={'component': 'EnumEditQuantity'}, + description=""" + Modules containing multiple detectors for different wavelength ranges. + | Detector Module | Description | + |--------------------------------------|----------------------| + | **Three Detector Module** | Installed as standard module on Perkin-Elmer Lambda 1050 WB and NB spectrophotometers. Contains three detectors for different wavelength ranges: PMT, InGaAs, PbS. | + | **Two Detector Module** | Installed on Perkin-Elmer Lambda 750, 900, 950 spectrophotometers. Contains two detectors for different wavelength ranges: PMT, PbS. | + | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | + """, # noqa: E501 + ) + detectors = Quantity( + type=str, + description=""" + Detectors used in the instrument. Some of the popular detectors are: + | Detector | Description | + |-------------------|----------------------| + | **PMT** | Photomultiplier Tube detector used for the Ultra-Violet (UV) or visible range.| + | **InGaAs** | Indium Gallium Arsenide detector used for Near-Infra-red (NIR) range.| + | **PbS** | Lead Sulphide detector used for Infrared (IR) range.| + """, # noqa: E501 + a_eln={'component': 'StringEditQuantity'}, + shape=['*'], + ) + detector_change_point = Quantity( + type=np.float64, + description='The wavelength at which the detector module changes.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'nm', + }, + unit='nm', + shape=['*'], + ) + nir_gain = SubSection( + section_def=NIRGain, + repeats=True, + ) + integration_time = SubSection( + section_def=IntegrationTime, + repeats=True, + ) + + def normalize(self, archive, logger): + super().normalize(archive, logger) + if self.module is not None: + if self.module == 'Three Detector Module': + self.detectors = ['PMT', 'InGaAs', 'PbS'] + elif self.module == 'Two Detector Module': + self.detectors = ['PMT', 'PbS'] + elif self.module == '150-mm Integrating Sphere': + self.detectors = ['PMT', 'InGaAs'] + + +class Attenuator(ArchiveSection): + """ + Attenuation setting for the sample and reference beam. + """ + + m_def = Section( + description='Attenuation setting for the sample and reference beam.', + ) + sample = Quantity( + type=int, + description='Sample beam attenuation in percentage.', + a_eln={ + 'component': 'NumberEditQuantity', + 'minValue': 0, + 'maxValue': 100, + }, + unit='dimensionless', + ) + reference = Quantity( + type=int, + description='Reference beam attenuation in percentage.', + a_eln={ + 'component': 'NumberEditQuantity', + 'minValue': 0, + 'maxValue': 100, + }, + unit='dimensionless', + ) + + +class TransmissionSettings(ArchiveSection): + """ + Section for the settings of the instrument used for transmission measurement. + """ + + ordinate_type = Quantity( + type=MEnum(['%T', 'A']), + description=( + 'Specifies whether the ordinate (y-axis) of the measurement data is ' + 'percent transmittance (%T) or absorbance (A).' + ), + a_eln={'component': 'EnumEditQuantity'}, + ) + + +class TransmissionResult(MeasurementResult): + """ + Section for the results of the Transmission measurement. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'transmittance', + 'absorbance', + 'wavelength', + ], + visible=Filter( + exclude=[ + 'array_index', + ], + ), + ) + ) + ) + array_index = Quantity( + type=int, + description='Array of indices used for plotting quantity vectors.', + shape=['*'], + ) + transmittance = Quantity( + type=np.float64, + description='Measured transmittance in percentage.', + shape=['*'], + unit='dimensionless', + a_plot={'x': 'array_index', 'y': 'transmittance'}, + ) + absorbance = Quantity( + type=np.float64, + description='Measured absorbance ranging from 0 to 1.', + shape=['*'], + unit='dimensionless', + a_plot={'x': 'array_index', 'y': 'absorbance'}, + ) + wavelength = Quantity( + type=np.float64, + description='Wavelength values for which the measurement was conducted.', + shape=['*'], + unit='m', + a_plot={'x': 'array_index', 'y': 'wavelength'}, + ) + + def generate_plots(self) -> list[PlotlyFigure]: + """ + Generate the plotly figures for the `TransmissionResult` section. + + Returns: + list[PlotlyFigure]: The plotly figures. + """ + figures = [] + if self.wavelength is None: + return figures + + for key in ['transmittance', 'absorbance']: + if getattr(self, key) is None: + continue + + x_label = 'Wavelength' + xaxis_title = x_label + ' (nm)' + x = self.wavelength.to('nm').magnitude + + y_label = key.capitalize() + yaxis_title = y_label + if key == 'transmittance': + yaxis_title += ' (%)' + y = getattr(self, key).magnitude + + line_linear = px.line(x=x, y=y) + + line_linear.update_layout( + title=f'{y_label} over {x_label}', + xaxis_title=xaxis_title, + yaxis_title=yaxis_title, + xaxis=dict( + fixedrange=False, + ), + yaxis=dict( + fixedrange=False, + ), + template='plotly_white', + ) + + figures.append( + PlotlyFigure( + label=f'{y_label} linear plot', + figure=line_linear.to_plotly_json(), + ), + ) + + return figures + + +class Transmission(Measurement): + """ + Schema for Transmission Spectrophotometry measurement. + """ + + user = Quantity( + type=str, + description='Name of user or analyst.', + a_eln={'component': 'StringEditQuantity'}, + ) + + method = Measurement.method.m_copy() + method.default = 'Transmission Spectrophotometry' + + samples = Measurement.samples.m_copy() + samples.section_def = TransmissionSampleReference + + results = Measurement.results.m_copy() + results.section_def = TransmissionResult + + transmission_settings = SubSection( + section_def=TransmissionSettings, + ) + + +class UVVisNirTransmissionSettings(TransmissionSettings): + """ + Section for setting of the instrument used for transmission measurement. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'ordinate_type', + 'sample_beam_position', + 'common_beam_mask', + 'common_beam_depolarizer', + ], + ), + ), + ) + sample_beam_position = Quantity( + type=MEnum(['Front', 'Rear']), + description=( + 'Position of the sample beam: either the front or the back of the sample ' + 'chamber.' + ), + a_eln={'component': 'EnumEditQuantity'}, + ) + common_beam_mask = Quantity( + type=int, + description=( + 'Mask setting for the common beam in percentage.' + '100% means the mask is fully open and ' + '100% of the beam passes. 0% means the mask is closed and no light passes.' + ), + a_eln={ + 'component': 'NumberEditQuantity', + 'minValue': 0, + 'maxValue': 100, + }, + unit='dimensionless', + ) + common_beam_depolarizer = Quantity( + type=bool, + description=( + 'True if the common beam depolarizer (CBD) is on, else False. CBD is an ' + 'optional accessory used to depolarize the radiation coming from the ' + 'monochromator.' + ), + default=False, + a_eln={'component': 'BoolEditQuantity'}, + ) + accessory = SubSection( + section_def=Accessory, + repeats=True, + ) + monochromator = SubSection( + section_def=Monochromator, + ) + lamp = SubSection( + section_def=Lamp, + ) + detector = SubSection( + section_def=Detector, + ) + attenuator = SubSection( + section_def=Attenuator, + ) + + +class UVVisNirTransmissionResult(TransmissionResult): + """ + Section for the results of the UV-Vis NIR Transmission measurement. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'transmittance', + 'absorbance', + 'wavelength', + 'extinction_coefficient', + ], + visible=Filter( + exclude=[ + 'array_index', + ], + ), + ) + ) + ) + extinction_coefficient = Quantity( + type=np.float64, + description=( + 'Extinction coefficient calculated from transmittance and sample thickness ' + 'values: -log(T)/L. The coefficient includes the effects of ' + 'absorption, reflection, and scattering.' + ), + shape=['*'], + unit='1/m', + a_plot={'x': 'array_index', 'y': 'extinction_coefficient'}, + ) + + def generate_plots(self) -> list[PlotlyFigure]: + """ + Extends TransmissionResult.generate_plots() method to include the plotly + figures for the `UVVisNirTransmissionResult` section. + + Returns: + list[PlotlyFigure]: The plotly figures. + """ + figures = super().generate_plots() + if self.wavelength is None: + return figures + + # generate plot for extinction coefficient + if self.extinction_coefficient is None: + return figures + + x = self.wavelength.to('nm').magnitude + x_label = 'Wavelength' + xaxis_title = x_label + ' (nm)' + + y = self.extinction_coefficient.to('1/cm').magnitude + y_label = 'Extinction coefficient' + yaxis_title = y_label + ' (1/cm)' + + line_linear = px.line(x=x, y=y) + + line_linear.update_layout( + title=f'{y_label} over {x_label}', + xaxis_title=xaxis_title, + yaxis_title=yaxis_title, + xaxis=dict( + fixedrange=False, + ), + yaxis=dict( + fixedrange=False, + ), + template='plotly_white', + ) + + figures.append( + PlotlyFigure( + label=f'{y_label} linear plot', + figure=line_linear.to_plotly_json(), + ), + ) + + return figures + + def calculate_extinction_coefficient(self, archive, logger): + """ + Calculate the extinction coefficient from the transmittance and sample + thickness. The formula used is: -log( T[%] / 100 ) / L. + + Args: + archive (EntryArchive): The archive containing the section. + logger (BoundLogger): A structlog logger. + """ + self.extinction_coefficient = None + if not archive.data.samples: + logger.warning( + 'Cannot calculate extinction coefficient as sample not found.' + ) + return + if not archive.data.samples[0].thickness: + logger.warning( + 'Cannot calculate extinction coefficient as sample thickness not found ' + 'or the value is 0.' + ) + return + + path_length = archive.data.samples[0].thickness + if self.transmittance is not None: + extinction_coeff = -np.log(self.transmittance / 100) / path_length + # TODO: The if-block is a temperary fix to avoid processing of nans in + # the archive. The issue will be fixed in the future. + if np.any(np.isnan(extinction_coeff)): + logger.warning( + 'Failed to save extinction coefficient. ' + 'Encountered NaN values in the calculation.' + ) + return + self.extinction_coefficient = extinction_coeff + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + """ + The normalizer for the `UVVisNirTransmissionResult` class. + + Args: + archive (EntryArchive): The archive containing the section that is being + normalized. + logger (BoundLogger): A structlog logger. + """ + super().normalize(archive, logger) + self.calculate_extinction_coefficient(archive, logger) + + +class UVVisNirTransmission(Transmission): + """ + Schema for UV-Vis NIR Transmission, which extends the `Transmission` class. + """ + + m_def = Section() + + method = Transmission.method.m_copy() + method.default = 'UV-Vis-NIR Transmission' + + results = Transmission.results.m_copy() + results.section_def = UVVisNirTransmissionResult + + transmission_settings = Transmission.transmission_settings.m_copy() + transmission_settings.section_def = UVVisNirTransmissionSettings + + +class ELNUVVisNirTransmission(UVVisNirTransmission, PlotSection, EntryData): + """ + Entry class for UVVisNirTransmission. Handles the population of the schema and + plotting. Data is added either through manual input in the GUI or by parsing + the measurement files coming from the instrument. + """ + + m_def = Section( + label='UV-Vis-NIR Transmission', + a_template={ + 'measurement_identifiers': {}, + }, + ) + + measurement_identifiers = SubSection( + section_def=ReadableIdentifiers, + ) + + data_file = Quantity( + type=str, + description='File generated by the instrument containing data and metadata.', + a_eln=ELNAnnotation( + component=ELNComponentEnum.FileEditQuantity, + ), + ) + + def get_read_write_functions(self) -> tuple[Callable, Callable]: + """ + Method for getting the correct read and write functions for the current data + file. + + Returns: + tuple[Callable, Callable]: The read, write functions. + """ + if self.data_file.endswith('.asc'): + return read_asc, self.write_transmission_data + return None, None + + def create_instrument_entry( + self, data_dict: dict[str, Any], archive: 'EntryArchive', logger: 'BoundLogger' + ) -> InstrumentReference: + """ + Method for creating the instrument entry. Returns a reference to the created + instrument. + + Args: + data_dict (dict[str, Any]): The dictionary containing the instrument data. + archive (EntryArchive): The archive containing the section. + logger (BoundLogger): A structlog logger. + + Returns: + InstrumentReference: The instrument reference. + """ + instrument = TransmissionSpectrophotometer( + name=data_dict['instrument_name'], + serial_number=data_dict['instrument_serial_number'], + software_version=data_dict['instrument_firmware_version'], + ) + if data_dict['start_datetime'] is not None: + instrument.datetime = data_dict['start_datetime'] + instrument.normalize(archive, logger) + + logger.info('Created instrument entry.') + m_proxy_value = create_archive(instrument, archive, 'instrument.archive.json') + + return InstrumentReference(reference=m_proxy_value) + + def get_instrument_reference( + self, data_dict: dict[str, Any], archive: 'EntryArchive', logger: 'BoundLogger' + ) -> Union[InstrumentReference, None]: + """ + Method for getting the instrument reference. + Looks for an existing instrument with the given serial number. + If found, it returns a reference to this instrument. + If no instrument is found, logs a warning, creates a new entry for the + instrument and returns a reference to this entry. + If multiple instruments are found, it logs a warning and returns None. + + Args: + data_dict (dict[str, Any]): The dictionary containing the instrument data. + archive (EntryArchive): The archive containing the section. + logger (BoundLogger): A structlog logger. + + Returns: + Union[InstrumentReference, None]: The instrument reference or None. + """ + from nomad.datamodel.context import ClientContext + + if isinstance(archive.m_context, ClientContext): + return None + + from nomad.search import search + + serial_number = data_dict['instrument_serial_number'] + api_query = { + 'search_quantities': { + 'id': ( + 'data.serial_number#transmission.schema.' + 'TransmissionSpectrophotometer' + ), + 'str_value': f'{serial_number}', + }, + } + search_result = search( + owner='visible', + query=api_query, + user_id=archive.metadata.main_author.user_id, + ) + + if not search_result.data: + logger.warning( + f'No "TransmissionSpectrophotometer" instrument found with the serial ' + f'number "{serial_number}". Creating an entry for the instrument.' + ) + return self.create_instrument_entry(data_dict, archive, logger) + + if len(search_result.data) > 1: + logger.warning( + f'Multiple "TransmissionSpectrophotometer" instruments found with the ' + f'serial number "{serial_number}". Please select it manually.' + ) + return None + + entry = search_result.data[0] + upload_id = entry['upload_id'] + entry_id = entry['entry_id'] + m_proxy_value = f'../uploads/{upload_id}/archive/{entry_id}#/data' + + return InstrumentReference(reference=m_proxy_value) + + def write_transmission_data( # noqa: PLR0912, PLR0915 + self, + transmission_dict: dict[str, Any], + archive: 'EntryArchive', + logger: 'BoundLogger', + ) -> None: + """ + Populate `UVVisNirTransmission` section using data from a dict. + + Args: + transmission_dict (dict[str, Any]): A dictionary with the transmission data. + archive (EntryArchive): The archive containing the section. + logger (BoundLogger): A structlog logger. + """ + self.user = transmission_dict['analyst_name'] + if transmission_dict['start_datetime'] is not None: + self.datetime = transmission_dict['start_datetime'] + + result = UVVisNirTransmissionResult( + wavelength=transmission_dict['measured_wavelength'], + ) + if transmission_dict['ordinate_type'] == 'A': + result.absorbance = transmission_dict['measured_ordinate'] + elif transmission_dict['ordinate_type'] == '%T': + result.transmittance = transmission_dict['measured_ordinate'] + else: + logger.warning(f"Unknown ordinate type '{transmission_dict['ordinate']}'.") + result.normalize(archive, logger) + + lamp = Lamp( + d2_lamp=transmission_dict['is_d2_lamp_used'], + tungsten_lamp=transmission_dict['is_tungsten_lamp_used'], + lamp_change_point=transmission_dict['lamp_change_wavelength'], + ) + lamp.normalize(archive, logger) + + detector_module = transmission_dict['detector_module'] + if detector_module == 'uv/vis/nir detector': + if 'lambda 1050' in transmission_dict['instrument_name'].lower(): + detector_module = 'Three Detector Module' + elif 'lambda 950' in transmission_dict['instrument_name'].lower(): + detector_module = 'Two Detector Module' + elif 'lambda 900' in transmission_dict['instrument_name'].lower(): + detector_module = 'Two Detector Module' + elif 'lambda 750' in transmission_dict['instrument_name'].lower(): + detector_module = 'Two Detector Module' + if detector_module == '150mm sphere': + detector_module = '150-mm Integrating Sphere' + detector = Detector( + module=detector_module, + ) + for idx, wavelength_value in enumerate(transmission_dict['detector_NIR_gain']): + nir_gain = NIRGain( + wavelength_upper_limit=wavelength_value['wavelength'], + value=wavelength_value['value'], + ) + if idx + 1 < len(transmission_dict['detector_NIR_gain']): + nir_gain.wavelength_lower_limit = transmission_dict[ + 'detector_NIR_gain' + ][idx + 1]['wavelength'] + nir_gain.normalize(archive, logger) + detector.nir_gain.append(nir_gain) + for idx, wavelength_value in enumerate( + transmission_dict['detector_integration_time'] + ): + integration_time = IntegrationTime( + wavelength_upper_limit=wavelength_value['wavelength'], + value=wavelength_value['value'], + ) + if idx + 1 < len(transmission_dict['detector_integration_time']): + integration_time.wavelength_lower_limit = transmission_dict[ + 'detector_integration_time' + ][idx + 1]['wavelength'] + integration_time.normalize(archive, logger) + detector.integration_time.append(integration_time) + + detector.detector_change_point = transmission_dict['detector_change_wavelength'] + detector.normalize(archive, logger) + + monochromator = Monochromator() + for idx, wavelength_value in enumerate( + transmission_dict['monochromator_slit_width'] + ): + slit_width = SlitWidth( + wavelength_upper_limit=wavelength_value['wavelength'], + ) + if ( + isinstance(wavelength_value['value'], str) + and wavelength_value['value'].lower() == 'servo' + ): + slit_width.value = None + slit_width.slit_width_servo = True + elif isinstance(wavelength_value['value'], ureg.Quantity): + slit_width.value = wavelength_value['value'] + slit_width.slit_width_servo = False + else: + logger.warning( + f'Invalid slit width value "{wavelength_value["value"]}" for ' + f'wavelength "{wavelength_value["wavelength"]}".' + ) + continue + if idx + 1 < len(transmission_dict['monochromator_slit_width']): + slit_width.wavelength_lower_limit = transmission_dict[ + 'monochromator_slit_width' + ][idx + 1]['wavelength'] + slit_width.normalize(archive, logger) + monochromator.monochromator_slit_width.append(slit_width) + monochromator.monochromator_change_point = transmission_dict[ + 'monochromator_change_wavelength' + ] + monochromator.normalize(archive, logger) + + attenuator = Attenuator( + sample=transmission_dict['attenuation_percentage']['sample'], + reference=transmission_dict['attenuation_percentage']['reference'], + ) + + if self.get('transmission_settings'): + if self.transmission_settings.get('accessory'): + for idx, accessory in enumerate(self.transmission_settings.accessory): + if isinstance(accessory, PolDepol): + if accessory.mode == 'Polarizer': + self.transmission_settings.accessory[ + idx + ].polarizer_angle = transmission_dict['polarizer_angle'] + + transmission_settings = UVVisNirTransmissionSettings( + ordinate_type=transmission_dict['ordinate_type'], + sample_beam_position=transmission_dict['sample_beam_position'], + common_beam_mask=transmission_dict['common_beam_mask_percentage'], + common_beam_depolarizer=transmission_dict['is_common_beam_depolarizer_on'], + lamp=lamp, + detector=detector, + monochromator=monochromator, + attenuator=attenuator, + ) + transmission_settings.normalize(archive, logger) + + instrument_reference = self.get_instrument_reference( + transmission_dict, archive, logger + ) + if instrument_reference is not None: + instruments = [instrument_reference] + else: + instruments = [] + + transmission = UVVisNirTransmission( + results=[result], + transmission_settings=transmission_settings, + instruments=instruments, + ) + merge_sections(self, transmission, logger) + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger'): + """ + The normalize function of the `UVVisNirTransmission` section. + + Args: + archive (EntryArchive): The archive containing the section that is being + normalized. + logger (BoundLogger): A structlog logger. + """ + if self.data_file is not None: + read_function, write_function = self.get_read_write_functions() + if read_function is None or write_function is None: + logger.warning( + f'No compatible reader found for the file: "{self.data_file}".' + ) + else: + with archive.m_context.raw_file(self.data_file) as file: + transmission_dict = read_function(file.name, logger) + write_function(transmission_dict, archive, logger) + super().normalize(archive, logger) + + if not self.results: + return + + self.figures = self.results[0].generate_plots() + + +class RawFileTransmissionData(EntryData): + """ + Section for a Transmission Spectrophotometry data file. + """ + + measurement = Quantity( + type=ELNUVVisNirTransmission, + a_eln=ELNAnnotation( + component='ReferenceEditQuantity', + ), + ) + + +ELNUVVisTransmission = ELNUVVisNirTransmission + +m_package.__init_metainfo__() From a0071168a270ce5150c37b30fa80c220c07e4f83 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 13:56:28 +0200 Subject: [PATCH 02/24] Add init file containing transmission entry points --- .../transmission/__init__.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/nomad_measurements/transmission/__init__.py diff --git a/src/nomad_measurements/transmission/__init__.py b/src/nomad_measurements/transmission/__init__.py new file mode 100644 index 00000000..dbb6e13b --- /dev/null +++ b/src/nomad_measurements/transmission/__init__.py @@ -0,0 +1,37 @@ +from nomad.config.models.plugins import ParserEntryPoint, SchemaPackageEntryPoint + + +class TransmissionSchemaEntryPoint(SchemaPackageEntryPoint): + """ + Entry point for lazy loading of the Transmission schemas. + """ + + def load(self): + from nomad_measurements.transmission.schema import m_package + + return m_package + + +class TransmissionParserEntryPoint(ParserEntryPoint): + """ + Entry point for lazy loading of the TransmissionParser. + """ + + def load(self): + from nomad_measurements.transmission.parser import TransmissionParser + + return TransmissionParser(**self.dict()) + + +schema = TransmissionSchemaEntryPoint( + name='Transmission Schema', + description='Schema for Transmission Spectrophotometry FAIR data.', +) + + +parser = TransmissionParserEntryPoint( + name='Transmission Parser', + description='Parser for raw files from Transmission measurements.', + mainfile_name_re='^.*\.asc$', + mainfile_mime_re='text/.*|application/zip', +) From e1f3c6274cb98db2a073418c56fe276672c21e45 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 13:59:32 +0200 Subject: [PATCH 03/24] Add deps and entry points to pyproject --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4153db87..2a9392d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,8 @@ dependencies = [ "nomad-lab>=1.3.6", "xmltodict==0.13.0", "fairmat-readers-xrd>=0.0.3", + "nomad-material-processing", + "fairmat-readers-transmission", ] [project.urls] "Homepage" = "https://github.com/FAIRmat-NFDI/nomad-measurements" @@ -137,5 +139,7 @@ xrd_parser = "nomad_measurements.xrd:parser" ppms_schema = "nomad_measurements.ppms:ppms_schema" ppms_data_parser = "nomad_measurements.ppms:ppms_data_parser" ppms_sequence_parser = "nomad_measurements.ppms:ppms_sequence_parser" +transmission_schema = "nomad_measurements.transmission:schema" +transmission_parser = "nomad_measurements.transmission:parser" [tool.setuptools_scm] \ No newline at end of file From ba1239d810c085c612ee53d84c0ea038ff116be8 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 18:16:16 +0200 Subject: [PATCH 04/24] Bugfix: name of the file reader --- src/nomad_measurements/transmission/schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 210218b3..f036313a 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -39,7 +39,7 @@ import numpy as np import plotly.express as px -from fairmat_readers_transmission import read_asc +from fairmat_readers_transmission import read_perkin_elmer_asc from nomad.datamodel.data import ( ArchiveSection, EntryData, @@ -996,7 +996,7 @@ def get_read_write_functions(self) -> tuple[Callable, Callable]: tuple[Callable, Callable]: The read, write functions. """ if self.data_file.endswith('.asc'): - return read_asc, self.write_transmission_data + return read_perkin_elmer_asc, self.write_transmission_data return None, None def create_instrument_entry( From a9d0e7f636c3183dc83bf0cbd9ddb8e12306f0aa Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 18:18:10 +0200 Subject: [PATCH 05/24] Add transmission raw files --- .../transmission/3DM_test01.Probe.Raw.asc | 371 +++ .../F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc | 2271 +++++++++++++++++ tests/data/transmission/KTF-D.Probe.Raw.asc | 1091 ++++++++ .../transmission/Sample5926.Probe.Raw.asc | 201 ++ .../transmission/sphere_test01.Probe.Raw.asc | 131 + 5 files changed, 4065 insertions(+) create mode 100644 tests/data/transmission/3DM_test01.Probe.Raw.asc create mode 100644 tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc create mode 100644 tests/data/transmission/KTF-D.Probe.Raw.asc create mode 100644 tests/data/transmission/Sample5926.Probe.Raw.asc create mode 100644 tests/data/transmission/sphere_test01.Probe.Raw.asc diff --git a/tests/data/transmission/3DM_test01.Probe.Raw.asc b/tests/data/transmission/3DM_test01.Probe.Raw.asc new file mode 100644 index 00000000..b80316b1 --- /dev/null +++ b/tests/data/transmission/3DM_test01.Probe.Raw.asc @@ -0,0 +1,371 @@ +PE UV SUBTECH SPECTRUM ASCII PEDS 4.00 + -1 +Sample5933.Probe.Raw.asc +24/02/27 +16:45:12.00 +24/02/27 +16:45:12.00 +Administrator + +500.000000 +1 +Lambda 1050 +1050L1611222 +PerkinElmer UV WinLab 6.3.2.0749 / 2.02.06 Lambda 900 UV/VIS/NIR, Jan 28 2016 16:58:34 + +0 +0 +3350/8 1800.8/2.1 860.8/1.95 750/1.75 +0 +0 +UV/VIS +1 +1 +1 +DoubleDePol,CommonBeamDepol,RBeamAtt,SBeamAtt,uv/vis/nir detector, TDS WB InGaAs Detector +0 +0 +0 +0 +Program +Program +3350/8 1800.8/2.1 860.8/1.95 750/1.75 +3350/0.18 1800.8/0.17 860.8/0.16 +3350/0.18 1800.8/0.17 860.8/0.16 +0 +3350/1.00 1800.8/1.00 + + + +0 + +860.8 +319.2 +1800.8 860.8 +Front +100 +on +3350/S:100 3350/R:100 +0 +0 +0 +0 + +0 + + + +0 + + +0 +0 +0 +0 +0 + + + + +0 +0 + + +0 +0 +#HDR +-1 +-1 +#GR +nm +%T +1.0 +0.0 +1900.000000 +-5.000000 +281 +8 +100.180677 +99.867107 +#DATA +1900.000000 99.999792 +1895.000000 99.990752 +1890.000000 99.983362 +1885.000000 99.998647 +1880.000000 99.973769 +1875.000000 99.965883 +1870.000000 99.973821 +1865.000000 100.003643 +1860.000000 99.992077 +1855.000000 99.968627 +1850.000000 100.006673 +1845.000000 100.011893 +1840.000000 100.014502 +1835.000000 100.001148 +1830.000000 100.005331 +1825.000000 99.978755 +1820.000000 99.976116 +1815.000000 100.002307 +1810.000000 99.997061 +1805.000000 100.003466 +1800.000000 99.973295 +1795.000000 99.978354 +1790.000000 100.002680 +1785.000000 99.972590 +1780.000000 99.987737 +1775.000000 99.983612 +1770.000000 99.960325 +1765.000000 99.974032 +1760.000000 100.008350 +1755.000000 100.010206 +1750.000000 100.000103 +1745.000000 100.018869 +1740.000000 99.964335 +1735.000000 99.975570 +1730.000000 99.970004 +1725.000000 99.983095 +1720.000000 99.990414 +1715.000000 99.977326 +1710.000000 99.984231 +1705.000000 99.982067 +1700.000000 100.008248 +1695.000000 100.018039 +1690.000000 99.971249 +1685.000000 99.964357 +1680.000000 99.972794 +1675.000000 99.975271 +1670.000000 99.962275 +1665.000000 99.971030 +1660.000000 100.001134 +1655.000000 99.990926 +1650.000000 99.971954 +1645.000000 99.978346 +1640.000000 99.980924 +1635.000000 99.976591 +1630.000000 99.986800 +1625.000000 99.974322 +1620.000000 99.946695 +1615.000000 99.982573 +1610.000000 99.988966 +1605.000000 99.952479 +1600.000000 99.971963 +1595.000000 99.990514 +1590.000000 99.982989 +1585.000000 99.995875 +1580.000000 99.973607 +1575.000000 99.985254 +1570.000000 99.999072 +1565.000000 99.978660 +1560.000000 99.958248 +1555.000000 99.977008 +1550.000000 99.965877 +1545.000000 99.995051 +1540.000000 99.995567 +1535.000000 99.987940 +1530.000000 99.993197 +1525.000000 99.992783 +1520.000000 99.975672 +1515.000000 99.990826 +1510.000000 99.981139 +1505.000000 99.975782 +1500.000000 99.980007 +1495.000000 100.008451 +1490.000000 99.997321 +1485.000000 100.010615 +1480.000000 99.994436 +1475.000000 99.985060 +1470.000000 99.984235 +1465.000000 99.991034 +1460.000000 99.992269 +1455.000000 99.980829 +1450.000000 99.992785 +1445.000000 99.982482 +1440.000000 99.986914 +1435.000000 99.987429 +1430.000000 99.983927 +1425.000000 99.977340 +1420.000000 99.996807 +1415.000000 99.979505 +1410.000000 99.983520 +1405.000000 99.987122 +1400.000000 100.000412 +1395.000000 99.979595 +1390.000000 99.988978 +1385.000000 100.007522 +1380.000000 100.004946 +1375.000000 99.992481 +1370.000000 99.972714 +1365.000000 99.968176 +1360.000000 99.995158 +1355.000000 99.980955 +1350.000000 99.964264 +1345.000000 99.987640 +1340.000000 100.000309 +1335.000000 99.998455 +1330.000000 99.995572 +1325.000000 99.967670 +1320.000000 99.967054 +1315.000000 99.978890 +1310.000000 99.980022 +1305.000000 99.986404 +1300.000000 99.976722 +1295.000000 99.966429 +1290.000000 99.977345 +1285.000000 99.952429 +1280.000000 99.966841 +1275.000000 99.983829 +1270.000000 99.994025 +1265.000000 99.993405 +1260.000000 100.001752 +1255.000000 99.998866 +1250.000000 99.990727 +1245.000000 99.983107 +1240.000000 99.981874 +1235.000000 99.986098 +1230.000000 99.983521 +1225.000000 99.973013 +1220.000000 99.984236 +1215.000000 99.984851 +1210.000000 99.988251 +1205.000000 99.995054 +1200.000000 99.989285 +1195.000000 99.988256 +1190.000000 99.983825 +1185.000000 99.997630 +1180.000000 99.996187 +1175.000000 99.995363 +1170.000000 99.995465 +1165.000000 99.996392 +1160.000000 99.986289 +1155.000000 99.993093 +1150.000000 99.989175 +1145.000000 99.990308 +1140.000000 99.992370 +1135.000000 99.981442 +1130.000000 99.990515 +1125.000000 100.000000 +1120.000000 99.996803 +1115.000000 99.995566 +1110.000000 99.989481 +1105.000000 99.986182 +1100.000000 100.000206 +1095.000000 99.992161 +1090.000000 99.993398 +1085.000000 99.994842 +1080.000000 99.992881 +1075.000000 99.988443 +1070.000000 100.003199 +1065.000000 100.013934 +1060.000000 100.015895 +1055.000000 100.004851 +1050.000000 100.002890 +1045.000000 100.006297 +1040.000000 99.991536 +1035.000000 99.982141 +1030.000000 99.991534 +1025.000000 99.999690 +1020.000000 99.974915 +1015.000000 99.981828 +1010.000000 99.977182 +1005.000000 99.955916 +1000.000000 99.975834 +995.000000 99.979341 +990.000000 99.979647 +985.000000 99.993904 +980.000000 100.004546 +975.000000 100.006097 +970.000000 99.998863 +965.000000 99.998140 +960.000000 99.976026 +955.000000 99.969407 +950.000000 99.975088 +945.000000 100.014682 +940.000000 100.075297 +935.000000 100.031750 +930.000000 99.998759 +925.000000 99.990693 +920.000000 99.945830 +915.000000 99.950165 +910.000000 100.001035 +905.000000 100.002795 +900.000000 100.009007 +895.000000 100.019266 +890.000000 99.982295 +885.000000 99.984564 +880.000000 99.903020 +875.000000 99.867107 +870.000000 99.962899 +865.000000 99.871219 +860.000000 100.180677 +855.000000 100.003702 +850.000000 99.996516 +845.000000 100.064222 +840.000000 99.989845 +835.000000 99.999909 +830.000000 99.983570 +825.000000 100.054972 +820.000000 100.029217 +815.000000 100.020912 +810.000000 99.989538 +805.000000 100.011191 +800.000000 99.942096 +795.000000 99.991132 +790.000000 99.979512 +785.000000 99.959867 +780.000000 99.980799 +775.000000 100.030847 +770.000000 100.002744 +765.000000 100.020367 +760.000000 99.992272 +755.000000 100.012276 +750.000000 100.043443 +745.000000 100.018571 +740.000000 100.014626 +735.000000 100.015052 +730.000000 100.024208 +725.000000 100.016760 +720.000000 100.018709 +715.000000 99.986158 +710.000000 99.973551 +705.000000 99.966689 +700.000000 100.002203 +695.000000 99.977711 +690.000000 99.977934 +685.000000 99.963673 +680.000000 99.979749 +675.000000 99.988824 +670.000000 99.952862 +665.000000 100.034171 +660.000000 100.002447 +655.000000 100.032373 +650.000000 100.028693 +645.000000 99.980802 +640.000000 99.960663 +635.000000 100.025783 +630.000000 99.995252 +625.000000 99.985836 +620.000000 100.030648 +615.000000 100.028995 +610.000000 99.979231 +605.000000 99.991743 +600.000000 100.036150 +595.000000 99.951162 +590.000000 100.001996 +585.000000 99.985010 +580.000000 99.989393 +575.000000 99.993287 +570.000000 100.009631 +565.000000 99.962745 +560.000000 99.992258 +555.000000 99.997081 +550.000000 100.003931 +545.000000 99.989805 +540.000000 99.984232 +535.000000 99.974899 +530.000000 100.003651 +525.000000 100.007822 +520.000000 100.001831 +515.000000 100.005195 +510.000000 100.017528 +505.000000 99.995005 +500.000000 100.014483 diff --git a/tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc b/tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc new file mode 100644 index 00000000..4558a895 --- /dev/null +++ b/tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc @@ -0,0 +1,2271 @@ +PE UV SUBTECH SPECTRUM ASCII PEDS 4.00 + -1 +F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc +18/12/18 +14:45:59.00 +18/12/18 +14:45:59.00 +Analyst +Thin films - spincoated on 1 inch sq substrates, at 750 RPM for 1 min. Ref Glass +320.000000 +1 +Lambda 950 +950N9080502 +PerkinElmer UV WinLab 6.3.2.0749 / 1.61.00 Lambda 900 + +0 +0 +3350/servo 860.8/2 +0 +0 +UV/VIS +1 +1 +1 +RBeamAtt,SBeamAtt,uv/vis/nir detector +0 +0 +0 +0 +Program +Program +3350/servo 860.8/2 +3350/0.2 860.8/0.2 +3350/0.2 860.8/0.2 +0 +1 + + + +0 + +860.8 +319.2 +860.8 +Front +100 + +S:100 R:100 + +0 +0 +0 + +0 + + + +0 + + +0 +0 +0 +0 +0 + + + + +0 +0 + + +0 +0 +#HDR +-1 +-1 +#GR +nm +A +1.0 +0.0 +2500.000000 +-1.000000 +2181 +8 +0.075666 +0.006549 +#DATA +2500.000000 0.012944 +2499.000000 0.012845 +2498.000000 0.012919 +2497.000000 0.012954 +2496.000000 0.012956 +2495.000000 0.012887 +2494.000000 0.012817 +2493.000000 0.012855 +2492.000000 0.012790 +2491.000000 0.012938 +2490.000000 0.012920 +2489.000000 0.012825 +2488.000000 0.012964 +2487.000000 0.012968 +2486.000000 0.012839 +2485.000000 0.012950 +2484.000000 0.012962 +2483.000000 0.013005 +2482.000000 0.012893 +2481.000000 0.012666 +2480.000000 0.012970 +2479.000000 0.012912 +2478.000000 0.012886 +2477.000000 0.012896 +2476.000000 0.012911 +2475.000000 0.013040 +2474.000000 0.012941 +2473.000000 0.013006 +2472.000000 0.012956 +2471.000000 0.012998 +2470.000000 0.012802 +2469.000000 0.013033 +2468.000000 0.013017 +2467.000000 0.012919 +2466.000000 0.013037 +2465.000000 0.012988 +2464.000000 0.013021 +2463.000000 0.012970 +2462.000000 0.012943 +2461.000000 0.012901 +2460.000000 0.012861 +2459.000000 0.012939 +2458.000000 0.012916 +2457.000000 0.012984 +2456.000000 0.012884 +2455.000000 0.012899 +2454.000000 0.012892 +2453.000000 0.013076 +2452.000000 0.013063 +2451.000000 0.012950 +2450.000000 0.012989 +2449.000000 0.012965 +2448.000000 0.012998 +2447.000000 0.013090 +2446.000000 0.012877 +2445.000000 0.012923 +2444.000000 0.012974 +2443.000000 0.013079 +2442.000000 0.013042 +2441.000000 0.012931 +2440.000000 0.012945 +2439.000000 0.012986 +2438.000000 0.013047 +2437.000000 0.013096 +2436.000000 0.013037 +2435.000000 0.013053 +2434.000000 0.013006 +2433.000000 0.012936 +2432.000000 0.012973 +2431.000000 0.013121 +2430.000000 0.013024 +2429.000000 0.013017 +2428.000000 0.012944 +2427.000000 0.012940 +2426.000000 0.013038 +2425.000000 0.013094 +2424.000000 0.012995 +2423.000000 0.012987 +2422.000000 0.012996 +2421.000000 0.013033 +2420.000000 0.013055 +2419.000000 0.012975 +2418.000000 0.013055 +2417.000000 0.013042 +2416.000000 0.013150 +2415.000000 0.013030 +2414.000000 0.013107 +2413.000000 0.013163 +2412.000000 0.013052 +2411.000000 0.012992 +2410.000000 0.013019 +2409.000000 0.013047 +2408.000000 0.013136 +2407.000000 0.013079 +2406.000000 0.013089 +2405.000000 0.013221 +2404.000000 0.013144 +2403.000000 0.013085 +2402.000000 0.013130 +2401.000000 0.013107 +2400.000000 0.013062 +2399.000000 0.013069 +2398.000000 0.013095 +2397.000000 0.013089 +2396.000000 0.013119 +2395.000000 0.013179 +2394.000000 0.013246 +2393.000000 0.013257 +2392.000000 0.013134 +2391.000000 0.013155 +2390.000000 0.013157 +2389.000000 0.013055 +2388.000000 0.013117 +2387.000000 0.013137 +2386.000000 0.013108 +2385.000000 0.013111 +2384.000000 0.013117 +2383.000000 0.013103 +2382.000000 0.013069 +2381.000000 0.013068 +2380.000000 0.013070 +2379.000000 0.013129 +2378.000000 0.013198 +2377.000000 0.013193 +2376.000000 0.013176 +2375.000000 0.013114 +2374.000000 0.013036 +2373.000000 0.013007 +2372.000000 0.013070 +2371.000000 0.013073 +2370.000000 0.013015 +2369.000000 0.013096 +2368.000000 0.013118 +2367.000000 0.013068 +2366.000000 0.013032 +2365.000000 0.013062 +2364.000000 0.013179 +2363.000000 0.013218 +2362.000000 0.013148 +2361.000000 0.013134 +2360.000000 0.013207 +2359.000000 0.013187 +2358.000000 0.013099 +2357.000000 0.012979 +2356.000000 0.012972 +2355.000000 0.013007 +2354.000000 0.012997 +2353.000000 0.012969 +2352.000000 0.013004 +2351.000000 0.013070 +2350.000000 0.013040 +2349.000000 0.012988 +2348.000000 0.013064 +2347.000000 0.013083 +2346.000000 0.013136 +2345.000000 0.013067 +2344.000000 0.013122 +2343.000000 0.013273 +2342.000000 0.013136 +2341.000000 0.012987 +2340.000000 0.012984 +2339.000000 0.013064 +2338.000000 0.013057 +2337.000000 0.013022 +2336.000000 0.013075 +2335.000000 0.013019 +2334.000000 0.012935 +2333.000000 0.013017 +2332.000000 0.013054 +2331.000000 0.013104 +2330.000000 0.013137 +2329.000000 0.013091 +2328.000000 0.012995 +2327.000000 0.013002 +2326.000000 0.013005 +2325.000000 0.012934 +2324.000000 0.012993 +2323.000000 0.013005 +2322.000000 0.012931 +2321.000000 0.013060 +2320.000000 0.013135 +2319.000000 0.013104 +2318.000000 0.013118 +2317.000000 0.013104 +2316.000000 0.013007 +2315.000000 0.012965 +2314.000000 0.013062 +2313.000000 0.013037 +2312.000000 0.013040 +2311.000000 0.013009 +2310.000000 0.013179 +2309.000000 0.013043 +2308.000000 0.013081 +2307.000000 0.013052 +2306.000000 0.013007 +2305.000000 0.013014 +2304.000000 0.013023 +2303.000000 0.012999 +2302.000000 0.012952 +2301.000000 0.013133 +2300.000000 0.013123 +2299.000000 0.013084 +2298.000000 0.013065 +2297.000000 0.012925 +2296.000000 0.013000 +2295.000000 0.012999 +2294.000000 0.013011 +2293.000000 0.012997 +2292.000000 0.013003 +2291.000000 0.013087 +2290.000000 0.013099 +2289.000000 0.013153 +2288.000000 0.013047 +2287.000000 0.013026 +2286.000000 0.012950 +2285.000000 0.012991 +2284.000000 0.013014 +2283.000000 0.013143 +2282.000000 0.013139 +2281.000000 0.013042 +2280.000000 0.012976 +2279.000000 0.012940 +2278.000000 0.012914 +2277.000000 0.013028 +2276.000000 0.013193 +2275.000000 0.013093 +2274.000000 0.013062 +2273.000000 0.012902 +2272.000000 0.013044 +2271.000000 0.012943 +2270.000000 0.012961 +2269.000000 0.012941 +2268.000000 0.013123 +2267.000000 0.013084 +2266.000000 0.013035 +2265.000000 0.013089 +2264.000000 0.013084 +2263.000000 0.012977 +2262.000000 0.013107 +2261.000000 0.013091 +2260.000000 0.013172 +2259.000000 0.012927 +2258.000000 0.012922 +2257.000000 0.013047 +2256.000000 0.013093 +2255.000000 0.013094 +2254.000000 0.013082 +2253.000000 0.013098 +2252.000000 0.013058 +2251.000000 0.013064 +2250.000000 0.013043 +2249.000000 0.012997 +2248.000000 0.013113 +2247.000000 0.013100 +2246.000000 0.013032 +2245.000000 0.012936 +2244.000000 0.012942 +2243.000000 0.012901 +2242.000000 0.013015 +2241.000000 0.013049 +2240.000000 0.013093 +2239.000000 0.013141 +2238.000000 0.012989 +2237.000000 0.013001 +2236.000000 0.013112 +2235.000000 0.012984 +2234.000000 0.013018 +2233.000000 0.013248 +2232.000000 0.013006 +2231.000000 0.012997 +2230.000000 0.012987 +2229.000000 0.012946 +2228.000000 0.013086 +2227.000000 0.013025 +2226.000000 0.013175 +2225.000000 0.013012 +2224.000000 0.013036 +2223.000000 0.013049 +2222.000000 0.013004 +2221.000000 0.013046 +2220.000000 0.013084 +2219.000000 0.013053 +2218.000000 0.013072 +2217.000000 0.012933 +2216.000000 0.013033 +2215.000000 0.013016 +2214.000000 0.012940 +2213.000000 0.012962 +2212.000000 0.013032 +2211.000000 0.013164 +2210.000000 0.012953 +2209.000000 0.012997 +2208.000000 0.013024 +2207.000000 0.012981 +2206.000000 0.012954 +2205.000000 0.013047 +2204.000000 0.013075 +2203.000000 0.012986 +2202.000000 0.012944 +2201.000000 0.012919 +2200.000000 0.013095 +2199.000000 0.012959 +2198.000000 0.013072 +2197.000000 0.013002 +2196.000000 0.013022 +2195.000000 0.013024 +2194.000000 0.012837 +2193.000000 0.012989 +2192.000000 0.012929 +2191.000000 0.013010 +2190.000000 0.013007 +2189.000000 0.012967 +2188.000000 0.013162 +2187.000000 0.013059 +2186.000000 0.013066 +2185.000000 0.013040 +2184.000000 0.012931 +2183.000000 0.012934 +2182.000000 0.012930 +2181.000000 0.013084 +2180.000000 0.013079 +2179.000000 0.013028 +2178.000000 0.013012 +2177.000000 0.013083 +2176.000000 0.013145 +2175.000000 0.013031 +2174.000000 0.013050 +2173.000000 0.012970 +2172.000000 0.013020 +2171.000000 0.012929 +2170.000000 0.012969 +2169.000000 0.012912 +2168.000000 0.012942 +2167.000000 0.012992 +2166.000000 0.012988 +2165.000000 0.012941 +2164.000000 0.013052 +2163.000000 0.012908 +2162.000000 0.012918 +2161.000000 0.012889 +2160.000000 0.012848 +2159.000000 0.012975 +2158.000000 0.012916 +2157.000000 0.012820 +2156.000000 0.012846 +2155.000000 0.012847 +2154.000000 0.012802 +2153.000000 0.012935 +2152.000000 0.012860 +2151.000000 0.012943 +2150.000000 0.012894 +2149.000000 0.012798 +2148.000000 0.012885 +2147.000000 0.012808 +2146.000000 0.012843 +2145.000000 0.012849 +2144.000000 0.012826 +2143.000000 0.012843 +2142.000000 0.012822 +2141.000000 0.012824 +2140.000000 0.012839 +2139.000000 0.012868 +2138.000000 0.012838 +2137.000000 0.012777 +2136.000000 0.012771 +2135.000000 0.012826 +2134.000000 0.012812 +2133.000000 0.012743 +2132.000000 0.012747 +2131.000000 0.012768 +2130.000000 0.012780 +2129.000000 0.012772 +2128.000000 0.012763 +2127.000000 0.012807 +2126.000000 0.012798 +2125.000000 0.012761 +2124.000000 0.012730 +2123.000000 0.012747 +2122.000000 0.012739 +2121.000000 0.012750 +2120.000000 0.012759 +2119.000000 0.012717 +2118.000000 0.012746 +2117.000000 0.012723 +2116.000000 0.012674 +2115.000000 0.012703 +2114.000000 0.012793 +2113.000000 0.012714 +2112.000000 0.012646 +2111.000000 0.012639 +2110.000000 0.012709 +2109.000000 0.012712 +2108.000000 0.012609 +2107.000000 0.012652 +2106.000000 0.012708 +2105.000000 0.012690 +2104.000000 0.012635 +2103.000000 0.012575 +2102.000000 0.012599 +2101.000000 0.012592 +2100.000000 0.012561 +2099.000000 0.012626 +2098.000000 0.012605 +2097.000000 0.012566 +2096.000000 0.012542 +2095.000000 0.012550 +2094.000000 0.012553 +2093.000000 0.012553 +2092.000000 0.012584 +2091.000000 0.012574 +2090.000000 0.012582 +2089.000000 0.012603 +2088.000000 0.012612 +2087.000000 0.012553 +2086.000000 0.012469 +2085.000000 0.012474 +2084.000000 0.012475 +2083.000000 0.012470 +2082.000000 0.012520 +2081.000000 0.012544 +2080.000000 0.012584 +2079.000000 0.012558 +2078.000000 0.012594 +2077.000000 0.012605 +2076.000000 0.012508 +2075.000000 0.012431 +2074.000000 0.012446 +2073.000000 0.012444 +2072.000000 0.012343 +2071.000000 0.012365 +2070.000000 0.012449 +2069.000000 0.012404 +2068.000000 0.012390 +2067.000000 0.012394 +2066.000000 0.012421 +2065.000000 0.012373 +2064.000000 0.012424 +2063.000000 0.012418 +2062.000000 0.012383 +2061.000000 0.012411 +2060.000000 0.012424 +2059.000000 0.012377 +2058.000000 0.012305 +2057.000000 0.012292 +2056.000000 0.012364 +2055.000000 0.012347 +2054.000000 0.012256 +2053.000000 0.012299 +2052.000000 0.012265 +2051.000000 0.012229 +2050.000000 0.012263 +2049.000000 0.012374 +2048.000000 0.012326 +2047.000000 0.012300 +2046.000000 0.012348 +2045.000000 0.012364 +2044.000000 0.012343 +2043.000000 0.012277 +2042.000000 0.012388 +2041.000000 0.012414 +2040.000000 0.012273 +2039.000000 0.012229 +2038.000000 0.012240 +2037.000000 0.012269 +2036.000000 0.012178 +2035.000000 0.012182 +2034.000000 0.012182 +2033.000000 0.012108 +2032.000000 0.012139 +2031.000000 0.012103 +2030.000000 0.012024 +2029.000000 0.012025 +2028.000000 0.012070 +2027.000000 0.012106 +2026.000000 0.012144 +2025.000000 0.012150 +2024.000000 0.012094 +2023.000000 0.012065 +2022.000000 0.012060 +2021.000000 0.012011 +2020.000000 0.011973 +2019.000000 0.012051 +2018.000000 0.012054 +2017.000000 0.012079 +2016.000000 0.011949 +2015.000000 0.012068 +2014.000000 0.012067 +2013.000000 0.012047 +2012.000000 0.011958 +2011.000000 0.011923 +2010.000000 0.011983 +2009.000000 0.011935 +2008.000000 0.011935 +2007.000000 0.011878 +2006.000000 0.011919 +2005.000000 0.011859 +2004.000000 0.011833 +2003.000000 0.011874 +2002.000000 0.011959 +2001.000000 0.011943 +2000.000000 0.011859 +1999.000000 0.011875 +1998.000000 0.011993 +1997.000000 0.011866 +1996.000000 0.011798 +1995.000000 0.011765 +1994.000000 0.011697 +1993.000000 0.011743 +1992.000000 0.011839 +1991.000000 0.011858 +1990.000000 0.011710 +1989.000000 0.011850 +1988.000000 0.011641 +1987.000000 0.011789 +1986.000000 0.011768 +1985.000000 0.011645 +1984.000000 0.011684 +1983.000000 0.011749 +1982.000000 0.011803 +1981.000000 0.011652 +1980.000000 0.011657 +1979.000000 0.011790 +1978.000000 0.011559 +1977.000000 0.011527 +1976.000000 0.011438 +1975.000000 0.011617 +1974.000000 0.011553 +1973.000000 0.011618 +1972.000000 0.011531 +1971.000000 0.011659 +1970.000000 0.011579 +1969.000000 0.011472 +1968.000000 0.011452 +1967.000000 0.011500 +1966.000000 0.011551 +1965.000000 0.011443 +1964.000000 0.011412 +1963.000000 0.011462 +1962.000000 0.011552 +1961.000000 0.011423 +1960.000000 0.011452 +1959.000000 0.011401 +1958.000000 0.011401 +1957.000000 0.011447 +1956.000000 0.011371 +1955.000000 0.011311 +1954.000000 0.011310 +1953.000000 0.011291 +1952.000000 0.011275 +1951.000000 0.011224 +1950.000000 0.011244 +1949.000000 0.011290 +1948.000000 0.011159 +1947.000000 0.011251 +1946.000000 0.010951 +1945.000000 0.011283 +1944.000000 0.011083 +1943.000000 0.010989 +1942.000000 0.010966 +1941.000000 0.010870 +1940.000000 0.011099 +1939.000000 0.010960 +1938.000000 0.010971 +1937.000000 0.010953 +1936.000000 0.010968 +1935.000000 0.010984 +1934.000000 0.010824 +1933.000000 0.010957 +1932.000000 0.010728 +1931.000000 0.010940 +1930.000000 0.010709 +1929.000000 0.010670 +1928.000000 0.010481 +1927.000000 0.010463 +1926.000000 0.010448 +1925.000000 0.010546 +1924.000000 0.010623 +1923.000000 0.010542 +1922.000000 0.010346 +1921.000000 0.010471 +1920.000000 0.010497 +1919.000000 0.010309 +1918.000000 0.010341 +1917.000000 0.010402 +1916.000000 0.010453 +1915.000000 0.010561 +1914.000000 0.010260 +1913.000000 0.010468 +1912.000000 0.010263 +1911.000000 0.010521 +1910.000000 0.010680 +1909.000000 0.010736 +1908.000000 0.010480 +1907.000000 0.010465 +1906.000000 0.010522 +1905.000000 0.010722 +1904.000000 0.010754 +1903.000000 0.010713 +1902.000000 0.010816 +1901.000000 0.010739 +1900.000000 0.010596 +1899.000000 0.010543 +1898.000000 0.010475 +1897.000000 0.010368 +1896.000000 0.010485 +1895.000000 0.010454 +1894.000000 0.010294 +1893.000000 0.010365 +1892.000000 0.010190 +1891.000000 0.010192 +1890.000000 0.010119 +1889.000000 0.010278 +1888.000000 0.010223 +1887.000000 0.010201 +1886.000000 0.010237 +1885.000000 0.010156 +1884.000000 0.010232 +1883.000000 0.010225 +1882.000000 0.010168 +1881.000000 0.010195 +1880.000000 0.010125 +1879.000000 0.010056 +1878.000000 0.010106 +1877.000000 0.010075 +1876.000000 0.009866 +1875.000000 0.009820 +1874.000000 0.009833 +1873.000000 0.009909 +1872.000000 0.009900 +1871.000000 0.009938 +1870.000000 0.010017 +1869.000000 0.009967 +1868.000000 0.010004 +1867.000000 0.010159 +1866.000000 0.010100 +1865.000000 0.010224 +1864.000000 0.010278 +1863.000000 0.010078 +1862.000000 0.009966 +1861.000000 0.009993 +1860.000000 0.010078 +1859.000000 0.010150 +1858.000000 0.010170 +1857.000000 0.010201 +1856.000000 0.010202 +1855.000000 0.010154 +1854.000000 0.010158 +1853.000000 0.010301 +1852.000000 0.010291 +1851.000000 0.010203 +1850.000000 0.010149 +1849.000000 0.010029 +1848.000000 0.009969 +1847.000000 0.010026 +1846.000000 0.010209 +1845.000000 0.010401 +1844.000000 0.009992 +1843.000000 0.009725 +1842.000000 0.009557 +1841.000000 0.009891 +1840.000000 0.009907 +1839.000000 0.009729 +1838.000000 0.009673 +1837.000000 0.009627 +1836.000000 0.009554 +1835.000000 0.009339 +1834.000000 0.009349 +1833.000000 0.009683 +1832.000000 0.009896 +1831.000000 0.009920 +1830.000000 0.010037 +1829.000000 0.010152 +1828.000000 0.010024 +1827.000000 0.009870 +1826.000000 0.009865 +1825.000000 0.009991 +1824.000000 0.009909 +1823.000000 0.009718 +1822.000000 0.009444 +1821.000000 0.009352 +1820.000000 0.009480 +1819.000000 0.009477 +1818.000000 0.009621 +1817.000000 0.009699 +1816.000000 0.009644 +1815.000000 0.009681 +1814.000000 0.009752 +1813.000000 0.009743 +1812.000000 0.009733 +1811.000000 0.009683 +1810.000000 0.009744 +1809.000000 0.009757 +1808.000000 0.009627 +1807.000000 0.009654 +1806.000000 0.009739 +1805.000000 0.009648 +1804.000000 0.009556 +1803.000000 0.009546 +1802.000000 0.009503 +1801.000000 0.009605 +1800.000000 0.009716 +1799.000000 0.009650 +1798.000000 0.009561 +1797.000000 0.009407 +1796.000000 0.009429 +1795.000000 0.009517 +1794.000000 0.009523 +1793.000000 0.009499 +1792.000000 0.009510 +1791.000000 0.009484 +1790.000000 0.009487 +1789.000000 0.009492 +1788.000000 0.009440 +1787.000000 0.009373 +1786.000000 0.009336 +1785.000000 0.009430 +1784.000000 0.009358 +1783.000000 0.009283 +1782.000000 0.009253 +1781.000000 0.009191 +1780.000000 0.009388 +1779.000000 0.009509 +1778.000000 0.009138 +1777.000000 0.009107 +1776.000000 0.009289 +1775.000000 0.009267 +1774.000000 0.009318 +1773.000000 0.009273 +1772.000000 0.009239 +1771.000000 0.009235 +1770.000000 0.009256 +1769.000000 0.009281 +1768.000000 0.009228 +1767.000000 0.009136 +1766.000000 0.009096 +1765.000000 0.009109 +1764.000000 0.009041 +1763.000000 0.009067 +1762.000000 0.009079 +1761.000000 0.009070 +1760.000000 0.009038 +1759.000000 0.008994 +1758.000000 0.008962 +1757.000000 0.008937 +1756.000000 0.008962 +1755.000000 0.008973 +1754.000000 0.008998 +1753.000000 0.008979 +1752.000000 0.008979 +1751.000000 0.008943 +1750.000000 0.008894 +1749.000000 0.008872 +1748.000000 0.008848 +1747.000000 0.008895 +1746.000000 0.008821 +1745.000000 0.008816 +1744.000000 0.008874 +1743.000000 0.008912 +1742.000000 0.008795 +1741.000000 0.008796 +1740.000000 0.008845 +1739.000000 0.008838 +1738.000000 0.008808 +1737.000000 0.008714 +1736.000000 0.008703 +1735.000000 0.008673 +1734.000000 0.008847 +1733.000000 0.008772 +1732.000000 0.008699 +1731.000000 0.008671 +1730.000000 0.008762 +1729.000000 0.008718 +1728.000000 0.008678 +1727.000000 0.008644 +1726.000000 0.008637 +1725.000000 0.008673 +1724.000000 0.008645 +1723.000000 0.008663 +1722.000000 0.008589 +1721.000000 0.008626 +1720.000000 0.008651 +1719.000000 0.008697 +1718.000000 0.008549 +1717.000000 0.008568 +1716.000000 0.008535 +1715.000000 0.008479 +1714.000000 0.008462 +1713.000000 0.008511 +1712.000000 0.008575 +1711.000000 0.008521 +1710.000000 0.008373 +1709.000000 0.008461 +1708.000000 0.008553 +1707.000000 0.008600 +1706.000000 0.008518 +1705.000000 0.008564 +1704.000000 0.008542 +1703.000000 0.008562 +1702.000000 0.008419 +1701.000000 0.008397 +1700.000000 0.008420 +1699.000000 0.008392 +1698.000000 0.008450 +1697.000000 0.008333 +1696.000000 0.008205 +1695.000000 0.008246 +1694.000000 0.008244 +1693.000000 0.008395 +1692.000000 0.008370 +1691.000000 0.008378 +1690.000000 0.008328 +1689.000000 0.008265 +1688.000000 0.008344 +1687.000000 0.008458 +1686.000000 0.008287 +1685.000000 0.008223 +1684.000000 0.008230 +1683.000000 0.008189 +1682.000000 0.008164 +1681.000000 0.008185 +1680.000000 0.008232 +1679.000000 0.008159 +1678.000000 0.008176 +1677.000000 0.008209 +1676.000000 0.008164 +1675.000000 0.008302 +1674.000000 0.008262 +1673.000000 0.008264 +1672.000000 0.008095 +1671.000000 0.008070 +1670.000000 0.008215 +1669.000000 0.008160 +1668.000000 0.008178 +1667.000000 0.008112 +1666.000000 0.008147 +1665.000000 0.008054 +1664.000000 0.008113 +1663.000000 0.007990 +1662.000000 0.008074 +1661.000000 0.008131 +1660.000000 0.008185 +1659.000000 0.008027 +1658.000000 0.008029 +1657.000000 0.007983 +1656.000000 0.008048 +1655.000000 0.007959 +1654.000000 0.007930 +1653.000000 0.007957 +1652.000000 0.007993 +1651.000000 0.007914 +1650.000000 0.008037 +1649.000000 0.008114 +1648.000000 0.007886 +1647.000000 0.007935 +1646.000000 0.007978 +1645.000000 0.007848 +1644.000000 0.008014 +1643.000000 0.007933 +1642.000000 0.007987 +1641.000000 0.007918 +1640.000000 0.007931 +1639.000000 0.007897 +1638.000000 0.007951 +1637.000000 0.007974 +1636.000000 0.007953 +1635.000000 0.007928 +1634.000000 0.007925 +1633.000000 0.007830 +1632.000000 0.007818 +1631.000000 0.007884 +1630.000000 0.007941 +1629.000000 0.007858 +1628.000000 0.007828 +1627.000000 0.007818 +1626.000000 0.007823 +1625.000000 0.007859 +1624.000000 0.007831 +1623.000000 0.007794 +1622.000000 0.007856 +1621.000000 0.007832 +1620.000000 0.007744 +1619.000000 0.007859 +1618.000000 0.007868 +1617.000000 0.007780 +1616.000000 0.007766 +1615.000000 0.007846 +1614.000000 0.007742 +1613.000000 0.007731 +1612.000000 0.007694 +1611.000000 0.007815 +1610.000000 0.007828 +1609.000000 0.007800 +1608.000000 0.007774 +1607.000000 0.007687 +1606.000000 0.007784 +1605.000000 0.007765 +1604.000000 0.007764 +1603.000000 0.007762 +1602.000000 0.007746 +1601.000000 0.007721 +1600.000000 0.007646 +1599.000000 0.007600 +1598.000000 0.007651 +1597.000000 0.007625 +1596.000000 0.007612 +1595.000000 0.007687 +1594.000000 0.007604 +1593.000000 0.007609 +1592.000000 0.007678 +1591.000000 0.007603 +1590.000000 0.007524 +1589.000000 0.007518 +1588.000000 0.007596 +1587.000000 0.007573 +1586.000000 0.007625 +1585.000000 0.007571 +1584.000000 0.007560 +1583.000000 0.007528 +1582.000000 0.007556 +1581.000000 0.007742 +1580.000000 0.008087 +1579.000000 0.008043 +1578.000000 0.007938 +1577.000000 0.007860 +1576.000000 0.007818 +1575.000000 0.007796 +1574.000000 0.007849 +1573.000000 0.007752 +1572.000000 0.007762 +1571.000000 0.007787 +1570.000000 0.007785 +1569.000000 0.007799 +1568.000000 0.007797 +1567.000000 0.007752 +1566.000000 0.007712 +1565.000000 0.007801 +1564.000000 0.007843 +1563.000000 0.007831 +1562.000000 0.007769 +1561.000000 0.007674 +1560.000000 0.007694 +1559.000000 0.007752 +1558.000000 0.007684 +1557.000000 0.007716 +1556.000000 0.007729 +1555.000000 0.007686 +1554.000000 0.007683 +1553.000000 0.007672 +1552.000000 0.007627 +1551.000000 0.007705 +1550.000000 0.007738 +1549.000000 0.007676 +1548.000000 0.007634 +1547.000000 0.007668 +1546.000000 0.007738 +1545.000000 0.007771 +1544.000000 0.007594 +1543.000000 0.007545 +1542.000000 0.007652 +1541.000000 0.007716 +1540.000000 0.007663 +1539.000000 0.007589 +1538.000000 0.007644 +1537.000000 0.007668 +1536.000000 0.007600 +1535.000000 0.007541 +1534.000000 0.007566 +1533.000000 0.007665 +1532.000000 0.007624 +1531.000000 0.007541 +1530.000000 0.007530 +1529.000000 0.007627 +1528.000000 0.007746 +1527.000000 0.007602 +1526.000000 0.007533 +1525.000000 0.007602 +1524.000000 0.007568 +1523.000000 0.007521 +1522.000000 0.007533 +1521.000000 0.007442 +1520.000000 0.007443 +1519.000000 0.007457 +1518.000000 0.007474 +1517.000000 0.007519 +1516.000000 0.007527 +1515.000000 0.007582 +1514.000000 0.007622 +1513.000000 0.007586 +1512.000000 0.007540 +1511.000000 0.007483 +1510.000000 0.007447 +1509.000000 0.007415 +1508.000000 0.007366 +1507.000000 0.007431 +1506.000000 0.007525 +1505.000000 0.007412 +1504.000000 0.007449 +1503.000000 0.007403 +1502.000000 0.007320 +1501.000000 0.007408 +1500.000000 0.007469 +1499.000000 0.007439 +1498.000000 0.007486 +1497.000000 0.007499 +1496.000000 0.007344 +1495.000000 0.007359 +1494.000000 0.007473 +1493.000000 0.007422 +1492.000000 0.007431 +1491.000000 0.007448 +1490.000000 0.007451 +1489.000000 0.007503 +1488.000000 0.007508 +1487.000000 0.007372 +1486.000000 0.007282 +1485.000000 0.007355 +1484.000000 0.007400 +1483.000000 0.007424 +1482.000000 0.007414 +1481.000000 0.007378 +1480.000000 0.007307 +1479.000000 0.007376 +1478.000000 0.007335 +1477.000000 0.007249 +1476.000000 0.007233 +1475.000000 0.007187 +1474.000000 0.007289 +1473.000000 0.007354 +1472.000000 0.007285 +1471.000000 0.007307 +1470.000000 0.007351 +1469.000000 0.007369 +1468.000000 0.007290 +1467.000000 0.007290 +1466.000000 0.007268 +1465.000000 0.007278 +1464.000000 0.007217 +1463.000000 0.007261 +1462.000000 0.007228 +1461.000000 0.007243 +1460.000000 0.007233 +1459.000000 0.007253 +1458.000000 0.007195 +1457.000000 0.007366 +1456.000000 0.007248 +1455.000000 0.007305 +1454.000000 0.007226 +1453.000000 0.007246 +1452.000000 0.007292 +1451.000000 0.007333 +1450.000000 0.007353 +1449.000000 0.007307 +1448.000000 0.007263 +1447.000000 0.007151 +1446.000000 0.007239 +1445.000000 0.007256 +1444.000000 0.007191 +1443.000000 0.007155 +1442.000000 0.007180 +1441.000000 0.007133 +1440.000000 0.007234 +1439.000000 0.007215 +1438.000000 0.007147 +1437.000000 0.007184 +1436.000000 0.007222 +1435.000000 0.007308 +1434.000000 0.007181 +1433.000000 0.007224 +1432.000000 0.007078 +1431.000000 0.007207 +1430.000000 0.007149 +1429.000000 0.007243 +1428.000000 0.007115 +1427.000000 0.007108 +1426.000000 0.007171 +1425.000000 0.007037 +1424.000000 0.007117 +1423.000000 0.007045 +1422.000000 0.007177 +1421.000000 0.007182 +1420.000000 0.007136 +1419.000000 0.007208 +1418.000000 0.007043 +1417.000000 0.007422 +1416.000000 0.007024 +1415.000000 0.007561 +1414.000000 0.007128 +1413.000000 0.007196 +1412.000000 0.007014 +1411.000000 0.006982 +1410.000000 0.007189 +1409.000000 0.007239 +1408.000000 0.007265 +1407.000000 0.006968 +1406.000000 0.006988 +1405.000000 0.007071 +1404.000000 0.007036 +1403.000000 0.007214 +1402.000000 0.007124 +1401.000000 0.006950 +1400.000000 0.006984 +1399.000000 0.007306 +1398.000000 0.006868 +1397.000000 0.006958 +1396.000000 0.007002 +1395.000000 0.007014 +1394.000000 0.007233 +1393.000000 0.007361 +1392.000000 0.006844 +1391.000000 0.007240 +1390.000000 0.007089 +1389.000000 0.006888 +1388.000000 0.007075 +1387.000000 0.007300 +1386.000000 0.006968 +1385.000000 0.007117 +1384.000000 0.007076 +1383.000000 0.006983 +1382.000000 0.007097 +1381.000000 0.006989 +1380.000000 0.007159 +1379.000000 0.006936 +1378.000000 0.007028 +1377.000000 0.007211 +1376.000000 0.007031 +1375.000000 0.007098 +1374.000000 0.006994 +1373.000000 0.006883 +1372.000000 0.007041 +1371.000000 0.007422 +1370.000000 0.007114 +1369.000000 0.006856 +1368.000000 0.006717 +1367.000000 0.007083 +1366.000000 0.007021 +1365.000000 0.006944 +1364.000000 0.006861 +1363.000000 0.006802 +1362.000000 0.006965 +1361.000000 0.006549 +1360.000000 0.007098 +1359.000000 0.007049 +1358.000000 0.006943 +1357.000000 0.007300 +1356.000000 0.007280 +1355.000000 0.007154 +1354.000000 0.007274 +1353.000000 0.007017 +1352.000000 0.006898 +1351.000000 0.006969 +1350.000000 0.007030 +1349.000000 0.007001 +1348.000000 0.007092 +1347.000000 0.007059 +1346.000000 0.007049 +1345.000000 0.006996 +1344.000000 0.007005 +1343.000000 0.007071 +1342.000000 0.006934 +1341.000000 0.006957 +1340.000000 0.007035 +1339.000000 0.006936 +1338.000000 0.006987 +1337.000000 0.006939 +1336.000000 0.006954 +1335.000000 0.006982 +1334.000000 0.006980 +1333.000000 0.007013 +1332.000000 0.007094 +1331.000000 0.007005 +1330.000000 0.006957 +1329.000000 0.007092 +1328.000000 0.007020 +1327.000000 0.006930 +1326.000000 0.006943 +1325.000000 0.007115 +1324.000000 0.007079 +1323.000000 0.007086 +1322.000000 0.007045 +1321.000000 0.007017 +1320.000000 0.006998 +1319.000000 0.007064 +1318.000000 0.007024 +1317.000000 0.007039 +1316.000000 0.007041 +1315.000000 0.007069 +1314.000000 0.007138 +1313.000000 0.007208 +1312.000000 0.007080 +1311.000000 0.006998 +1310.000000 0.007017 +1309.000000 0.007072 +1308.000000 0.006989 +1307.000000 0.007022 +1306.000000 0.007038 +1305.000000 0.007049 +1304.000000 0.007017 +1303.000000 0.006952 +1302.000000 0.007016 +1301.000000 0.007046 +1300.000000 0.006985 +1299.000000 0.007074 +1298.000000 0.007050 +1297.000000 0.007091 +1296.000000 0.007115 +1295.000000 0.007041 +1294.000000 0.006982 +1293.000000 0.007101 +1292.000000 0.007107 +1291.000000 0.007101 +1290.000000 0.007042 +1289.000000 0.006985 +1288.000000 0.007048 +1287.000000 0.007051 +1286.000000 0.007015 +1285.000000 0.006936 +1284.000000 0.006933 +1283.000000 0.006964 +1282.000000 0.006972 +1281.000000 0.007062 +1280.000000 0.007089 +1279.000000 0.007070 +1278.000000 0.006999 +1277.000000 0.007006 +1276.000000 0.007046 +1275.000000 0.007021 +1274.000000 0.007015 +1273.000000 0.007004 +1272.000000 0.007081 +1271.000000 0.007106 +1270.000000 0.007003 +1269.000000 0.006995 +1268.000000 0.007010 +1267.000000 0.006989 +1266.000000 0.007010 +1265.000000 0.007017 +1264.000000 0.007043 +1263.000000 0.007028 +1262.000000 0.007023 +1261.000000 0.007015 +1260.000000 0.006993 +1259.000000 0.007011 +1258.000000 0.007035 +1257.000000 0.007050 +1256.000000 0.007030 +1255.000000 0.007000 +1254.000000 0.007005 +1253.000000 0.006980 +1252.000000 0.006961 +1251.000000 0.007021 +1250.000000 0.007031 +1249.000000 0.007007 +1248.000000 0.006961 +1247.000000 0.007097 +1246.000000 0.007095 +1245.000000 0.007005 +1244.000000 0.006968 +1243.000000 0.007078 +1242.000000 0.007150 +1241.000000 0.007078 +1240.000000 0.006980 +1239.000000 0.006949 +1238.000000 0.006982 +1237.000000 0.006982 +1236.000000 0.006892 +1235.000000 0.006915 +1234.000000 0.007013 +1233.000000 0.006998 +1232.000000 0.007008 +1231.000000 0.007025 +1230.000000 0.007075 +1229.000000 0.007111 +1228.000000 0.007056 +1227.000000 0.007048 +1226.000000 0.007037 +1225.000000 0.007085 +1224.000000 0.007077 +1223.000000 0.007078 +1222.000000 0.006936 +1221.000000 0.006988 +1220.000000 0.007075 +1219.000000 0.007069 +1218.000000 0.007090 +1217.000000 0.007034 +1216.000000 0.007022 +1215.000000 0.007058 +1214.000000 0.007077 +1213.000000 0.007124 +1212.000000 0.007117 +1211.000000 0.007041 +1210.000000 0.007105 +1209.000000 0.007182 +1208.000000 0.007182 +1207.000000 0.007146 +1206.000000 0.007134 +1205.000000 0.007169 +1204.000000 0.007234 +1203.000000 0.007197 +1202.000000 0.007101 +1201.000000 0.007155 +1200.000000 0.007190 +1199.000000 0.007227 +1198.000000 0.007136 +1197.000000 0.007109 +1196.000000 0.007171 +1195.000000 0.007193 +1194.000000 0.007088 +1193.000000 0.007127 +1192.000000 0.007101 +1191.000000 0.007214 +1190.000000 0.007195 +1189.000000 0.007127 +1188.000000 0.007218 +1187.000000 0.007306 +1186.000000 0.007134 +1185.000000 0.007132 +1184.000000 0.007183 +1183.000000 0.007132 +1182.000000 0.007100 +1181.000000 0.007143 +1180.000000 0.007166 +1179.000000 0.007059 +1178.000000 0.007105 +1177.000000 0.007093 +1176.000000 0.007176 +1175.000000 0.007233 +1174.000000 0.007277 +1173.000000 0.007225 +1172.000000 0.007248 +1171.000000 0.007252 +1170.000000 0.007249 +1169.000000 0.007329 +1168.000000 0.007298 +1167.000000 0.007310 +1166.000000 0.007305 +1165.000000 0.007291 +1164.000000 0.007291 +1163.000000 0.007321 +1162.000000 0.007287 +1161.000000 0.007313 +1160.000000 0.007318 +1159.000000 0.007341 +1158.000000 0.007409 +1157.000000 0.007363 +1156.000000 0.007268 +1155.000000 0.007224 +1154.000000 0.007359 +1153.000000 0.007368 +1152.000000 0.007260 +1151.000000 0.007351 +1150.000000 0.007306 +1149.000000 0.007403 +1148.000000 0.007490 +1147.000000 0.007358 +1146.000000 0.007323 +1145.000000 0.007406 +1144.000000 0.007347 +1143.000000 0.007430 +1142.000000 0.007463 +1141.000000 0.007510 +1140.000000 0.007425 +1139.000000 0.007501 +1138.000000 0.007522 +1137.000000 0.007551 +1136.000000 0.007427 +1135.000000 0.007488 +1134.000000 0.007565 +1133.000000 0.007574 +1132.000000 0.007488 +1131.000000 0.007616 +1130.000000 0.007521 +1129.000000 0.007682 +1128.000000 0.007734 +1127.000000 0.007571 +1126.000000 0.007639 +1125.000000 0.007670 +1124.000000 0.007721 +1123.000000 0.007718 +1122.000000 0.007703 +1121.000000 0.007568 +1120.000000 0.007616 +1119.000000 0.007683 +1118.000000 0.007739 +1117.000000 0.007652 +1116.000000 0.007764 +1115.000000 0.007776 +1114.000000 0.007807 +1113.000000 0.007817 +1112.000000 0.007770 +1111.000000 0.007837 +1110.000000 0.007851 +1109.000000 0.007871 +1108.000000 0.007881 +1107.000000 0.008088 +1106.000000 0.008049 +1105.000000 0.007899 +1104.000000 0.008035 +1103.000000 0.008133 +1102.000000 0.008230 +1101.000000 0.008134 +1100.000000 0.008154 +1099.000000 0.008229 +1098.000000 0.008042 +1097.000000 0.008166 +1096.000000 0.008214 +1095.000000 0.008248 +1094.000000 0.008268 +1093.000000 0.008280 +1092.000000 0.008348 +1091.000000 0.008336 +1090.000000 0.008579 +1089.000000 0.008366 +1088.000000 0.008468 +1087.000000 0.008486 +1086.000000 0.008517 +1085.000000 0.008522 +1084.000000 0.008586 +1083.000000 0.008600 +1082.000000 0.008550 +1081.000000 0.008638 +1080.000000 0.008573 +1079.000000 0.008630 +1078.000000 0.008672 +1077.000000 0.008731 +1076.000000 0.008757 +1075.000000 0.008863 +1074.000000 0.008895 +1073.000000 0.009009 +1072.000000 0.009033 +1071.000000 0.008953 +1070.000000 0.009024 +1069.000000 0.009121 +1068.000000 0.009102 +1067.000000 0.009149 +1066.000000 0.009159 +1065.000000 0.009167 +1064.000000 0.009265 +1063.000000 0.009304 +1062.000000 0.009332 +1061.000000 0.009398 +1060.000000 0.009405 +1059.000000 0.009353 +1058.000000 0.009458 +1057.000000 0.009625 +1056.000000 0.009623 +1055.000000 0.009626 +1054.000000 0.009728 +1053.000000 0.009730 +1052.000000 0.009817 +1051.000000 0.009791 +1050.000000 0.009801 +1049.000000 0.009990 +1048.000000 0.010018 +1047.000000 0.010051 +1046.000000 0.010152 +1045.000000 0.010153 +1044.000000 0.010210 +1043.000000 0.010291 +1042.000000 0.010346 +1041.000000 0.010391 +1040.000000 0.010428 +1039.000000 0.010473 +1038.000000 0.010408 +1037.000000 0.010545 +1036.000000 0.010605 +1035.000000 0.010573 +1034.000000 0.010715 +1033.000000 0.010758 +1032.000000 0.010764 +1031.000000 0.010854 +1030.000000 0.010921 +1029.000000 0.010944 +1028.000000 0.011007 +1027.000000 0.011068 +1026.000000 0.011106 +1025.000000 0.011162 +1024.000000 0.011257 +1023.000000 0.011170 +1022.000000 0.011285 +1021.000000 0.011407 +1020.000000 0.011452 +1019.000000 0.011445 +1018.000000 0.011565 +1017.000000 0.011631 +1016.000000 0.011525 +1015.000000 0.011650 +1014.000000 0.011745 +1013.000000 0.011713 +1012.000000 0.011888 +1011.000000 0.011911 +1010.000000 0.011837 +1009.000000 0.011941 +1008.000000 0.012035 +1007.000000 0.012064 +1006.000000 0.012003 +1005.000000 0.012148 +1004.000000 0.012169 +1003.000000 0.012166 +1002.000000 0.012325 +1001.000000 0.012384 +1000.000000 0.012460 +999.000000 0.012424 +998.000000 0.012383 +997.000000 0.012483 +996.000000 0.012548 +995.000000 0.012586 +994.000000 0.012601 +993.000000 0.012597 +992.000000 0.012688 +991.000000 0.012737 +990.000000 0.012842 +989.000000 0.012895 +988.000000 0.012851 +987.000000 0.012913 +986.000000 0.012979 +985.000000 0.012927 +984.000000 0.013009 +983.000000 0.013168 +982.000000 0.013195 +981.000000 0.013122 +980.000000 0.013139 +979.000000 0.013125 +978.000000 0.013174 +977.000000 0.013268 +976.000000 0.013246 +975.000000 0.013313 +974.000000 0.013423 +973.000000 0.013462 +972.000000 0.013476 +971.000000 0.013495 +970.000000 0.013497 +969.000000 0.013540 +968.000000 0.013635 +967.000000 0.013704 +966.000000 0.013749 +965.000000 0.013690 +964.000000 0.013644 +963.000000 0.013664 +962.000000 0.013644 +961.000000 0.013714 +960.000000 0.013842 +959.000000 0.013827 +958.000000 0.013749 +957.000000 0.013864 +956.000000 0.014008 +955.000000 0.013996 +954.000000 0.013830 +953.000000 0.013860 +952.000000 0.013901 +951.000000 0.014077 +950.000000 0.014099 +949.000000 0.013968 +948.000000 0.014090 +947.000000 0.014254 +946.000000 0.014180 +945.000000 0.014189 +944.000000 0.014287 +943.000000 0.014306 +942.000000 0.014215 +941.000000 0.014238 +940.000000 0.014333 +939.000000 0.014316 +938.000000 0.014268 +937.000000 0.014475 +936.000000 0.014561 +935.000000 0.014584 +934.000000 0.014492 +933.000000 0.014496 +932.000000 0.014660 +931.000000 0.014734 +930.000000 0.014671 +929.000000 0.014752 +928.000000 0.014777 +927.000000 0.014757 +926.000000 0.014875 +925.000000 0.014843 +924.000000 0.014829 +923.000000 0.014923 +922.000000 0.015027 +921.000000 0.014959 +920.000000 0.015013 +919.000000 0.015125 +918.000000 0.015191 +917.000000 0.015189 +916.000000 0.015231 +915.000000 0.015323 +914.000000 0.015371 +913.000000 0.015470 +912.000000 0.015316 +911.000000 0.015411 +910.000000 0.015449 +909.000000 0.015333 +908.000000 0.015552 +907.000000 0.015671 +906.000000 0.015570 +905.000000 0.015677 +904.000000 0.015753 +903.000000 0.015864 +902.000000 0.015902 +901.000000 0.015907 +900.000000 0.015780 +899.000000 0.016169 +898.000000 0.016058 +897.000000 0.016056 +896.000000 0.016229 +895.000000 0.016355 +894.000000 0.016481 +893.000000 0.016466 +892.000000 0.016626 +891.000000 0.016730 +890.000000 0.016732 +889.000000 0.016806 +888.000000 0.016994 +887.000000 0.017093 +886.000000 0.017169 +885.000000 0.017383 +884.000000 0.017360 +883.000000 0.017541 +882.000000 0.017585 +881.000000 0.017651 +880.000000 0.017662 +879.000000 0.017949 +878.000000 0.017881 +877.000000 0.017987 +876.000000 0.017990 +875.000000 0.017881 +874.000000 0.018215 +873.000000 0.018042 +872.000000 0.018074 +871.000000 0.018122 +870.000000 0.018083 +869.000000 0.018054 +868.000000 0.018178 +867.000000 0.018151 +866.000000 0.018217 +865.000000 0.018110 +864.000000 0.018361 +863.000000 0.018366 +862.000000 0.018379 +861.000000 0.018389 +860.000000 0.018579 +859.000000 0.019997 +858.000000 0.020184 +857.000000 0.020465 +856.000000 0.020227 +855.000000 0.020097 +854.000000 0.020551 +853.000000 0.020788 +852.000000 0.020421 +851.000000 0.020584 +850.000000 0.019960 +849.000000 0.019830 +848.000000 0.019919 +847.000000 0.019635 +846.000000 0.019824 +845.000000 0.019791 +844.000000 0.019125 +843.000000 0.019180 +842.000000 0.019093 +841.000000 0.019308 +840.000000 0.019121 +839.000000 0.019154 +838.000000 0.019074 +837.000000 0.018894 +836.000000 0.018882 +835.000000 0.018638 +834.000000 0.018347 +833.000000 0.018603 +832.000000 0.018313 +831.000000 0.018574 +830.000000 0.018380 +829.000000 0.018285 +828.000000 0.018093 +827.000000 0.018366 +826.000000 0.018148 +825.000000 0.018066 +824.000000 0.018230 +823.000000 0.018069 +822.000000 0.018079 +821.000000 0.018098 +820.000000 0.018056 +819.000000 0.018113 +818.000000 0.018128 +817.000000 0.018150 +816.000000 0.017885 +815.000000 0.018244 +814.000000 0.018232 +813.000000 0.018418 +812.000000 0.018138 +811.000000 0.018127 +810.000000 0.018411 +809.000000 0.018315 +808.000000 0.018639 +807.000000 0.018474 +806.000000 0.018587 +805.000000 0.018681 +804.000000 0.018725 +803.000000 0.018868 +802.000000 0.018773 +801.000000 0.019006 +800.000000 0.018948 +799.000000 0.019129 +798.000000 0.019164 +797.000000 0.019286 +796.000000 0.019384 +795.000000 0.019402 +794.000000 0.019487 +793.000000 0.019645 +792.000000 0.019740 +791.000000 0.019795 +790.000000 0.019939 +789.000000 0.019734 +788.000000 0.019931 +787.000000 0.020130 +786.000000 0.020206 +785.000000 0.020170 +784.000000 0.020298 +783.000000 0.020317 +782.000000 0.020572 +781.000000 0.020726 +780.000000 0.020815 +779.000000 0.020894 +778.000000 0.021072 +777.000000 0.021171 +776.000000 0.021286 +775.000000 0.021580 +774.000000 0.021776 +773.000000 0.021900 +772.000000 0.021994 +771.000000 0.022005 +770.000000 0.022148 +769.000000 0.022174 +768.000000 0.022158 +767.000000 0.022290 +766.000000 0.022249 +765.000000 0.022201 +764.000000 0.022188 +763.000000 0.022104 +762.000000 0.022058 +761.000000 0.022135 +760.000000 0.022010 +759.000000 0.022048 +758.000000 0.022203 +757.000000 0.022183 +756.000000 0.022228 +755.000000 0.022422 +754.000000 0.022307 +753.000000 0.022254 +752.000000 0.022235 +751.000000 0.022144 +750.000000 0.022219 +749.000000 0.022193 +748.000000 0.022015 +747.000000 0.021963 +746.000000 0.021820 +745.000000 0.021706 +744.000000 0.021663 +743.000000 0.021553 +742.000000 0.021428 +741.000000 0.021334 +740.000000 0.021269 +739.000000 0.021244 +738.000000 0.021244 +737.000000 0.021258 +736.000000 0.021212 +735.000000 0.021156 +734.000000 0.021084 +733.000000 0.020998 +732.000000 0.020991 +731.000000 0.021042 +730.000000 0.021016 +729.000000 0.020912 +728.000000 0.020903 +727.000000 0.020934 +726.000000 0.020880 +725.000000 0.020892 +724.000000 0.020916 +723.000000 0.020922 +722.000000 0.020892 +721.000000 0.020904 +720.000000 0.020969 +719.000000 0.021024 +718.000000 0.021005 +717.000000 0.021023 +716.000000 0.021091 +715.000000 0.021147 +714.000000 0.021145 +713.000000 0.021120 +712.000000 0.021160 +711.000000 0.021231 +710.000000 0.021285 +709.000000 0.021359 +708.000000 0.021393 +707.000000 0.021424 +706.000000 0.021438 +705.000000 0.021409 +704.000000 0.021522 +703.000000 0.021625 +702.000000 0.021636 +701.000000 0.021684 +700.000000 0.021650 +699.000000 0.021580 +698.000000 0.021679 +697.000000 0.021781 +696.000000 0.021802 +695.000000 0.021857 +694.000000 0.021929 +693.000000 0.021996 +692.000000 0.022050 +691.000000 0.022124 +690.000000 0.022084 +689.000000 0.022084 +688.000000 0.022156 +687.000000 0.022213 +686.000000 0.022229 +685.000000 0.022238 +684.000000 0.022285 +683.000000 0.022327 +682.000000 0.022391 +681.000000 0.022397 +680.000000 0.022414 +679.000000 0.022498 +678.000000 0.022566 +677.000000 0.022597 +676.000000 0.022615 +675.000000 0.022731 +674.000000 0.022750 +673.000000 0.022762 +672.000000 0.022865 +671.000000 0.022908 +670.000000 0.022979 +669.000000 0.023078 +668.000000 0.023179 +667.000000 0.023256 +666.000000 0.023355 +665.000000 0.023451 +664.000000 0.023566 +663.000000 0.023669 +662.000000 0.023820 +661.000000 0.024027 +660.000000 0.024271 +659.000000 0.024355 +658.000000 0.024535 +657.000000 0.024729 +656.000000 0.024969 +655.000000 0.025150 +654.000000 0.025385 +653.000000 0.025707 +652.000000 0.025984 +651.000000 0.026362 +650.000000 0.026663 +649.000000 0.027076 +648.000000 0.027393 +647.000000 0.027773 +646.000000 0.028272 +645.000000 0.028723 +644.000000 0.029309 +643.000000 0.029802 +642.000000 0.030356 +641.000000 0.030984 +640.000000 0.031705 +639.000000 0.032300 +638.000000 0.032994 +637.000000 0.033732 +636.000000 0.034543 +635.000000 0.035366 +634.000000 0.036205 +633.000000 0.037117 +632.000000 0.038064 +631.000000 0.039030 +630.000000 0.040126 +629.000000 0.041116 +628.000000 0.042033 +627.000000 0.043090 +626.000000 0.044107 +625.000000 0.045229 +624.000000 0.046230 +623.000000 0.047254 +622.000000 0.048347 +621.000000 0.049289 +620.000000 0.050352 +619.000000 0.051331 +618.000000 0.052230 +617.000000 0.053113 +616.000000 0.053971 +615.000000 0.054821 +614.000000 0.055576 +613.000000 0.056232 +612.000000 0.056970 +611.000000 0.057571 +610.000000 0.058159 +609.000000 0.058734 +608.000000 0.059267 +607.000000 0.059758 +606.000000 0.060208 +605.000000 0.060565 +604.000000 0.060933 +603.000000 0.061306 +602.000000 0.061580 +601.000000 0.061813 +600.000000 0.062151 +599.000000 0.062419 +598.000000 0.062716 +597.000000 0.062898 +596.000000 0.063042 +595.000000 0.063178 +594.000000 0.063469 +593.000000 0.063707 +592.000000 0.063942 +591.000000 0.064180 +590.000000 0.064540 +589.000000 0.064696 +588.000000 0.065081 +587.000000 0.065392 +586.000000 0.065629 +585.000000 0.066028 +584.000000 0.066308 +583.000000 0.066740 +582.000000 0.067226 +581.000000 0.067521 +580.000000 0.068030 +579.000000 0.068522 +578.000000 0.069021 +577.000000 0.069404 +576.000000 0.069877 +575.000000 0.070338 +574.000000 0.070825 +573.000000 0.071192 +572.000000 0.071661 +571.000000 0.072203 +570.000000 0.072587 +569.000000 0.073057 +568.000000 0.073366 +567.000000 0.073782 +566.000000 0.074079 +565.000000 0.074326 +564.000000 0.074623 +563.000000 0.074809 +562.000000 0.075073 +561.000000 0.075292 +560.000000 0.075441 +559.000000 0.075548 +558.000000 0.075664 +557.000000 0.075661 +556.000000 0.075655 +555.000000 0.075666 +554.000000 0.075608 +553.000000 0.075566 +552.000000 0.075495 +551.000000 0.075367 +550.000000 0.075209 +549.000000 0.075200 +548.000000 0.075036 +547.000000 0.074795 +546.000000 0.074742 +545.000000 0.074588 +544.000000 0.074351 +543.000000 0.074218 +542.000000 0.073961 +541.000000 0.073821 +540.000000 0.073640 +539.000000 0.073467 +538.000000 0.073230 +537.000000 0.073039 +536.000000 0.072961 +535.000000 0.072733 +534.000000 0.072445 +533.000000 0.072315 +532.000000 0.072129 +531.000000 0.071921 +530.000000 0.071811 +529.000000 0.071641 +528.000000 0.071244 +527.000000 0.071126 +526.000000 0.070892 +525.000000 0.070682 +524.000000 0.070405 +523.000000 0.070132 +522.000000 0.069994 +521.000000 0.069539 +520.000000 0.069359 +519.000000 0.069008 +518.000000 0.068709 +517.000000 0.068404 +516.000000 0.068063 +515.000000 0.067679 +514.000000 0.067318 +513.000000 0.066936 +512.000000 0.066516 +511.000000 0.066159 +510.000000 0.065740 +509.000000 0.065287 +508.000000 0.064930 +507.000000 0.064514 +506.000000 0.064082 +505.000000 0.063677 +504.000000 0.063203 +503.000000 0.062712 +502.000000 0.062355 +501.000000 0.061951 +500.000000 0.061509 +499.000000 0.061054 +498.000000 0.060700 +497.000000 0.060275 +496.000000 0.059898 +495.000000 0.059461 +494.000000 0.058964 +493.000000 0.058480 +492.000000 0.058143 +491.000000 0.057803 +490.000000 0.057261 +489.000000 0.056802 +488.000000 0.056433 +487.000000 0.055974 +486.000000 0.055426 +485.000000 0.055081 +484.000000 0.054572 +483.000000 0.054170 +482.000000 0.053733 +481.000000 0.053364 +480.000000 0.052942 +479.000000 0.052331 +478.000000 0.051923 +477.000000 0.051466 +476.000000 0.051002 +475.000000 0.050516 +474.000000 0.050023 +473.000000 0.049600 +472.000000 0.049159 +471.000000 0.048746 +470.000000 0.048282 +469.000000 0.047899 +468.000000 0.047439 +467.000000 0.046896 +466.000000 0.046514 +465.000000 0.046114 +464.000000 0.045679 +463.000000 0.045200 +462.000000 0.044762 +461.000000 0.044350 +460.000000 0.043902 +459.000000 0.043508 +458.000000 0.043170 +457.000000 0.042760 +456.000000 0.042370 +455.000000 0.042009 +454.000000 0.041656 +453.000000 0.041318 +452.000000 0.041008 +451.000000 0.040688 +450.000000 0.040337 +449.000000 0.039904 +448.000000 0.039582 +447.000000 0.039228 +446.000000 0.038934 +445.000000 0.038658 +444.000000 0.038343 +443.000000 0.038093 +442.000000 0.037835 +441.000000 0.037550 +440.000000 0.037297 +439.000000 0.037083 +438.000000 0.036865 +437.000000 0.036681 +436.000000 0.036496 +435.000000 0.036298 +434.000000 0.036161 +433.000000 0.036055 +432.000000 0.035954 +431.000000 0.035829 +430.000000 0.035759 +429.000000 0.035787 +428.000000 0.035837 +427.000000 0.035935 +426.000000 0.035920 +425.000000 0.035794 +424.000000 0.035818 +423.000000 0.035823 +422.000000 0.035829 +421.000000 0.035776 +420.000000 0.035641 +419.000000 0.035542 +418.000000 0.035535 +417.000000 0.035559 +416.000000 0.035574 +415.000000 0.035502 +414.000000 0.035290 +413.000000 0.035124 +412.000000 0.034947 +411.000000 0.034671 +410.000000 0.034394 +409.000000 0.034122 +408.000000 0.033937 +407.000000 0.033720 +406.000000 0.033489 +405.000000 0.033217 +404.000000 0.032952 +403.000000 0.032715 +402.000000 0.032471 +401.000000 0.032172 +400.000000 0.031945 +399.000000 0.031776 +398.000000 0.031640 +397.000000 0.031626 +396.000000 0.031513 +395.000000 0.031348 +394.000000 0.031197 +393.000000 0.031015 +392.000000 0.030799 +391.000000 0.030561 +390.000000 0.030436 +389.000000 0.030401 +388.000000 0.030221 +387.000000 0.029985 +386.000000 0.029798 +385.000000 0.029579 +384.000000 0.029376 +383.000000 0.029213 +382.000000 0.029028 +381.000000 0.028861 +380.000000 0.028750 +379.000000 0.028546 +378.000000 0.028314 +377.000000 0.027956 +376.000000 0.027791 +375.000000 0.027772 +374.000000 0.027695 +373.000000 0.027540 +372.000000 0.027444 +371.000000 0.027210 +370.000000 0.027154 +369.000000 0.027002 +368.000000 0.026878 +367.000000 0.026673 +366.000000 0.026505 +365.000000 0.026284 +364.000000 0.026195 +363.000000 0.025960 +362.000000 0.025728 +361.000000 0.025488 +360.000000 0.025257 +359.000000 0.024981 +358.000000 0.024757 +357.000000 0.024413 +356.000000 0.024163 +355.000000 0.023858 +354.000000 0.023491 +353.000000 0.023211 +352.000000 0.022970 +351.000000 0.022715 +350.000000 0.022313 +349.000000 0.022096 +348.000000 0.021830 +347.000000 0.021619 +346.000000 0.021411 +345.000000 0.021070 +344.000000 0.020837 +343.000000 0.020636 +342.000000 0.020454 +341.000000 0.020235 +340.000000 0.020001 +339.000000 0.019970 +338.000000 0.019776 +337.000000 0.019513 +336.000000 0.019305 +335.000000 0.019259 +334.000000 0.019247 +333.000000 0.019054 +332.000000 0.018831 +331.000000 0.018503 +330.000000 0.018468 +329.000000 0.018455 +328.000000 0.018314 +327.000000 0.017943 +326.000000 0.017808 +325.000000 0.017649 +324.000000 0.017436 +323.000000 0.017523 +322.000000 0.017331 +321.000000 0.017387 +320.000000 0.017154 diff --git a/tests/data/transmission/KTF-D.Probe.Raw.asc b/tests/data/transmission/KTF-D.Probe.Raw.asc new file mode 100644 index 00000000..5cbc3a28 --- /dev/null +++ b/tests/data/transmission/KTF-D.Probe.Raw.asc @@ -0,0 +1,1091 @@ +PE UV SUBTECH SPECTRUM ASCII PEDS 4.00 + -1 +D.Probe.Raw.asc +24/01/17 +09:26:08.00 +24/01/17 +09:26:08.00 +Administrator + +200.000000 +1 +Lambda 1050 +1050L1611222 +PerkinElmer UV WinLab 6.3.2.0749 / 2.02.06 Lambda 900 UV/VIS/NIR, Jan 28 2016 16:58:34 + +0 +0 +3350/servo 1800.8/servo 860.8/2 +0 +0 +UV/VIS +1 +1 +1 +DoubleDePol,CommonBeamDepol,RBeamAtt,SBeamAtt,uv/vis/nir detector, TDS WB InGaAs Detector +0 +0 +0 +0 +Program +Program +3350/servo 1800.8/servo 860.8/2 +3350/0.2 1800.8/0.2 860.8/0.2 +3350/0.2 1800.8/0.2 860.8/0.2 +0 +3350/1.00 1800.8/1.00 + + + +0 + +860.8 +319.2 +1800.8 860.8 +Front +100 +on +S:100 R:100 +0 +0 +0 +0 + +0 + + + +0 + + +0 +0 +0 +0 +0 + + + + +0 +0 + + +0 +0 +#HDR +-1 +-1 +#GR +nm +%T +1.0 +0.0 +1200.000000 +-1.000000 +1001 +8 +89.950410 +-0.010797 +#DATA +1200.000000 89.940565 +1199.000000 89.936272 +1198.000000 89.933389 +1197.000000 89.933428 +1196.000000 89.934908 +1195.000000 89.935244 +1194.000000 89.939344 +1193.000000 89.926334 +1192.000000 89.927789 +1191.000000 89.929268 +1190.000000 89.943318 +1189.000000 89.946581 +1188.000000 89.947232 +1187.000000 89.938574 +1186.000000 89.922683 +1185.000000 89.924418 +1184.000000 89.923898 +1183.000000 89.938172 +1182.000000 89.934926 +1181.000000 89.932802 +1180.000000 89.928369 +1179.000000 89.922433 +1178.000000 89.931312 +1177.000000 89.921788 +1176.000000 89.920807 +1175.000000 89.920839 +1174.000000 89.914935 +1173.000000 89.920378 +1172.000000 89.934316 +1171.000000 89.946367 +1170.000000 89.935694 +1169.000000 89.935719 +1168.000000 89.941593 +1167.000000 89.935434 +1166.000000 89.939471 +1165.000000 89.941434 +1164.000000 89.944902 +1163.000000 89.950410 +1162.000000 89.948068 +1161.000000 89.947069 +1160.000000 89.944395 +1159.000000 89.939406 +1158.000000 89.931027 +1157.000000 89.916600 +1156.000000 89.913578 +1155.000000 89.922228 +1154.000000 89.924431 +1153.000000 89.933106 +1152.000000 89.932611 +1151.000000 89.934066 +1150.000000 89.943947 +1149.000000 89.944786 +1148.000000 89.933102 +1147.000000 89.922481 +1146.000000 89.923356 +1145.000000 89.927551 +1144.000000 89.923337 +1143.000000 89.929449 +1142.000000 89.942445 +1141.000000 89.933603 +1140.000000 89.922724 +1139.000000 89.923242 +1138.000000 89.917248 +1137.000000 89.923819 +1136.000000 89.920826 +1135.000000 89.909206 +1134.000000 89.901351 +1133.000000 89.895424 +1132.000000 89.908213 +1131.000000 89.910111 +1130.000000 89.916098 +1129.000000 89.917206 +1128.000000 89.926170 +1127.000000 89.930050 +1126.000000 89.925899 +1125.000000 89.927923 +1124.000000 89.918232 +1123.000000 89.923583 +1122.000000 89.921227 +1121.000000 89.924364 +1120.000000 89.924935 +1119.000000 89.916651 +1118.000000 89.911840 +1117.000000 89.901137 +1116.000000 89.903321 +1115.000000 89.898788 +1114.000000 89.893987 +1113.000000 89.884348 +1112.000000 89.889048 +1111.000000 89.899160 +1110.000000 89.899406 +1109.000000 89.894188 +1108.000000 89.892961 +1107.000000 89.901491 +1106.000000 89.907113 +1105.000000 89.906967 +1104.000000 89.899574 +1103.000000 89.890688 +1102.000000 89.882939 +1101.000000 89.878005 +1100.000000 89.885919 +1099.000000 89.887608 +1098.000000 89.887906 +1097.000000 89.899593 +1096.000000 89.902176 +1095.000000 89.904246 +1094.000000 89.896015 +1093.000000 89.898687 +1092.000000 89.918131 +1091.000000 89.919336 +1090.000000 89.911142 +1089.000000 89.912549 +1088.000000 89.915636 +1087.000000 89.912067 +1086.000000 89.911050 +1085.000000 89.911728 +1084.000000 89.913343 +1083.000000 89.917732 +1082.000000 89.913542 +1081.000000 89.897374 +1080.000000 89.889796 +1079.000000 89.903770 +1078.000000 89.911270 +1077.000000 89.908527 +1076.000000 89.913797 +1075.000000 89.919395 +1074.000000 89.927383 +1073.000000 89.922988 +1072.000000 89.906476 +1071.000000 89.898588 +1070.000000 89.899603 +1069.000000 89.898082 +1068.000000 89.904193 +1067.000000 89.913407 +1066.000000 89.914726 +1065.000000 89.915061 +1064.000000 89.915088 +1063.000000 89.906608 +1062.000000 89.901033 +1061.000000 89.909276 +1060.000000 89.914541 +1059.000000 89.913017 +1058.000000 89.907415 +1057.000000 89.899105 +1056.000000 89.879946 +1055.000000 89.866366 +1054.000000 89.872097 +1053.000000 89.879013 +1052.000000 89.876731 +1051.000000 89.872236 +1050.000000 89.877634 +1049.000000 89.893811 +1048.000000 89.896290 +1047.000000 89.892564 +1046.000000 89.891927 +1045.000000 89.892014 +1044.000000 89.888243 +1043.000000 89.893347 +1042.000000 89.903901 +1041.000000 89.912124 +1040.000000 89.915989 +1039.000000 89.915515 +1038.000000 89.913726 +1037.000000 89.909149 +1036.000000 89.900656 +1035.000000 89.897012 +1034.000000 89.894049 +1033.000000 89.892146 +1032.000000 89.890333 +1031.000000 89.891140 +1030.000000 89.890457 +1029.000000 89.890497 +1028.000000 89.892854 +1027.000000 89.897736 +1026.000000 89.904648 +1025.000000 89.916713 +1024.000000 89.923896 +1023.000000 89.924118 +1022.000000 89.919216 +1021.000000 89.909335 +1020.000000 89.911284 +1019.000000 89.920351 +1018.000000 89.927203 +1017.000000 89.927886 +1016.000000 89.920812 +1015.000000 89.914607 +1014.000000 89.904770 +1013.000000 89.895585 +1012.000000 89.885653 +1011.000000 89.876819 +1010.000000 89.872139 +1009.000000 89.876669 +1008.000000 89.882298 +1007.000000 89.877635 +1006.000000 89.878744 +1005.000000 89.880519 +1004.000000 89.873552 +1003.000000 89.879232 +1002.000000 89.890386 +1001.000000 89.887040 +1000.000000 89.875525 +999.000000 89.870712 +998.000000 89.861940 +997.000000 89.855220 +996.000000 89.871073 +995.000000 89.879300 +994.000000 89.874894 +993.000000 89.884017 +992.000000 89.891589 +991.000000 89.892599 +990.000000 89.896675 +989.000000 89.891181 +988.000000 89.891479 +987.000000 89.890810 +986.000000 89.879751 +985.000000 89.878132 +984.000000 89.884076 +983.000000 89.883088 +982.000000 89.883967 +981.000000 89.881255 +980.000000 89.873482 +979.000000 89.876861 +978.000000 89.879794 +977.000000 89.882647 +976.000000 89.883456 +975.000000 89.875468 +974.000000 89.870651 +973.000000 89.880036 +972.000000 89.893052 +971.000000 89.884676 +970.000000 89.880986 +969.000000 89.888288 +968.000000 89.895779 +967.000000 89.889119 +966.000000 89.871686 +965.000000 89.869303 +964.000000 89.867891 +963.000000 89.865057 +962.000000 89.860859 +961.000000 89.857534 +960.000000 89.852866 +959.000000 89.848690 +958.000000 89.855581 +957.000000 89.870732 +956.000000 89.867713 +955.000000 89.864537 +954.000000 89.855054 +953.000000 89.853146 +952.000000 89.848084 +951.000000 89.835981 +950.000000 89.843556 +949.000000 89.846039 +948.000000 89.844299 +947.000000 89.848287 +946.000000 89.850050 +945.000000 89.846363 +944.000000 89.840724 +943.000000 89.849189 +942.000000 89.856696 +941.000000 89.851759 +940.000000 89.840260 +939.000000 89.835584 +938.000000 89.851286 +937.000000 89.861648 +936.000000 89.860179 +935.000000 89.854228 +934.000000 89.855483 +933.000000 89.846589 +932.000000 89.852521 +931.000000 89.871054 +930.000000 89.870572 +929.000000 89.861227 +928.000000 89.853478 +927.000000 89.860110 +926.000000 89.853318 +925.000000 89.836741 +924.000000 89.823836 +923.000000 89.819284 +922.000000 89.824264 +921.000000 89.821677 +920.000000 89.820396 +919.000000 89.809184 +918.000000 89.798406 +917.000000 89.802484 +916.000000 89.815438 +915.000000 89.822160 +914.000000 89.819504 +913.000000 89.810704 +912.000000 89.791452 +911.000000 89.789408 +910.000000 89.806709 +909.000000 89.813208 +908.000000 89.805215 +907.000000 89.806785 +906.000000 89.802322 +905.000000 89.798129 +904.000000 89.802919 +903.000000 89.791493 +902.000000 89.794066 +901.000000 89.824626 +900.000000 89.836284 +899.000000 89.821966 +898.000000 89.805700 +897.000000 89.795870 +896.000000 89.804189 +895.000000 89.814193 +894.000000 89.804723 +893.000000 89.802597 +892.000000 89.786294 +891.000000 89.772357 +890.000000 89.773908 +889.000000 89.767693 +888.000000 89.763169 +887.000000 89.767915 +886.000000 89.780158 +885.000000 89.783667 +884.000000 89.782188 +883.000000 89.792282 +882.000000 89.785931 +881.000000 89.777499 +880.000000 89.787863 +879.000000 89.775420 +878.000000 89.751198 +877.000000 89.751043 +876.000000 89.761509 +875.000000 89.758201 +874.000000 89.763569 +873.000000 89.752861 +872.000000 89.737432 +871.000000 89.741713 +870.000000 89.743672 +869.000000 89.754561 +868.000000 89.765166 +867.000000 89.748872 +866.000000 89.736450 +865.000000 89.725078 +864.000000 89.752111 +863.000000 89.777767 +862.000000 89.745112 +861.000000 89.738119 +860.000000 89.344938 +859.000000 89.398683 +858.000000 89.228788 +857.000000 89.336537 +856.000000 89.352309 +855.000000 89.412493 +854.000000 89.433720 +853.000000 89.264915 +852.000000 89.432519 +851.000000 89.364818 +850.000000 89.350629 +849.000000 89.284246 +848.000000 89.344989 +847.000000 89.420206 +846.000000 89.417680 +845.000000 89.335331 +844.000000 89.358021 +843.000000 89.413506 +842.000000 89.325874 +841.000000 89.490346 +840.000000 89.350241 +839.000000 89.359784 +838.000000 89.343470 +837.000000 89.390469 +836.000000 89.358958 +835.000000 89.341898 +834.000000 89.281859 +833.000000 89.278460 +832.000000 89.329447 +831.000000 89.341637 +830.000000 89.333882 +829.000000 89.309805 +828.000000 89.301368 +827.000000 89.361333 +826.000000 89.315879 +825.000000 89.301107 +824.000000 89.306069 +823.000000 89.268320 +822.000000 89.290880 +821.000000 89.308909 +820.000000 89.236444 +819.000000 89.258453 +818.000000 89.300086 +817.000000 89.301718 +816.000000 89.308191 +815.000000 89.242885 +814.000000 89.224568 +813.000000 89.266568 +812.000000 89.240979 +811.000000 89.207387 +810.000000 89.194236 +809.000000 89.209182 +808.000000 89.188319 +807.000000 89.196268 +806.000000 89.142992 +805.000000 89.154845 +804.000000 89.175357 +803.000000 89.067808 +802.000000 88.980814 +801.000000 89.041732 +800.000000 89.124814 +799.000000 89.204898 +798.000000 89.198866 +797.000000 89.200993 +796.000000 89.202930 +795.000000 89.217243 +794.000000 89.238291 +793.000000 89.201226 +792.000000 89.196135 +791.000000 89.206100 +790.000000 89.216437 +789.000000 89.245456 +788.000000 89.275869 +787.000000 89.269794 +786.000000 89.273056 +785.000000 89.247789 +784.000000 89.276980 +783.000000 89.283370 +782.000000 89.283200 +781.000000 89.257196 +780.000000 89.231039 +779.000000 89.256467 +778.000000 89.265838 +777.000000 89.279175 +776.000000 89.255728 +775.000000 89.240882 +774.000000 89.268384 +773.000000 89.224716 +772.000000 89.241236 +771.000000 89.273469 +770.000000 89.267062 +769.000000 89.232618 +768.000000 89.274411 +767.000000 89.297047 +766.000000 89.284120 +765.000000 89.252774 +764.000000 89.207749 +763.000000 89.263354 +762.000000 89.247806 +761.000000 89.253861 +760.000000 89.210638 +759.000000 89.230614 +758.000000 89.237806 +757.000000 89.221876 +756.000000 89.221170 +755.000000 89.183424 +754.000000 89.145853 +753.000000 89.135917 +752.000000 89.141721 +751.000000 89.135054 +750.000000 89.161156 +749.000000 89.165279 +748.000000 89.178796 +747.000000 89.142874 +746.000000 89.079012 +745.000000 89.093454 +744.000000 89.135208 +743.000000 89.173285 +742.000000 89.193282 +741.000000 89.169564 +740.000000 89.160682 +739.000000 89.144640 +738.000000 89.133933 +737.000000 89.129045 +736.000000 89.121605 +735.000000 89.151688 +734.000000 89.179468 +733.000000 89.191954 +732.000000 89.191759 +731.000000 89.208097 +730.000000 89.223098 +729.000000 89.239254 +728.000000 89.220341 +727.000000 89.214127 +726.000000 89.219796 +725.000000 89.216023 +724.000000 89.196011 +723.000000 89.203443 +722.000000 89.198345 +721.000000 89.211362 +720.000000 89.199045 +719.000000 89.202282 +718.000000 89.195863 +717.000000 89.207562 +716.000000 89.184501 +715.000000 89.209613 +714.000000 89.213251 +713.000000 89.189391 +712.000000 89.199177 +711.000000 89.180791 +710.000000 89.173833 +709.000000 89.160334 +708.000000 89.176118 +707.000000 89.171085 +706.000000 89.175886 +705.000000 89.170481 +704.000000 89.151313 +703.000000 89.156345 +702.000000 89.158441 +701.000000 89.176024 +700.000000 89.159642 +699.000000 89.158051 +698.000000 89.148169 +697.000000 89.136634 +696.000000 89.142774 +695.000000 89.135448 +694.000000 89.144259 +693.000000 89.145405 +692.000000 89.144474 +691.000000 89.143963 +690.000000 89.124955 +689.000000 89.128150 +688.000000 89.139969 +687.000000 89.125100 +686.000000 89.112496 +685.000000 89.118857 +684.000000 89.122010 +683.000000 89.114115 +682.000000 89.114342 +681.000000 89.105058 +680.000000 89.109278 +679.000000 89.119296 +678.000000 89.100195 +677.000000 89.102858 +676.000000 89.105855 +675.000000 89.080987 +674.000000 89.098505 +673.000000 89.122641 +672.000000 89.095034 +671.000000 89.089223 +670.000000 89.107228 +669.000000 89.098724 +668.000000 89.087655 +667.000000 89.095975 +666.000000 89.080390 +665.000000 89.083987 +664.000000 89.090435 +663.000000 89.089089 +662.000000 89.082143 +661.000000 89.070787 +660.000000 89.080362 +659.000000 89.080691 +658.000000 89.079657 +657.000000 89.074447 +656.000000 89.070900 +655.000000 89.065926 +654.000000 89.057447 +653.000000 89.065314 +652.000000 89.062962 +651.000000 89.044225 +650.000000 89.050902 +649.000000 89.065633 +648.000000 89.052690 +647.000000 89.029757 +646.000000 89.024692 +645.000000 89.042341 +644.000000 89.031971 +643.000000 89.019123 +642.000000 89.037259 +641.000000 89.044184 +640.000000 89.035651 +639.000000 89.012680 +638.000000 89.003029 +637.000000 89.017681 +636.000000 89.016372 +635.000000 89.004538 +634.000000 89.015708 +633.000000 89.024029 +632.000000 89.008867 +631.000000 88.996395 +630.000000 89.001205 +629.000000 88.995278 +628.000000 88.990090 +627.000000 88.993181 +626.000000 88.987885 +625.000000 88.984694 +624.000000 88.986509 +623.000000 88.982908 +622.000000 88.974340 +621.000000 88.963702 +620.000000 88.987098 +619.000000 88.985675 +618.000000 88.959705 +617.000000 88.960840 +616.000000 88.954549 +615.000000 88.957482 +614.000000 88.963328 +613.000000 88.961970 +612.000000 88.963287 +611.000000 88.949611 +610.000000 88.941202 +609.000000 88.959265 +608.000000 88.944694 +607.000000 88.919268 +606.000000 88.908403 +605.000000 88.905120 +604.000000 88.903156 +603.000000 88.896499 +602.000000 88.893977 +601.000000 88.900402 +600.000000 88.889681 +599.000000 88.890590 +598.000000 88.907110 +597.000000 88.909739 +596.000000 88.904164 +595.000000 88.890855 +594.000000 88.873011 +593.000000 88.830823 +592.000000 88.817155 +591.000000 88.826655 +590.000000 88.832153 +589.000000 88.830472 +588.000000 88.825296 +587.000000 88.794355 +586.000000 88.776317 +585.000000 88.742686 +584.000000 88.725580 +583.000000 88.751800 +582.000000 88.770072 +581.000000 88.748526 +580.000000 88.765103 +579.000000 88.771426 +578.000000 88.771399 +577.000000 88.764357 +576.000000 88.773423 +575.000000 88.774263 +574.000000 88.764390 +573.000000 88.759798 +572.000000 88.757306 +571.000000 88.768945 +570.000000 88.751292 +569.000000 88.742972 +568.000000 88.759040 +567.000000 88.759025 +566.000000 88.767993 +565.000000 88.781538 +564.000000 88.757487 +563.000000 88.748233 +562.000000 88.740908 +561.000000 88.746945 +560.000000 88.741960 +559.000000 88.738038 +558.000000 88.730839 +557.000000 88.730154 +556.000000 88.730850 +555.000000 88.717614 +554.000000 88.702761 +553.000000 88.710804 +552.000000 88.696252 +551.000000 88.693442 +550.000000 88.691219 +549.000000 88.703569 +548.000000 88.688391 +547.000000 88.680845 +546.000000 88.688299 +545.000000 88.645065 +544.000000 88.636679 +543.000000 88.636721 +542.000000 88.628976 +541.000000 88.614525 +540.000000 88.620695 +539.000000 88.642079 +538.000000 88.638456 +537.000000 88.633739 +536.000000 88.614889 +535.000000 88.598082 +534.000000 88.597470 +533.000000 88.602485 +532.000000 88.588903 +531.000000 88.575070 +530.000000 88.577612 +529.000000 88.570050 +528.000000 88.569491 +527.000000 88.544292 +526.000000 88.534111 +525.000000 88.517836 +524.000000 88.544519 +523.000000 88.524256 +522.000000 88.530070 +521.000000 88.533138 +520.000000 88.526827 +519.000000 88.528381 +518.000000 88.519411 +517.000000 88.495852 +516.000000 88.497279 +515.000000 88.486407 +514.000000 88.474281 +513.000000 88.461732 +512.000000 88.459462 +511.000000 88.436289 +510.000000 88.428294 +509.000000 88.420013 +508.000000 88.407122 +507.000000 88.374992 +506.000000 88.314341 +505.000000 88.240700 +504.000000 88.163205 +503.000000 88.000424 +502.000000 87.789694 +501.000000 87.470963 +500.000000 86.979411 +499.000000 86.266133 +498.000000 85.109278 +497.000000 82.927827 +496.000000 79.102145 +495.000000 73.936869 +494.000000 67.594751 +493.000000 58.462422 +492.000000 48.892849 +491.000000 44.412880 +490.000000 43.865889 +489.000000 40.425033 +488.000000 30.281482 +487.000000 18.137824 +486.000000 13.319778 +485.000000 18.525774 +484.000000 27.884058 +483.000000 37.748108 +482.000000 49.721660 +481.000000 60.540613 +480.000000 67.167738 +479.000000 71.512142 +478.000000 74.999932 +477.000000 78.031572 +476.000000 80.896004 +475.000000 83.372519 +474.000000 85.088793 +473.000000 86.152126 +472.000000 86.826635 +471.000000 87.274952 +470.000000 87.548850 +469.000000 87.758262 +468.000000 87.863923 +467.000000 87.946253 +466.000000 87.986727 +465.000000 87.998111 +464.000000 88.032246 +463.000000 88.030580 +462.000000 88.044214 +461.000000 88.041181 +460.000000 88.042413 +459.000000 88.014308 +458.000000 88.006678 +457.000000 87.997930 +456.000000 87.982353 +455.000000 87.989772 +454.000000 87.967050 +453.000000 87.948221 +452.000000 87.946273 +451.000000 87.943574 +450.000000 87.933594 +449.000000 87.917323 +448.000000 87.899556 +447.000000 87.856379 +446.000000 87.857152 +445.000000 87.833599 +444.000000 87.799812 +443.000000 87.826698 +442.000000 87.799062 +441.000000 87.781773 +440.000000 87.788596 +439.000000 87.767850 +438.000000 87.731003 +437.000000 87.745266 +436.000000 87.704337 +435.000000 87.727716 +434.000000 87.717693 +433.000000 87.691662 +432.000000 87.675527 +431.000000 87.670157 +430.000000 87.650542 +429.000000 87.666135 +428.000000 87.627481 +427.000000 87.641321 +426.000000 87.620060 +425.000000 87.624062 +424.000000 87.578259 +423.000000 87.542107 +422.000000 87.543978 +421.000000 87.507919 +420.000000 87.496767 +419.000000 87.500017 +418.000000 87.496800 +417.000000 87.471725 +416.000000 87.422357 +415.000000 87.406795 +414.000000 87.408210 +413.000000 87.391752 +412.000000 87.351524 +411.000000 87.339455 +410.000000 87.345958 +409.000000 87.347386 +408.000000 87.310784 +407.000000 87.302290 +406.000000 87.265458 +405.000000 87.247346 +404.000000 87.246998 +403.000000 87.229484 +402.000000 87.191118 +401.000000 87.174519 +400.000000 87.187022 +399.000000 87.135502 +398.000000 87.143455 +397.000000 87.098643 +396.000000 87.099782 +395.000000 87.042885 +394.000000 87.011983 +393.000000 86.972065 +392.000000 86.828019 +391.000000 86.602724 +390.000000 86.202711 +389.000000 85.351999 +388.000000 83.517579 +387.000000 79.678722 +386.000000 71.351026 +385.000000 55.785167 +384.000000 35.612852 +383.000000 18.316294 +382.000000 8.239977 +381.000000 3.894249 +380.000000 1.923887 +379.000000 0.671909 +378.000000 0.134849 +377.000000 0.001382 +376.000000 -0.010797 +375.000000 0.018010 +374.000000 0.092527 +373.000000 0.162995 +372.000000 0.156701 +371.000000 0.096643 +370.000000 0.037291 +369.000000 0.051228 +368.000000 0.125181 +367.000000 0.213322 +366.000000 0.586778 +365.000000 1.965680 +364.000000 4.428118 +363.000000 6.808413 +362.000000 8.929760 +361.000000 10.164516 +360.000000 8.300491 +359.000000 4.110789 +358.000000 1.111269 +357.000000 0.340887 +356.000000 0.296803 +355.000000 0.227439 +354.000000 0.129728 +353.000000 0.069094 +352.000000 0.046724 +351.000000 0.063232 +350.000000 0.270441 +349.000000 1.153773 +348.000000 3.370821 +347.000000 6.372345 +346.000000 8.340437 +345.000000 8.200834 +344.000000 6.119191 +343.000000 3.353808 +342.000000 1.717948 +341.000000 1.560695 +340.000000 1.784757 +339.000000 2.653897 +338.000000 5.254438 +337.000000 8.624799 +336.000000 12.602685 +335.000000 21.305791 +334.000000 35.736422 +333.000000 49.311115 +332.000000 56.734061 +331.000000 59.357296 +330.000000 56.216013 +329.000000 45.799275 +328.000000 38.588133 +327.000000 43.723556 +326.000000 54.299179 +325.000000 61.142804 +324.000000 60.537486 +323.000000 50.838449 +322.000000 35.471481 +321.000000 20.891366 +320.000000 10.324630 +319.000000 4.672504 +318.000000 3.157227 +317.000000 4.305590 +316.000000 11.247495 +315.000000 26.441519 +314.000000 45.278068 +313.000000 61.696786 +312.000000 73.024075 +311.000000 79.048573 +310.000000 80.798109 +309.000000 78.647465 +308.000000 70.191284 +307.000000 54.395844 +306.000000 36.940835 +305.000000 24.572038 +304.000000 16.090792 +303.000000 9.912369 +302.000000 12.283295 +301.000000 26.384603 +300.000000 44.682997 +299.000000 57.201748 +298.000000 60.940674 +297.000000 57.899738 +296.000000 51.077165 +295.000000 47.539897 +294.000000 53.706693 +293.000000 64.227844 +292.000000 69.442911 +291.000000 67.516823 +290.000000 59.111098 +289.000000 43.623476 +288.000000 24.365185 +287.000000 9.050362 +286.000000 1.967865 +285.000000 0.263930 +284.000000 0.145201 +283.000000 0.784588 +282.000000 4.051173 +281.000000 14.035573 +280.000000 32.087447 +279.000000 51.740155 +278.000000 63.486635 +277.000000 61.926006 +276.000000 47.214160 +275.000000 26.453011 +274.000000 10.154371 +273.000000 3.599685 +272.000000 6.171505 +271.000000 14.413982 +270.000000 21.649820 +269.000000 21.913873 +268.000000 15.688570 +267.000000 7.977094 +266.000000 2.747714 +265.000000 0.586529 +264.000000 0.081533 +263.000000 0.026039 +262.000000 0.021156 +261.000000 0.018122 +260.000000 0.019188 +259.000000 0.020256 +258.000000 0.023443 +257.000000 0.024382 +256.000000 0.024923 +255.000000 0.024535 +254.000000 0.024940 +253.000000 0.024017 +252.000000 0.023227 +251.000000 0.023099 +250.000000 0.020047 +249.000000 0.025624 +248.000000 0.036645 +247.000000 0.072216 +246.000000 0.133280 +245.000000 0.183741 +244.000000 0.213243 +243.000000 0.201084 +242.000000 0.190519 +241.000000 0.512719 +240.000000 1.368258 +239.000000 2.207211 +238.000000 2.330419 +237.000000 1.780629 +236.000000 1.183627 +235.000000 0.876085 +234.000000 0.659933 +233.000000 0.375938 +232.000000 0.133049 +231.000000 0.037813 +230.000000 0.026866 +229.000000 0.023267 +228.000000 0.023407 +227.000000 0.022742 +226.000000 0.024757 +225.000000 0.025570 +224.000000 0.022232 +223.000000 0.024651 +222.000000 0.025601 +221.000000 0.023336 +220.000000 0.023079 +219.000000 0.025375 +218.000000 0.024587 +217.000000 0.025678 +216.000000 0.028384 +215.000000 0.028808 +214.000000 0.024924 +213.000000 0.025213 +212.000000 0.029280 +211.000000 0.027010 +210.000000 0.027711 +209.000000 0.030312 +208.000000 0.030080 +207.000000 0.031742 +206.000000 0.035711 +205.000000 0.035196 +204.000000 0.032357 +203.000000 0.037788 +202.000000 0.041853 +201.000000 0.041012 +200.000000 0.040434 diff --git a/tests/data/transmission/Sample5926.Probe.Raw.asc b/tests/data/transmission/Sample5926.Probe.Raw.asc new file mode 100644 index 00000000..e6861729 --- /dev/null +++ b/tests/data/transmission/Sample5926.Probe.Raw.asc @@ -0,0 +1,201 @@ +PE UV SUBTECH SPECTRUM ASCII PEDS 4.00 + -1 +Sample5926.Probe.Raw.asc +24/02/27 +14:48:54.00 +24/02/27 +14:48:54.00 +Hiroki Tanaka + +800.000000 +1 +Lambda 1050 +1050L1611222 +PerkinElmer UV WinLab 6.3.2.0749 / 2.02.06 Lambda 900 UV/VIS/NIR, Jan 28 2016 16:58:34 + +0 +0 +3350/2.4 860.8/2.05 +0 +0 +UV/VIS +0 +1 +1 +DoubleDePol,CommonBeamDepol,RBeamAtt,SBeamAtt,150mm sphere / downward view, WB InGaAs Detector +0 +0 +0 +0 +Program +Program +3350/2.4 860.8/2.05 +3350/0.28 860.9/0.24 +3350/0.28 860.9/0.24 +0 +4.50 + + + +0 + +860.8 +319.2 +860.9 +Front +90 +on +S:1 R:1 +20 +0 +0 +0 + +0 + + + +0 + + +0 +0 +0 +0 +0 + + + + +0 +0 + + +0 +0 +#HDR +-1 +-1 +#GR +nm +%T +1.0 +0.0 +1900.000000 +-10.000000 +111 +8 +1953.805925 +-519.417000 +#DATA +1900.000000 100.000300 +1890.000000 100.166975 +1880.000000 98.004086 +1870.000000 95.916633 +1860.000000 95.975899 +1850.000000 95.383303 +1840.000000 102.560523 +1830.000000 95.849951 +1820.000000 91.514774 +1810.000000 97.304283 +1800.000000 107.086048 +1790.000000 100.442958 +1780.000000 91.647705 +1770.000000 103.816448 +1760.000000 105.627416 +1750.000000 104.463029 +1740.000000 95.814773 +1730.000000 88.682330 +1720.000000 87.513803 +1710.000000 93.991677 +1700.000000 101.813552 +1690.000000 108.143654 +1680.000000 93.592581 +1670.000000 95.995099 +1660.000000 104.397806 +1650.000000 96.642487 +1640.000000 89.577292 +1630.000000 99.363969 +1620.000000 102.460960 +1610.000000 106.579704 +1600.000000 94.831499 +1590.000000 103.161968 +1580.000000 105.142328 +1570.000000 89.249170 +1560.000000 113.022615 +1550.000000 99.880663 +1540.000000 106.491797 +1530.000000 92.729152 +1520.000000 96.075583 +1510.000000 113.446791 +1500.000000 98.113851 +1490.000000 106.418663 +1480.000000 111.146035 +1470.000000 110.025058 +1460.000000 95.691015 +1450.000000 85.672996 +1440.000000 107.797323 +1430.000000 102.948201 +1420.000000 102.097307 +1410.000000 90.907868 +1400.000000 110.026437 +1390.000000 99.677528 +1380.000000 105.566718 +1370.000000 107.721126 +1360.000000 103.554160 +1350.000000 95.716645 +1340.000000 93.917424 +1330.000000 101.985110 +1320.000000 111.753864 +1310.000000 98.945696 +1300.000000 95.638532 +1290.000000 110.210880 +1280.000000 108.732557 +1270.000000 92.991173 +1260.000000 102.143676 +1250.000000 99.496360 +1240.000000 90.790517 +1230.000000 91.076400 +1220.000000 108.749757 +1210.000000 98.746219 +1200.000000 108.266155 +1190.000000 100.951911 +1180.000000 100.554966 +1170.000000 98.664566 +1160.000000 97.978359 +1150.000000 102.831375 +1140.000000 107.638028 +1130.000000 107.079612 +1120.000000 103.497314 +1110.000000 93.841245 +1100.000000 95.282118 +1090.000000 98.424715 +1080.000000 108.024753 +1070.000000 102.439734 +1060.000000 94.924478 +1050.000000 97.239655 +1040.000000 104.058523 +1030.000000 93.854662 +1020.000000 107.395376 +1010.000000 93.660511 +1000.000000 96.748399 +990.000000 99.753195 +980.000000 105.985487 +970.000000 94.943735 +960.000000 88.767622 +950.000000 91.592405 +940.000000 93.202856 +930.000000 94.323137 +920.000000 93.382423 +910.000000 100.155528 +900.000000 96.159797 +890.000000 99.952929 +880.000000 106.983233 +870.000000 103.785678 +860.000000 68.355925 +850.000000 44.984769 +840.000000 126.074100 +830.000000 -137.331700 +820.000000 -519.417000 +810.000000 72.713300 +800.000000 1953.805925 diff --git a/tests/data/transmission/sphere_test01.Probe.Raw.asc b/tests/data/transmission/sphere_test01.Probe.Raw.asc new file mode 100644 index 00000000..49ee48b4 --- /dev/null +++ b/tests/data/transmission/sphere_test01.Probe.Raw.asc @@ -0,0 +1,131 @@ +PE UV SUBTECH SPECTRUM ASCII PEDS 4.00 + -1 +Sample5925.Probe.Raw.asc +24/02/27 +14:41:25.00 +24/02/27 +14:41:25.00 +Hiroki Tanaka + +800.000000 +1 +Lambda 1050 +1050L1611222 +PerkinElmer UV WinLab 6.3.2.0749 / 2.02.06 Lambda 900 UV/VIS/NIR, Jan 28 2016 16:58:34 + +0 +0 +3350/2 1800.8/2 860.8/2 +0 +0 +UV/VIS +0 +1 +1 +DoubleDePol,CommonBeamDepol,RBeamAtt,SBeamAtt,150mm sphere / downward view, WB InGaAs Detector +0 +0 +0 +0 +Program +Program +3350/2 1800.8/2 860.8/2 +3350/0.2 1800.8/0.2 860.8/0.2 +3350/0.2 1800.8/0.2 860.8/0.2 +0 +3350/7.00 1800.8/7.00 + + + +0 + +860.8 +319.2 +860.9 +Front +90 +on +3350/S:100 3350/R:100 +20 +0 +0 +0 + +0 + + + +0 + + +0 +0 +0 +0 +0 + + + + +0 +0 + + +0 +0 +#HDR +-1 +-1 +#GR +nm +%T +1.0 +0.0 +1200.000000 +-10.000000 +41 +8 +686.813676 +-451.648178 +#DATA +1200.000000 183.367051 +1190.000000 -49.448635 +1180.000000 264.227734 +1170.000000 645.814302 +1160.000000 32.601486 +1150.000000 86.963537 +1140.000000 138.443926 +1130.000000 37.753200 +1120.000000 72.489443 +1110.000000 170.793652 +1100.000000 147.100845 +1090.000000 251.994567 +1080.000000 126.780150 +1070.000000 80.260626 +1060.000000 54.136363 +1050.000000 65.594526 +1040.000000 64.655391 +1030.000000 76.662967 +1020.000000 37.504395 +1010.000000 223.694380 +1000.000000 275.354160 +990.000000 50.631816 +980.000000 99.616171 +970.000000 272.929363 +960.000000 123.177259 +950.000000 80.929253 +940.000000 138.346642 +930.000000 111.995496 +920.000000 55.982196 +910.000000 175.621043 +900.000000 127.765547 +890.000000 257.176083 +880.000000 122.822312 +870.000000 51.229765 +860.000000 -281.074660 +850.000000 177.282100 +840.000000 102.836600 +830.000000 453.588521 +820.000000 355.950160 +810.000000 -451.648178 +800.000000 686.813676 From d5cdc24e724d333ada5f0e24328e50060cbcdaea Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 11 Sep 2024 18:31:54 +0200 Subject: [PATCH 06/24] Add parser test --- tests/transmission/__init__.py | 0 tests/transmission/test_parser.py | 57 +++++++++++++++++++++++++++++++ tests/xrd/__init__.py | 0 3 files changed, 57 insertions(+) create mode 100644 tests/transmission/__init__.py create mode 100644 tests/transmission/test_parser.py create mode 100644 tests/xrd/__init__.py diff --git a/tests/transmission/__init__.py b/tests/transmission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/transmission/test_parser.py b/tests/transmission/test_parser.py new file mode 100644 index 00000000..96b56665 --- /dev/null +++ b/tests/transmission/test_parser.py @@ -0,0 +1,57 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os + +import pytest +from nomad.client import normalize_all, parse + + +@pytest.fixture( + params=[ + '3DM_test01.Probe.Raw.asc', + 'F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc', + 'KTF-D.Probe.Raw.asc', + 'Sample5926.Probe.Raw.asc', + 'sphere_test01.Probe.Raw.asc', + ] +) +def parsed_archive(request): + """ + Sets up data for testing and cleans up after the test. + """ + rel_file = os.path.join( + os.path.dirname(__file__), '../data/transmission', request.param + ) + file_archive = parse(rel_file)[0] + measurement = os.path.join( + os.path.dirname(__file__), + '../data/transmission', + '.'.join(request.param.split('.')[:-1]) + '.archive.json', + ) + assert file_archive.data.measurement.m_proxy_value == os.path.abspath(measurement) + measurement_archive = parse(measurement)[0] + + yield measurement_archive + + if os.path.exists(measurement): + os.remove(measurement) + + +def test_normalize_all(parsed_archive): + normalize_all(parsed_archive) + # TODO test the normalized data diff --git a/tests/xrd/__init__.py b/tests/xrd/__init__.py new file mode 100644 index 00000000..e69de29b From 3849fd3457a6baad2f2d668e93bacbae7fc5043c Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 16 Sep 2024 09:36:03 +0200 Subject: [PATCH 07/24] Reference any composite system in the sample reference --- src/nomad_measurements/transmission/schema.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index f036313a..25813bef 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -70,7 +70,6 @@ SubSection, ) from nomad.units import ureg -from nomad_material_processing.general import Sample from nomad_measurements.utils import create_archive, merge_sections @@ -108,8 +107,8 @@ class TransmissionSpectrophotometer(Instrument, EntryData): class TransmissionSampleReference(CompositeSystemReference): """ - Reference to the sample used in the transmission measurement. Additionally, - contains the thickness and orientation of the sample. + Extends `CompositeSystemReference` to include contains the thickness and orientation + of the sample used in the transmission. """ m_def = Section( @@ -125,16 +124,6 @@ class TransmissionSampleReference(CompositeSystemReference): ) ) ) - reference = Quantity( - type=Sample, - description=""" - A reference to the sample used. - """, - a_eln=ELNAnnotation( - component='ReferenceEditQuantity', - label='sample reference', - ), - ) thickness = Quantity( type=np.float64, description=""" From 30c024a85dc64ee80ebb697b129e81e0b5b1690c Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Tue, 17 Sep 2024 11:50:03 +0200 Subject: [PATCH 08/24] Rename to path_length --- src/nomad_measurements/transmission/schema.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 25813bef..afcd265d 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -118,17 +118,15 @@ class TransmissionSampleReference(CompositeSystemReference): 'name', 'lab_id', 'reference', - 'thickness', + 'path_length', 'orientation', ] ) ) ) - thickness = Quantity( + path_length = Quantity( type=np.float64, - description=""" - Thickness of the sample along the direction of the light beam. - Also referred to as path length of the beam.""", + description='Length of the sample along the direction of the light beam.', a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 'millimeter', From 3365c73ae9bb7e0e1643b0c73858d1ef54e45f75 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Fri, 20 Dec 2024 22:16:09 +0100 Subject: [PATCH 09/24] Copy latest changes from AreaA repo --- .../transmission/__init__.py | 24 +- src/nomad_measurements/transmission/schema.py | 1212 ++++++++++------- src/nomad_measurements/transmission/utils.py | 122 ++ 3 files changed, 836 insertions(+), 522 deletions(-) create mode 100644 src/nomad_measurements/transmission/utils.py diff --git a/src/nomad_measurements/transmission/__init__.py b/src/nomad_measurements/transmission/__init__.py index dbb6e13b..fb06fe36 100644 --- a/src/nomad_measurements/transmission/__init__.py +++ b/src/nomad_measurements/transmission/__init__.py @@ -1,3 +1,21 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + from nomad.config.models.plugins import ParserEntryPoint, SchemaPackageEntryPoint @@ -25,13 +43,13 @@ def load(self): schema = TransmissionSchemaEntryPoint( name='Transmission Schema', - description='Schema for Transmission Spectrophotometry FAIR data.', + description='Schema for data from Transmission Spectrophotometry.', ) parser = TransmissionParserEntryPoint( name='Transmission Parser', - description='Parser for raw files from Transmission measurements.', - mainfile_name_re='^.*\.asc$', + description='Parser for data from Transmission Spectrophotometry.', mainfile_mime_re='text/.*|application/zip', + mainfile_name_re='^.*\.asc$', ) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index afcd265d..f1f8eada 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -38,6 +38,7 @@ ) import numpy as np +import pint import plotly.express as px from fairmat_readers_transmission import read_perkin_elmer_asc from nomad.datamodel.data import ( @@ -51,12 +52,15 @@ SectionProperties, ) from nomad.datamodel.metainfo.basesections import ( + CompositeSystem, CompositeSystemReference, + Entity, Instrument, InstrumentReference, Measurement, MeasurementResult, ReadableIdentifiers, + SystemComponent, ) from nomad.datamodel.metainfo.plot import ( PlotlyFigure, @@ -64,32 +68,183 @@ ) from nomad.metainfo import ( MEnum, + MProxy, Quantity, SchemaPackage, Section, SubSection, ) -from nomad.units import ureg +from nomad_material_processing.general import Geometry -from nomad_measurements.utils import create_archive, merge_sections +from nomad_measurements.transmission.utils import create_archive, merge_sections if TYPE_CHECKING: from nomad.datamodel.datamodel import EntryArchive from structlog.stdlib import BoundLogger -m_package = SchemaPackage( - aliases=[ - 'uv_vis_nir_transmission', - 'uv_vis_nir_transmission.schema', - 'uv_vis_nir_transmission.parser', - ], -) +m_package = SchemaPackage() + + +class Sample(CompositeSystem, EntryData): + """ + Contains information about the sample id, geometry, and reference to the material + system under `components` sub-section. + """ + + components = SubSection( + section_def=SystemComponent, + repeats=True, + ) + geometry = SubSection( + section_def=Geometry, + ) + + +class Detector(Entity): + """ + Light detector used in the transmission spectroscopy instruments. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + visible=Filter( + exclude=['datetime', 'lab_id'], + ), + ), + ), + ) + type = Quantity( + type=str, + description=""" + Type of the detector used in the instrument. Some of the popular detectors are: + | Detector | Description | + |-------------------|----------------------| + | **PMT** | Photomultiplier Tube detector used for the Ultra-Violet (UV) or visible range.| + | **InGaAs** | Indium Gallium Arsenide detector used for Near-Infra-red (NIR) range.| + | **PbS** | Lead Sulphide detector used for Infrared (IR) range.| + """, # noqa: E501 + a_eln={'component': 'StringEditQuantity'}, + ) + + def normalize(self, archive, logger): + self.name = f'{self.type} Detector' if self.type else 'Detector' + super().normalize(archive, logger) + + +class LightSource(Entity): + """ + Section to bring together different types of light sources. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + visible=Filter( + exclude=['datetime', 'lab_id'], + ), + ), + ), + ) + power = Quantity( + type=np.float64, + description='Power of the light source.', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'mW', + }, + unit='W', + ) + + +class Lamp(LightSource): + """ + Lamp used in transmission spectroscopy instruments. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + visible=Filter( + exclude=['datetime', 'lab_id'], + ), + ), + ), + ) + type = Quantity( + type=str, + description=""" + Type of the lamp used. Some of the popular materials are: + | Detector | Description | + |-------------------|----------------------| + | **Deuterium** | Used for light generation in the UV range (160 nm to 400 nm).| + | **Tungsten** | Used for light generation in the near-infrared range (320nm to 2500nm).| + """, # noqa: E501 + a_eln={'component': 'StringEditQuantity'}, + ) + + def normalize(self, archive, logger): + self.name = f'{self.type} Lamp' if self.type else 'Lamp' + super().normalize(archive, logger) + + +class Monochromator(Entity): + """ + Monochromator used to select a narrow band of wavelengths from the light source in + transmission spectroscopy instruments. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + visible=Filter( + exclude=['datetime', 'lab_id'], + ), + ), + ), + ) + +class GratingMonochromator(Monochromator): + """ + Grating monochromator used in transmission spectroscopy instruments. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + visible=Filter( + exclude=['datetime', 'lab_id'], + ), + ), + ), + ) + groove_density = Quantity( + description='Number of grooves per unit length of the grating.', + type=float, + unit='1/m', + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': '1/mm', + }, + ) + + def normalize(self, archive, logger): + self.name = 'Grating Monochromator' + super().normalize(archive, logger) -class TransmissionSpectrophotometer(Instrument, EntryData): + +class Spectrophotometer(Instrument, EntryData): """ - Entry section for the transmission spectrophotometer. + Entry section for transmission spectrophotometer. + + When extending the section for a specific instrument, the components (detectors, + light sources, and monochromators) should be instantiated in the `self.normalize` + method based on what components are physically available in the instrument. + The component list should be populated such that components used in lower wavelength + ranges are listed first. For instance, PMT detector should be listed before InGaAs. + Take a look at the `PerkinElmersLambdaSpectrophotometer` class for an example. """ m_def = Section() @@ -103,12 +258,96 @@ class TransmissionSpectrophotometer(Instrument, EntryData): description='Software/firmware version.', a_eln={'component': 'StringEditQuantity'}, ) + detectors = SubSection( + section_def=Detector, + repeats=True, + ) + light_sources = SubSection( + section_def=Lamp, + repeats=True, + ) + monochromators = SubSection( + section_def=Monochromator, + repeats=True, + ) + + +class PerkinElmersLambdaSpectrophotometer(Spectrophotometer): + """ + Entry section for Perkin Elmers Lambda series transmission spectrophotometer. + """ + + monochromators = SubSection( + section_def=GratingMonochromator, + repeats=True, + ) + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + """ + The normalizer for the `PerkinElmersLambdaSpectrophotometer` class. + + Args: + archive (EntryArchive): The NOMAD archive. + logger (BoundLogger): A structlog logger. + """ + # add detectors + if not self.detectors: + self.m_setdefault('detectors/0') + self.detectors[0].type = 'PMT' + self.detectors[0].description = """ + Photomultiplier Tube used for UV or visible range.""" + + self.m_setdefault('detectors/1') + self.detectors[1].type = 'InGaAs' + self.detectors[1].description = """ + Indium Gallium Arsenide used for NIR range.""" + + self.m_setdefault('detectors/2') + self.detectors[2].type = 'PbS' + self.detectors[2].description = 'Lead Sulphide used for IR range.' + + for detector in self.detectors: + detector.normalize(archive, logger) + + # add light sources + if not self.light_sources: + self.m_setdefault('light_sources/0') + self.light_sources[0].type = 'Deuterium' + self.light_sources[0].description = """ + Deuterium lamp used for light generation in the UV range.""" + + self.m_setdefault('light_sources/1') + self.light_sources[1].type = 'Tungsten' + self.light_sources[1].description = """ + Tungsten lamp used for light generation in the NIR range.""" + + for light_source in self.light_sources: + light_source.normalize(archive, logger) + + # add monochromators + if not self.monochromators: + self.m_setdefault('monochromators/0') + self.monochromators[0].groove_density = 1440000 + self.monochromators[0].description = """ + Holographic gratings with 1440 lines/mm used for generating light in UV/Vis + range.""" + + self.m_setdefault('monochromators/1') + self.monochromators[1].groove_density = 360000 + self.monochromators[1].description = """ + Holographic gratings with 360 lines/mm used for generating light in NIR + range.""" + + for monochromator in self.monochromators: + monochromator.normalize(archive, logger) + + super().normalize(archive, logger) class TransmissionSampleReference(CompositeSystemReference): """ - Extends `CompositeSystemReference` to include contains the thickness and orientation - of the sample used in the transmission. + Reference to the sample used in the transmission measurement. Additionally, + contains the thickness and orientation of the sample. """ m_def = Section( @@ -118,15 +357,27 @@ class TransmissionSampleReference(CompositeSystemReference): 'name', 'lab_id', 'reference', - 'path_length', + 'thickness', 'orientation', ] ) ) ) - path_length = Quantity( + reference = Quantity( + type=Sample, + description=""" + A reference to the sample used. + """, + a_eln=ELNAnnotation( + component='ReferenceEditQuantity', + label='sample reference', + ), + ) + thickness = Quantity( type=np.float64, - description='Length of the sample along the direction of the light beam.', + description=""" + Thickness of the sample along the direction of the light beam. + Also referred to as path length of the beam.""", a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 'millimeter', @@ -157,11 +408,11 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: class Accessory(ArchiveSection): """ - Section for adding setting for a custom accessory. + Section to specify settings of a custom accessory used in transmission spectroscopy + instruments. """ m_def = Section( - description='An accessory used in the instrument.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ @@ -185,14 +436,10 @@ class Accessory(ArchiveSection): class PolDepol(Accessory): """ - Optional accessory to polarize or depolarize the light beam entering the sample. + Accessory to polarize or depolarize the light beam entering the sample chamber. """ m_def = Section( - description=( - 'Optional accessory to polarize or depolarize the light beam ' - 'entering the sample.' - ), a_eln=ELNAnnotation( properties=SectionProperties( order=[ @@ -229,13 +476,10 @@ def normalize(self, archive, logger): class Aperture(Accessory): """ - Section for adding settings of a custom aperture. + Custom aperture placed in front of the sample inside the sample compartment. """ m_def = Section( - description=""" - Custom aperture placed in front of the sample inside the sample compartment. - """, a_eln=ELNAnnotation( properties=SectionProperties( order=[ @@ -262,14 +506,12 @@ class SettingOverWavelengthRange(ArchiveSection): """ m_def = Section( - description='An instrument setting set over a range of wavelength.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ 'name', - 'wavelength_upper_limit', 'wavelength_lower_limit', - 'value', + 'wavelength_upper_limit', ], ), ), @@ -279,30 +521,24 @@ class SettingOverWavelengthRange(ArchiveSection): description='Short description containing wavelength range.', a_eln={'component': 'StringEditQuantity'}, ) - wavelength_upper_limit = Quantity( + wavelength_lower_limit = Quantity( type=np.float64, - description='Upper limit of wavelength range.', + description='Lower limit of wavelength range.', a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 'nm', }, unit='nm', ) - wavelength_lower_limit = Quantity( + wavelength_upper_limit = Quantity( type=np.float64, - description='Lower limit of wavelength range.', + description='Upper limit of wavelength range.', a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 'nm', }, unit='nm', ) - value = Quantity( - type=np.float64, - description='Value of the given instrument setting.', - a_eln={'component': 'NumberEditQuantity'}, - unit='dimensionless', - ) def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: """ @@ -333,25 +569,25 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: self.name = f'[{lower_limit}, {upper_limit}]' -class SlitWidth(SettingOverWavelengthRange): +class MonochromatorSlitWidth(SettingOverWavelengthRange): """ - Slit width setting over a wavelength range. + Monochromator slit width setting over a wavelength range. """ m_def = Section( - description='Slit width value over a wavelength range.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ 'name', - 'wavelength_upper_limit', 'wavelength_lower_limit', - 'value', + 'wavelength_upper_limit', + 'slit_width_servo', + 'slit_width', ], ), ), ) - value = Quantity( + slit_width = Quantity( type=np.float64, description='Slit width value.', a_eln={ @@ -370,86 +606,49 @@ class SlitWidth(SettingOverWavelengthRange): ) -class Monochromator(ArchiveSection): - """ - Monochromator setting over a wavelength range. - """ - - m_def = Section( - description='Monochromator setting over a wavelength range.', - ) - monochromator_change_point = Quantity( - type=np.float64, - description='The wavelength at which the monochromator changes settings.', - a_eln={ - 'component': 'NumberEditQuantity', - 'defaultDisplayUnit': 'nm', - }, - unit='nm', - shape=['*'], - ) - monochromator_slit_width = SubSection( - section_def=SlitWidth, - repeats=True, - ) - - -class Lamp(ArchiveSection): +class MonochromatorSettings(SettingOverWavelengthRange): """ - Lamp setting over a wavelength range. + Monochromator used over a wavelength range. """ m_def = Section( - description='Lamp setting over a wavelength range.', - ) - d2_lamp = Quantity( - type=bool, - description=( - 'True if the Deuterium (D2) lamp is used ' - '(typically covers the UV range from about 160 nm to 400 nm).' - ), - a_eln={'component': 'BoolEditQuantity'}, - ) - tungsten_lamp = Quantity( - type=bool, - description=( - 'True if the Tungsten lamp is used ' - '(typically covers the visible to near-infrared range from about ' - '320 nm to 2500 nm)' + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_lower_limit', + 'wavelength_upper_limit', + 'monochromator', + ], + ), ), - a_eln={'component': 'BoolEditQuantity'}, ) - lamp_change_point = Quantity( - type=np.float64, - description='The wavelength at which lamp used for the beam changes.', - a_eln={ - 'component': 'NumberEditQuantity', - 'defaultDisplayUnit': 'nm', - }, - unit='nm', - shape=['*'], + monochromator = Quantity( + type=Monochromator, + description='Monochromator used in the current wavelength range.', + a_eln={'component': 'ReferenceEditQuantity'}, ) class NIRGain(SettingOverWavelengthRange): """ - NIR gain factor over a range of wavelength. + NIR gain factor of detectors used in transmission spectrophotometry over a range of + wavelength. """ m_def = Section( - description='NIR gain factor over a wavelength range.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ 'name', - 'wavelength_upper_limit', 'wavelength_lower_limit', - 'value', + 'wavelength_upper_limit', + 'nir_gain_factor', ], ), ), ) - value = Quantity( + nir_gain_factor = Quantity( type=np.float64, description='NIR gain factor of the detector.', a_eln={'component': 'NumberEditQuantity'}, @@ -459,23 +658,23 @@ class NIRGain(SettingOverWavelengthRange): class IntegrationTime(SettingOverWavelengthRange): """ - Integration time over a wavelength range. + Integration time of the detectors used in transmission spectrophotometry over a + wavelength range. """ m_def = Section( - description='Integration time over a wavelength range.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ 'name', - 'wavelength_upper_limit', 'wavelength_lower_limit', - 'value', + 'wavelength_upper_limit', + 'integration_time', ], ), ), ) - value = Quantity( + integration_time = Quantity( type=np.float64, description='Integration time value.', a_eln={ @@ -486,84 +685,54 @@ class IntegrationTime(SettingOverWavelengthRange): ) -class Detector(ArchiveSection): +class DetectorSettings(SettingOverWavelengthRange): """ - Detector setting over a wavelength range. + Settings of the detector used in transmission spectrophotometry instruments. """ m_def = Section( - description='Detector setting over a wavelength range.', a_eln=ELNAnnotation( properties=SectionProperties( order=[ - 'module', - 'detectors', - 'detector_change_point', - 'nir_gain', - 'integration_time', + 'name', + 'wavelength_lower_limit', + 'wavelength_upper_limit', + 'detector', ], ), ), ) - module = Quantity( - type=MEnum( - [ - 'Three Detector Module', - 'Two Detector Module', - '150-mm Integrating Sphere', - ] - ), - a_eln={'component': 'EnumEditQuantity'}, - description=""" - Modules containing multiple detectors for different wavelength ranges. - | Detector Module | Description | - |--------------------------------------|----------------------| - | **Three Detector Module** | Installed as standard module on Perkin-Elmer Lambda 1050 WB and NB spectrophotometers. Contains three detectors for different wavelength ranges: PMT, InGaAs, PbS. | - | **Two Detector Module** | Installed on Perkin-Elmer Lambda 750, 900, 950 spectrophotometers. Contains two detectors for different wavelength ranges: PMT, PbS. | - | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | - """, # noqa: E501 - ) - detectors = Quantity( - type=str, - description=""" - Detectors used in the instrument. Some of the popular detectors are: - | Detector | Description | - |-------------------|----------------------| - | **PMT** | Photomultiplier Tube detector used for the Ultra-Violet (UV) or visible range.| - | **InGaAs** | Indium Gallium Arsenide detector used for Near-Infra-red (NIR) range.| - | **PbS** | Lead Sulphide detector used for Infrared (IR) range.| - """, # noqa: E501 - a_eln={'component': 'StringEditQuantity'}, - shape=['*'], - ) - detector_change_point = Quantity( - type=np.float64, - description='The wavelength at which the detector module changes.', - a_eln={ - 'component': 'NumberEditQuantity', - 'defaultDisplayUnit': 'nm', - }, - unit='nm', - shape=['*'], - ) - nir_gain = SubSection( - section_def=NIRGain, - repeats=True, + + detector = Quantity( + type=Detector, + description='Detector used in the current wavelength range', + a_eln={'component': 'ReferenceEditQuantity'}, ) - integration_time = SubSection( - section_def=IntegrationTime, - repeats=True, + + +class LampSettings(SettingOverWavelengthRange): + """ + Settings of the lamp used in transmission spectrophotometry instruments. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'wavelength_lower_limit', + 'wavelength_upper_limit', + 'lamp', + ], + ), + ), ) - def normalize(self, archive, logger): - super().normalize(archive, logger) - if self.module is not None: - if self.module == 'Three Detector Module': - self.detectors = ['PMT', 'InGaAs', 'PbS'] - elif self.module == 'Two Detector Module': - self.detectors = ['PMT', 'PbS'] - elif self.module == '150-mm Integrating Sphere': - self.detectors = ['PMT', 'InGaAs'] + lamp = Quantity( + type=Lamp, + description='Lamp used in the current wavelength range.', + a_eln={'component': 'ReferenceEditQuantity'}, + ) class Attenuator(ArchiveSection): @@ -571,49 +740,33 @@ class Attenuator(ArchiveSection): Attenuation setting for the sample and reference beam. """ - m_def = Section( - description='Attenuation setting for the sample and reference beam.', - ) - sample = Quantity( - type=int, - description='Sample beam attenuation in percentage.', + m_def = Section() + sample_beam_attenuation = Quantity( + type=float, + description='Value of sample beam attenuation ranging from 0 to 1.', a_eln={ 'component': 'NumberEditQuantity', 'minValue': 0, - 'maxValue': 100, + 'maxValue': 1, }, unit='dimensionless', ) - reference = Quantity( - type=int, - description='Reference beam attenuation in percentage.', + reference_beam_attenuation = Quantity( + type=float, + description='Value of reference beam attenuation ranging from 0 to 1.', a_eln={ 'component': 'NumberEditQuantity', 'minValue': 0, - 'maxValue': 100, + 'maxValue': 1, }, unit='dimensionless', ) -class TransmissionSettings(ArchiveSection): - """ - Section for the settings of the instrument used for transmission measurement. - """ - - ordinate_type = Quantity( - type=MEnum(['%T', 'A']), - description=( - 'Specifies whether the ordinate (y-axis) of the measurement data is ' - 'percent transmittance (%T) or absorbance (A).' - ), - a_eln={'component': 'EnumEditQuantity'}, - ) - - -class TransmissionResult(MeasurementResult): +class UVVisNirTransmissionResult(MeasurementResult): """ - Section for the results of the Transmission measurement. + Section for the results of the Transmission Spectroscopy measurement in UV, visible, + and near IR ranges of wavelength. """ m_def = Section( @@ -630,7 +783,7 @@ class TransmissionResult(MeasurementResult): ], ), ) - ) + ), ) array_index = Quantity( type=int, @@ -639,14 +792,16 @@ class TransmissionResult(MeasurementResult): ) transmittance = Quantity( type=np.float64, - description='Measured transmittance in percentage.', + description='Measured transmittance ranging from 0 to 1.', shape=['*'], unit='dimensionless', a_plot={'x': 'array_index', 'y': 'transmittance'}, ) absorbance = Quantity( type=np.float64, - description='Measured absorbance ranging from 0 to 1.', + description=""" + Calculated absorbance using the relation A = -log(T), where T denotes + transmittance.""", shape=['*'], unit='dimensionless', a_plot={'x': 'array_index', 'y': 'absorbance'}, @@ -661,7 +816,7 @@ class TransmissionResult(MeasurementResult): def generate_plots(self) -> list[PlotlyFigure]: """ - Generate the plotly figures for the `TransmissionResult` section. + Generate the plotly figures for the `UVVisNirTransmissionResult` section. Returns: list[PlotlyFigure]: The plotly figures. @@ -680,8 +835,6 @@ def generate_plots(self) -> list[PlotlyFigure]: y_label = key.capitalize() yaxis_title = y_label - if key == 'transmittance': - yaxis_title += ' (%)' y = getattr(self, key).magnitude line_linear = px.line(x=x, y=y) @@ -709,41 +862,16 @@ def generate_plots(self) -> list[PlotlyFigure]: return figures -class Transmission(Measurement): - """ - Schema for Transmission Spectrophotometry measurement. - """ - - user = Quantity( - type=str, - description='Name of user or analyst.', - a_eln={'component': 'StringEditQuantity'}, - ) - - method = Measurement.method.m_copy() - method.default = 'Transmission Spectrophotometry' - - samples = Measurement.samples.m_copy() - samples.section_def = TransmissionSampleReference - - results = Measurement.results.m_copy() - results.section_def = TransmissionResult - - transmission_settings = SubSection( - section_def=TransmissionSettings, - ) - - -class UVVisNirTransmissionSettings(TransmissionSettings): +class UVVisNirTransmissionSettings(ArchiveSection): """ - Section for setting of the instrument used for transmission measurement. + Section for the settings of the Transmission Spectroscopy measurement in UV, + visible, and near IR ranges of wavelength. """ m_def = Section( a_eln=ELNAnnotation( properties=SectionProperties( order=[ - 'ordinate_type', 'sample_beam_position', 'common_beam_mask', 'common_beam_depolarizer', @@ -751,6 +879,24 @@ class UVVisNirTransmissionSettings(TransmissionSettings): ), ), ) + detector_module = Quantity( + type=MEnum( + [ + 'Three Detector Module', + 'Two Detector Module', + '150-mm Integrating Sphere', + ] + ), + a_eln={'component': 'EnumEditQuantity'}, + description=""" + Modules containing multiple detectors for different wavelength ranges. + | Detector Module | Description | + |--------------------------------------|----------------------| + | **Three Detector Module** | Installed as standard module on Perkin-Elmer Lambda 1050 WB and NB spectrophotometers. Contains three detectors for different wavelength ranges: PMT, InGaAs, PbS. | + | **Two Detector Module** | Installed on Perkin-Elmer Lambda 750, 900, 950 spectrophotometers. Contains two detectors for different wavelength ranges: PMT, PbS. | + | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | + """, # noqa: E501 + ) sample_beam_position = Quantity( type=MEnum(['Front', 'Rear']), description=( @@ -760,16 +906,16 @@ class UVVisNirTransmissionSettings(TransmissionSettings): a_eln={'component': 'EnumEditQuantity'}, ) common_beam_mask = Quantity( - type=int, + type=float, description=( - 'Mask setting for the common beam in percentage.' - '100% means the mask is fully open and ' - '100% of the beam passes. 0% means the mask is closed and no light passes.' + 'Mask setting for the common beam ranging from 0 to 1.' + '1 means the mask is fully open and the beam passes completely. ' + '0 means the mask is closed and no light passes.' ), a_eln={ 'component': 'NumberEditQuantity', 'minValue': 0, - 'maxValue': 100, + 'maxValue': 1, }, unit='dimensionless', ) @@ -787,170 +933,73 @@ class UVVisNirTransmissionSettings(TransmissionSettings): section_def=Accessory, repeats=True, ) - monochromator = SubSection( - section_def=Monochromator, + attenuator = SubSection( + section_def=Attenuator, ) - lamp = SubSection( - section_def=Lamp, + light_source = SubSection( + section_def=LampSettings, + repeats=True, + ) + monochromator = SubSection( + section_def=MonochromatorSettings, + repeats=True, ) detector = SubSection( - section_def=Detector, + section_def=DetectorSettings, + repeats=True, ) - attenuator = SubSection( - section_def=Attenuator, + monochromator_slit_width = SubSection( + section_def=MonochromatorSlitWidth, + repeats=True, ) - - -class UVVisNirTransmissionResult(TransmissionResult): - """ - Section for the results of the UV-Vis NIR Transmission measurement. - """ - - m_def = Section( - a_eln=ELNAnnotation( - properties=SectionProperties( - order=[ - 'transmittance', - 'absorbance', - 'wavelength', - 'extinction_coefficient', - ], - visible=Filter( - exclude=[ - 'array_index', - ], - ), - ) - ) + nir_gain = SubSection( + section_def=NIRGain, + repeats=True, ) - extinction_coefficient = Quantity( - type=np.float64, - description=( - 'Extinction coefficient calculated from transmittance and sample thickness ' - 'values: -log(T)/L. The coefficient includes the effects of ' - 'absorption, reflection, and scattering.' - ), - shape=['*'], - unit='1/m', - a_plot={'x': 'array_index', 'y': 'extinction_coefficient'}, + integration_time = SubSection( + section_def=IntegrationTime, + repeats=True, ) - def generate_plots(self) -> list[PlotlyFigure]: - """ - Extends TransmissionResult.generate_plots() method to include the plotly - figures for the `UVVisNirTransmissionResult` section. - - Returns: - list[PlotlyFigure]: The plotly figures. - """ - figures = super().generate_plots() - if self.wavelength is None: - return figures - - # generate plot for extinction coefficient - if self.extinction_coefficient is None: - return figures - - x = self.wavelength.to('nm').magnitude - x_label = 'Wavelength' - xaxis_title = x_label + ' (nm)' - - y = self.extinction_coefficient.to('1/cm').magnitude - y_label = 'Extinction coefficient' - yaxis_title = y_label + ' (1/cm)' - - line_linear = px.line(x=x, y=y) - - line_linear.update_layout( - title=f'{y_label} over {x_label}', - xaxis_title=xaxis_title, - yaxis_title=yaxis_title, - xaxis=dict( - fixedrange=False, - ), - yaxis=dict( - fixedrange=False, - ), - template='plotly_white', - ) - - figures.append( - PlotlyFigure( - label=f'{y_label} linear plot', - figure=line_linear.to_plotly_json(), - ), - ) - - return figures - - def calculate_extinction_coefficient(self, archive, logger): - """ - Calculate the extinction coefficient from the transmittance and sample - thickness. The formula used is: -log( T[%] / 100 ) / L. - - Args: - archive (EntryArchive): The archive containing the section. - logger (BoundLogger): A structlog logger. - """ - self.extinction_coefficient = None - if not archive.data.samples: - logger.warning( - 'Cannot calculate extinction coefficient as sample not found.' - ) - return - if not archive.data.samples[0].thickness: - logger.warning( - 'Cannot calculate extinction coefficient as sample thickness not found ' - 'or the value is 0.' - ) - return - - path_length = archive.data.samples[0].thickness - if self.transmittance is not None: - extinction_coeff = -np.log(self.transmittance / 100) / path_length - # TODO: The if-block is a temperary fix to avoid processing of nans in - # the archive. The issue will be fixed in the future. - if np.any(np.isnan(extinction_coeff)): - logger.warning( - 'Failed to save extinction coefficient. ' - 'Encountered NaN values in the calculation.' - ) - return - self.extinction_coefficient = extinction_coeff - - def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: - """ - The normalizer for the `UVVisNirTransmissionResult` class. - - Args: - archive (EntryArchive): The archive containing the section that is being - normalized. - logger (BoundLogger): A structlog logger. - """ - super().normalize(archive, logger) - self.calculate_extinction_coefficient(archive, logger) - -class UVVisNirTransmission(Transmission): +class UVVisNirTransmission(Measurement): """ - Schema for UV-Vis NIR Transmission, which extends the `Transmission` class. + Section for Transmission Spectroscopy measurement in UV, visible, and near IR ranges + of wavelength. """ m_def = Section() - method = Transmission.method.m_copy() - method.default = 'UV-Vis-NIR Transmission' + user = Quantity( + type=str, + description='Name of user or analyst.', + a_eln={'component': 'StringEditQuantity'}, + ) - results = Transmission.results.m_copy() - results.section_def = UVVisNirTransmissionResult + method = Quantity( + type=str, + default='UV-Vis-NIR Transmission Spectrophotometry', + ) + + samples = SubSection( + section_def=TransmissionSampleReference, + description='A list of all the samples measured during the measurement.', + repeats=True, + ) + results = SubSection( + section_def=UVVisNirTransmissionResult, + description='The result of the UV-Vis-NIR measurement.', + repeats=True, + ) - transmission_settings = Transmission.transmission_settings.m_copy() - transmission_settings.section_def = UVVisNirTransmissionSettings + transmission_settings = SubSection( + section_def=UVVisNirTransmissionSettings, + ) class ELNUVVisNirTransmission(UVVisNirTransmission, PlotSection, EntryData): """ - Entry class for UVVisNirTransmission. Handles the population of the schema and + Entry section for UVVisNirTransmission. Handles the population of the schema and plotting. Data is added either through manual input in the GUI or by parsing the measurement files coming from the instrument. """ @@ -1001,17 +1050,25 @@ def create_instrument_entry( Returns: InstrumentReference: The instrument reference. """ - instrument = TransmissionSpectrophotometer( - name=data_dict['instrument_name'], - serial_number=data_dict['instrument_serial_number'], - software_version=data_dict['instrument_firmware_version'], - ) + instrument_name = data_dict['instrument_name'].lower() + + if 'lambda' in instrument_name: + instrument = PerkinElmersLambdaSpectrophotometer() + else: + instrument = Spectrophotometer() + + instrument.name = data_dict['instrument_name'] + instrument.serial_number = data_dict['instrument_serial_number'] + instrument.software_version = data_dict['instrument_firmware_version'] if data_dict['start_datetime'] is not None: instrument.datetime = data_dict['start_datetime'] + instrument.normalize(archive, logger) + file_name = f'{instrument.name}_{instrument.serial_number}.archive.json' + file_name = file_name.replace(' ', '_') + m_proxy_value = create_archive(instrument, archive, file_name) logger.info('Created instrument entry.') - m_proxy_value = create_archive(instrument, archive, 'instrument.archive.json') return InstrumentReference(reference=m_proxy_value) @@ -1043,13 +1100,10 @@ def get_instrument_reference( serial_number = data_dict['instrument_serial_number'] api_query = { - 'search_quantities': { - 'id': ( - 'data.serial_number#transmission.schema.' - 'TransmissionSpectrophotometer' - ), - 'str_value': f'{serial_number}', - }, + 'entry_type:any': [ + 'Spectrophotometer', + 'PerkinElmersLambdaSpectrophotometer', + ] } search_result = search( owner='visible', @@ -1057,30 +1111,35 @@ def get_instrument_reference( user_id=archive.metadata.main_author.user_id, ) - if not search_result.data: + valid_instruments = [] + for entry in search_result.data: + if entry['data']['serial_number'] == serial_number: + valid_instruments.append(entry) + + if not valid_instruments: logger.warning( - f'No "TransmissionSpectrophotometer" instrument found with the serial ' + f'No "Spectrophotometer" instrument found with the serial ' f'number "{serial_number}". Creating an entry for the instrument.' ) return self.create_instrument_entry(data_dict, archive, logger) - if len(search_result.data) > 1: + if len(valid_instruments) > 1: logger.warning( - f'Multiple "TransmissionSpectrophotometer" instruments found with the ' + f'Multiple "Spectrophotometer" instruments found with the ' f'serial number "{serial_number}". Please select it manually.' ) return None - entry = search_result.data[0] - upload_id = entry['upload_id'] - entry_id = entry['entry_id'] + upload_id = valid_instruments[0]['upload_id'] + entry_id = valid_instruments[0]['entry_id'] m_proxy_value = f'../uploads/{upload_id}/archive/{entry_id}#/data' return InstrumentReference(reference=m_proxy_value) def write_transmission_data( # noqa: PLR0912, PLR0915 self, - transmission_dict: dict[str, Any], + transmission: UVVisNirTransmission, + data_dict: dict[str, Any], archive: 'EntryArchive', logger: 'BoundLogger', ) -> None: @@ -1088,112 +1147,251 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 Populate `UVVisNirTransmission` section using data from a dict. Args: - transmission_dict (dict[str, Any]): A dictionary with the transmission data. + data_dict (dict[str, Any]): A dictionary with the transmission data. archive (EntryArchive): The archive containing the section. logger (BoundLogger): A structlog logger. """ - self.user = transmission_dict['analyst_name'] - if transmission_dict['start_datetime'] is not None: - self.datetime = transmission_dict['start_datetime'] - - result = UVVisNirTransmissionResult( - wavelength=transmission_dict['measured_wavelength'], - ) - if transmission_dict['ordinate_type'] == 'A': - result.absorbance = transmission_dict['measured_ordinate'] - elif transmission_dict['ordinate_type'] == '%T': - result.transmittance = transmission_dict['measured_ordinate'] + transmission.user = data_dict['analyst_name'] + if data_dict['start_datetime'] is not None: + transmission.datetime = data_dict['start_datetime'] + + # add instrument + instruments = [] + instrument_reference = self.get_instrument_reference(data_dict, archive, logger) + if instrument_reference: + if isinstance(instrument_reference.reference, MProxy): + instrument_reference.reference.m_proxy_context = archive.m_context + instruments = [instrument_reference] + transmission.instruments = instruments + + # add results + transmission.m_setdefault('results/0') + transmission.results[0].wavelength = data_dict['measured_wavelength'] + if data_dict['ordinate_type'] == 'A': + transmission.results[0].absorbance = data_dict['measured_ordinate'] + elif data_dict['ordinate_type'] == '%T': + transmission.results[0].transmittance = data_dict['measured_ordinate'] / 100 else: - logger.warning(f"Unknown ordinate type '{transmission_dict['ordinate']}'.") - result.normalize(archive, logger) + logger.warning(f"Unknown ordinate type '{data_dict['ordinate']}'.") + transmission.results[0].normalize(archive, logger) - lamp = Lamp( - d2_lamp=transmission_dict['is_d2_lamp_used'], - tungsten_lamp=transmission_dict['is_tungsten_lamp_used'], - lamp_change_point=transmission_dict['lamp_change_wavelength'], - ) - lamp.normalize(archive, logger) + # add settings + transmission.m_setdefault('transmission_settings') + transmission.transmission_settings.sample_beam_position = data_dict[ + 'sample_beam_position' + ] + transmission.transmission_settings.common_beam_depolarizer = data_dict[ + 'is_common_beam_depolarizer_on' + ] + if data_dict['common_beam_mask_percentage'] is not None: + transmission.transmission_settings.common_beam_mask = ( + data_dict['common_beam_mask_percentage'] / 100 + ) - detector_module = transmission_dict['detector_module'] + # add settings: light sources + lamps = [] + if data_dict['is_d2_lamp_used']: + lamps.append('Deuterium') + if data_dict['is_tungsten_lamp_used']: + lamps.append('Tungsten') + try: + i = 0 + for light_source in instrument_reference.reference.light_sources: + if light_source.type in lamps: + transmission.m_setdefault(f'transmission_settings/light_source/{i}') + transmission.transmission_settings.light_source[ + i + ].lamp = light_source + i += 1 + except Exception as e: + logger.warning( + f'Failed to add lamp settings. Error: {e}', + ) + lamp_change_points = data_dict['lamp_change_wavelength'] + if ( + lamp_change_points is not None + and len(lamp_change_points) + == len(transmission.transmission_settings.light_source) - 1 + ): + for idx, lamp_change_point in enumerate(lamp_change_points): + transmission.transmission_settings.light_source[ + idx + ].wavelength_upper_limit = lamp_change_point + transmission.transmission_settings.light_source[ + idx + 1 + ].wavelength_lower_limit = lamp_change_point + for light_source_setting in transmission.transmission_settings.light_source: + light_source_setting.normalize(archive, logger) + + # add settings: detector + detector_module = data_dict['detector_module'] + detector_list = [] if detector_module == 'uv/vis/nir detector': - if 'lambda 1050' in transmission_dict['instrument_name'].lower(): - detector_module = 'Three Detector Module' - elif 'lambda 950' in transmission_dict['instrument_name'].lower(): - detector_module = 'Two Detector Module' - elif 'lambda 900' in transmission_dict['instrument_name'].lower(): - detector_module = 'Two Detector Module' - elif 'lambda 750' in transmission_dict['instrument_name'].lower(): - detector_module = 'Two Detector Module' + if 'lambda 1050' in data_dict['instrument_name'].lower(): + transmission.transmission_settings.detector_module = ( + 'Three Detector Module' + ) + detector_list = ['PMT', 'InGaAs', 'PbS'] + elif any( + [ + 'lambda 950' in data_dict['instrument_name'].lower(), + 'lambda 900' in data_dict['instrument_name'].lower(), + 'lambda 750' in data_dict['instrument_name'].lower(), + ] + ): + transmission.transmission_settings.detector_module = ( + 'Two Detector Module' + ) + detector_list = ['PMT', 'PbS'] if detector_module == '150mm sphere': - detector_module = '150-mm Integrating Sphere' - detector = Detector( - module=detector_module, - ) - for idx, wavelength_value in enumerate(transmission_dict['detector_NIR_gain']): - nir_gain = NIRGain( - wavelength_upper_limit=wavelength_value['wavelength'], - value=wavelength_value['value'], + transmission.transmission_settings.detector_module = ( + '150-mm Integrating Sphere' ) - if idx + 1 < len(transmission_dict['detector_NIR_gain']): - nir_gain.wavelength_lower_limit = transmission_dict[ - 'detector_NIR_gain' - ][idx + 1]['wavelength'] - nir_gain.normalize(archive, logger) - detector.nir_gain.append(nir_gain) - for idx, wavelength_value in enumerate( - transmission_dict['detector_integration_time'] + detector_list = ['PMT', 'InGaAs'] + try: + i = 0 + for detector in instrument_reference.reference.detectors: + if detector.type in detector_list: + transmission.m_setdefault(f'transmission_settings/detector/{i}') + transmission.transmission_settings.detector[i].detector = detector + i += 1 + except Exception as e: + logger.warning( + f'Failed to add detector settings. Error: {e}', + ) + detector_change_points = data_dict['detector_change_wavelength'] + if ( + detector_change_points is not None + and len(detector_change_points) + == len(transmission.transmission_settings.detector) - 1 ): - integration_time = IntegrationTime( - wavelength_upper_limit=wavelength_value['wavelength'], - value=wavelength_value['value'], + for idx, change_point in enumerate(detector_change_points): + transmission.transmission_settings.detector[ + idx + ].wavelength_upper_limit = change_point + transmission.transmission_settings.detector[ + idx + 1 + ].wavelength_lower_limit = change_point + for detector_setting in transmission.transmission_settings.detector: + detector_setting.normalize(archive, logger) + + # add settings: monochromator + try: + i = 0 + for monochromator in instrument_reference.reference.monochromators: + transmission.m_setdefault(f'transmission_settings/monochromator/{i}') + transmission.transmission_settings.monochromator[ + i + ].monochromator = monochromator + i += 1 + except Exception as e: + logger.warning( + f'Failed to add monochromator settings. Error: {e}', ) - if idx + 1 < len(transmission_dict['detector_integration_time']): - integration_time.wavelength_lower_limit = transmission_dict[ - 'detector_integration_time' - ][idx + 1]['wavelength'] - integration_time.normalize(archive, logger) - detector.integration_time.append(integration_time) - - detector.detector_change_point = transmission_dict['detector_change_wavelength'] - detector.normalize(archive, logger) - - monochromator = Monochromator() - for idx, wavelength_value in enumerate( - transmission_dict['monochromator_slit_width'] + monochromator_change_points = data_dict['monochromator_change_wavelength'] + if ( + monochromator_change_points is not None + and len(monochromator_change_points) + == len(transmission.transmission_settings.monochromator) - 1 ): - slit_width = SlitWidth( - wavelength_upper_limit=wavelength_value['wavelength'], + for idx, change_point in enumerate(monochromator_change_points): + transmission.transmission_settings.monochromator[ + idx + ].wavelength_upper_limit = change_point + transmission.transmission_settings.monochromator[ + idx + 1 + ].wavelength_lower_limit = change_point + for monochromator_setting in transmission.transmission_settings.monochromator: + monochromator_setting.normalize(archive, logger) + + # add settings: monochromator slit width + for idx, wavelength_value in enumerate(data_dict['monochromator_slit_width']): + transmission.m_setdefault( + f'transmission_settings/monochromator_slit_width/{idx}' ) + transmission.transmission_settings.monochromator_slit_width[ + idx + ].wavelength_upper_limit = wavelength_value['wavelength'] if ( isinstance(wavelength_value['value'], str) and wavelength_value['value'].lower() == 'servo' ): - slit_width.value = None - slit_width.slit_width_servo = True - elif isinstance(wavelength_value['value'], ureg.Quantity): - slit_width.value = wavelength_value['value'] - slit_width.slit_width_servo = False + transmission.transmission_settings.monochromator_slit_width[ + idx + ].slit_width = None + transmission.transmission_settings.monochromator_slit_width[ + idx + ].slit_width_servo = True + elif isinstance(wavelength_value['value'], pint.Quantity): + transmission.transmission_settings.monochromator_slit_width[ + idx + ].slit_width = wavelength_value['value'] + transmission.transmission_settings.monochromator_slit_width[ + idx + ].slit_width_servo = False else: logger.warning( f'Invalid slit width value "{wavelength_value["value"]}" for ' f'wavelength "{wavelength_value["wavelength"]}".' ) continue - if idx + 1 < len(transmission_dict['monochromator_slit_width']): - slit_width.wavelength_lower_limit = transmission_dict[ - 'monochromator_slit_width' - ][idx + 1]['wavelength'] - slit_width.normalize(archive, logger) - monochromator.monochromator_slit_width.append(slit_width) - monochromator.monochromator_change_point = transmission_dict[ - 'monochromator_change_wavelength' - ] - monochromator.normalize(archive, logger) + if idx - 1 >= 0: + transmission.transmission_settings.monochromator_slit_width[ + idx + ].wavelength_lower_limit = data_dict['monochromator_slit_width'][ + idx - 1 + ]['wavelength'] + transmission.transmission_settings.monochromator_slit_width[idx].normalize( + archive, logger + ) - attenuator = Attenuator( - sample=transmission_dict['attenuation_percentage']['sample'], - reference=transmission_dict['attenuation_percentage']['reference'], - ) + # add settings: NIR gain + for idx, wavelength_value in enumerate(data_dict['detector_NIR_gain']): + transmission.m_setdefault(f'transmission_settings/nir_gain/{idx}') + transmission.transmission_settings.nir_gain[ + idx + ].wavelength_upper_limit = wavelength_value['wavelength'] + transmission.transmission_settings.nir_gain[ + idx + ].nir_gain_factor = wavelength_value['value'] + if idx - 1 >= 0: + transmission.transmission_settings.nir_gain[ + idx + ].wavelength_lower_limit = data_dict['detector_NIR_gain'][idx - 1][ + 'wavelength' + ] + transmission.transmission_settings.nir_gain[idx].normalize(archive, logger) + + # add settings: integration time + for idx, wavelength_value in enumerate(data_dict['detector_integration_time']): + transmission.m_setdefault(f'transmission_settings/integration_time/{idx}') + transmission.transmission_settings.integration_time[ + idx + ].wavelength_upper_limit = wavelength_value['wavelength'] + transmission.transmission_settings.integration_time[ + idx + ].integration_time = wavelength_value['value'] + if idx - 1 >= 0: + transmission.transmission_settings.integration_time[ + idx + ].wavelength_lower_limit = data_dict['detector_integration_time'][ + idx - 1 + ]['wavelength'] + transmission.transmission_settings.integration_time[idx].normalize( + archive, logger + ) + + # add settings: attenuator + transmission.m_setdefault('transmission_settings/attenuator') + if data_dict['attenuation_percentage']['sample'] is not None: + transmission.transmission_settings.attenuator.sample_beam_attenuation = ( + data_dict['attenuation_percentage']['sample'] / 100 + ) + if data_dict['attenuation_percentage']['reference'] is not None: + transmission.transmission_settings.attenuator.reference_beam_attenuation = ( + data_dict['attenuation_percentage']['reference'] / 100 + ) + transmission.transmission_settings.attenuator.normalize(archive, logger) if self.get('transmission_settings'): if self.transmission_settings.get('accessory'): @@ -1202,38 +1400,13 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 if accessory.mode == 'Polarizer': self.transmission_settings.accessory[ idx - ].polarizer_angle = transmission_dict['polarizer_angle'] - - transmission_settings = UVVisNirTransmissionSettings( - ordinate_type=transmission_dict['ordinate_type'], - sample_beam_position=transmission_dict['sample_beam_position'], - common_beam_mask=transmission_dict['common_beam_mask_percentage'], - common_beam_depolarizer=transmission_dict['is_common_beam_depolarizer_on'], - lamp=lamp, - detector=detector, - monochromator=monochromator, - attenuator=attenuator, - ) - transmission_settings.normalize(archive, logger) + ].polarizer_angle = data_dict['polarizer_angle'] - instrument_reference = self.get_instrument_reference( - transmission_dict, archive, logger - ) - if instrument_reference is not None: - instruments = [instrument_reference] - else: - instruments = [] - - transmission = UVVisNirTransmission( - results=[result], - transmission_settings=transmission_settings, - instruments=instruments, - ) - merge_sections(self, transmission, logger) + transmission.transmission_settings.normalize(archive, logger) def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger'): """ - The normalize function of the `UVVisNirTransmission` section. + The normalize function of the `ELNUVVisNirTransmission` section. Args: archive (EntryArchive): The archive containing the section that is being @@ -1248,8 +1421,11 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger'): ) else: with archive.m_context.raw_file(self.data_file) as file: - transmission_dict = read_function(file.name, logger) - write_function(transmission_dict, archive, logger) + data_dict = read_function(file.name, logger) + transmission = self.m_def.section_cls() + write_function(transmission, data_dict, archive, logger) + merge_sections(self, transmission, logger) + super().normalize(archive, logger) if not self.results: @@ -1260,7 +1436,7 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger'): class RawFileTransmissionData(EntryData): """ - Section for a Transmission Spectrophotometry data file. + Entry section for transmission spectrophotometry data file. """ measurement = Quantity( @@ -1271,6 +1447,4 @@ class RawFileTransmissionData(EntryData): ) -ELNUVVisTransmission = ELNUVVisNirTransmission - m_package.__init_metainfo__() diff --git a/src/nomad_measurements/transmission/utils.py b/src/nomad_measurements/transmission/utils.py new file mode 100644 index 00000000..232f0605 --- /dev/null +++ b/src/nomad_measurements/transmission/utils.py @@ -0,0 +1,122 @@ +import os +from typing import ( + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from nomad.datamodel import ( + EntryArchive, + ) + from nomad.datamodel.data import ( + ArchiveSection, + ) + from structlog.stdlib import ( + BoundLogger, + ) + + +def merge_sections( # noqa: PLR0912 + section: 'ArchiveSection', + update: 'ArchiveSection', + overwrite_quantity: bool = False, + logger: 'BoundLogger' = None, +) -> None: + """ + Updates the `section` based on the `update` section. + Unpopulated quantities and subsections in the `section` will be populated with the + values from the `update` section. + In case a repeating subsection in update section has a different number of entries, + a warning will be issued, and `section` will remain unchanged. + In case a quantity is present in both sections, the one in `section` will be + overwritten provided that `overwrite_quantity` is set to `True`. + + Args: + section (ArchiveSection): section to update. + update (ArchiveSection): section to update from. + overwrite_quantity (bool, optional): Whether to overwrite in `section`. + logger (BoundLogger, optional): A structlog logger. + + Raises: + TypeError: If the sections are of different types. + """ + if update is None: + return + if section is None: + section = update.m_copy() + return + if not isinstance(section, type(update)): + raise TypeError( + 'Cannot merge sections of different types: ' + f'{type(section)} and {type(update)}' + ) + for name, quantity in update.m_def.all_quantities.items(): + if not update.m_is_set(quantity): + continue + if not section.m_is_set(quantity): + section.m_set(quantity, update.m_get(quantity)) + elif ( + quantity.is_scalar + and section.m_get(quantity) != update.m_get(quantity) + or not quantity.is_scalar + and (section.m_get(quantity) != update.m_get(quantity)).any() + ): + if overwrite_quantity: + section.m_set(quantity, update.m_get(quantity)) + warning = f'Merging sections with different values for quantity "{name}".' + if logger: + logger.warning(warning) + else: + print(warning) + for name, sub_section_def in update.m_def.all_sub_sections.items(): + count = section.m_sub_section_count(sub_section_def) + if count == 0: + for update_sub_section in update.m_get_sub_sections(sub_section_def): + section.m_add_sub_section(sub_section_def, update_sub_section) + elif count == update.m_sub_section_count(sub_section_def): + for i in range(count): + merge_sections( + section.m_get_sub_section(sub_section_def, i), + update.m_get_sub_section(sub_section_def, i), + logger, + ) + elif update.m_sub_section_count(sub_section_def) > 0: + warning = ( + f'Merging sections with different number of "{name}" sub sections.' + ) + if logger: + logger.warning(warning) + else: + print(warning) + + +def get_reference(upload_id, entry_id): + return f'../uploads/{upload_id}/archive/{entry_id}#/data' + + +def get_entry_id_from_file_name(file_name, archive): + from nomad.utils import hash + + return hash(archive.metadata.upload_id, file_name) + + +def create_archive( + entity: 'ArchiveSection', + archive: 'EntryArchive', + file_name: str, +) -> str: + import json + + from nomad.datamodel.context import ClientContext + + entity_entry = entity.m_to_dict(with_root_def=True) + if isinstance(archive.m_context, ClientContext): + with open(file_name, 'w') as outfile: + json.dump({'data': entity_entry}, outfile, indent=4) + return os.path.abspath(file_name) + if not archive.m_context.raw_path_exists(file_name): + with archive.m_context.raw_file(file_name, 'w') as outfile: + json.dump({'data': entity_entry}, outfile) + archive.m_context.process_updated_raw_file(file_name) + return get_reference( + archive.metadata.upload_id, get_entry_id_from_file_name(file_name, archive) + ) From f84ceec406827598718c1535a47e660033dd5d1c Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Fri, 20 Dec 2024 22:39:38 +0100 Subject: [PATCH 10/24] Integrate suggestions from AreaA TF: sample path lenght and orientation --- src/nomad_measurements/transmission/schema.py | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index f1f8eada..ec2cfcf9 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -347,7 +347,7 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: class TransmissionSampleReference(CompositeSystemReference): """ Reference to the sample used in the transmission measurement. Additionally, - contains the thickness and orientation of the sample. + it contains specific sample properties important for transmission measurements. """ m_def = Section( @@ -357,8 +357,8 @@ class TransmissionSampleReference(CompositeSystemReference): 'name', 'lab_id', 'reference', - 'thickness', - 'orientation', + 'geometric_path_length', + 'optical_path_length', ] ) ) @@ -373,18 +373,54 @@ class TransmissionSampleReference(CompositeSystemReference): label='sample reference', ), ) - thickness = Quantity( + geometric_path_length = Quantity( type=np.float64, description=""" - Thickness of the sample along the direction of the light beam. - Also referred to as path length of the beam.""", + Length of the sample along the direction of the light beam, i.e., the Euclidean + distance between the entry and exit points of the light beam on the sample. + This is different from the optical path length, which is the product of the + refractive index of the sample and the geometric path length. + """, + a_eln={ + 'component': 'NumberEditQuantity', + 'defaultDisplayUnit': 'millimeter', + }, + unit='meter', + ) + optical_path_length = Quantity( + type=np.float64, + description=""" + Product of the refractive index of the sample and the geometric path length. + """, a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 'millimeter', }, unit='meter', ) - orientation = Quantity( + + +class CrystallographicTransmissionSampleReference(TransmissionSampleReference): + """ + Reference to the sample used in the transmission measurement. Additionally, it + includes specific properties for crystallographic samples. + """ + + m_def = Section( + a_eln=ELNAnnotation( + properties=SectionProperties( + order=[ + 'name', + 'lab_id', + 'reference', + 'geometric_path_length', + 'optical_path_length', + 'crystal_orientation', + ] + ) + ) + ) + crystal_orientation = Quantity( type=str, description=""" Crystallographic orientation of the sample surface on which the light beam is @@ -393,18 +429,6 @@ class TransmissionSampleReference(CompositeSystemReference): a_eln={'component': 'StringEditQuantity'}, ) - def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: - """ - The normalizer for the `TransmissionSampleReference` class. - - Args: - archive (EntryArchive): The NOMAD archive. - logger (BoundLogger): A structlog logger. - """ - super().normalize(archive, logger) - # TODO: if the thickness is not mentioned, it should be copied from the - # geometry of the referenced sample. - class Accessory(ArchiveSection): """ From 591c44d6431e2ac21ef78e646714e541c04cf819 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 14:22:35 +0100 Subject: [PATCH 11/24] Rename to detector_gain, detector_integration_time --- src/nomad_measurements/transmission/schema.py | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index ec2cfcf9..ff7fe272 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -526,7 +526,7 @@ class Aperture(Accessory): class SettingOverWavelengthRange(ArchiveSection): """ - An instrument setting set over a range of wavelength. + An instrument setting set over a wavelength range. """ m_def = Section( @@ -654,9 +654,9 @@ class MonochromatorSettings(SettingOverWavelengthRange): ) -class NIRGain(SettingOverWavelengthRange): +class DetectorGain(SettingOverWavelengthRange): """ - NIR gain factor of detectors used in transmission spectrophotometry over a range of + Gain factor of the detector used in transmission spectrophotometry over a range of wavelength. """ @@ -667,22 +667,22 @@ class NIRGain(SettingOverWavelengthRange): 'name', 'wavelength_lower_limit', 'wavelength_upper_limit', - 'nir_gain_factor', + 'gain', ], ), ), ) - nir_gain_factor = Quantity( + gain = Quantity( type=np.float64, - description='NIR gain factor of the detector.', + description='Gain factor of the detector used over a wavelength range.', a_eln={'component': 'NumberEditQuantity'}, unit='dimensionless', ) -class IntegrationTime(SettingOverWavelengthRange): +class DetectorIntegrationTime(SettingOverWavelengthRange): """ - Integration time of the detectors used in transmission spectrophotometry over a + Integration time of the detector used in transmission spectrophotometry over a wavelength range. """ @@ -700,7 +700,9 @@ class IntegrationTime(SettingOverWavelengthRange): ) integration_time = Quantity( type=np.float64, - description='Integration time value.', + description=""" + Integration time of the detector used in the given wavelength range. + """, a_eln={ 'component': 'NumberEditQuantity', 'defaultDisplayUnit': 's', @@ -976,12 +978,12 @@ class UVVisNirTransmissionSettings(ArchiveSection): section_def=MonochromatorSlitWidth, repeats=True, ) - nir_gain = SubSection( - section_def=NIRGain, + detector_gain = SubSection( + section_def=DetectorGain, repeats=True, ) - integration_time = SubSection( - section_def=IntegrationTime, + detector_integration_time = SubSection( + section_def=DetectorIntegrationTime, repeats=True, ) @@ -1371,37 +1373,41 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 # add settings: NIR gain for idx, wavelength_value in enumerate(data_dict['detector_NIR_gain']): - transmission.m_setdefault(f'transmission_settings/nir_gain/{idx}') - transmission.transmission_settings.nir_gain[ + transmission.m_setdefault(f'transmission_settings/detector_gain/{idx}') + transmission.transmission_settings.detector_gain[ idx ].wavelength_upper_limit = wavelength_value['wavelength'] - transmission.transmission_settings.nir_gain[ + transmission.transmission_settings.detector_gain[ idx - ].nir_gain_factor = wavelength_value['value'] + ].gain = wavelength_value['value'] if idx - 1 >= 0: - transmission.transmission_settings.nir_gain[ + transmission.transmission_settings.detector_gain[ idx ].wavelength_lower_limit = data_dict['detector_NIR_gain'][idx - 1][ 'wavelength' ] - transmission.transmission_settings.nir_gain[idx].normalize(archive, logger) + transmission.transmission_settings.detector_gain[idx].normalize( + archive, logger + ) # add settings: integration time for idx, wavelength_value in enumerate(data_dict['detector_integration_time']): - transmission.m_setdefault(f'transmission_settings/integration_time/{idx}') - transmission.transmission_settings.integration_time[ + transmission.m_setdefault( + f'transmission_settings/detector_integration_time/{idx}' + ) + transmission.transmission_settings.detector_integration_time[ idx ].wavelength_upper_limit = wavelength_value['wavelength'] - transmission.transmission_settings.integration_time[ + transmission.transmission_settings.detector_integration_time[ idx - ].integration_time = wavelength_value['value'] + ].detector_integration_time = wavelength_value['value'] if idx - 1 >= 0: - transmission.transmission_settings.integration_time[ + transmission.transmission_settings.detector_integration_time[ idx ].wavelength_lower_limit = data_dict['detector_integration_time'][ idx - 1 ]['wavelength'] - transmission.transmission_settings.integration_time[idx].normalize( + transmission.transmission_settings.detector_integration_time[idx].normalize( archive, logger ) From c478def7cedc6ba68bb5b380d916524323e4612b Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 14:29:24 +0100 Subject: [PATCH 12/24] Reordering settings sections --- src/nomad_measurements/transmission/schema.py | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index ff7fe272..185d2ea0 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -894,35 +894,7 @@ class UVVisNirTransmissionSettings(ArchiveSection): visible, and near IR ranges of wavelength. """ - m_def = Section( - a_eln=ELNAnnotation( - properties=SectionProperties( - order=[ - 'sample_beam_position', - 'common_beam_mask', - 'common_beam_depolarizer', - ], - ), - ), - ) - detector_module = Quantity( - type=MEnum( - [ - 'Three Detector Module', - 'Two Detector Module', - '150-mm Integrating Sphere', - ] - ), - a_eln={'component': 'EnumEditQuantity'}, - description=""" - Modules containing multiple detectors for different wavelength ranges. - | Detector Module | Description | - |--------------------------------------|----------------------| - | **Three Detector Module** | Installed as standard module on Perkin-Elmer Lambda 1050 WB and NB spectrophotometers. Contains three detectors for different wavelength ranges: PMT, InGaAs, PbS. | - | **Two Detector Module** | Installed on Perkin-Elmer Lambda 750, 900, 950 spectrophotometers. Contains two detectors for different wavelength ranges: PMT, PbS. | - | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | - """, # noqa: E501 - ) + m_def = Section() sample_beam_position = Quantity( type=MEnum(['Front', 'Rear']), description=( @@ -955,29 +927,43 @@ class UVVisNirTransmissionSettings(ArchiveSection): default=False, a_eln={'component': 'BoolEditQuantity'}, ) - accessory = SubSection( - section_def=Accessory, - repeats=True, - ) - attenuator = SubSection( - section_def=Attenuator, + detector_module = Quantity( + type=MEnum( + [ + 'Three Detector Module', + 'Two Detector Module', + '150-mm Integrating Sphere', + ] + ), + a_eln={'component': 'EnumEditQuantity'}, + description=""" + Modules containing multiple detectors for different wavelength ranges. + | Detector Module | Description | + |--------------------------------------|----------------------| + | **Three Detector Module** | Installed as standard module on Perkin-Elmer Lambda 1050 WB and NB spectrophotometers. Contains three detectors for different wavelength ranges: PMT, InGaAs, PbS. | + | **Two Detector Module** | Installed on Perkin-Elmer Lambda 750, 900, 950 spectrophotometers. Contains two detectors for different wavelength ranges: PMT, PbS. | + | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | + """, # noqa: E501 ) light_source = SubSection( section_def=LampSettings, repeats=True, ) + attenuator = SubSection( + section_def=Attenuator, + ) monochromator = SubSection( section_def=MonochromatorSettings, repeats=True, ) - detector = SubSection( - section_def=DetectorSettings, - repeats=True, - ) monochromator_slit_width = SubSection( section_def=MonochromatorSlitWidth, repeats=True, ) + detector = SubSection( + section_def=DetectorSettings, + repeats=True, + ) detector_gain = SubSection( section_def=DetectorGain, repeats=True, @@ -986,6 +972,10 @@ class UVVisNirTransmissionSettings(ArchiveSection): section_def=DetectorIntegrationTime, repeats=True, ) + accessory = SubSection( + section_def=Accessory, + repeats=True, + ) class UVVisNirTransmission(Measurement): From 9213487b1e8a581fd507cf888007170620606a67 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 14:29:33 +0100 Subject: [PATCH 13/24] Fix --- src/nomad_measurements/transmission/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 185d2ea0..25b9b7d2 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -1390,7 +1390,7 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 ].wavelength_upper_limit = wavelength_value['wavelength'] transmission.transmission_settings.detector_integration_time[ idx - ].detector_integration_time = wavelength_value['value'] + ].integration_time = wavelength_value['value'] if idx - 1 >= 0: transmission.transmission_settings.detector_integration_time[ idx From 5063b19cab9e8d84c0d1eaf997154fa8d8139928 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 14:35:59 +0100 Subject: [PATCH 14/24] Rename attenuator settings class --- src/nomad_measurements/transmission/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 25b9b7d2..7362adbe 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -761,9 +761,9 @@ class LampSettings(SettingOverWavelengthRange): ) -class Attenuator(ArchiveSection): +class AttenuatorSettings(ArchiveSection): """ - Attenuation setting for the sample and reference beam. + Settings of the attenuator used for the sample and reference light beam. """ m_def = Section() @@ -950,7 +950,7 @@ class UVVisNirTransmissionSettings(ArchiveSection): repeats=True, ) attenuator = SubSection( - section_def=Attenuator, + section_def=AttenuatorSettings, ) monochromator = SubSection( section_def=MonochromatorSettings, From b2fc81be3a30823cfb4b67610a3df3823afd441b Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 14:56:30 +0100 Subject: [PATCH 15/24] Plural names --- src/nomad_measurements/transmission/schema.py | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 7362adbe..b0d7ced3 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -945,14 +945,14 @@ class UVVisNirTransmissionSettings(ArchiveSection): | **150-mm Integrating Sphere** | Includes an integrating sphere with a diameter of 150 mm which is equipped with PMT (R928) and InGaAs detector. The PMT covers 200-860.8 nm and the InGaAs detector covers 860.8-2500 nm. | """, # noqa: E501 ) - light_source = SubSection( + light_sources = SubSection( section_def=LampSettings, repeats=True, ) attenuator = SubSection( section_def=AttenuatorSettings, ) - monochromator = SubSection( + monochromators = SubSection( section_def=MonochromatorSettings, repeats=True, ) @@ -960,7 +960,7 @@ class UVVisNirTransmissionSettings(ArchiveSection): section_def=MonochromatorSlitWidth, repeats=True, ) - detector = SubSection( + detectors = SubSection( section_def=DetectorSettings, repeats=True, ) @@ -972,7 +972,7 @@ class UVVisNirTransmissionSettings(ArchiveSection): section_def=DetectorIntegrationTime, repeats=True, ) - accessory = SubSection( + accessories = SubSection( section_def=Accessory, repeats=True, ) @@ -1214,8 +1214,10 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 i = 0 for light_source in instrument_reference.reference.light_sources: if light_source.type in lamps: - transmission.m_setdefault(f'transmission_settings/light_source/{i}') - transmission.transmission_settings.light_source[ + transmission.m_setdefault( + f'transmission_settings/light_sources/{i}' + ) + transmission.transmission_settings.light_sources[ i ].lamp = light_source i += 1 @@ -1227,16 +1229,16 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 if ( lamp_change_points is not None and len(lamp_change_points) - == len(transmission.transmission_settings.light_source) - 1 + == len(transmission.transmission_settings.light_sources) - 1 ): for idx, lamp_change_point in enumerate(lamp_change_points): - transmission.transmission_settings.light_source[ + transmission.transmission_settings.light_sources[ idx ].wavelength_upper_limit = lamp_change_point - transmission.transmission_settings.light_source[ + transmission.transmission_settings.light_sources[ idx + 1 ].wavelength_lower_limit = lamp_change_point - for light_source_setting in transmission.transmission_settings.light_source: + for light_source_setting in transmission.transmission_settings.light_sources: light_source_setting.normalize(archive, logger) # add settings: detector @@ -1268,8 +1270,8 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 i = 0 for detector in instrument_reference.reference.detectors: if detector.type in detector_list: - transmission.m_setdefault(f'transmission_settings/detector/{i}') - transmission.transmission_settings.detector[i].detector = detector + transmission.m_setdefault(f'transmission_settings/detectors/{i}') + transmission.transmission_settings.detectors[i].detector = detector i += 1 except Exception as e: logger.warning( @@ -1279,24 +1281,24 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 if ( detector_change_points is not None and len(detector_change_points) - == len(transmission.transmission_settings.detector) - 1 + == len(transmission.transmission_settings.detectors) - 1 ): for idx, change_point in enumerate(detector_change_points): - transmission.transmission_settings.detector[ + transmission.transmission_settings.detectors[ idx ].wavelength_upper_limit = change_point - transmission.transmission_settings.detector[ + transmission.transmission_settings.detectors[ idx + 1 ].wavelength_lower_limit = change_point - for detector_setting in transmission.transmission_settings.detector: + for detector_setting in transmission.transmission_settings.detectors: detector_setting.normalize(archive, logger) - # add settings: monochromator + # add settings: monochromators try: i = 0 for monochromator in instrument_reference.reference.monochromators: - transmission.m_setdefault(f'transmission_settings/monochromator/{i}') - transmission.transmission_settings.monochromator[ + transmission.m_setdefault(f'transmission_settings/monochromators/{i}') + transmission.transmission_settings.monochromators[ i ].monochromator = monochromator i += 1 @@ -1308,16 +1310,16 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 if ( monochromator_change_points is not None and len(monochromator_change_points) - == len(transmission.transmission_settings.monochromator) - 1 + == len(transmission.transmission_settings.monochromators) - 1 ): for idx, change_point in enumerate(monochromator_change_points): - transmission.transmission_settings.monochromator[ + transmission.transmission_settings.monochromators[ idx ].wavelength_upper_limit = change_point - transmission.transmission_settings.monochromator[ + transmission.transmission_settings.monochromators[ idx + 1 ].wavelength_lower_limit = change_point - for monochromator_setting in transmission.transmission_settings.monochromator: + for monochromator_setting in transmission.transmission_settings.monochromators: monochromator_setting.normalize(archive, logger) # add settings: monochromator slit width From 28346064fa0c33e074e0b027998d07ec79b970f0 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 15:30:03 +0100 Subject: [PATCH 16/24] Use common utils --- src/nomad_measurements/transmission/schema.py | 2 +- src/nomad_measurements/transmission/utils.py | 122 ------------------ src/nomad_measurements/utils.py | 13 ++ 3 files changed, 14 insertions(+), 123 deletions(-) delete mode 100644 src/nomad_measurements/transmission/utils.py diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index b0d7ced3..19355adf 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -76,7 +76,7 @@ ) from nomad_material_processing.general import Geometry -from nomad_measurements.transmission.utils import create_archive, merge_sections +from nomad_measurements.utils import create_archive, merge_sections if TYPE_CHECKING: from nomad.datamodel.datamodel import EntryArchive diff --git a/src/nomad_measurements/transmission/utils.py b/src/nomad_measurements/transmission/utils.py deleted file mode 100644 index 232f0605..00000000 --- a/src/nomad_measurements/transmission/utils.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -from typing import ( - TYPE_CHECKING, -) - -if TYPE_CHECKING: - from nomad.datamodel import ( - EntryArchive, - ) - from nomad.datamodel.data import ( - ArchiveSection, - ) - from structlog.stdlib import ( - BoundLogger, - ) - - -def merge_sections( # noqa: PLR0912 - section: 'ArchiveSection', - update: 'ArchiveSection', - overwrite_quantity: bool = False, - logger: 'BoundLogger' = None, -) -> None: - """ - Updates the `section` based on the `update` section. - Unpopulated quantities and subsections in the `section` will be populated with the - values from the `update` section. - In case a repeating subsection in update section has a different number of entries, - a warning will be issued, and `section` will remain unchanged. - In case a quantity is present in both sections, the one in `section` will be - overwritten provided that `overwrite_quantity` is set to `True`. - - Args: - section (ArchiveSection): section to update. - update (ArchiveSection): section to update from. - overwrite_quantity (bool, optional): Whether to overwrite in `section`. - logger (BoundLogger, optional): A structlog logger. - - Raises: - TypeError: If the sections are of different types. - """ - if update is None: - return - if section is None: - section = update.m_copy() - return - if not isinstance(section, type(update)): - raise TypeError( - 'Cannot merge sections of different types: ' - f'{type(section)} and {type(update)}' - ) - for name, quantity in update.m_def.all_quantities.items(): - if not update.m_is_set(quantity): - continue - if not section.m_is_set(quantity): - section.m_set(quantity, update.m_get(quantity)) - elif ( - quantity.is_scalar - and section.m_get(quantity) != update.m_get(quantity) - or not quantity.is_scalar - and (section.m_get(quantity) != update.m_get(quantity)).any() - ): - if overwrite_quantity: - section.m_set(quantity, update.m_get(quantity)) - warning = f'Merging sections with different values for quantity "{name}".' - if logger: - logger.warning(warning) - else: - print(warning) - for name, sub_section_def in update.m_def.all_sub_sections.items(): - count = section.m_sub_section_count(sub_section_def) - if count == 0: - for update_sub_section in update.m_get_sub_sections(sub_section_def): - section.m_add_sub_section(sub_section_def, update_sub_section) - elif count == update.m_sub_section_count(sub_section_def): - for i in range(count): - merge_sections( - section.m_get_sub_section(sub_section_def, i), - update.m_get_sub_section(sub_section_def, i), - logger, - ) - elif update.m_sub_section_count(sub_section_def) > 0: - warning = ( - f'Merging sections with different number of "{name}" sub sections.' - ) - if logger: - logger.warning(warning) - else: - print(warning) - - -def get_reference(upload_id, entry_id): - return f'../uploads/{upload_id}/archive/{entry_id}#/data' - - -def get_entry_id_from_file_name(file_name, archive): - from nomad.utils import hash - - return hash(archive.metadata.upload_id, file_name) - - -def create_archive( - entity: 'ArchiveSection', - archive: 'EntryArchive', - file_name: str, -) -> str: - import json - - from nomad.datamodel.context import ClientContext - - entity_entry = entity.m_to_dict(with_root_def=True) - if isinstance(archive.m_context, ClientContext): - with open(file_name, 'w') as outfile: - json.dump({'data': entity_entry}, outfile, indent=4) - return os.path.abspath(file_name) - if not archive.m_context.raw_path_exists(file_name): - with archive.m_context.raw_file(file_name, 'w') as outfile: - json.dump({'data': entity_entry}, outfile) - archive.m_context.process_updated_raw_file(file_name) - return get_reference( - archive.metadata.upload_id, get_entry_id_from_file_name(file_name, archive) - ) diff --git a/src/nomad_measurements/utils.py b/src/nomad_measurements/utils.py index 58389fca..250e030f 100644 --- a/src/nomad_measurements/utils.py +++ b/src/nomad_measurements/utils.py @@ -79,6 +79,19 @@ def merge_sections( # noqa: PLR0912 update: 'ArchiveSection', logger: 'BoundLogger' = None, ) -> None: + """ + Unpopulated quantities and subsections in the `section` will be populated with the + values from the `update` section. + If a quantity is present in both sections but with different values, no change is + made. + If a repeating subsection is present in both sections, and they are of the same + length, the subsections will be merged recursively. Else, no change is made. + + Args: + section (ArchiveSection): section to update. + update (ArchiveSection): section to update from. + logger (BoundLogger, optional): A structlog logger. + """ if update is None: return if section is None: From f552c3141b7cf4898154d9d96c7b04badab48265 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Mon, 6 Jan 2025 15:39:14 +0100 Subject: [PATCH 17/24] Use composite system for sample reference --- src/nomad_measurements/transmission/schema.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 19355adf..92734ed0 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -60,7 +60,6 @@ Measurement, MeasurementResult, ReadableIdentifiers, - SystemComponent, ) from nomad.datamodel.metainfo.plot import ( PlotlyFigure, @@ -74,7 +73,6 @@ Section, SubSection, ) -from nomad_material_processing.general import Geometry from nomad_measurements.utils import create_archive, merge_sections @@ -86,21 +84,6 @@ m_package = SchemaPackage() -class Sample(CompositeSystem, EntryData): - """ - Contains information about the sample id, geometry, and reference to the material - system under `components` sub-section. - """ - - components = SubSection( - section_def=SystemComponent, - repeats=True, - ) - geometry = SubSection( - section_def=Geometry, - ) - - class Detector(Entity): """ Light detector used in the transmission spectroscopy instruments. @@ -364,7 +347,7 @@ class TransmissionSampleReference(CompositeSystemReference): ) ) reference = Quantity( - type=Sample, + type=CompositeSystem, description=""" A reference to the sample used. """, From 0b095b5dfba1375dd02d5d823ff335baf59076be Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Tue, 7 Jan 2025 13:24:43 +0100 Subject: [PATCH 18/24] Move transmission test file --- tests/{transmission/test_parser.py => test_transmission.py} | 0 tests/transmission/__init__.py | 0 tests/xrd/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/{transmission/test_parser.py => test_transmission.py} (100%) delete mode 100644 tests/transmission/__init__.py delete mode 100644 tests/xrd/__init__.py diff --git a/tests/transmission/test_parser.py b/tests/test_transmission.py similarity index 100% rename from tests/transmission/test_parser.py rename to tests/test_transmission.py diff --git a/tests/transmission/__init__.py b/tests/transmission/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/xrd/__init__.py b/tests/xrd/__init__.py deleted file mode 100644 index e69de29b..00000000 From 2b317b3b7eb58e327c86b33b7918e9f4689be776 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Tue, 7 Jan 2025 13:29:05 +0100 Subject: [PATCH 19/24] Refactor test file --- tests/test_transmission.py | 54 +++++++++++++++----------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/tests/test_transmission.py b/tests/test_transmission.py index 96b56665..f581a061 100644 --- a/tests/test_transmission.py +++ b/tests/test_transmission.py @@ -15,43 +15,31 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import os - import pytest -from nomad.client import normalize_all, parse +from nomad.client import normalize_all + +test_files = [ + 'tests/data/transmission/3DM_test01.Probe.Raw.asc', + 'tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc', + 'tests/data/transmission/KTF-D.Probe.Raw.asc', + 'tests/data/transmission/Sample5926.Probe.Raw.asc', + 'tests/data/transmission/sphere_test01.Probe.Raw.asc', +] +log_levels = ['error', 'critical'] -@pytest.fixture( - params=[ - '3DM_test01.Probe.Raw.asc', - 'F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc', - 'KTF-D.Probe.Raw.asc', - 'Sample5926.Probe.Raw.asc', - 'sphere_test01.Probe.Raw.asc', - ] +@pytest.mark.parametrize( + 'parsed_measurement_archive, caplog', + [(file, log_level) for file in test_files for log_level in log_levels], + indirect=True, ) -def parsed_archive(request): +def test_normalize_all(parsed_measurement_archive, caplog): """ - Sets up data for testing and cleans up after the test. - """ - rel_file = os.path.join( - os.path.dirname(__file__), '../data/transmission', request.param - ) - file_archive = parse(rel_file)[0] - measurement = os.path.join( - os.path.dirname(__file__), - '../data/transmission', - '.'.join(request.param.split('.')[:-1]) + '.archive.json', - ) - assert file_archive.data.measurement.m_proxy_value == os.path.abspath(measurement) - measurement_archive = parse(measurement)[0] - - yield measurement_archive + Tests the normalization of the parsed archive. - if os.path.exists(measurement): - os.remove(measurement) - - -def test_normalize_all(parsed_archive): - normalize_all(parsed_archive) + Args: + parsed_archive (pytest.fixture): Fixture to handle the parsing of archive. + caplog (pytest.fixture): Fixture to capture errors from the logger. + """ + normalize_all(parsed_measurement_archive) # TODO test the normalized data From 52a4670819d2ffb98b5d235020ab6fa724a1dcc8 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Tue, 7 Jan 2025 13:37:05 +0100 Subject: [PATCH 20/24] Add schema config --- src/nomad_measurements/transmission/schema.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index 92734ed0..e402728f 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -80,6 +80,9 @@ from nomad.datamodel.datamodel import EntryArchive from structlog.stdlib import BoundLogger +from nomad.config import config + +configuration = config.get_plugin_entry_point('nomad_measurements.transmission:schema') m_package = SchemaPackage() From b16eb999126c1e39b1de75014dfa6fd01bb7ca38 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Tue, 7 Jan 2025 14:07:00 +0100 Subject: [PATCH 21/24] Test normalized data for one file --- tests/test_transmission.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_transmission.py b/tests/test_transmission.py index f581a061..3b9cd12e 100644 --- a/tests/test_transmission.py +++ b/tests/test_transmission.py @@ -42,4 +42,15 @@ def test_normalize_all(parsed_measurement_archive, caplog): caplog (pytest.fixture): Fixture to capture errors from the logger. """ normalize_all(parsed_measurement_archive) - # TODO test the normalized data + + # testing normalized data for a single file + if parsed_measurement_archive.data.data_file == 'KTF-D.Probe.Raw.asc': + assert ( + parsed_measurement_archive.data.transmission_settings.sample_beam_position + == 'Front' + ) + assert parsed_measurement_archive.data.results[0].wavelength.shape == (1001,) + assert ( + 'UV-Vis-NIR Transmission Spectrophotometry' + in parsed_measurement_archive.results.eln.methods + ) From 17511e6d02d3ce09bf2a8a410db6aff88bc3bcf1 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 8 Jan 2025 14:17:56 +0100 Subject: [PATCH 22/24] Regex for matching raw file content --- src/nomad_measurements/transmission/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nomad_measurements/transmission/__init__.py b/src/nomad_measurements/transmission/__init__.py index fb06fe36..81915533 100644 --- a/src/nomad_measurements/transmission/__init__.py +++ b/src/nomad_measurements/transmission/__init__.py @@ -50,6 +50,7 @@ def load(self): parser = TransmissionParserEntryPoint( name='Transmission Parser', description='Parser for data from Transmission Spectrophotometry.', - mainfile_mime_re='text/.*|application/zip', - mainfile_name_re='^.*\.asc$', + mainfile_mime_re=r'text/.*|application/zip', + mainfile_name_re=r'^.*\.asc$', + mainfile_contents_re=r'^PE UV', ) From 400faa11c9e534e740a8b14e47d742b430ccaaaf Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 8 Jan 2025 15:20:52 +0100 Subject: [PATCH 23/24] Review: from sourcery --- src/nomad_measurements/transmission/schema.py | 65 ++++++++++--------- tests/test_transmission.py | 21 +++++- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/nomad_measurements/transmission/schema.py b/src/nomad_measurements/transmission/schema.py index e402728f..ac7f6cd6 100644 --- a/src/nomad_measurements/transmission/schema.py +++ b/src/nomad_measurements/transmission/schema.py @@ -474,13 +474,14 @@ class PolDepol(Accessory): ) def normalize(self, archive, logger): - if self.mode is None or self.mode == 'Depolarizer': - if self.polarizer_angle is not None: - logger.warning( - 'Ambiguous polarizer angle: ' - 'PolDepol accessory is not set to "Polarizer" mode, ' - 'but `polarizer_angle` is set.' - ) + if ( + self.mode is None or self.mode == 'Depolarizer' + ) and self.polarizer_angle is not None: + logger.warning( + 'Ambiguous polarizer angle: ' + 'PolDepol accessory is not set to "Polarizer" mode, ' + 'but `polarizer_angle` is set.' + ) super().normalize(archive, logger) @@ -566,16 +567,19 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: upper_limit = self.wavelength_upper_limit.magnitude if self.wavelength_lower_limit is not None: lower_limit = self.wavelength_lower_limit.magnitude - if isinstance(upper_limit, float) and isinstance(lower_limit, float): - if upper_limit < lower_limit: - logger.warning( - f'Upper limit of wavelength "{upper_limit}" should be greater than' - f'lower limit of wavelength "{lower_limit}".' - ) - upper_limit = '-' - lower_limit = '-' - self.wavelength_upper_limit = None - self.wavelength_lower_limit = None + if ( + isinstance(upper_limit, float) + and isinstance(lower_limit, float) + and upper_limit < lower_limit + ): + logger.warning( + f'Upper limit of wavelength "{upper_limit}" should be greater than' + f'lower limit of wavelength "{lower_limit}".' + ) + upper_limit = '-' + lower_limit = '-' + self.wavelength_upper_limit = None + self.wavelength_lower_limit = None self.name = f'[{lower_limit}, {upper_limit}]' @@ -842,7 +846,7 @@ def generate_plots(self) -> list[PlotlyFigure]: continue x_label = 'Wavelength' - xaxis_title = x_label + ' (nm)' + xaxis_title = f'{x_label} (nm)' x = self.wavelength.to('nm').magnitude y_label = key.capitalize() @@ -1113,10 +1117,11 @@ def get_instrument_reference( user_id=archive.metadata.main_author.user_id, ) - valid_instruments = [] - for entry in search_result.data: - if entry['data']['serial_number'] == serial_number: - valid_instruments.append(entry) + valid_instruments = [ + entry + for entry in search_result.data + if entry['data']['serial_number'] == serial_number + ] if not valid_instruments: logger.warning( @@ -1401,14 +1406,14 @@ def write_transmission_data( # noqa: PLR0912, PLR0915 ) transmission.transmission_settings.attenuator.normalize(archive, logger) - if self.get('transmission_settings'): - if self.transmission_settings.get('accessory'): - for idx, accessory in enumerate(self.transmission_settings.accessory): - if isinstance(accessory, PolDepol): - if accessory.mode == 'Polarizer': - self.transmission_settings.accessory[ - idx - ].polarizer_angle = data_dict['polarizer_angle'] + if self.get('transmission_settings') and self.transmission_settings.get( + 'accessory' + ): + for idx, accessory in enumerate(self.transmission_settings.accessory): + if isinstance(accessory, PolDepol) and accessory.mode == 'Polarizer': + self.transmission_settings.accessory[ + idx + ].polarizer_angle = data_dict['polarizer_angle'] transmission.transmission_settings.normalize(archive, logger) diff --git a/tests/test_transmission.py b/tests/test_transmission.py index 3b9cd12e..a22e65d6 100644 --- a/tests/test_transmission.py +++ b/tests/test_transmission.py @@ -19,9 +19,9 @@ from nomad.client import normalize_all test_files = [ + 'tests/data/transmission/KTF-D.Probe.Raw.asc', 'tests/data/transmission/3DM_test01.Probe.Raw.asc', 'tests/data/transmission/F4-P3HT 1-10 0,5 mgml.Probe.Raw.asc', - 'tests/data/transmission/KTF-D.Probe.Raw.asc', 'tests/data/transmission/Sample5926.Probe.Raw.asc', 'tests/data/transmission/sphere_test01.Probe.Raw.asc', ] @@ -38,7 +38,23 @@ def test_normalize_all(parsed_measurement_archive, caplog): Tests the normalization of the parsed archive. Args: - parsed_archive (pytest.fixture): Fixture to handle the parsing of archive. + parsed_measurement_archive (pytest.fixture): Fixture to setup the archive. + caplog (pytest.fixture): Fixture to capture errors from the logger. + """ + normalize_all(parsed_measurement_archive) + + +@pytest.mark.parametrize( + 'parsed_measurement_archive, caplog', + [(test_files[0], log_level) for log_level in log_levels], + indirect=True, +) +def test_normalized_data(parsed_measurement_archive, caplog): + """ + Tests the normalized data for a single file. + + Args: + parsed_measurement_archive (pytest.fixture): Fixture to setup the archive. caplog (pytest.fixture): Fixture to capture errors from the logger. """ normalize_all(parsed_measurement_archive) @@ -50,6 +66,7 @@ def test_normalize_all(parsed_measurement_archive, caplog): == 'Front' ) assert parsed_measurement_archive.data.results[0].wavelength.shape == (1001,) + assert parsed_measurement_archive.data.results[0].transmittance.shape == (1001,) assert ( 'UV-Vis-NIR Transmission Spectrophotometry' in parsed_measurement_archive.results.eln.methods From 2c07af5b0d532918e363627fe38a3fd55ee76d26 Mon Sep 17 00:00:00 2001 From: Sarthak Kapoor Date: Wed, 8 Jan 2025 16:50:31 +0100 Subject: [PATCH 24/24] Review: from Hampus --- src/nomad_measurements/transmission/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nomad_measurements/transmission/__init__.py b/src/nomad_measurements/transmission/__init__.py index 81915533..07007ec9 100644 --- a/src/nomad_measurements/transmission/__init__.py +++ b/src/nomad_measurements/transmission/__init__.py @@ -49,7 +49,10 @@ def load(self): parser = TransmissionParserEntryPoint( name='Transmission Parser', - description='Parser for data from Transmission Spectrophotometry.', + description=""" + Parser for data from Transmission Spectrophotometry measurements. Currently + supports `.asc` files generated by Perkin Elmer UV WinLab software. + """, mainfile_mime_re=r'text/.*|application/zip', mainfile_name_re=r'^.*\.asc$', mainfile_contents_re=r'^PE UV',