Python Usage
Introduction
The image processing workflow can also be controlled via Python API.
To do so, first define the parameters:
from lls_core import LatticeData
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output"
)
Then save the result to disk:
Or work with the images in memory:
Other more advanced options are listed below.
Cropping
Cropping functionality can be enabled by setting the crop parameter:
from lls_core import LatticeData, CropParams
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
crop=CropParams(
roi_list=["/path/to/roi.zip"]
)
)
Other more advanced options are listed below.
ROI files and their units
roi_list accepts Fiji ROI Manager files (.roi, .zip) and napari shapes files
(.csv, as written by File -> Save Selected Layers), as well as coordinates given
directly as arrays.
Files do not record whether their coordinates are pixels or microns, and the two
differ by the pixel size, so roi_units declares it. The default, Auto, takes it
from the file type — Fiji files are pixels, a napari .csv saved from the plugin's
crop layer is microns:
from lls_core import CropParams
from lls_core.cropping import RoiUnits
# Auto (default): .zip -> pixels, .csv -> microns
CropParams(roi_list=["/path/to/roi.zip"])
# A CSV in pixel coordinates, written by something other than napari
CropParams(roi_list=["/path/to/roi.csv"], roi_units=RoiUnits.Pixels)
Internally roi_list is always converted to deskewed-image pixels, using the
image's pixel size. Coordinates passed directly as arrays are assumed to be pixels.
Mixing file types that imply different units in one roi_list is an error — set
roi_units explicitly in that case.
Selecting which ROIs to process
A ROI file (or roi_list) may contain many regions. By default all of them are
processed. To process only some, pass their indices via roi_subset:
from lls_core import LatticeData, CropParams
# Process ALL ROIs in the file (default — roi_subset omitted)
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
crop=CropParams(roi_list=["/path/to/roi.zip"])
)
# Process ONLY ROIs 0 and 2
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
crop=CropParams(roi_list=["/path/to/roi.zip"], roi_subset=[0, 2])
)
The rule is: omit roi_subset to process every ROI; pass a list of indices to
restrict processing to that subset. The same setting controls the serial and
parallel paths alike.
Parallel ROI Processing
When cropping is enabled, multiple ROIs can be processed in parallel worker
processes that share the GPU. This is controlled by process_parallel:
from lls_core import LatticeData, CropParams
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
process_parallel=4, # distribute the selected ROIs across 4 workers
crop=CropParams(roi_list=["/path/to/roi.zip"])
)
process_parallel accepts:
1(default) — serial processing, one ROI at a time.N > 1— an explicit number of worker processes. The selected ROIs (see ROI selection) are split into roughly equal chunks, one per worker. Useful when a single ROI does not saturate the GPU.0— auto: a memory-safe worker count, the largest that fits every limit — GPU memory, host memory, the number of ROIs, the machine's CPU count, andSLURM_CPUS_PER_TASKwhen running under SLURM. This is disabled for deconvolution and workflow runs (whose memory cannot be sized), which fall back to serial.
Notes:
- The selected ROIs are distributed across workers; parallelism never changes which ROIs are processed, only how they are spread across processes.
- If only a single ROI is selected, processing always runs serially regardless
of
process_parallel, since there is nothing to distribute. process_parallelis ignored when cropping is disabled.- Workers re-open the input file rather than being sent the pixels. If the input is a lazily-loaded image with no single source file to re-open — for example napari layers stacked from several files — processing falls back to serial and logs why. Loading one file, including a multi-channel file split across per-channel layers, uses workers as normal.
Every fallback to serial is logged, so a run that was slower than expected can be explained from the log rather than guessed at.
Defaults differ by entry point
The Python API and napari GUI default to serial (process_parallel=1 / the
GUI's "Parallel ROI Processing" checkbox off). The lls-pipeline CLI instead
defaults to 0 (auto) when you don't set a value, so an unconfigured CLI run
picks a memory-safe worker count automatically.
Maximum Intensity Projections (MIPs)
Instead of writing the full deskewed volume, you can ask for a deskewed 2D maximum-intensity projection (a top-down, coverslip-view MIP). This is computed directly from the raw data without ever materialising the deskewed volume, so it is memory-light and fast — ideal for very large acquisitions or for generating an image to define cropping ROIs against.
from lls_core import LatticeData
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
save_mip=True,
)
params.save()
- One MIP is written per timepoint and channel, using the configured
save_type. - Set
mip_interpolation="linear"for a smoother projection (the default is"nearest", which is fastest but blocky). - Cropping and deconvolution are ignored for MIP output — it is a fast whole-frame projection.
Flipping the scan direction
For microscopes whose stage/galvo scans run opposite to the Zeiss LLS (common on some OPM
systems), set invert_scan_direction=True to reverse the plane order along the scan axis
before deskewing:
params = LatticeData(
input_image="/path/to/some/file.tiff",
save_dir="/path/to/output",
invert_scan_direction=True,
)
This can be combined with skew="X"/skew="Y" and coverslip_rotation=False to match a
range of OPM acquisition geometries.
Type Checking
Because of Pydantic idiosyncrasies, the LatticeData constructor can accept more data types than the type system realises.
For example, input_image="/some/path" like we used above is not considered correct, because ultimately the input image has to become an xarray (aka DataArray).
You can solve this in three ways.
The first is to use the types precisely as defined. In this case, we might define the parameters "correctly" (if verbosely) like this:
from lls_core import LatticeData
from bioio import BioImage
from pathlib import Path
params = LatticeData(
input_image=BioImage("/path/to/some/file.tiff").xarray_dask_data,
save_dir=Path("/path/to/output")
)
The second is to use LatticeData.parse_obj, which takes a dictionary of options and allows incorrect types:
params = LatticeData.parse_obj({
"input_image": "/path/to/some/file.tiff",
"save_dir": "/path/to/output"
})
Finally, if you're using MyPy, you can install the pydantic plugin, which solves this problem via the init_typed = False option.
API Docs
lls_core.LatticeData
Parameters for the entire deskewing process, including outputs and optional steps such as deconvolution. This is the recommended entry point for Python users: construct an instance of this class, and then perform the processing using methods.
Note that none of this class's methods have any parameters: all parameters are class fields for validation purposes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_image
|
DataArray
|
A 3-5D array containing the image data. Can be anything convertible to an Xarray, including a |
None
|
input_image_path
|
Path | None
|
Internal: the filesystem path the input image was loaded from, if any. Set automatically when |
None
|
skew
|
DeskewDirection
|
Axis along which to deskew the image. Choices: |
<DeskewDirection.Y: 2>
|
angle
|
float
|
Angle of deskewing, in degrees, as a float. |
30.0
|
physical_pixel_sizes
|
DefinedPixelSizes
|
Pixel size of the microscope, in microns. This can alternatively be provided as a |
None
|
invert_scan_direction
|
bool
|
If |
False
|
coverslip_rotation
|
bool
|
Apply the coverslip rotation (rotate the deskewed volume by the deskew angle). Effect is acquisition-geometry dependent: True (default) uses the standard deskew (cle.deskew_y/x) and is coverslip-level for Zeiss LLS7; False skips the rotation and is coverslip-level for some OPM/SOPi. |
True
|
derived
|
DerivedDeskewFields
|
Refer to the |
None
|
save_dir
|
DirectoryPath
|
The directory where the output data will be saved. This can be specified as a |
None
|
save_suffix
|
str
|
The filename suffix that will be used for output files. This will be added as a suffix to the input file name if the input image was specified using a file name. If the input image was provided as an in-memory object, the |
'_deskewed'
|
save_name
|
str
|
The filename that will be used for output files. This should not contain a leading directory or file extension. The final output files will have additional elements added to the end of this prefix to indicate the region of interest, channel, timepoint, file extension etc. |
None
|
save_type
|
SaveFileType
|
The data type to save the result as. This will also be used to determine the file extension of the output files. Choices: |
<SaveFileType.h5: 'h5'>
|
time_range
|
range
|
The range of times to process. This defaults to all time points in the image array. |
None
|
channel_range
|
range
|
The range of channels to process. This defaults to all time points in the image array. |
None
|
process_parallel
|
ConstrainedIntValue
|
Number of worker processes for cropping ROIs. Each worker processes a subset of the ROI list independently, sharing the GPU. 1 (default) keeps the serial behaviour; higher values help when a single ROI does not saturate the GPU. 0 means 'auto': a memory-safe worker count is derived from the memory estimate (disabled for deconvolution/workflow runs, which it cannot size). Ignored when cropping is disabled. |
1
|
memory_safety_factor
|
ConstrainedFloatValue
|
Multiplier applied to the estimated per-worker working set in the memory estimate, covering OpenCL scratch buffers and fragmentation. Increase if you hit OOM crashes with parallel processing; decrease for more aggressive packing. |
1.5
|
save_mip
|
bool
|
If |
False
|
mip_interpolation
|
MipInterpolation
|
Interpolation used when |
<MipInterpolation.nearest: 'nearest'>
|
deconvolution
|
DeconvolutionParams | None
|
Parameters associated with the deconvolution. If this is None, then deconvolution is disabled |
None
|
crop
|
CropParams | None
|
Cropping parameters. If this is None, then cropping is disabled |
None
|
workflow
|
Workflow | None
|
If defined, this is a workflow to add lightsheet processing onto |
None
|
progress_bar
|
bool
|
If true, show progress bars |
True
|
process
process() -> ImageSlices
Execute the processing and return the result. This will not execute the attached workflow.
process_workflow
process_workflow() -> WorkflowSlices
Runs the workflow on each slice and returns the workflow results
lls_core.DeconvolutionParams
Parameters for the optional deconvolution step
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
decon_processing
|
DeconvolutionChoice
|
Hardware to use to perform the deconvolution. Choices: |
<DeconvolutionChoice.cpu: 'cpu'>
|
psf
|
List[DataArray]
|
List of Point Spread Functions to use for deconvolution. Each of which should be a 3D array. Each PSF can also be provided as a |
[]
|
decon_num_iter
|
NonNegativeInt
|
Number of iterations to perform in deconvolution |
10
|
background
|
float | Literal['auto', 'second_last']
|
Background value to subtract for deconvolution. Only used when |
0
|
lls_core.CropParams
Parameters for the optional cropping step. Note that cropping is performed in the space of the deskewed shape. This is to support the workflow of performing a preview deskew and using that to calculate the cropping coordinates.
roi_list is always in deskewed-image pixels. A file given in microns is
converted on the way in, once the pixel size is known - see roi_units.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
roi_list
|
List[Roi]
|
List of regions of interest, each of which must be an |
[]
|
roi_units
|
RoiUnits
|
The units the |
<RoiUnits.Auto: 'Auto'>
|
roi_subset
|
List[int]
|
A subset of all the ROIs to process. Each list item should be an index into the ROI list indicating an ROI to include. This allows you to process only a subset of the regions from a ROI file specified using the |
None
|
z_range
|
Tuple[NonNegativeInt, NonNegativeInt]
|
The range of Z slices to take as a tuple of the form |
None
|
lls_core.models.results.ImageSlices
A collection of image slices, which is the main output from deskewing.
This holds an iterable of output image slices before they are saved to disk,
and provides a save_image() method for this purpose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slices
|
Iterable[ProcessedSlice[Array | ndarray[Any, dtype[_ScalarType_co]] | Array | DataArray]]
|
Iterable of result slices. For a given slice, you can access the image data through the |
None
|
lattice_data
|
ForwardRef('LatticeData')
|
The "parent" LatticeData that was used to create this result |
required |
lls_core.models.results.WorkflowSlices
The counterpart of ImageSlices, but for workflow outputs.
This is needed because workflows have vastly different outputs that may include regular
Python types rather than only image slices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slices
|
Iterable[ProcessedSlice[Tuple[Array | ndarray[Any, dtype[_ScalarType_co]] | Array | DataArray | dict | list] | Array | ndarray[Any, dtype[_ScalarType_co]] | Array | DataArray | dict | list]]
|
Iterable of raw workflow results, the exact nature of which is determined by the author of the workflow. Not typically useful directly, and using he result of |
None
|
lattice_data
|
ForwardRef('LatticeData')
|
The "parent" LatticeData that was used to create this result |
required |
process
process() -> Iterable[ProcessedWorkflowOutput]
Incrementally processes the workflow outputs, and returns both image paths and data frames of the outputs, for image slices and dict/list outputs respectively
lls_core.models.results.ProcessedWorkflowOutput
Result class for one single workflow output, after it has been processed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
|
None
|
roi_index
|
int | None
|
|
None
|
data
|
Path | DataFrame
|
|
None
|
lattice_data
|
ForwardRef('LatticeData')
|
|
required |
lls_core.models.deskew.DefinedPixelSizes
Like PhysicalPixelSizes, but it's a dataclass, and none of its fields are None
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
NonNegativeFloat
|
Size of the X dimension of the microscope pixels, in microns. |
0.1499219272808386
|
Y
|
NonNegativeFloat
|
Size of the Y dimension of the microscope pixels, in microns. |
0.1499219272808386
|
Z
|
NonNegativeFloat
|
Size of the Z dimension of the microscope pixels, in microns. |
0.3
|
lls_core.models.deskew.DerivedDeskewFields
Fields that are automatically calculated based on other fields in DeskewParams. Grouping these together into one model makes validation simpler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deskew_vol_shape
|
Tuple[int, ...]
|
Dimensions of the deskewed output. This is set automatically based on other input parameters, and doesn't need to be provided by the user. |
None
|
deskew_affine_transform
|
AffineTransform3D
|
Deskewing affine transformation matrix (in xyz order for OpenCL). This is set automatically based on other input parameters, and doesn't need to be provided by the user. |
None
|
deskew_affine_transform_zyx
|
ndarray
|
Deskewing affine transformation matrix (zyx order). This is set automatically based on other input parameters, and doesn't need to be provided by the user. |
None
|
lls_core.models.output.SaveFileType
Choice of File extension to save