Skip to content
Snippets Groups Projects
Commit 3d2fbba1 authored by Pierre Tocquin's avatar Pierre Tocquin
Browse files

feat(pipeline): add script for automating CZI to TIFF processing and analysis

Introduced `process_pipeline.py` to automate conversion of CZI images to TIFF, perform analysis with Ilastik, and resize outputs based on YAML configuration. Updated dependencies in `requirements.txt` to support new functionality.
parent f416381e
No related branches found
No related tags found
No related merge requests found
......@@ -10,10 +10,15 @@
- The update aims to improve workflow efficiency and maintain image quality handling in microscopy applications.
### Added
- **step2_batch_ilastik.py:** Add script for batch processing and resizing images with Ilastik.
- **feat(image-processing):** Add script for batch processing and resizing images with Ilastik.
- This new script automates the process of running Ilastik in headless mode for image segmentation while resizing input and output images.
- Input images are resized using quality-preserving interpolation, whereas segmented outputs use nearest-neighbor interpolation to maintain segmentation integrity.
- It is designed to handle large batches of images efficiently, supporting user-specified scale factors and ensuring ease of use with command-line arguments.
- **feat(pipeline):** Add script for automating CZI to TIFF processing and analysis.
- Introduced `process_pipeline.py` to automate conversion of CZI images to TIFF.
- Integrated analysis with Ilastik and resizing outputs based on YAML configuration.
- Updated dependencies in `requirements.txt` to support new functionality.
## [Unreleased] - 2024-12-01
......
"""
Author: Pierre Tocquin (University of Liège, ptocquin@uliege.be)
Description: This script automates the processing of CZI images by converting them to TIFF,
analyzing them with Ilastik, and resizing the input and segmented outputs.
The script uses a YAML configuration file to define global parameters such as:
- Output directory for processed files (`output_dir`)
- Compression type for TIFF files (`compression`)
- Resize factor for scaling images (`resize_factor`)
- Path to the Ilastik project file (`project`)
- Path to the Ilastik executable (`ilastik`)
Usage: python process_pipeline.py file1.czi file2.czi ... --config /path/to/config.yaml
Example configuration file (config.yaml):
output_dir: ./output
compression: lzw
resize_factor: 0.25
project: /path/to/ilastik_project.ilp
ilastik: /path/to/ilastik_executable
Dependencies:
- PyYAML: For reading the YAML configuration file.
- aicspylibczi: For processing CZI files.
- tifffile: For handling TIFF files.
- OpenCV: For image resizing.
- Ilastik: For segmentation in headless mode.
License: MIT License
Copyright (c) 2024 Pierre Tocquin
"""
import argparse
import os
import cv2
import subprocess
import yaml
import tifffile as tiff
import numpy as np
from aicspylibczi import CziFile
def load_config(config_path):
"""
Loads and validates the configuration from a YAML file.
Args:
config_path (str): Path to the YAML configuration file.
Returns:
dict: Dictionary of configuration options.
"""
with open(config_path, 'r') as file:
config = yaml.safe_load(file)
required_keys = ['output_dir', 'compression', 'resize_factor', 'project', 'ilastik']
for key in required_keys:
if key not in config:
raise ValueError(f"Missing required configuration key: {key}")
return config
def process_czi_file(file_path, config):
"""
Processes a CZI file, assembles mosaics, analyzes them with Ilastik, and resizes outputs.
Each series within the CZI file is processed sequentially.
Args:
file_path (str): Path to the CZI file to process.
config (dict): Configuration options loaded from YAML.
"""
print(f"Processing file: {file_path}")
# Load the CZI file
czi = CziFile(file_path)
dims = czi.dims # Example: "HSCMYXA"
x_idx = dims.index('X')
y_idx = dims.index('Y')
num_series = len(czi.get_dims_shape())
print(f"Number of detected series (S): {num_series}")
for s in range(num_series):
print(f" Processing series {s}")
# Retrieve information about the tiles for this series
tiles_nb = len(czi.get_all_mosaic_tile_bounding_boxes(S=s, C=0, A=0, H=0))
print(f" Number of tiles for S={s}: {tiles_nb}")
# Initialize the global dimensions of the mosaic
max_x, max_y = 0, 0
tiles = []
# Iterate through the tiles and retrieve their data
for m in range(tiles_nb):
# Read the tile
tile, _ = czi.read_image(S=s, C=0, M=m, A=0, H=0)
# Retrieve the tile's coordinates and dimensions
tile_boundings = czi.get_mosaic_tile_bounding_box(S=s, C=0, M=m, A=0, H=0)
# Use the first tile's coordinates to adjust the offsets
if m == 0:
shift_x = tile_boundings.x
shift_y = tile_boundings.y
# Compute the corrected positions
pos_x = tile_boundings.x - shift_x
pos_y = tile_boundings.y - shift_y
width, height = tile_boundings.w, tile_boundings.h
# Add the tile data to the list
tiles.append((tile, pos_x, pos_y, width, height))
# Update the global dimensions
max_x = max(max_x, pos_x + width)
max_y = max(max_y, pos_y + height)
# Create an empty image for the final mosaic
mosaic_image = np.zeros((max_y, max_x), dtype=tiles[0][0].dtype)
# Assemble the tiles into the final image
for tile, pos_x, pos_y, width, height in tiles:
slices = [slice(None)] * len(dims)
slices[x_idx] = slice(0, width)
slices[y_idx] = slice(0, height)
mosaic_image[pos_y:pos_y + height, pos_x:pos_x + width] = tile[0, 0, 0, 0, :, :, 1]
# Save the assembled mosaic as a TIFF with a series suffix
output_filename = os.path.join(config['output_dir'], f"{os.path.basename(file_path)}_series_{s}.tif")
tiff.imwrite(output_filename, mosaic_image, compression=config['compression'])
print(f"Generated TIFF for series {s}: {output_filename}")
# Process the generated TIFF with Ilastik
segmented_path = run_ilastik(output_filename, config['project'], config['ilastik'])
# Resize the original TIFF and the segmented output
resize_image(output_filename, config['resize_factor'], cv2.INTER_AREA)
resize_image(segmented_path, config['resize_factor'], cv2.INTER_NEAREST_EXACT)
# Remove the intermediate TIFF
os.remove(output_filename)
print(f"Deleted intermediate TIFF: {output_filename}")
def run_ilastik(input_path, project_path, ilastik_path, output_format="tif", export_source="Simple Segmentation"):
"""
Runs Ilastik in headless mode on the given input file.
"""
output_filename_format = os.path.join(
os.path.dirname(input_path), f"{os.path.splitext(os.path.basename(input_path))[0]}_segmented"
)
cmd = [
ilastik_path,
"--headless",
f"--project={project_path}",
f"--output_format={output_format}",
f"--export_source={export_source}",
f"--output_filename_format={output_filename_format}",
f"--raw-data={input_path}"
]
try:
subprocess.run(cmd, check=True)
segmented_path = f"{output_filename_format}.{output_format}"
print(f"Processed: {input_path} -> {segmented_path}")
return segmented_path
except subprocess.CalledProcessError as e:
print(f"Error processing: {input_path}")
raise e
def resize_image(image_path, scale_factor, interpolation):
"""
Resizes an image using OpenCV.
"""
image = tiff.imread(image_path)
if image is None:
raise FileNotFoundError(f"Could not read image: {image_path}")
new_size = (int(image.shape[1] * scale_factor), int(image.shape[0] * scale_factor))
resized_image = cv2.resize(image, new_size, interpolation=interpolation)
# Save resized image
file_dir, file_name = os.path.split(image_path)
file_base, file_ext = os.path.splitext(file_name)
resized_path = os.path.join(file_dir, f"{file_base}_resized{file_ext}")
cv2.imwrite(resized_path, resized_image)
print(f"Saved resized image: {resized_path}")
return resized_path
def main():
parser = argparse.ArgumentParser(description="Automate CZI to Ilastik processing pipeline.")
parser.add_argument("files", nargs='+', help="List of CZI files to process.")
parser.add_argument("--config", required=True, help="Path to the YAML configuration file.")
args = parser.parse_args()
# Load configuration
config = load_config(args.config)
# Ensure output directory exists
if not os.path.isdir(config['output_dir']):
os.makedirs(config['output_dir'])
# Process each file through the pipeline
for file_path in args.files:
if not os.path.isfile(file_path):
print(f"File not found: {file_path}")
continue
process_czi_file(file_path, config)
if __name__ == "__main__":
main()
......@@ -5,4 +5,7 @@ czifile==2019.7.2
numpy==2.1.3
opencv-python-headless==4.10.0.84
pillow==11.0.0
pip-autoremove==0.10.0
PyYAML==6.0.2
setuptools==75.6.0
tifffile==2024.9.20
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment