-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathparsers.py
More file actions
336 lines (253 loc) · 13.8 KB
/
parsers.py
File metadata and controls
336 lines (253 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
from typing import List, Optional, Type, Union
import numpy as np
import numpy.typing as npt
from nrrd.errors import NRRDError
def parse_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray:
"""Parse NRRD vector from string into (N,) :class:`numpy.ndarray`.
See :ref:`background/datatypes:int vector` and :ref:`background/datatypes:double vector` for more information on
the format.
Parameters
----------
x : :class:`str`
String containing NRRD vector
dtype : data-type, optional
Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If
:obj:`dtype` is :obj:`None`, then it will be automatically determined by checking any of the vector elements
for fractional numbers. If found, then the vector will be converted to :class:`float`, otherwise :class:`int`.
Default is to automatically determine datatype.
Returns
-------
vector : (N,) :class:`numpy.ndarray`
Vector that is parsed from the :obj:`x` string
"""
if x[0] != '(' or x[-1] != ')':
raise NRRDError('Vector should be enclosed by parentheses.')
# Always convert to float and then truncate to integer if desired
# The reason why is parsing a floating point string to int will fail (i.e. int('25.1') will fail)
vector = np.array([float(x) for x in x[1:-1].split(',')])
# If using automatic datatype detection, then start by converting to float and determining if the number is whole
# Truncate to integer if dtype is int also
if dtype is None:
vector_trunc = vector.astype(int)
if np.all((vector - vector_trunc) == 0):
vector = vector_trunc
elif dtype == int:
vector = vector.astype(int)
elif dtype != float:
raise NRRDError('dtype should be None for automatic type detection, float or int')
return vector
def parse_optional_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> \
Optional[npt.NDArray]:
"""Parse optional NRRD vector from string into (N,) :class:`numpy.ndarray` or :obj:`None`.
Function parses optional NRRD vector from string into an (N,) :class:`numpy.ndarray`. This function works the same
as :meth:`parse_vector` except if :obj:`x` is 'none', :obj:`vector` will be :obj:`None`
See :ref:`background/datatypes:int vector` and :ref:`background/datatypes:double vector` for more information on
the format.
Parameters
----------
x : :class:`str`
String containing NRRD vector or 'none'
dtype : data-type, optional
Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If
:obj:`dtype` is :obj:`None`, then it will be automatically determined by checking any of the vector elements
for fractional numbers. If found, then the vector will be converted to :class:`float`, otherwise :class:`int`.
Default is to automatically determine datatype.
Returns
-------
vector : (N,) :class:`numpy.ndarray` or :obj:`None`
Vector that is parsed from the :obj:`x` string or :obj:`None` if :obj:`x` is 'none'
"""
if x == 'none':
return None
else:
return parse_vector(x, dtype)
def parse_matrix(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray:
"""Parse NRRD matrix from string into (M,N) :class:`numpy.ndarray`.
See :ref:`background/datatypes:int matrix` and :ref:`background/datatypes:double matrix` for more information on
the format.
Parameters
----------
x : :class:`str`
String containing NRRD matrix
dtype : data-type, optional
Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If
:obj:`dtype` is :obj:`None`, then it will be automatically determined by checking any of the elements
for fractional numbers. If found, then the matrix will be converted to :class:`float`, otherwise :class:`int`.
Default is to automatically determine datatype.
Returns
-------
matrix : (M,N) :class:`numpy.ndarray`
Matrix that is parsed from the :obj:`x` string
"""
# Split input by spaces, convert each row into a vector and stack them vertically to get a matrix
matrix = [parse_vector(x, dtype=float) for x in x.split()]
# Get the size of each row vector and then remove duplicate sizes
# There should be exactly one value in the matrix because all row sizes need to be the same
if len(np.unique([len(x) for x in matrix])) != 1:
raise NRRDError('Matrix should have same number of elements in each row')
matrix = np.vstack(matrix)
# If using automatic datatype detection, then start by converting to float and determining if the number is whole
# Truncate to integer if dtype is int also
if dtype is None:
matrix_trunc = matrix.astype(int)
if np.all((matrix - matrix_trunc) == 0):
matrix = matrix_trunc
elif dtype == int:
matrix = matrix.astype(int)
elif dtype != float:
raise NRRDError('dtype should be None for automatic type detection, float or int')
return matrix
def parse_optional_matrix(x: str) -> Optional[npt.NDArray]:
"""Parse optional NRRD matrix from string into (M,N) :class:`numpy.ndarray` of :class:`float`.
Function parses optional NRRD matrix from string into an (M,N) :class:`numpy.ndarray` of :class:`float`. This
function works the same as :meth:`parse_matrix` except if a row vector in the matrix is none, the resulting row in
the returned matrix will be all NaNs.
See :ref:`background/datatypes:double matrix` for more information on the format.
Parameters
----------
x : :class:`str`
String containing NRRD matrix
Returns
-------
matrix : (M,N) :class:`numpy.ndarray` of :class:`float`
Matrix that is parsed from the :obj:`x` string
"""
# Split input by spaces to get each row and convert into a vector. The row can be 'none', in which case it will
# return None
matrix = [parse_optional_vector(x, dtype=float) for x in x.split()]
# Get the size of each row vector, 0 if None
sizes = np.array([0 if x is None else len(x) for x in matrix])
# Get sizes of each row vector removing duplicate sizes
# Since each row vector should be same size, the unique sizes should return one value for the row size or it may
# return a second one (0) if there are None vectors
unique_sizes = np.unique(sizes)
if len(unique_sizes) != 1 and (len(unique_sizes) != 2 or unique_sizes.min() != 0):
raise NRRDError('Matrix should have same number of elements in each row')
# Create a vector row of NaN's that matches same size of remaining vector rows
# Stack the vector rows together to create matrix
nan_row = np.full((unique_sizes.max()), np.nan)
matrix = np.vstack([nan_row if x is None else x for x in matrix])
return matrix
def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray:
"""Parse NRRD number list from string into (N,) :class:`numpy.ndarray`.
See :ref:`background/datatypes:int list` and :ref:`background/datatypes:double list` for more information on the
format.
Parameters
----------
x : :class:`str`
String containing NRRD number list
dtype : data-type, optional
Datatype to use for the resulting Numpy array. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If
:obj:`dtype` is :obj:`None`, then it will be automatically determined by checking for fractional numbers. If
found, then the string will be converted to :class:`float`, otherwise :class:`int`. Default is to automatically
determine datatype.
Returns
-------
vector : (N,) :class:`numpy.ndarray`
Vector that is parsed from the :obj:`x` string
"""
# Always convert to float and then perform truncation to integer if necessary
number_list = np.array([float(x) for x in x.split()])
if dtype is None:
number_list_trunc = number_list.astype(int)
# If there is no difference between the truncated number list and the number list, then that means that the
# number list was all integers and we can just return that
if np.all((number_list - number_list_trunc) == 0):
number_list = number_list_trunc
elif dtype == int:
number_list = number_list.astype(int)
elif dtype != float:
raise NRRDError('dtype should be None for automatic type detection, float or int')
return number_list
def parse_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[npt.NDArray]:
"""Parse NRRD vector list from string into a :class:`list` of (N,) :class:`numpy.ndarray`.
Parses input string to convert it into a list of Numpy arrays using the NRRD vector list format.
See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more
information on the format.
Parameters
----------
x : :class:`str`
String containing NRRD vector list
dtype : data-type, optional
Datatype to use for the resulting Numpy arrays. Datatype can be :class:`float`, :class:`int` or :obj:`None`. If
:obj:`dtype` is :obj:`None`, it will be automatically determined by checking any of the vector elements
for fractional numbers. If found, the vectors will be converted to :class:`float`, otherwise :class:`int`.
Default is to automatically determine datatype.
Returns
-------
vector_list : :class:`list` of (N,) :class:`numpy.ndarray`
List of vectors that are parsed from the :obj:`x` string
"""
# Split input by spaces, convert each row into a vector
vector_list = [parse_vector(x, dtype=float) for x in x.split()]
# Get the size of each row vector and then remove duplicate sizes
# There should be exactly one value in the matrix because all row sizes need to be the same
if len(np.unique([len(x) for x in vector_list])) != 1:
raise NRRDError('Vector list should have same number of elements in each row')
# If using automatic datatype detection, then start by converting to float and determining if the number is whole
# Truncate to integer if dtype is int also
if dtype is None:
vector_list_trunc = [x.astype(int) for x in vector_list]
if np.all([np.array_equal(x, y) for x, y in zip(vector_list, vector_list_trunc)]):
vector_list = vector_list_trunc
elif dtype == int:
vector_list = [x.astype(int) for x in vector_list]
elif dtype != float:
raise NRRDError('dtype should be None for automatic type detection, float or int')
return vector_list
def parse_optional_vector_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> List[Optional[npt.NDArray]]:
"""Parse optional NRRD vector list from string into :class:`list` of (N,) :class:`numpy.ndarray` of :class:`float`.
Function parses optional NRRD vector list from string into a list of (N,) :class:`numpy.ndarray` or :obj:`None`.
This function works the same as :meth:`parse_vector_list` except if a row vector in the list is none, the resulting
row in the returned list will be :obj:`None`.
See :ref:`background/datatypes:int vector list` and :ref:`background/datatypes:double vector list` for more
information on the format.
Parameters
----------
x : :class:`str`
String containing NRRD vector list
Returns
-------
vector_list : :class:`list` of (N,) :class:`numpy.ndarray` or :obj:`None`
List of vectors that is parsed from the :obj:`x` string
"""
# Split input by spaces to get each row and convert into a vector. The row can be 'none', in which case it will
# return None
vector_list = [parse_optional_vector(x, dtype=float) for x in x.split()]
# Get the size of each row vector, 0 if None
sizes = np.array([0 if x is None else len(x) for x in vector_list])
# Get sizes of each row vector removing duplicate sizes
# Since each row vector should be same size, the unique sizes should return one value for the row size or it may
# return a second one (0) if there are None vectors
unique_sizes = np.unique(sizes)
if len(unique_sizes) != 1 and (len(unique_sizes) != 2 or unique_sizes.min() != 0):
raise NRRDError('Vector list should have same number of elements in each row')
# If using automatic datatype detection, then start by converting to float and determining if the number is whole
# Truncate to integer if dtype is int also
if dtype is None:
vector_list_trunc = [x.astype(int) if x is not None else None for x in vector_list]
if np.all([np.array_equal(x, y) for x, y in zip(vector_list, vector_list_trunc)]):
vector_list = vector_list_trunc
elif dtype == int:
vector_list = [x.astype(int) if x is not None else None for x in vector_list]
elif dtype != float:
raise NRRDError('dtype should be None for automatic type detection, float or int')
return vector_list
def parse_number_auto_dtype(x: str) -> Union[int, float]:
"""Parse number from string with automatic type detection.
Parses input string and converts to a number using automatic type detection. If the number contains any
fractional parts, then the number will be converted to float, otherwise the number will be converted to an int.
See :ref:`background/datatypes:int` and :ref:`background/datatypes:double` for more information on the format.
Parameters
----------
x : :class:`str`
String representation of number
Returns
-------
result : :class:`int` or :class:`float`
Number parsed from :obj:`x` string
"""
value: Union[int, float] = float(x)
if value.is_integer():
value = int(value)
return value