From 92b8ebe3960b1e021e71166491c3d7076c8f0caf Mon Sep 17 00:00:00 2001 From: Guillaume Cerutti <guillaume.cerutti@inria.fr> Date: Wed, 6 Mar 2024 11:46:02 +0100 Subject: [PATCH] add command line script --- script/nuclei_quantification.py | 141 ++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 script/nuclei_quantification.py diff --git a/script/nuclei_quantification.py b/script/nuclei_quantification.py new file mode 100644 index 0000000..0f6bc41 --- /dev/null +++ b/script/nuclei_quantification.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import logging + +import numpy as np +import pandas as pd + +import matplotlib.pyplot as plt + +from timagetk import SpatialImage +from timagetk.io import imread +from timagetk.algorithms.peak_detection import detect_nuclei +from timagetk.algorithms.signal_quantification import quantify_nuclei_signal_intensity + + +if __name__ == "__main__": + logging.getLogger().setLevel(logging.INFO) + + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--dirname', help='Path to the data directory', default="../data/") + parser.add_argument('-f', '--filename', help='Image name identifier', required=True) + parser.add_argument('-c', '--channel-names', help='List of channel names in the same order as in the microscopy image', nargs='+', type=str) + parser.add_argument('-w', '--channel-weights', help='List of weights associated to each channel to compute the detection image', nargs='+', type=float) + parser.add_argument('-q', '--quantification-channels', help='List of channel names on which to quantify nuclei intensities', nargs='+', type=str) + + parser.add_argument('-t', '--detection-threshold', default=100, help="Threshold used for nuclei detection", type=float) + parser.add_argument('-rmin', '--detection-radius-min', default=0.8, help="Min radius used for signal quantification", type=float) + parser.add_argument('-rmax', '--detection-radius-max', default=3, help="Max radius used for signal quantification", type=float) + parser.add_argument('-r', '--quantification-radius', default=2.5, help="Radius used for signal quantification", type=float) + args = parser.parse_args() + + dirname = args.dirname # "../data/" + filename = args.filename # "230315_GK_86_9_T1_5" + channel_names = args.channel_names + + image_filename = f"{dirname}/{filename}.tif" + img = imread(image_filename, channel_names=channel_names) + channel_names = img.channel_names + + logging.info(f"--> Reading image {image_filename}") + if args.quantification_channels is not None: + quantification_channels = args.quantification_channels + assert all([c in channel_names for c in quantification_channels]) + else: + quantification_channels = channel_names + + detection_threshold = args.detection_threshold # 100 + detection_radius_range = (args.detection_radius_min, args.detection_radius_max) # (0.8, 3) + quantification_radius = args.quantification_radius # 2.5 + + logging.info(f"--> Computing merged image for detection") + if args.channel_weights is not None: + channel_weights = args.channel_weights + assert len(channel_weights) == len(channel_names) + else: + channel_weights = [1. for c in channel_names] + + nuclei_img = SpatialImage( + np.sum([ + w*img[c].get_array() + for c, w in zip(channel_names, channel_weights) + ], axis=0), + voxelsize=img.voxelsize + ) + + logging.info(f"--> Detecting nuclei") + nuclei_points = detect_nuclei( + nuclei_img, + threshold=detection_threshold, + radius_range=detection_radius_range + ) + + logging.info(f"--> Quantifying nuclei signals for {quantification_channels}") + nuclei_signals = { + signal_name: quantify_nuclei_signal_intensity( + img[signal_name], + nuclei_points, + nuclei_sigma=quantification_radius + ) + for signal_name in quantification_channels + } + + logging.info(f"--> Displaying results") + figure = plt.figure(figsize=(10*len(quantification_channels), 10)) + + extent = ( + -img.voxelsize[2]/2, + img.extent[2]+img.voxelsize[2]/2, + img.extent[1]+img.voxelsize[1]/2, + -img.voxelsize[1]/2, + ) + + for i, signal_name in enumerate(quantification_channels): + figure.add_subplot(1, 2, i+1) + figure.gca().imshow( + img[signal_name].get_array().max(axis=0), + extent=extent, + ) + + figure.gca().scatter( + nuclei_points[:, 0], + nuclei_points[:, 1], + c=nuclei_signals[signal_name], + cmap='Reds' + ) + + figure.gca().set_title(f"{signal_name} nuclei signal intensity", size=18) + + figure.tight_layout() + figure_filename = f"{dirname}/{filename}_nuclei_quantification.png" + figure.savefig(figure_filename) + + figure = plt.figure(figsize=(10, 10)) + + figure.gca().scatter( + nuclei_signals[quantification_channels[0]], + nuclei_signals[quantification_channels[1]], + c=nuclei_points[:, 1], + cmap="Reds", + ) + figure.gca().set_xlabel(quantification_channels[0], size=18) + figure.gca().set_ylabel(quantification_channels[1], size=18) + + figure.tight_layout() + figure_filename = f"{dirname}/{filename}_nuclei_{quantification_channels[0]}_{quantification_channels[1]}_signals.png" + figure.savefig(figure_filename) + + nuclei_data = { + 'id': range(len(nuclei_points)), + } + nuclei_data.update({ + f'center_{dim}': nuclei_points[:, i] + for i, dim in enumerate('xyz') + }) + nuclei_data.update(nuclei_signals) + + nuclei_df = pd.DataFrame(nuclei_data) + nuclei_filename = f"{dirname}/{filename}_nuclei_data.csv" + nuclei_df.to_csv(nuclei_filename, index=False) -- GitLab