Skip to content

Commit 1d1551a

Browse files
committed
Reading and writing JSON/binary JSON based surface data (.jmsh and .bmsh)
1 parent b7bbf0e commit 1d1551a

File tree

4 files changed

+248
-1
lines changed

4 files changed

+248
-1
lines changed

nibabel/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from .cifti2 import Cifti2Header, Cifti2Image
5757
from .gifti import GiftiImage
5858
from .freesurfer import MGHImage
59+
from .jmesh import JMesh
5960
from .funcs import (squeeze_image, concat_images, four_to_three,
6061
as_closest_canonical)
6162
from .orientations import (io_orientation, orientation_affine,

nibabel/imageclasses.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .spm99analyze import Spm99AnalyzeImage
2222
from .spm2analyze import Spm2AnalyzeImage
2323
from .volumeutils import Recoder
24+
from .jmesh import JMesh
2425
from .deprecated import deprecate_with_version
2526

2627
from .optpkg import optional_package
@@ -32,7 +33,7 @@
3233
Cifti2Image, Nifti2Image, # Cifti2 before Nifti2
3334
Spm2AnalyzeImage, Spm99AnalyzeImage, AnalyzeImage,
3435
Minc1Image, Minc2Image, MGHImage,
35-
PARRECImage, GiftiImage, AFNIImage]
36+
PARRECImage, GiftiImage, AFNIImage, JMesh]
3637

3738

3839
# DEPRECATED: mapping of names to classes and class functionality

nibabel/jmesh/__init__.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
4+
#
5+
# See COPYING file distributed along with the NiBabel package for the
6+
# copyright and license terms.
7+
#
8+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9+
"""JSON and BJData based JMesh format IO
10+
11+
.. currentmodule:: nibabel.jmesh
12+
13+
.. autosummary::
14+
:toctree: ../generated
15+
16+
jmesh
17+
"""
18+
19+
from .jmesh import load, save, JMesh, default_header

nibabel/jmesh/jmesh.py

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
4+
#
5+
# See COPYING file distributed along with the NiBabel package for the
6+
# copyright and license terms.
7+
#
8+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9+
# General JMesh Input - Output to and from the filesystem
10+
# Qianqian Fang <q.fang at neu.edu>
11+
##############
12+
13+
__all__ = ['JMesh','read','write','default_header']
14+
15+
from jdata import (load as jdload, save as jdsave)
16+
import numpy as np
17+
from ..filebasedimages import FileBasedImage
18+
19+
default_header = {
20+
"JMeshVersion":"0.5",
21+
"Comment":"Created by NiPy with NeuroJSON JMesh specification",
22+
"AnnotationFormat":"https://neurojson.org/jmesh/draft2",
23+
"Parser":{
24+
"Python":[
25+
"https://pypi.org/project/jdata",
26+
"https://pypi.org/project/bjdata"
27+
],
28+
"MATLAB":[
29+
"https://github.com/NeuroJSON/jnifty",
30+
"https://github.com/NeuroJSON/jsonlab"
31+
],
32+
"JavaScript":"https://github.com/NeuroJSON/jsdata",
33+
"CPP":"https://github.com/NeuroJSON/json",
34+
"C":"https://github.com/NeuroJSON/ubj"
35+
}
36+
}
37+
38+
class JMesh(FileBasedImage):
39+
"""JMesh: a simple data structure representing a brain surface
40+
41+
* Description - JMesh defines a set of language-neutral JSON annotations for
42+
storage and exchange of mesh-related data. The details of the specification
43+
can be found in NeuroJSON's website at https://neurojson.org
44+
45+
* Child Elements: [NA]
46+
* Text Content: [NA]
47+
48+
Attributes
49+
----------
50+
info: a dict
51+
A dict object storing the metadata (`_DataInfo_`) section of the JMesh
52+
file
53+
node : 2-D list or numpy array
54+
A 2-D numpy.ndarray object to store the vertices of the mesh
55+
nodelabel : 1-D list or numpy array
56+
A 1-D numpy.ndarray object to store the label of each vertex
57+
face : 2-D list or numpy array
58+
A 2-D numpy.ndarray object to store the triangular elements of the
59+
mesh; indices start from 1
60+
facelabel : 1-D list or numpy array
61+
A 1-D numpy.ndarray object to store the label of each triangle
62+
raw : a dict
63+
The raw data loaded from the .jmsh or .bmsh file
64+
"""
65+
valid_exts = ('.jmsh', '.bmsh')
66+
files_types = (('image', '.jmsh'), ('image', '.bmsh'))
67+
makeable = False
68+
rw = True
69+
70+
def __init__(self, info=None, node=None, nodelabel=None, face=None,
71+
facelabel=None):
72+
73+
self.raw = {}
74+
if(not info is None):
75+
self.raw['_DataInfo_'] = info
76+
77+
if(not nodelabel is None):
78+
self.raw['MeshVertex3'] = {'Data': node, 'Properties': {'Tag': nodelabel} }
79+
self.node = self.raw['MeshVertex3']['Data']
80+
self.nodelabel = self.raw['MeshVertex3']['Properties']['Tag']
81+
else:
82+
self.raw['MeshVertex3'] = node
83+
self.node = self.raw['MeshVertex3']
84+
85+
if(not facelabel is None):
86+
self.raw['MeshTri3'] = {'Data': face, 'Properties': {'Tag': facelabel} }
87+
self.face = self.raw['MeshTri3']['Data']
88+
self.facelabel = self.raw['MeshTri3']['Properties']['Tag']
89+
else:
90+
self.raw['MeshTri3'] = face
91+
self.face = self.raw['MeshTri3']
92+
93+
@classmethod
94+
def from_filename(self, filename, opt={}, **kwargs):
95+
self = read(filename, opt, **kwargs)
96+
return self
97+
98+
@classmethod
99+
def to_filename(self, filename, opt={}, **kwargs):
100+
write(self, filename, opt, **kwargs)
101+
102+
def read(filename, opt={}, **kwargs):
103+
""" Load a JSON or binary JData (BJData) based JMesh file
104+
105+
Parameters
106+
----------
107+
filename : string
108+
The JMesh file to open, it has usually ending .gii
109+
opt: a dict that may contain below option keys
110+
ndarray: boolean, if True, node/face/nodelabel/facelabel are converted
111+
to numpy.ndarray, otherwise, leave those unchanged
112+
kwargs: additional keyword arguments for `json.load` when .jmsh file is being loaded
113+
114+
Returns
115+
-------
116+
mesh : a JMesh object
117+
Return a JMesh object containing mesh data fields such as node, face, nodelabel etc
118+
"""
119+
opt.setdefault('ndarray',True)
120+
121+
mesh = JMesh
122+
mesh.raw = jdload(filename, opt, **kwargs)
123+
124+
#--------------------------------------------------
125+
# read metadata as `info`
126+
#--------------------------------------------------
127+
if('_DataInfo_' in mesh.raw):
128+
mesh.info = mesh.raw['_DataInfo_']
129+
130+
#--------------------------------------------------
131+
# read vertices as `node` and `nodelabel`
132+
#--------------------------------------------------
133+
if('MeshVertex3' in mesh.raw):
134+
mesh.node = mesh.raw['MeshVertex3']
135+
elif('MeshNode' in mesh.raw):
136+
mesh.node = mesh.raw['MeshNode']
137+
else:
138+
raise Exception('JMesh', 'JMesh surface must contain node (MeshVertex3 or MeshNode)')
139+
140+
if(isinstance(mesh.node, dict)):
141+
if(('Properties' in mesh.node) and ('Tag' in mesh.node['Properties'])):
142+
mesh.nodelabel = mesh.node['Properties']['Tag']
143+
if('Data' in mesh.node):
144+
mesh.node = mesh.node['Data']
145+
if(isinstance(mesh.node, np.ndarray) and mesh.node.ndim == 2 and mesh.node.shape[1] > 3):
146+
mesh.nodelabel = mesh.node[:,3:]
147+
mesh.node = mesh.node[:, 0:3]
148+
149+
#--------------------------------------------------
150+
# read triangles as `face` and `facelabel`
151+
#--------------------------------------------------
152+
if('MeshTri3' in mesh.raw):
153+
mesh.face = mesh.raw['MeshTri3']
154+
elif('MeshSurf' in mesh.raw):
155+
mesh.face = mesh.raw['MeshSurf']
156+
157+
if(isinstance(mesh.face, dict)):
158+
if(('Properties' in mesh.face) and ('Tag' in mesh.face['Properties'])):
159+
mesh.facelabel = mesh.face['Properties']['Tag']
160+
if('Data' in mesh.face):
161+
mesh.face = mesh.face['Data']
162+
if(isinstance(mesh.face, np.ndarray) and mesh.face.ndim == 2 and mesh.face.shape[1] > 3):
163+
mesh.facelabel = mesh.face[:,3:]
164+
mesh.face = mesh.face[:, 0:3]
165+
166+
#--------------------------------------------------
167+
# convert to numpy ndarray
168+
#--------------------------------------------------
169+
if(opt['ndarray']):
170+
if hasattr(mesh, 'node') and (not mesh.node is None) and (not isinstance(mesh.node, np.ndarray)):
171+
mesh.node = np.array(mesh.node)
172+
173+
if hasattr(mesh, 'face') and (not mesh.face is None) and (not isinstance(mesh.face, np.ndarray)):
174+
mesh.face = np.array(mesh.face)
175+
176+
if hasattr(mesh, 'nodelabel') and (not mesh.nodelabel is None) and (not isinstance(mesh.nodelabel, np.ndarray)):
177+
mesh.nodelabel = np.array(mesh.nodelabel)
178+
179+
if hasattr(mesh, 'facelabel') and (not mesh.facelabel is None) and (not isinstance(mesh.facelabel, np.ndarray)):
180+
mesh.facelabel = np.array(mesh.facelabel)
181+
182+
return mesh
183+
184+
def write(mesh, filename, opt={}, **kwargs):
185+
""" Save the current mesh to a new file
186+
187+
Parameters
188+
----------
189+
mesh : a JMesh object
190+
filename : string
191+
Filename to store the JMesh file (.jmsh for JSON based JMesh and
192+
.bmsh for binary JMesh files)
193+
opt: a dict that may contain below option keys
194+
ndarray: boolean, if True, node/face/nodelabel/facelabel are converted
195+
to numpy.ndarray, otherwise, leave those unchanged
196+
kwargs: additional keyword arguments for `json.dump` when .jmsh file is being saved
197+
198+
Returns
199+
-------
200+
None
201+
202+
We update the mesh related data fields `MeshVetex3`, `MeshTri3` and metadata `_DataInfo_`
203+
from mesh.node, mesh.face and mesh.info, then save mesh.raw to JData files
204+
"""
205+
206+
if not hasattr(mesh, 'raw') or mesh.raw is None:
207+
mesh.raw = {}
208+
209+
if hasattr(mesh, 'info') and not mesh.info is None:
210+
mesh.raw['_DataInfo_']=mesh.info
211+
if hasattr(mesh, 'node') and not mesh.node is None:
212+
if(hasattr(mesh, 'facelabel') and not mesh.nodelabel is None):
213+
mesh.raw['MeshVertex3']={'Data': mesh.node, 'Properties': {'Tag': mesh.nodelabel}}
214+
else:
215+
mesh.raw['MeshVertex3']=mesh.node
216+
217+
if hasattr(mesh, 'info') and not mesh.face is None:
218+
if(hasattr(mesh, 'facelabel') and not mesh.facelabel is None):
219+
mesh.raw['MeshTri3']={'Data': mesh.face, 'Properties': {'Tag': mesh.facelabel}}
220+
else:
221+
mesh.raw['MeshTri3']=mesh.face
222+
223+
return jdsave(mesh.raw, filename, opt, **kwargs)
224+
225+
load = read
226+
save = write

0 commit comments

Comments
 (0)