From 513db8b99b152068a57db5987293b4d741576c04 Mon Sep 17 00:00:00 2001 From: Matt Mallory Date: Wed, 19 Mar 2025 15:28:03 -0700 Subject: [PATCH 1/3] proj matrix edits --- morph_utils/ccf.py | 33 ++++--- .../aggregate_single_cell_projection_csvs.py | 92 +++++++++++++++++++ .../projection_matrix_for_single_cell.py | 11 ++- .../projection_matrix_from_swc_directory.py | 86 +++++++++++++++-- setup.cfg | 1 + 5 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 morph_utils/executable_scripts/aggregate_single_cell_projection_csvs.py diff --git a/morph_utils/ccf.py b/morph_utils/ccf.py index 46c7523..702841f 100644 --- a/morph_utils/ccf.py +++ b/morph_utils/ccf.py @@ -289,7 +289,7 @@ def get_ccf_structure(voxel, name_map=None, annotation=None, coordinate_to_voxel return name_map[structure_id] def projection_matrix_for_swc(input_swc_file, mask_method = "tip_and_branch", - tip_count = False, annotation=None, + count_method = "node", annotation=None, annotation_path = None, volume_shape=(1320, 800, 1140), resolution=10, node_type_list=[2], resample_spacing=None): @@ -304,7 +304,8 @@ def projection_matrix_for_swc(input_swc_file, mask_method = "tip_and_branch", 'tip_and_branch' will return a projection matrix masking only structures with tip and branch nodes. If 'tip' will only look at structures with tip nodes. And last, if 'branch' will only look at structures with branch nodes. - tip_count (bool): if True, will count number of tips instead of the length of axon + count_method (str): ['node','tip','branch']. When 'node', will measure axon length by multiplying by internode spacing. + Otherwise will return the count of tip or branch nodes in each structure annotation (array, optional): 3 dimensional ccf annotation array. Defaults to None. annotation_path (str, optional): path to nrrd file to use (optional). Defaults to None. volume_shape (tuple, optional): the size in voxels of the ccf atlas (annotation volume). Defaults to (1320, 800, 1140). @@ -317,7 +318,7 @@ def projection_matrix_for_swc(input_swc_file, mask_method = "tip_and_branch", filename (str) specimen_projection_summary (dict): keys are strings of structures and values are the quantitiave projection - values. Either axon length, or number numbe of nodes depending on tip_count argument. + values. Either axon length, or number numbe of nodes depending on count_method argument. """ @@ -329,8 +330,11 @@ def projection_matrix_for_swc(input_swc_file, mask_method = "tip_and_branch", print(f"WARNING: Annotation path provided does not exist, defaulting to 10um resolution, (1320,800, 1140) ccf.\n{annotation_path}") annotation_path = None annotation = open_ccf_annotation(with_nrrd=True, annotation_path=annotation_path) - - + + if count_method not in ['node','tip','branch']: + msg = f"count_method must be 'node','tip', or 'branch'. You passed in: {count_method}" + raise ValueError(msg) + sg_df = load_structure_graph() name_map = NAME_MAP full_name_to_abbrev_dict = dict(zip(sg_df.name, sg_df.index)) @@ -379,15 +383,13 @@ def node_ider(morph,i): # determine ipsi/contra projections morph_df["ccf_structure_sided"] = morph_df.apply(lambda row: "ipsi_{}".format(row.ccf_structure) if row.z Date: Mon, 19 May 2025 13:45:48 -0700 Subject: [PATCH 2/3] fix: small bug in resample --- morph_utils/modifications.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/morph_utils/modifications.py b/morph_utils/modifications.py index 01e7125..1773e32 100644 --- a/morph_utils/modifications.py +++ b/morph_utils/modifications.py @@ -129,9 +129,8 @@ def resample_morphology(morph, spacing_size): old_irr_id_to_new_irr_id_dict[root['id']]=new_root['id'] this_roots_children = morph.get_children(root) node_ct+=1 - - for child in this_roots_children: - + for child in this_roots_children: + # in a sense children of the root nodes are also treated as irreducible nodes new_child = copy(child) new_child['parent'] = new_root['id'] new_child['id'] = node_ct @@ -151,18 +150,31 @@ def resample_morphology(morph, spacing_size): parent = morph.node_by_id(current_node['parent']) siblings = morph.get_children(parent) + # so if the current_node has siblings (it's parent furcates), + # and we've already visited the parent, we should add that parent to this_list + # as it will be the first upstream irreducible node in this segment if len(siblings)>1 and parent['id'] in seen_ids: if parent['id']!=morph.get_soma()['id']: + # this needs to happend before we add current_node to the list this_list.append(parent) - + + # now add current node, and update that we've seen curent node this_list.append(current_node) seen_ids.update([current_node['id']]) children_list = morph.get_children(current_node) if len(children_list)!=1: - irreducible_segments.append(this_list) + # if `current_node` is the immediate child of `child`, and is irreducible, + # then `this_list` will only contain `current_node` + if this_list!= [current_node]: + # otherwise, add this_list to our meta list + irreducible_segments.append(this_list) + + # refresh the list this_list = [] for ch_no in children_list: + # add the children, and the upstream irreducible parent node will be + # added using the logic from beore queue.appendleft(ch_no) @@ -227,8 +239,12 @@ def resample_morphology(morph, spacing_size): node_ct+=1 new_node_2 = copy(irr_node_2) if len(reducible_arr)==0: + # if there are no reducible nodes, the down tree reducible node + # should point to the upstream one new_node_2['parent'] = red_1_parent_id else: + # otherwise, the downstream irreducible node should point to + # the last redubile node id, which will be node_ct-1 new_node_2['parent'] = node_ct-1 new_node_2['id']=node_ct new_nodes.append(new_node_2) @@ -239,10 +255,10 @@ def resample_morphology(morph, spacing_size): node_ct+=1 - resampled_morph = Morphology(new_nodes, - parent_id_cb=lambda x:x['parent'], - node_id_cb=lambda x:x['id']) - + resampled_morph = Morphology(new_nodes, + parent_id_cb=lambda x:x['parent'], + node_id_cb=lambda x:x['id']) + return resampled_morph From 6933b1227c367383f7d3e7de183b421e29f831eb Mon Sep 17 00:00:00 2001 From: Matt Mallory Date: Mon, 19 May 2025 13:53:53 -0700 Subject: [PATCH 3/3] remove prints --- morph_utils/ccf.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/morph_utils/ccf.py b/morph_utils/ccf.py index 702841f..79ea5dd 100644 --- a/morph_utils/ccf.py +++ b/morph_utils/ccf.py @@ -362,7 +362,9 @@ def projection_matrix_for_swc(input_swc_file, mask_method = "tip_and_branch", # annotate each node if morph_df.empty: - print("Its empty") + + msg = "morph_df is empty, possibly caused by `morph_df = morph_df[morph_df['type'].isin(node_type_list)]`" + warnings.warn(msg) return input_swc_file, {} morph_df['ccf_structure'] = morph_df.apply(lambda rw: full_name_to_abbrev_dict[get_ccf_structure( np.array([rw.x, rw.y, rw.z]) , name_map, annotation, True)], axis=1) @@ -383,7 +385,6 @@ def node_ider(morph,i): # determine ipsi/contra projections morph_df["ccf_structure_sided"] = morph_df.apply(lambda row: "ipsi_{}".format(row.ccf_structure) if row.z