Skip to content

Aerodynamic Particle Sizer (APS)

The APS is an instrument used for measuring aerodynamic particle size distributions in the micrometer range.

AeroViz.rawDataReader.script.APS.Reader

Reader(path: Path | str, reset: bool | str = False, qc: bool | str = True, **kwargs)

Bases: AbstractReader

APS (Aerodynamic Particle Sizer) Data Reader

A specialized reader for APS data files, which measure particle size distributions in the range of 542-1981 nm (aerodynamic diameter).

See docs/api/instruments/particle-sizers/APS.md for usage and docs/guide/reader-reference.md for the file layout, status codes and QC rules.

Attributes

nam class-attribute instance-attribute

nam = 'APS'

MIN_TOTAL_CONC class-attribute instance-attribute

MIN_TOTAL_CONC = 1

MAX_TOTAL_CONC class-attribute instance-attribute

MAX_TOTAL_CONC = 700

STATUS_COLUMN class-attribute instance-attribute

STATUS_COLUMN = 'Status Flags'

STATUS_OK class-attribute instance-attribute

STATUS_OK = '0000 0000 0000 0000'

STATUS_ENCODING class-attribute instance-attribute

STATUS_ENCODING = 'binary_string'

STATUS_BITS class-attribute instance-attribute

STATUS_BITS = {1 << 0: 'Laser fault', 1 << 1: 'Total Flow out of range', 1 << 2: 'Sheath Flow out of range', 1 << 3: 'Excessive sample concentration', 1 << 4: 'Accumulator clipped', 1 << 5: 'Autocal failed', 1 << 6: 'Internal temperature < 10°C', 1 << 7: 'Internal temperature > 40°C', 1 << 8: 'Detector voltage out of range'}

DATE_FORMATS class-attribute instance-attribute

DATE_FORMATS = ['%m/%d/%y %H:%M:%S', '%m/%d/%Y %H:%M:%S']

EXPECTED_BIN_GRID class-attribute instance-attribute

EXPECTED_BIN_GRID = (0.542, 19.81, 51)

UNIT_COLUMN class-attribute instance-attribute

UNIT_COLUMN = 'Aerodynamic Diameter'

Methods:

__call__

__call__(start=None, end=None, mean_freq=None)

Return the dN/dlogDp distribution; write S/V + a stats sidecar.

The parent pipeline produces the QC-applied, resampled dN/dlogDp frame (diameters in µm as columns) and stamps df.attrs. We then write the number / surface / volume distributions and a QC-aligned statistics file next to the main output. Pass append_stats=True to also append the statistics columns to the returned frame (default keeps it a clean PSD matrix for psd_stats / merge_psd / SizeDist).

_raw_reader

_raw_reader(file)

Read and parse raw APS data files.

Returns all columns from the raw file. Column selection is deferred to _QC() and _process() stages.

Handles files with multiple concatenated headers (when multiple APS export files are merged into one). Header rows are identified and filtered out.

_QC

_QC(_df)

Perform quality control on APS data.

QC Rules Applied
  1. Status Error : Non-zero status flags indicate instrument error
  2. Insufficient : Less than 5 measurements per hour
  3. Invalid Number Conc : Total number concentration outside valid range (1-700 #/cm³)

_process

_process(_df)

Return the QC'd dN/dlogDp size bins (plus QC_Flag).

The size distribution itself is the canonical APS product. Summary statistics (total per size cut, GMD / GSD / mode) and the surface and volume distributions are derived quantities — compute them on demand with :func:AeroViz.psd_stats / :func:AeroViz.psd_distributions rather than baking them into the reader output. This keeps the reader's return type a plain dN/dlogDp DataFrame (diameters in µm as columns), which is exactly what psd_stats / merge_psd / SizeDist consume.

Raw format

  • File pattern: *.txt, tab-delimited
  • Native frequency: 6min is the config fallback, but the file header carries Sample Time (115 s in the corpus) — not a whole number of minutes. The native grid follows the period detected per file, so scans are not lost to bin collisions; df.attrs['raw_freq'] reports what was actually used.
  • Encoding: opened with encoding='utf-8', errors='ignore'
  • Header layout: ~6 metadata lines (Sample File, Sample Time, Density, Stokes Correction, Lower/Upper Channel Bound) then a Sample # header row, found by scanning for that first cell (so files with several concatenated headers still locate it)
  • Time columns: Date and Start Time
  • Size distribution data: columns 3–54 of the export (the under-range <0.523 column plus the 51 bins)

Parse recipe

  • Transposed exports (Sample # as a row rather than a column header) are rotated with set_index('Sample #').T; if that fails the reader raises NotImplementedError with the original exception attached and logs the first columns it saw.
  • Date from Date + Start Time, formats %m/%d/%y %H:%M:%S then %m/%d/%Y %H:%M:%S; the winning format is logged, and a file matching neither raises with the list of known formats.
  • Size bins are the numeric column names in 0.5–20 (µm), converted to float and rounded to 4 decimal places. Expected grid (0.542, 19.81, 51 bins) (TSI 3321/3320 factory-fixed); a deviation warns loudly but does not reject — an 8-year × 4-station audit of 1 485 files showed zero drift, so a deviation means a firmware change, and concatenating it with other files would create NaN-poisoned columns. The under-range <0.523 column is kept as metadata, not a bin.
  • The consumed index columns (Date, Start Time, Sample #, Aerodynamic Diameter) are dropped; rows with an unparseable or duplicated timestamp are dropped; every other source column is kept through L1.

Export units

AIM writes the export unit on every row, in the Aerodynamic Diameter column (e.g. dN/dlogDp, dN, dM, Raw Counts). Everything after parsing treats the bins as dN/dlogDp, so each row is checked — on dated rows only, since a merged export repeats its header mid-file — and converted where the conversion is exact:

Row unit Handling
dN/dlogDp as is
dN ÷ dlogDp of the bin (bin_widths of the grid)
dS/dlogDp, dS ÷ π·Dp² (µm²/cm³), and ÷ dlogDp for dS
dV/dlogDp, dV ÷ (π/6)·Dp³ (µm³/cm³), and ÷ dlogDp for dV
dM/dlogDp, dM × 10³ ÷ (ρ·(π/6)·Dp³) — AIM mass is mg/m³, ρ is the header's Density (g/cm³)
Raw Counts, %dN, anything else (incl. corrupted cells such as dN/dlggDp) row dropped — Raw Counts needs AIM's flow, dead-time and Stokes corrections, a percentage needs the total

A file mixing units is converted row by row; one with no convertible row left is skipped. Transposed (*M.txt-style) mass exports parse their bins as strings and are made numeric before conversion. The mass factor was checked on 11 days of paired TCLab exports (mass vs number of the same scans): ratio 1.000. Skipped files and dropped rows are named in the log and in the terminal summary.

Measurement parameters

The APS provides aerodynamic particle size distribution measurements:

Parameter Value Description
Size range 0.542–19.81 µm (51 bins) Aerodynamic diameter range
Output dN/dlogDp Number concentration per size bin
Unit #/cm³ Particle number concentration

Status & error codes

Column Status Flags, mode binary_string (see status modes). OK is '0000 0000 0000 0000' (16-bit binary, all zeros); any bit still set after clearing the whitelist mask is an error. A missing Status Flags column is logged by check_status_columns and leaves the rule inert.

Bit meanings (from the TSI RF command):

  • bit 0 (0000 0000 0000 0001) — Laser fault
  • bit 1 (0000 0000 0000 0010) — Total Flow out of range
  • bit 2 (0000 0000 0000 0100) — Sheath Flow out of range
  • bit 3 (0000 0000 0000 1000) — Excessive sample concentration
  • bit 4 (0000 0000 0001 0000) — Accumulator clipped (> 65535)
  • bit 5 (0000 0000 0010 0000) — Autocal failed
  • bit 6 (0000 0000 0100 0000) — Internal temperature < 10°C
  • bit 7 (0000 0000 1000 0000) — Internal temperature > 40°C
  • bit 8 (0000 0001 0000 0000) — Detector voltage out of range (±10% Vb)
  • bit 9 (0000 0010 0000 0000) — Reserved (unused)

Status Condition Register

The Status Flags column is a bitfield: the instrument OR-sums every active condition and reports the sum, so one value can mean several things at once. Reader.STATUS_BITS carries this table, and it is what turns a raw status into df.attrs['status_conditions'] — a named condition instead of a number.

Bit Decimal Condition
8 256 Detector voltage out of range
7 128 Internal temperature > 40°C
6 64 Internal temperature < 10°C
5 32 Autocal failed
4 16 Accumulator clipped
3 8 Excessive sample concentration
2 4 Sheath Flow out of range
1 2 Total Flow out of range
0 1 Laser fault

The register is written as a space-grouped bit string ('0000 0000 0000 0001' is bit 0, not the number one thousand). Bit 9 is reserved. Every non-whitelisted bit counts as an error — this table names the conditions, it does not decide which of them matter.

To stop treating one condition as an error, whitelist its decimal value:

RawDataReader('APS', path, ignored_status_errors=[1])  # ignore Laser fault

QC rules

Before the rules run, the total number concentration is computed as Σ (dN/dlogDp × dlogDp) over the per-bin widths. A natural log was used here until 2026-09-09 where the data is dN/dlog₁₀Dp, inflating every total by ln 10 = 2.303×; that mattered more for APS than for SMPS because the range is tight — a MAX_TOTAL_CONC of 700 was really 304 /cm³, so valid high-loading scans were being rejected.

Rule Condition Severity
Status Error Status Flags has any non-whitelisted bit set (see above) error
Insufficient an hour holds < 50 % of the scans it could have held at the detected frequency (edge hours scaled by coverage) advisory (WARNING) — recorded, data kept
Invalid Number Conc total < 1 or > 700 #/cm³ (MIN_TOTAL_CONC / MAX_TOTAL_CONC); a NaN total is also flagged error

Output

The L2 frame holds the dN/dlogDp matrix only (diameters in µm as columns) plus the QC bookkeeping; Status Flags and the other metadata columns are dropped in _process. Statistics are not in the frame.

Column Unit Description
Size bins (0.542–19.81 µm) dN/dlogDp Number concentration for each size

At L3 the same sidecars as SMPS are written next to the main output (all four by default; choose a subset with size_dist_outputs=): {prefix}_dNdlogDp.csv, {prefix}_dSdlogDp.csv (π·d²·dN), {prefix}_dVdlogDp.csv (π·d³/6·dN) and {prefix}_stats.csv (from psd_stats, QC-aligned; a failure there is logged and never fails the read). Pass append_stats=True to also append the statistics columns to the returned frame; the default keeps it a clean PSD matrix for psd_stats / merge_psd / SizeDist.

df.attrs['size_dist_unit'] is always 'dN/dlogDp', checked by SizeDist, psd_stats and merge_psd before they compute (see Size Distribution); df.attrs['source_units'] is {export unit: [files]} for the files that held other units, and absent when none did.

Files written per read are listed in RawDataReader Reference §1.

Notes

  • Measures aerodynamic particle diameter directly
  • Complementary to SMPS for larger particle sizes
  • Size range approximately 0.5–20 μm
  • Logarithmic bin spacing in size distribution
  • Counting efficiency: the APS under-counts at both ends — 85–99 % for solid particles, but falling from 75 % at 0.8 µm to 25 % at 10 µm for droplets (Volckens & Peters 2005). Not corrected for; see Counting Efficiency.