Source code for lbm_caiman_python.lcp_io
import os
import numpy as np
import tifffile
from pathlib import Path
def make_json_serializable(obj):
"""Convert metadata to JSON serializable format."""
if isinstance(obj, dict):
return {k: make_json_serializable(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [make_json_serializable(v) for v in obj]
elif isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, (np.integer, np.floating)):
return obj.item()
else:
return obj
[docs]
def get_metadata(file: os.PathLike | str):
"""
Extract metadata from a TIFF file. This can be a raw ScanImage TIFF or one
processed via [lbm_caiman_python.save_as()](#save_as).
Parameters
----------
file: os.PathLike
Path to the TIFF file.
Returns
-------
dict
Metadata extracted from the TIFF file.
Raises
------
ValueError
If no metadata is found in the TIFF file. This can occur when the file is not a ScanImage TIFF.
"""
if not file:
return None
tiff_file = tifffile.TiffFile(file)
if (
hasattr(tiff_file, 'shaped_metadata')
and tiff_file.shaped_metadata is not None
and isinstance(tiff_file.shaped_metadata, (list, tuple))
and tiff_file.shaped_metadata
and tiff_file.shaped_metadata[0] not in ([], (), None)
):
if 'image' in tiff_file.shaped_metadata[0]:
return tiff_file.shaped_metadata[0]['image']
if hasattr(tiff_file, 'scanimage_metadata'):
meta = tiff_file.scanimage_metadata
if meta is None:
return None
si = meta.get('FrameData', {})
if not si:
print(f"No FrameData found in {file}.")
return None
series = tiff_file.series[0]
pages = tiff_file.pages
# Extract ROI and imaging metadata
roi_group = meta["RoiGroups"]["imagingRoiGroup"]["rois"]
num_rois = len(roi_group)
num_planes = len(si["SI.hChannels.channelSave"])
sizes = [roi_group[i]["scanfields"][i]["sizeXY"] for i in range(num_rois)]
num_pixel_xys = [roi_group[i]["scanfields"][i]["pixelResolutionXY"] for i in range(num_rois)]
# see if each item in sizes is the same
assert all([sizes[0] == size for size in sizes]), "ROIs have different sizes"
assert all([num_pixel_xys[0] == num_pixel_xy for num_pixel_xy in
num_pixel_xys]), "ROIs have different pixel resolutions"
size_xy = sizes[0]
num_pixel_xy = num_pixel_xys[0]
# TIFF header-derived metadata
sample_format = pages[0].dtype.name
objective_resolution = si["SI.objectiveResolution"]
frame_rate = si["SI.hRoiManager.scanFrameRate"]
# Field-of-view calculations
# TODO: We may want an FOV measure that takes into account contiguous ROIs
# As of now, this is for a single ROI
fov_x = round(objective_resolution * size_xy[0])
fov_y = round(objective_resolution * size_xy[1])
fov_xy = (fov_x, fov_y / num_rois)
# Pixel resolution (dxy) calculation
pixel_resolution = (fov_x / num_pixel_xy[0], fov_y / num_pixel_xy[1])
return {
"num_planes": num_planes,
"num_frames": int(len(pages) / num_planes),
"fov": fov_xy, # in microns
"num_rois": num_rois,
"frame_rate": frame_rate,
"pixel_resolution": np.round(pixel_resolution, 2),
"ndim": series.ndim,
"dtype": 'uint16',
"size": series.size,
"raw_height": pages[0].shape[0],
"raw_width": pages[0].shape[1],
"tiff_pages": len(pages),
"roi_width_px": num_pixel_xy[0],
"roi_height_px": num_pixel_xy[1],
"sample_format": sample_format,
"objective_resolution": objective_resolution,
}
else:
raise ValueError(f"No metadata found in {file}.")
[docs]
def get_files_ext(base_dir, extension, max_depth) -> list:
"""
Recursively searches for files with a specific extension up to a given depth and stores their paths in a pickle file.
Parameters
----------
base_dir : str or Path
The base directory to start searching.
extension : str
The file extension to look for (e.g., '.txt').
max_depth : int
The maximum depth of subdirectories to search.
Returns
-------
list
A list of full file paths matching the given extension.
"""
base_path = Path(base_dir).expanduser().resolve()
if not base_path.exists():
raise FileNotFoundError(f"Directory '{base_path}' does not exist.")
if not base_path.is_dir():
raise NotADirectoryError(f"'{base_path}' is not a directory.")
return [
str(file)
for file in base_path.rglob(f'*{extension}')
if len(file.relative_to(base_path).parts) <= max_depth + 1
]
def get_metrics_path(fname: Path) -> Path:
"""
Get the path to the computed metrics file for a given data file.
Assumes the metrics file is to be stored in the same directory as the data file,
with the same name stem and a '_metrics.npz' suffix.
Parameters
----------
fname : Path
The path to the input data file.
Returns
-------
metrics_path : Path
The path to the computed metrics file.
"""
fname = Path(fname)
return fname.with_stem(fname.stem + '_metrics').with_suffix('.npz')