Source code for ts_ids_components.particle_sizer.result

"""
Result components for particle sizer IDSs.

:py:class:`Result` bundles the technique-specific result blocks
(:py:class:`DLSResult`, :py:class:`ELSResult`, :py:class:`SLSResult`,
:py:class:`LaserDiffractionResult`) alongside the size distributions
(:py:class:`SizeDistribution`) that DLS and Laser Diffraction share. A
consuming IDS includes only the blocks relevant to the techniques it
supports.
"""

from enum import Enum
from typing import List

from ts_ids_core.annotations import (
    NullableNumber,
    NullableString,
    UUIDForeignKey,
    UUIDPrimaryKey,
)
from ts_ids_core.base.ids_element import IdsElement
from ts_ids_core.base.ids_field import IdsField
from ts_ids_core.schema import RawValueUnit


[docs] class SizeDistributionWeighting(str, Enum): """ Statistical weighting of a size distribution. The same physical distribution can be expressed weighted by scattered intensity, by particle volume/mass, or by particle number; the three differ substantially for polydisperse samples, so the weighting must be preserved to interpret the distribution correctly. """ INTENSITY = "intensity" VOLUME = "volume" MASS = "mass" NUMBER = "number"
[docs] class PolydispersityIndexDefinition(str, Enum): """ Algorithm used to compute the polydispersity index (PDI). The two definitions are **not** interchangeable, so the definition must be recorded alongside the value. * :py:attr:`ISO_22412` -- µ₂/Γ̄² from the cumulants fit (Zetasizer). * :py:attr:`PERCENT_PD_OVER_100` -- (%Pd/100)² (DynaPro). """ ISO_22412 = "iso_22412" PERCENT_PD_OVER_100 = "percent_pd_over_100"
[docs] class ResultAnalysisPass(str, Enum): """ Zetasizer-specific analysis pass. The Zetasizer reports two ``Result`` entries per measurement, one for each pass. DynaPro and Mastersizer set this to null. """ FILTERED = "filtered" UNFILTERED = "unfiltered"
# --------------------------------------------------------------------------- # Size distributions (shared across DLS and Laser Diffraction) # ---------------------------------------------------------------------------
[docs] class SizeDistributionPeak(IdsElement): """A single peak within a size distribution.""" mean: RawValueUnit = IdsField(description="Mean size of the peak.") mode: RawValueUnit = IdsField(description="Modal (most frequent) size of the peak.") standard_deviation: RawValueUnit = IdsField( description="Standard deviation (width) of the peak." ) area: RawValueUnit = IdsField(description="Area under the peak.") radius: RawValueUnit = IdsField(description="Radius corresponding to the peak.") polydispersity: RawValueUnit = IdsField(description="Polydispersity of the peak.") molecular_weight: RawValueUnit = IdsField( description="Molecular weight estimated for the peak." ) diffusion_coefficient: RawValueUnit = IdsField( description="Diffusion coefficient corresponding to the peak." ) percent_intensity: RawValueUnit = IdsField( description="Contribution of the peak to the intensity-weighted distribution, as a percentage." ) percent_mass: RawValueUnit = IdsField( description="Contribution of the peak to the mass/volume-weighted distribution, as a percentage." ) percent_number: RawValueUnit = IdsField( description="Contribution of the peak to the number-weighted distribution, as a percentage." )
[docs] class SizeDistribution(IdsElement): """ A size distribution result for a given weighting, summarized by its percentile statistics and constituent peaks. """ weighting: NullableString = IdsField( description="Statistical weighting of the distribution.", json_schema_extra={ "example_values": [w.value for w in SizeDistributionWeighting] }, ) source: NullableString = IdsField( description="Analysis/source that produced the distribution, e.g. 'cumulants' or 'distribution analysis'." ) d10: RawValueUnit = IdsField( description="10th percentile size: 10% of the distribution lies below this size." ) d50: RawValueUnit = IdsField(description="50th percentile (median) size.") d90: RawValueUnit = IdsField( description="90th percentile size: 90% of the distribution lies below this size." ) span: RawValueUnit = IdsField(description="Distribution width, (d90 - d10) / d50.") peaks: List[SizeDistributionPeak] = IdsField( description="Individual peaks that make up the distribution." )
# --------------------------------------------------------------------------- # DLS-specific results # ---------------------------------------------------------------------------
[docs] class ZAverage(IdsElement): """ Intensity-weighted harmonic mean hydrodynamic size from the cumulants fit. The Zetasizer reports diameter natively; DynaPro reports radius natively. """ diameter: RawValueUnit = IdsField(description="Z-average hydrodynamic diameter.") radius: RawValueUnit = IdsField(description="Z-average hydrodynamic radius.")
[docs] class PolydispersityIndex(IdsElement): """ Polydispersity index (PDI) from the cumulants fit: <0.1 indicates a monodisperse sample, >0.4 a broad or aggregated one. The computation differs between instruments -- always check ``definition``. """ value: NullableNumber = IdsField(description="Numeric PDI value.") unit: NullableString = IdsField( description="Unit of the PDI value (typically dimensionless)." ) definition: NullableString = IdsField( description="Algorithm used to compute the PDI. The definitions are not interchangeable.", json_schema_extra={ "example_values": [d.value for d in PolydispersityIndexDefinition] }, )
[docs] class CumulantFit(IdsElement): """ Cumulant analysis fit parameters from the DLS autocorrelation function (ACF). Regularized NNLS deconvolution yields amplitude, decay time, and diffusion coefficient from the fitted ACF decay. """ amplitude: RawValueUnit = IdsField( description="Fitted amplitude (intercept) of the ACF." ) decay_time: RawValueUnit = IdsField( description="Characteristic decay time of the ACF." ) diffusion_coefficient: RawValueUnit = IdsField( description="Translational diffusion coefficient from the fit." ) radius: RawValueUnit = IdsField(description="Hydrodynamic radius from the fit.") molecular_weight: RawValueUnit = IdsField( description="Molecular weight estimated from the fit." ) viscosity: RawValueUnit = IdsField(description="Viscosity used in the fit.") q: RawValueUnit = IdsField( description="Scattering vector magnitude used in the fit." ) polydispersity: RawValueUnit = IdsField(description="Polydispersity from the fit.") polydispersity_index: RawValueUnit = IdsField( description="Polydispersity index from the fit." ) polydispersity_percent: RawValueUnit = IdsField( description="Polydispersity expressed as a percentage." ) baseline: RawValueUnit = IdsField(description="Fitted baseline of the ACF.") sos_error: RawValueUnit = IdsField(description="Sum-of-squares error of the fit.") lambda_: RawValueUnit = IdsField( alias="lambda", description="Regularization parameter used in the deconvolution.", ) sigma: RawValueUnit = IdsField( description="Standard deviation (width) parameter of the fit." ) first_cumulant: RawValueUnit = IdsField( description="First cumulant (mean decay rate)." ) second_cumulant: RawValueUnit = IdsField( description="Second cumulant (variance of the decay rate)." ) measured_baseline: RawValueUnit = IdsField( description="Measured (as opposed to fitted) baseline." ) fit_error: RawValueUnit = IdsField(description="Overall error metric of the fit.") order_of_fit: NullableNumber = IdsField( description="Order of the cumulant polynomial fit." )
[docs] class RegularizedFitPeak(IdsElement): """A single peak from regularized NNLS deconvolution of the DLS ACF.""" dist: NullableNumber = IdsField( description="Distribution index the peak belongs to." ) peak_index: NullableNumber = IdsField( description="Index of the peak within the distribution." ) radius: RawValueUnit = IdsField(description="Hydrodynamic radius of the peak.") diffusion_coefficient: RawValueUnit = IdsField( description="Diffusion coefficient of the peak." ) molecular_weight: RawValueUnit = IdsField( description="Molecular weight estimated for the peak." ) polydispersity: RawValueUnit = IdsField(description="Polydispersity of the peak.") first_index: NullableNumber = IdsField( description="Index of the first ACF grid point contributing to the peak." ) number_of_points: NullableNumber = IdsField( description="Number of ACF grid points contributing to the peak." ) decay_time: RawValueUnit = IdsField( description="Characteristic decay time of the peak." ) percent_intensity: RawValueUnit = IdsField( description="Contribution of the peak to the intensity-weighted distribution, as a percentage." ) percent_mass: RawValueUnit = IdsField( description="Contribution of the peak to the mass/volume-weighted distribution, as a percentage." ) percent_number: RawValueUnit = IdsField( description="Contribution of the peak to the number-weighted distribution, as a percentage." )
[docs] class DLSAcquisition(IdsElement): """A single DLS acquisition within a measurement, with its own fit results.""" index: NullableNumber = IdsField( description="Index of the acquisition within the measurement." ) cumulant_fit: CumulantFit = IdsField( description="Cumulant fit for this acquisition." ) regularized_fit_peaks: List[RegularizedFitPeak] = IdsField( description="Peaks from the regularized fit of this acquisition." )
[docs] class InstrumentReading(IdsElement): """Average instrument reading over all acquisitions in a measurement.""" intensity: RawValueUnit = IdsField(description="Average scattered light intensity.") temperature: RawValueUnit = IdsField(description="Average measured temperature.") time: RawValueUnit = IdsField(description="Average acquisition time.")
[docs] class DerivedAnalysis(IdsElement): """ Derived concentration-dependent analysis from DLS (DynaPro SLS companion), computed across a concentration series. """ kd: RawValueUnit = IdsField( description="Diffusion interaction parameter (k_D) from the concentration series." ) a2: RawValueUnit = IdsField( description="Second virial coefficient (A2) from the concentration series." ) tagg: RawValueUnit = IdsField( description="Aggregation onset temperature (T_agg), when a thermal ramp is run." )
[docs] class DLSResult(IdsElement): """Dynamic Light Scattering (DLS) specific results.""" z_average: ZAverage = IdsField( description=( "Intensity-weighted harmonic mean hydrodynamic size from the " "cumulants fit. Zetasizer reports diameter natively; DynaPro " "reports radius natively." ) ) polydispersity_index: PolydispersityIndex = IdsField( description=( "PDI from the cumulants fit. <0.1 monodisperse, >0.4 " "broad/aggregated. Definition differs between instruments -- " "check the ``definition`` field." ) ) diffusion_coefficient: RawValueUnit = IdsField( description="Translational diffusion coefficient from the cumulants fit." ) intercept: RawValueUnit = IdsField( description="ACF y-intercept at tau=0. Data quality indicator; ideal = 1.0." ) mean_count_rate: RawValueUnit = IdsField( description="Photon count rate in kcps. Signal strength indicator." ) derived_mean_count_rate: RawValueUnit = IdsField( description="Derived (attenuation-corrected) mean count rate." ) in_range: RawValueUnit = IdsField( description="Fraction of the signal within the configured size range." ) quality_indicator: NullableString = IdsField( description="Overall data-quality verdict reported by the instrument, e.g. 'good' or a warning message." ) number_of_runs: NullableNumber = IdsField( description="Number of sub-runs/acquisitions averaged into this result." ) run_retention: RawValueUnit = IdsField( description="Fraction of sub-runs retained after quality filtering." ) particle_concentration: RawValueUnit = IdsField( description="Estimated particle concentration." ) cumulant_fit: CumulantFit = IdsField( description="Cumulant fit for the averaged measurement." ) regularized_fit_peaks: List[RegularizedFitPeak] = IdsField( description="Peaks from the regularized fit of the averaged measurement." ) acquisitions: List[DLSAcquisition] = IdsField( description="Per-acquisition results within the measurement." ) average_instrument_reading: InstrumentReading = IdsField( description="Average instrument reading over all acquisitions." ) derived: DerivedAnalysis = IdsField( description="Derived concentration-dependent analysis (DynaPro SLS companion)." )
# --------------------------------------------------------------------------- # ELS-specific results # ---------------------------------------------------------------------------
[docs] class ZetaDistributionPeak(IdsElement): """A single peak in a zeta potential distribution.""" mean: RawValueUnit = IdsField(description="Mean zeta potential of the peak.") area: RawValueUnit = IdsField( description="Area (relative contribution) of the peak." )
[docs] class ZetaDistribution(IdsElement): """Zeta potential distribution, expressed as a set of peaks.""" peaks: List[ZetaDistributionPeak] = IdsField( description="Peaks that make up the zeta potential distribution." )
[docs] class ELSResult(IdsElement): """Electrophoretic Light Scattering (ELS) / zeta potential results.""" potential: RawValueUnit = IdsField(description="Zeta potential.") zeta_deviation: RawValueUnit = IdsField( description="Standard deviation (width) of the zeta potential distribution." ) mobility: RawValueUnit = IdsField(description="Electrophoretic mobility.") model: NullableString = IdsField( description="Model used to convert mobility to zeta potential, e.g. 'Smoluchowski' or 'Huckel'." ) conductivity: RawValueUnit = IdsField(description="Conductivity of the dispersant.") mean_count_rate: RawValueUnit = IdsField( description="Photon count rate in kcps during the ELS measurement." ) quality_factor: RawValueUnit = IdsField( description="Data quality factor reported for the measurement." ) effective_voltage: RawValueUnit = IdsField( description="Effective voltage applied across the cell." ) measured_voltage: RawValueUnit = IdsField( description="Measured voltage across the cell." ) measured_current: RawValueUnit = IdsField( description="Measured current through the cell." ) distribution: ZetaDistribution = IdsField( description="Zeta potential distribution." )
# --------------------------------------------------------------------------- # SLS-specific results (DynaPro only, simultaneous with DLS) # ---------------------------------------------------------------------------
[docs] class SLSResult(IdsElement): """Static Light Scattering (SLS) results (DynaPro only, simultaneous with DLS).""" molar_mass: RawValueUnit = IdsField( description="Molar mass from the static scattering intensity." ) particle_concentration: RawValueUnit = IdsField( description="Particle concentration used in the SLS calculation." ) error: NullableString = IdsField( description="Error/warning message for the SLS calculation, when present." )
# --------------------------------------------------------------------------- # Laser Diffraction-specific results # ---------------------------------------------------------------------------
[docs] class AdditionalStatistic(IdsElement): """An additional Dxx percentile beyond the standard D10/D50/D90.""" name: NullableString = IdsField( description="Name of the percentile, e.g. 'D[v,0.25]'." ) value: NullableNumber = IdsField(description="Numeric value of the percentile.") unit: NullableString = IdsField(description="Unit of the value, e.g. 'Micrometer'.")
[docs] class LaserDiffractionResult(IdsElement): """Laser Diffraction specific results.""" d10: RawValueUnit = IdsField( description="10th percentile size: 10% of the volume distribution lies below this size." ) d50: RawValueUnit = IdsField(description="50th percentile (median) size.") d90: RawValueUnit = IdsField( description="90th percentile size: 90% of the volume distribution lies below this size." ) span: RawValueUnit = IdsField(description="Distribution width, (d90 - d10) / d50.") volume_weighted_mean: RawValueUnit = IdsField( description="D[4,3] - volume-weighted mean diameter." ) surface_weighted_mean: RawValueUnit = IdsField( description="D[3,2] - surface-weighted mean diameter (Sauter mean)." ) obscuration: RawValueUnit = IdsField( description="Obscuration (fraction of light attenuated) during the measurement." ) concentration: RawValueUnit = IdsField( description="Sample concentration in the measurement zone." ) weighted_residual: RawValueUnit = IdsField( description="Weighted residual of the model fit to the diffraction pattern; a goodness-of-fit indicator." ) uniformity: RawValueUnit = IdsField( description="Uniformity of the size distribution." ) specific_surface_area: RawValueUnit = IdsField( description="Specific surface area computed from the volume distribution." ) additional_statistics: List[AdditionalStatistic] = IdsField( description="Additional Dxx percentiles beyond D10/D50/D90." )
# --------------------------------------------------------------------------- # Top-level Result # ---------------------------------------------------------------------------
[docs] class Result(IdsElement): """ A measurement result: the fields common to every technique (size distributions plus the foreign keys linking it back to its run, method, sample, and originating curve). The technique-specific result blocks are *not* included here. As with :py:class:`Method <ts_ids_components.particle_sizer.method.Method>`, a vendor-specific IDS composes only the blocks its instrument produces by inheriting from this base plus the relevant nested mixin classes -- e.g. ``class MyResult(Result, Result.DynamicLightScattering, Result.ElectrophoreticLightScattering)`` for a DLS/ELS instrument. """
[docs] class DynamicLightScattering(IdsElement): """Adds the Dynamic Light Scattering result block.""" dynamic_light_scattering: DLSResult = IdsField( description="Dynamic Light Scattering results." )
[docs] class ElectrophoreticLightScattering(IdsElement): """Adds the Electrophoretic Light Scattering (zeta potential) result block.""" electrophoretic_light_scattering: ELSResult = IdsField( description="Electrophoretic Light Scattering / zeta potential results." )
[docs] class StaticLightScattering(IdsElement): """Adds the Static Light Scattering result block.""" static_light_scattering: SLSResult = IdsField( description="Static Light Scattering results." )
[docs] class LaserDiffraction(IdsElement): """Adds the Laser Diffraction result block.""" laser_diffraction: LaserDiffractionResult = IdsField( description="Laser Diffraction results." )
pk: UUIDPrimaryKey = IdsField(description="Primary key for this result.") analysis_pass: NullableString = IdsField( description=( "Zetasizer-specific analysis pass. Two ``Result`` entries per " "measurement (one per pass). DynaPro and Mastersizer set null." ), json_schema_extra={"example_values": [p.value for p in ResultAnalysisPass]}, ) size_distributions: List[SizeDistribution] = IdsField( description="Size distributions for this result, one per weighting (shared across DLS and Laser Diffraction)." ) fk_run: UUIDForeignKey = IdsField( primary_key="/properties/runs/items/properties/pk", description="Foreign key to the run that produced this result.", ) fk_method: UUIDForeignKey = IdsField( primary_key="/properties/methods/items/properties/pk", description="Foreign key to the method used for this result.", ) fk_sample: UUIDForeignKey = IdsField( primary_key="/properties/samples/items/properties/pk", description=( "Foreign key to the sample. Essential for plate-reader " "instruments where one run produces results for many samples." ), ) fk_curve: UUIDForeignKey = IdsField( primary_key="/properties/curves/items/properties/pk", description=( "Foreign key to the raw curve (in the ``curves`` array) this " "result was derived from. See :py:class:`CurveFile " "<ts_ids_components.particle_sizer.curve.CurveFile>`. When a " "result has no corresponding curve, point this at a placeholder " "curve entry so the foreign key is always satisfied." ), )