Skip to content

Commit 80c725c

Browse files
committed
Merge pull request #52 from matthew-brett/netcdf-updates
BF - update netcdf from scipy trunk
2 parents bfac136 + 38b05d2 commit 80c725c

File tree

1 file changed

+67
-38
lines changed

1 file changed

+67
-38
lines changed

nibabel/externals/netcdf.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
modules, allowing these modules to be used interchangeably when working
4848
with NetCDF files. The major advantage of this module over other
4949
modules is that it doesn't require the code to be linked to the NetCDF
50-
libraries as the other modules do.
50+
C libraries.
5151
5252
The code is based on the `NetCDF file format specification
5353
<http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html>`_. A
@@ -91,7 +91,7 @@
9191
9292
Examples
9393
--------
94-
To create a NetCDF file::
94+
To create a NetCDF file:
9595
9696
Make a temporary file for testing:
9797
@@ -133,14 +133,16 @@
133133
>>> del f, time # needed for windows unlink
134134
>>> os.unlink(fname)
135135
>>> os.rmdir(tmp_pth)
136-
137-
TODO:
138-
* properly implement ``_FillValue``.
139-
* implement Jeff Whitaker's patch for masked variables.
140-
* fix character variables.
141-
* implement PAGESIZE for Python 2.6?
142136
"""
143137

138+
139+
140+
#TODO:
141+
# * properly implement ``_FillValue``.
142+
# * implement Jeff Whitaker's patch for masked variables.
143+
# * fix character variables.
144+
# * implement PAGESIZE for Python 2.6?
145+
144146
#The Scientific.IO.NetCDF API allows attributes to be added directly to
145147
#instances of ``netcdf_file`` and ``netcdf_variable``. To differentiate
146148
#between user-set attributes and instance attributes, user-set attributes
@@ -149,7 +151,8 @@
149151
#``obj.__dict__['key'] = value``, instead of simply ``obj.key = value``;
150152
#otherwise the key would be inserted into userspace attributes.
151153

152-
__all__ = ['netcdf_file', 'netcdf_variable']
154+
155+
__all__ = ['netcdf_file']
153156

154157

155158
from operator import mul
@@ -181,17 +184,18 @@
181184
NC_FLOAT: ('f', 4),
182185
NC_DOUBLE: ('d', 8) }
183186

184-
REVERSE = { 'b': NC_BYTE,
185-
'c': NC_CHAR,
186-
'h': NC_SHORT,
187-
'i': NC_INT,
188-
'f': NC_FLOAT,
189-
'd': NC_DOUBLE,
187+
REVERSE = { ('b', 1): NC_BYTE,
188+
('B', 1): NC_CHAR,
189+
('c', 1): NC_CHAR,
190+
('h', 2): NC_SHORT,
191+
('i', 4): NC_INT,
192+
('f', 4): NC_FLOAT,
193+
('d', 8): NC_DOUBLE,
190194

191195
# these come from asarray(1).dtype.char and asarray('foo').dtype.char,
192196
# used when getting the types from generic attributes.
193-
'l': NC_INT,
194-
'S': NC_CHAR }
197+
('l', 4): NC_INT,
198+
('S', 1): NC_CHAR }
195199

196200

197201
class netcdf_file(object):
@@ -226,7 +230,7 @@ class netcdf_file(object):
226230
227231
"""
228232
def __init__(self, filename, mode='r', mmap=None, version=1):
229-
''' Initialize netcdf_file from fileobj (string or file-like)
233+
"""Initialize netcdf_file from fileobj (str or file-like).
230234
231235
Parameters
232236
----------
@@ -242,7 +246,7 @@ def __init__(self, filename, mode='r', mmap=None, version=1):
242246
version of netcdf to read / write, where 1 means *Classic
243247
format* and 2 means *64-bit offset format*. Default is 1. See
244248
http://www.unidata.ucar.edu/software/netcdf/docs/netcdf/Which-Format.html#Which-Format
245-
'''
249+
"""
246250
if hasattr(filename, 'seek'): # file-like
247251
self.fp = filename
248252
self.filename = 'None'
@@ -355,11 +359,13 @@ def createVariable(self, name, type, dimensions):
355359

356360
if isinstance(type, basestring): type = dtype(type)
357361
typecode, size = type.char, type.itemsize
362+
if (typecode, size) not in REVERSE:
363+
raise ValueError("NetCDF 3 does not support type %s" % type)
358364
dtype_ = '>%s' % typecode
359365
if size > 1: dtype_ += str(size)
360366

361367
data = empty(shape_, dtype=dtype_)
362-
self.variables[name] = netcdf_variable(data, typecode, shape, dimensions)
368+
self.variables[name] = netcdf_variable(data, typecode, size, shape, dimensions)
363369
return self.variables[name]
364370

365371
def flush(self):
@@ -452,7 +458,7 @@ def _write_var_metadata(self, name):
452458

453459
self._write_att_array(var._attributes)
454460

455-
nc_type = REVERSE[var.typecode()]
461+
nc_type = REVERSE[var.typecode(), var.itemsize()]
456462
self.fp.write(asbytes(nc_type))
457463

458464
if not var.isrec:
@@ -512,7 +518,7 @@ def _write_var_data(self, name):
512518

513519
def _write_values(self, values):
514520
if hasattr(values, 'dtype'):
515-
nc_type = REVERSE[values.dtype.char]
521+
nc_type = REVERSE[values.dtype.char, values.dtype.itemsize]
516522
else:
517523
types = [
518524
(int, NC_INT),
@@ -528,11 +534,7 @@ def _write_values(self, values):
528534
if isinstance(sample, class_): break
529535

530536
typecode, size = TYPEMAP[nc_type]
531-
if typecode is 'c':
532-
dtype_ = '>c'
533-
else:
534-
dtype_ = '>%s' % typecode
535-
if size > 1: dtype_ += str(size)
537+
dtype_ = '>%s' % typecode
536538

537539
values = asarray(values, dtype=dtype_)
538540

@@ -570,7 +572,8 @@ def _read_numrecs(self):
570572

571573
def _read_dim_array(self):
572574
header = self.fp.read(4)
573-
assert header in [ZERO, NC_DIMENSION]
575+
if not header in [ZERO, NC_DIMENSION]:
576+
raise ValueError("Unexpected header.")
574577
count = self._unpack_int()
575578

576579
for dim in range(count):
@@ -585,7 +588,8 @@ def _read_gatt_array(self):
585588

586589
def _read_att_array(self):
587590
header = self.fp.read(4)
588-
assert header in [ZERO, NC_ATTRIBUTE]
591+
if not header in [ZERO, NC_ATTRIBUTE]:
592+
raise ValueError("Unexpected header.")
589593
count = self._unpack_int()
590594

591595
attributes = {}
@@ -596,7 +600,8 @@ def _read_att_array(self):
596600

597601
def _read_var_array(self):
598602
header = self.fp.read(4)
599-
assert header in [ZERO, NC_VARIABLE]
603+
if not header in [ZERO, NC_VARIABLE]:
604+
raise ValueError("Unexpected header.")
600605

601606
begin = 0
602607
dtypes = {'names': [], 'formats': []}
@@ -654,7 +659,7 @@ def _read_var_array(self):
654659

655660
# Add variable.
656661
self.variables[name] = netcdf_variable(
657-
data, typecode, shape, dimensions, attributes)
662+
data, typecode, size, shape, dimensions, attributes)
658663

659664
if rec_vars:
660665
# Remove padding when only one record variable.
@@ -698,11 +703,7 @@ def _read_var(self):
698703
begin = [self._unpack_int, self._unpack_int64][self.version_byte-1]()
699704

700705
typecode, size = TYPEMAP[nc_type]
701-
if typecode is 'c':
702-
dtype_ = '>c'
703-
else:
704-
dtype_ = '>%s' % typecode
705-
if size > 1: dtype_ += str(size)
706+
dtype_ = '>%s' % typecode
706707

707708
return name, dimensions, shape, attributes, typecode, size, dtype_, begin, vsize
708709

@@ -717,7 +718,7 @@ def _read_values(self):
717718
self.fp.read(-count % 4) # read padding
718719

719720
if typecode is not 'c':
720-
values = fromstring(values, dtype='>%s%d' % (typecode, size))
721+
values = fromstring(values, dtype='>%s' % typecode)
721722
if values.shape == (1,): values = values[0]
722723
else:
723724
values = values.rstrip(asbytes('\x00'))
@@ -781,6 +782,8 @@ class netcdf_variable(object):
781782
Typically, this is initialized as empty, but with the proper shape.
782783
typecode : dtype character code
783784
Desired data-type for the data array.
785+
size : int
786+
Desired element size for the data array.
784787
shape : sequence of ints
785788
The shape of the array. This should match the lengths of the
786789
variable's dimensions.
@@ -804,9 +807,10 @@ class netcdf_variable(object):
804807
isrec, shape
805808
806809
"""
807-
def __init__(self, data, typecode, shape, dimensions, attributes=None):
810+
def __init__(self, data, typecode, size, shape, dimensions, attributes=None):
808811
self.data = data
809812
self._typecode = typecode
813+
self._size = size
810814
self._shape = shape
811815
self.dimensions = dimensions
812816

@@ -824,10 +828,23 @@ def __setattr__(self, attr, value):
824828
self.__dict__[attr] = value
825829

826830
def isrec(self):
831+
"""Returns whether the variable has a record dimension or not.
832+
833+
A record dimension is a dimension along which additional data could be
834+
easily appended in the netcdf data structure without much rewriting of
835+
the data file. This attribute is a read-only property of the
836+
`netcdf_variable`.
837+
838+
"""
827839
return self.data.shape and not self._shape[0]
828840
isrec = property(isrec)
829841

830842
def shape(self):
843+
"""Returns the shape tuple of the data variable.
844+
845+
This is a read-only attribute and can not be modified in the
846+
same manner of other numpy arrays.
847+
"""
831848
return self.data.shape
832849
shape = property(shape)
833850

@@ -875,6 +892,18 @@ def typecode(self):
875892
"""
876893
return self._typecode
877894

895+
def itemsize(self):
896+
"""
897+
Return the itemsize of the variable.
898+
899+
Returns
900+
-------
901+
itemsize : int
902+
The element size of the variable (eg, 8 for float64).
903+
904+
"""
905+
return self._size
906+
878907
def __getitem__(self, index):
879908
return self.data[index]
880909

0 commit comments

Comments
 (0)