~ubuntu-branches/ubuntu/oneiric/python-scipy/oneiric-proposed

« back to all changes in this revision

Viewing changes to scipy/io/netcdf.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-04-06 21:26:25 UTC
  • mfrom: (9.2.1 sid)
  • Revision ID: james.westby@ubuntu.com-20110406212625-3izdplobqe6fzeql
Tags: 0.9.0+dfsg1-1
* New upstream release (Closes: #614407, #579041, #569008)
* Convert to dh_python2 (Closes: #617028)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""
2
2
NetCDF reader/writer module.
3
3
 
 
4
This module is used to read and create NetCDF files. NetCDF files are
 
5
accessed through the `netcdf_file` object. Data written to and from NetCDF
 
6
files are contained in `netcdf_variable` objects. Attributes are given
 
7
as member variables of the `netcdf_file` and `netcdf_variable` objects.
 
8
 
 
9
Notes
 
10
-----
 
11
NetCDF files are a self-describing binary data format. The file contains
 
12
metadata that describes the dimensions and variables in the file. More
 
13
details about NetCDF files can be found `here
 
14
<http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html>`_. There
 
15
are three main sections to a NetCDF data structure:
 
16
 
 
17
1. Dimensions
 
18
2. Variables
 
19
3. Attributes
 
20
 
 
21
The dimensions section records the name and length of each dimension used
 
22
by the variables. The variables would then indicate which dimensions it
 
23
uses and any attributes such as data units, along with containing the data
 
24
values for the variable. It is good practice to include a
 
25
variable that is the same name as a dimension to provide the values for
 
26
that axes. Lastly, the attributes section would contain additional
 
27
information such as the name of the file creator or the instrument used to
 
28
collect the data.
 
29
 
 
30
When writing data to a NetCDF file, there is often the need to indicate the
 
31
'record dimension'. A record dimension is the unbounded dimension for a
 
32
variable. For example, a temperature variable may have dimensions of
 
33
latitude, longitude and time. If one wants to add more temperature data to
 
34
the NetCDF file as time progresses, then the temperature variable should
 
35
have the time dimension flagged as the record dimension.
 
36
 
4
37
This module implements the Scientific.IO.NetCDF API to read and create
5
38
NetCDF files. The same API is also used in the PyNIO and pynetcdf
6
 
modules, allowing these modules to be used interchangebly when working
7
 
with NetCDF files. The major advantage of ``scipy.io.netcdf`` over other
 
39
modules, allowing these modules to be used interchangeably when working
 
40
with NetCDF files. The major advantage of this module over other
8
41
modules is that it doesn't require the code to be linked to the NetCDF
9
 
libraries as the other modules do.
10
 
 
11
 
The code is based on the `NetCDF file format specification
12
 
<http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html>`_. A
13
 
NetCDF file is a self-describing binary format, with a header followed
14
 
by data. The header contains metadata describing dimensions, variables
15
 
and the position of the data in the file, so access can be done in an
16
 
efficient manner without loading unnecessary data into memory. We use
17
 
the ``mmap`` module to create Numpy arrays mapped to the data on disk,
18
 
for the same purpose.
19
 
 
20
 
The structure of a NetCDF file is as follows:
21
 
 
22
 
    C D F <VERSION BYTE> <NUMBER OF RECORDS>
23
 
    <DIMENSIONS> <GLOBAL ATTRIBUTES> <VARIABLES METADATA>
24
 
    <NON-RECORD DATA> <RECORD DATA>
25
 
 
26
 
Record data refers to data where the first axis can be expanded at
27
 
will. All record variables share a same dimension at the first axis,
28
 
and they are stored at the end of the file per record, ie
29
 
 
30
 
    A[0], B[0], ..., A[1], B[1], ..., etc,
31
 
 
32
 
so that new data can be appended to the file without changing its original
33
 
structure. Non-record data are padded to a 4n bytes boundary. Record data
34
 
are also padded, unless there is exactly one record variable in the file,
35
 
in which case the padding is dropped.  All data is stored in big endian
36
 
byte order.
37
 
 
38
 
The Scientific.IO.NetCDF API allows attributes to be added directly to
39
 
instances of ``netcdf_file`` and ``netcdf_variable``. To differentiate
40
 
between user-set attributes and instance attributes, user-set attributes
41
 
are automatically stored in the ``_attributes`` attribute by overloading
42
 
``__setattr__``. This is the reason why the code sometimes uses
43
 
``obj.__dict__['key'] = value``, instead of simply ``obj.key = value``;
44
 
otherwise the key would be inserted into userspace attributes.
45
 
 
46
 
To create a NetCDF file::
47
 
 
48
 
    >>> import time
49
 
    >>> f = netcdf_file('simple.nc', 'w')
 
42
libraries.
 
43
 
 
44
In addition, the NetCDF file header contains the position of the data in
 
45
the file, so access can be done in an efficient manner without loading
 
46
unnecessary data into memory. It uses the ``mmap`` module to create
 
47
Numpy arrays mapped to the data on disk, for the same purpose.
 
48
 
 
49
Examples
 
50
--------
 
51
To create a NetCDF file:
 
52
 
 
53
    >>> from scipy.io import netcdf
 
54
    >>> f = netcdf.netcdf_file('simple.nc', 'w')
50
55
    >>> f.history = 'Created for a test'
51
56
    >>> f.createDimension('time', 10)
52
57
    >>> time = f.createVariable('time', 'i', ('time',))
54
59
    >>> time.units = 'days since 2008-01-01'
55
60
    >>> f.close()
56
61
 
57
 
To read the NetCDF file we just created::
58
 
 
59
 
    >>> f = netcdf_file('simple.nc', 'r')
 
62
Note the assignment of ``range(10)`` to ``time[:]``.  Exposing the slice
 
63
of the time variable allows for the data to be set in the object, rather
 
64
than letting ``range(10)`` overwrite the ``time`` variable.
 
65
 
 
66
To read the NetCDF file we just created:
 
67
 
 
68
    >>> from scipy.io import netcdf
 
69
    >>> f = netcdf.netcdf_file('simple.nc', 'r')
60
70
    >>> print f.history
61
71
    Created for a test
62
72
    >>> time = f.variables['time']
68
78
    9
69
79
    >>> f.close()
70
80
 
71
 
TODO:
72
 
 * properly implement ``_FillValue``.
73
 
 * implement Jeff Whitaker's patch for masked variables.
74
 
 * fix character variables.
75
 
 * implement PAGESIZE for Python 2.6?
76
81
"""
77
82
 
 
83
#TODO:
 
84
# * properly implement ``_FillValue``.
 
85
# * implement Jeff Whitaker's patch for masked variables.
 
86
# * fix character variables.
 
87
# * implement PAGESIZE for Python 2.6?
 
88
 
 
89
#The Scientific.IO.NetCDF API allows attributes to be added directly to
 
90
#instances of ``netcdf_file`` and ``netcdf_variable``. To differentiate
 
91
#between user-set attributes and instance attributes, user-set attributes
 
92
#are automatically stored in the ``_attributes`` attribute by overloading
 
93
#``__setattr__``. This is the reason why the code sometimes uses
 
94
#``obj.__dict__['key'] = value``, instead of simply ``obj.key = value``;
 
95
#otherwise the key would be inserted into userspace attributes.
 
96
 
 
97
 
78
98
__all__ = ['netcdf_file', 'netcdf_variable']
79
99
 
80
100
 
82
102
from mmap import mmap, ACCESS_READ
83
103
 
84
104
import numpy as np
 
105
from numpy.compat import asbytes, asstr
85
106
from numpy import fromstring, ndarray, dtype, empty, array, asarray
86
107
from numpy import little_endian as LITTLE_ENDIAN
87
108
 
88
109
 
89
 
ABSENT       = '\x00\x00\x00\x00\x00\x00\x00\x00'
90
 
ZERO         = '\x00\x00\x00\x00'
91
 
NC_BYTE      = '\x00\x00\x00\x01'
92
 
NC_CHAR      = '\x00\x00\x00\x02'
93
 
NC_SHORT     = '\x00\x00\x00\x03'
94
 
NC_INT       = '\x00\x00\x00\x04'
95
 
NC_FLOAT     = '\x00\x00\x00\x05'
96
 
NC_DOUBLE    = '\x00\x00\x00\x06'
97
 
NC_DIMENSION = '\x00\x00\x00\n'
98
 
NC_VARIABLE  = '\x00\x00\x00\x0b'
99
 
NC_ATTRIBUTE = '\x00\x00\x00\x0c'
 
110
ABSENT       = asbytes('\x00\x00\x00\x00\x00\x00\x00\x00')
 
111
ZERO         = asbytes('\x00\x00\x00\x00')
 
112
NC_BYTE      = asbytes('\x00\x00\x00\x01')
 
113
NC_CHAR      = asbytes('\x00\x00\x00\x02')
 
114
NC_SHORT     = asbytes('\x00\x00\x00\x03')
 
115
NC_INT       = asbytes('\x00\x00\x00\x04')
 
116
NC_FLOAT     = asbytes('\x00\x00\x00\x05')
 
117
NC_DOUBLE    = asbytes('\x00\x00\x00\x06')
 
118
NC_DIMENSION = asbytes('\x00\x00\x00\n')
 
119
NC_VARIABLE  = asbytes('\x00\x00\x00\x0b')
 
120
NC_ATTRIBUTE = asbytes('\x00\x00\x00\x0c')
100
121
 
101
122
 
102
123
TYPEMAP = { NC_BYTE:   ('b', 1),
121
142
 
122
143
class netcdf_file(object):
123
144
    """
124
 
    A ``netcdf_file`` object has two standard attributes: ``dimensions`` and
125
 
    ``variables``. The values of both are dictionaries, mapping dimension
 
145
    A file object for NetCDF data.
 
146
 
 
147
    A `netcdf_file` object has two standard attributes: `dimensions` and
 
148
    `variables`. The values of both are dictionaries, mapping dimension
126
149
    names to their associated lengths and variable names to variables,
127
150
    respectively. Application programs should never modify these
128
151
    dictionaries.
129
152
 
130
153
    All other attributes correspond to global attributes defined in the
131
154
    NetCDF file. Global file attributes are created by assigning to an
132
 
    attribute of the ``netcdf_file`` object.
 
155
    attribute of the `netcdf_file` object.
 
156
 
 
157
    Parameters
 
158
    ----------
 
159
    filename : string or file-like
 
160
        string -> filename
 
161
    mode : {'r', 'w'}, optional
 
162
        read-write mode, default is 'r'
 
163
    mmap : None or bool, optional
 
164
        Whether to mmap `filename` when reading.  Default is True
 
165
        when `filename` is a file name, False when `filename` is a
 
166
        file-like object
 
167
    version : {1, 2}, optional
 
168
        version of netcdf to read / write, where 1 means *Classic
 
169
        format* and 2 means *64-bit offset format*.  Default is 1.  See
 
170
        `here <http://www.unidata.ucar.edu/software/netcdf/docs/netcdf/Which-Format.html>`_
 
171
        for more info.
133
172
 
134
173
    """
135
174
    def __init__(self, filename, mode='r', mmap=None, version=1):
136
 
        ''' Initialize netcdf_file from fileobj (string or file-like)
137
 
 
138
 
        Parameters
139
 
        ----------
140
 
        filename : string or file-like
141
 
           string -> filename
142
 
        mode : {'r', 'w'}, optional
143
 
           read-write mode, default is 'r'
144
 
        mmap : None or bool, optional
145
 
           Whether to mmap `filename` when reading.  Default is True
146
 
           when `filename` is a file name, False when `filename` is a
147
 
           file-like object
148
 
        version : {1, 2}, optional
149
 
           version of netcdf to read / write, where 1 means *Classic
150
 
           format* and 2 means *64-bit offset format*.  Default is 1.  See
151
 
           http://www.unidata.ucar.edu/software/netcdf/docs/netcdf/Which-Format.html#Which-Format
152
 
        '''
 
175
        """Initialize netcdf_file from fileobj (str or file-like)."""
153
176
        if hasattr(filename, 'seek'): # file-like
154
177
            self.fp = filename
155
178
            self.filename = 'None'
191
214
        self.__dict__[attr] = value
192
215
 
193
216
    def close(self):
 
217
        """Closes the NetCDF file."""
194
218
        if not self.fp.closed:
195
219
            try:
196
220
               self.flush()
199
223
    __del__ = close
200
224
 
201
225
    def createDimension(self, name, length):
 
226
        """
 
227
        Adds a dimension to the Dimension section of the NetCDF data structure.
 
228
 
 
229
        Note that this function merely adds a new dimension that the variables can
 
230
        reference.  The values for the dimension, if desired, should be added as
 
231
        a variable using `createVariable`, referring to this dimension.
 
232
 
 
233
        Parameters
 
234
        ----------
 
235
        name : str
 
236
            Name of the dimension (Eg, 'lat' or 'time').
 
237
        length : int
 
238
            Length of the dimension.
 
239
 
 
240
        See Also
 
241
        --------
 
242
        createVariable
 
243
 
 
244
        """
202
245
        self.dimensions[name] = length
203
246
        self._dims.append(name)
204
247
 
205
248
    def createVariable(self, name, type, dimensions):
 
249
        """
 
250
        Create an empty variable for the `netcdf_file` object, specifying its data
 
251
        type and the dimensions it uses.
 
252
 
 
253
        Parameters
 
254
        ----------
 
255
        name : str
 
256
            Name of the new variable.
 
257
        type : dtype or str
 
258
            Data type of the variable.
 
259
        dimensions : sequence of str
 
260
            List of the dimension names used by the variable, in the desired order.
 
261
 
 
262
        Returns
 
263
        -------
 
264
        variable : netcdf_variable
 
265
            The newly created ``netcdf_variable`` object.
 
266
            This object has also been added to the `netcdf_file` object as well.
 
267
 
 
268
        See Also
 
269
        --------
 
270
        createDimension
 
271
 
 
272
        Notes
 
273
        -----
 
274
        Any dimensions to be used by the variable should already exist in the
 
275
        NetCDF data structure or should be created by `createDimension` prior to
 
276
        creating the NetCDF variable.
 
277
 
 
278
        """
206
279
        shape = tuple([self.dimensions[dim] for dim in dimensions])
207
280
        shape_ = tuple([dim or 0 for dim in shape])  # replace None with 0 for numpy
208
281
 
216
289
        return self.variables[name]
217
290
 
218
291
    def flush(self):
 
292
        """
 
293
        Perform a sync-to-disk flush if the `netcdf_file` object is in write mode.
 
294
 
 
295
        See Also
 
296
        --------
 
297
        sync : Identical function
 
298
 
 
299
        """
219
300
        if hasattr(self, 'mode') and self.mode is 'w':
220
301
            self._write()
221
302
    sync = flush
222
303
 
223
304
    def _write(self):
224
 
        self.fp.write('CDF')
 
305
        self.fp.write(asbytes('CDF'))
225
306
        self.fp.write(array(self.version_byte, '>b').tostring())
226
307
 
227
308
        # Write headers and data.
298
379
        self._write_att_array(var._attributes)
299
380
 
300
381
        nc_type = REVERSE[var.typecode()]
301
 
        self.fp.write(nc_type)
 
382
        self.fp.write(asbytes(nc_type))
302
383
 
303
384
        if not var.isrec:
304
385
            vsize = var.data.size * var.data.itemsize
332
413
        if not var.isrec:
333
414
            self.fp.write(var.data.tostring())
334
415
            count = var.data.size * var.data.itemsize
335
 
            self.fp.write('0' * (var._vsize - count))
 
416
            self.fp.write(asbytes('0') * (var._vsize - count))
336
417
        else:  # record variable
337
418
            # Handle rec vars with shape[0] < nrecs.
338
419
            if self._recs > len(var.data):
350
431
                self.fp.write(rec.tostring())
351
432
                # Padding
352
433
                count = rec.size * rec.itemsize
353
 
                self.fp.write('0' * (var._vsize - count))
 
434
                self.fp.write(asbytes('0') * (var._vsize - count))
354
435
                pos += self._recsize
355
436
                self.fp.seek(pos)
356
437
            self.fp.seek(pos0 + var._vsize)
381
462
 
382
463
        values = asarray(values, dtype=dtype_)
383
464
 
384
 
        self.fp.write(nc_type)
 
465
        self.fp.write(asbytes(nc_type))
385
466
 
386
467
        if values.dtype.char == 'S':
387
468
            nelems = values.itemsize
394
475
            values = values.byteswap()
395
476
        self.fp.write(values.tostring())
396
477
        count = values.size * values.itemsize
397
 
        self.fp.write('0' * (-count % 4))  # pad
 
478
        self.fp.write(asbytes('0') * (-count % 4))  # pad
398
479
 
399
480
    def _read(self):
400
481
        # Check magic bytes and version
401
482
        magic = self.fp.read(3)
402
 
        if not magic == 'CDF':
 
483
        if not magic == asbytes('CDF'):
403
484
            raise TypeError("Error: %s is not a valid NetCDF 3 file" %
404
485
                            self.filename)
405
486
        self.__dict__['version_byte'] = fromstring(self.fp.read(1), '>b')[0]
419
500
        count = self._unpack_int()
420
501
 
421
502
        for dim in range(count):
422
 
            name = self._unpack_string()
 
503
            name = asstr(self._unpack_string())
423
504
            length = self._unpack_int() or None  # None for record dimension
424
505
            self.dimensions[name] = length
425
506
            self._dims.append(name)  # preserve order
435
516
 
436
517
        attributes = {}
437
518
        for attr in range(count):
438
 
            name = self._unpack_string()
 
519
            name = asstr(self._unpack_string())
439
520
            attributes[name] = self._read_values()
440
521
        return attributes
441
522
 
523
604
                self.variables[var].__dict__['data'] = rec_array[var]
524
605
 
525
606
    def _read_var(self):
526
 
        name = self._unpack_string()
 
607
        name = asstr(self._unpack_string())
527
608
        dimensions = []
528
609
        shape = []
529
610
        dims = self._unpack_int()
558
639
        typecode, size = TYPEMAP[nc_type]
559
640
 
560
641
        count = n*size
561
 
        values = self.fp.read(count)
 
642
        values = self.fp.read(int(count))
562
643
        self.fp.read(-count % 4)  # read padding
563
644
 
564
645
        if typecode is not 'c':
565
646
            values = fromstring(values, dtype='>%s%d' % (typecode, size))
566
647
            if values.shape == (1,): values = values[0]
567
648
        else:
568
 
            values = values.rstrip('\x00')
 
649
            values = values.rstrip(asbytes('\x00'))
569
650
        return values
570
651
 
571
652
    def _pack_begin(self, begin):
579
660
    _pack_int32 = _pack_int
580
661
 
581
662
    def _unpack_int(self):
582
 
        return fromstring(self.fp.read(4), '>i')[0]
 
663
        return int(fromstring(self.fp.read(4), '>i')[0])
583
664
    _unpack_int32 = _unpack_int
584
665
 
585
666
    def _pack_int64(self, value):
591
672
    def _pack_string(self, s):
592
673
        count = len(s)
593
674
        self._pack_int(count)
594
 
        self.fp.write(s)
595
 
        self.fp.write('0' * (-count % 4))  # pad
 
675
        self.fp.write(asbytes(s))
 
676
        self.fp.write(asbytes('0') * (-count % 4))  # pad
596
677
 
597
678
    def _unpack_string(self):
598
679
        count = self._unpack_int()
599
 
        s = self.fp.read(count).rstrip('\x00')
 
680
        s = self.fp.read(count).rstrip(asbytes('\x00'))
600
681
        self.fp.read(-count % 4)  # read padding
601
682
        return s
602
683
 
603
684
 
604
685
class netcdf_variable(object):
605
686
    """
606
 
    ``netcdf_variable`` objects are constructed by calling the method
607
 
    ``createVariable`` on the netcdf_file object.
 
687
    A data object for the `netcdf` module.
608
688
 
609
 
    ``netcdf_variable`` objects behave much like array objects defined in
610
 
    Numpy, except that their data resides in a file. Data is read by
611
 
    indexing and written by assigning to an indexed subset; the entire
612
 
    array can be accessed by the index ``[:]`` or using the methods
613
 
    ``getValue`` and ``assignValue``. ``netcdf_variable`` objects also
614
 
    have attribute ``shape`` with the same meaning as for arrays, but
615
 
    the shape cannot be modified. There is another read-only attribute
616
 
    ``dimensions``, whose value is the tuple of dimension names.
 
689
    `netcdf_variable` objects are constructed by calling the method
 
690
    `netcdf_file.createVariable` on the `netcdf_file` object. `netcdf_variable`
 
691
    objects behave much like array objects defined in numpy, except that their
 
692
    data resides in a file. Data is read by indexing and written by assigning
 
693
    to an indexed subset; the entire array can be accessed by the index ``[:]``
 
694
    or (for scalars) by using the methods `getValue` and `assignValue`.
 
695
    `netcdf_variable` objects also have attribute `shape` with the same meaning
 
696
    as for arrays, but the shape cannot be modified. There is another read-only
 
697
    attribute `dimensions`, whose value is the tuple of dimension names.
617
698
 
618
699
    All other attributes correspond to variable attributes defined in
619
700
    the NetCDF file. Variable attributes are created by assigning to an
620
 
    attribute of the ``netcdf_variable`` object.
 
701
    attribute of the `netcdf_variable` object.
 
702
 
 
703
    Parameters
 
704
    ----------
 
705
    data : array_like
 
706
        The data array that holds the values for the variable.
 
707
        Typically, this is initialized as empty, but with the proper shape.
 
708
    typecode : dtype character code
 
709
        Desired data-type for the data array.
 
710
    shape : sequence of ints
 
711
        The shape of the array.  This should match the lengths of the
 
712
        variable's dimensions.
 
713
    dimensions : sequence of strings
 
714
        The names of the dimensions used by the variable.  Must be in the
 
715
        same order of the dimension lengths given by `shape`.
 
716
    attributes : dict, optional
 
717
        Attribute values (any type) keyed by string names.  These attributes
 
718
        become attributes for the netcdf_variable object.
 
719
 
 
720
 
 
721
    Attributes
 
722
    ----------
 
723
    dimensions : list of str
 
724
        List of names of dimensions used by the variable object.
 
725
    isrec, shape
 
726
        Properties
 
727
 
 
728
    See also
 
729
    --------
 
730
    isrec, shape
621
731
 
622
732
    """
623
733
    def __init__(self, data, typecode, shape, dimensions, attributes=None):
648
758
    shape = property(shape)
649
759
 
650
760
    def getValue(self):
 
761
        """
 
762
        Retrieve a scalar value from a `netcdf_variable` of length one.
 
763
 
 
764
        Raises
 
765
        ------
 
766
        ValueError
 
767
            If the netcdf variable is an array of length greater than one,
 
768
            this exception will be raised.
 
769
 
 
770
        """
651
771
        return self.data.item()
652
772
 
653
773
    def assignValue(self, value):
 
774
        """
 
775
        Assign a scalar value to a `netcdf_variable` of length one.
 
776
 
 
777
        Parameters
 
778
        ----------
 
779
        value : scalar
 
780
            Scalar value (of compatible type) to assign to a length-one netcdf
 
781
            variable. This value will be written to file.
 
782
 
 
783
        Raises
 
784
        ------
 
785
        ValueError
 
786
            If the input is not a scalar, or if the destination is not a length-one
 
787
            netcdf variable.
 
788
 
 
789
        """
654
790
        self.data.itemset(value)
655
791
 
656
792
    def typecode(self):
 
793
        """
 
794
        Return the typecode of the variable.
 
795
 
 
796
        Returns
 
797
        -------
 
798
        typecode : char
 
799
            The character typecode of the variable (eg, 'i' for int).
 
800
 
 
801
        """
657
802
        return self._typecode
658
803
 
659
804
    def __getitem__(self, index):