Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f67ed36

Browse files
Add type hints (#131)
1 parent 4a26224 commit f67ed36

8 files changed

Lines changed: 79 additions & 43 deletions

File tree

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ from numpy arrays.
3030

3131
Dependencies
3232
------------
33+
34+
* `Numpy <https://numpy.org/>`_
35+
* nptyping
36+
* typing_extensions
37+
3338
The module's only dependency is `numpy <http://numpy.scipy.org/>`_.
3439

3540
Installation

nrrd/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from nrrd.formatters import *
33
from nrrd.parsers import *
44
from nrrd.reader import read, read_data, read_header
5+
from nrrd.types import NRRDFieldMap, NRRDFieldType, NRRDHeader
56
from nrrd.writer import write
67

78
__all__ = ['read', 'read_data', 'read_header', 'write', 'format_number_list', 'format_number', 'format_matrix',
89
'format_optional_matrix', 'format_optional_vector', 'format_vector', 'parse_matrix',
910
'parse_number_auto_dtype', 'parse_number_list', 'parse_optional_matrix', 'parse_optional_vector',
10-
'parse_vector', '__version__']
11+
'parse_vector', 'NRRDFieldType', 'NRRDFieldMap', 'NRRDHeader', '__version__']

nrrd/formatters.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
from typing import Any, Optional, Union
2+
3+
import nptyping as npt
14
import numpy as np
5+
from typing_extensions import Literal
26

37

4-
def format_number(x):
8+
def format_number(x: Union[int, float]) -> str:
59
"""Format number to string
610
711
Function converts a number to string. For numbers of class :class:`float`, up to 17 digits will be used to print
@@ -38,7 +42,7 @@ def format_number(x):
3842
return value
3943

4044

41-
def format_vector(x):
45+
def format_vector(x: npt.NDArray[Literal['*'], Any]) -> str:
4246
"""Format a (N,) :class:`numpy.ndarray` into a NRRD vector string
4347
4448
See :ref:`user-guide:int vector` and :ref:`user-guide:double vector` for more information on the format.
@@ -57,7 +61,7 @@ def format_vector(x):
5761
return '(' + ','.join([format_number(y) for y in x]) + ')'
5862

5963

60-
def format_optional_vector(x):
64+
def format_optional_vector(x: Optional[npt.NDArray[Literal['*'], Any]]) -> str:
6165
"""Format a (N,) :class:`numpy.ndarray` into a NRRD optional vector string
6266
6367
Function converts a (N,) :class:`numpy.ndarray` or :obj:`None` into a string using NRRD vector format. If the input
@@ -84,7 +88,7 @@ def format_optional_vector(x):
8488
return format_vector(x)
8589

8690

87-
def format_matrix(x):
91+
def format_matrix(x: npt.NDArray[Literal['*, *'], Any]) -> str:
8892
"""Format a (M,N) :class:`numpy.ndarray` into a NRRD matrix string
8993
9094
See :ref:`user-guide:int matrix` and :ref:`user-guide:double matrix` for more information on the format.
@@ -103,7 +107,7 @@ def format_matrix(x):
103107
return ' '.join([format_vector(y) for y in x])
104108

105109

106-
def format_optional_matrix(x):
110+
def format_optional_matrix(x: Optional[npt.NDArray[Literal['*, *'], Any]]) -> str:
107111
"""Format a (M,N) :class:`numpy.ndarray` of :class:`float` into a NRRD optional matrix string
108112
109113
Function converts a (M,N) :class:`numpy.ndarray` of :class:`float` into a string using the NRRD matrix format. For
@@ -129,7 +133,7 @@ def format_optional_matrix(x):
129133
return ' '.join([format_optional_vector(y) for y in x])
130134

131135

132-
def format_number_list(x):
136+
def format_number_list(x: npt.NDArray[Literal['*'], Any]) -> str:
133137
"""Format a (N,) :class:`numpy.ndarray` into a NRRD number list.
134138
135139
See :ref:`user-guide:int list` and :ref:`user-guide:double list` for more information on the format.

nrrd/parsers.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from typing import Any, Optional, Type, Union
2+
3+
import nptyping as npt
14
import numpy as np
5+
from typing_extensions import Literal
26

37
from nrrd.errors import NRRDError
48

59

6-
def parse_vector(x, dtype=None):
10+
def parse_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*'], Any]:
711
"""Parse NRRD vector from string into (N,) :class:`numpy.ndarray`.
812
913
See :ref:`user-guide:int vector` and :ref:`user-guide:double vector` for more information on the format.
@@ -46,7 +50,8 @@ def parse_vector(x, dtype=None):
4650
return vector
4751

4852

49-
def parse_optional_vector(x, dtype=None):
53+
def parse_optional_vector(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> \
54+
Optional[npt.NDArray[Literal['*'], Any]]:
5055
"""Parse optional NRRD vector from string into (N,) :class:`numpy.ndarray` or :obj:`None`.
5156
5257
Function parses optional NRRD vector from string into an (N,) :class:`numpy.ndarray`. This function works the same
@@ -76,7 +81,7 @@ def parse_optional_vector(x, dtype=None):
7681
return parse_vector(x, dtype)
7782

7883

79-
def parse_matrix(x, dtype=None):
84+
def parse_matrix(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*, *'], Any]:
8085
"""Parse NRRD matrix from string into (M,N) :class:`numpy.ndarray`.
8186
8287
See :ref:`user-guide:int matrix` and :ref:`user-guide:double matrix` for more information on the format.
@@ -122,7 +127,7 @@ def parse_matrix(x, dtype=None):
122127
return matrix
123128

124129

125-
def parse_optional_matrix(x):
130+
def parse_optional_matrix(x: str) -> Optional[npt.NDArray[Literal['*, *'], Any]]:
126131
"""Parse optional NRRD matrix from string into (M,N) :class:`numpy.ndarray` of :class:`float`.
127132
128133
Function parses optional NRRD matrix from string into an (M,N) :class:`numpy.ndarray` of :class:`float`. This
@@ -165,7 +170,7 @@ def parse_optional_matrix(x):
165170
return matrix
166171

167172

168-
def parse_number_list(x, dtype=None):
173+
def parse_number_list(x: str, dtype: Optional[Type[Union[int, float]]] = None) -> npt.NDArray[Literal['*'], Any]:
169174
"""Parse NRRD number list from string into (N,) :class:`numpy.ndarray`.
170175
171176
See :ref:`user-guide:int list` and :ref:`user-guide:double list` for more information on the format.
@@ -204,7 +209,7 @@ def parse_number_list(x, dtype=None):
204209
return number_list
205210

206211

207-
def parse_number_auto_dtype(x):
212+
def parse_number_auto_dtype(x: str) -> Union[int, float]:
208213
"""Parse number from string with automatic type detection.
209214
210215
Parses input string and converts to a number using automatic type detection. If the number contains any
@@ -223,7 +228,7 @@ def parse_number_auto_dtype(x):
223228
Number parsed from :obj:`x` string
224229
"""
225230

226-
value = float(x)
231+
value: Union[int, float] = float(x)
227232

228233
if value.is_integer():
229234
value = int(value)

nrrd/reader.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
import warnings
77
import zlib
88
from collections import OrderedDict
9+
from typing import IO, AnyStr, Iterable, Tuple
910

1011
from nrrd.parsers import *
12+
from nrrd.types import IndexOrder, NRRDFieldMap, NRRDFieldType, NRRDHeader
1113

1214
# Older versions of Python had issues when uncompressed data was larger than 4GB (2^32). This should be fixed in latest
1315
# version of Python 2.7 and all versions of Python 3. The fix for this issue is to read the data in smaller chunks.
1416
# Chunk size is set to be large at 1GB to improve performance. If issues arise decompressing larger files, try to reduce
1517
# this value
16-
_READ_CHUNKSIZE = 2 ** 32
18+
_READ_CHUNKSIZE: int = 2 ** 32
1719

1820
_NRRD_REQUIRED_FIELDS = ['dimension', 'type', 'encoding', 'sizes']
1921

@@ -84,7 +86,7 @@
8486
}
8587

8688

87-
def _get_field_type(field, custom_field_map):
89+
def _get_field_type(field: str, custom_field_map: Optional[NRRDFieldMap]) -> NRRDFieldType:
8890
if field in ['dimension', 'lineskip', 'line skip', 'byteskip', 'byte skip', 'space dimension']:
8991
return 'int'
9092
elif field in ['min', 'max', 'oldmin', 'old min', 'oldmax', 'old max']:
@@ -99,7 +101,7 @@ def _get_field_type(field, custom_field_map):
99101
return 'string list'
100102
elif field in ['labels', 'units', 'space units']:
101103
return 'quoted string list'
102-
# No int vector fields as of now
104+
# No int vector fields yet
103105
# elif field in []:
104106
# return 'int vector'
105107
elif field in ['space origin']:
@@ -116,7 +118,7 @@ def _get_field_type(field, custom_field_map):
116118
return 'string'
117119

118120

119-
def _parse_field_value(value, field_type):
121+
def _parse_field_value(value: str, field_type: NRRDFieldType) -> Any:
120122
if field_type == 'int':
121123
return int(value)
122124
elif field_type == 'double':
@@ -146,28 +148,28 @@ def _parse_field_value(value, field_type):
146148
raise NRRDError(f'Invalid field type given: {field_type}')
147149

148150

149-
def _determine_datatype(fields):
151+
def _determine_datatype(header: NRRDHeader) -> np.dtype:
150152
"""Determine the numpy dtype of the data."""
151153

152154
# Convert the NRRD type string identifier into a NumPy string identifier using a map
153-
np_typestring = _TYPEMAP_NRRD2NUMPY[fields['type']]
155+
np_typestring = _TYPEMAP_NRRD2NUMPY[header['type']]
154156

155157
# This is only added if the datatype has more than one byte and is not using ASCII encoding
156158
# Note: Endian is not required for ASCII encoding
157-
if np.dtype(np_typestring).itemsize > 1 and fields['encoding'] not in ['ASCII', 'ascii', 'text', 'txt']:
158-
if 'endian' not in fields:
159+
if np.dtype(np_typestring).itemsize > 1 and header['encoding'] not in ['ASCII', 'ascii', 'text', 'txt']:
160+
if 'endian' not in header:
159161
raise NRRDError('Header is missing required field: endian')
160-
elif fields['endian'] == 'big':
162+
elif header['endian'] == 'big':
161163
np_typestring = '>' + np_typestring
162-
elif fields['endian'] == 'little':
164+
elif header['endian'] == 'little':
163165
np_typestring = '<' + np_typestring
164166
else:
165-
raise NRRDError(f'Invalid endian value in header: {fields["endian"]}')
167+
raise NRRDError(f'Invalid endian value in header: {header["endian"]}')
166168

167169
return np.dtype(np_typestring)
168170

169171

170-
def _validate_magic_line(line):
172+
def _validate_magic_line(line: str) -> int:
171173
"""For NRRD files, the first four characters are always "NRRD", and
172174
remaining characters give information about the file format version
173175
@@ -197,7 +199,7 @@ def _validate_magic_line(line):
197199
return len(line)
198200

199201

200-
def read_header(file, custom_field_map=None):
202+
def read_header(file: Union[str, Iterable[AnyStr]], custom_field_map: Optional[NRRDFieldMap] = None) -> NRRDHeader:
201203
"""Read contents of header and parse values from :obj:`file`
202204
203205
:obj:`file` can be a filename indicating where the NRRD header is located or a string iterator object. If a
@@ -284,7 +286,7 @@ def read_header(file, custom_field_map=None):
284286
else:
285287
warnings.warn(f'Duplicate header field: {field}')
286288

287-
# Get the datatype of the field based on it's field name and custom field map
289+
# Get the datatype of the field based on its field name and custom field map
288290
field_type = _get_field_type(field, custom_field_map)
289291

290292
# Parse the field value using the datatype retrieved
@@ -299,7 +301,8 @@ def read_header(file, custom_field_map=None):
299301
return header
300302

301303

302-
def read_data(header, fh=None, filename=None, index_order='F'):
304+
def read_data(header: NRRDHeader, fh: Optional[IO] = None, filename: Optional[str] = None,
305+
index_order: IndexOrder = 'F') -> npt.NDArray:
303306
"""Read data from file into :class:`numpy.ndarray`
304307
305308
The two parameters :obj:`fh` and :obj:`filename` are optional depending on the parameters but it never hurts to
@@ -427,7 +430,7 @@ def read_data(header, fh=None, filename=None, index_order='F'):
427430
# Loop through the file and read a chunk at a time (see _READ_CHUNKSIZE why it is read in chunks)
428431
decompressed_data = bytearray()
429432

430-
# Read all of the remaining data from the file
433+
# Read all the remaining data from the file
431434
# Obtain the length of the compressed data since we will be using it repeatedly, more efficient
432435
compressed_data = fh.read()
433436
compressed_data_len = len(compressed_data)
@@ -474,7 +477,8 @@ def read_data(header, fh=None, filename=None, index_order='F'):
474477
return data
475478

476479

477-
def read(filename, custom_field_map=None, index_order='F'):
480+
def read(filename: str, custom_field_map: Optional[NRRDFieldMap] = None, index_order: IndexOrder = 'F') \
481+
-> Tuple[npt.NDArray, NRRDHeader]:
478482
"""Read a NRRD file and return the header and data
479483
480484
See :ref:`user-guide:Reading NRRD files` for more information on reading NRRD files.

nrrd/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from typing import Any, Dict
2+
3+
from typing_extensions import Literal
4+
5+
NRRDFieldType = Literal['int', 'double', 'string', 'int list', 'double list', 'string list', 'quoted string list',
6+
'int vector', 'double vector', 'int matrix', 'double matrix']
7+
8+
IndexOrder = Literal['F', 'C']
9+
10+
NRRDFieldMap = Dict[str, NRRDFieldType]
11+
NRRDHeader = Dict[str, Any]

nrrd/writer.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
import zlib
55
from collections import OrderedDict
66
from datetime import datetime
7+
from typing import IO, Dict
8+
9+
import nptyping as npt
710

811
from nrrd.errors import NRRDError
912
from nrrd.formatters import *
1013
from nrrd.reader import _get_field_type
14+
from nrrd.types import IndexOrder, NRRDFieldMap, NRRDFieldType, NRRDHeader
1115

1216
# Older versions of Python had issues when uncompressed data was larger than 4GB (2^32). This should be fixed in latest
1317
# version of Python 2.7 and all versions of Python 3. The fix for this issue is to read the data in smaller chunks. The
1418
# chunk size is set to be small here at 1MB since performance did not vary much based on the chunk size. A smaller chunk
1519
# size has the benefit of using less RAM at once.
16-
_WRITE_CHUNKSIZE = 2 ** 20
20+
_WRITE_CHUNKSIZE: int = 2 ** 20
1721

1822
_NRRD_FIELD_ORDER = [
1923
'type',
@@ -45,7 +49,8 @@
4549
'space units',
4650
'space origin',
4751
'measurement frame',
48-
'data file']
52+
'data file'
53+
]
4954

5055
_TYPEMAP_NUMPY2NRRD = {
5156
'i1': 'int8',
@@ -69,7 +74,7 @@
6974
}
7075

7176

72-
def _format_field_value(value, field_type):
77+
def _format_field_value(value: Any, field_type: NRRDFieldType) -> str:
7378
if field_type == 'int':
7479
return format_number(value)
7580
elif field_type == 'double':
@@ -96,7 +101,7 @@ def _format_field_value(value, field_type):
96101
raise NRRDError(f'Invalid field type given: {field_type}')
97102

98103

99-
def _handle_header(data, header=None, index_order='F'):
104+
def _handle_header(data: npt.NDArray, header: Optional[NRRDHeader] = None, index_order: IndexOrder = 'F') -> NRRDHeader:
100105
if header is None:
101106
header = {}
102107

@@ -133,7 +138,7 @@ def _handle_header(data, header=None, index_order='F'):
133138
return header
134139

135140

136-
def _write_header(file, header, custom_field_map=None):
141+
def _write_header(file: IO, header: Dict[str, Any], custom_field_map: Optional[NRRDFieldMap] = None):
137142
file.write(b'NRRD0005\n')
138143
file.write(b'# This NRRD file was generated by pynrrd\n')
139144
file.write(b'# on ' + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S').encode('ascii') + b'(GMT).\n')
@@ -177,7 +182,8 @@ def _write_header(file, header, custom_field_map=None):
177182
file.write(b'\n')
178183

179184

180-
def _write_data(data, fh, header, compression_level=None, index_order='F'):
185+
def _write_data(data: npt.NDArray, fh: IO, header: NRRDHeader, compression_level: Optional[int] = None,
186+
index_order: IndexOrder = 'F'):
181187
if index_order not in ['F', 'C']:
182188
raise NRRDError('Invalid index order')
183189

@@ -243,8 +249,9 @@ def _write_data(data, fh, header, compression_level=None, index_order='F'):
243249
fh.flush()
244250

245251

246-
def write(file, data, header=None, detached_header=False, relative_data_path=True, custom_field_map=None,
247-
compression_level=9, index_order='F'):
252+
def write(file: Union[str, IO], data: npt.NDArray, header: Optional[NRRDHeader] = None,
253+
detached_header: bool = False, relative_data_path: bool = True,
254+
custom_field_map: Optional[NRRDFieldMap] = None, compression_level: int = 9, index_order: IndexOrder = 'F'):
248255
"""Write :class:`numpy.ndarray` to NRRD file
249256
250257
The :obj:`file` parameter specifies the absolute or relative filename to write the NRRD file to or an
@@ -346,14 +353,12 @@ def write(file, data, header=None, detached_header=False, relative_data_path=Tru
346353
# Update the data file field in the header with the path of the detached data
347354
# TODO This will cause problems when the user specifies a relative data path and gives a custom path OUTSIDE
348355
# of the current directory.
349-
header['data file'] = os.path.basename(data_filename) \
350-
if relative_data_path else os.path.abspath(data_filename)
356+
header['data file'] = os.path.basename(data_filename) if relative_data_path else os.path.abspath(data_filename)
351357
detached_header = True
352358
elif file.endswith('.nrrd') and detached_header:
353359
data_filename = file
354360
file = f'{os.path.splitext(file)[0]}.nhdr'
355-
header['data file'] = os.path.basename(data_filename) \
356-
if relative_data_path else os.path.abspath(data_filename)
361+
header['data file'] = os.path.basename(data_filename) if relative_data_path else os.path.abspath(data_filename)
357362
else:
358363
# Write header & data as one file
359364
data_filename = file

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
numpy>=1.11.1
2+
nptyping
23
typing_extensions

0 commit comments

Comments
 (0)