Source code for soma.aims.labelstools

'''
Manipulate labels maps in several formats (json, csv, xml, hie...) in a GIFTI-colormap compatible dict.

Such labels maps can be saved in textures or volumes headers. In a GIFTI texture, it will be saved in native GIFTI colormap format, otherwise it will be saved in the object .minf. Anatomist will read and use it.

'''


from soma import aims
import json
import os.path as osp
import xml.etree.cElementTree as ET
import argparse


read_labels_formats = ['json', 'hie', 'csv', 'xml']


[docs]def read_labels(nomenclature): ''' Read a nomenclature file, which may be in several formats: - JSON (JulichBrain nomenclature): {"properties": {"regions": [<region_def>, ...]}} region_def: { "labelIndex": "12", "name": "rototo", "color": [255, 255, 0], "children": [<region_def>, ...] } - CSV (JulichBrain nomenclature): 12, rototo (all colors will be black) - HIE (AIMS Hierarchical nomenclature) - XML (JulichBrain niftis) Returns ------- labels: dict {12: {"Label": "rototo", "RGB": [1., 1., 0., 1.]}, ...} ''' ext = osp.splitext(nomenclature)[1][1:] if ext: formats = [ext] + [format for format in read_labels_formats if format !=ext] else: formats = read_labels_formats for format in formats: exc = None if nomenclature.endswith('.%s' % format): read_func = globals().get('read_labels_%s' % format) if read_func: try: return read_func(nomenclature) except Exception as e: if not exc: exc = e pass if exc: raise exc
[docs]def read_labels_hie(nomenclature): ''' Read a nomenclature as labels dict. Used by :func:`read_labels`, does not need to be called directly. ''' # print('READ .hie labels') hie = aims.read(nomenclature) labels = {} todo = [hie] while todo: tree = todo.pop(0) todo += tree.children() if 'label' in tree and 'name' in tree: num = int(tree['label']) if 'color' in tree: color = [float(c)/255 for c in tree['color']] color += [1.] * (4 - len(color)) else: color = [.5, .5, .5, 1.] labels[num] = {'Label': tree['name'], 'RGB': color} if 0 not in labels: labels[0] = {'Label': 'unknown', 'RGB': [0., 0., 0., 1.]} # print('labels:', len(labels)) # delete thingsin correct order to avoid crash del tree, todo del hie return labels
[docs]def read_labels_json(nomenclature): ''' Read a nomenclature as labels dict. Used by :func:`read_labels`, does not need to be called directly. JSON (JulichBrain nomenclature v2.9):: {"properties": {"regions": [<region_def>, ...]}} region_def: { "labelIndex": "12", "name": "rototo", "color": [255, 255, 0], "children": [<region_def>, ...] } ''' labels = {} # process void also labels[0] = {'Label': 'unknown', 'RGB': [0., 0., 0., 1.]} with open(nomenclature) as f: labels_map = json.load(f) todo = list(labels_map['properties']['regions']) while todo: item = todo.pop(0) index = item.get('labelIndex') name = item['name'] if index is not None: index = int(index) if index in labels and labels[index]['Label'] != name: print('multiple index', index, ':', labels[index]['Label'], 'and:', name) continue labels[index] = {'Label': name} color = item.get('color') if color is not None: color = [float(c.strip()) / 255 for c in color.split(',')] + [1.] labels[index]['RGB'] = color todo += item.get('children', []) return labels
[docs]def read_labels_csv(nomenclature): ''' Read a nomenclature as labels dict. Used by :func:`read_labels`, does not need to be called directly. CSV (JulichBrain nomenclature):: 12, rototo (all colors will be black) ''' with open(nomenclature) as f: labels = f.read().strip().split('\n') labels = [l.strip().split() for l in labels] labels = {int(l[0]): {'Label': l[1]} for l in labels} # process void also labels[0] = {'Label': 'unknown', 'RGB': [0., 0., 0., 1.]} return labels
[docs]def read_labels_xml(nomenclature): ''' Read a nomenclature as labels dict. Used by :func:`read_labels`, does not need to be called directly. XML (JulichBrain v2.9-) ''' xml_trees = [ET.parse(nomenclature)] gifti_label_map = {} todo = [] for xml_tree in xml_trees: todo += xml_tree.getroot()[0][:] while todo: region = todo.pop(0) # print(region.text, region.items()) if region.get('grayvalue'): int_k = int(region.get('grayvalue')) else: int_k = int(region.get('id')) label = region.text rgb = region.get('color') if not rgb: rgb = np.random.rand(4) print('assign random color for', label, ':', rgb) rgb[3] = 1. else: rgb = rgb.split('(')[-1].split(')')[0].split(',') rgb = [float(s.strip()) / 255. for s in rgb] gifti_label_map[int_k] = {'Label': label, 'RGB': rgb} todo = region[:] + todo return gifti_label_map
[docs]def set_labels_to_object(aims_object, gifti_label_map): ''' Set a labels map (as obtained by read_labels(), or from a GIFTI texture header, into an Aims object header ''' aims_object.header()['GIFTI_labels_table'] = gifti_label_map if not type(aims_object).__name__.startswith('Volume'): aims_object.header()['texture_properties'] = [{'interpolation': 'rgb'}]
if __name__ == '__main__': parser = argparse.ArgumentParser( description='Set JulichBrain labels maps and colors into a ' 'Gifti texture or volume') parser.add_argument('-i', '--input', help='input AIMS object (texture/volume...)') parser.add_argument('-o', '--output', help='output AIMS object') parser.add_argument('-n', '--nomenclature', action='append', default=[], help='json, xml, csv, hie nomenclature. Several ' 'values are allowed') options = parser.parse_args() #print(options) if options.nomenclature: ontology_fnames = options.nomenclature tex_fname = options.input out_fname = options.output tex = aims.read(tex_fname) labels_maps = [read_labels(f) for f in ontology_fnames] labels_map = {} for lm in labels_maps: labels_map.update(lm) set_labels_to_object(tex, labels_map) print('save:', out_fname) aims.write(tex, out_fname)