Skip to content

Commit 14f2e5c

Browse files
authored
Merge pull request #3 from ttngu207/main
added brain atlas and ontology loader
2 parents 8c5f8cf + bed5ccf commit 14f2e5c

File tree

5 files changed

+494
-30
lines changed

5 files changed

+494
-30
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ fully functional electrode localization pipeline.
3333
3434
+ Install `element-interface`
3535
36-
+ `element-interface` is a dependency of `element-electrode-localization`, however it is not contained within `requirements.txt`.
36+
+ `element-interface` is a dependency of `element-electrode-localization`, however
37+
it is not contained within `requirements.txt`.
3738
3839
```
3940
pip install "element-interface @ git+https://github.com/datajoint/element-interface"
@@ -53,12 +54,13 @@ To activate the `element-electrode-localization`, ones need to provide:
5354
5455
3. Utility functions. See [example definitions](https://github.com/datajoint/workflow-array-ephys/blob/main/workflow_array_ephys/paths.py).
5556
+ get_ephys_root_data_dir(): Returns your root data directory.
56-
+ get_session_directory(): Returns the path of the session data relative to the root.
57+
+ get_session_directory(): Returns the path of the session data relative to the
58+
root.
5759
5860
For more details, check the docstring of the `element-electrode-localization`:
59-
61+
```python
6062
help(electrode.activate)
61-
63+
```
6264
### Example usage
6365

6466
See the [workflow-array-ephys project](https://github.com/datajoint/workflow-array-ephys) for an example usage of this Element.
@@ -75,4 +77,4 @@ See the [workflow-array-ephys project](https://github.com/datajoint/workflow-arr
7577
+ DataJoint Elements
7678
+ Yatsenko D, Nguyen T, Shen S, Gunalan K, Turner CA, Guzman R, Sasaki M, Sitonic D, Reimer J, Walker EY, Tolias AS. DataJoint Elements: Data Workflows for Neurophysiology. bioRxiv. 2021 Jan 1. doi: https://doi.org/10.1101/2021.03.30.437358
7779

78-
+ DataJoint Elements ([RRID:SCR_021894](https://scicrunch.org/resolver/SCR_021894)) - Element Electrode Localization (version `<Enter version number>`)
80+
+ DataJoint Elements ([RRID:SCR_021894](https://scicrunch.org/resolver/SCR_021894)) - Element Electrode Localization (version `<Enter version number>`)

element_electrode_localization/coordinate_framework.py

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
1+
import logging
2+
import pathlib
3+
from tqdm import tqdm
14
import datajoint as dj
5+
import pandas as pd
6+
import numpy as np
7+
from tqdm import tqdm
8+
import nrrd
9+
import re
210

311

12+
log = logging.getLogger(__name__)
413
schema = dj.schema()
514

615

716
def activate(schema_name, *, create_schema=True, create_tables=True):
817
"""
918
activate(schema_name, create_schema=True, create_tables=True)
10-
:param schema_name: schema name on the database server to activate the `coordinate_framework` element
11-
:param create_schema: when True (default), create schema in the database if it does not yet exist.
12-
:param create_tables: when True (default), create tables in the database if they do not yet exist.
19+
:param schema_name: schema name on the database server to activate the
20+
`coordinate_framework` element
21+
:param create_schema: when True (default), create schema in the database if it
22+
does not yet exist.
23+
:param create_tables: when True (default), create tables in the database if
24+
they do not yet exist.
1325
"""
14-
schema.activate(schema_name, create_schema=create_schema, create_tables=create_tables)
26+
schema.activate(schema_name, create_schema=create_schema,
27+
create_tables=create_tables)
28+
29+
# ----------------------------- Table declarations ----------------------
1530

1631

1732
@schema
1833
class CCF(dj.Lookup):
1934
definition = """ # Common Coordinate Framework
20-
# CCF Dataset Information
21-
ccf_id: int # CCF ID
35+
ccf_id : int # CCF ID, a.k.a atlas ID
2236
---
23-
ccf_version: int # Allen CCF Version - e.g. CCFv3
37+
ccf_version : varchar(64) # Allen CCF Version - e.g. CCFv3
38+
ccf_resolution : float # voxel resolution in micron
2439
ccf_description='': varchar(255) # CCFLabel Description
2540
"""
2641

@@ -43,7 +58,7 @@ class BrainRegionAnnotation(dj.Lookup):
4358
class BrainRegion(dj.Part):
4459
definition = """
4560
-> master
46-
acronym: varchar(32)
61+
acronym: varchar(32) # CHARACTER SET utf8 COLLATE utf8_bin
4762
---
4863
region_name: varchar(128)
4964
region_id=null: int
@@ -56,13 +71,124 @@ class Voxel(dj.Part):
5671
-> CCF.Voxel
5772
"""
5873

74+
@classmethod
75+
def retrieve_acronym(self, acronym):
76+
""" Retrieve the DataJoint translation of the CCF acronym"""
77+
return re.sub(r'(?<!^)(?=[A-Z])', '_', acronym).lower()
78+
79+
@classmethod
80+
def voxel_query(self, x=None, y=None, z=None):
81+
"""Given one or more coordinates, return unique brain regions
82+
:param x: x coordinate
83+
:param y: y coordinate
84+
:param z: z coordinate
85+
"""
86+
if not any(x, y, z):
87+
raise ValueError('Must specify at least one dimension')
88+
# query = self.Voxel # TODO: add utility function name lookup
89+
raise NotImplementedError('Coming soon')
90+
5991

6092
@schema
6193
class ParentBrainRegion(dj.Lookup):
62-
definition = """ # Hierarchical structure between the brain regions
94+
definition = """ # Hierarchical structure between the brain regionss
6395
-> BrainRegionAnnotation.BrainRegion
6496
---
6597
-> BrainRegionAnnotation.BrainRegion.proj(parent='acronym')
6698
"""
6799

68100

101+
# ---- HELPERS ----
102+
103+
104+
def load_ccf_annotation(ccf_id, version_name, voxel_resolution,
105+
nrrd_filepath, ontology_csv_filepath):
106+
"""
107+
:param ccf_id: unique id to identify a new CCF dataset to be inserted
108+
:param version_name: CCF version
109+
:param voxel_resolution: voxel resolution in micron
110+
:param nrrd_filepath: path to the .nrrd file for the volume data
111+
:param ontology_csv_filepath: path to the .csv file for the brain region ontology
112+
113+
load_ccf_annotation(
114+
ccf_id=0, version_name='ccf_2017', voxel_resolution=10,
115+
nrrd_filepath='./data/annotation_10.nrrd',
116+
ontology_csv_filepath='./data/query.csv')
117+
118+
For an example Allen brain atlas for mouse, see:
119+
http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017
120+
121+
For the structure/ontology tree, see:
122+
https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359
123+
(particularly the ontology file downloadable as CSV)
124+
"""
125+
ccf_key = {'ccf_id': ccf_id}
126+
if CCF & ccf_key:
127+
print(f'CCF ID {ccf_id} already exists!')
128+
return
129+
130+
nrrd_filepath = pathlib.Path(nrrd_filepath)
131+
ontology_csv_filepath = pathlib.Path(ontology_csv_filepath)
132+
133+
def to_snake_case(s):
134+
return re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()
135+
136+
ontology = pd.read_csv(ontology_csv_filepath)
137+
138+
stack, hdr = nrrd.read(nrrd_filepath.as_posix()) # AP (x), DV (y), ML (z)
139+
140+
log.info('.. loaded atlas brain volume of shape '
141+
+ f'{stack.shape} from {nrrd_filepath}')
142+
143+
ccf_key = {'ccf_id': ccf_id}
144+
ccf_entry = {**ccf_key,
145+
'ccf_version': version_name,
146+
'ccf_resolution': voxel_resolution,
147+
'ccf_description': (f'Version: {version_name}'
148+
+ f' - Voxel resolution (uM): {voxel_resolution}'
149+
+ f' - Volume file: {nrrd_filepath.name}'
150+
+ ' - Region ontology file: '
151+
+ ontology_csv_filepath.name)
152+
}
153+
154+
with dj.conn().transaction:
155+
CCF.insert1(ccf_entry)
156+
BrainRegionAnnotation.insert1(ccf_key)
157+
BrainRegionAnnotation.BrainRegion.insert([
158+
dict(ccf_id=ccf_id,
159+
acronym=to_snake_case(r.acronym),
160+
region_id=r.id,
161+
region_name=r.safe_name,
162+
color_code=r.color_hex_triplet) for _, r in ontology.iterrows()])
163+
164+
# Process voxels per brain region
165+
for idx, (region_id, r) in enumerate(tqdm(ontology.iterrows())):
166+
dj.conn().ping()
167+
region_id = int(region_id)
168+
169+
log.info('.. loading region {} ({}/{}) ({})'
170+
.format(region_id, idx, len(ontology), r.safe_name))
171+
172+
# extracting filled volumes from stack in scaled [[x,y,z]] shape,
173+
vol = (np.array(np.where(stack == region_id)).T * voxel_resolution)
174+
vol = pd.DataFrame(vol, columns=['x', 'y', 'z'])
175+
176+
if not vol.shape[0]:
177+
log.info('.. region {} volume: shape {} - skipping'
178+
.format(region_id, vol.shape))
179+
continue
180+
else:
181+
log.info('.. region {} volume: shape {}'.format(
182+
region_id, vol.shape))
183+
184+
vol['ccf_id'] = [ccf_key['ccf_id']] * len(vol)
185+
CCF.Voxel.insert(vol)
186+
187+
vol['acronym'] = [to_snake_case(r.acronym)] * len(vol)
188+
BrainRegionAnnotation.Voxel.insert(vol)
189+
190+
log.info('.. done.')
191+
192+
193+
def load_parent_regions(ccf_id):
194+
raise NotImplementedError('Coming soon')

0 commit comments

Comments
 (0)