From f0aac758ff45d2feadad2cf057b044d7b55f1ee9 Mon Sep 17 00:00:00 2001 From: Micaela Roth Date: Fri, 13 Jun 2025 21:46:20 -0700 Subject: [PATCH] add ves_analysis repo as subfolder --- ves_analysis/LICENSE.md | 21 + ves_analysis/README.md | 71 ++ .../metadata_and_kdtree/kdTreeMeta.py | 202 ++++++ ves_analysis/neuroglancer_heatmap/heatmap.py | 235 +++++++ .../neuroglancer_heatmap/ng_heatmap_vis.py | 168 +++++ .../ng_shader_script.glsl | 41 ++ .../neuroglancer_types_map/types_ng.py | 154 +++++ .../types_visualization.py | 175 +++++ ves_analysis/neuron_stats/surface_area.py | 185 ++++++ .../neuron_stitching/stitching_new.py | 221 +++++++ .../sample/sample_data/7-13_lv_label.txt | 1 + ves_analysis/sample/sample_data/7-13_mask.h5 | Bin 0 -> 526278 bytes .../sample/sample_data/7-13_pred_filtered.h5 | Bin 0 -> 242774 bytes .../sample/sample_outputs/lv_thresholds.xlsx | Bin 0 -> 5342 bytes .../sample_outputs/sample_com_mapping.txt | 204 ++++++ .../sample_outputs/sample_near_counts.xlsx | Bin 0 -> 5230 bytes .../sample/sample_outputs/vesicle_stats.xlsx | Bin 0 -> 5684 bytes .../threshold_density_map/color_new.py | 103 +++ .../threshold_density_map/color_new_ng.py | 122 ++++ ves_analysis/vesicle_counts/LV_type_counts.py | 95 +++ ves_analysis/vesicle_counts/SV_type_counts.py | 112 ++++ .../vesicle_counts/pointcloud_near_counts.py | 468 ++++++++++++++ .../vesicle_counts/pointcloud_soma_counts.py | 130 ++++ ves_analysis/vesicle_counts/slow_counts.py | 402 ++++++++++++ ves_analysis/vesicle_stats/LUX2_density.py | 277 ++++++++ ves_analysis/vesicle_stats/lv_thresholds.py | 212 ++++++ .../vesicle_stats/updated_extract_stats.py | 612 ++++++++++++++++++ .../vesicle_stats/vesicle_volume_stats.py | 230 +++++++ 28 files changed, 4441 insertions(+) create mode 100644 ves_analysis/LICENSE.md create mode 100644 ves_analysis/README.md create mode 100644 ves_analysis/metadata_and_kdtree/kdTreeMeta.py create mode 100644 ves_analysis/neuroglancer_heatmap/heatmap.py create mode 100644 ves_analysis/neuroglancer_heatmap/ng_heatmap_vis.py create mode 100644 ves_analysis/neuroglancer_heatmap/ng_shader_script.glsl create mode 100644 ves_analysis/neuroglancer_types_map/types_ng.py create mode 100644 ves_analysis/neuroglancer_types_map/types_visualization.py create mode 100644 ves_analysis/neuron_stats/surface_area.py create mode 100644 ves_analysis/neuron_stitching/stitching_new.py create mode 100644 ves_analysis/sample/sample_data/7-13_lv_label.txt create mode 100644 ves_analysis/sample/sample_data/7-13_mask.h5 create mode 100644 ves_analysis/sample/sample_data/7-13_pred_filtered.h5 create mode 100644 ves_analysis/sample/sample_outputs/lv_thresholds.xlsx create mode 100644 ves_analysis/sample/sample_outputs/sample_com_mapping.txt create mode 100644 ves_analysis/sample/sample_outputs/sample_near_counts.xlsx create mode 100644 ves_analysis/sample/sample_outputs/vesicle_stats.xlsx create mode 100644 ves_analysis/threshold_density_map/color_new.py create mode 100644 ves_analysis/threshold_density_map/color_new_ng.py create mode 100644 ves_analysis/vesicle_counts/LV_type_counts.py create mode 100644 ves_analysis/vesicle_counts/SV_type_counts.py create mode 100644 ves_analysis/vesicle_counts/pointcloud_near_counts.py create mode 100644 ves_analysis/vesicle_counts/pointcloud_soma_counts.py create mode 100644 ves_analysis/vesicle_counts/slow_counts.py create mode 100644 ves_analysis/vesicle_stats/LUX2_density.py create mode 100644 ves_analysis/vesicle_stats/lv_thresholds.py create mode 100644 ves_analysis/vesicle_stats/updated_extract_stats.py create mode 100644 ves_analysis/vesicle_stats/vesicle_volume_stats.py diff --git a/ves_analysis/LICENSE.md b/ves_analysis/LICENSE.md new file mode 100644 index 0000000..8c40326 --- /dev/null +++ b/ves_analysis/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Micaela Aeyoung Roth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ves_analysis/README.md b/ves_analysis/README.md new file mode 100644 index 0000000..742c6f5 --- /dev/null +++ b/ves_analysis/README.md @@ -0,0 +1,71 @@ +# ves_analysis + +## Instructions for running sample data + +#### Given files in `sample/sample_data`: +- `7-13_mask.h5` mask of two adjacent neuron pieces (small chunk, not full neurons) + - Note: since we are using a small chunk of data in a neuron adjacency region instead of storing each neuron as an individual file, there is no stitching step for the sample. + +- `7-13_pred_filtered.h5` large vesicles within the target neuron (segid 62) in this sample region +- `7-13_lv_label.txt` vesicle type classifications + +#### Generate metadata +Run `python -i metadata_and_kdtree/kdTreeMeta.py --which_neurons "sample"` + +→ Metadata will be saved to `sample/sample_outputs/sample_com_mapping.txt` + +#### Export statistics +Run `python -i updated_export_stats.py --which_neurons “sample”` + +→ Volume/diameter stats and list exports will be saved to `sample/sample_outputs/` in multiple files + +#### Generate thresholds +Run `python -i lv_thresholds.py --which_neurons “sample”` + +→ Thresholds spreadsheet will be saved to `sample/sample_outputs/lv_thresholds.xlsx` + +#### Generate pointcloud counts (note segid of target neuron is 62) +Run `python -i vesicle_counts/pointcloud_near_counts.py --which_neurons "sample" --target_segid 62 --lv_threshold [lv threshold] --cv_threshold [cv threshold] --dv_threshold [dv threshold] --dvh_threshold [dvh threshold]` (replacing thresholds with those found from sheet exported in previous step) + +→ Near neuron counts spreadsheet will be saved to `sample/sample_outputs/sample_near_counts.xlsx` + + + +## Analysis scripts + +### metadata_and_kdtree +- `kdTreeMeta.py`: Convert format of dataset from binary mask to point cloud for each neuron, storing as a list of local coordinates of vesicle COMs and calculate statistics to store as corresponding attributes. Finally, construct kdtree from point cloud for density map, and export point cloud + metadata information into txt format for easy readability to dictionary format. + +### neuron_stitching +- `stitching_new.py`: Stitching together all pieces of adjacent neurons into the bounding box of a target neuron using global coordinate offsets; stitching two at a time to reduce memory consumption. + +### vesicle_counts +- `pointcloud_soma_counts.py`: Easily generalizable method to return the number of vesicles within a neuronal region given as a binary mask (in this case, the somas of the neurons). Uses extracted values for each vesicle COM coordinate from the mask of the neuron region to determine which vesicles are within the region. +- `pointcloud_near_counts.py`: Extracts “near neuron” regions of interest via Euclidean Distance Transform for adjacent neuron pieces from stitched files (see `neuron_stitching/stitching_new.py`). Uses different thresholds for each vesicle type (see `vesicle_stats/lv_diameters.py`), then counts the number of vesicles of each type within their corresponding regions of interest, using the aforementioned method from `pointcloud_soma_counts.py`). +- `LV_type_counts.py, SV_type_counts.py`: Given exported lists (from `pointcloud_near_counts.py`) of segmentation IDs of vesicles within regions of interest and segID to type mappings, returns vesicle counts separated by type. Allows for adaptability and efficiency in cases of changes to type classifications. +- `slow_counts.py`: Slow, inefficient method—ignore if using point cloud metadata pipeline for vesicles. Manually counts the number of overlapping vesicles in a given region using vectorized binary mask operations (if both neurons and vesicles in form of binary masks). + +### neuron_stats +- `surface_area.py`: Computes the surface area for a given binary mask of a neuron by generating a 1 voxel border using Euclidean distance transform and calculating the volume of the border region. + +### vesicle_stats +- `updated_extract_stats.py`: Extract statistics from point cloud metadata txt format (`metadata_and_kdtree/metadata_and_kdtree.py`), save as a pandas dataframe, and export into spreadsheet format. Also export lists of volumes and diameters as txt files. +- `lv_diameters.py`: Finding diameter-based thresholds for near neuron counts, to be used in `vesicle_counts/pointcloud_near_counts.py`. +- `vesicle_volume_stats.py`: For calculating and exporting vesicle volumes only (not useful if using point cloud metadata format). +- `LUX2_density.py`: Calculating more specific stats for a particular region of interest within the LUX2 neuron. + +## Alternate visualization scripts +Currently UNUSED but functional alternate visualization methods, see `vesicleEM/ves_vis` for final visualization methods which are in use. + +### neuroglancer_heatmap +- `heatmap.py`: Uses a Gaussian filter to calculate a heatmap image for vesicles within a given neuron, for visualization in Neuroglancer. +- `ng_heatmap_vis.py`: Neuroglancer rendering script to display a full dataset heatmap (from individual neuron heatmap images created using `heatmap.py`), assembled together by projecting into 2D and using coordinate offsets. Normalizing values and using a 4th color channel along with necessary rotations to align files for later color visualization. +- `ng_shader_script.glsl`: Shader script to plug into Neuroglancer visualization in order to render a gradient of colors based on values from 0.0 to 1.0. + +### neuroglancer_types_map +- `types_visualization.py`: Generates “color coded” vesicle mask files given segmentation ID to type mappings by relabeling each vesicle to indicate its type. +- `types_ng.py`: Neuroglancer rendering script to display the full dataset of vesicles color coded by type, using previously generated mask files and using the offset feature to align neurons accurately. + +### threshold_density_map +- `color_new.py`: Generates a heatmap using a Gaussian filter, then classifies/colors vesicles according to their density value through three thresholds as a simpler method if a continuous heatmap is not necessary. +- `color_new_ng.py`: Neuroglancer rendering script for thresholded heatmaps generated in `color_new.py`. diff --git a/ves_analysis/metadata_and_kdtree/kdTreeMeta.py b/ves_analysis/metadata_and_kdtree/kdTreeMeta.py new file mode 100644 index 0000000..b384aea --- /dev/null +++ b/ves_analysis/metadata_and_kdtree/kdTreeMeta.py @@ -0,0 +1,202 @@ +#author https://github.com/akgohain + +import os +import gc +import math +import h5py +import numpy as np +from tqdm import tqdm +import argparse +from scipy import spatial +from concurrent.futures import ProcessPoolExecutor, as_completed + +chunk_size = 25 #25 + +ds_factor = 1 + +voxel_dims = (30, 8 * ds_factor, 8 * ds_factor) + +sample_dir = '/home/rothmr/hydra/sample/' + + +#inpute into args to compute for all neurons +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7", "SHL17"] + + +def process_chunk_range(params): + start, end, file_path, ds_factor, name = params + chunk_stats = {} + with h5py.File(file_path, 'r', swmr=True) as f: + dset = f["main"] + + chunk = dset[start:end, ::ds_factor, ::ds_factor] + unique_labels = np.unique(chunk) + for label in unique_labels: + if label == 0: + continue + mask = (chunk == label) + coords = np.argwhere(mask) + if coords.size == 0: + continue + coords[:, 0] += start + sum_coords = coords.sum(axis=0) + count = coords.shape[0] + if label in chunk_stats: + chunk_stats[label]['sum'] += sum_coords + chunk_stats[label]['count'] += count + else: + chunk_stats[label] = {'sum': sum_coords, 'count': count} + del chunk + gc.collect() + return chunk_stats + +def process_chunks(file_path, ds_factor, chunk_size, name, num_workers=None): + chunk_stats = {} + with h5py.File(file_path, 'r', swmr=True) as f: + full_shape = f["main"].shape + + chunk_ranges = [] + for start in range(0, full_shape[0], chunk_size): + end = min(start + chunk_size, full_shape[0]) + chunk_ranges.append((start, end, file_path, ds_factor, name)) + + with ProcessPoolExecutor(max_workers=num_workers) as executor: + futures = [executor.submit(process_chunk_range, params) for params in chunk_ranges] + for future in tqdm(as_completed(futures), total=len(futures), desc="Processing chunks", ncols=100): + result = future.result() + for label, data in result.items(): + if label in chunk_stats: + chunk_stats[label]['sum'] += data['sum'] + chunk_stats[label]['count'] += data['count'] + else: + chunk_stats[label] = data + return chunk_stats + + +def compute_metadata(chunk_stats, voxel_dims): + """ + Computes the center-of-mass (COM), volume, and radius for each vesicle. + The volume is computed as: voxel_count * (30 * 64 * 64) + The radius (in nm) is computed using the micaela/shulin's method: + sqrt((voxel_count * (64*64)) / pi) + """ + metadata = {} + for label, stats in chunk_stats.items(): + com = stats['sum'] / stats['count'] + volume_nm = stats['count'] * (voxel_dims[0] * voxel_dims[1] * voxel_dims[2]) + radius_nm = math.sqrt((stats['count'] * (voxel_dims[1] * voxel_dims[2])) / math.pi) + metadata[label] = { + 'com': com, + 'volume_nm': volume_nm, + 'radius_nm': radius_nm + } + return metadata + +def compute_density(metadata, voxel_dims, kd_radius=500): + """ + Computes local vesicle density using a KDTree. + COM coordinates are converted from voxel indices to physical units (nm) and + then the density is calculated as: frequency / (kd_radius^2) + The density values are also normalized. + """ + com_list = [] + labels = [] + for label, data in metadata.items(): + com = data['com'] + # Convert voxel indices to physical coordinates (nm) + physical_com = np.array([ + com[0] * voxel_dims[0], + com[1] * voxel_dims[1], + com[2] * voxel_dims[2] + ]) + com_list.append(physical_com) + labels.append(label) + + print("total num of ves: ", len(com_list)) + com_array = np.array(com_list) + + + # Build the KDTree and query neighbors within kd_radius (nm) + tree = spatial.KDTree(com_array) + neighbors = tree.query_ball_tree(tree, kd_radius) + frequency = np.array([len(n) for n in neighbors]) + density = frequency / (kd_radius ** 2) + + # Normalize the density values + min_density = np.min(density) + max_density = np.max(density) + if max_density > min_density: + normalized_density = (density - min_density) / (max_density - min_density) + else: + normalized_density = density + + # Add density info to metadata + for i, label in enumerate(labels): + metadata[label]['density'] = density[i] + metadata[label]['normalized_density'] = normalized_density[i] + return metadata + +def process_vesicle_data(name, vesicle_type="lv"): + """ + Processes vesicle data (either 'lv' or 'sv') using sequential chunking. + Computes COM, volume, radius, and density via KDTree. + The metadata is written out to a text file. + + Note: Re-enumerates labels to ensure uniqueness across LV and SV datasets. + """ + file_prefix = "vesicle_big_" if vesicle_type == "lv" else "vesicle_small_" + file_path = f"/data/projects/weilab/dataset/hydra/results/{file_prefix}{name}_30-8-8.h5" + + #if sample + if(name=="sample"): + if(vesicle_type == "lv"): #should only be lv + file_path = f"{sample_dir}sample_data/7-13_pred_filtered.h5" + + print(f"Starting chunked processing for {vesicle_type} data of {name}...") + chunk_stats = process_chunks(file_path, ds_factor, chunk_size, name) + metadata = compute_metadata(chunk_stats, voxel_dims) + metadata = compute_density(metadata, voxel_dims, kd_radius=500) + + # Re-enumerate labels to ensure uniqueness by prefixing with vesicle type + unique_metadata = {} + for label, data in metadata.items(): + new_label = f"{vesicle_type}_{label}" + unique_metadata[new_label] = data + metadata = unique_metadata + + # Ensure output directory exists + output_dir = f"metadata/{name}/" + os.makedirs(output_dir, exist_ok=True) + + output_file = f"metadata/{name}/{name}_{vesicle_type}_com_mapping.txt" + + if(name=="sample"): + output_file = f"{sample_dir}sample_outputs/sample_com_mapping.txt" + + with open(output_file, "w") as f: + for label, data in metadata.items(): + f.write(f"{data['com']}: ('{vesicle_type}', {label}, {data['volume_nm']}, {data['radius_nm']}, {data['density']}, {data['normalized_density']})\n") + print(f"Chunked processing complete for {vesicle_type} data of {name}!") + return metadata + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument("--which_neurons", type=str, help="all or sample?") #enter as "all" or "sample" + args = parser.parse_args() + which_neurons = args.which_neurons + + if(which_neurons=="sample"): + lv_metadata = process_vesicle_data("sample", vesicle_type="lv") + #only need LV for the near counts example pipeline + + elif(which_neurons=="all"): + for name in names_20: + lv_metadata = process_vesicle_data(name, vesicle_type="lv") + sv_metadata = process_vesicle_data(name, vesicle_type="sv") + + + + diff --git a/ves_analysis/neuroglancer_heatmap/heatmap.py b/ves_analysis/neuroglancer_heatmap/heatmap.py new file mode 100644 index 0000000..776aeea --- /dev/null +++ b/ves_analysis/neuroglancer_heatmap/heatmap.py @@ -0,0 +1,235 @@ +import numpy as np +import h5py +from scipy.ndimage import gaussian_filter +import matplotlib as plt +import matplotlib.cm as cm +import argparse +import gc + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D5 = '/data/rothmr/hydra/heatmaps/' #output dir + +cache = {} + +#downsample bc of the full ng vis +def load_data(name): + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", 'r') as f: + cache["lv"] = np.array(f["main"][:][:, ::2, ::2]) #30-16-16 + with h5py.File(f"{D0}vesicle_small_{name}_30-8-8.h5", 'r') as f: + cache["sv"] = np.array(f["main"][:][:, ::2, ::2]) #30-16-16 + +#when needing to ds more +def load_data_DS(name): + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", 'r') as f: + cache["lv"] = f["main"][:, ::16, ::16] #30-64-64 + with h5py.File(f"{D0}vesicle_small_{name}_30-8-8.h5", 'r') as f: + cache["sv"] = f["main"][:, ::16, ::16] #30-64-64 + + +#write this for heatmap +def load_chunks(name, chunk_num, num_chunks): + print(f"begin loading vesicles chunk #{chunk_num+1}") #bc zero indexing + chunk_length = 0 #initialize for scope + + lv_path = f'{D0}vesicle_big_{name}_30-8-8.h5' + with h5py.File(lv_path, 'r') as f: + shape = f["main"].shape + dtype = f["main"].dtype + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + if(chunk_num!=num_chunks-1): + cache["lv"] = f["main"][:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + else: #case of the last chunk + cache["lv"] = f["main"][:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + print(f"done loading LV chunk #{chunk_num+1} of {num_chunks}") #bc zero indexing + path = f'{D0}vesicle_small_{name}_30-8-8.h5' + with h5py.File(path, 'r') as f: + shape = f["main"].shape + dtype = f["main"].dtype + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + if(chunk_num!=num_chunks-1): + cache["sv"] = f["main"][:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + else: #case of the last chunk + cache["sv"] = f["main"][:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + print(f"done loading SV chunk #{chunk_num+1} of {num_chunks}") #bc zero indexing + + return chunk_length + +def calculate_chunks(name, num_chunks): + current_chunk = 0 + path = f"{D0}vesicle_big_{name}_30-8-8.h5" + with h5py.File(path, 'r') as f: + #output = np.zeros(shape=f["main"].shape, dtype=f["main"].dtype) #initialize the full output thing + shape = f["main"].shape + dtype = f["main"].dtype + + #init output file + with h5py.File(f"{D5}{name}_heatmap.h5", 'w') as f: + f.create_dataset('main', data=np.zeros(shape=shape,dtype=dtype)) + + while(current_chunk!=num_chunks): #runs [num_chunk] times + chunk_length = load_chunks(name, current_chunk, num_chunks) + + cache["lv"][cache["lv"] > 0] = 1 + cache["sv"][cache["sv"] > 0] = 1 + print("binary conversion done") + + #combine lv and sv + cache["lv"] |= cache["sv"] #combine and save into cache["lv to not take up more memory"] + cache["lv"] = cache["lv"].astype(np.int32) #convert bool to 1s and 0s + del cache["sv"] #clear sv + gc.collect() + print("lv and sv combined") + + sigma = 10.0 + smoothed_density_map = gaussian_filter(cache["lv"].astype(np.float32), sigma=sigma) + del cache["lv"] + gc.collect() + max_value = np.max(smoothed_density_map[smoothed_density_map > 0]) #find max + if max_value > 0: + smoothed_density_map[smoothed_density_map > 0] = (smoothed_density_map[smoothed_density_map > 0] / max_value * 255).astype(np.uint8) + print("gaussian and normalization done") + + smoothed_density_map = smoothed_density_map.astype(np.float16) #reduce memory usage - change from float64 to float16... + colormap = cm.plasma + colored_density_map = (colormap(smoothed_density_map)[:, :, :, :3] * 255).astype(np.uint8) #drop alpha + print("final density map shape:", colored_density_map.shape) + + if(current_chunk!=num_chunks-1): #not last chunk + with h5py.File(f"{D5}{name}_heatmap.h5", "a") as f: + f["main"][:, y_start:(current_chunk + 1) * chunk_length, :] = heatmap_chunk + + else: #last chunk + with h5py.File(f"{D5}{name}_heatmap.h5", "a") as f: + f["main"][:, y_start:, :] = heatmap_chunk + + print(f"CHUNK {current_chunk} DONE \n") + + current_chunk+=1 + + #save the output file to an h5 + print("done generating FULL heatmap file") + return output + + +def calculate_density_map_WITH_SMOOTHING(name, downsample=False): + if downsample: #ds by 8 + print(f"calculate_density_map running for {name}") + + load_data_DS(name) + print("data loading and downsampling done") + + #change to in-place ops + cache["lv"][cache["lv"] > 0] = 1 + cache["sv"][cache["sv"] > 0] = 1 + + print("binary conversion done") + + #combine lv and sv + cache["lv"] |= cache["sv"] #combine and save into cache["lv to not take up more memory"] + cache["lv"] = cache["lv"].astype(np.int32) #convert bool to 1s and 0s + del cache["sv"] #clear sv + gc.collect() + print("lv and sv combined") + + sigma = 10.0 #testing 2.0 to 20.0 - last was 10.0 + smoothed_density_map = gaussian_filter(cache["lv"].astype(np.float32), sigma=sigma) + del cache["lv"] + gc.collect() + + max_value = np.max(smoothed_density_map[smoothed_density_map > 0]) #find max + if max_value > 0: + #take out zero values so that normalization is not skewed + smoothed_density_map[smoothed_density_map > 0] = (smoothed_density_map[smoothed_density_map > 0] / max_value * 255).astype(np.uint8) + + print("gaussian and normalization done") + return smoothed_density_map + + else: #ds by 2 only + print(f"calculate_density_map running for {name}") + + load_data(name) + print("data loading and downsampling done") + + #change to in-place ops + cache["lv"][cache["lv"] > 0] = 1 + cache["sv"][cache["sv"] > 0] = 1 + + print("binary conversion done") + + #combine lv and sv + cache["lv"] |= cache["sv"] #combine and save into cache["lv to not take up more memory"] + cache["lv"] = cache["lv"].astype(np.int32) #convert bool to 1s and 0s + del cache["sv"] #clear sv + gc.collect() + print("lv and sv combined") + + sigma = 10.0 #testing 2.0 to 20.0 - last was 10.0 + smoothed_density_map = gaussian_filter(cache["lv"].astype(np.float32), sigma=sigma) + del cache["lv"] + gc.collect() + + max_value = np.max(smoothed_density_map[smoothed_density_map > 0]) #find max + if max_value > 0: + #take out zero values so that normalization is not skewed + smoothed_density_map[smoothed_density_map > 0] = (smoothed_density_map[smoothed_density_map > 0] / max_value * 255).astype(np.uint8) + + print("gaussian and normalization done") + return smoothed_density_map + + + +#takes in the normalized non color density map +def calculate_density_map_COLOR(density_map): + density_map = density_map.astype(np.float16) #reduce memory usage - change from float64 to float16... + colormap = cm.plasma + ''' + colored_density_map = colormap(density_map) #rgba array + colored_density_map = (colored_density_map[:, :, :, :3] * 255).astype(np.uint8) #drop alpha + ''' + colored_density_map = (colormap(density_map)[:, :, :, :3] * 255).astype(np.uint8) #drop alpha + + print("final density map shape:", colored_density_map.shape) + + return colored_density_map + +if __name__ == "__main__": + to_generate = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", "RGC2", "KM4", "SHL17", + "NET12", "NET10", "NET11", "PN7", "SHL18", "SHL24", "SHL28", "RGC7"] #20 neurons + + chunking = False + num_chunks = 4 + downsample = True + + if chunking: + for name in to_generate: + print(f"-----{name}-----") + print(f"use chunking with {num_chunks} chunks") + calculate_chunks(name, num_chunks) + print(f"successfully saved {name}_heatmap \n") + + + elif downsample: + for name in to_generate: + smoothed_density_map = calculate_density_map_WITH_SMOOTHING(name, downsample=True) + color_density_map = calculate_density_map_COLOR(smoothed_density_map) + + with h5py.File(f"{D5}{name}_heatmap_DS.h5", 'w') as f: + f.create_dataset('main', data=color_density_map) + print(f"successfully saved {name}_heatmap_DS.h5 \n") + + + else: + for name in to_generate: + smoothed_density_map = calculate_density_map_WITH_SMOOTHING(name) + color_density_map = calculate_density_map_COLOR(smoothed_density_map) + + with h5py.File(f"{D5}{name}_heatmap.h5", 'w') as f: + f.create_dataset('main', data=color_density_map) + print(f"successfully saved {name}_heatmap \n") + + + + + diff --git a/ves_analysis/neuroglancer_heatmap/ng_heatmap_vis.py b/ves_analysis/neuroglancer_heatmap/ng_heatmap_vis.py new file mode 100644 index 0000000..06b6c0c --- /dev/null +++ b/ves_analysis/neuroglancer_heatmap/ng_heatmap_vis.py @@ -0,0 +1,168 @@ +import h5py +import os,sys +import numpy as np +from scipy.ndimage import zoom, binary_dilation +import neuroglancer +import socket +from contextlib import closing +import argparse +import yaml +from PIL import Image +import matplotlib.pyplot as plt + +#full visualization, collapsed into 2d + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D5 = '/data/rothmr/hydra/heatmaps/' +bbox = np.loadtxt('/data/projects/weilab/dataset/hydra/mask_mip1/bbox.txt').astype(int) #for offsets + + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + return s.getsockname()[1] + +ip = 'localhost' #or public ip for sharable display +port = find_free_port() +neuroglancer.set_server_bind_address(bind_address=ip,bind_port=port) +viewer=neuroglancer.Viewer() + +resM = [32, 32, 30] #neuron mask +res_test = [16, 16, 30] #ves + +#zyx +def ng_layer(data,res,oo=[0,0,0],tt='segmentation'): + #data = data.transpose(2, 1, 0) #transpose from zyx to xyz + data = data.transpose(1,2,0) #just this + #data = data.transpose(0,2,1) #TESTING + dim = neuroglancer.CoordinateSpace(names=["x", "y", "z"], units="nm", scales=res) + return neuroglancer.LocalVolume(data, volume_type=tt, dimensions=dim, voxel_offset=oo) + +#takes in 2d input and adds dim size 1 for z axis - for mask border +def ng_layer_2d(data, res, oo=[0,0,0], tt='segmentation'): + data = np.expand_dims(data, axis=0) #add z axis + #data = data.transpose(0,2,1) + data = data.transpose(2,0,1) #just this 201 + #data = data.transpose(1,0,2) #TESTING + dim = neuroglancer.CoordinateSpace(names=["x", "y", "z"], units="nm", scales=res) + return neuroglancer.LocalVolume(data, volume_type=tt, dimensions=dim, voxel_offset=oo) + + +#for density map - process 4d input instead of 3d - xyz plus extra color channel +def ng_layer_edited(data, res, oo=[0, 0, 0], tt="image"): + if data.ndim == 4: + #shape is (d,h,w,c) + data = data.transpose(2, 1, 0, 3) #now (h,w,d,c) - from before + #data = data.transpose(0, 1, 2, 3) #or this + + data = data.mean(axis=-1) #average color channels -> (d,h,w) xzy + + #clip and change to uint8 + data = np.clip(data, 0, 255).astype(np.uint8) + + dim = neuroglancer.CoordinateSpace(names=["x", "y", "z"], units="nm", scales=res) + return neuroglancer.LocalVolume(data, volume_type=tt, dimensions=dim, voxel_offset=oo) + +#extract the neuron border from collapsed 2d image for visualization +def extract_border(mask_2d): + eroded_mask = binary_dilation(mask_2d, iterations=5) #more iterations for line thickness + border = mask_2d ^ eroded_mask #XOR + return border.astype(np.uint8) + +def neuron_name_to_id(name): + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') #switch to local path + + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + + +def get_offset(name): #returns in 30-8-8 and xyz + nid = neuron_name_to_id(name)[0] + bb = bbox[bbox[:,0]==nid, 1:][0] #bbox is in zyx + output = [bb[4], bb[2], bb[0]] #min coords, but change to xyz + return output + + +def screenshot(path='temp.png', save=True, show=True, size=[4096, 4096]): + ss = viewer.screenshot(size=size).screenshot.image_pixels + if save: + Image.fromarray(ss).save(path) + if show: + plt.imshow(ss) + plt.show() + + +def visualize(): + names = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7", "SHL17"] + + #once all heatmaps already generated + for name in names: + v_fname = f"{D5}{name}_heatmap.h5" #30-16-16 + with h5py.File(v_fname, "r") as f: + vesicles = f["main"][:] + #print("original size of heatmap:", vesicles.shape) + + m_fname = f"{D0}neuron_{name}_30-32-32.h5" #30-32-32 + with h5py.File(m_fname, "r") as f: + mask = f["main"][:] #30-32-32 + + offset = get_offset(name) #IN ZYX + print("offset in 30-8-8 and xyz: ", offset) + + res1_offset = [offset[0]/4, 0, offset[1]/4] #mask + res2_offset = [offset[0]/4, 0, offset[1]/2] #heatmap + + + with viewer.txn() as s: + summed = np.sum(vesicles, axis=2) #collapse z-axis at angle of vis - sum up all densities + density_map_normalized = (summed - np.min(summed)) / (np.max(summed) - np.min(summed)) + density_map_normalized = (density_map_normalized * 255).astype(np.uint8) + #print("shape of collapsed density map: ", density_map_normalized.shape) + + density_map_normalized = np.rot90(density_map_normalized, k=3, axes=(0,1)) + + s.layers.append(name=f'{name}_summed_density_map', layer=ng_layer(density_map_normalized, res=[30, 16, 16], tt='image', oo=res2_offset)) + print(f"added density map layer for {name}") + + mask_summed = np.any(mask, axis=2) #collapse z-axis - logical OR + #print("shape of collapsed mask: ", mask_summed.shape) + border = extract_border(mask_summed) + + border = np.rot90(border, k=3, axes=(0,1)) #rotate + + s.layers.append(name=f'{name}_mask',layer=ng_layer_2d(border,res=[30, 32, 32], tt='segmentation', oo=res1_offset)) #seg + print(f"added mask border layer for {name}") + + + #working 3d mask & 3d heatmap visualization for single neuron - can scroll heatmap layers to get true layer by layer overlay + ''' + print("map shape: ", vesicles.shape) + print("mask shape: ", mask.shape) + s.layers.append(name=f'{name}_mask',layer=ng_layer(mask,res=[32, 32, 30], tt='segmentation')) #seg + + vesicles_transpose = vesicles.transpose(1,2,3,0) #3,0,1,2 #2,1,0,3 + print("shape of vesicles after transpose:", vesicles_transpose.shape) + s.layers.append(name='density_map',layer=ng_layer_edited(vesicles,res=res_test, tt='image')) + ''' + + + +visualize() + +print(viewer) + + + + + + + + diff --git a/ves_analysis/neuroglancer_heatmap/ng_shader_script.glsl b/ves_analysis/neuroglancer_heatmap/ng_shader_script.glsl new file mode 100644 index 0000000..0cfd50d --- /dev/null +++ b/ves_analysis/neuroglancer_heatmap/ng_shader_script.glsl @@ -0,0 +1,41 @@ +//shader script for neuroglancer rendering + +//set normalization in ng to around 50 to take out gb +//set opacity of all heatmap layers to 1 (under rendering) + +#uicontrol invlerp normalized + +void main() { + float value = normalized(); + float threshold = 0.23; + + //taking out the bg + if (value < threshold) { + discard; //if value below threshold, make transparent + } + + //nonlinear transformation to adjust contrast + value = pow(value, 1.0); //can adjust exponent to adjust initial contrast + + vec3 color = vec3(0.0); + + //color transitions based on value in density map data + if (value < 0.33) { //green -> yellow range (0.0 to 0.33) + color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), value / 0.33); + } + else if (value < 0.66) { //yellow -> orange range (0.33 to 0.66) + color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.25, 0.0), (value - 0.33) / 0.33); + } + else { + // orange -> red range (0.66 to 1.0) + color = mix(vec3(1.0, 0.25, 0.0), vec3(1.0, 0.0, 0.0), (value - 0.66) / 0.33); + } + + //contrast increase to look more vibrant + color = pow(color, vec3(1.8)); + + //clamp color values to 0 -> 1 range to avoid overflow + color = clamp(color, 0.0, 1.0); + + emitRGB(color); +} \ No newline at end of file diff --git a/ves_analysis/neuroglancer_types_map/types_ng.py b/ves_analysis/neuroglancer_types_map/types_ng.py new file mode 100644 index 0000000..87f7b0e --- /dev/null +++ b/ves_analysis/neuroglancer_types_map/types_ng.py @@ -0,0 +1,154 @@ +import numpy as np +import h5py +import neuroglancer +import imageio +import socket +from contextlib import closing +import yaml +import os +import gc +from PIL import Image +import matplotlib.pyplot as plt + + +bbox = np.loadtxt('/data/projects/weilab/dataset/hydra/mask_mip1/bbox.txt').astype(int) + + +cache = {} + +#only store one at a time +def load_data(name): + with h5py.File(f"color_coded/{name}_color_coded.h5", "r") as f: + cache["vesicles"] = np.array(f["main"][:][:,::8,::8]) + with h5py.File(f"/data/projects/weilab/dataset/hydra/results/neuron_{name}_30-32-32.h5") as f: + cache["mask"] = np.array(f["main"][:][:,::2,::2]) + #load SV + with h5py.File(f"/data/projects/weilab/dataset/hydra/results/vesicle_small_{name}_30-8-8.h5") as f: + cache["sv"] = np.array(f["main"][:][:,::8,::8]) #30-8-8 + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + return s.getsockname()[1] + +ip = 'localhost' #or public IP of the machine for sharable display +port = find_free_port() #change to an unused port number +neuroglancer.set_server_bind_address(bind_address=ip,bind_port=port) +viewer=neuroglancer.Viewer() + + +# SNEMI (# 3d vol dim: z,y,x) +D0='./' +res1 = neuroglancer.CoordinateSpace( + names=['z', 'y', 'x'], + units=['nm', 'nm', 'nm'], + scales = [30,64,64]) + +res2 = neuroglancer.CoordinateSpace( + names=['z','y','x'], + units=['nm', 'nm', 'nm'], + scales=[30,16,16]) + +res3 = neuroglancer.CoordinateSpace( + names=['z','y','x'], + units=['nm', 'nm', 'nm'], + scales=[30,32,32]) + + +def ngLayer(data,res,oo=[0,0,0],tt='segmentation'): + return neuroglancer.LocalVolume(data,dimensions=res,volume_type=tt,voxel_offset=oo) + + +#for density map - process 4d input instead of 3d +def ng_layer_edited(data, res, oo=[0, 0, 0], tt="image"): + if data.ndim == 4: + #shape is (d,h,w,c) + data = data.transpose(2, 1, 0, 3) #now (h,w,d,c) + data = data.mean(axis=-1) #average color channels -> (d,h,w) + + #clip and change to uint8 + data = np.clip(data, 0, 255).astype(np.uint8) + + dim = neuroglancer.CoordinateSpace(names=["x", "y", "z"], units="nm", scales=res) + return neuroglancer.LocalVolume(data, volume_type=tt, dimensions=dim, voxel_offset=oo) + + +def neuron_name_to_id(name): + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + +#find the min bbox coord (bottom left corner) for this neuron, return as list of 3 coords +def get_offset(name): #returns in 30-8-8 + nid = neuron_name_to_id(name)[0] + bb = bbox[bbox[:,0]==nid, 1:][0] + output = [bb[0], bb[2], bb[4]] + + return output + + +def screenshot(path='temp.png', save=True, show=True, size=[4096, 4096]): + ss = viewer.screenshot(size=size).screenshot.image_pixels + if save: + Image.fromarray(ss).save(path) + if show: + plt.imshow(ss) + plt.show() + + +with viewer.txn() as s: + + ##COLOR CODED VIS + names = ['KR4', 'KR5', 'KR6', 'SHL55', 'PN3', 'LUX2', 'SHL20', 'KR11', 'KR10', 'RGC2', 'KM4', 'NET12', 'SHL17', 'NET10', 'NET11', 'PN7', 'SHL18', 'SHL24', 'SHL28', 'RGC7'] + + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') #switch to local path + + for name in names: + path = f"color_coded/{name}_color_coded.h5" + if(os.path.exists(path)): + load_data(name) #cache["vesicles"] is updated to the current neuron's vesicle data + print(f"done loading data for {name}") + + #do ds when loading + + vesicles = cache["vesicles"] + mask = cache["mask"] + sv = cache["sv"] + + offset = get_offset(name) #this is in 30-8-8? + res1_offset = [offset[0], offset[1]/8, offset[2]/8] #mask + res2_offset = [offset[0], offset[1]/2, offset[2]/2] #lv + res3_offset = [offset[0], offset[1]/4, offset[2]/4] #sv + + #note - downsampled everything to 30-64-64 for full vis so use res1 for everything + + s.layers.append(name=f'{name}_mask',layer=ngLayer(mask,res=res1, tt='segmentation', oo=res1_offset)) + s.layers.append(name=f'{name}_CV',layer=ngLayer((vesicles==1).astype('uint16'),res=res1, tt='segmentation', oo=res1_offset)) + s.layers.append(name=f'{name}_DV',layer=ngLayer((vesicles==2).astype('uint16'),res=res1, tt='segmentation', oo=res1_offset)) + s.layers.append(name=f'{name}_DVH',layer=ngLayer((vesicles==3).astype('uint16'),res=res1, tt='segmentation', oo=res1_offset)) + + #add SV layer + s.layers.append(name=f'{name}_small',layer=ngLayer(sv,res=res1, tt='segmentation', oo=res1_offset)) + + print(f"added all layers for {name}") + else: + print(f"file for {name} does not exist") + + del mask, vesicles + cache.clear() + gc.collect() + + +print(viewer) + + + + + + + diff --git a/ves_analysis/neuroglancer_types_map/types_visualization.py b/ves_analysis/neuroglancer_types_map/types_visualization.py new file mode 100644 index 0000000..acccf4d --- /dev/null +++ b/ves_analysis/neuroglancer_types_map/types_visualization.py @@ -0,0 +1,175 @@ +import numpy as np +import h5py +import sys +import yaml +import argparse + +#generates color coded vesicle files + +cache = {} +D0 = '/data/projects/weilab/dataset/hydra/results/' +D4 = '/data/rothmr/hydra/types_lists/' #full types lists +D5 = '/data/rothmr/hydra/color_coded/' #output dir for files + + +#data loader +def load_data(name): + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", "r") as f: + cache["vesicles"] = f["main"][:] + print("done loading data") + + +def read_txt_to_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + + +#generate the file with different segids for each vesicle type +def generate_color_coded(vesicles, d): + #print num of unique labels in vesicles + unique_labels = np.unique(vesicles) + num_labels = len(unique_labels) - 1 #minus the bg label + + print("checkpoint 0") + + v = np.zeros(vesicles.shape, dtype=vesicles.dtype) #new file + + #first relabel for vesicles 1, 2 + if np.any(vesicles == 1): + v[vesicles==1] = d[1] + if np.any(vesicles == 2): + v[vesicles==2] = d[2] + + print("checkpoint 1") + + for label, new_label in d.items(): + print("label: ", label, "new label: ", new_label) + #avoid relabeling vesicles 0, 1, 2 again + if (label not in (0,1,2)): + #v[(vesicles==label) & (v!=new_label)] = new_label + v[vesicles==label] = new_label + print("loop done") + return v + +#write method to combine + + +#doesn't ever load full data +def load_chunks(name, chunk_num, num_chunks): + print(f"begin loading chunk #{chunk_num+1}") #bc zero indexing + chunk_length = 0 #initialize for scope + + path = f'{D0}vesicle_big_{name}_30-8-8.h5' #use name param + with h5py.File(path, 'r') as f: + shape = f["main"].shape + dtype = f["main"].dtype + + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + + if(chunk_num!=num_chunks-1): + cache["chunk"] = f["main"][:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + + else: #case of the last chunk + cache["chunk"] = f["main"][:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + + print(f"done loading chunk #{chunk_num+1} of {num_chunks}") #bc zero indexing + + return chunk_length + + +#main +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--name', type=str, help='neuron name') + args = parser.parse_args() + name = args.name + + chunking = True + num_chunks = 8 + + + if(chunking): + current_chunk = 7 + path = f"{D0}vesicle_big_{name}_30-8-8.h5" + with h5py.File(path, 'r') as f: + shape=f["main"].shape + dtype=f["main"].dtype + + while(current_chunk!=num_chunks): #runs [num_chunk] times + chunk_length = load_chunks(name, current_chunk, num_chunks) + chunk = cache["chunk"] + + #generate color coded for the chunk + data = chunk.flatten() + unique_labels = np.unique(data) + dictionary = read_txt_to_dict(f"{D4}{name}_types.txt") + remove = [k for k, v in dictionary.items() if v==0] + for k in remove: + del dictionary[k] + print("checkpoint") + + color_coded_chunk = generate_color_coded(chunk, dictionary) #not padded + print(f"done generating color coded chunk #{current_chunk+1}") + + #find coords of current chunk + y_start = current_chunk * chunk_length + + #cases for last chunk or not + insert chunk into place + if(current_chunk!=num_chunks-1): #not last chunk + with h5py.File(f"{D5}{name}_color_coded.h5", "a") as f: + if(current_chunk == 0): # create dataset for the first chunk + f.create_dataset("main", shape=shape, dtype=dtype) + f["main"][:, y_start:(current_chunk + 1) * chunk_length, :] = color_coded_chunk + #output[:, y_start:(current_chunk+1)*chunk_length, :] = color_coded_chunk + + else: #last chunk + with h5py.File(f"{D5}{name}_color_coded.h5", "a") as f: + f["main"][:, y_start:, :] = color_coded_chunk + #output[:, y_start:, :] = color_coded_chunk + + current_chunk+=1 + + del chunk + del color_coded_chunk + + #save the output file to an h5 + print("done generating color coded file") + + + else: + load_data(name) + #print num of components in vesicles + data = cache["vesicles"].flatten() + unique_labels = np.unique(data) + print("num vesicles: ", len(unique_labels)-1) #remove bg label + + dictionary = read_txt_to_dict(f"{D4}{name}_types.txt") + print("original num of keys: ", len(dictionary.keys())) + + #remove everything with value 0 + remove = [k for k, v in dictionary.items() if v==0] + for k in remove: + del dictionary[k] + + print("new num of keys: ", len(dictionary.keys())) + + color_coded = generate_color_coded(cache["vesicles"], dictionary) + print("done generating color coded file") + + with h5py.File(f"{D5}{name}_color_coded.h5", "w") as f: + f.create_dataset("main", shape=color_coded.shape, data=color_coded) + print(f"saved as {name}_color_coded.h5") + + + + diff --git a/ves_analysis/neuron_stats/surface_area.py b/ves_analysis/neuron_stats/surface_area.py new file mode 100644 index 0000000..28ae50c --- /dev/null +++ b/ves_analysis/neuron_stats/surface_area.py @@ -0,0 +1,185 @@ +import numpy as np +import h5py +from scipy.ndimage import distance_transform_edt as edt +import numpy as np +import yaml + +cache = {} +D1 = '/data/rothmr/hydra/stitched/' +D3 = '/data/rothmr/hydra/sa/' #exporting for visualization + +def load_data(name): + with h5py.File(f"{D1}neuron_{name}_box_30-32-32.h5", 'r') as f: + cache["box"] = f["main"][:] + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + +def neuron_name_to_id(name): + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +def expand_mask(original_mask, threshold_nm, res): #threshold in physical units, res in xyz + expanded_mask = np.copy(original_mask) + + print("begin distance transform for expansion") + dt = edt.edt(1 - original_mask, anisotropy=(res[2],res[1],res[0])) #needs to be in xyz by default for edt + print("end distance transform for expansion") + doubled_perimeter = dt <= threshold_nm #all in nm + expanded_mask[doubled_perimeter] = 1 + + expanded_mask_binary = (expanded_mask>=1).astype(int) + return expanded_mask_binary + + +def surface_area(mask, name, nid): + other_neurons = ((mask!=nid) & (mask!=0)).astype(np.uint16) + neuron = (mask==nid).astype(np.uint16) + + print("start edt for border") + neuron_edt = edt(neuron) + print("end edt for border") + neuron_border = ((neuron_edt>0) * (neuron_edt<=1)).astype(np.uint16) + + border_voxels = int(neuron_border.sum()) + print(f'{name} total SA (nm^2): {border_voxels*32**2}') #output 1 + + print("start edt for expansion") + other_neurons_edt = edt(1-other_neurons) + print("end edt for expansion") + expansion = ((other_neurons_edt>0) * (other_neurons_edt<=3)).astype(np.uint16) #3 voxels expansion + + intersection = np.bitwise_and(neuron_border, expansion) + intersection_voxels = int(intersection.sum()) + + print(f'{name} near neuron SA (nm^2): {intersection_voxels*32**2}') #output 2 + + + ''' + #optional for visualization: checking that the expansion is enough - doesn't affect actual code + fname = f'{D3}surfaces_{name}.h5' + with h5py.File(fname, 'w') as f: + f.create_dataset("main", data=intersection, shape = intersection.shape, dtype=intersection.dtype) #or dtype=np.uint8 ? + print(f"successfully exported surface file as {fname}") + + fname = f'{D3}border_{name}.h5' + with h5py.File(fname, 'w') as f: + f.create_dataset("main", data=neuron_border, shape = neuron_border.shape, dtype=neuron_border.dtype) #or dtype=np.uint8 ? + print(f"successfully border surface file as {fname}") + + fname = f'{D3}expansion_{name}.h5' + with h5py.File(fname, 'w') as f: + f.create_dataset("main", data=expansion, shape = expansion.shape, dtype=expansion.dtype) #or dtype=np.uint8 ? + print(f"successfully expansion surface file as {fname}") + ''' + +#no padding, loads chunks on their own +def load_chunks(mask, chunk_num, num_chunks, key): + shape = mask.shape + dtype = mask.dtype + + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + + if(chunk_num!=num_chunks-1): + output = mask[:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + cache[key] = output + + else: #case of the last chunk + output = mask[:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + cache[key] = output + + print(f"done loading {key} for chunk #{chunk_num+1}") #bc zero indexing + + +#process in chunks due to edt taking to long +def surface_area_chunks(mask, name, nid, num_chunks): + neuron = (mask==nid).astype(np.uint16) + other_neurons = ((mask!=nid) & (mask!=0)).astype(np.uint16) + + current_chunk = 0 + total_surface_area = 0 + intersection_surface_area = 0 + + while(current_chunk!=num_chunks): #this loop runs [num_chunks] times + print(f"processing chunk {current_chunk+1}") + load_chunks(neuron, current_chunk, num_chunks, "neuron") + #cache["neuron"] now holds the current chunk of neuron mask + load_chunks(other_neurons, current_chunk, num_chunks, "other_neurons") + #cache["other_neurons"] now holds the current chunk of other_neurons mask + #these files are cut off on each side (no padding) -> expansions not affected + + print("start edt for border") + neuron_edt = edt(cache["neuron"]) + print("end edt for border") + neuron_border = ((neuron_edt>0) * (neuron_edt<=1)).astype(np.uint16) + + border_voxels = int(neuron_border.sum()) + total_surface_area += (border_voxels*32**2) #output 1 + + + print("start edt for expansion") + other_neurons_edt = edt(1-cache["other_neurons"]) + print("end edt for expansion") + expansion = ((other_neurons_edt>0) * (other_neurons_edt<=3)).astype(np.uint16) #3 voxels expansion + + intersection = np.bitwise_and(neuron_border, expansion) + intersection_voxels = int(intersection.sum()) + intersection_surface_area += (intersection_voxels*32**2) + + current_chunk += 1 + + print(f'{name} total SA (nm^2): {total_surface_area}') #output 1 + print(f'{name} near neuron SA (nm^2): {intersection_surface_area}\n') #output 2 + + + +if __name__ == "__main__": + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') + + #to_calculate = ['SHL17'] + to_calculate = neuron_dict.keys() + + chunking = True + num_chunks = 4 + + for name in to_calculate: + print(f"calculating surface area for {name}") + nid = neuron_name_to_id(name)[0] + load_data(name) + + if chunking: + surface_area_chunks(cache["box"], name, nid, num_chunks) + else: + surface_area(cache["box"], name, nid) + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/neuron_stitching/stitching_new.py b/ves_analysis/neuron_stitching/stitching_new.py new file mode 100644 index 0000000..2891ea0 --- /dev/null +++ b/ves_analysis/neuron_stitching/stitching_new.py @@ -0,0 +1,221 @@ +import numpy as np +import h5py +import sys +import yaml + +bbox = np.loadtxt('/data/projects/weilab/dataset/hydra/mask_mip1/bbox.txt').astype(int) +D0 = '/data/projects/weilab/dataset/hydra/results/' +D1 = '/home/rothmr/projects/stitched/stitched/' #for output + +#dict by neuron, all zyx [neuron]: (corner), (other corner) +boxes = {} +for row in bbox: + nid = row[0] + corner1 = (row[1], row[3], row[5]) + corner2 = (row[2], row[4], row[6]) + boxes[nid] = ((corner1), (corner2)) + + +cache={} +#use cache +def load_data(names_list): + print("start loading data") + + for name in names_list: + with h5py.File(f'{D0}neuron_{name}_30-32-32.h5', 'r') as f: + cache[name] = f["main"][:] + print("loaded ",name) + + +#range for a given axis, assume range is a1 we can safely write data + out[(bb2[0]-bb_all[0]) : (bb2[1]-bb_all[0]) + 1, \ + (bb2[2]-bb_all[2]) : (bb2[3]-bb_all[2]) + 1, \ + (bb2[4]-bb_all[4]) : (bb2[5]-bb_all[4]) + 1] = np.where(out_zero[(bb2[0]-bb_all[0]) : (bb2[1]-bb_all[0]) + 1, \ + (bb2[2]-bb_all[2]) : (bb2[3]-bb_all[2]) + 1, \ + (bb2[4]-bb_all[4]) : (bb2[5]-bb_all[4]) + 1], + nid[1] * cache[names[1]], + out[(bb2[0]-bb_all[0]) : (bb2[1]-bb_all[0]) + 1, \ + (bb2[2]-bb_all[2]) : (bb2[3]-bb_all[2]) + 1, \ + (bb2[4]-bb_all[4]) : (bb2[5]-bb_all[4]) + 1]) + + #now crop + cropped_bbox = find_crop(bb_all, bb1) + cropped_out = out[cropped_bbox[0]:cropped_bbox[1], cropped_bbox[2]:cropped_bbox[3], cropped_bbox[4]:cropped_bbox[5]] + + print("successfully merged with ", names[1]) + + return cropped_out + + +def stitch(name): + neuron_id_1 = neuron_name_to_id(name)[0] + + to_stitch = [] #neuron IDs + to_stitch_names = [] #names + for neuron_id_2 in boxes: + axes = (0,1,2) #xyz + first = boxes[neuron_id_1] + second = boxes[neuron_id_2] + + #if(neuron_id_1 != neuron_id_2): + #check if all axis ranges overlap - includes the neuron itself + if(ranges_overlap((first[0][0], first[1][0]), (second[0][0], second[1][0])) and + ranges_overlap((first[0][1], first[1][1]), (second[0][1], second[1][1])) and + ranges_overlap((first[0][2], first[1][2]), (second[0][2], second[1][2]))): + + #and check if it exists / is complete + if(neuron_id_2 in neuron_dict.values()): + to_stitch.append(neuron_id_2) + to_stitch_names.append(neuron_id_to_name([neuron_id_2])[0]) + else: + print("neuron ", neuron_id_2, " missing") + + #to_stitch should now contain IDs of: the current neuron plus all overlap box neurons + print("BOX OVERLAPS: ", to_stitch) + + to_load = to_stitch_names.copy() + + load_data(to_load) + print("done loading data") + + #create dataset + stitched = np.zeros(cache[name].shape, dtype=cache[name].dtype) + + #stitch together in a loop using the stitch_two method + for n in to_stitch: + print("shape of stitched file: ", stitched.shape) #stays constant for each adjacent neuron + print("seg ids in stitched file: ", np.unique(stitched)) + stitched = stitch_two(stitched, [neuron_id_1, n]) + + print("final seg ids in stitched file: ", np.unique(stitched)) + + fname = f'{D1}neuron_{name}_box_30-32-32.h5' + with h5py.File(fname, 'w') as f: + f.create_dataset("main", shape=stitched.shape, data=stitched) + + print("") + print("saved stitched file as ", fname) + + +if __name__ == "__main__": + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') + + names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "SHL17", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7"] + + to_stitch = names_20 + + for n in to_stitch: + print("") + print("stitching ", n) + stitch(n) + + + + + + + + + + + + diff --git a/ves_analysis/sample/sample_data/7-13_lv_label.txt b/ves_analysis/sample/sample_data/7-13_lv_label.txt new file mode 100644 index 0000000..6f29215 --- /dev/null +++ b/ves_analysis/sample/sample_data/7-13_lv_label.txt @@ -0,0 +1 @@ +(1:1),(2:1),(3:1),(4:1),(5:1),(6:1),(7:1),(10:1),(11:1),(14:1),(16:1),(17:1),(19:1),(20:1),(22:1),(23:1),(25:1),(26:1),(27:1),(28:1),(30:1),(32:1),(33:1),(34:1),(37:1),(40:1),(42:1),(43:1),(45:1),(49:1),(51:1),(52:1),(53:1),(55:1),(56:1),(57:1),(58:1),(59:1),(60:1),(61:1),(62:1),(63:1),(64:1),(66:1),(69:1),(71:1),(72:1),(73:1),(74:1),(75:1),(76:1),(77:1),(79:1),(80:1),(81:1),(82:1),(85:1),(87:1),(91:1),(92:1),(94:1),(95:1),(96:1),(98:1),(100:1),(101:1),(104:1),(105:1),(107:1),(108:1),(110:1),(111:1),(112:1),(113:1),(115:1),(116:1),(117:1),(118:1),(119:1),(120:1),(121:1),(122:1),(123:1),(124:1),(125:1),(127:1),(128:1),(129:1),(130:1),(133:1),(136:1),(138:1),(139:1),(141:1),(142:1),(145:1),(146:1),(147:1),(148:1),(149:1),(150:1),(151:1),(152:1),(153:1),(155:1),(156:1),(157:1),(158:1),(160:1),(161:1),(162:1),(163:1),(165:1),(166:1),(167:1),(168:1),(169:1),(170:1),(171:1),(172:1),(175:1),(177:1),(178:1),(180:1),(181:1),(182:1),(183:1),(184:1),(186:1),(187:1),(193:1),(195:1),(196:1),(197:1),(201:1),(204:1),(205:1),(210:1),(211:1),(213:1),(215:1),(217:1),(218:1),(219:1),(223:1),(225:1),(226:1),(228:1),(229:1),(230:1),(231:1),(233:1),(235:1),(236:1),(237:1),(238:1),(239:1),(240:1),(242:1),(244:1),(246:1),(248:1),(249:1),(250:1),(251:1),(252:1),(253:1),(254:1),(255:1),(8:2),(9:2),(12:2),(29:2),(31:2),(38:2),(39:2),(41:2),(46:2),(50:2),(67:2),(78:2),(83:2),(84:2),(90:2),(97:2),(99:2),(103:2),(106:2),(131:2),(132:2),(143:2),(154:2),(159:2),(164:2),(173:2),(174:2),(176:2),(179:2),(185:2),(189:2),(190:2),(194:2),(198:2),(202:2),(203:2),(207:2),(220:2),(221:2),(224:2),(234:2),(243:2),(245:2),(13:3),(15:3),(18:3),(21:3),(24:3),(35:3),(36:3),(44:3),(47:3),(48:3),(54:3),(65:3),(68:3),(70:3),(86:3),(88:3),(89:3),(93:3),(102:3),(109:3),(114:3),(126:3),(134:3),(135:3),(137:3),(140:3),(144:3),(188:3),(191:3),(192:3),(199:3),(206:3),(208:3),(209:3),(212:3),(214:3),(216:3),(222:3),(227:3),(232:3),(241:3),(247:3) \ No newline at end of file diff --git a/ves_analysis/sample/sample_data/7-13_mask.h5 b/ves_analysis/sample/sample_data/7-13_mask.h5 new file mode 100644 index 0000000000000000000000000000000000000000..b552ec6ddf62f6c1686c60259464214ae0c9a11f GIT binary patch literal 526278 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�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 CI;oBT literal 0 HcmV?d00001 diff --git a/ves_analysis/sample/sample_data/7-13_pred_filtered.h5 b/ves_analysis/sample/sample_data/7-13_pred_filtered.h5 new file mode 100644 index 0000000000000000000000000000000000000000..13107d64ff07b8e10f0c8a9a3ba57834a94e5f1f GIT binary patch literal 242774 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 V^+e@zMeA5_H~cK#F6)B({{mGqS2h3u literal 0 HcmV?d00001 diff --git a/ves_analysis/sample/sample_outputs/lv_thresholds.xlsx b/ves_analysis/sample/sample_outputs/lv_thresholds.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6bfed3ebaf3d6e86116bbb83be0ddd7e608cf9ac GIT binary patch literal 5342 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 literal 0 HcmV?d00001 diff --git a/ves_analysis/sample/sample_outputs/vesicle_stats.xlsx b/ves_analysis/sample/sample_outputs/vesicle_stats.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..886c66a506727f645e84824e8610e5019c7257a9 GIT binary patch literal 5684 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 UOhf0 and density<=percentiles[0]): + label = 1 + #orange - assign 2 + elif(density>percentiles[0] and density<=percentiles[1]): + label = 2 + #red - assign 3 + elif(density>percentiles[1]): + label = 3 + else: + label = 0 + return label + + +''' +#optional +def plot(d): + data = np.ndarray(d.values()) + plt.hist(data, bins=10, edgecolor='black', alpha=0.7) + plt.show() +''' + + +def generate(vesicles, d): + #squish dict into a Gaussian distribution + percentiles = np.percentile(list(d.values()), [33, 66]) + print("percentiles: ", percentiles) + + unique_labels = np.unique(vesicles) + num_labels = len(unique_labels) - 1 #minus the bg label + print("checkpoint 0") + + v = np.zeros(vesicles.shape, dtype=vesicles.dtype) #new file + + #first relabel for vesicles 1, 2, 3 + if np.any(vesicles == 1): + v[vesicles==1] = convert(d[1], percentiles) + if np.any(vesicles == 2): + v[vesicles==2] = convert(d[2], percentiles) + if np.any(vesicles == 3): + v[vesicles==3] = convert(d[3], percentiles) + + print("checkpoint 1") + + for label, new_label in d.items(): + print("label: ", label, "new label: ", convert(new_label, percentiles)) + #avoid relabeling vesicles 0, 1, 2, 3 again + if (label not in (0,1,2,3)): + v[vesicles==label] = convert(new_label, percentiles) + print("loop done") + + #save "v" as a new file + with h5py.File(f"color_test.h5", "w") as f: + f.create_dataset("main", shape=v.shape, data=v) + print(f"saved file as color_test.h5") + + +if __name__ == "__main__": + path = "KR6_density_mapping.txt" + d = to_dict(path) + print("dict initialized") + load_data() + generate(cache["lv"], d) #saves file + + + + + + + + diff --git a/ves_analysis/threshold_density_map/color_new_ng.py b/ves_analysis/threshold_density_map/color_new_ng.py new file mode 100644 index 0000000..5d0200b --- /dev/null +++ b/ves_analysis/threshold_density_map/color_new_ng.py @@ -0,0 +1,122 @@ +import numpy as np +import h5py +import neuroglancer +import imageio +import socket +from contextlib import closing +import yaml +import os +import gc +from PIL import Image +import matplotlib.pyplot as plt + + +bbox = np.loadtxt('/data/projects/weilab/dataset/hydra/mask_mip1/bbox.txt').astype(int) + +cache = {} + +#only store one at a time +def load_data(name): + with h5py.File(f"color_test.h5", "r") as f: + cache["vesicles"] = np.array(f["main"][:]) #30-16-16 + with h5py.File(f"/data/projects/weilab/dataset/hydra/results/neuron_{name}_30-32-32.h5") as f: + cache["mask"] = np.array(f["main"][:]) #30-32-32 + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + return s.getsockname()[1] + +ip = 'localhost' #or public IP of the machine for sharable display +port = find_free_port() #change to an unused port number +neuroglancer.set_server_bind_address(bind_address=ip,bind_port=port) +viewer=neuroglancer.Viewer() + + +# SNEMI (# 3d vol dim: z,y,x) +D0='./' +res1 = neuroglancer.CoordinateSpace( + names=['z', 'y', 'x'], + units=['nm', 'nm', 'nm'], + scales = [30,64,64]) + +res2 = neuroglancer.CoordinateSpace( + names=['z','y','x'], + units=['nm', 'nm', 'nm'], + scales=[30,16,16]) + +res3 = neuroglancer.CoordinateSpace( + names=['z','y','x'], + units=['nm', 'nm', 'nm'], + scales=[30,32,32]) + + +def ngLayer(data,res,oo=[0,0,0],tt='segmentation'): + return neuroglancer.LocalVolume(data,dimensions=res,volume_type=tt,voxel_offset=oo) + +def neuron_name_to_id(name): + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + +#find the min bbox coord (bottom left corner) for this neuron, return as list of 3 coords +def get_offset(name): #returns in 30-8-8 + nid = neuron_name_to_id(name)[0] + bb = bbox[bbox[:,0]==nid, 1:][0] + output = [bb[0], bb[2], bb[4]] + + return output + + +def screenshot(path='temp.png', save=True, show=True, size=[4096, 4096]): + ss = viewer.screenshot(size=size).screenshot.image_pixels + if save: + Image.fromarray(ss).save(path) + if show: + plt.imshow(ss) + plt.show() + + +with viewer.txn() as s: + + ##COLOR CODED VIS + names = ['KR6'] #test + + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') + + for name in names: + path = f"color_test.h5" + if(os.path.exists(path)): + load_data(name) #cache["vesicles"] is updated to the current neuron's vesicle data + print(f"done loading data for {name}") + + vesicles = cache["vesicles"] + mask = cache["mask"] + + s.layers.append(name=f'{name}_mask',layer=ngLayer(mask,res=res3, tt='segmentation')) #30-32-32 + s.layers.append(name=f'{name}_yellow',layer=ngLayer((vesicles==1).astype('uint16'),res=res2, tt='segmentation')) + s.layers.append(name=f'{name}_orange',layer=ngLayer((vesicles==2).astype('uint16'),res=res2, tt='segmentation')) + s.layers.append(name=f'{name}_red',layer=ngLayer((vesicles==3).astype('uint16'),res=res2, tt='segmentation')) + + print(f"added all layers for {name}") + else: + print(f"file for {name} does not exist") + + del mask, vesicles + cache.clear() + gc.collect() + + +print(viewer) + + + + + + + diff --git a/ves_analysis/vesicle_counts/LV_type_counts.py b/ves_analysis/vesicle_counts/LV_type_counts.py new file mode 100644 index 0000000..196fffd --- /dev/null +++ b/ves_analysis/vesicle_counts/LV_type_counts.py @@ -0,0 +1,95 @@ +import numpy as np +import h5py +import re +import argparse + +def read_txt_to_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + +def read_txt_to_list(file_path): + result_list = [] + with open(file_path, 'r') as file: + for line in file: + line = re.sub(r'[\[\]{}]','',line) + result_list.extend(map(int, line.strip().split())) + + return result_list + +if __name__ == "__main__": + #neurons_list = ['KR4'] + + parser = argparse.ArgumentParser() + parser.add_argument('--name', type=str, help='neuron name') + args = parser.parse_args() + name = args.name + + neurons_list = [name] + + + for name in neurons_list: + print(f"----{name}----") + dictionary = read_txt_to_dict(f"types_lists/{name}_types.txt") + lst = read_txt_to_list(f"lists/within_{name}.txt") + print("length of dict: ", len(dictionary.keys())) + print("length of list: ", len(lst)) + + #total by type count stuff + print("---total counts---") + total_num_unrecognized=0 + total_num_CV=0 + total_num_DV=0 + total_num_DVH=0 + + for vesicle in dictionary.keys(): + if(dictionary[vesicle]==0): + total_num_unrecognized+=1 + if(dictionary[vesicle]==1): + total_num_CV+=1 + if(dictionary[vesicle]==2): + total_num_DV+=1 + if(dictionary[vesicle]==3): + total_num_DVH+=1 + + print(f"CV: {total_num_CV}, DV: {total_num_DV}, DVH: {total_num_DVH}, unrecognized: {total_num_unrecognized}") + print("total length check: ", total_num_unrecognized+total_num_CV+total_num_DV+total_num_DVH==len(dictionary.keys())) + + + print("---near neuron counts---") + num_unrecognized=0 + num_CV=0 + num_DV=0 + num_DVH=0 + + for vesicle in lst: + if vesicle in dictionary.keys(): + if(dictionary[vesicle]==0): + num_unrecognized+=1 + if(dictionary[vesicle]==1): + num_CV+=1 + if(dictionary[vesicle]==2): + num_DV+=1 + if(dictionary[vesicle]==3): + num_DVH+=1 + else: + print(f"error in mapping at key {vesicle}") + + print(f"CV: {num_CV}, DV: {num_DV}, DVH: {num_DVH}, unrecognized: {num_unrecognized}") + print("total length check: ", num_unrecognized+num_CV+num_DV+num_DVH==len(lst)) + + + + + + + + diff --git a/ves_analysis/vesicle_counts/SV_type_counts.py b/ves_analysis/vesicle_counts/SV_type_counts.py new file mode 100644 index 0000000..26de62c --- /dev/null +++ b/ves_analysis/vesicle_counts/SV_type_counts.py @@ -0,0 +1,112 @@ +import numpy as np +from numpy import load +import h5py +import re + +#different script from lv type counts since sv data/mappings are in different format + +cache = {} + +#initialize dicts in cache +names_13 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", "RGC2", "KM4", "SHL17", "NET12"] + +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", "RGC2", "KM4", "SHL17", + "NET12", "NET10", "NET11", "PN7", "SHL18", "SHL24", "SHL28", "RGC7"] + +names_7 = ["NET10", "NET11", "PN7", "SHL18", "SHL24", "SHL28", "RGC7"] + +for name in names_20: + dictionary = dict() + cache[f"{name}"] = dictionary + + +def read_txt_to_list(file_path): + result_list = [] + with open(file_path, 'r') as file: + for line in file: + line = re.sub(r'[\[\]{}]','',line) + result_list.extend(map(int, line.strip().split())) + + return result_list + + +if __name__ == "__main__": + + #fill in dicts for all neurons + with np.load("sv_types/SV_types.npz") as data: + ''' + #check to make sure same length + print("length of data[ids]: ", len(data["ids"])) + print("length of data[embeddings]: ", len(data["embeddings"])) + ''' + + current = 0 + while (current0): + vesicle_type = 4 + #SCV + if(embedding<0): + vesicle_type = 5 + + + cache[name] = {**cache[name], vesicle_id:vesicle_type} #update dict for the [name] neuron + current+=1; + + print("initialized all types mapping dictionaries") + + + neurons_list = ["SHL28"] + + #print out counts for each neuron dict + for name in neurons_list: + print(f"----{name}----") + dictionary = cache[name] #format is vid:type for this neuron + lst = read_txt_to_list(f"sv_nn/sv_nn_{name}.txt") #near neuron IDs list + + print("total num vesicles: ", len(dictionary.keys())) + print("total NN vesicles: ", len(lst)) #near neuron + + #total by type count stuff + print("---total counts---") + total_num_SDV=0 #positive embedding, black + total_num_SCV=0 #negative embedding, white + + for vesicle in dictionary.keys(): + if(dictionary[vesicle]==4): + total_num_SDV+=1 + if(dictionary[vesicle]==5): + total_num_SCV+=1 + + print(f"SCV: {total_num_SCV}, SDV: {total_num_SDV}") + print("total length check: ", total_num_SDV+total_num_SCV==len(dictionary.keys())) + + + print("---near neuron counts---") + nn_SDV = 0 + nn_SCV = 0 + + for vesicle in lst: #the near neuron IDs list + if vesicle in dictionary.keys(): + if(dictionary[vesicle]==4): + nn_SDV+=1 + if(dictionary[vesicle]==5): + nn_SCV+=1 + + else: + print(f"error in mapping at key {vesicle}") + + print(f"nn SCV: {nn_SCV}, nn SDV: {nn_SDV}") + print("total length check: ", nn_SDV+nn_SCV==len(lst)) + + + + + + + + diff --git a/ves_analysis/vesicle_counts/pointcloud_near_counts.py b/ves_analysis/vesicle_counts/pointcloud_near_counts.py new file mode 100644 index 0000000..59986b1 --- /dev/null +++ b/ves_analysis/vesicle_counts/pointcloud_near_counts.py @@ -0,0 +1,468 @@ +import h5py +import numpy as np +import pandas as pd +import scipy.stats as stats +import re +import yaml +import edt +import ast +import gc +import argparse + +#also install openpyxl into conda env + +#export totals and (mean, sem, n) over all neurons + +#ONLY CONSIDER LV + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D1 = '/home/rothmr/projects/stitched/stitched/' #stitched box files source dir +sample_dir = '/home/rothmr/hydra/sample/' + +cache = {} + + +#use for full names list +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7", "SHL17"] + +#neuron ID and type info +neuron_info = {"KR4": [1, 5], "KR5": [2, 2], "KR6": [4, 2], "SHL55": [3, 5], "PN3": [10, 3], "LUX2": [11, 5], "SHL20": [17, 5], "KR11": [8, 5], "KR10": [7, 4], + "RGC2": [6, 5], "KM4": [9, 5], "NET12": [15, 5], "NET10": [16, 1], "NET11": [14, 1], "PN7": [12, 5], "SHL18": [18, 1], + "SHL24": [5, 1], "SHL28": [20, 1], "RGC7": [13, 1], "SHL17": [19, 4]} + + + +def read_txt_to_dict(name, which): + results_dict = {} + + if(name=="sample"): + file_path = f'{sample_dir}sample_outputs/sample_com_mapping.txt' + + else: + file_path = f'/home/rothmr/hydra/meta/new_meta/{name}_{which}_com_mapping.txt' + + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_str = re.sub(r'[\(\)\,]', '', coords_str) #strip parens + #print("cleaned: ", coords_str) + + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + + attributes = ast.literal_eval(value_string) + results_dict[tuple(coords)]=list(attributes) + + return results_dict + + +#takes in segid->classification labeling file +def lv_labels_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + + +def load_data(name): #load into cache + if(name=="sample"): + file_path = f'{sample_dir}sample_data/7-13_mask.h5' + else: + file_path = f"{D1}neuron_{name}_box_30-32-32.h5" + + with h5py.File(file_path, 'r') as f: #stitched mask, contains everything w segids + cache["box"] = np.array(f["main"]).astype(int) + print("done loading neuron box") + + cache["lv_mapping"] = read_txt_to_dict(name, "lv") + #cache["sv_mapping"] = read_txt_to_dict(name, "sv") #not needed for near neuron counts + + + +#outputs the num of vesicles within and the total +#9 means just everything +def calculate_vesicles_within(name, mask_name, which=9): #use data from cache - after loading + #note that all data in the mapping txt file is in nm + mask = cache[mask_name] + + #lv + lv_mapping = cache["lv_mapping"] #com-> attributes metadata + + if(name=="sample"): + types_dict_path = f"{sample_dir}sample_data/7-13_lv_label.txt" + + else: + types_dict_path = f"/home/rothmr/hydra/types/new_types/new_v0+v2/{name}_lv_label.txt" + + labels_dict = lv_labels_dict(types_dict_path) + + total_num = 0 + num_in_mask = 0 + + for com, attributes in lv_mapping.items(): + #find subtype + subtype = 0 + label = int(attributes[1][3:]) #just the number; could be overlap + + if(label in labels_dict.keys()): + subtype = labels_dict[label] + else: #if doesn't exist in the dict, set to 0 - we ignore unclassified vesicles + subtype = 0 + + + ##now, subtype should have the correct value + if((which==9 and subtype!=0) or (subtype==which)): #if we are including everything, or if subtype matches up + #then consider this vesicle + total_num +=1 + + voxels_com = [com[0], com[1]/4, com[2]/4] #downsample since mask in 30-32-32 + voxels_com = np.round(voxels_com).astype(int) #round for indexing in the mask + if (mask[tuple(voxels_com)]!=0): #boolean + num_in_mask += 1 + + print("total num: ", total_num) + print("num in region: ", num_in_mask) + + + return total_num, num_in_mask + + +def expand_mask(original_mask, threshold_nm, res): #threshold in physical units, res in xyz + expanded_mask = np.copy(original_mask) + + print("begin distance transform for expansion") + dt = edt.edt(1 - original_mask, anisotropy=(res[2],res[1],res[0])) #needs to be in xyz by default for edt + print("end distance transform for expansion") + doubled_perimeter = dt <= threshold_nm #all in nm + expanded_mask[doubled_perimeter] = 1 + + expanded_mask_binary = (expanded_mask>=1).astype(int) + return expanded_mask_binary + +#takes in a list of masks, outputs a binary mask +def mask_intersection(mask_list): #assume same size files, assume list has size at least 1 + intersection = np.bitwise_and.reduce(mask_list) + return intersection + +#calculate the volume given a boolean mask & convert to nm^3 units +def calculate_volume_nm(mask, res): + binary_mask = (mask>=1).astype(int) + volume_voxels = np.sum(binary_mask) + volume_nm = volume_voxels * (res[0]*res[1]*res[2]) + return volume_nm + + +def neuron_name_to_id(name): #for a list of names and returns list?? + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + +if __name__ == "__main__": + + neuron_dict = read_yml('/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') + names = names_20 + mask_res = [30, 32, 32] + + results = [] #for xlxs export + + all_percentages_in = [] + + + ##### + parser = argparse.ArgumentParser() + parser.add_argument("--which_neurons", type=str, help="all or sample?") #enter as "all" or "sample" + parser.add_argument("--target_segid", type=float, help="target segid") #enter as an integer, only if sample + parser.add_argument("--lv_threshold", type=float, help="lv threshold") + parser.add_argument("--cv_threshold", type=float, help="cv threshold") + parser.add_argument("--dv_threshold", type=float, help="dv threshold") + parser.add_argument("--dvh_threshold", type=float, help="dvh threshold") + args = parser.parse_args() + which_neurons = args.which_neurons + target_segid = args.target_segid + lv_threshold = args.lv_threshold + cv_threshold = args.cv_threshold + dv_threshold = args.dv_threshold + dvh_threshold = args.dvh_threshold + + + #ensure which_neurons is entered + if(args.which_neurons is None): + parser.error("error - must enter all or sample for --which_neurons") + + #ensure target segid and thresholds added if sample + if(args.which_neurons=="sample" and target_segid is None): + parser.error("--target_segid required if --which_neurons is sample") + + if(args.which_neurons=="sample" and lv_threshold is None): + parser.error("--lv_threshold required") + + if(args.which_neurons=="sample" and cv_threshold is None): + parser.error("--cv_threshold required") + + if(args.which_neurons=="sample" and dv_threshold is None): + parser.error("--dv_threshold required") + + if(args.which_neurons=="sample" and dvh_threshold is None): + parser.error("--dvh_threshold required") + + ##### + + if(which_neurons=="sample"): + name = "sample" + neuron_id = 62 + neuron_type = "n/a" #for the neuron typings, not relevant for sample + load_data(name) + print(f"loaded data for {name}") + nid = target_segid + neurons = cache["box"] + + #extract regions of interest from mask + neurons = cache["box"] + + neuron_only = np.zeros(neurons.shape, dtype=neurons.dtype) + neuron_only[neurons==nid] = 1 #binary mask + + other_neurons = np.zeros(neurons.shape, dtype = neurons.dtype) + other_neurons[(neurons!=nid) & (neurons!=0)] = 1 #binary mask + print("final size of other_neurons mask in voxels: ", np.sum(other_neurons)) #error checking + + + print("LV") + expanded_others = expand_mask(other_neurons, lv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections") #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "LV", total_num, nn_lv, num_far, lv_threshold]) + + print(f"{name} LV done \n") + + + + #cv + print("CV") + expanded_others = expand_mask(other_neurons, cv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=1) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "CV", total_num, nn_lv, num_far, cv_threshold]) + print(f"{name} CV done \n") + + + + #dv + print("DV") + expanded_others = expand_mask(other_neurons, dv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=2) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "DV", total_num, nn_lv, num_far, dv_threshold]) + print(f"{name} DV done \n") + + + + #dvh + print("DVH") + expanded_others = expand_mask(other_neurons, dv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=3) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "DVH", total_num, nn_lv, num_far, dvh_threshold]) + + print(f"{name} DVH done\n") + + + + #loop thru all neurons + elif(which_neurons=="all"): + names_list = names_20 + for name in names_list: + #neuron id/type info + neuron_id = neuron_info[name][0] + neuron_type = neuron_info[name][1] + + + load_data(name) #load neurons and mappings + print(f"loaded data for {name}") + nid = neuron_name_to_id(name) + + ### + + #extract regions of interest from mask + neurons = cache["box"] + + neuron_only = np.zeros(neurons.shape, dtype=neurons.dtype) + neuron_only[neurons==nid] = 1 #binary mask + + other_neurons = np.zeros(neurons.shape, dtype = neurons.dtype) + + #restrict to only neurons w these names: + included_neuron_types = [1,2,3] + other_included_names = ["SHL29", "SHL53", "PN8", "SHL26", "SHL51", "KM1", "KM2"] + total_to_include = [n for n in neuron_dict.keys() if ((n in other_included_names) or (n in neuron_info.keys() and neuron_info[n][1] in included_neuron_types))] + included_ids = [neuron_dict[n] for n in total_to_include] #turns list of names into list of IDs + print("included ids: ", included_ids) #overall + print("total num of IDs: ", len(neuron_dict), "num of potentially included IDs: ", len(included_ids)) + + other_neurons[(neurons!=nid) & (neurons!=0) & np.isin(neurons, included_ids)] = 1 #binary mask + print("final size of other_neurons mask in voxels: ", np.sum(other_neurons)) + + + + removed_neurons = [] + for segid in np.unique(neurons): + if(segid not in included_ids): + removed_neurons.append(segid) + print("removed neurons: ", removed_neurons) + if(removed_neurons==[0]): + print("NO CHANGE for ", name) + + + + print("LV") + expanded_others = expand_mask(other_neurons, lv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections") #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "LV", total_num, nn_lv, num_far, lv_threshold]) + + print(f"{name} LV done \n") + + + + #cv + print("CV") + expanded_others = expand_mask(other_neurons, cv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=1) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "CV", total_num, nn_lv, num_far, cv_threshold]) + print(f"{name} CV done \n") + + + + #dv + print("DV") + expanded_others = expand_mask(other_neurons, dv_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=2) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "DV", total_num, nn_lv, num_far, dv_threshold]) + print(f"{name} DV done \n") + + + + #dvh + print("DVH") + expanded_others = expand_mask(other_neurons, dvh_threshold, mask_res) + print("mask expanding done") + cache["intersections"] = mask_intersection((neuron_only, expanded_others)) + print("size of intersection region: ", np.sum(cache["intersections"])) + print("mask intersection done") + total_num, nn_lv = calculate_vesicles_within(name, "intersections", which=3) #name of cache file + num_far = total_num-nn_lv + print("num outside of region: ", num_far) + del expanded_others, cache["intersections"] + gc.collect() + #append to individual row of the results output + results.append([name, neuron_id, neuron_type, "DVH", total_num, nn_lv, num_far, dvh_threshold]) + + print(f"{name} DVH done\n") + + + + df = pd.DataFrame(results, columns=["Neuron", "Neuron ID", "Neuron type", "Vesicle type", "Total num", "Near neuron", "Not near neuron", "Nearness threshold (nm)"]) + if(which_neurons=="all"): + export_path = "/home/rothmr/hydra/sheet_exports/lv_near_counts.xlsx" + elif(which_neurons=="sample"): + export_path = f"{sample_dir}sample_outputs/sample_near_counts.xlsx" + df.to_excel(export_path, index=False) + print("export done") + print("done") + + + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/vesicle_counts/pointcloud_soma_counts.py b/ves_analysis/vesicle_counts/pointcloud_soma_counts.py new file mode 100644 index 0000000..dfd75ff --- /dev/null +++ b/ves_analysis/vesicle_counts/pointcloud_soma_counts.py @@ -0,0 +1,130 @@ +import h5py +import numpy as np +import pandas as pd +import scipy.stats as stats +import re + +#refactoring counts.py - for the soma counts, pointcloud based approach +#export totals and (mean, sem, n) over all neurons + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D9 = 'sv/' + +cache = {} #stores "soma", "sv_mapping" + +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "SHL17", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7"] + +def read_txt_to_dict(name): + results_dict = {} + file_path = f"{D9}{name}_sv_com_mapping.txt" #only consider SV here + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + attributes=eval(value_string) + results_dict[tuple(coords)]=list(attributes) + + return results_dict + +def load_data(name): #load into cache + #soma + with h5py.File(f"{D0}soma_{name}_30-32-32.h5") as f: + cache["soma"] = (np.array(f["main"]) > 0).astype(int) #make boolean mask + #neuron + with h5py.File(f"{D0}neuron_{name}_30-32-32.h5") as f: + cache["neuron"] = (np.array(f["main"]) > 0).astype(int) #make boolean mask + #sv_mapping + cache["sv_mapping"] = read_txt_to_dict(name) + + + +#outputs the num of vesicles within and the total +def calculate_vesicles_within(mask_res): #use data from cache - after loading + #note that all data in the mapping txt file is in nm + soma = cache["soma"] + mapping = cache["sv_mapping"] + + total_num_sv = len(mapping) + num_in_soma = 0 + + coms_list = mapping.keys() #extract from mapping + for com in coms_list: + voxels_com = [com[0], com[1]/4, com[2]/4] #downsample since mask in 30-32-32 + voxels_com = np.round(voxels_com).astype(int) #round for indexing in the mask + if (soma[tuple(voxels_com)]!=0): #boolean + num_in_soma+=1 + + print("num in soma: ", num_in_soma) + return total_num_sv, num_in_soma + + +if __name__ == "__main__": + names = names_20 + mask_res = [30, 32, 32] + + results = [] #for xlxs export + + all_percentages_in = [] #percentages in soma + + for name in names: + load_data(name) #loads soma and mapping into cache + print(f"loaded data for {name}") + total_num_sv, num_in_soma = calculate_vesicles_within(mask_res) + num_outside_soma = total_num_sv - num_in_soma + percent_in_soma = num_in_soma/total_num_sv * 100 + percent_outside_soma = 100-percent_in_soma + + soma = cache["soma"] + neuron = cache["neuron"] + + #we can do this bc both masks are in the same res (30-32-32) + soma_vol = np.sum(soma) + neuron_vol = np.sum(neuron) + soma_size = (soma_vol/neuron_vol)*100 + + #append to individual row of the results output + results.append([name, total_num_sv, num_in_soma, num_outside_soma, percent_in_soma, percent_outside_soma, soma_size]) + + all_percentages_in.append(percent_in_soma) + + print(f"{name} done") + + results.append([]) #blank line to separate + + + #calculate mean, sem, n - this is of the list of percentages, across all neurons? + mean = np.mean(all_percentages_in) + sem = stats.sem(all_percentages_in) + n = len(all_percentages_in) + results.append(["total stats", f"mean percentage in soma: {mean}", f"sem: {sem}", f"n: {n}"]) + + df = pd.DataFrame(results, columns=["Neuron", "Total num SV", "Num in soma", "Num outside soma", "Percent in soma", "Percent outside soma", "Soma size"]) + df.to_excel("sv_soma_percentages.xlsx", index=False) + print("export done") + + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/vesicle_counts/slow_counts.py b/ves_analysis/vesicle_counts/slow_counts.py new file mode 100644 index 0000000..39065db --- /dev/null +++ b/ves_analysis/vesicle_counts/slow_counts.py @@ -0,0 +1,402 @@ +import edt +import h5py +import numpy as np +from scipy.ndimage import distance_transform_edt, gaussian_filter, zoom +import os +import yaml +import argparse + +#slow, inefficient method - ignore this if using pointcloud/metadata pipeline for vesicles + +cache = {} + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D1 = '/data/rothmr/hydra/stitched/' +D2 = '/data/rothmr/hydra/lists/' #LV near neuron IDs +D6 = '/data/rothmr/hydra/sv_nn/' #SV near neuron IDs + +#all files should be in the shape of the neuron mask file for the neuron name eg. "KR5" +#use for non chunking pipeline +def load_data(name): + print("begin loading data") + with h5py.File(f"{D1}neuron_{name}_box_30-32-32.h5", 'r') as f: #stitched mask, contains everything w segids + cache["box"] = f["main"][:] + print("done loading neuron box") + + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", 'r') as f: #high res large vesicles data for [name] + cache["lv"] = f["main"][:] + print("done loading LV") + + with h5py.File(f"{D0}vesicle_small_{name}_30-8-8.h5", 'r') as f: #high res small vesicles data for [name] + cache["sv"] = f["main"][:] + print("done loading SV") + +#use for chunking pipeline +def load_neurons(name): + print("begin loading data") + with h5py.File(f"{D1}neuron_{name}_box_30-32-32.h5", 'r') as f: #stitched mask, contains everything w segids + cache["box"] = f["main"][:] + print("done loading neuron box") + +#always splitting along the y axis; index starting at 'chunk 0' +#chunks are not padded -> coords are adjusted in the calculate_vesicles_within function +#returns the chunk_length variable for offset purposes +def load_lv_chunks(chunk_num, num_chunks): + print(f"begin loading LV chunk #{chunk_num+1}") #bc zero indexing + chunk_length = 0 #initialize for scope + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", 'r') as f: #high res large vesicles data for [name] + shape = f["main"].shape + dtype = f["main"].dtype + + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + + if(chunk_num!=num_chunks-1): + cache["lv_chunk"] = f["main"][:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + + else: #case of the last chunk + cache["lv_chunk"] = f["main"][:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + + print(f"done loading LV chunk #{chunk_num+1}") #bc zero indexing + + return chunk_length + + +#same logic as load_lv_chunks +def load_sv_chunks(chunk_num, num_chunks): + print(f"begin loading SV chunk #{chunk_num+1}") #bc zero indexing + chunk_length = 0 #initialize for scope + with h5py.File(f"{D0}vesicle_small_{name}_30-8-8.h5", 'r') as f: #high res large vesicles data for [name] + shape = f["main"].shape + dtype = f["main"].dtype + + #calculate chunk_length (last chunk might be this plus some remainder if this doesn't divide evenly) + chunk_length = (shape[1])//num_chunks #integer division, dividing up y axis length + + if(chunk_num!=num_chunks-1): + cache["sv_chunk"] = f["main"][:, chunk_num*chunk_length:(chunk_num+1)*chunk_length, :] + + else: #case of the last chunk + cache["sv_chunk"] = f["main"][:, chunk_num*chunk_length:, :] #go to end of the file - last chunk includes any leftover stuff + + print(f"done loading SV chunk #{chunk_num+1}") #bc zero indexing + + return chunk_length + + +def read_yml(filename): + with open(filename, 'r') as file: + data = yaml.safe_load(file) + return data + + +#calculate num of vesicles within any given mask; takes in mask and corresponding vesicles file +#vesicles file is in higher res +#if chunking - accounts for potential overlap vesicles across chunks +def calculate_vesicles_within(mask, vesicles=None, save_list=False, name=None, chunking=False, num_chunks=0, sv=False, lv=False): + bool_mask = (mask>=1) + bool_mask = zoom(bool_mask, (1, 4, 4), order=0) #change into ves file res + print("done changing mask file resolution") + + within_ids_set = set() #within the mask + total_ids_set = set() #within the full vesicles file + + if(chunking): #find lists of seg ids within the mask, for each chunk. merge uniquely into one list, then return final list length + if(lv): + + #loop thru each chunk and extend list every time + current_chunk = 0 + while(current_chunk!=num_chunks): #this loop runs [num_chunks] times + chunk_length = load_lv_chunks(current_chunk, num_chunks) + y_start = current_chunk * chunk_length + + #cases for last chunk or not + if(current_chunk!=num_chunks-1): #not last chunk + cache["mask_chunk"] = bool_mask[:, y_start:(current_chunk+1)*chunk_length, :] + else: #last chunk + cache["mask_chunk"] = bool_mask[:, y_start:, :] + + labeled_vesicles = cache["lv_chunk"] + mask_chunk = cache["mask_chunk"] + print("checkpoint 1") + + vesicle_coords = np.column_stack(np.nonzero(labeled_vesicles)) + print("checkpoint 2") + + unique_labels = set(labeled_vesicles.ravel()) + total_ids_set.update(unique_labels) #updating our set + + mask_values = mask_chunk[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]] + print("checkpoint 3") + + vesicles_within_mask = set(labeled_vesicles[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]][mask_values]) + within_ids_set.update(vesicles_within_mask) + + current_chunk += 1 + + print("total num LV: ", len(total_ids_set)-1) #minus bg label 0 + + #path for LV + if(save_list): + #export list as a txt file + path = f'{D2}within_{name}.txt' + with open(path, "w") as f: + for vesicle_id in within_ids_set: + f.write(f"{vesicle_id}\n") + print("list for LV near neuron exported") + + return len(within_ids_set) #num of unique ids within the mask + + #same logic as for lv + if(sv): + + #loop thru each chunk and extend list every time + current_chunk = 0 + while(current_chunk!=num_chunks): #this loop runs [num_chunks] times + chunk_length = load_sv_chunks(current_chunk, num_chunks) + y_start = current_chunk * chunk_length + + #cases for last chunk or not + if(current_chunk!=num_chunks-1): #not last chunk + cache["mask_chunk"] = bool_mask[:, y_start:(current_chunk+1)*chunk_length, :] + else: #last chunk + cache["mask_chunk"] = bool_mask[:, y_start:, :] + + labeled_vesicles = cache["sv_chunk"] + mask_chunk = cache["mask_chunk"] + print("checkpoint 1") + + vesicle_coords = np.column_stack(np.nonzero(labeled_vesicles)) + print("checkpoint 2") + + unique_labels = set(labeled_vesicles.ravel()) + total_ids_set.update(unique_labels) #updating our set + + mask_values = mask_chunk[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]] + print("checkpoint 3") + + vesicles_within_mask = set(labeled_vesicles[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]][mask_values]) + within_ids_set.update(vesicles_within_mask) + + current_chunk += 1 + + print("total num SV: ", len(total_ids_set)-1) #minus bg label 0 + + #path for SV + if(save_list): + #export list as a txt file + path = f'{D6}sv_nn_{name}.txt' + with open(path, "w") as f: + for vesicle_id in within_ids_set: + f.write(f"{vesicle_id}\n") + print("list for SV near neuron exported") + + return len(within_ids_set) + + else: #no chunking + if(lv): + num_vesicles_within = 0 + + labeled_vesicles = vesicles + vesicle_coords = np.column_stack(np.nonzero(labeled_vesicles)) + + unique_labels = np.unique(labeled_vesicles) + num_labels = len(unique_labels) - 1 #minus bg label 0 + print("total num of vesicles: ", num_labels) + + vesicles_within_mask = np.zeros(num_labels, dtype=bool) + mask_values = bool_mask[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]] + vesicles_within_mask = np.unique(labeled_vesicles[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]][mask_values]) + within_ids_set.update(vesicles_within_mask) + num_vesicles_within = len(vesicles_within_mask) + + #path for LV + if(save_list): + #export vesicles_within_mask as a txt file + path = f'{D2}within_{name}.txt' + with open(path, "w") as f: + for vesicle_id in list(within_ids_set): + f.write(f"{str(vesicle_id)}\n") + print("list for LV near neuron exported") + + return num_vesicles_within + if(sv): + num_vesicles_within = 0 + + labeled_vesicles = vesicles + vesicle_coords = np.column_stack(np.nonzero(labeled_vesicles)) + + unique_labels = np.unique(labeled_vesicles) + num_labels = len(unique_labels) - 1 #minus bg label 0 + print("total num of vesicles: ", num_labels) + + vesicles_within_mask = np.zeros(num_labels, dtype=bool) + mask_values = bool_mask[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]] + vesicles_within_mask = np.unique(labeled_vesicles[vesicle_coords[:, 0], vesicle_coords[:, 1], vesicle_coords[:, 2]][mask_values]) + within_ids_set.update(vesicles_within_mask) + num_vesicles_within = len(vesicles_within_mask) + + #path for SV + if(save_list): + #export vesicles_within_mask as a txt file + path = f'{D6}sv_nn_{name}.txt' + with open(path, "w") as f: + for vesicle_id in list(within_ids_set): + f.write(f"{str(vesicle_id)}\n") + print("list for SV near neuron exported") + + return num_vesicles_within + + +#use if neurons and vesicles files have different res (testing phase) +def convert_coords(original_coords, original_shape, original_res, target_shape, target_res): + relative_coord = np.array(original_coords) / np.array(original_shape) + target_coord = np.round(relative_coord * np.array(target_shape)).astype(int) + return target_coord + +def expand_mask(original_mask, threshold_nm, res): #threshold in physical units, res in xyz + expanded_mask = np.copy(original_mask) + + print("begin distance transform for expansion") + dt = edt.edt(1 - original_mask, anisotropy=(res[2],res[1],res[0])) #needs to be in xyz by default for edt + print("end distance transform for expansion") + doubled_perimeter = dt <= threshold_nm #all in nm + expanded_mask[doubled_perimeter] = 1 + + expanded_mask_binary = (expanded_mask>=1).astype(int) + return expanded_mask_binary + + +#for usage for a singular mask, docked vesicle counts +def erode_mask(original_mask, threshold_nm, res): + eroded_mask = np.copy(original_mask) + + dt = edt.edt(1 - original_mask, anisotropy=(res[2],res[1],res[0])) #needs to be in xyz by default for edt + perimeter = dt <= threshold_nm #all in nm; double perim + eroded_mask[perimeter] = 0 + + eroded_mask_binary = (eroded_mask>=1).astype(int) + return eroded_mask_binary + + +def docked_vesicles(original_mask, vesicles, threshold_nm, res): #use 250nm + eroded_mask = erode_mask(original_mask, threshold_nm, res) + return calculate_vesicles_within(eroded_mask, vesicles), calculate_volume_nm(eroded_mask, res) + + +#for visualization uses only +def extract_perimeter(expanded_mask, original_mask): + #expanded mask should already be binary + #change original mask to binary + original_mask_binary = (original_mask>=1).astype(int) + original_mask_inverted = ~original_mask_binary + + #return everything in expanded but NOT in original + perimeter_only_mask = np.copy(expanded_mask) + perimeter_only_mask[perimeter_only_mask & original_mask_inverted] = 0 + + return perimeter_only_mask + + +#takes in a list of masks, outputs a binary mask +def mask_intersection(mask_list): #assume same size files, assume list has size at least 1 + intersection = np.bitwise_and.reduce(mask_list) + return intersection + + +#calculate the volume given a boolean mask & convert to nm^3 units +def calculate_volume_nm(mask, res): + binary_mask = (mask>=1).astype(int) + volume_voxels = np.sum(binary_mask) + volume_nm = volume_voxels * (res[0]*res[1]*res[2]) + return volume_nm + + +def neuron_name_to_id(name): + if isinstance(name, str): + name = [name] + return [neuron_dict[x] for x in name] + +if __name__ == "__main__": + neuron_dict = read_yml('/data/projects/weilab/dataset/hydra/mask_mip1/neuron_id.txt') + res1 = (30,8,8) + res2 = (30,32,32) + + chunking = True #use chunking only if memory issues + num_chunks = 4 + + #for calculating one neuron - set from command line + ''' + parser = argparse.ArgumentParser() + parser.add_argument('--name', type=str, help='neuron name') + args = parser.parse_args() + to_calculate = [args.name] + ''' + + to_calculate = ["KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", "RGC2", "KM4", "NET12"] #example usage + + for name in to_calculate: + print(f"-----------{name}-----------") + nid = neuron_name_to_id(name) + threshold = 1000 #nm + #docked_threshold = 250 + + if(chunking): + #only load neurons, load vesicles later in chunks + load_neurons(name) + + else: + #load everything + load_data(name) + + neurons = cache["box"] + + neuron_only = np.zeros(neurons.shape, dtype=neurons.dtype) + neuron_only[neurons==nid] = 1 #binary mask + print(f"Volume (in nm^3) of {name} only: ", calculate_volume_nm(neuron_only, res2)) + + other_neurons = np.zeros(neurons.shape, dtype = neurons.dtype) + other_neurons[(neurons!=nid) & (neurons!=0)] = 1 #binary mask + expanded_others = expand_mask(other_neurons, threshold, res2) + print("mask expanding done") + intersections = mask_intersection((neuron_only, expanded_others)) + print("mask intersection done") + print(f"Volume (in nm^3) of intersections for {name} and {threshold}nm: ", calculate_volume_nm(intersections, res2)) + print("\ndone initializing everything\n") + + + if(chunking): + #implement chunking + print(f"calculate using chunking with {num_chunks} chunks") + + + print("---LV---") + print(f"LV within neuron {name} & within {threshold}nm of another neuron: ", + calculate_vesicles_within(intersections, save_list=True, name=name, chunking=True, num_chunks=num_chunks, lv=True), "\n") + + print("---SV---") + print(f"SV within neuron {name} & within {threshold}nm of another neuron: ", + calculate_vesicles_within(intersections, save_list=True, name=name, chunking=True, num_chunks=num_chunks, sv=True)) + + + else: + #no chunking, pass in vesicle files directly as parameter + print("no chunking") + + print("---LV---") + print(f"LV within neuron {name} & within {threshold}nm of another neuron: ", + calculate_vesicles_within(intersections, vesicles=cache["lv"], save_list=True, name=name, lv=True), "\n") + + print("---SV---") + print(f"SV within neuron {name} & within {threshold}nm of another neuron: ", + calculate_vesicles_within(intersections, vesicles=cache["sv"], save_list=True, name=name, sv=True), "\n") + + + + + + + + + + diff --git a/ves_analysis/vesicle_stats/LUX2_density.py b/ves_analysis/vesicle_stats/LUX2_density.py new file mode 100644 index 0000000..53343aa --- /dev/null +++ b/ves_analysis/vesicle_stats/LUX2_density.py @@ -0,0 +1,277 @@ +import numpy as np +import h5py +import ast +import math +import re +import scipy.stats as stats + +#calculate average density value for vesicles in the ball, vs vesicles in the rest of the neuron +#normalize by number of vesicles in each of these regions + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D9 = '' + + +cache = {} +cache["dict"] = {} +cache["full_dict"] = {} #all attribute info + +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "SHL17", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7"] + +for name in names_20: + dictionary = dict() + cache[f"{name}"] = dictionary + + +#LUX2 +def load_data(): + #load pointcloud for LUX2 from metadata + sv_dictionary = read_txt_to_dict("LUX2", "sv") + com_to_density = {key:value[-1] for key, value in sv_dictionary.items()} + #add in lv + lv_dictionary = read_txt_to_dict("LUX2", "lv") + for key,value in lv_dictionary.items(): + com_to_density[key] = value[-1] + + print("loaded coms and densities") #from metadata + cache["dict"]=com_to_density + + for key,value in sv_dictionary.items(): + cache["full_dict"][key] = value + for key,value in lv_dictionary.items(): + cache["full_dict"][key] = value + + #load ball + path = f"LUX2_ball_manual.h5" + with h5py.File(path, "r") as f: + cache["ball"] = (np.array(f["main"]) > 0).astype(int) #make boolean mask + print("shape: ", f["main"].shape) + print("mask loaded") + +def read_txt_to_dict(name, which): + if(which=="sv"): + results_dict = {} + file_path = f"{D9}sv/{name}_sv_com_mapping.txt" #only consider SV here + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + attributes=eval(value_string) + results_dict[tuple(coords)]=list(attributes) + + if(which=="lv"): + results_dict = {} + file_path = f"{D9}lv/{name}_lv_com_mapping.txt" #only consider SV here + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + attributes=eval(value_string) + results_dict[tuple(coords)]=list(attributes) + + return results_dict + +def lv_labels_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + + +#return list of densities for vesicles within ball +def vesicles_within_ball(): + ball = cache["ball"] + original_res = [30, 8, 8] + ball_res = [30, 32, 32] + mapping = com_to_density + num_in_ball = 0 + + within_ball_densities = [] + outside_ball_densities = [] + ves_within_ball = 0 + ves_outside_ball = 0 + + LV_within_ball = 0 + CV_within_ball = 0 + DV_within_ball = 0 + DVH_within_ball = 0 + SV_within_ball = 0 + SDV_within_ball = 0 + SCV_within_ball = 0 + + LV_outside_ball = 0 + CV_outside_ball = 0 + DV_outside_ball = 0 + DVH_outside_ball = 0 + SV_outside_ball = 0 + SDV_outside_ball = 0 + SCV_outside_ball = 0 + + + coms_list = mapping.keys() #extract from mapping + for com in coms_list: + voxels_com = [com[0]*(original_res[0]/ball_res[0]), com[1]*(original_res[1]/ball_res[1]), com[2]*(original_res[2]/ball_res[2])] #change based on res + voxels_com = np.round(voxels_com).astype(int) #round for indexing in the mask + if (ball[tuple(voxels_com)]!=0): #boolean + within_ball_densities.append(com_to_density[com]) + ves_within_ball += 1 + + if(cache["full_dict"][com][0])=="lv": + LV_within_ball += 1 + label = (int)(cache["full_dict"][com][1][3:]) + lv_label_to_type = lv_labels_dict(f"types_lists/LUX2_types.txt") + subtype = lv_label_to_type[label] + + if(subtype==1): + CV_within_ball+=1 + if(subtype==2): + DV_within_ball+=1 + if(subtype==3): + DVH_within_ball+=1 + + else: #sv + SV_within_ball += 1 + label = (int)(cache["full_dict"][com][1][3:]) + sv_label_to_type = cache["LUX2"] + subtype = sv_label_to_type[label] + + if(subtype==4): + SDV_within_ball+=1 + if(subtype==5): + SCV_within_ball+=1 + + + else: + outside_ball_densities.append(com_to_density[com]) + ves_outside_ball += 1 + + if(cache["full_dict"][com][0])=="lv": + LV_outside_ball += 1 + label = (int)(cache["full_dict"][com][1][3:]) + lv_label_to_type = lv_labels_dict(f"types_lists/LUX2_types.txt") + subtype = lv_label_to_type[label] + + if(subtype==1): + CV_outside_ball+=1 + if(subtype==2): + DV_outside_ball+=1 + if(subtype==3): + DVH_outside_ball+=1 + + else: #sv + SV_outside_ball += 1 + label = (int)(cache["full_dict"][com][1][3:]) + sv_label_to_type = cache["LUX2"] + subtype = sv_label_to_type[label] + + if(subtype==4): + SDV_outside_ball+=1 + if(subtype==5): + SCV_outside_ball+=1 + + + print() + print("LV within ball: ", LV_within_ball) + print("CV within ball: ", CV_within_ball) + print("DV within ball: ", DV_within_ball) + print("DVH within ball: ", DVH_within_ball) + print("SV within ball: ", SV_within_ball) + print("SDV within ball: ", SDV_within_ball) + print("SCV within ball: ", SCV_within_ball) + + print() + print("LV outside ball: ", LV_outside_ball) + print("CV outside ball: ", CV_outside_ball) + print("DV outside ball: ", DV_outside_ball) + print("DVH outside ball: ", DVH_outside_ball) + print("SV outside ball: ", SV_outside_ball) + print("SDV outside ball: ", SDV_outside_ball) + print("SCV outside ball: ", SCV_outside_ball) + print() + + return within_ball_densities, outside_ball_densities + +if __name__ == "__main__": + name = "LUX2" + + with np.load("sv_types/SV_types.npz") as data: + current = 0 + while (current0): + vesicle_type = 4 + #SCV + if(embedding<0): + vesicle_type = 5 + + cache[name] = {**cache[name], vesicle_id:vesicle_type} #update dict for the [name] neuron + current+=1; + + print("initialized all types mapping dictionaries") + + load_data() + com_to_density = cache["dict"] + print("total num vesicles", len(com_to_density)) + #filter out to only coms in the ball + within_ball_densities, outside_ball_densities = vesicles_within_ball() + + print("average for within ball: ", np.mean(within_ball_densities)) + print("average for outside ball: ", np.mean(outside_ball_densities)) + + print("SEM for within ball: ", stats.sem(within_ball_densities)) + print("SEM for outside ball: ", stats.sem(outside_ball_densities)) + + print("num within ball: ", len(within_ball_densities)) + print("num outside ball: ", len (outside_ball_densities)) + + #export within ball densities + with open(f"LUX2/LUX2_within_ball_densities.txt", "w") as f: + for density in within_ball_densities: + f.write(str(density) + "\n") + + #export outside ball densities + with open(f"LUX2/LUX2_outside_ball_densities.txt", "w") as f: + for density in outside_ball_densities: + f.write(str(density) + "\n") + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/vesicle_stats/lv_thresholds.py b/ves_analysis/vesicle_stats/lv_thresholds.py new file mode 100644 index 0000000..d6b2431 --- /dev/null +++ b/ves_analysis/vesicle_stats/lv_thresholds.py @@ -0,0 +1,212 @@ +import numpy as np +import re +import scipy.stats as stats +import statistics +import pandas as pd +import ast +import os +import argparse + +#for finding thresholds for near neuron counts +#extract vesicle stats among all metadata to find overall mean and standard dev + +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7", "SHL17"] + +sample_dir = '/home/rothmr/hydra/sample/' + + +cache = {} #for SV types mapping dictionaries +for name in names_20: + dictionary = dict() + cache[f"{name}"] = dictionary + +#lv only +def read_txt_to_dict(name, which): + results_dict = {} + + if(name=="sample"): + file_path = f"{sample_dir}sample_outputs/sample_com_mapping.txt" + else: + file_path = f'/home/rothmr/hydra/meta/new_meta/{name}_{which}_com_mapping.txt' + + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_str = re.sub(r'[\(\)\,]', '', coords_str) #strip parens + + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + attributes = ast.literal_eval(value_string) + results_dict[tuple(coords)]=list(attributes) + + return results_dict + + +def lv_labels_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--which_neurons", type=str, help="all or sample?") #enter as "all" or "sample" + args = parser.parse_args() + which_neurons = args.which_neurons + + if(which_neurons=="sample"): + names = ["sample"] + elif(which_neurons=="all"): + names = names_20 + + #ensure which_neurons is entered + if(args.which_neurons is None): + parser.error("error - must enter all or sample for --which_neurons") + + + results = [] + + #initialize lists to store values for all neurons combined + all_lv_diameters = [] + all_cv_diameters = [] + all_dv_diameters = [] + all_dvh_diameters = [] + + for name in names: + print(f"---diameter stats for {name}---") + lv_dictionary = read_txt_to_dict(name, "lv") + + ####initialize stuff for LV diameters + + ####LV diameters + + if(name=="sample"): + labels_dict_path = f"{sample_dir}/sample_data/7-13_lv_label.txt" + else: + labels_dict_path = f"/home/rothmr/hydra/types/new_types/new_v0+v2/{name}_lv_label.txt" + + labels_dict = lv_labels_dict(labels_dict_path) + + ####types diameters + cv_diameters = [] + dv_diameters = [] + dvh_diameters = [] + print(f"length of dict for {name}: ", len(lv_dictionary.items())) + for com,attributes in lv_dictionary.items(): + label = int(attributes[1][3:]) #just the number; could be overlap + subtype = labels_dict[label] + diameter = attributes[3]*2 + + if(subtype==1): + cv_diameters.append(diameter) + + if(subtype==2): + dv_diameters.append(diameter) + + if(subtype==3): + dvh_diameters.append(diameter) + + + all_cv_diameters.extend(cv_diameters) + all_dv_diameters.extend(dv_diameters) + all_dvh_diameters.extend(dvh_diameters) + + num_lv_this_neuron = len(cv_diameters) + len(dv_diameters) + len(dvh_diameters) + print(f"{name} total num LV (minus extraneous and unclassified): ", num_lv_this_neuron) + + + ####now do calculations for all neurons combined - alr done in sheet + print("---all neurons combined stats---") + + ####diameters + all_lv_diameters = all_cv_diameters + all_dv_diameters + all_dvh_diameters + all_lv_diameters = np.array(all_lv_diameters) + mean = np.mean(all_lv_diameters) + stdev = statistics.stdev(all_lv_diameters) + sem = stats.sem(all_lv_diameters) + n = len(all_lv_diameters) + threshold = mean + (2*stdev) + print(f"ALL NEURONS - LV mean (diameters): {mean}, LV stdev: {stdev}, LV sem: {sem}, LV n: {n}, LV Threshold: {threshold}") + results.append(["LV", "TOTAL", "Diameter", mean, stdev, sem, n, threshold]) + + + all_cv_diameters = np.array(all_cv_diameters) + mean = np.mean(all_cv_diameters) + stdev = statistics.stdev(all_cv_diameters) + sem = stats.sem(all_cv_diameters) + n = len(all_cv_diameters) + threshold = mean + (2*stdev) + print(f"ALL NEURONS - CV mean (diameters): {mean}, CV stdev: {stdev}, CV sem: {sem}, CV n: {n}, CV Threshold: {threshold}") + results.append(["CV", "TOTAL", "Diameter", mean, stdev, sem, n, threshold]) + + all_dv_diameters = np.array(all_dv_diameters) + mean = np.mean(all_dv_diameters) + stdev = statistics.stdev(all_dv_diameters) + sem = stats.sem(all_dv_diameters) + n = len(all_dv_diameters) + threshold = mean + (2*stdev) + print(f"ALL NEURONS - DV mean (diameters): {mean}, DV stdev: {stdev}, DV sem: {sem}, DV n: {n}, DV Threshold: {threshold}") + results.append(["DV", "TOTAL", "Diameter", mean, stdev, sem, n, threshold]) + + all_dvh_diameters = np.array(all_dvh_diameters) + mean = np.mean(all_dvh_diameters) + stdev = statistics.stdev(all_dvh_diameters) + sem = stats.sem(all_dvh_diameters) + n = len(all_dvh_diameters) + threshold = mean + (2*stdev) + print(f"ALL NEURONS - DVH mean (diameters): {mean}, DVH stdev: {stdev}, DVH sem: {sem}, DVH n: {n}, DVH Threshold: {threshold}") + results.append(["DVH", "TOTAL", "Diameter", mean, stdev, sem, n, threshold]) + + + + df = pd.DataFrame(results, columns=["Type", "Neuron", "Measurement", "Mean", "Std Dev", "SEM", "N", "Near neuron threshold"]) + #df.to_excel("/home/rothmr/hydra/sheet_exports/lv_diameters.xlsx", index=False) + df.to_excel(f"{sample_dir}sample_outputs/lv_thresholds.xlsx", index=False) + print("export done") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/vesicle_stats/updated_extract_stats.py b/ves_analysis/vesicle_stats/updated_extract_stats.py new file mode 100644 index 0000000..10ef949 --- /dev/null +++ b/ves_analysis/vesicle_stats/updated_extract_stats.py @@ -0,0 +1,612 @@ +import numpy as np +import re +import scipy.stats as stats +import pandas as pd +import ast +import argparse + +#install openpyxl into conda env + +#extract volume and diameter stats +#note - set up directories for list exports before running + + +sample_dir = '/home/rothmr/hydra/sample/' + +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", + "RGC2", "KM4", "SHL17", "NET12", "NET10", "NET11", "PN7", "SHL18", + "SHL24", "SHL28", "RGC7"] + +cache = {} #for SV types mapping dictionaries +for name in names_20: + dictionary = dict() + cache[f"{name}"] = dictionary + +def read_txt_to_dict(name, which): + results_dict = {} + + if(name=="sample"): + file_path = f'{sample_dir}sample_outputs/sample_com_mapping.txt' + + else: + file_path = f'/home/rothmr/hydra/meta/new_meta/{name}_{which}_com_mapping.txt' + + with open(file_path, 'r') as file: + for line in file: + key_string, value_string = line.split(": ") + value_string = re.sub(r'\b(lv_\d+)\b', r"'\1'", value_string) #change into string literals + value_string = re.sub(r'\b(sv_\d+)\b', r"'\1'", value_string) #same for sv + + coords_str = key_string.replace("[", "").replace("]", "").strip() + coords_str = re.sub(r'[\(\)\,]', '', coords_str) #strip parens + #print("cleaned: ", coords_str) + + coords_list=coords_str.split() + coords = [float(coord) for coord in coords_list] + + #add quotes around "new" attribute + value_string = re.sub(r'(? attributes metadata + attributes = lv_mapping[com] + label = int(attributes[1][3:]) + + if(name=="sample"): + labels_dict_path = f"{sample_dir}sample_data/7-13_lv_label.txt" + else: + labels_dict_path = f"/home/rothmr/hydra/types/new_types/new_v0+v2/{name}_lv_label.txt" + + labels_dict = lv_labels_dict(labels_dict_path) + + if(label in labels_dict.keys()): + subtype = labels_dict[label] + else: #if doesn't exist in the dict, set to 0 (extraneous/unclassified) + print("error - subtype not found") + subtype = 0 + + return subtype + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--which_neurons", type=str, help="all or sample?") #enter as "all" or "sample" + args = parser.parse_args() + which_neurons = args.which_neurons + + #ensure which_neurons is entered + if(args.which_neurons is None): + parser.error("error - must enter all or sample for --which_neurons") + + #set export dir, neuron list, and SV processing based on which arg chosen + if(which_neurons=="sample"): + D_exports = f'{sample_dir}sample_outputs/' + sv_stats = False + names = ["sample"] + elif(which_neurons=="all"): + D_exports = f"/home/rothmr/hydra/list_exports/" + sv_stats = True + names = names_20 + + results = [] + + #initialize lists to store values for all neurons combined + all_lv_volumes = [] + all_cv_volumes = [] + all_dv_volumes = [] + all_dvh_volumes = [] + all_sv_volumes = [] + all_scv_volumes = [] + all_sdv_volumes = [] + all_all_volumes = [] + + all_lv_diameters = [] + all_cv_diameters = [] + all_dv_diameters = [] + all_dvh_diameters = [] + all_sv_diameters = [] + all_scv_diameters = [] + all_sdv_diameters = [] + all_all_diameters = [] + + + #only if running all neurons, not sample + if(sv_stats==True): + #fill in SV dicts for all neurons + with np.load("/home/rothmr/hydra/types/sv_types/SV_types_new.npz") as data: #UPDATED to new types mapping file for new classifications + print("initializing SV types mapping dictionaries") + current = 0 + while (current attributes metadata + + lv_volumes = [] + lv_diameters = [] + cv_volumes = [] + cv_diameters = [] + dv_volumes = [] + dv_diameters = [] + dvh_volumes = [] + dvh_diameters = [] + + + for com, attributes in lv_dictionary.items(): + subtype = find_subtype(name, com) + volume = attributes[2] + diameter = attributes[3]*2 #since value in attributes is radius + + if(subtype==1): + cv_volumes.append(volume) + lv_volumes.append(volume) + cv_diameters.append(diameter) + lv_diameters.append(diameter) + + if(subtype==2): + dv_volumes.append(volume) + lv_volumes.append(volume) + dv_diameters.append(diameter) + lv_diameters.append(diameter) + + if(subtype==3): + dvh_volumes.append(volume) + lv_volumes.append(volume) + dvh_diameters.append(diameter) + lv_diameters.append(diameter) + + + ####LV volumes + all_lv_volumes.extend(lv_volumes) #for later all neurons + lv_volumes = np.array(lv_volumes) + mean = np.mean(lv_volumes) + sem = stats.sem(lv_volumes) + n = len(lv_volumes) + print(f"Total LV mean (volumes): {mean}, LV sem: {sem}, LV n: {n}") + results.append(["LV", name, "Volume", mean, sem, n]) + #export to list + with open(f"{D_exports}{name}_LV_volumes.txt", "w") as f: + for vol in lv_volumes: + f.write(str(vol) + "\n") + + all_cv_volumes.extend(cv_volumes) #for later all neurons + cv_volumes = np.array(cv_volumes) + mean = np.mean(cv_volumes) + sem = stats.sem(cv_volumes) + n = len(cv_volumes) + print(f"CV mean (volumes): {mean}, CV sem: {sem}, CV n: {n}") + results.append(["CV", name, "Volume", mean, sem, n]) + #export to list + with open(f"{D_exports}{name}_CV_volumes.txt", "w") as f: + for vol in cv_volumes: + f.write(str(vol) + "\n") + + all_dv_volumes.extend(dv_volumes) #for later all neurons + dv_volumes = np.array(dv_volumes) + mean = np.mean(dv_volumes) + sem = stats.sem(dv_volumes) + n = len(dv_volumes) + print(f"DV mean (volumes): {mean}, DV sem: {sem}, DV n: {n}") + results.append(["DV", name, "Volume", mean, sem, n]) + #export to list + with open(f"{D_exports}{name}_DV_volumes.txt", "w") as f: + for vol in dv_volumes: + f.write(str(vol) + "\n") + + all_dvh_volumes.extend(dvh_volumes) #for later all neurons + dvh_volumes = np.array(dvh_volumes) + mean = np.mean(dvh_volumes) + sem = stats.sem(dvh_volumes) + n = len(dvh_volumes) + print(f"DVH mean (volumes): {mean}, DVH sem: {sem}, DVH n: {n}") + results.append(["DVH", name, "Volume", mean, sem, n]) + #export to list + with open(f"{D_exports}{name}_DVH_volumes.txt", "w") as f: + for vol in dvh_volumes: + f.write(str(vol) + "\n") + + if(sv_stats): + ####initialize stuff for SV volumes + sv_label_to_type = cache[name] #for type info + sv_dictionary = read_txt_to_dict(name, "sv") + sv_label_to_vol = {int(value_list[1][3:]):value_list[2] for value_list in sv_dictionary.values()} + + ####SV volumes + sv_volumes = list(sv_label_to_vol.values()) + all_sv_volumes.extend(sv_volumes) #for later all neurons + sv_volumes = np.array(sv_volumes) + mean = np.mean(sv_volumes) + sem = stats.sem(sv_volumes) + n = len(sv_volumes) + print(f"Total SV mean (volumes): {mean}, SV sem: {sem}, SV n: {n}") + results.append(["SV", name, "Volume", mean, sem, n]) + with open(f"{D_exports}{name}_SV_volumes.txt", "w") as f: + for vol in sv_volumes: + f.write(str(vol) + "\n") + + sdv_volumes = list(value for label, value in sv_label_to_vol.items() if (label in sv_label_to_type and sv_label_to_type[label] == 4)) + all_sdv_volumes.extend(sdv_volumes) #for later all neurons + sdv_volumes = np.array(sdv_volumes) + mean = np.mean(sdv_volumes) + sem = stats.sem(sdv_volumes) + n = len(sdv_volumes) + print(f"SDV mean (volumes): {mean}, SDV sem: {sem}, SDV n: {n}") + results.append(["SDV", name, "Volume", mean, sem, n]) + with open(f"{D_exports}{name}_SDV_volumes.txt", "w") as f: + for vol in sdv_volumes: + f.write(str(vol) + "\n") + + scv_volumes = list(value for label, value in sv_label_to_vol.items() if (label in sv_label_to_type and sv_label_to_type[label] == 5)) + all_scv_volumes.extend(scv_volumes) #for later all neurons + scv_volumes = np.array(scv_volumes) + mean = np.mean(scv_volumes) + sem = stats.sem(scv_volumes) + n = len(scv_volumes) + print(f"SCV mean (volumes): {mean}, SCV sem: {sem}, SCV n: {n}") + results.append(["SCV", name, "Volume", mean, sem, n]) + with open(f"{D_exports}{name}_SCV_volumes.txt", "w") as f: + for vol in scv_volumes: + f.write(str(vol) + "\n") + + + ####before moving on - combine all types for this neuron and export stats + all_volumes = [] + all_volumes.extend(lv_volumes) + if(sv_stats): + all_volumes.extend(sv_volumes) + all_all_volumes.extend(all_volumes) + all_volumes = np.array(all_volumes) + mean = np.mean(all_volumes) + sem = stats.sem(all_volumes) + n = len(all_volumes) + print(f"TOTAL mean (volumes): {mean}, TOTAL sem: {sem}, TOTAL n: {n}") + results.append(["Total", name, "Volume", mean, sem, n]) + with open(f"{D_exports}{name}_all_volumes.txt", "w") as f: + for vol in all_volumes: + f.write(str(vol) + "\n") + + print(f"---diameter stats for {name}---") + + ####LV diameters (lists already populated from before) + all_lv_diameters.extend(lv_diameters) + lv_diameters = np.array(lv_diameters) + mean = np.mean(lv_diameters) + sem = stats.sem(lv_diameters) + n = len(lv_diameters) + print(f"Total LV mean (diameters): {mean}, LV sem: {sem}, LV n: {n}") + results.append(["LV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_LV_diameters.txt", "w") as f: + for diam in lv_diameters: + f.write(str(diam) + "\n") + + all_cv_diameters.extend(cv_diameters) #for later all neurons + cv_diameters = np.array(cv_diameters) + mean = np.mean(cv_diameters) + sem = stats.sem(cv_diameters) + n = len(cv_diameters) + print(f"CV mean (diameters): {mean}, CV sem: {sem}, CV n: {n}") + results.append(["CV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_CV_diameters.txt", "w") as f: + for diam in cv_diameters: + f.write(str(diam) + "\n") + + all_dv_diameters.extend(dv_diameters) #for later all neurons + dv_diameters = np.array(dv_diameters) + mean = np.mean(dv_diameters) + sem = stats.sem(dv_diameters) + n = len(dv_diameters) + print(f"DV mean (diameters): {mean}, DV sem: {sem}, DV n: {n}") + results.append(["DV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_DV_diameters.txt", "w") as f: + for diam in dv_diameters: + f.write(str(diam) + "\n") + + all_dvh_diameters.extend(dvh_diameters) #for later all neurons + dvh_diameters = np.array(dvh_diameters) + mean = np.mean(dvh_diameters) + sem = stats.sem(dvh_diameters) + n = len(dvh_diameters) + print(f"DVH mean (diameters): {mean}, DVH sem: {sem}, DVH n: {n}") + results.append(["DVH", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_DVH_diameters.txt", "w") as f: + for diam in dvh_diameters: + f.write(str(diam) + "\n") + + if(sv_stats): + ####initialize stuff for SV diameters + sv_label_to_diam = {int(value_list[1][3:]):value_list[3]*2 for value_list in sv_dictionary.values()} + + ####SV diameters + sv_diameters = list(sv_label_to_diam.values()) + all_sv_diameters.extend(sv_diameters) #for later all neurons + sv_diameters = np.array(sv_diameters) + mean = np.mean(sv_diameters) + sem = stats.sem(sv_diameters) + n = len(sv_diameters) + print(f"Total SV mean (diameters): {mean}, SV sem: {sem}, SV n: {n}") + results.append(["SV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_SV_diameters.txt", "w") as f: + for diam in sv_diameters: + f.write(str(diam) + "\n") + + sdv_diameters = list(value for label, value in sv_label_to_diam.items() if (label in sv_label_to_type and sv_label_to_type[label] == 4)) + all_sdv_diameters.extend(sdv_diameters) #for later all neurons + sdv_diameters = np.array(sdv_diameters) + mean = np.mean(sdv_diameters) + sem = stats.sem(sdv_diameters) + n = len(sdv_diameters) + print(f"SDV mean (diameters): {mean}, SDV sem: {sem}, SDV n: {n}") + results.append(["SDV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_SDV_diameters.txt", "w") as f: + for diam in sdv_diameters: + f.write(str(diam) + "\n") + + scv_diameters = list(value for label, value in sv_label_to_diam.items() if (label in sv_label_to_type and sv_label_to_type[label] == 5)) + all_scv_diameters.extend(scv_diameters) #for later all neurons + scv_diameters = np.array(scv_diameters) + mean = np.mean(scv_diameters) + sem = stats.sem(scv_diameters) + n = len(scv_diameters) + print(f"SCV mean (diameters): {mean}, SCV sem: {sem}, SCV n: {n}") + results.append(["SCV", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_SCV_diameters.txt", "w") as f: + for diam in scv_diameters: + f.write(str(diam) + "\n") + + ####before moving on - combine all types for this neuron and export stats + all_diameters = [] + all_diameters.extend(lv_diameters) + if(sv_stats): + all_diameters.extend(sv_diameters) + all_all_diameters.extend(all_diameters) + all_diameters = np.array(all_diameters) + mean = np.mean(all_diameters) + sem = stats.sem(all_diameters) + n = len(all_diameters) + print(f"TOTAL mean (diameters): {mean}, TOTAL sem: {sem}, TOTAL n: {n}") + results.append(["Total", name, "Diameter", mean, sem, n]) + with open(f"{D_exports}{name}_all_diameters.txt", "w") as f: + for diam in all_diameters: + f.write(str(diam) + "\n") + + print() + + + + ####now do calculations for all neurons combined - alr done in sheet + print("---all neurons combined stats---") + + ####volumes + all_lv_volumes = np.array(all_lv_volumes) + mean = np.mean(all_lv_volumes) + sem = stats.sem(all_lv_volumes) + n = len(all_lv_volumes) + print(f"ALL NEURONS - LV mean (volumes): {mean}, LV sem: {sem}, LV n: {n}") + results.append(["LV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_LV_volumes.txt", "w") as f: + for vol in all_lv_volumes: + f.write(str(vol) + "\n") + + all_cv_volumes = np.array(all_cv_volumes) + mean = np.mean(all_cv_volumes) + sem = stats.sem(all_cv_volumes) + n = len(all_cv_volumes) + print(f"ALL NEURONS - CV mean (volumes): {mean}, CV sem: {sem}, CV n: {n}") + results.append(["CV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_CV_volumes.txt", "w") as f: + for vol in all_cv_volumes: + f.write(str(vol) + "\n") + + all_dv_volumes = np.array(all_dv_volumes) + mean = np.mean(all_dv_volumes) + sem = stats.sem(all_dv_volumes) + n = len(all_dv_volumes) + print(f"ALL NEURONS - DV mean (volumes): {mean}, DV sem: {sem}, DV n: {n}") + results.append(["DV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_DV_volumes.txt", "w") as f: + for vol in all_dv_volumes: + f.write(str(vol) + "\n") + + all_dvh_volumes = np.array(all_dvh_volumes) + mean = np.mean(all_dvh_volumes) + sem = stats.sem(all_dvh_volumes) + n = len(all_dvh_volumes) + print(f"ALL NEURONS - DVH mean (volumes): {mean}, DVH sem: {sem}, DVH n: {n}") + results.append(["DVH", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_DVH_volumes.txt", "w") as f: + for vol in all_dvh_volumes: + f.write(str(vol) + "\n") + + if(sv_stats): + all_sv_volumes = np.array(all_sv_volumes) + mean = np.mean(all_sv_volumes) + sem = stats.sem(all_sv_volumes) + n = len(all_sv_volumes) + print(f"ALL NEURONS - SV mean (volumes): {mean}, SV sem: {sem}, SV n: {n}") + results.append(["SV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_SV_volumes.txt", "w") as f: + for vol in all_sv_volumes: + f.write(str(vol) + "\n") + + all_scv_volumes = np.array(all_scv_volumes) + mean = np.mean(all_scv_volumes) + sem = stats.sem(all_scv_volumes) + n = len(all_scv_volumes) + print(f"ALL NEURONS - SCV mean (volumes): {mean}, SCV sem: {sem}, SCV n: {n}") + results.append(["SCV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_SCV_volumes.txt", "w") as f: + for vol in all_scv_volumes: + f.write(str(vol) + "\n") + + all_sdv_volumes = np.array(all_sdv_volumes) + mean = np.mean(all_sdv_volumes) + sem = stats.sem(all_sdv_volumes) + n = len(all_sdv_volumes) + print(f"ALL NEURONS - SDV mean (volumes): {mean}, SDV sem: {sem}, SDV n: {n}") + results.append(["SDV", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_SDV_volumes.txt", "w") as f: + for vol in all_sdv_volumes: + f.write(str(vol) + "\n") + + all_all_volumes = np.array(all_all_volumes) + mean = np.mean(all_all_volumes) + sem = stats.sem(all_all_volumes) + n = len(all_all_volumes) + print(f"ALL NEURONS - TOTAL mean (volumes): {mean}, TOTAL sem: {sem}, TOTAL n: {n}") + results.append(["Total", "TOTAL", "Volume", mean, sem, n]) + with open(f"{D_exports}all_all_volumes.txt", "w") as f: + for vol in all_all_volumes: + f.write(str(vol) + "\n") + + ####diameters + all_lv_diameters = np.array(all_lv_diameters) + mean = np.mean(all_lv_diameters) + sem = stats.sem(all_lv_diameters) + n = len(all_lv_diameters) + print(f"ALL NEURONS - LV mean (diameters): {mean}, LV sem: {sem}, LV n: {n}") + results.append(["LV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_LV_diameters.txt", "w") as f: + for diam in all_lv_diameters: + f.write(str(diam) + "\n") + + all_cv_diameters = np.array(all_cv_diameters) + mean = np.mean(all_cv_diameters) + sem = stats.sem(all_cv_diameters) + n = len(all_cv_diameters) + print(f"ALL NEURONS - CV mean (diameters): {mean}, CV sem: {sem}, CV n: {n}") + results.append(["CV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_CV_diameters.txt", "w") as f: + for diam in all_cv_diameters: + f.write(str(diam) + "\n") + + all_dv_diameters = np.array(all_dv_diameters) + mean = np.mean(all_dv_diameters) + sem = stats.sem(all_dv_diameters) + n = len(all_dv_diameters) + print(f"ALL NEURONS - DV mean (diameters): {mean}, DV sem: {sem}, DV n: {n}") + results.append(["DV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_DV_diameters.txt", "w") as f: + for diam in all_dv_diameters: + f.write(str(diam) + "\n") + + all_dvh_diameters = np.array(all_dvh_diameters) + mean = np.mean(all_dvh_diameters) + sem = stats.sem(all_dvh_diameters) + n = len(all_dvh_diameters) + print(f"ALL NEURONS - DVH mean (diameters): {mean}, DVH sem: {sem}, DVH n: {n}") + results.append(["DVH", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_DVH_diameters.txt", "w") as f: + for diam in all_dvh_diameters: + f.write(str(diam) + "\n") + + if(sv_stats): + all_sv_diameters = np.array(all_sv_diameters) + mean = np.mean(all_sv_diameters) + sem = stats.sem(all_sv_diameters) + n = len(all_sv_diameters) + print(f"ALL NEURONS - SV mean (diameters): {mean}, SV sem: {sem}, SV n: {n}") + results.append(["SV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_SV_diameters.txt", "w") as f: + for diam in all_sv_diameters: + f.write(str(diam) + "\n") + + all_scv_diameters = np.array(all_scv_diameters) + mean = np.mean(all_scv_diameters) + sem = stats.sem(all_scv_diameters) + n = len(all_scv_diameters) + print(f"ALL NEURONS - SCV mean (diameters): {mean}, SCV sem: {sem}, SCV n: {n}") + results.append(["SCV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_SCV_diameters.txt", "w") as f: + for diam in all_scv_diameters: + f.write(str(diam) + "\n") + + all_sdv_diameters = np.array(all_sdv_diameters) + mean = np.mean(all_sdv_diameters) + sem = stats.sem(all_sdv_diameters) + n = len(all_sdv_diameters) + print(f"ALL NEURONS - SDV mean (diameters): {mean}, SDV sem: {sem}, SDV n: {n}") + results.append(["SDV", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_SDV_diameters.txt", "w") as f: + for diam in all_sdv_diameters: + f.write(str(diam) + "\n") + + all_all_diameters = np.array(all_all_diameters) + mean = np.mean(all_all_diameters) + sem = stats.sem(all_all_diameters) + n = len(all_all_diameters) + print(f"ALL NEURONS - TOTAL mean (diameters): {mean}, TOTAL sem: {sem}, TOTAL n: {n}") + results.append(["Total", "TOTAL", "Diameter", mean, sem, n]) + with open(f"{D_exports}all_all_diameters.txt", "w") as f: + for diam in all_all_diameters: + f.write(str(diam) + "\n") + + + df = pd.DataFrame(results, columns=["Type", "Neuron", "Measurement", "Mean", "SEM", "N"]) + df.to_excel(f"{D_exports}vesicle_stats.xlsx", index=False) + print("export done") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ves_analysis/vesicle_stats/vesicle_volume_stats.py b/ves_analysis/vesicle_stats/vesicle_volume_stats.py new file mode 100644 index 0000000..4e0dc71 --- /dev/null +++ b/ves_analysis/vesicle_stats/vesicle_volume_stats.py @@ -0,0 +1,230 @@ +import h5py +import numpy as np +import scipy.stats as stats + +cache = {} + +D0 = '/data/projects/weilab/dataset/hydra/results/' +D1 = '/data/rothmr/hydra/stitched/' +D_volumes = '/data/rothmr/hydra/volumes/' #for raw list of volumes for each neuron + +#dictionaries for SV type classifications will be saved in cache +names_20 = ["KR4", "KR5", "KR6", "SHL55", "PN3", "LUX2", "SHL20", "KR11", "KR10", "RGC2", "KM4", "SHL17", + "NET12", "NET10", "NET11", "PN7", "SHL18", "SHL24", "SHL28", "RGC7"] + +for name in names_20: + dictionary = dict() + cache[f"{name}_SV_dict"] = dictionary + + +def load_data(name, lv=False, sv=False): + print(f"begin loading data for {name}") + if(lv): + with h5py.File(f"{D0}vesicle_big_{name}_30-8-8.h5", 'r') as f: #high res large vesicles data for [name] + cache["lv"] = f["main"][:] + print("done loading LV") + + if(sv): + with h5py.File(f"{D0}vesicle_small_{name}_30-8-8.h5", 'r') as f: #high res small vesicles data for [name] + cache["sv"] = f["main"][:] + print("done loading SV") + +def read_txt_to_dict(file_path): + result_dict = {} + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + pairs = line.strip('()').split('),(') + + for pair in pairs: + key, value = pair.split(':') + result_dict[int(key)] = int(value) + + return result_dict + + +def calculate_stats(name, lv=False, sv=False): + dictionary = read_txt_to_dict(f"types_lists/{name}_types.txt") + res = [30,8,8] + lv_volumes = [] + sv_volumes = [] + + #load the data into the cache for this neuron + load_data(name, lv, sv) + + #calculate distribution of vesicle volumes for lv and sv separately + + if(lv): + #find list of unique labels + unique_labels = np.unique(cache["lv"]) + print("LV num of unique labels including 0: ", len(unique_labels)) + voxel_volume = res[0]*res[1]*res[2] + + for label in unique_labels: + if label!=0: + mask = (cache["lv"] == label) #binary mask + volume_voxels = np.sum(mask) + volume = volume_voxels * voxel_volume + #print(volume) + lv_volumes.append(volume) + lv_volumes = np.array(lv_volumes) + mean = np.mean(lv_volumes) + sem = stats.sem(lv_volumes) + n = len(lv_volumes) + print(f"Total LV mean: {mean}, LV sem: {sem}, LV n: {n}") + + #save all VOLUMES to a file + with open(f"{D_volumes}{name}_LV_volumes.txt", "w") as f: + for v in lv_volumes: + f.write(f"{v}\n") + + # cv = 1 + cv_volumes = [] + for label in unique_labels: + if label!=0: + vesicle_subtype = dictionary[label] + if vesicle_subtype==1: + mask = (cache["lv"] == label) #binary mask + volume_voxels = np.sum(mask) + volume = volume_voxels * voxel_volume + cv_volumes.append(volume) + cv_volumes = np.array(cv_volumes) + mean = np.mean(cv_volumes) + sem = stats.sem(cv_volumes) + n = len(cv_volumes) + print(f"CV mean: {mean}, CV sem: {sem}, CV n: {n}") + + # dv = 2 + dv_volumes = [] + for label in unique_labels: + if label!=0: + vesicle_subtype = dictionary[label] + if vesicle_subtype==2: + mask = (cache["lv"] == label) #binary mask + volume_voxels = np.sum(mask) + volume = volume_voxels * voxel_volume + dv_volumes.append(volume) + dv_volumes = np.array(dv_volumes) + mean = np.mean(dv_volumes) + sem = stats.sem(dv_volumes) + n = len(dv_volumes) + print(f"DV mean: {mean}, DV sem: {sem}, DV n: {n}") + + # dvh = 3 + dvh_volumes = [] + for label in unique_labels: + if label!=0: + vesicle_subtype = dictionary[label] + if vesicle_subtype==3: + mask = (cache["lv"] == label) #binary mask + volume_voxels = np.sum(mask) + volume = volume_voxels * voxel_volume + dvh_volumes.append(volume) + dvh_volumes = np.array(dvh_volumes) + mean = np.mean(dvh_volumes) + sem = stats.sem(dvh_volumes) + n = len(dvh_volumes) + print(f"DVH mean: {mean}, DVH sem: {sem}, DVH n: {n}") + + + if(sv): + #fill in dicts for all neurons + with np.load("sv_types/SV_types_new.npz") as data: + current = 0 + while (current