BCLIF

BCLIF is the LIF-scan container exposed by BCExperiment as exp.lif whenever the experiment folder contains a lif/ subdirectory. On construction it reads lif/lifparams.csv and lif/processing.csv and pulls the DelayPoints / DelayStart / DelayStep and LaserPoints / LaserStart / LaserStep rows out of the experiment header so the full delay × laser scan grid is known up-front. Per-point trace files are read lazily through get_trace; opening an LIF experiment is therefore cheap regardless of grid size.

The class provides three aggregating helpers — delay_slice, laser_slice, and image — that integrate every present scan point with one processing-override surface, returning numpy arrays sized against the full scan axes. Scan grids are routinely incomplete: acquisitions can stop partway through, leaving some (lIndex, dIndex) positions with no trace file. Those positions are filled with np.nan by default; pass fill=0.0 (or any other numeric value) when a zero-baseline image is preferred. The has_ref attribute reports whether any point in the scan recorded a reference channel, derived from the refsize column of lifparams.csv.

The on-disk format that BCLIF reads — the lif/ subdirectory layout, the meaning of each lifparams.csv column, the processing.csv integration-gate settings — is documented in detail on the LIF Data Storage user-guide page.

When the experiment’s LIF setup runs the tunable laser through one or more optical conversion stages — a doubling crystal, a mixing crystal — before the beam reaches the sample, BCLIF also exposes the recorded frequency-conversion topology, read from liftopology.csv via BCExperiment.liftopology (None for a bare laser with no conversion stages); see the file’s column reference on the LIF Data Storage page. The boolean has_topology reports whether a topology was recorded; stages, final_stage, and laser_key name every stage in it, the stage whose output is the excitation beam reaching the sample, and the laser the topology was built around.

Three accessors translate between the laser’s tuning value and any beam in the topology, in any of cm-1 / nm / GHz / eV: fundamental answers “what laser setting produced this beam value?”; at_stage is the inverse, answering “what beam value does this laser setting produce?”; and stage_frequencies returns a table of every beam in the topology for a single laser setting or excitation-beam value. All three fall back to the identity relationship (the laser setting is the excitation-beam value) when has_topology is False.

API Reference

class blackchirp.BCLIF(path: str, sep: str, header: pandas.DataFrame, liftopology: pandas.DataFrame | None = None)

Container for a complete LIF scan.

Loads lif/lifparams.csv and lif/processing.csv and reads the scan-axis parameters (DelayStart, LaserStart, etc.) from the supplied experiment header. Exposes per-point access via get_trace() and aggregating helpers (delay_slice(), laser_slice(), image()) that integrate every present point with a single processing-override surface.

This class is not intended to be instantiated directly; it is constructed by BCExperiment whenever the experiment folder contains a lif/ subdirectory.

Parameters:
  • path – Experiment folder path.

  • sep – CSV delimiter for the experiment.

  • header – Experiment header DataFrame (BCExperiment.header) used to read LifConfig scan-axis rows.

lifparams

Contents of lif/lifparams.csv.

Type:

pd.DataFrame

proc

Contents of lif/processing.csv parsed to typed values (ints, floats, bools).

Type:

dict

delay_points

Number of delay-axis points.

Type:

int

delay_start

First delay value, in delay_units.

Type:

float

delay_step

Delay-axis step, in delay_units.

Type:

float

delay_units

Units of delay_start / delay_step.

Type:

str

laser_points

Number of laser-axis points.

Type:

int

laser_start

First laser value, in laser_units.

Type:

float

laser_step

Laser-axis step, in laser_units.

Type:

float

laser_units

Units of laser_start / laser_step.

Type:

str

has_ref

True if any lifparams row has refsize > 0.

Type:

bool

numtraces

Number of populated scan points (len(lifparams)).

Type:

int

has_topology

True if a non-identity frequency-conversion topology (liftopology.csv) was recorded for this experiment.

Type:

bool

stages

Conversion-stage hardware keys in DAG order (empty when has_topology is False).

Type:

list[str]

final_stage

Hardware key of the stage that produces the output (excitation) beam, or None for the identity case.

Type:

str | None

laser_key

Hardware key of the tunable laser the topology was captured against, or None for the identity case.

Type:

str | None

at_stage(fundamental, at='final', side='output', unit='cm-1')

Evaluate the beam at one location for a given grating fundamental.

Inverse of fundamental(). Accepts a scalar or a NumPy array of fundamentals and matches the input shape on output, so a whole laser sweep can be mapped in one call.

Parameters:
  • fundamental – Grating fundamental value(s) in unit.

  • at – A stage’s hardware key, "final", or "laser".

  • side"output" or "input" (primary input) of the stage.

  • unit – Unit of both fundamental and the result.

Returns:

The beam value(s) at at/side in unit (scalar or array, matching fundamental).

Raises:

KeyError – If at names an unknown stage.

Example

>>> lif.at_stage(560.0, at="final", side="output", unit="nm")
280.0
delay_axis() Tuple[numpy.ndarray, str]

Return the delay-axis sample values and their units string.

delay_slice(l_index: int, fill=numpy.nan, **proc) Tuple[numpy.ndarray, numpy.ndarray]

Integrate every present trace at one laser index.

Parameters:
  • l_index – Laser-axis index to slice.

  • fill – Value substituted at delay indices that have no acquired trace (np.nan by default; pass 0.0 to represent missing points as zero).

  • **proc – Optional processing overrides, forwarded to BCLifTrace.integrate().

Returns:

Tuple (delay_axis, integrals). Length of both is delay_points.

fundamental(value, at='final', side='output', unit='cm-1')

Solve for the grating fundamental from a beam value at one location.

Because every beam is affine in the tunable fundamental, this is an exact inversion (no numerics). Accepts a scalar or a NumPy array and matches the input shape on output.

Parameters:
  • value – Beam value(s) at the location, expressed in unit.

  • at – A stage’s hardware key, "final" (the output beam), or "laser" (the fundamental itself).

  • side"output" or "input" of the stage (the primary, tunable-path input for a two-input mixing stage). Ignored for at="laser".

  • unit – Unit of both value and the returned fundamental — one of cm-1, nm, GHz, eV (or a header.csv label such as cm⁻¹).

Returns:

The grating fundamental in unit (scalar or array, matching value). For an experiment with no conversion, the fundamental equals value at at in {"final", "laser"}.

Raises:
  • ValueError – If the location does not depend on the tunable laser (e.g. a fixed mixing beam), so it cannot be inverted.

  • KeyError – If at names an unknown stage.

Example

>>> lif.fundamental(280.0, at="final", side="output", unit="nm")
560.0
get_trace(l_index: int, d_index: int) BCLifTrace

Load a single BCLifTrace from disk.

Parameters:
  • l_index – Laser-axis index.

  • d_index – Delay-axis index.

Returns:

A freshly loaded BCLifTrace.

Raises:

KeyError – If no row exists in lifparams.csv for the requested (l_index, d_index) pair (i.e. the scan point was not acquired).

image(fill=numpy.nan, **proc) Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]

Integrate every present trace into a 2D delay × laser image.

Parameters:
  • fill – Value substituted at (d, l) positions that have no acquired trace.

  • **proc – Optional processing overrides, forwarded to BCLifTrace.integrate().

Returns:

Tuple (delay_axis, laser_axis, integrals) where integrals has shape (delay_points, laser_points).

laser_axis() Tuple[numpy.ndarray, str]

Return the laser-axis sample values and their units string.

laser_slice(d_index: int, fill=numpy.nan, **proc) Tuple[numpy.ndarray, numpy.ndarray]

Integrate every present trace at one delay index.

Parameters:
  • d_index – Delay-axis index to slice.

  • fill – Value substituted at laser indices that have no acquired trace.

  • **proc – Optional processing overrides, forwarded to BCLifTrace.integrate().

Returns:

Tuple (laser_axis, integrals). Length of both is laser_points.

stage_frequencies(value=None, *, fundamental=None, at='final', side='output', unit='cm-1') pandas.DataFrame

Tabulate every stage’s inputs and output for a single frequency.

Give it either a known beam value at a location (at/side) or a fundamental directly; it solves for the fundamental and evaluates the whole graph. This is the single-frequency, descriptive counterpart to fundamental()/at_stage() — for a bulk sweep, call those with an array (an (N, M) grid is np.stack([lif.at_stage(f, at=s) for s in lif.stages], axis=1)).

Parameters:
  • value – A known beam value at at/side, in unit. Mutually exclusive with fundamental.

  • fundamental – The grating fundamental in unit. Mutually exclusive with value.

  • at – Location that value refers to (stage key, "final", or "laser").

  • side"output" or "input" that value refers to.

  • unit – Unit for the input and every value in the returned frame.

Returns:

A pandas.DataFrame indexed by "laser" followed by each stage’s hardware key, with columns op, isfinal, input0, input1 (NaN for single-input stages), and output, all in unit. For an experiment with no conversion, only the laser row is returned.

Raises:
  • ValueError – If neither or both of value/fundamental are given, or a non-scalar is passed.

  • KeyError – If at names an unknown stage.