Chromatophore processing
The scripts code/process_video.py and code/process_video_batch.py process either on video or a folder of videos and save results into a new folder under video_name_export next to the original video file. The export folders contain
- ... _masks.zarr: Zarr archive containing masks across frames in the resolution of the analyzed video
- ... _cell_tracking.parquet: Parquet archive containing region specific info across frames for each region in the extracted masks
The notebook load_data.ipynb shows an overview of methods to load + process the exported data. It shows how to interpolate timeseries data (area mesaurements) and how to cluster chromatophores with kmeans on one loaded video (cluster n = 3).
The function load_video_zarr take a video_path as input and returns the zarr_path (masks)
# Load data
import pandas as pd # Use pandas for reading parquet files back in
from process_video import load_video_zarr
video_path = 'your_video_path.mp4'
zarr_path, parquet_path, video, mask_zarr = load_video_zarr(Path(video_path),
remove_previous_zarr=False
)
# Returns
# -------
# zarr_path : pathlib.Path
# Path to the Zarr file for storing cell masks.
# parquet_path : pathlib.Path
# Path to the Parquet file for storing cell tracking data.
# video : FastVideoReader
# Video reader object for accessing video frames.
# This allows frame by frame access to the video file (logical indexing).
# mask_zarr : zarr.core.Array or similar
# Zarr array object for cell masks, loaded from disk.
export_path = parquet_path.parent
num_frames = video.shape[0]
image_height = video.shape[1]
image_width = video.shape[2]
chroma_data = pd.read_parquet(parquet_path)Columns in parquet dataframe:
label: Unique identifier for each detected region BEFORE distance sorting (ignore this!)frame: Frame index (as in original video)region_id: Unique identifier for the region across frames AFTER sorting. Matches the label of each region in corresponding mask at this frame.area: Number of pixels within the region.area_bbox: Area of the bounding box surrounding the region.area_convex: Area of the convex hull of the region.centroid-0: Y-coordinate of the region's centroid.centroid-1: X-coordinate of the region's centroid.orientation: Angle of the region's major axis.eccentricity: Measure of how elongated the region is.solidity: Ratio of region area to its convex hull area.extent: Ratio of region area to bounding box area.major_axis_length: Length of the major axis of the region.minor_axis_length: Length of the minor axis of the region.corner_top_left_mean_l: Mean lightness (L) in the top-left corner of the frame (for laser stim. extraction).corner_top_right_mean_l: Mean lightness (L) in the top-right corner of the frame (for laser stim. extraction).corner_bottom_left_meanl: Mean lightness (L) in the bottom-left corner of the frame (for laser stim. extraction).corner_bottom_right_mean_l: Mean lightness (L) in the bottom-right corner of the frame (for laser stim. extraction).mean_l: Mean lightness (L) value of the region.mean_a: Mean green-red (a) value of the region.mean_b: Mean blue-yellow (b) value of the region.mean_hues: Mean hue value of the region.mean_sats: Mean saturation value of the region.mean_vals: Mean value (brightness) of the region.- centroid_dist: Distance from the region's centroid the identified reference point.
threshold_rel_laser = 1.005 # e.g. 1.25 x median is threshold for detecting laser stim
# chroma_data is loaded parquet dataframe
frame_data = chroma_data.groupby('frame').mean(numeric_only=True).reset_index()
# Extract all laser indices, laser pulse on and off
# Get average and peaks
average_of_corners = np.nanmean(np.stack([frame_data['corner_top_left_mean_l'],
frame_data['corner_top_right_mean_l'],
frame_data['corner_bottom_left_mean_l'],
frame_data['corner_bottom_right_mean_l']]),
axis=0
)
average_of_corners /= np.nanmedian(average_of_corners)
laser_indices = np.argwhere(average_of_corners>threshold_rel_laser)
print(f'Found {len(laser_indices)} laser pulses')
print(f'Laser pulses visible at indices: {laser_indices}')
# Extract laser onsets
gap_idx = np.where(np.diff(laser_indices.squeeze()) > 1)[0]
# Onset of each pulse train is the first index after each gap (plus the very first pulse)
laser_onsets = laser_indices.squeeze()[np.insert(gap_idx + 1, 0, 0)]
print("Laser pulse train onsets at frames:", laser_onsets)
# Extract offsets (last index before each gap, plus the last pulse)
laser_offsets = laser_indices.squeeze()[np.append(gap_idx, len(laser_indices.squeeze()) - 1)]
print("Laser pulse train offsets at frames:", laser_offsets)
