PKÖ€”Gº¿(5ÍKÍKnrrd.py#!/usr/bin/env python # encoding: utf-8 """ nrrd.py An all-python (and numpy) implementation for reading and writing nrrd files. See http://teem.sourceforge.net/nrrd/format.html for the specification. Copyright (c) 2011-2015 Maarten Everts and others. See LICENSE and AUTHORS. """ import numpy as np import zlib import bz2 import mmap import os from datetime import datetime # Reading and writing gzipped data directly gives problems when the uncompressed # data is larger than 4GB (2^32). Therefore we'll read and write the data in # chunks. How this affects speed and/or memory usage is something to be analyzed # further. The following two values define the size of the chunks. _READ_CHUNKSIZE = 2**20 _WRITE_CHUNKSIZE = 2**20 class NrrdError(Exception): """Exceptions for Nrrd class.""" pass #This will help prevent loss of precision #IEEE754-1985 standard says that 17 decimal digits is enough in all cases. def _convert_to_reproducible_floatingpoint( x ): if type(x) == float: value = '{:.16f}'.format(x).rstrip('0').rstrip('.') # Remove trailing zeros, and dot if at end else: value = str(x) return value _TYPEMAP_NRRD2NUMPY = { 'signed char': 'i1', 'int8': 'i1', 'int8_t': 'i1', 'uchar': 'u1', 'unsigned char': 'u1', 'uint8': 'u1', 'uint8_t': 'u1', 'short': 'i2', 'short int': 'i2', 'signed short': 'i2', 'signed short int': 'i2', 'int16': 'i2', 'int16_t': 'i2', 'ushort': 'u2', 'unsigned short': 'u2', 'unsigned short int': 'u2', 'uint16': 'u2', 'uint16_t': 'u2', 'int': 'i4', 'signed int': 'i4', 'int32': 'i4', 'int32_t': 'i4', 'uint': 'u4', 'unsigned int': 'u4', 'uint32': 'u4', 'uint32_t': 'u4', 'longlong': 'i8', 'long long': 'i8', 'long long int': 'i8', 'signed long long': 'i8', 'signed long long int': 'i8', 'int64': 'i8', 'int64_t': 'i8', 'ulonglong': 'u8', 'unsigned long long': 'u8', 'unsigned long long int': 'u8', 'uint64': 'u8', 'uint64_t': 'u8', 'float': 'f4', 'double': 'f8', 'block': 'V' } _TYPEMAP_NUMPY2NRRD = { 'i1': 'int8', 'u1': 'uint8', 'i2': 'int16', 'u2': 'uint16', 'i4': 'int32', 'u4': 'uint32', 'i8': 'int64', 'u8': 'uint64', 'f4': 'float', 'f8': 'double', 'V': 'block' } _NUMPY2NRRD_ENDIAN_MAP = { '<': 'little', 'L': 'little', '>': 'big', 'B': 'big' } def parse_nrrdvector(inp): """Parse a vector from a nrrd header, return a list.""" assert inp[0] == '(', "Vector should be enclosed by parenthesis." assert inp[-1] == ')', "Vector should be enclosed by parenthesis." return [_convert_to_reproducible_floatingpoint(x) for x in inp[1:-1].split(',')] def parse_optional_nrrdvector(inp): """Parse a vector from a nrrd header that can also be none.""" if (inp == "none"): return inp else: return parse_nrrdvector(inp) _NRRD_FIELD_PARSERS = { 'dimension': int, 'type': str, 'sizes': lambda fieldValue: [int(x) for x in fieldValue.split()], 'endian': str, 'encoding': str, 'min': float, 'max': float, 'oldmin': float, 'old min': float, 'oldmax': float, 'old max': float, 'lineskip': int, 'line skip': int, 'byteskip': int, 'byte skip': int, 'content': str, 'sample units': str, 'datafile': str, 'data file': str, 'spacings': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'thicknesses': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'axis mins': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'axismins': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'axis maxs': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'axismaxs': lambda fieldValue: [_convert_to_reproducible_floatingpoint(x) for x in fieldValue.split()], 'centerings': lambda fieldValue: [str(x) for x in fieldValue.split()], 'labels': lambda fieldValue: [str(x) for x in fieldValue.split()], 'units': lambda fieldValue: [str(x) for x in fieldValue.split()], 'kinds': lambda fieldValue: [str(x) for x in fieldValue.split()], 'space': str, 'space dimension': int, 'space units': lambda fieldValue: [str(x) for x in fieldValue.split()], 'space origin': parse_nrrdvector, 'space directions': lambda fieldValue: [parse_optional_nrrdvector(x) for x in fieldValue.split()], 'measurement frame': lambda fieldValue: [parse_nrrdvector(x) for x in fieldValue.split()], } _NRRD_REQUIRED_FIELDS = ['dimension', 'type', 'encoding', 'sizes'] # The supported field values _NRRD_FIELD_ORDER = [ 'type', 'dimension', 'space dimension', 'space', 'sizes', 'space directions', 'kinds', 'endian', 'encoding', 'min', 'max', 'oldmin', 'old min', 'oldmax', 'old max', 'content', 'sample units', 'spacings', 'thicknesses', 'axis mins', 'axismins', 'axis maxs', 'axismaxs', 'centerings', 'labels', 'units', 'space units', 'space origin', 'measurement frame', 'data file'] def _determine_dtype(fields): """Determine the numpy dtype of the data.""" # Check whether the required fields are there for field in _NRRD_REQUIRED_FIELDS: if field not in fields: raise NrrdError('Nrrd header misses required field: "%s".' % (field)) # Process the data type np_typestring = _TYPEMAP_NRRD2NUMPY[fields['type']] if np.dtype(np_typestring).itemsize > 1: if 'endian' not in fields: raise NrrdError('Nrrd header misses required field: "endian".') if fields['endian'] == 'big': np_typestring = '>' + np_typestring elif fields['endian'] == 'little': np_typestring = '<' + np_typestring return np.dtype(np_typestring) def read_data(fields, filehandle, filename=None, seek_past_header=True): """Read the NRRD data from a file object into a numpy structure. If seek_past_header is True, the '\n\n' header-data separator will be found, otherwise it is assumed that the current fpos of the filehandle object is pointing to the first byte after the '\n\n' line. seek_past_headeronly only applies to attached headers. """ data = np.zeros(0) # Determine the data type from the fields dtype = _determine_dtype(fields) # determine byte skip, line skip, and data file (there are two ways to write them) lineskip = fields.get('lineskip', fields.get('line skip', 0)) byteskip = fields.get('byteskip', fields.get('byte skip', 0)) datafile = fields.get('datafile', fields.get('data file', None)) datafilehandle = filehandle if datafile is not None: # If the datafile path is absolute, don't muck with it. Otherwise # treat the path as relative to the directory in which the detached # header is in if os.path.isabs(datafile): datafilename = datafile else: datafilename = os.path.join(os.path.dirname(filename), datafile) datafilehandle = open(datafilename,'rb') elif seek_past_header: # Efficiently find the header-data seperator line no matter how big # any header line is datafilehandle.seek(0) if os.name == "nt": m = mmap.mmap(datafilehandle.fileno(), 0, access=mmap.ACCESS_READ) else: m = mmap.mmap(datafilehandle.fileno(), 0, mmap.MAP_PRIVATE, mmap.PROT_READ) seek_past_header_pos = m.find(b'\n\n') if seek_past_header_pos == -1: raise NrrdError('Invalid NRRD: Missing header-data separator line') datafilehandle.seek(seek_past_header_pos + 2) numPixels = np.array(fields['sizes']).prod() # Seek to start of data based on lineskip/byteskip. byteskip == -1 is # only valid for raw encoding and overrides any lineskip if fields['encoding'] == 'raw' and byteskip == -1: datafilehandle.seek(-dtype.itemsize * numPixels, 2) else: for _ in range(lineskip): datafilehandle.readline() if fields['encoding'] == 'raw': datafilehandle.seek(byteskip, os.SEEK_CUR) data = np.fromfile(datafilehandle, dtype) else: # Probably the data is compressed then if fields['encoding'] == 'gzip' or\ fields['encoding'] == 'gz': decompobj = zlib.decompressobj(zlib.MAX_WBITS | 16) elif fields['encoding'] == 'bzip2' or\ fields['encoding'] == 'bz2': decompobj = bz2.BZ2Decompressor() else: raise NrrdError('Unsupported encoding: "%s"' % fields['encoding']) decompressed_data = b'' while True: chunk = datafilehandle.read(_READ_CHUNKSIZE) if not chunk: break decompressed_data += decompobj.decompress(chunk) # byteskip applies to the _decompressed_ byte stream data = np.fromstring(decompressed_data[byteskip:], dtype) if datafilehandle: datafilehandle.close() if numPixels != data.size: raise NrrdError('ERROR: {0}-{1}={2}'.format(numPixels,data.size,numPixels-data.size)) # dkh : eliminated need to reverse order of dimensions. nrrd's # data layout is same as what numpy calls 'Fortran' order, shape_tmp = list(fields['sizes']) data = np.reshape(data, tuple(shape_tmp), order='F') return data def _validate_magic_line(line): """For NRRD files, the first four characters are always "NRRD", and remaining characters give information about the file format version >>> _validate_magic_line('NRRD0005') >>> _validate_magic_line('NRRD0006') Traceback (most recent call last): ... NrrdError: NRRD file version too new for this library. >>> _validate_magic_line('NRRD') Traceback (most recent call last): ... NrrdError: Invalid NRRD magic line: NRRD """ if not line.startswith('NRRD'): raise NrrdError('Missing magic "NRRD" word. Is this an NRRD file?') try: if int(line[4:]) > 5: raise NrrdError('NRRD file version too new for this library.') except ValueError: raise NrrdError('Invalid NRRD magic line: %s' % (line,)) return len(line) def read_header(nrrdfile): """Parse the fields in the nrrd header nrrdfile can be any object which supports the iterator protocol and returns a string each time its next() method is called — file objects and list objects are both suitable. If nrrdfile is a file object, it must be opened with the ‘b’ flag on platforms where that makes a difference (e.g. Windows) >>> read_header(("NRRD0005", "type: float", "dimension: 3")) {u'type': 'float', u'dimension': 3, u'keyvaluepairs': {}} >>> read_header(("NRRD0005", "my extra info:=my : colon-separated : values")) {u'keyvaluepairs': {'my extra info': 'my : colon-separated : values'}} """ # Collect number of bytes in the file header (for seeking below) headerSize = 0 it = iter(nrrdfile) headerSize += _validate_magic_line(next(it).decode('ascii')) header = { u'keyvaluepairs': {} } for raw_line in it: headerSize += len(raw_line) raw_line = raw_line.decode('ascii') # Trailing whitespace ignored per the NRRD spec line = raw_line.rstrip() # Comments start with '#', no leading whitespace allowed if line.startswith('#'): continue # Single blank line separates the header from the data if line == '': break # Handle the := lines first since may contain a # ': ' which messes up the : parsing key_value = line.split(':=', 1) if len(key_value) is 2: key, value = key_value # TODO: escape \\ and \n ?? # value.replace(r'\\\\', r'\\').replace(r'\n', '\n') header[u'keyvaluepairs'][key] = value continue # Handle the ": " lines. field_desc = line.split(': ', 1) if len(field_desc) is 2: field, desc = field_desc ## preceeding and suffixing white space should be ignored. field = field.rstrip().lstrip() desc = desc.rstrip().lstrip() if field not in _NRRD_FIELD_PARSERS: raise NrrdError('Unexpected field in nrrd header: %s' % repr(field)) if field in header.keys(): raise NrrdError('Duplicate header field: %s' % repr(field)) header[field] = _NRRD_FIELD_PARSERS[field](desc) continue # Should not reach here raise NrrdError('Invalid header line: %s' % repr(line)) # line reading was buffered; correct file pointer to just behind header: nrrdfile.seek(headerSize) return header def read(filename): """Read a nrrd file and return a tuple (data, header).""" with open(filename,'rb') as filehandle: header = read_header(filehandle) data = read_data(header, filehandle, filename) return (data, header) def _format_nrrd_list(fieldValue) : return ' '.join([_convert_to_reproducible_floatingpoint(x) for x in fieldValue]) def _format_nrrdvector(v) : return '(' + ','.join([_convert_to_reproducible_floatingpoint(x) for x in v]) + ')' def _format_optional_nrrdvector(v): if (v == 'none') : return 'none' else : return _format_nrrdvector(v) _NRRD_FIELD_FORMATTERS = { 'dimension': str, 'type': str, 'sizes': _format_nrrd_list, 'endian': str, 'encoding': str, 'min': str, 'max': str, 'oldmin': str, 'old min': str, 'oldmax': str, 'old max': str, 'lineskip': str, 'line skip': str, 'byteskip': str, 'byte skip': str, 'content': str, 'sample units': str, 'datafile': str, 'data file': str, 'spacings': _format_nrrd_list, 'thicknesses': _format_nrrd_list, 'axis mins': _format_nrrd_list, 'axismins': _format_nrrd_list, 'axis maxs': _format_nrrd_list, 'axismaxs': _format_nrrd_list, 'centerings': _format_nrrd_list, 'labels': _format_nrrd_list, 'units': _format_nrrd_list, 'kinds': _format_nrrd_list, 'space': str, 'space dimension': str, 'space units': _format_nrrd_list, 'space origin': _format_nrrdvector, 'space directions': lambda fieldValue: ' '.join([_format_optional_nrrdvector(x) for x in fieldValue]), 'measurement frame': lambda fieldValue: ' '.join([_format_optional_nrrdvector(x) for x in fieldValue]), } def _write_data(data, filehandle, options): # Now write data directly rawdata = data.tostring(order = 'F') if options['encoding'] == 'raw': filehandle.write(rawdata) else: if options['encoding'] == 'gzip': comp_obj = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) elif options['encoding'] == 'bzip2': comp_obj = bz2.BZ2Compressor() else: raise NrrdError('Unsupported encoding: "%s"' % options['encoding']) # write data in chunks start_index = 0 while start_index < len(rawdata): end_index = start_index + _WRITE_CHUNKSIZE if end_index > len(rawdata): end_index = len(rawdata) filehandle.write(comp_obj.compress(rawdata[start_index:end_index])) start_index = end_index filehandle.write(comp_obj.flush()) filehandle.flush() def write(filename, data, options={}, detached_header=False): """Write the numpy data to a nrrd file. The nrrd header values to use are inferred from from the data. Additional options can be passed in the options dictionary. See the read() function for the structure of this dictionary. To set data samplings, use e.g. `options['spacings'] = [s1, s2, s3]` for 3d data with sampling deltas `s1`, `s2`, and `s3` in each dimension. """ # Infer a number of fields from the ndarray and ignore values # in the options dictionary. options['type'] = _TYPEMAP_NUMPY2NRRD[data.dtype.str[1:]] if data.dtype.itemsize > 1: options['endian'] = _NUMPY2NRRD_ENDIAN_MAP[data.dtype.str[:1]] # if 'space' is specified 'space dimension' can not. See # http://teem.sourceforge.net/nrrd/format.html#space if 'space' in options.keys() and 'space dimension' in options.keys(): del options['space dimension'] options['dimension'] = data.ndim options['sizes'] = list(data.shape) # The default encoding is 'gzip' if 'encoding' not in options: options['encoding'] = 'gzip' # A bit of magic in handling options here. # If *.nhdr filename provided, this overrides `detached_header=False` # If *.nrrd filename provided AND detached_header=True, separate header # and data files written. # For all other cases, header & data written to same file. if filename[-5:] == '.nhdr': detached_header = True if 'data file' not in options: datafilename = filename[:-4] + str('raw') if options['encoding'] == 'gzip': datafilename += '.gz' options['data file'] = datafilename else: datafilename = options['data file'] elif filename[-5:] == '.nrrd' and detached_header: datafilename = filename filename = filename[:-4] + str('nhdr') else: # Write header & data as one file datafilename = filename with open(filename,'wb') as filehandle: filehandle.write(b'NRRD0005\n') filehandle.write(b'# This NRRD file was generated by pynrrd\n') filehandle.write(b'# on ' + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S').encode('ascii') + b'(GMT).\n') filehandle.write(b'# Complete NRRD file format specification at:\n'); filehandle.write(b'# http://teem.sourceforge.net/nrrd/format.html\n'); # Write the fields in order, this ignores fields not in # _NRRD_FIELD_ORDER for field in _NRRD_FIELD_ORDER: if field in options: outline = (field + ': ' + _NRRD_FIELD_FORMATTERS[field](options[field]) + '\n').encode('ascii') filehandle.write(outline) d = options.get('keyvaluepairs', {}) for (k,v) in sorted(d.items(), key=lambda t: t[0]): outline = (str(k) + ':=' + str(v) + '\n').encode('ascii') filehandle.write(outline) # Write the closing extra newline filehandle.write(b'\n') # If a single file desired, write data if not detached_header: _write_data(data, filehandle, options) # If detached header desired, write data to different file if detached_header: with open(datafilename, 'wb') as datafilehandle: _write_data(data, datafilehandle, options) if __name__ == "__main__": import doctest doctest.testmod() PKl‚#HA$ëècc&pynrrd-0.2.1.dist-info/DESCRIPTION.rstPure python module for reading and writing nrrd files. See the github page for more information. PKl‚#H¾ Q¡¡$pynrrd-0.2.1.dist-info/metadata.json{"classifiers": ["License :: OSI Approved :: MIT License", "Topic :: Scientific/Engineering", "Programming Language :: Python", "Programming Language :: Python :: 3"], "extensions": {"python.details": {"contacts": [{"email": "me@nn8.nl", "name": "Maarten Everts", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/mhe/pynrrd"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["nrrd"], "license": "MIT License", "metadata_version": "2.0", "name": "pynrrd", "run_requires": [{"requires": ["numpy"]}], "summary": "Pure python module for reading and writing nrrd files.", "version": "0.2.1"}PKl‚#H¥­'—$pynrrd-0.2.1.dist-info/top_level.txtnrrd PKl‚#Hìndªnnpynrrd-0.2.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKl‚#HÌoîõ22pynrrd-0.2.1.dist-info/METADATAMetadata-Version: 2.0 Name: pynrrd Version: 0.2.1 Summary: Pure python module for reading and writing nrrd files. Home-page: http://github.com/mhe/pynrrd Author: Maarten Everts Author-email: me@nn8.nl License: MIT License Keywords: nrrd Platform: UNKNOWN Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Scientific/Engineering Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Requires-Dist: numpy Pure python module for reading and writing nrrd files. See the github page for more information. PKl‚#H–MŒÊ&&pynrrd-0.2.1.dist-info/RECORDnrrd.py,sha256=GHfm39WueWzVb3CNnWn2xxUHLUExkmxE3MiaaTXwKwM,19405 pynrrd-0.2.1.dist-info/DESCRIPTION.rst,sha256=AKsFRjb6eX0e-BJh8KGWV8E-Z0SZyKDuuSMycQvZ-Cc,99 pynrrd-0.2.1.dist-info/METADATA,sha256=6VOyA5opQp__ick4L9hgeG3a3a3zJ9w0bydXnuIrBh8,562 pynrrd-0.2.1.dist-info/RECORD,, pynrrd-0.2.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 pynrrd-0.2.1.dist-info/metadata.json,sha256=PrclVClA3LFmPxE8JunHsgFRusjAfitTtdXD5jk-vmQ,673 pynrrd-0.2.1.dist-info/top_level.txt,sha256=Ha1Zo_wlEiK8eV-VPFC4Dv0GqCTnMEwOBppv_5WuiXg,5 PKÖ€”Gº¿(5ÍKÍKnrrd.pyPKl‚#HA$ëècc&òKpynrrd-0.2.1.dist-info/DESCRIPTION.rstPKl‚#H¾ Q¡¡$™Lpynrrd-0.2.1.dist-info/metadata.jsonPKl‚#H¥­'—$|Opynrrd-0.2.1.dist-info/top_level.txtPKl‚#HìndªnnÃOpynrrd-0.2.1.dist-info/WHEELPKl‚#HÌoîõ22kPpynrrd-0.2.1.dist-info/METADATAPKl‚#H–MŒÊ&&ÚRpynrrd-0.2.1.dist-info/RECORDPK;U