diff --git a/.gitignore b/.gitignore index 4df6b687cc41473a27d6adcc788b5b6167d3b94b..8c2731a39d9cb4c88fc8d53eb266fe6c658daf80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +dmg.py # dev environment .vscode diff --git a/PyNutil/io/file_operations.py b/PyNutil/io/file_operations.py index f4d08afa32b93bf4ec43929a7b2b6960ab046d46..8e4656f4da6146b8827891f43291d736fb7e56cb 100644 --- a/PyNutil/io/file_operations.py +++ b/PyNutil/io/file_operations.py @@ -54,7 +54,9 @@ def save_analysis_output( os.makedirs(f"{output_folder}/per_section_meshview", exist_ok=True) os.makedirs(f"{output_folder}/per_section_reports", exist_ok=True) os.makedirs(f"{output_folder}/whole_series_meshview", exist_ok=True) - + # Filter out rows where 'region_area' is 0 in label_df + if label_df is not None and "region_area" in label_df.columns: + label_df = label_df[label_df["region_area"] != 0] if label_df is not None: label_df.to_csv( f"{output_folder}/whole_series_report/{prepend}counts.csv", diff --git a/PyNutil/io/read_and_write.py b/PyNutil/io/read_and_write.py index 3e77f70fc9a3df0fcf8a1c2e1ddd71bd7dc51d23..c4c5e228fd8d1177908cf8d72b573e5c065c7d6f 100644 --- a/PyNutil/io/read_and_write.py +++ b/PyNutil/io/read_and_write.py @@ -162,20 +162,12 @@ def load_visualign_json(filename): slice["nr"] = int(re.search(r"_s(\d+)", slice["filename"]).group(1)) if "ouv" in slice: slice["anchoring"] = slice["ouv"] - - name = os.path.basename(filename) - lz_compat_file = { - "name": name, - "target": vafile["atlas"], - "target-resolution": [456, 528, 320], - "slices": slices, - } - else: slices = vafile["slices"] if len(slices) > 1: slices = propagate(slices) - return slices + gridspacing = vafile["gridspacing"] if "gridspacing" in vafile else None + return slices, gridspacing # related to read_and_write, used in write_points_to_meshview @@ -278,19 +270,6 @@ def flat_to_array(file, labelfile): return allen_id_image -def label_to_array(label_path, image_array): - """assign label file values into image array, return array""" - labelfile = pd.read_csv(label_path) - allen_id_image = np.zeros((h, w)) # create an empty image array - coordsy, coordsx = np.meshgrid(list(range(w)), list(range(h))) - values = image_array[ - coordsx, coordsy - ] # assign x,y coords from image_array into values - lbidx = labelfile["idx"].values - allen_id_image = lbidx[values.astype(int)] # assign allen IDs into image array - return allen_id_image - - def files_in_directory(directory): """return list of flat file names in a directory""" list_of_files = [] diff --git a/PyNutil/main.py b/PyNutil/main.py index df2f0af73de386668871280e8c3f8426d3317ad3..c28a63c54c679be458e581b2e7bc7cf1e20a2909 100644 --- a/PyNutil/main.py +++ b/PyNutil/main.py @@ -4,7 +4,6 @@ from .processing.data_analysis import quantify_labeled_points, map_to_custom_reg from .io.file_operations import save_analysis_output from .io.read_and_write import open_custom_region_file from .processing.coordinate_extraction import folder_to_atlas_space -import numpy as np @@ -153,6 +152,8 @@ class PyNutil: self.points_len, self.centroids_len, self.segmentation_filenames, + self.per_point_undamaged, + self.per_centroid_undamaged ) = folder_to_atlas_space( self.segmentation_folder, self.alignment_json, @@ -202,6 +203,8 @@ class PyNutil: self.points_labels, self.centroids_labels, self.atlas_labels, + self.per_point_undamaged, + self.per_centroid_undamaged ) if self.custom_regions_dict is not None: self.custom_label_df, self.label_df = apply_custom_regions( diff --git a/PyNutil/processing/coordinate_extraction.py b/PyNutil/processing/coordinate_extraction.py index b9fbe516e47b88297367e6a20ce69012a9445e47..210be15890bbb1ed3dcd08fa67956fb8ca5f6c69 100644 --- a/PyNutil/processing/coordinate_extraction.py +++ b/PyNutil/processing/coordinate_extraction.py @@ -1,7 +1,7 @@ import numpy as np import pandas as pd from ..io.read_and_write import load_visualign_json -from .counting_and_load import flat_to_dataframe, rescale_image +from .counting_and_load import flat_to_dataframe, rescale_image, load_image from .visualign_deformations import triangulate from glob import glob import cv2 @@ -44,75 +44,37 @@ def get_centroids_and_area(segmentation, pixel_cut_off=0): coords = np.array([label.coords for label in labels_info], dtype=object) return centroids, area, coords +def update_spacing(anchoring, width, height, grid_spacing): + if len(anchoring) != 9: + print("Anchoring does not have 9 elements.") + ow = np.sqrt(sum([anchoring[i+3] ** 2 for i in range(3)])) + oh = np.sqrt(sum([anchoring[i+6] ** 2 for i in range(3)])) + xspacing = int(width * grid_spacing / ow) + yspacing = int(height * grid_spacing / oh) + return xspacing, yspacing -def create_threads( - segmentations, - slices, - flat_files, - flat_file_nrs, - atlas_labels, - pixel_id, - non_linear, - points_list, - centroids_list, - region_areas_list, - object_cutoff, - atlas_volume, - use_flat, -): - """ - Creates threads for processing segmentations. +def create_damage_mask(section, grid_spacing): + width = section["width"] + height = section["height"] + anchoring = section["anchoring"] + grid_values = section["grid"] + gridx = section["gridx"] + gridy = section["gridy"] - Args: - segmentations (list): List of segmentation files. - slices (list): List of slices. - flat_files (list): List of flat files. - flat_file_nrs (list): List of flat file section numbers. - atlas_labels (DataFrame): DataFrame with atlas labels. - pixel_id (list): Pixel ID to match. - non_linear (bool): Whether to use non-linear transformation. - points_list (list): List to store points. - centroids_list (list): List to store centroids. - region_areas_list (list): List to store region areas. - object_cutoff (int): Pixel cutoff to remove small objects. - atlas_volume (ndarray): Volume with atlas labels. - use_flat (bool): Whether to use flat files. + xspacing, yspacing = update_spacing(anchoring, width, height, grid_spacing) + x_coords = np.arange(gridx, width, xspacing) + y_coords = np.arange(gridy, height, yspacing) - Returns: - list: List of threads. - """ - threads = [] - for segmentation_path, index in zip(segmentations, range(len(segmentations))): - seg_nr = int(number_sections([segmentation_path])[0]) - current_slice_index = np.where([s["nr"] == seg_nr for s in slices]) - current_slice = slices[current_slice_index[0][0]] - if current_slice["anchoring"] == []: - continue - current_flat = get_current_flat_file( - seg_nr, flat_files, flat_file_nrs, use_flat - ) + num_markers = len(grid_values) + markers = [(x_coords[i % len(x_coords)], y_coords[i // len(x_coords)]) for i in range(num_markers)] - x = threading.Thread( - target=segmentation_to_atlas_space, - args=( - current_slice, - segmentation_path, - atlas_labels, - current_flat, - pixel_id, - non_linear, - points_list, - centroids_list, - region_areas_list, - index, - object_cutoff, - atlas_volume, - use_flat, - ), - ) - threads.append(x) - return threads + binary_image = np.ones((len(y_coords), len(x_coords)), dtype=int) + for i, (x, y) in enumerate(markers): + if grid_values[i] == 4: + binary_image[y // yspacing, x // xspacing] = 0 + + return binary_image def folder_to_atlas_space( folder, @@ -140,10 +102,10 @@ def folder_to_atlas_space( Returns: tuple: Points, centroids, region areas list, points length, centroids length, segmentations. """ - slices = load_visualign_json(quint_alignment) + slices, gridspacing = load_visualign_json(quint_alignment) segmentations = get_segmentations(folder) flat_files, flat_file_nrs = get_flat_files(folder, use_flat) - points_list, centroids_list, region_areas_list, centroids_labels, points_labels = ( + points_list, centroids_list, region_areas_list, centroids_labels, points_labels,per_point_undamaged_list,per_centroid_undamaged_list = ( initialize_lists(len(segmentations)) ) threads = create_threads( @@ -159,13 +121,16 @@ def folder_to_atlas_space( centroids_labels, points_labels, region_areas_list, + per_point_undamaged_list, + per_centroid_undamaged_list, object_cutoff, atlas_volume, use_flat, + gridspacing ) start_and_join_threads(threads) - points, centroids, points_labels, centroids_labels, points_len, centroids_len = ( - process_results(points_list, centroids_list, points_labels, centroids_labels) + points, centroids, points_labels, centroids_labels, points_len, centroids_len, per_point_undamaged_list, per_centroid_undamaged_list = ( + process_results(points_list, centroids_list, points_labels, centroids_labels, per_point_undamaged_list, per_centroid_undamaged_list) ) return ( points, @@ -176,6 +141,8 @@ def folder_to_atlas_space( points_len, centroids_len, segmentations, + per_point_undamaged_list, + per_centroid_undamaged_list ) @@ -193,7 +160,8 @@ def initialize_lists(length): centroids_list = [np.array([])] * length centroids_labels = [np.array([])] * length points_labels = [np.array([])] * length - + per_point_undamaged_list = [np.array([])] * length + per_centroid_undamaged_list = [np.array([])] * length region_areas_list = [ pd.DataFrame( { @@ -215,6 +183,8 @@ def initialize_lists(length): region_areas_list, centroids_labels, points_labels, + per_point_undamaged_list, + per_centroid_undamaged_list ) @@ -231,9 +201,12 @@ def create_threads( centroids_labels, points_labels, region_areas_list, + per_point_undamaged_list, + per_centroid_undamaged_list, object_cutoff, atlas_volume, use_flat, + gridspacing ): """ Creates threads for processing segmentations. @@ -283,10 +256,13 @@ def create_threads( points_labels, centroids_labels, region_areas_list, + per_point_undamaged_list, + per_centroid_undamaged_list, index, object_cutoff, atlas_volume, use_flat, + gridspacing ), ) threads.append(x) @@ -334,6 +310,7 @@ def get_region_areas( slice_dict, atlas_volume, triangulation, + damage_mask ): """ Gets the region areas. @@ -351,19 +328,11 @@ def get_region_areas( Returns: DataFrame: DataFrame with region areas. """ - if use_flat: - region_areas, atlas_map = flat_to_dataframe( - atlas_labels, flat_file_atlas, (seg_width, seg_height) - ) - else: - region_areas, atlas_map = flat_to_dataframe( - atlas_labels, - flat_file_atlas, - (seg_width, seg_height), - slice_dict["anchoring"], - atlas_volume, - triangulation, - ) + atlas_map = load_image(flat_file_atlas,slice_dict["anchoring"], atlas_volume, triangulation, (seg_width, seg_height), atlas_labels) + + region_areas = flat_to_dataframe( + atlas_map, damage_mask, (seg_width, seg_height) + ) return region_areas, atlas_map @@ -379,10 +348,13 @@ def segmentation_to_atlas_space( points_labels=None, centroids_labels=None, region_areas_list=None, + per_point_undamaged_list=None, + per_centroid_undamaged_list=None, index=None, object_cutoff=0, atlas_volume=None, use_flat=False, + grid_spacing=None, ): """ Converts a segmentation to atlas space. @@ -408,6 +380,10 @@ def segmentation_to_atlas_space( seg_height, seg_width = segmentation.shape[:2] reg_height, reg_width = slice_dict["height"], slice_dict["width"] triangulation = get_triangulation(slice_dict, reg_width, reg_height, non_linear) + if "grid" in slice_dict: + damage_mask = create_damage_mask(slice_dict, grid_spacing) + else: + damage_mask = None region_areas, atlas_map = get_region_areas( use_flat, atlas_labels, @@ -417,6 +393,7 @@ def segmentation_to_atlas_space( slice_dict, atlas_volume, triangulation, + damage_mask ) atlas_map = rescale_image(atlas_map, (reg_width, reg_height)) y_scale, x_scale = transform_to_registration( @@ -434,15 +411,26 @@ def segmentation_to_atlas_space( per_centroid_labels = atlas_map[ np.round(scaled_centroidsX).astype(int), np.round(scaled_centroidsY).astype(int) ] - # per_point_region = + if damage_mask is not None: + damage_mask = cv2.resize(damage_mask.astype(np.uint8), (atlas_map.shape[::-1]), interpolation=cv2.INTER_NEAREST).astype(bool) + per_point_undamaged = damage_mask[ + np.round(scaled_x).astype(int), np.round(scaled_y).astype(int) + ] + per_centroid_undamaged = damage_mask[ + np.round(scaled_centroidsX).astype(int), np.round(scaled_centroidsY).astype(int) + ] + else: + per_point_undamaged = np.ones(scaled_x.shape, dtype=bool) + per_centroid_undamaged = np.ones(scaled_centroidsX.shape, dtype=bool) + per_point_labels = per_point_labels[per_point_undamaged] + per_centroid_labels = per_centroid_labels[per_centroid_undamaged] new_x, new_y, centroids_new_x, centroids_new_y = get_transformed_coordinates( non_linear, slice_dict, - scaled_x, - scaled_y, - centroids, - scaled_centroidsX, - scaled_centroidsY, + scaled_x[per_point_undamaged], + scaled_y[per_point_undamaged], + scaled_centroidsX[per_centroid_undamaged], + scaled_centroidsY[per_centroid_undamaged], triangulation, ) points, centroids = transform_points_to_atlas_space( @@ -460,7 +448,11 @@ def segmentation_to_atlas_space( centroids_labels[index] = np.array( per_centroid_labels if centroids is not None else [] ) + per_centroid_undamaged_list[index] = np.array( + per_centroid_undamaged if centroids is not None else [] + ) points_labels[index] = np.array(per_point_labels if points is not None else []) + per_point_undamaged_list[index] = np.array(per_point_undamaged if points is not None else []) def get_triangulation(slice_dict, reg_width, reg_height, non_linear): diff --git a/PyNutil/processing/counting_and_load.py b/PyNutil/processing/counting_and_load.py index 6641c642cb676599e5a9476bb1129c89a63130f4..c52c45abf25e26be800770216b3b45e9e33fd9d7 100644 --- a/PyNutil/processing/counting_and_load.py +++ b/PyNutil/processing/counting_and_load.py @@ -50,7 +50,7 @@ def label_points(points, label_volume, scale_factor=1): # related to counting_and_load def pixel_count_per_region( - labels_dict_points, labeled_dict_centroids, df_label_colours + labels_dict_points, labeled_dict_centroids, current_points_undamaged, current_centroids_undamaged, df_label_colours ): """ Counts the number of pixels per region and writes to a DataFrame. @@ -63,47 +63,91 @@ def pixel_count_per_region( Returns: DataFrame: DataFrame with counts and colours per region. """ - counted_labels_points, label_counts_points = np.unique( - labels_dict_points, return_counts=True + counted_labels_points_undamaged, label_counts_points_undamaged = np.unique( + labels_dict_points[current_points_undamaged], return_counts=True ) - counted_labels_centroids, label_counts_centroids = np.unique( - labeled_dict_centroids, return_counts=True + counted_labels_points_damaged, label_counts_points_damaged = np.unique( + labels_dict_points[~current_points_undamaged], return_counts=True ) - # Which regions have pixels, and how many pixels are there per region - dummy_column = np.zeros(len(counted_labels_points)) - counts_per_label = list( - zip(counted_labels_points, label_counts_points, dummy_column) + counted_labels_centroids_undamaged, label_counts_centroids_undamaged = np.unique( + labeled_dict_centroids[current_centroids_undamaged], return_counts=True + ) + counted_labels_centroids_damaged, label_counts_centroids_damaged = np.unique( + labeled_dict_centroids[~current_centroids_undamaged], return_counts=True ) + # Which regions have pixels, and how many pixels are there per region # Create a list of unique regions and pixel counts per region + counts_per_label = { + "idx" : [], + "name" : [], + "r" : [], + "g" : [], + "b" : [], + "pixel_count" : [], + "undamaged_pixel_count" : [], + "damaged_pixel_counts" : [], + "object_count" : [], + "undamaged_object_count" : [], + "damaged_object_count" : [], + } + for index, row in df_label_colours.iterrows(): + if row["idx"] in counted_labels_points_undamaged: + clpu = label_counts_points_undamaged[counted_labels_points_undamaged == row["idx"]][0] + else: + clpu = 0 + if row["idx"] in counted_labels_points_damaged: + clpd = label_counts_points_damaged[counted_labels_points_damaged == row["idx"]][0] + else: + clpd = 0 + if row["idx"] in counted_labels_centroids_undamaged: + clcu = counted_labels_centroids_undamaged[counted_labels_centroids_undamaged == row["idx"]][0] + else: + clcu = 0 + if row["idx"] in counted_labels_centroids_damaged: + clcd = counted_labels_centroids_damaged[counted_labels_centroids_damaged == row["idx"]][0] + else: + clcd = 0 + if clcd==clcu==clpd==clpu==0: + continue + + counts_per_label["idx"].append( + row["idx"] + ) + counts_per_label["name"].append( + row["name"] + ) + counts_per_label["r"].append( + int(row["r"]) + ) + counts_per_label["g"].append( + int(row["g"]) + ) + counts_per_label["b"].append( + int(row["b"]) + ) + counts_per_label["pixel_count"].append( + clpu + clpd + ) + counts_per_label["undamaged_pixel_count"].append( + clpu + ) + counts_per_label["damaged_pixel_counts"].append( + clpd + ) + counts_per_label["object_count"].append( + clcu + clcd + ) + counts_per_label["undamaged_object_count"].append( + clcu + ) + counts_per_label["damaged_object_count"].append( + clcd + ) + df_counts_per_label = pd.DataFrame( - counts_per_label, columns=["idx", "pixel_count", "object_count"] + counts_per_label ) - for clc, lcc in zip(counted_labels_centroids, label_counts_centroids): - df_counts_per_label.loc[df_counts_per_label["idx"] == clc, "object_count"] = lcc - new_rows = [] - for index, row in df_counts_per_label.iterrows(): - mask = df_label_colours["idx"] == row["idx"].astype(int) - current_region_row = df_label_colours[mask] - current_region_name = current_region_row["name"].values - current_region_red = current_region_row["r"].values - current_region_green = current_region_row["g"].values - current_region_blue = current_region_row["b"].values - row["name"] = current_region_name[0] - row["r"] = int(current_region_red[0]) - row["g"] = int(current_region_green[0]) - row["b"] = int(current_region_blue[0]) - new_rows.append(row) - - df_counts_per_label_name = pd.DataFrame( - new_rows, columns=["idx", "name", "pixel_count", "object_count", "r", "g", "b"] - ) - # Task for Sharon: - # If you can get the areas per region from the flat file here - # you can then use those areas to calculate the load per region here - # and add to dataframe - # see messing around pyflat.py - - return df_counts_per_label_name + return df_counts_per_label """Read flat file and write into an np array""" @@ -269,13 +313,9 @@ def warp_image(image, triangulation, rescaleXY): def flat_to_dataframe( - labelfile, - file=None, - rescaleXY=None, - image_vector=None, - volume=None, - triangulation=None, -): + image, + damage_mask, + rescaleXY=None): """ Converts a flat file to a DataFrame. @@ -291,18 +331,24 @@ def flat_to_dataframe( DataFrame: DataFrame with area per label. np.array: array in shape of alignment XY scaled by rescaleXY with allen ID for each point """ - image = load_image(file, image_vector, volume, triangulation, rescaleXY) scale_factor = calculate_scale_factor(image, rescaleXY) - allen_id_image = ( - assign_labels_to_image(image, labelfile) - if (image_vector is None or volume is None) - else image - ) - df_area_per_label = count_pixels_per_label(allen_id_image, scale_factor) - return df_area_per_label, allen_id_image + if damage_mask is not None: + damage_mask = cv2.resize(damage_mask.astype(np.uint8), (image.shape[::-1]), interpolation=cv2.INTER_NEAREST).astype(bool) + undamaged_df_area_per_label = count_pixels_per_label(image[damage_mask], scale_factor) + damaged_df_area_per_label = count_pixels_per_label(image[~damage_mask], scale_factor) + undamaged_df_area_per_label = undamaged_df_area_per_label.rename(columns={"region_area": "undamaged_region_area"}) + damaged_df_area_per_label = damaged_df_area_per_label.rename(columns={"region_area": "damaged_region_area"}) + df_area_per_label = pd.merge(undamaged_df_area_per_label, damaged_df_area_per_label, on='idx', how='outer').fillna(0) + df_area_per_label["region_area"] = df_area_per_label["undamaged_region_area"] + df_area_per_label["damaged_region_area"] + else: + df_area_per_label = count_pixels_per_label(image, scale_factor) + df_area_per_label["undamaged_region_area"] = df_area_per_label["region_area"] + df_area_per_label["damaged_region_area"] = 0 + + return df_area_per_label -def load_image(file, image_vector, volume, triangulation, rescaleXY): +def load_image(file, image_vector, volume, triangulation, rescaleXY, labelfile=None): """ Loads an image from a file or generates it from a vector and volume. @@ -321,10 +367,13 @@ def load_image(file, image_vector, volume, triangulation, rescaleXY): image = np.float64(image) if triangulation is not None: image = warp_image(image, triangulation, rescaleXY) - elif file.endswith(".flat"): - image = read_flat_file(file) - elif file.endswith(".seg"): - image = read_seg_file(file) + else: + if file.endswith(".flat"): + image = read_flat_file(file) + if file.endswith(".seg"): + image = read_seg_file(file) + image = assign_labels_to_image(image, labelfile) + return image diff --git a/PyNutil/processing/data_analysis.py b/PyNutil/processing/data_analysis.py index cd7c3e2453ef921474a3459e5663f1a11a940b5b..70ab405ee56294199e2cbc47a2fb54301e7bb451 100644 --- a/PyNutil/processing/data_analysis.py +++ b/PyNutil/processing/data_analysis.py @@ -36,7 +36,6 @@ def apply_custom_regions(df, custom_regions_dict): # Update the original df with new columns df["custom_region_name"] = df["idx"].map(name_mapping).fillna("") temp_df = df.copy() - temp_df["idx"] = temp_df["idx"].map(id_mapping) temp_df["r"] = temp_df["idx"].map( lambda x: rgb_mapping[x][0] if x in rgb_mapping else None @@ -47,6 +46,7 @@ def apply_custom_regions(df, custom_regions_dict): temp_df["b"] = temp_df["idx"].map( lambda x: rgb_mapping[x][2] if x in rgb_mapping else None ) + temp_df["idx"] = temp_df["idx"].map(id_mapping) # Group and aggregate grouped_df = ( @@ -55,8 +55,14 @@ def apply_custom_regions(df, custom_regions_dict): .agg( { "pixel_count": "sum", + "undamaged_pixel_count": "sum", + "damaged_pixel_counts": "sum", "region_area": "sum", + "undamaged_region_area": "sum", + "damaged_region_area": "sum", "object_count": "sum", + "undamaged_object_count": "sum", + "damaged_object_count": "sum", "r": "first", "g": "first", "b": "first", @@ -68,6 +74,7 @@ def apply_custom_regions(df, custom_regions_dict): grouped_df = grouped_df.rename(columns={"custom_region_name": "name"}) grouped_df["area_fraction"] = grouped_df["pixel_count"] / grouped_df["region_area"] + grouped_df["undamaged_area_fraction"] = grouped_df["undamaged_pixel_count"] / grouped_df["undamaged_region_area"] common_columns = [col for col in df.columns if col in grouped_df.columns] grouped_df = grouped_df.reindex( columns=common_columns @@ -82,7 +89,8 @@ def quantify_labeled_points( labeled_points, labeled_points_centroids, atlas_labels, - # atlas_volume, + per_point_undamaged, + per_centroid_undamaged ): """ Quantifies labeled points and returns various DataFrames. @@ -109,6 +117,8 @@ def quantify_labeled_points( centroids_len, region_areas_list, atlas_labels, + per_point_undamaged, + per_centroid_undamaged ) label_df = _combine_slice_reports(per_section_df, atlas_labels) @@ -122,6 +132,8 @@ def _quantify_per_section( centroids_len, region_areas_list, atlas_labels, + per_point_undamaged, + per_centroid_undamaged ): """ Quantifies labeled points per section. @@ -144,8 +156,10 @@ def _quantify_per_section( for pl, cl, ra in zip(points_len, centroids_len, region_areas_list): current_centroids = labeled_points_centroids[prev_cl : prev_cl + cl] current_points = labeled_points[prev_pl : prev_pl + pl] + current_points_undamaged = per_point_undamaged[prev_pl : prev_pl + pl] + current_centroids_undamaged = per_centroid_undamaged[prev_cl : prev_cl + cl] current_df = pixel_count_per_region( - current_points, current_centroids, atlas_labels + current_points, current_centroids, current_points_undamaged, current_centroids_undamaged, atlas_labels ) current_df_new = _merge_dataframes(current_df, ra, atlas_labels) per_section_df.append(current_df_new) @@ -197,6 +211,7 @@ def _combine_slice_reports(per_section_df, atlas_labels): .drop(columns=["area_fraction"]) ) label_df["area_fraction"] = label_df["pixel_count"] / label_df["region_area"] + label_df["undamaged_area_fraction"] = label_df["undamaged_pixel_count"] / label_df["undamaged_region_area"] label_df.fillna(0, inplace=True) label_df = label_df.set_index("idx") diff --git a/PyNutil/processing/transformations.py b/PyNutil/processing/transformations.py index a61fd0cd111b442e2dbcb29f5d40e03a77ece50b..62b8ddb4ebdc8b18ce4fb05c07b45fd65114f0e4 100644 --- a/PyNutil/processing/transformations.py +++ b/PyNutil/processing/transformations.py @@ -52,7 +52,6 @@ def get_transformed_coordinates( slice_dict, scaled_x, scaled_y, - centroids, scaled_centroidsX, scaled_centroidsY, triangulation, @@ -77,7 +76,7 @@ def get_transformed_coordinates( if non_linear and "markers" in slice_dict: if scaled_x is not None: new_x, new_y = transform_vec(triangulation, scaled_x, scaled_y) - if centroids is not None: + if scaled_centroidsX is not None: centroids_new_x, centroids_new_y = transform_vec( triangulation, scaled_centroidsX, scaled_centroidsY ) diff --git a/PyNutil/processing/utils.py b/PyNutil/processing/utils.py index a7162d75d3fc10e5a664fbb45b93d2cba0b75d74..4f3e80065f575f7f115e3786bff4524ea9ea7d5e 100644 --- a/PyNutil/processing/utils.py +++ b/PyNutil/processing/utils.py @@ -198,7 +198,7 @@ def start_and_join_threads(threads): [t.join() for t in threads] -def process_results(points_list, centroids_list, points_labels, centroids_labels): +def process_results(points_list, centroids_list, points_labels, centroids_labels, points_undamaged_list, centroids_undamaged_list): """ Processes the results from the threads. @@ -219,16 +219,24 @@ def process_results(points_list, centroids_list, points_labels, centroids_labels ] points_labels = [pl for pl in points_labels if None not in pl] centroids_labels = [cl for cl in centroids_labels if None not in cl] + points_undamaged_list = [pul for pul in points_undamaged_list if None not in pul] + centroids_undamaged_list = [cul for cul in centroids_undamaged_list if None not in cul] if len(points_list) == 0: points = np.array([]) points_labels = np.array([]) + points_undamaged = np.array([]) else: points = np.concatenate(points_list) points_labels = np.concatenate(points_labels) + points_undamaged = np.concatenate(points_undamaged_list) + if len(centroids_list) == 0: centroids = np.array([]) centroids_labels = np.array([]) + centroids_undamaged = np.array([]) else: centroids = np.concatenate(centroids_list) centroids_labels = np.concatenate(centroids_labels) - return points, centroids, points_labels, centroids_labels, points_len, centroids_len + centroids_undamaged = np.concatenate(centroids_undamaged_list) + + return points, centroids, points_labels, centroids_labels, points_len, centroids_len, points_undamaged, centroids_undamaged diff --git a/README.md b/README.md index b657f424de37d473f4013d2c31d6a3ce95c23478..ee1b7b4a305defa8a37198ca5892f545672025e2 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,13 @@ PyNutil can be run using a custom atlas in .nrrd format (e.g. tests/test_data/Al Pynutil can also be used with the atlases available in the [BrainGlobe_Atlas API](https://github.com/brainglobe/brainglobe-atlasapi). # Installation +## Python package ``` pip install PyNutil ``` +## GUI +download the executable for windows and macOS via the github releases tab + # Usage PyNutil requires Python 3.8 or above. diff --git a/binary.png b/binary.png new file mode 100644 index 0000000000000000000000000000000000000000..d39efaca6a843b463955cb33b1a5313732d8d2da Binary files /dev/null and b/binary.png differ diff --git a/demos/basic_example_custom_atlas.py b/demos/basic_example_custom_atlas.py index c93ba8d561176b5d40ed91555ecc98b81757dfef..d586e12e89840f7a75d53d71243ca5bbdbf715ad 100644 --- a/demos/basic_example_custom_atlas.py +++ b/demos/basic_example_custom_atlas.py @@ -23,7 +23,7 @@ pnt = PyNutil( script_dir, "../tests/test_data/nonlinear_allen_mouse/segmentations/" ), alignment_json=os.path.join( - script_dir, "../tests/test_data/nonlinear_allen_mouse/alignment.json" + script_dir, "../tests/test_data/nonlinear_allen_mouse/damage_markers.json" ), colour=[0, 0, 0], atlas_path=os.path.join( @@ -40,4 +40,4 @@ pnt = PyNutil( ) pnt.get_coordinates(object_cutoff=0, use_flat=False) pnt.quantify_coordinates() -pnt.save_analysis("../test_result/custom_regions_test_12_02_2025") +pnt.save_analysis("../test_result/damage_regions_test_17_03_2025") diff --git a/tests/test_data/nonlinear_allen_mouse/damage_markers.json b/tests/test_data/nonlinear_allen_mouse/damage_markers.json new file mode 100644 index 0000000000000000000000000000000000000000..5c3421264e2db562554def59d0d2b54a614cb747 --- /dev/null +++ b/tests/test_data/nonlinear_allen_mouse/damage_markers.json @@ -0,0 +1,982 @@ +{ + "version10": 8, + "name": "PyNutil_testdataset", + "target": "ABA_Mouse_CCFv3_2017_25um.cutlas", + "target-resolution": [ + 456.0, + 528.0, + 320.0 + ], + "gridspacing": 20, + "collapsed": [], + "slices": [ + { + "filename": "test_s001.png", + "nr": 1, + "width": 1500, + "height": 1000, + "anchoring": [ + -5.145275115966797, + 361.8014440433213, + 331.1490739071843, + 456.0, + 0.0, + 0.0, + 0.0, + 0.0, + -320.0 + ], + "clear": true + }, + { + "filename": "test_s002.png", + "nr": 2, + "width": 1500, + "height": 1000, + "anchoring": [ + -3.8589563369750977, + 318.7157039711191, + 340.24552914037605, + 456.0, + 0.0, + 0.0, + 0.0, + 0.0, + -320.0 + ], + "markers": [ + [ + 636.8098159509204, + 603.4958601655935, + 672.6993865030674, + 593.3762649494021 + ], + [ + 902.9868982011025, + 615.5567336628567, + 843.8650306748466, + 610.8555657773691 + ], + [ + 561.2609204260139, + 750.3661510917975, + 558.5889570552147, + 775.5289788408462 + ] + ], + "gridx": 22, + "gridy": 37, + "grid": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 4.0, + 0.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "clear": false + }, + { + "filename": "test_s003.png", + "nr": 3, + "width": 1500, + "height": 1000, + "anchoring": [ + -2.8942172527313232, + 275.6299638989171, + 350.0189942541106, + 456.0, + 0.0, + 0.0, + 0.0, + 0.0, + -320.0 + ], + "markers": [ + [ + 761.0429447852762, + 629.2548298068077, + 761.0429447852761, + 629.2548298068077 + ], + [ + 204.29447852760745, + 613.6154553817848, + 365.3374233128834, + 612.695492180313 + ], + [ + 482.6376861494953, + 714.9876920193675, + 623.0061349693251, + 747.0101195952162 + ], + [ + 434.00208292806684, + 606.7676930120547, + 578.8343558282207, + 601.6559337626494 + ], + [ + 959.8159509202455, + 636.6145354185834, + 980.0613496932516, + 628.3348666053357 + ], + [ + 957.8161415293836, + 523.3987679117937, + 953.3742331288344, + 547.3781048758049 + ] + ], + "gridx": 39, + "gridy": 27, + "grid": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 0.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 0.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 4.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "clear": false + }, + { + "filename": "test_s004.png", + "nr": 4, + "width": 1500, + "height": 1000, + "anchoring": [ + -3.0282087922096252, + 232.54422382671487, + 364.1366059225139, + 456.0, + 0.0, + 0.0, + 0.0, + 0.0, + -320.0 + ], + "markers": [ + [ + 522.6993865030674, + 712.9714811407543, + 530.6338374725239, + 715.7313707451705 + ], + [ + 630.6438500513034, + 523.1833943062713, + 550.3067484662577, + 519.7792088316469 + ], + [ + 916.4047791164191, + 534.1886045162021, + 962.5766871165645, + 523.4590616375344 + ], + [ + 950.8076359295408, + 705.46368100121, + 949.6932515337423, + 701.0119595216191 + ], + [ + 1096.1208774051677, + 649.3153717520049, + 1099.6932515337426, + 656.8537258509658 + ], + [ + 1030.2378410339393, + 594.2011916220456, + 1065.644171779141, + 604.4158233670653 + ] + ], + "clear": true + }, + { + "filename": "test_s005.png", + "nr": 5, + "width": 1500, + "height": 1000, + "anchoring": [ + -0.6163610816001892, + 189.45848375451277, + 374.2485759765199, + 456.0, + 0.0, + 0.0, + 0.0, + 0.0, + -320.0 + ], + "markers": [ + [ + 6.441717791411037, + 588.7764489420423, + 186.80981595092024, + 425.0229990800368 + ], + [ + 766.3836793259812, + 987.3315412211099, + 834.6625766871166, + 946.6421343146276 + ], + [ + 447.4522362685242, + 740.6361518118404, + 565.9509202453987, + 643.0542778288868 + ], + [ + 392.2943778764543, + 594.4761834437141, + 542.9447852760735, + 478.3808647654094 + ], + [ + 1117.436664514756, + 564.5422630203066, + 788.6503067484659, + 422.2631094756209 + ], + [ + 1045.7025247722827, + 975.3508578153971, + 893.5582822085888, + 873.045078196872 + ] + ], + "clear": true + } + ] +} \ No newline at end of file