def test_clean_structures_str_id():

    dirty_node = {'id': '0', 'structure_id_path': '/0/', 
                  'color_hex_triplet': '000000', 'acronym': 'rt', 
                  'name': 'root', 'structure_set_ids': [1, 2, 3], 
                  'structure_sets': [{'id': 1}, {'id': 4}] }
    
    clean_node = StructureTree.clean_structures([dirty_node])
    st = StructureTree(clean_node)
    
    assert( set(st.node_ids()) == set([0]) )
def test_clean_structures_weird_keys():
    
    dirty_node = {'id': 5, 'dummy_key': 'dummy_val'}
    clean_node = StructureTree.clean_structures([dirty_node])[0]

    assert( len(clean_node) == 2 )
    assert( clean_node['id'] == 5 )
def test_clean_structures(nodes):

    dirty_node = {'id': 0, 'structure_id_path': '/0/', 
                  'color_hex_triplet': '000000', 'acronym': 'rt', 
                  'name': 'root', 'structure_sets':[{'id': 1}, {'id': 4}]}
                  
    clean_node = StructureTree.clean_structures([dirty_node])[0]
    assert( isinstance(clean_node['rgb_triplet'], list) )
    assert( isinstance(clean_node['structure_id_path'], list) )
def test_clean_structures_only_ids():
    
    dirty_node = {'id': 0, 'structure_id_path': '/0/', 
                  'color_hex_triplet': '000000', 'acronym': 'rt', 
                  'name': 'root', 'structure_set_ids': [1, 2, 3] }
    
    clean_node = StructureTree.clean_structures([dirty_node])
    st = StructureTree(clean_node)
    
    assert( len(clean_node[0]['structure_set_ids']) == 3 )
def test_get_reference_space(mcc, new_nodes):

    tree = StructureTree(StructureTree.clean_structures(new_nodes))
    with mock.patch.object(mcc, "get_structure_tree",
                           new=lambda *a, **k: tree):
        annot = np.arange(125).reshape((5, 5, 5))
        with mock.patch.object(mcc, "get_annotation_volume",
                               new=lambda *a, **k: (annot, 'foo')):
            rsp_obt = mcc.get_reference_space()

    assert( np.allclose(rsp_obt.resolution, [25, 25, 25]) )
    assert( np.allclose( rsp_obt.annotation, annot ) )
def test_get_reference_space(rsp, new_nodes):

    tree = StructureTree(StructureTree.clean_structures(new_nodes))
    rsp.get_structure_tree = lambda *a, **k: tree

    annot = np.arange(125).reshape((5, 5, 5))
    rsp.get_annotation_volume = lambda *a, **k: (annot, 'foo')

    rsp_obt = rsp.get_reference_space()

    assert( np.allclose(rsp_obt.resolution, [25, 25, 25]) )
    assert( np.allclose( rsp_obt.annotation, annot ) ) 
 def remove_unassigned(self, update_self=True):
     '''Obtains a structure tree consisting only of structures that have 
     at least one voxel in the annotation.
     
     Parameters
     ----------
     update_self : bool, optional
         If True, the contained structure tree will be replaced,
         
     Returns
     -------
     list of dict : 
         elements are filtered structures
     
     '''
 
     structures = self.structure_tree.filter_nodes(
         lambda x: self.total_voxel_map[x['id']] > 0)
         
     if update_self:
         self.structure_tree = StructureTree(structures)
         
     return structures
示例#8
0
img_annotation.set_qform(qform, code=1)
img_average_template.set_qform(qform, code=1)
img_annotation.set_sform(np.eye(4), code=0)
img_average_template.set_sform(np.eye(4), code=0)
# img_average_template.set_qform(img_average_template_wrongread.get_qform())
nib.save(img_annotation, allen_annotation_path)
nib.save(img_average_template, allen_average_template_path)



# Get structure graph
oapi = OntologiesApi()
allen_structure_graph_dict = oapi.get_structures([1]) # Get structure graph with structure graph id = 1, which is the Mouse Brain Atlas structure graph

# This removes some unused fields returned by the query
allen_structure_graph_dict = StructureTree.clean_structures(allen_structure_graph_dict)

# Get tree
allen_structure_graph_tree = StructureTree(allen_structure_graph_dict)

# now let's take a look at a structure
allen_structure_graph_tree.get_structures_by_name(['Dorsal auditory area'])

# Look at children or parent of structure, important for later (volume calculations)

# Define path of structure graph table
allen_average_template_csv_path=os.path.join(allen_dir, 'structure_graph.csv')
allen_average_template_csv_remapped_path = os.path.join(allen_dir, 'structure_graph_remapped.csv')

# If structure graph already created, simply load old table
if os.path.exists(allen_average_template_csv_remapped_path):
def test_hex_to_rgb(inp, out):
    obt = StructureTree.hex_to_rgb(inp)
    assert (allclose(obt, out))
def test_path_to_list(inp, out):
    obt = StructureTree.path_to_list(inp)
    assert (allclose(obt, out))
def tree(nodes):
    return StructureTree(nodes)
示例#12
0
class ReferenceSpace(object):

    @property
    def direct_voxel_map(self):
        if not hasattr(self, '_direct_voxel_map'):
            self.direct_voxel_counts()
        return self._direct_voxel_map 
        
    @direct_voxel_map.setter
    def direct_voxel_map(self, data):
        self._direct_voxel_map = data
    
    @property
    def total_voxel_map(self):
        if not hasattr(self, '_total_voxel_map'):
            self.total_voxel_counts()
        return self._total_voxel_map
        
    @total_voxel_map.setter
    def total_voxel_map(self, data):
        self._total_voxel_map = data
        
    def __init__(self, structure_tree, annotation, resolution):
        '''Handles brain structures in a 3d reference space
        
        Parameters
        ----------
        structure_tree : StructureTree
            Defines the heirarchy and properties of the brain structures.
        annotation : numpy ndarray
            3d volume whose elements are structure ids.
        resolution : length-3 tuple of numeric
            Resolution of annotation voxels along each dimension.
        
        '''
        
        self.structure_tree = structure_tree
        self.resolution = resolution
        
        self.annotation = np.ascontiguousarray(annotation)
        
    def direct_voxel_counts(self):
        '''Determines the number of voxels directly assigned to one or more 
        structures.
        
        Returns
        -------
        dict : 
            Keys are structure ids, values are the number of voxels directly 
            assigned to those structures.
        
        '''

        uniques = np.unique(self.annotation, return_counts=True)
        found = {k: v for k, v in zip(*uniques) if k != 0}

        self._direct_voxel_map = {k: (found[k] if k in found else 0) for k 
                                  in self.structure_tree.node_ids()}
          
    def total_voxel_counts(self):
        '''Determines the number of voxels assigned to a structure or its 
        descendants
            
        Returns
        -------
        dict : 
            Keys are structure ids, values are the number of voxels assigned 
            to structures' descendants.
        
        ''' 

        self._total_voxel_map = {}
        for stid in self.structure_tree.node_ids():

            desc_ids = self.structure_tree.descendant_ids([stid])[0]
            self._total_voxel_map[stid] = sum([self.direct_voxel_map[dscid] 
                                               for dscid in desc_ids])
    
    def remove_unassigned(self, update_self=True):
        '''Obtains a structure tree consisting only of structures that have 
        at least one voxel in the annotation.
        
        Parameters
        ----------
        update_self : bool, optional
            If True, the contained structure tree will be replaced,
            
        Returns
        -------
        list of dict : 
            elements are filtered structures
        
        '''
    
        structures = self.structure_tree.filter_nodes(
            lambda x: self.total_voxel_map[x['id']] > 0)
            
        if update_self:
            self.structure_tree = StructureTree(structures)
            
        return structures
    
    def make_structure_mask(self, structure_ids, direct_only=False):
        '''Return an indicator array for one or more structures
        
        Parameters
        ----------
        structure_ids : list of int
            Make a mask that indicates the union of these structures' voxels
        direct_only : bool, optional
            If True, only include voxels directly assigned to a structure in 
            the mask. Otherwise include voxels assigned to descendants.
            
        Returns
        -------
        numpy ndarray :
            Same shape as annotation. 1 inside mask, 0 outside.
        
        '''
    
        if direct_only:
            mask = np.zeros(self.annotation.shape, dtype=np.uint8, order='C')
            for stid in structure_ids:
                
                if self.direct_voxel_map[stid] == 0:
                    continue
                    
                mask[self.annotation == stid] = True
                
            return mask
            
        else:
            structure_ids = self.structure_tree.descendant_ids(structure_ids)
            structure_ids = set(functools.reduce(op.add, structure_ids))
            return self.make_structure_mask(structure_ids, direct_only=True)
                        
    def many_structure_masks(self, structure_ids, output_cb=None, 
                             direct_only=False):
        '''Build one or more structure masks and do something with them
        
        Parameters
        ----------
        structure_ids : list of int
            Specify structures to be masked
        output_cb : function, optional
            Must have the following signature: output_cb(structure_id, fn). 
            On each requested id, fn will be curried to make a mask for that 
            id. Defaults to returning the structure id and mask.
        direct_only : bool, optional
            If True, only include voxels directly assigned to a structure in 
            the mask. Otherwise include voxels assigned to descendants.
            
        Yields
        -------
        Return values of output_cb called on each structure_id, structure_mask 
        pair.
        
        Notes
        -----
        output_cb is called on every yield, so any side-effects (such as 
        writing to a file) will be carried out regardless of what you do with 
        the return values. You do actually have to iterate through the output, 
        though.
        
        '''
        
        if output_cb is None:
            output_cb = ReferenceSpace.return_mask_cb
                                              
        for stid in structure_ids:
            yield output_cb(stid, functools.partial(self.make_structure_mask, 
                                                    [stid], direct_only))


    def check_coverage(self, structure_ids, domain_mask):
        '''Determines whether a spatial domain is completely covered by 
        structures in a set.
        
        Parameters
        ----------
        structure_ids : list of int 
            Specifies the set of structures to check.
        domain_mask : numpy ndarray
            Same shape as annotation. 1 inside the mask, 0 out. Specifies 
            spatial domain.
            
        Returns
        -------
        numpy ndarray : 
            1 where voxels are missing from the candidate, 0 where the 
            candidate exceeds the domain
        
        '''
    
        candidate_mask = self.make_structure_mask(structure_ids)
        return domain_mask - candidate_mask
        
    def validate_structures(self, structure_ids, domain_mask):
        '''Determines whether a set of structures produces an exact and 
        nonoverlapping tiling of a spatial domain
        
        Parameters
        ----------
        structure_ids : list of int 
            Specifies the set of structures to check.
        domain_mask : numpy ndarray
           Same shape as annotation. 1 inside the mask, 0 out. Specifies 
           spatial domain.
           
        Returns
        -------
        set : 
            Ids of structures that are the ancestors of other structures in 
            the supplied set.
        numpy ndarray : 
            Indicator for missing voxels.
            
        '''
        
        return [self.structure_tree.has_overlaps(structure_ids), 
                self.check_coverage(structure_ids, domain_mask)]
        
        
    def downsample(self, target_resolution):
        '''Obtain a smaller reference space by downsampling
        
        Parameters
        ----------
        target_resolution : tuple of numeric
            Resolution in microns of the output space.
        interpolator : string
            Method used to interpolate the volume. Currently only 'nearest' 
            is supported
            
        Returns
        -------
        ReferenceSpace : 
            A new ReferenceSpace with the same structure tree and a 
            downsampled annotation.
        
        '''
        
        factors = [ float(ii / jj) for ii, jj in zip(self.resolution, 
                                                     target_resolution)]
                                                     
        target = zoom(self.annotation, factors, order=0)
        
        return ReferenceSpace(self.structure_tree, target, target_resolution)
        
        
    def get_slice_image(self, axis, position, cmap=None):
        '''Produce a AxBx3 RGB image from a slice in the annotation
        
        Parameters
        ----------
        axis : int
            Along which to slice the annotation volume. 0 is coronal, 1 is 
            horizontal, and 2 is sagittal.
        position : int 
            In microns. Take the slice from this far along the specified axis.
        cmap : dict, optional
            Keys are structure ids, values are rgb triplets. Defaults to 
            structure rgb_triplets. 
            
        Returns
        -------
        np.ndarray : 
            RGB image array. 
            
        Notes
        -----
        If you assign a custom colormap, make sure that you take care of the 
        background in addition to the structures.
        
        '''
        
        if cmap is None:
            cmap = self.structure_tree.get_colormap()
            cmap[0] = [0, 0, 0]
        
        position = int(np.around(position / self.resolution[axis]))
        image = np.squeeze(self.annotation.take([position], axis=axis))
            
        return np.reshape([cmap[point] for point in image.flat], 
                      list(image.shape) + [3]).astype(np.uint8)
            
            
    def export_itksnap_labels(self, id_type=np.uint16, label_description_kwargs=None):
        '''Produces itksnap labels, remapping large ids if needed.

        Parameters
        ----------
        id_type : np.integer, optional
            Used to determine the type of the output annotation and whether ids need to be remapped to smaller values.
        label_description_kwargs : dict, optional
            Keyword arguments passed to StructureTree.export_label_description

        Returns
        -------
        np.ndarray : 
            Annotation volume, remapped if needed
        pd.DataFrame
            label_description dataframe

        '''

        if label_description_kwargs is None:
            label_description_kwargs = {}

        label_description = self.structure_tree.export_label_description(**label_description_kwargs)

        if np.any(label_description['IDX'].values > np.iinfo(id_type).max):
            label_description = label_description.sort_values(by='LABEL')
            label_description = label_description.reset_index(drop=True)
            new_annotation = np.zeros(self.annotation.shape, dtype=id_type)
            id_map = {}

            for ii, idx in enumerate(label_description['IDX'].values):
                id_map[idx] = ii + 1
                new_annotation[self.annotation == idx] = ii + 1

            label_description['IDX'] = label_description.apply(lambda row: id_map[row['IDX']], axis=1)
            return new_annotation, label_description

        return self.annotation, label_description

    
    def write_itksnap_labels(self, annotation_path, label_path, **kwargs):
        '''Generate a label file (nrrd) and a label_description file (csv) for use with ITKSnap

        Parameters
        ----------
        annotation_path : str
            write generated label file here
        label_path : str
            write generated label_description file here
        **kwargs : 
            will be passed to self.export_itksnap_labels

        '''

        annotation, labels = self.export_itksnap_labels(**kwargs)
        nrrd.write(annotation_path, annotation, header={'spacings': self.resolution})
        labels.to_csv(label_path, sep=' ', index=False, header=False, quoting=csv.QUOTE_NONNUMERIC)


    @staticmethod
    def return_mask_cb(structure_id, fn):
        '''A basic callback for many_structure_masks
        '''
    
        return structure_id, fn()
        
        
    @staticmethod
    def check_and_write(base_dir, structure_id, fn):
        '''A many_structure_masks callback that writes the mask to a nrrd file 
        if the file does not already exist.
        '''
    
        mask_path = os.path.join(base_dir, 
                                 'structure_{0}.nrrd'.format(structure_id))
        
        if not os.path.exists(mask_path):
            nrrd.write(mask_path, fn())
            
        return structure_id
示例#13
0
import os
import json
import nrrd
import re
import numpy as np
import pandas as pd
from allensdk.api.queries.ontologies_api import OntologiesApi
from allensdk.core.structure_tree import StructureTree
from skimage.measure import regionprops

# --------------------------------------------------- Globals ----------------------------------------------------------

oapi = OntologiesApi()
structure_graph = oapi.get_structures_with_sets([1])
structure_graph = StructureTree.clean_structures(structure_graph)
tree = StructureTree(structure_graph)
del oapi
name_map = tree.get_name_map()

# ------------------------------------------- Obtain CSV from json  ----------------------------------------------------


def parseExpressionToDF(fileName, queryList, saveName=None):
    """
    FUNCTION: pull a specified pd.DataFrame object out of a jsonn file obtained from an AllenSDK:
        RMA api-based AGEA structure unionize query.
    ARGUMENTS:
        fileName = json file path (str).
            Forces you to save the direct database output to json.
            Please keep all of this (regardless of how much you choose to obtain)!
        queryList = contains (1) the keys from which you wish to obtain data for each row.
def test_hex_to_rgb(inp, out):
    obt = StructureTree.hex_to_rgb(inp)
    assert(allclose(obt, out))
示例#15
0
# Set input/output filenames:
structIDSource = 'structIDs_Oh.csv'
structInfoFilename = 'strutInfo_Oh.csv'
outputFilename = 'mask_Oh.h5'
print("Making a mask for Oh structures as %s" % outputFilename)

# Set max number of voxels:
maxVoxels = 0
# (0: no max)
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
oapi = OntologiesApi()
structure_graph = oapi.get_structures_with_sets([adultMouseStructureGraphID])
# Removes some unused fields returned by the query:
structure_graph = StructureTree.clean_structures(structure_graph)
tree = StructureTree(structure_graph)

# Example:
# tree.get_structures_by_name(['Dorsal auditory area'])
# The annotation download writes a file, so we will need somwhere to put it
annotation_dir = 'annotation'
Manifest.safe_mkdir(annotation_dir)
annotation_path = os.path.join(annotation_dir, 'annotation.nrrd')

#-------------------------------------------------------------------------------
# Use the connectivity API:
mcapi = MouseConnectivityApi()
# The name of the latest ccf version (a string):
annotation_version = mcapi.CCF_VERSION_DEFAULT
mcapi.download_annotation_volume(annotation_version, resolution,
示例#16
0
def test_notebook(fn_temp_dir):

    # coding: utf-8

    # # Reference Space
    #
    # This notebook contains example code demonstrating the use of the StructureTree and ReferenceSpace classes. These classes provide methods for interacting with the 3d spaces to which Allen Institute data and atlases are registered.
    #
    # Unlike the AllenSDK cache classes, StructureTree and ReferenceSpace operate entirely in memory. We recommend using json files to store text and nrrd files to store volumetric images.
    #
    # The MouseConnectivityCache class has methods for downloading, storing, and constructing StructureTrees and ReferenceSpaces. Please see [here](https://alleninstitute.github.io/AllenSDK/_static/examples/nb/mouse_connectivity.html) for examples.

    # ## Constructing a StructureTree
    #
    # A StructureTree object is a wrapper around a structure graph - a list of dictionaries documenting brain structures and their containment relationships. To build a structure tree, you will first need to obtain a structure graph.
    #
    # For a list of atlases and corresponding structure graph ids, see [here](http://help.brain-map.org/display/api/Atlas+Drawings+and+Ontologies).

    # In[1]:

    from allensdk.api.queries.ontologies_api import OntologiesApi
    from allensdk.core.structure_tree import StructureTree

    oapi = OntologiesApi()
    structure_graph = oapi.get_structures_with_sets(
        [1])  # 1 is the id of the adult mouse structure graph

    # This removes some unused fields returned by the query
    structure_graph = StructureTree.clean_structures(structure_graph)

    tree = StructureTree(structure_graph)

    # In[2]:

    # now let's take a look at a structure
    tree.get_structures_by_name(['Dorsal auditory area'])

    # The fields are:
    #     * acronym: a shortened name for the structure
    #     * rgb_triplet: each structure is assigned a consistent color for visualizations
    #     * graph_id: the structure graph to which this structure belongs
    #     * graph_order: each structure is assigned a consistent position in the flattened graph
    #     * id: a unique integer identifier
    #     * name: the full name of the structure
    #     * structure_id_path: traces a path from the root node of the tree to this structure
    #     * structure_set_ids: the structure belongs to these predefined groups

    # ## Using a StructureTree

    # In[3]:

    # get a structure's parent
    tree.parent([1011])

    # In[4]:

    # get a dictionary mapping structure ids to names

    name_map = tree.get_name_map()
    name_map[247]

    # In[5]:

    # ask whether one structure is contained within another

    strida = 385
    stridb = 247

    is_desc = '' if tree.structure_descends_from(385, 247) else ' not'

    print('{0} is{1} in {2}'.format(name_map[strida], is_desc,
                                    name_map[stridb]))

    # In[6]:

    # build a custom map that looks up acronyms by ids
    # the syntax here is just a pair of node-wise functions.
    # The first one returns keys while the second one returns values

    acronym_map = tree.value_map(lambda x: x['id'], lambda y: y['acronym'])
    print(acronym_map[385])

    # ## Downloading an annotation volume
    #
    # This code snippet will download and store a nrrd file containing the Allen Common Coordinate Framework annotation. We have requested an annotation with 25-micron isometric spacing. The orientation of this space is:
    #     * Anterior -> Posterior
    #     * Superior -> Inferior
    #     * Left -> Right
    # This is the no-frills way to download an annotation volume. See the <a href='_static/examples/nb/mouse_connectivity.html#Manipulating-Grid-Data'>mouse connectivity</a> examples if you want to properly cache the downloaded data.

    # In[7]:

    import os
    import nrrd
    from allensdk.api.queries.mouse_connectivity_api import MouseConnectivityApi
    from allensdk.config.manifest import Manifest

    # the annotation download writes a file, so we will need somwhere to put it
    annotation_dir = 'annotation'
    Manifest.safe_mkdir(annotation_dir)

    annotation_path = os.path.join(annotation_dir, 'annotation.nrrd')

    mcapi = MouseConnectivityApi()
    mcapi.download_annotation_volume('annotation/ccf_2016', 25,
                                     annotation_path)

    annotation, meta = nrrd.read(annotation_path)

    # ## Constructing a ReferenceSpace

    # In[8]:

    from allensdk.core.reference_space import ReferenceSpace

    # build a reference space from a StructureTree and annotation volume, the third argument is
    # the resolution of the space in microns
    rsp = ReferenceSpace(tree, annotation, [25, 25, 25])

    # ## Using a ReferenceSpace

    # #### making structure masks
    #
    # The simplest use of a Reference space is to build binary indicator masks for structures or groups of structures.

    # In[9]:

    # A complete mask for one structure
    whole_cortex_mask = rsp.make_structure_mask([315])

    # view in coronal section

    # What if you want a mask for a whole collection of ontologically disparate structures? Just pass more structure ids to make_structure_masks:

    # In[10]:

    # This gets all of the structures targeted by the Allen Brain Observatory project
    brain_observatory_structures = rsp.structure_tree.get_structures_by_set_id(
        [514166994])
    brain_observatory_ids = [st['id'] for st in brain_observatory_structures]

    brain_observatory_mask = rsp.make_structure_mask(brain_observatory_ids)

    # view in horizontal section

    # You can also make and store a number of structure_masks at once:

    # In[11]:

    import functools

    # Define a wrapper function that will control the mask generation.
    # This one checks for a nrrd file in the specified base directory
    # and builds/writes the mask only if one does not exist
    mask_writer = functools.partial(ReferenceSpace.check_and_write,
                                    annotation_dir)

    # many_structure_masks is a generator - nothing has actrually been run yet
    mask_generator = rsp.many_structure_masks([385, 1097], mask_writer)

    # consume the resulting iterator to make and write the masks
    for structure_id in mask_generator:
        print('made mask for structure {0}.'.format(structure_id))

    os.listdir(annotation_dir)

    # #### Removing unassigned structures

    # A structure graph may contain structures that are not used in a particular reference space. Having these around can complicate use of the reference space, so we generally want to remove them.
    #
    # We'll try this using "Somatosensory areas, layer 6a" as a test case. In the 2016 ccf space, this structure is unused in favor of finer distinctions (e.g. "Primary somatosensory area, barrel field, layer 6a").

    # In[12]:

    # Double-check the voxel counts
    no_voxel_id = rsp.structure_tree.get_structures_by_name(
        ['Somatosensory areas, layer 6a'])[0]['id']
    print('voxel count for structure {0}: {1}'.format(
        no_voxel_id, rsp.total_voxel_map[no_voxel_id]))

    # remove unassigned structures from the ReferenceSpace's StructureTree
    rsp.remove_unassigned()

    # check the structure tree
    no_voxel_id in rsp.structure_tree.node_ids()

    # #### View a slice from the annotation

    # In[13]:

    import numpy as np

    # #### Downsample the space
    #
    # If you want an annotation at a resolution we don't provide, you can make one with the downsample method.

    # In[14]:

    import warnings

    target_resolution = [75, 75, 75]

    # in some versions of scipy, scipy.ndimage.zoom raises a helpful but distracting
    # warning about the method used to truncate integers.
    warnings.simplefilter('ignore')

    sf_rsp = rsp.downsample(target_resolution)

    # re-enable warnings
    warnings.simplefilter('default')

    print(rsp.annotation.shape)
    print(sf_rsp.annotation.shape)
        os.makedirs(os.path.dirname(path))
    with open(path + name + ".obj", 'w') as file:
        for vert in verts:
            file.write("v " + str(vert[0]) + " " + str(vert[1]) + " " +
                       str(vert[2]) + "\n")
        for face in faces:
            file.write("f " + str(face[0] + 1) + " " + str(face[1] + 1) + " " +
                       str(face[2] + 1) + "\n")


graph_id = 1  # Graph_id is the id of the structure we want to load. 1 is the id of the adult mouse structure graph

oapi = OntologiesApi()
structure_graph = oapi.get_structures_with_sets([graph_id])
# This removes some unused fields returned by the query
structure_graph = StructureTree.clean_structures(structure_graph)
tree = StructureTree(structure_graph)

# the annotation download writes a file, so we will need somwhere to put it
annotation_dir = 'E:\\Histology\\allen_rsp'

annotation_path = os.path.join(annotation_dir, 'annotation_10.nrrd')

# this is a string which contains the name of the latest ccf version
annotation_version = MouseConnectivityApi.CCF_VERSION_DEFAULT

mcapi = MouseConnectivityApi()
#Next line commented because the annotation volume is already downloaded
mcapi.download_annotation_volume(annotation_version, 10, annotation_path)

annotation, meta = nrrd.read(annotation_path)
def test_path_to_list(inp, out):
    obt = StructureTree.path_to_list(inp)
    assert(allclose(obt, out))
示例#19
0
class ReferenceSpace(object):
    @property
    def direct_voxel_map(self):
        if not hasattr(self, '_direct_voxel_map'):
            self.direct_voxel_counts()
        return self._direct_voxel_map

    @direct_voxel_map.setter
    def direct_voxel_map(self, data):
        self._direct_voxel_map = data

    @property
    def total_voxel_map(self):
        if not hasattr(self, '_total_voxel_map'):
            self.total_voxel_counts()
        return self._total_voxel_map

    @total_voxel_map.setter
    def total_voxel_map(self, data):
        self._total_voxel_map = data

    def __init__(self, structure_tree, annotation, resolution):
        '''Handles brain structures in a 3d reference space
        
        Parameters
        ----------
        structure_tree : StructureTree
            Defines the heirarchy and properties of the brain structures.
        annotation : numpy ndarray
            3d volume whose elements are structure ids.
        resolution : length-3 tuple of numeric
            Resolution of annotation voxels along each dimension.
        
        '''

        self.structure_tree = structure_tree
        self.resolution = resolution

        self.annotation = np.ascontiguousarray(annotation)

    def direct_voxel_counts(self):
        '''Determines the number of voxels directly assigned to one or more 
        structures.
        
        Returns
        -------
        dict : 
            Keys are structure ids, values are the number of voxels directly 
            assigned to those structures.
        
        '''

        uniques = np.unique(self.annotation, return_counts=True)
        found = {k: v for k, v in zip(*uniques) if k != 0}

        self._direct_voxel_map = {
            k: (found[k] if k in found else 0)
            for k in self.structure_tree.node_ids()
        }

    def total_voxel_counts(self):
        '''Determines the number of voxels assigned to a structure or its 
        descendants
            
        Returns
        -------
        dict : 
            Keys are structure ids, values are the number of voxels assigned 
            to structures' descendants.
        
        '''

        self._total_voxel_map = {}
        for stid in self.structure_tree.node_ids():

            desc_ids = self.structure_tree.descendant_ids([stid])[0]
            self._total_voxel_map[stid] = sum(
                [self.direct_voxel_map[dscid] for dscid in desc_ids])

    def remove_unassigned(self, update_self=True):
        '''Obtains a structure tree consisting only of structures that have 
        at least one voxel in the annotation.
        
        Parameters
        ----------
        update_self : bool, optional
            If True, the contained structure tree will be replaced,
            
        Returns
        -------
        list of dict : 
            elements are filtered structures
        
        '''

        structures = self.structure_tree.filter_nodes(
            lambda x: self.total_voxel_map[x['id']] > 0)

        if update_self:
            self.structure_tree = StructureTree(structures)

        return structures

    def make_structure_mask(self, structure_ids, direct_only=False):
        '''Return an indicator array for one or more structures
        
        Parameters
        ----------
        structure_ids : list of int
            Make a mask that indicates the union of these structures' voxels
        direct_only : bool, optional
            If True, only include voxels directly assigned to a structure in 
            the mask. Otherwise include voxels assigned to descendants.
            
        Returns
        -------
        numpy ndarray :
            Same shape as annotation. 1 inside mask, 0 outside.
        
        '''

        if direct_only:
            mask = np.zeros(self.annotation.shape, dtype=np.uint8, order='C')
            for stid in structure_ids:

                if self.direct_voxel_map[stid] == 0:
                    continue

                mask[self.annotation == stid] = True

            return mask

        else:
            structure_ids = self.structure_tree.descendant_ids(structure_ids)
            structure_ids = set(functools.reduce(op.add, structure_ids))
            return self.make_structure_mask(structure_ids, direct_only=True)

    def many_structure_masks(self,
                             structure_ids,
                             output_cb=None,
                             direct_only=False):
        '''Build one or more structure masks and do something with them
        
        Parameters
        ----------
        structure_ids : list of int
            Specify structures to be masked
        output_cb : function, optional
            Must have the following signature: output_cb(structure_id, fn). 
            On each requested id, fn will be curried to make a mask for that 
            id. Defaults to returning the structure id and mask.
        direct_only : bool, optional
            If True, only include voxels directly assigned to a structure in 
            the mask. Otherwise include voxels assigned to descendants.
            
        Yields
        -------
        Return values of output_cb called on each structure_id, structure_mask 
        pair.
        
        Notes
        -----
        output_cb is called on every yield, so any side-effects (such as 
        writing to a file) will be carried out regardless of what you do with 
        the return values. You do actually have to iterate through the output, 
        though.
        
        '''

        if output_cb is None:
            output_cb = ReferenceSpace.return_mask_cb

        for stid in structure_ids:
            yield output_cb(
                stid,
                functools.partial(self.make_structure_mask, [stid],
                                  direct_only))

    def check_coverage(self, structure_ids, domain_mask):
        '''Determines whether a spatial domain is completely covered by 
        structures in a set.
        
        Parameters
        ----------
        structure_ids : list of int 
            Specifies the set of structures to check.
        domain_mask : numpy ndarray
            Same shape as annotation. 1 inside the mask, 0 out. Specifies 
            spatial domain.
            
        Returns
        -------
        numpy ndarray : 
            1 where voxels are missing from the candidate, 0 where the 
            candidate exceeds the domain
        
        '''

        candidate_mask = self.make_structure_mask(structure_ids)
        return domain_mask - candidate_mask

    def validate_structures(self, structure_ids, domain_mask):
        '''Determines whether a set of structures produces an exact and 
        nonoverlapping tiling of a spatial domain
        
        Parameters
        ----------
        structure_ids : list of int 
            Specifies the set of structures to check.
        domain_mask : numpy ndarray
           Same shape as annotation. 1 inside the mask, 0 out. Specifies 
           spatial domain.
           
        Returns
        -------
        set : 
            Ids of structures that are the ancestors of other structures in 
            the supplied set.
        numpy ndarray : 
            Indicator for missing voxels.
            
        '''

        return [
            self.structure_tree.has_overlaps(structure_ids),
            self.check_coverage(structure_ids, domain_mask)
        ]

    def downsample(self, target_resolution):
        '''Obtain a smaller reference space by downsampling
        
        Parameters
        ----------
        target_resolution : tuple of numeric
            Resolution in microns of the output space.
        interpolator : string
            Method used to interpolate the volume. Currently only 'nearest' 
            is supported
            
        Returns
        -------
        ReferenceSpace : 
            A new ReferenceSpace with the same structure tree and a 
            downsampled annotation.
        
        '''

        factors = [
            float(ii / jj)
            for ii, jj in zip(self.resolution, target_resolution)
        ]

        target = zoom(self.annotation, factors, order=0)

        return ReferenceSpace(self.structure_tree, target, target_resolution)

    def get_slice_image(self, axis, position, cmap=None):
        '''Produce a AxBx3 RGB image from a slice in the annotation
        
        Parameters
        ----------
        axis : int
            Along which to slice the annotation volume. 0 is coronal, 1 is 
            horizontal, and 2 is sagittal.
        position : int 
            In microns. Take the slice from this far along the specified axis.
        cmap : dict, optional
            Keys are structure ids, values are rgb triplets. Defaults to 
            structure rgb_triplets. 
            
        Returns
        -------
        np.ndarray : 
            RGB image array. 
            
        Notes
        -----
        If you assign a custom colormap, make sure that you take care of the 
        background in addition to the structures.
        
        '''

        if cmap is None:
            cmap = self.structure_tree.get_colormap()
            cmap[0] = [0, 0, 0]

        position = int(np.around(position / self.resolution[axis]))
        image = np.squeeze(self.annotation.take([position], axis=axis))

        return np.reshape([cmap[point] for point in image.flat],
                          list(image.shape) + [3]).astype(np.uint8)

    def export_itksnap_labels(self,
                              id_type=np.uint16,
                              label_description_kwargs=None):
        '''Produces itksnap labels, remapping large ids if needed.

        Parameters
        ----------
        id_type : np.integer, optional
            Used to determine the type of the output annotation and whether ids need to be remapped to smaller values.
        label_description_kwargs : dict, optional
            Keyword arguments passed to StructureTree.export_label_description

        Returns
        -------
        np.ndarray : 
            Annotation volume, remapped if needed
        pd.DataFrame
            label_description dataframe

        '''

        if label_description_kwargs is None:
            label_description_kwargs = {}

        label_description = self.structure_tree.export_label_description(
            **label_description_kwargs)

        if np.any(label_description['IDX'].values > np.iinfo(id_type).max):
            label_description = label_description.sort_values(by='LABEL')
            label_description = label_description.reset_index(drop=True)
            new_annotation = np.zeros(self.annotation.shape, dtype=id_type)
            id_map = {}

            for ii, idx in enumerate(label_description['IDX'].values):
                id_map[idx] = ii + 1
                new_annotation[self.annotation == idx] = ii + 1

            label_description['IDX'] = label_description.apply(
                lambda row: id_map[row['IDX']], axis=1)
            return new_annotation, label_description

        return self.annotation, label_description

    def write_itksnap_labels(self, annotation_path, label_path, **kwargs):
        '''Generate a label file (nrrd) and a label_description file (csv) for use with ITKSnap

        Parameters
        ----------
        annotation_path : str
            write generated label file here
        label_path : str
            write generated label_description file here
        **kwargs : 
            will be passed to self.export_itksnap_labels

        '''

        annotation, labels = self.export_itksnap_labels(**kwargs)
        nrrd.write(annotation_path,
                   annotation,
                   header={'spacings': self.resolution})
        labels.to_csv(label_path,
                      sep=' ',
                      index=False,
                      header=False,
                      quoting=csv.QUOTE_NONNUMERIC)

    @staticmethod
    def return_mask_cb(structure_id, fn):
        '''A basic callback for many_structure_masks
        '''

        return structure_id, fn()

    @staticmethod
    def check_and_write(base_dir, structure_id, fn):
        '''A many_structure_masks callback that writes the mask to a nrrd file 
        if the file does not already exist.
        '''

        mask_path = os.path.join(base_dir,
                                 'structure_{0}.nrrd'.format(structure_id))

        if not os.path.exists(mask_path):
            nrrd.write(mask_path, fn())

        return structure_id