Source code for unravel.image_io.img_to_points

#!/usr/bin/env python3

"""
Use `io_img_to_points` from UNRAVEL to convert non-zero voxels in a 3D image to a set of points, accounting for voxel intensity (e.g., number of detections).

Input image types:
    - .czi, .nii.gz, .ome.tif series, .tif series, .h5, .zarr

Output:
    - A CSV file where each row represents a point corresponding to a detection in the 3D image. 
    - The coordinates (x, y, z) are derived from the voxel locations in the image, with multiple points generated for voxels with intensities greater than 1.
    - An optional 'Region_ID' column is added based on the corresponding atlas image.

Usage: 
------
    io_img_to_points -i path/image [-a path/atlas_image.nii.gz] [-o path/points.csv] [-v]
"""

from pathlib import Path
import numpy as np
import pandas as pd
from rich import print
from rich.traceback import install

from unravel.core.help_formatter import RichArgumentParser, SuppressMetavar, SM

from unravel.core.config import Configuration
from unravel.core.img_io import load_3D_img
from unravel.core.utils import log_command, verbose_start_msg, verbose_end_msg

[docs] def parse_args(): parser = RichArgumentParser(formatter_class=SuppressMetavar, add_help=False, docstring=__doc__) reqs = parser.add_argument_group('Required arguments') reqs.add_argument('-i', '--input', help='Path to the input 3D image.', required=True, action=SM) opts = parser.add_argument_group('Optional arguments') opts.add_argument('-a', '--atlas_img', help="Path to the atlas image matching -i for adding a 'Region_ID' column to the CSV.", action=SM) opts.add_argument('-o', '--output', help='Path to save the output points (CSV format). Default: path/input_points.csv', default=None, action=SM) general = parser.add_argument_group('General arguments') general.add_argument('-v', '--verbose', help='Increase verbosity. Default: False', action='store_true', default=False) return parser.parse_args()
[docs] def img_to_points(img, atlas_img=None): """ Converts non-zero voxel coordinates in a 3D ndarray to a list of points, accounting for voxel intensity. Optionally adds a 'Region_ID' based on the corresponding atlas image. Parameters: ----------- img : numpy.ndarray The 3D array representing the image data, where voxel intensities > 0 indicate the number of detections. atlas_img : numpy.ndarray, optional The 3D array representing the atlas image data. If provided, a 'Region_ID' column will be added to the output points. Returns: -------- points : numpy.ndarray An array of points where each row corresponds to the (x, y, z) coordinates of a detection. If atlas_img is provided, each point will have an additional 'Region_ID' column. Note: ----- Convert the points to a DataFrame using: >>> points_df = pd.DataFrame(points_ndarray, columns=['x', 'y', 'z']) or >>> points_df = pd.DataFrame(points_ndarray, columns=['x', 'y', 'z', 'Region_ID']) # With atlas_img """ points = [] # Find the coordinates of non-zero voxels coords = np.argwhere(img > 0) for coord in coords: point_intensity = int(img[tuple(coord)]) # Create multiple points for the same coordinate based on intensity for _ in range(point_intensity): if atlas_img is not None: region_id = int(atlas_img[tuple(coord)]) # Look up Region_ID in the atlas image points.append(np.append(coord, region_id)) # Append coordinate and Region_ID else: points.append(coord) return np.array(points)
[docs] @log_command def main(): install() args = parse_args() Configuration.verbose = args.verbose verbose_start_msg() img = load_3D_img(args.input) atlas_img = load_3D_img(args.atlas_img) if args.atlas_img else None # Convert the image to points points_ndarray = img_to_points(img, atlas_img=atlas_img) if int(points_ndarray.shape[1]) == 3: points_df = pd.DataFrame(points_ndarray, columns=['x', 'y', 'z']) elif int(points_ndarray.shape[1]) == 4: points_df = pd.DataFrame(points_ndarray, columns=['x', 'y', 'z', 'Region_ID']) # Save the points to a CSV file if args.output: csv_output_path = Path(args.output) csv_output_path.parent.mkdir(parents=True, exist_ok=True) else: csv_output_path = args.input.replace('.nii.gz', '_points.csv') points_df.to_csv(csv_output_path, index=False) print(f"\n Points saved to {csv_output_path}\n") verbose_end_msg()
if __name__ == "__main__": main()