[pyepr] 02/08: Imported Upstream version 0.9.1

Antonio Valentino a_valentino-guest at moszumanska.debian.org
Sun Mar 1 08:17:00 UTC 2015


This is an automated email from the git hooks/post-receive script.

a_valentino-guest pushed a commit to branch master
in repository pyepr.

commit 2dacf023d1868da7132075d442efc1204ccbaa6b
Author: Antonio Valentino <antonio.valentino at tiscali.it>
Date:   Sat Feb 28 17:00:00 2015 +0000

    Imported Upstream version 0.9.1
---
 MANIFEST.in                                   |    3 +-
 Makefile                                      |    8 +-
 README.txt                                    |   14 +-
 doc/NEWS.txt                                  |   98 ++-
 doc/_templates/appveyor.html                  |    5 +
 doc/_templates/ohloh.html                     |    2 +-
 doc/_templates/pypi.html                      |   17 +
 doc/_templates/travis-ci.html                 |    2 +
 doc/bands_example.txt                         |    4 +-
 doc/bitmask_example.txt                       |    6 +-
 doc/conf.py                                   |   19 +-
 doc/examples/update_elements.py               |   84 ++
 doc/examples/write_bands.py                   |    2 +-
 doc/examples/write_bitmask.py                 |    2 +-
 doc/examples/write_ndvi.py                    |    2 +-
 doc/images/modified_water_vapour.png          |  Bin 0 -> 43775 bytes
 doc/images/modified_water_vapour_with_box.png |  Bin 0 -> 46723 bytes
 doc/images/water_vapour_histogram_01.png      |  Bin 0 -> 27892 bytes
 doc/images/water_vapour_histogram_02.png      |  Bin 0 -> 32226 bytes
 doc/index.txt                                 |   14 +-
 doc/interactive_use.txt                       |   20 +-
 doc/ndvi_example.txt                          |    8 +-
 doc/reference.txt                             |  561 ++++++++-----
 doc/tutorials.txt                             |    1 +
 doc/update_example.txt                        |  205 +++++
 doc/usermanual.txt                            |   87 +-
 requirements.txt                              |    2 +-
 setup.py                                      |  224 +++--
 src/epr.pxd                                   |  370 +++++++++
 src/epr.pyx                                   | 1099 ++++++++++++++-----------
 tests/__init__.py                             |    0
 tests/checksetup.mak                          |  202 +++++
 {test => tests}/test_all.py                   |  870 +++++++++++++++++--
 33 files changed, 3057 insertions(+), 874 deletions(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index 672b0b5..bd4e011 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,10 @@
 include MANIFEST.in
 include LICENSE.txt
 include requirements.txt
-include src/*.c
 
 recursive-include LICENSES *.txt
+recursive-include tests *.py
+recursive-include src *.pyx *.pxd *.c
 recursive-include epr-api-src *.c *.h
 
 recursive-include doc *.txt *.py Makefile make.bat *.png
diff --git a/Makefile b/Makefile
index dd773fc..5ab584d 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@
 PYTHON = python3
 CYTHON = cython
 TEST_DATSET_URL = "http://earth.esa.int/services/sample_products/meris/LRC/L2/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz"
-TEST_DATSET = test/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1
+TEST_DATSET = tests/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1
 
 EPRAPIROOT = ../epr-api
 
@@ -63,6 +63,7 @@ doc:
 clean:
 	$(PYTHON) setup.py clean --all
 	$(RM) -r build dist pyepr.egg-info
+	$(RM) -r $$(find doc -name __pycache__) $$(find tests -name __pycache__)
 	$(RM) MANIFEST src/*.c src/*.o *.so
 	$(RM) tests/*.py[co] doc/sphinxext/*.py[co] README.html
 	$(MAKE) -C doc clean
@@ -72,9 +73,10 @@ distclean: clean
 	$(RM) $(TEST_DATSET)
 	$(RM) -r doc/html
 	$(RM) -r LICENSES epr-api-src
+	$(MAKE) -C tests -f checksetup.mak distclean
 
 check: ext $(TEST_DATSET)
-	env PYTHONPATH=. $(PYTHON) test/test_all.py --verbose
+	env PYTHONPATH=. $(PYTHON) tests/test_all.py --verbose
 
 debug:
 	$(PYTHON) setup.py build_ext --inplace --debug
@@ -82,5 +84,5 @@ debug:
 data: $(TEST_DATSET)
 
 $(TEST_DATSET):
-	wget -P test $(TEST_DATSET_URL)
+	wget -P tests $(TEST_DATSET_URL)
 	gunzip $@
diff --git a/README.txt b/README.txt
index c017621..ae05e3e 100644
--- a/README.txt
+++ b/README.txt
@@ -5,8 +5,8 @@ ENVISAT Product Reader Python API
 :HomePage:  http://avalentino.github.io/pyepr
 :Author:    Antonio Valentino
 :Contact:   antonio.valentino at tiscali.it
-:Copyright: 2011-2014, Antonio Valentino <antonio.valentino at tiscali.it>
-:Version:   0.8.2
+:Copyright: 2011-2015, Antonio Valentino <antonio.valentino at tiscali.it>
+:Version:   0.9.1
 
 
 Introduction
@@ -23,10 +23,10 @@ or on a raw data layer. The raw data access makes it possible to read
 any data field contained in a product file.
 
 .. _PyEPR: https://github.com/avalentino/pyepr
-.. _Python: http://www.python.org
+.. _Python: https://www.python.org
 .. _`EPR API`: https://github.com/bcdev/epr-api
-.. _ENVISAT: http://envisat.esa.int
-.. _ESA: http://earth.esa.int
+.. _ENVISAT: https://envisat.esa.int
+.. _ESA: https://earth.esa.int
 
 
 Requirements
@@ -40,7 +40,7 @@ correctly installed and configured:
 * `EPR API`_ >= 2.2 (optional, since PyEPR 0.7 the source tar-ball comes
   with a copy of the PER C API sources)
 * a reasonably updated C compiler (build only)
-* Cython_ >= 0.13 (build only)
+* Cython_ >= 0.15 (build only)
 * unittest2_ (only required for Python < 2.7)
 
 .. _Python2: Python_
@@ -99,7 +99,7 @@ To install PyEPR_ in a non-standard path::
 License
 =======
 
-Copyright (C) 2011-2014 Antonio Valentino <antonio.valentino at tiscali.it>
+Copyright (C) 2011-2015 Antonio Valentino <antonio.valentino at tiscali.it>
 
 PyEPR is free software: you can redistribute it and/or modify
 it under the terms of the `GNU General Public License`_ as published by
diff --git a/doc/NEWS.txt b/doc/NEWS.txt
index 44d61fe..493a6e9 100644
--- a/doc/NEWS.txt
+++ b/doc/NEWS.txt
@@ -2,6 +2,94 @@ Change history
 ==============
 
 
+PyEPR 0.9.1 (27/02/2015)
+------------------------
+
+* Fix source distribution (missing epr-api C sources)
+
+
+PyEPR 0.9 (27/02/2015)
+----------------------
+
+* basic support for update mode: products can now be opened in update mode
+  ('rb+') and it is possible to call :meth:`epr.Field.set_elem` and
+  :meth:`epr.Field.set_elems` methods to set :class:`epr.Field` elements
+  changing the contents of the :class:`epr.Product` on disk.
+  This feature is not available in the EPR C API.
+* new functions/methods and properties:
+
+  - :attr:`epr.Record.index` property: returns the index of the
+    :class:`epr.Record` within the :class:`epr.Dataset`
+  - :attr:`epr.Band.dataset` property: returns the source
+    :class:`epr.Dataset` object containing the raw data used to create
+    the :class:`epr.Band`\ ’s pixel values
+  - :attr:`epr.Band._field_index` and :attr:`epr.Band._elem_index`
+    properties: return the :class:`epr.Field` index (within the
+    :class:`epr.Record`) and the element index (within the
+    :class:`epr.Field`) containing the raw data used to create the
+    :class:`epr.Band`\ ’s pixel values
+  - :attr:`epr.Record.dataset_name` property: returns the name of the
+    :class:`epr.Dataset` from which the :class:`Record` has bee read
+  - :attr:`epr.Record.tot_size` and :attr:`epr.Field.tot_size` properties:
+    return the total size in bytes of the :class:`epr.Record` and
+    :class:`epr.Field` respectively
+  - :func:`epr.get_numpy_dtype` function: retrieves the numpy_ data type
+    corresponding to the specified EPR type ID
+  - added support for some low level feature: the *_magic* private attribute
+    stores the identifier of EPR C stricture, the
+    :meth:`epr.Record.get_offset` returns the offset in bytes of the
+    :class:`epr.Record` within the file, and the :meth:`epr.Field.get_offset`
+    method returns the :clasS:`epr.Field` offset within the
+    :class:`epr.Record`
+
+* improved functions/methods:
+
+  - :meth:`epr.Field.get_elems` now also handles :data:`epr.E_TID_STRING` and
+    :data:`epr.E_TID_TIME` data types
+  - improved :func:`epr.get_data_type_size`, :func:`epr.data_type_id_to_str`,
+    :func:`epr.get_scaling_method_name` and :func:`epr.get_sample_model_name`
+    functions that are now defined using the cython `cpdef` directive
+  - the :meth:`epr.Field.get_elems` method has been re-written to remove
+    loops and unnecessary data copy
+  - now generator expressions are used to implement `__iter__` special methods
+
+* the *index* parameter of the :meth:`epr.Dataset.read_record` method is
+  now optional (defaults to zero)
+* the deprecated `__revision__` variable has been removed
+* declarations of the EPR C API have been moved to the new :file:`epr.pyd`
+* the `const_char` and `const_void` definitions have been dropped,
+  no longer necessary with cython_ >= 0.19
+* minimum required version for cython_ is now 0.19
+* the :file:`setup.py` script has been completely rewritten to be more
+  "pip_ friendly".  The new script uses setuptools_ if available and
+  functions that use numpy_ are evaluated lazily so to give a chance to
+  pip_ and setuptools_ to install dependencies, numpy_, before they are
+  actually used.
+  This should make PyEPR "pip-installable" even on system there numpy_
+  is not already installed.
+* the :file:`test` directory has been renamed into :file:`tests`
+* the test suite now has a :func:`setUpModule` function that automatically
+  downloads the ENVISAT test data required for test execution.
+  The download only happens if the test dataset is not already available.
+* tests can now be run using the :file:`setup.py` script::
+
+    $ python3 setup.py test
+
+* enable continuous integration and testing in for Windows_ using AppVeyor_
+  (32bit only)
+* status badges for
+  `AppVeyor CI <https://ci.appveyor.com/project/avalentino/pyepr>`_ and
+  PyPI_ added to the HTML doc index
+
+
+.. _pip: https://pip.pypa.io
+.. _setuptools: https://bitbucket.org/pypa/setuptools
+.. _numpy: http://www.numpy.org
+.. _Windows: http://windows.microsoft.com
+.. _AppVeyor: http://www.appveyor.com
+.. _PyPI: https://pypi.python.org/pypi/pyepr
+
+
 PyEPR 0.8.2 (03/08/2014)
 ------------------------
 
@@ -73,7 +161,7 @@ PyEPR 0.7.1 (19/08/2013)
 
   - updated the :file:`README.txt` file to mention EPR C API sourced inclusion
     in the PyEPR 0.7 (and lates) source tar-ball
-  - small fix in the installation instructions: the pip tool does not have  a
+  - small fix in the installation instructions: the pip_ tool does not have  a
     "--prefix" parameter
   - always use the python3 syntax for the *print* function in all examples in
     the documentation
@@ -87,7 +175,7 @@ PyEPR 0.7.1 (19/08/2013)
     script
   - formatting
 
-.. _Ohloh: http://www.ohloh.net
+.. _Ohloh: https://www.openhub.net
 
 
 PyEPR 0.7 (04/08/2013)
@@ -99,7 +187,7 @@ PyEPR 0.7 (04/08/2013)
 * now the source tar-ball also includes a copy of the EPR C API sources
   so that no external C library is required to build PyEPR.
 
-  This features also makes it easier to install PyEPR using pip.
+  This features also makes it easier to install PyEPR using pip_.
 
   The user can still guild PyEPR against a system version of the ERP-API
   library simply using the :option:`--epr-api-src` option of the
@@ -149,9 +237,9 @@ PyEPR 0.5 (25/04/2011)
 * dropped old versions of cython_; now cython_ 0.14.1 or newer is required
 * suppressed several constness related warnings
 
-.. _`Python 3`: http://docs.python.org/3
+.. _`Python 3`: https://docs.python.org/3
 .. _intersphinx: http://sphinx-doc.org/latest/ext/intersphinx.html
-.. _cython: http://www.cython.org
+.. _cython: http://cython.org
 
 
 PyEPR 0.4 (10/04/2011)
diff --git a/doc/_templates/appveyor.html b/doc/_templates/appveyor.html
new file mode 100644
index 0000000..d3caaa3
--- /dev/null
+++ b/doc/_templates/appveyor.html
@@ -0,0 +1,5 @@
+<div>
+<p>
+<a href="https://ci.appveyor.com/project/avalentino/pyepr"><img src="https://ci.appveyor.com/api/projects/status/pno3t4bwf3pwqdwi?svg=true" alt="AppVeyor status page"/></a>
+</p>
+</div>
diff --git a/doc/_templates/ohloh.html b/doc/_templates/ohloh.html
index c0cd17a..d96a1cb 100644
--- a/doc/_templates/ohloh.html
+++ b/doc/_templates/ohloh.html
@@ -1,3 +1,3 @@
 <div>
-<script type="text/javascript" src="http://www.ohloh.net/p/588314/widgets/project_thin_badge.js"></script>
+<script type="text/javascript" src="http://www.openhub.net/p/588314/widgets/project_thin_badge.js"></script>
 </div>
diff --git a/doc/_templates/pypi.html b/doc/_templates/pypi.html
new file mode 100644
index 0000000..cd0aad0
--- /dev/null
+++ b/doc/_templates/pypi.html
@@ -0,0 +1,17 @@
+<div>
+<p>
+<a href="https://pypi.python.org/pypi/pyepr"><img src="https://pypip.in/version/pyepr/badge.svg" alt="Latest Version"/></a>
+</p>
+<p>
+<a href="https://pypi.python.org/pypi/pyepr"><img src="https://pypip.in/py_versions/pyepr/badge.svg" alt="Supported Python versions"/></a>
+</p>
+<p>
+<a href="https://pypi.python.org/pypi/pyepr"><img src="https://pypip.in/license/pyepr/badge.svg" alt="License"/></a>
+</p>
+<p>
+<a href="https://pypi.python.org/pypi/pyepr"><img src="https://pypip.in/download/pyepr/badge.svg" alt="Downloads"/></a>
+</p>
+<p>
+<a href="https://pypi.python.org/pypi/pyepr"><img src="https://pypip.in/wheel/pyepr/badge.svg" alt="Wheel Status"/></a>
+</p>
+</div>
diff --git a/doc/_templates/travis-ci.html b/doc/_templates/travis-ci.html
index 8913bd1..fae4fe3 100644
--- a/doc/_templates/travis-ci.html
+++ b/doc/_templates/travis-ci.html
@@ -1,3 +1,5 @@
 <div>
+<p>
 <a href="https://travis-ci.org/avalentino/pyepr"><img src="https://travis-ci.org/avalentino/pyepr.png" alt="travis-ci status page"/></a>
+</p>
 </div>
diff --git a/doc/bands_example.txt b/doc/bands_example.txt
index 7a39575..e59e92c 100644
--- a/doc/bands_example.txt
+++ b/doc/bands_example.txt
@@ -18,7 +18,7 @@ The program is invoked as follows:
     <output directory for the raster file> <dataset name 1> \
     [<dataset name 2> ... <dataset name N>]
 
-.. _ENVISAT: http://envisat.esa.int
+.. _ENVISAT: https://envisat.esa.int
 .. _PyEPR: https://github.com/avalentino/pyepr
 .. _`write_bands.c`: https://github.com/bcdev/epr-api/blob/master/src/examples/write_bands.c
 
@@ -194,5 +194,5 @@ powerful :class:`numpy.ndarray` interface.
         raster.data.tofile(out_stream)
 
 
-.. _Python: http://www.python.org
+.. _Python: https://www.python.org
 .. _pythonic: http://www.cafepy.com/article/be_pythonic
diff --git a/doc/bitmask_example.txt b/doc/bitmask_example.txt
index 7cc9c0a..0232114 100644
--- a/doc/bitmask_example.txt
+++ b/doc/bitmask_example.txt
@@ -15,7 +15,7 @@ The program is invoked as follows:
     $ python write_bitmask.py <envisat-product> <bitmask-expression> \
     <output-file>
 
-.. _ENVISAT: http://envisat.esa.int
+.. _ENVISAT: https://envisat.esa.int
 .. _PyEPR: https://github.com/avalentino/pyepr
 .. _`write_bitmask.c`: https://github.com/bcdev/epr-api/blob/master/src/examples/write_bitmask.c
 
@@ -120,5 +120,5 @@ of the :class:`epr.Raster` objects that exposes data via the
    :lines: 65-66
 
 
-.. _Python: http://www.python.org
-.. _ENVISAT: http://envisat.esa.int
+.. _Python: https://www.python.org
+.. _ENVISAT: https://envisat.esa.int
diff --git a/doc/conf.py b/doc/conf.py
index 51beae7..d22d603 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -61,16 +61,16 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'PyEPR'
-copyright = u'2011-2014, Antonio Valentino'
+copyright = u'2011-2015, Antonio Valentino'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '0.8.2'
+version = '0.9.1'
 # The full version, including alpha/beta/rc tags.
-release = version  # + 'dev'
+release = version  #+ 'dev'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -130,7 +130,7 @@ extlinks = {
 
 # Intersphinx
 intersphinx_mapping = {
-    'python': ('http://docs.python.org', None),
+    'python': ('https://docs.python.org/3', None),
     'numpy':  ('http://docs.scipy.org/doc/numpy', None),
 }
 
@@ -188,7 +188,8 @@ html_last_updated_fmt = '%b %d, %Y'
 # Custom sidebar templates, maps document names to template names.
 html_sidebars = {
    'index': ['globaltoc.html', 'relations.html', 'sourcelink.html',
-             'searchbox.html', 'ohloh.html', 'travis-ci.html'],
+             'searchbox.html', 'ohloh.html', 'pypi.html',
+             'travis-ci.html', 'appveyor.html'],
 }
 
 # Additional templates that should be rendered to pages, maps page names to
@@ -307,10 +308,10 @@ texinfo_documents = [
 # -- Options for Epub output ----------------------------------------------
 
 # Bibliographic Dublin Core info.
-epub_title = u'PyEPR'
+epub_title = project
 epub_author = u'Antonio Valentino'
-epub_publisher = u'Antonio Valentino'
-epub_copyright = u'2011-2014, Antonio Valentino'
+epub_publisher = epub_author
+epub_copyright = copyright
 
 # The basename for the epub file. It defaults to the project name.
 #epub_basename = u'PyEPR'
@@ -375,4 +376,4 @@ epub_exclude_files = ['search.html']
 
 
 # Example configuration for intersphinx: refer to the Python standard library.
-#intersphinx_mapping = {'http://docs.python.org/': None}
+#intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/doc/examples/update_elements.py b/doc/examples/update_elements.py
new file mode 100755
index 0000000..74a8410
--- /dev/null
+++ b/doc/examples/update_elements.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+import numpy as np
+from matplotlib import pyplot as plt
+import epr
+
+
+FILENAME = 'MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1'
+
+# load original data
+with epr.open(FILENAME) as product:
+    band = product.get_band('water_vapour')
+    wv_orig_histogram, orig_bins = np.histogram(band.read_as_array().flat, 50)
+
+# plot water vapour histogram
+plt.figure()
+plt.bar(orig_bins[:-1], wv_orig_histogram, 0.02, label='original')
+plt.grid(True)
+plt.title('Water Vapour Histogram')
+plt.savefig('water_vapour_histogram_01.png')
+
+# modily scaling facotrs
+with epr.open(FILENAME, 'rb+') as product:
+    dataset = product.get_dataset('Scaling_Factor_GADS')
+    record = dataset.read_record(0)
+
+    field = record.get_field('sf_wvapour')
+    scaling = field.get_elem()
+    scaling *= 1.1
+    field.set_elem(scaling)
+
+# re-open the product and load modified data
+with epr.open(FILENAME) as product:
+    band = product.get_band('water_vapour')
+    unit = band.unit
+    new_data = band.read_as_array()
+    wv_new_histogram, new_bins = np.histogram(new_data.flat, 50)
+
+# plot histogram of modified data
+plt.figure()
+plt.bar(orig_bins[:-1], wv_orig_histogram, 0.02, label='original')
+plt.grid(True)
+plt.title('Water Vapour Histogram')
+plt.hold(True)
+plt.bar(new_bins[:-1], wv_new_histogram, 0.02, color='red', label='new')
+plt.legend()
+plt.savefig('water_vapour_histogram_02.png')
+
+# plot the water vapour map
+plt.figure(figsize=(8, 4))
+plt.imshow(new_data)
+plt.grid(True)
+plt.title('Water Vapour')
+cb = plt.colorbar()
+cb.set_label('[{}]'.format(unit))
+plt.savefig('modified_water_vapour.png')
+
+# modify the "Vapour_Content" dataset
+with epr.open(FILENAME, 'rb+') as product:
+    dataset = product.get_dataset('Vapour_Content')
+    for line in range(70, 100):
+        record = dataset.read_record(line)
+        field = record.get_field_at(2)
+        elems = field.get_elems()
+        elems[50:100] = 0
+        field.set_elems(elems)
+
+# re-open the product and load modified data
+with epr.open(FILENAME) as product:
+    band = product.get_band('water_vapour')
+    unit = band.unit
+    data = band.read_as_array()
+
+# plot the water vapour map
+plt.figure(figsize=(8, 4))
+plt.imshow(data)
+plt.grid(True)
+plt.title('Water Vapour with box')
+cb = plt.colorbar()
+cb.set_label('[{}]'.format(unit))
+plt.savefig('modified_water_vapour_with_box.png')
+plt.show()
diff --git a/doc/examples/write_bands.py b/doc/examples/write_bands.py
index ccd0caf..2f3f303 100755
--- a/doc/examples/write_bands.py
+++ b/doc/examples/write_bands.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # This program is a direct translation of the sample program
 # "write_bands.c" bundled with the EPR-API distribution.
diff --git a/doc/examples/write_bitmask.py b/doc/examples/write_bitmask.py
index e9855a0..7543e18 100755
--- a/doc/examples/write_bitmask.py
+++ b/doc/examples/write_bitmask.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # This program is a direct translation of the sample program
 # "write_bitmask.c" bundled with the EPR-API distribution.
diff --git a/doc/examples/write_ndvi.py b/doc/examples/write_ndvi.py
index 3c2a27a..4385f2d 100755
--- a/doc/examples/write_ndvi.py
+++ b/doc/examples/write_ndvi.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # This program is a direct translation of the sample program
 # "write_ndvi.c" bundled with the EPR-API distribution.
diff --git a/doc/images/modified_water_vapour.png b/doc/images/modified_water_vapour.png
new file mode 100644
index 0000000..a7c0c28
Binary files /dev/null and b/doc/images/modified_water_vapour.png differ
diff --git a/doc/images/modified_water_vapour_with_box.png b/doc/images/modified_water_vapour_with_box.png
new file mode 100644
index 0000000..36abd4e
Binary files /dev/null and b/doc/images/modified_water_vapour_with_box.png differ
diff --git a/doc/images/water_vapour_histogram_01.png b/doc/images/water_vapour_histogram_01.png
new file mode 100644
index 0000000..7b260fe
Binary files /dev/null and b/doc/images/water_vapour_histogram_01.png differ
diff --git a/doc/images/water_vapour_histogram_02.png b/doc/images/water_vapour_histogram_02.png
new file mode 100644
index 0000000..57a80c9
Binary files /dev/null and b/doc/images/water_vapour_histogram_02.png differ
diff --git a/doc/index.txt b/doc/index.txt
index 0c27a95..2d6d31c 100644
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -12,7 +12,7 @@ ENVISAT Product Reader Python API
 :HomePage:  http://avalentino.github.io/pyepr
 :Author:    Antonio Valentino
 :Contact:   antonio.valentino at tiscali.it
-:Copyright: 2011-2014, Antonio Valentino
+:Copyright: 2011-2015, Antonio Valentino
 :Version:   |release|
 
 
@@ -32,10 +32,10 @@ ENVISAT Product Reader Python API
     any data field contained in a product file.
 
     .. _PyEPR: https://github.com/avalentino/pyepr
-    .. _Python: http://www.python.org
+    .. _Python: https://www.python.org
     .. _`EPR API`: https://github.com/bcdev/epr-api
-    .. _ENVISAT: http://envisat.esa.int
-    .. _ESA: http://earth.esa.int
+    .. _ENVISAT: https://envisat.esa.int
+    .. _ESA: https://earth.esa.int
 
 
     Documentation
@@ -58,7 +58,9 @@ ENVISAT Product Reader Python API
     Online documentation for other PyEpr_ versions:
 
     * `latest <https://pyepr.readthedocs.org/en/latest/>`_ development
-    * `0.8.2 <https://pyepr.readthedocs.org/en/v0.8.2/>`_ (latest stable)
+    * `0.9.1 <https://pyepr.readthedocs.org/en/v0.9.1/>`_ (latest stable)
+    * `0.9 <https://pyepr.readthedocs.org/en/v0.9/>`_
+    * `0.8.2 <https://pyepr.readthedocs.org/en/v0.8.2/>`_
     * `0.8.1 <https://pyepr.readthedocs.org/en/v0.8.1/>`_
     * `0.8 <https://pyepr.readthedocs.org/en/v0.8/>`_
     * `0.7.1 <https://pyepr.readthedocs.org/en/v0.7.1/>`_
@@ -70,7 +72,7 @@ ENVISAT Product Reader Python API
 License
 =======
 
-Copyright (C) 2011-2014 Antonio Valentino <antonio.valentino at tiscali.it>
+Copyright (C) 2011-2015 Antonio Valentino <antonio.valentino at tiscali.it>
 
 PyEPR is free software: you can redistribute it and/or modify
 it under the terms of the `GNU General Public License`_ as published by
diff --git a/doc/interactive_use.txt b/doc/interactive_use.txt
index 5d2da36..a84edd7 100644
--- a/doc/interactive_use.txt
+++ b/doc/interactive_use.txt
@@ -14,12 +14,12 @@ The ASAR_ product used in this example is a `free sample`_ available at the
 ESA_ web site.
 
 .. _PyEPR: https://github.com/avalentino/pyepr
-.. _ENVISAT: http://envisat.esa.int
-.. _ASAR: http://envisat.esa.int/handbooks/asar
+.. _ENVISAT: https://envisat.esa.int
+.. _ASAR: https://earth.esa.int/handbooks/asar/CNTR.html
 .. _IPython: http://ipython.org
 .. _matplotlib: http://matplotlib.org
-.. _`free sample`: http://earth.esa.int/services/sample_products/asar/IMP/ASA_IMP_1PNUPA20060202_062233_000000152044_00435_20529_3110.N1.gz
-.. _ESA: http://earth.esa.int
+.. _`free sample`: https://earth.esa.int/services/sample_products/asar/IMP/ASA_IMP_1PNUPA20060202_062233_000000152044_00435_20529_3110.N1.gz
+.. _ESA: https://earth.esa.int
 
 
 :mod:`epr` module and classes
@@ -51,7 +51,7 @@ available classes and functions::
     In [2]: epr?
 
     Base Class:       <type 'module'>
-    String Form:   <module 'epr' from 'epr.so'>
+    String Form:      <module 'epr' from 'epr.so'>
     Namespace:        Interactive
     File:             /home/antonio/projects/pyepr/epr.so
     Docstring:
@@ -69,19 +69,19 @@ available classes and functions::
         in a product file.
 
         .. _PyEPR: http://avalentino.github.io/pyepr
-        .. _Python: http://www.python.org
+        .. _Python: https://www.python.org
         .. _`EPR API`: https://github.com/bcdev/epr-api
-        .. _ENVISAT: http://envisat.esa.int
-        .. _ESA: http://earth.esa.int
+        .. _ENVISAT: https://envisat.esa.int
+        .. _ESA: https://earth.esa.int
 
     In [3]: epr.__version__, epr.EPR_C_API_VERSION
-    Out[3]: ('0.8.2', '2.3dev')
+    Out[3]: ('0.9.1', '2.3dev')
 
 Docstrings are available for almost all classes, methods and functions in
 the :mod:`epr` and they can be displayed using the :func:`help` python_
 command or the ``?`` IPython_ shortcut as showed above.
 
-.. _python: http://www.python.org
+.. _python: https://www.python.org
 
 Also IPython_ provides a handy tab completion mechanism to automatically
 complete commands or to display available functions and classes::
diff --git a/doc/ndvi_example.txt b/doc/ndvi_example.txt
index 5900b29..86a90be 100644
--- a/doc/ndvi_example.txt
+++ b/doc/ndvi_example.txt
@@ -16,7 +16,7 @@ The program is invoked as follows:
     $ python write_ndvi.py <envisat-oroduct> <output-file>
 
 .. _PyEPR: https://github.com/avalentino/pyepr
-.. _MERIS: http://envisat.esa.int/handbooks/meris
+.. _MERIS: https://earth.esa.int/handbooks/meris/CNTR.html
 .. _`write_ndvi.c`: https://github.com/bcdev/epr-api/blob/master/src/examples/write_ndvi.c
 
 The code have been kept very simple and it consists in a single function
@@ -45,7 +45,7 @@ the block.
 Of course it is possible to use a simple assignment form::
 
     product = open(argv[1])
-    
+
 but in this case the user should take care of manually call::
 
     product.close()
@@ -185,7 +185,7 @@ used to write the NDVI of the pixel n the file in binary format.
         ndvi.tofile(out_stream)
 
 
-.. _ENVISAT: http://envisat.esa.int
-.. _Python: http://www.python.org
+.. _ENVISAT: https://envisat.esa.int
+.. _Python: https://www.python.org
 .. _pythonic: http://www.cafepy.com/article/be_pythonic
 
diff --git a/doc/reference.txt b/doc/reference.txt
index 0f067c8..a75e239 100644
--- a/doc/reference.txt
+++ b/doc/reference.txt
@@ -16,10 +16,10 @@ The raw data access makes it possible to read any data field contained
 in a product file.
 
 .. _PyEPR: https://github.com/avalentino/pyepr
-.. _Python: http://www.python.org
+.. _Python: https://www.python.org
 .. _`EPR API`: https://github.com/bcdev/epr-api
-.. _ENVISAT: http://envisat.esa.int
-.. _ESA: http://earth.esa.int
+.. _ENVISAT: https://envisat.esa.int
+.. _ESA: https://earth.esa.int
 
 
 .. currentmodule:: epr
@@ -48,6 +48,13 @@ Product
       The file's path including the file name
 
 
+   .. attribute:: mode
+
+      String that specifies the mode in which the file is opened
+
+      Possible values: `rb` for read-only mode, `rb+` for read-write mode.
+
+
    .. attribute:: id_string
 
       The product identifier string obtained from the MPH parameter 'PRODUCT'
@@ -82,10 +89,11 @@ Product
 
    .. method:: get_band_at(index)
 
-      Gets the band at the specified position within the product
+      Gets the :class:`Band` at the specified position within the
+      :class:`product`
 
       :param index:
-            the index identifying the position of the band, starting
+            the index identifying the position of the :class:`Band`, starting
             with 0, must not be negative
       :returns:
             the requested :class:`Band` instance, or raises a
@@ -94,21 +102,22 @@ Product
 
    .. method:: get_dataset(name)
 
-      Gets the dataset corresponding to the specified dataset name
+      Gets the :class:`Dataset` corresponding to the specified dataset name
 
       :param name:
-            the dataset name
+            the :class:`Dataset` name
       :returns:
             the requested :class:`Dataset` instance
 
 
    .. method:: get_dataset_at(index)
 
-      Gets the dataset at the specified position within the product
+      Gets the :class:`Dataset` at the specified position within the
+      :class:`Product`
 
       :param index:
-            the index identifying the position of the dataset, starting
-            with 0, must not be negative
+            the index identifying the position of the :class:`Dataset`,
+            starting with 0, must not be negative
       :returns:
             the requested :class:`Dataset`
 
@@ -117,8 +126,8 @@ Product
 
       Gets the :class:`DSD` at the specified position
 
-      Gets the :class:`DSD` (dataset descriptor) at the specified
-      position within the product.
+      Gets the :class:`DSD` (:class:`Dataset` descriptor) at the specified
+      position within the :class:`Product`.
 
       :param index:
             the index identifying the position of the :class:`DSD`,
@@ -127,38 +136,39 @@ Product
             the requested :class:`DSD` instance
 
 
-   .. method:: get_num_bands
+   .. method:: get_num_bands()
 
-      Gets the number of all bands contained in a product
+      Gets the number of all :class:`Band`\ s contained in a :class:`Product`
 
 
-   .. method:: get_num_datasets
+   .. method:: get_num_datasets()
 
-      Gets the number of all datasets contained in a product
+      Gets the number of all :class:`Dataset`\ s contained in a
+      :class:`Product`
 
 
-   .. method:: get_num_dsds
+   .. method:: get_num_dsds()
 
-      Gets the number of all :class:`DSD`\ s (dataset descriptors)
-      contained in the product
+      Gets the number of all :class:`DSD`\ s (:class:`Dataset` descriptors)
+      contained in the :class:`Product`
 
 
-   .. method:: get_scene_height
+   .. method:: get_scene_height()
 
-      Gets the product's scene height in pixels
+      Gets the :class:`Product` scene height in pixels
 
 
-   .. method:: get_scene_width
+   .. method:: get_scene_width()
 
-      Gets the product's scene width in pixels
+      Gets the :class:`Product` scene width in pixels
 
 
-   .. method:: get_mph
+   .. method:: get_mph()
 
       The :class:`Record` representing the main product header (MPH)
 
 
-   .. method:: get_sph
+   .. method:: get_sph()
 
       The :class:`Record` representing the specific product header (SPH)
 
@@ -167,7 +177,7 @@ Product
 
       Calculates a bit-mask raster
 
-      Calculates a bit-mask, composed of flags of the given product
+      Calculates a bit-mask, composed of flags of the given :class:`Product`
       and combined as described in the given bit-mask expression, for
       the a certain dimension and sub-sampling as defined in the
       given raster.
@@ -202,42 +212,48 @@ Product
 
        This method has no effect if the :class:`Product` is already
        closed. Once the :class:`Product` is closed, any operation on
-       it will raise a ValueError.
+       it will raise a :exc:`ValueError`.
 
        As a convenience, it is allowed to call this method more than
        once; only the first call, however, will have an effect.
 
 
+    .. method:: flush()
+
+       Flush the file stream
+
+
    .. rubric:: High level interface methods
 
    .. note::
 
       the following methods are part of the *high level* Python API and
-      do not have any correspondent function in the C API.
+      do not have any corresponding function in the C API.
 
    .. attribute:: closed
 
       True if the :class:`Product` is closed.
 
 
-   .. method:: get_dataset_names
+   .. method:: get_dataset_names()
 
-      Return the list of names of the datasets in the product
+      Return the list of names of the :class:`Dataset`\ s in the
+      :class:`Product`
 
 
-   .. method:: get_band_names
+   .. method:: get_band_names()
 
-      Return the list of names of the bands in the product
+      Return the list of names of the :class:`Band`\ s in the :class:`Product`
 
 
-   .. method:: datasets
+   .. method:: datasets()
 
-      Return the list of dataset in the product
+      Return the list of :class:`Dataset`\ s in the :class:`Product`
 
 
-   .. method:: bands
+   .. method:: bands()
 
-      Return the list of bands in the product
+      Return the list of :class:`Band`\ s in the :class:`Product`
 
 
    .. rubric:: Special methods
@@ -270,85 +286,89 @@ Dataset
 
    .. attribute:: description
 
-      A short description of the band's contents
+      A short description of the :class:`Band` contents
 
 
    .. attribute:: product
 
-      The :class:`Product` instance to which this dataset belongs to
+      The :class:`Product` instance to which this :class:`Dataset` belongs to
 
 
    .. rubric:: Methods
 
-   .. method:: get_name
+   .. method:: get_name()
 
-      Gets the name of the dataset
+      Gets the name of the :class:`Dataset`
 
 
-   .. method:: get_dsd
+   .. method:: get_dsd()
 
-      Gets the dataset descriptor (DSD)
+      Gets the :class:`Dataset` descriptor (:class:`DSD`)
 
 
-   .. method:: get_dsd_name
+   .. method:: get_dsd_name()
 
-      Gets the name of the DSD (dataset descriptor)
+      Gets the name of the :class:`DSD` (:class:`Dataset` descriptor)
 
 
-   .. method:: get_num_records
+   .. method:: get_num_records()
 
-      Gets the number of records of the dataset
+      Gets the number of :class:`Record`\ s of the :class:`Dataset`
 
 
-   .. method:: create_record
+   .. method:: create_record()
 
-      Creates a new record
+      Creates a new :class:`Record`
 
-      Creates a new, empty record with a structure compatible with
-      the dataset. Such a record is typically used in subsequent
-      calls to :meth:`Dataset.read_record`.
+      Creates a new, empty :class:`Record` with a structure compatible with
+      the :class:`Dataset`. Such a :class:`Record` is typically used in
+      subsequent calls to :meth:`Dataset.read_record`.
 
       :returns:
-            the new record instance
+            the new :class:`Record` instance
 
 
    .. method:: read_record(index[, record])
 
-      Reads specified record of the dataset
+      Reads specified :class:`Record` of the :class:`Dataset`
 
-      The record is identified through the given zero-based record
-      index. In order to reduce memory reallocation, a record
-      (pre-)created by the method :meth:`Dataset.create_record` can
-      be passed to this method.
-      Data is then read into this given record.
+      The :class:`Record` is identified through the given zero-based
+      :class:`Record` index. In order to reduce memory reallocation, a
+      :class:`Record` (pre-)created by the method
+      :meth:`Dataset.create_record` can be passed to this method.
+      Data is then read into this given :class:`Record`.
 
-      If no record (``None``) is given, the method initiates a new
+      If no :class:`Record` (``None``) is given, the method initiates a new
       one.
 
-      In both cases, the record in which the data is read into will
-      be  returned.
+      In both cases, the :class:`Record` in which the data is read into will
+      be returned.
 
       :param index:
-            the zero-based record index
+            the zero-based :class:`Record` index (default: 0)
       :param record:
-            a pre-created record to reduce memory reallocation, can be
-            ``None`` (default) to let the function allocate a new
-            record
+            a pre-created :class:`Record` to reduce memory reallocation,
+            can be ``None`` (default) to let the function allocate a new
+            :class:`Record`
       :returns:
             the record in which the data has been read into or raises
             an exception (:exc:`EPRValueError`) if an error occurred
 
+      .. versionchanged:: 0.9
+
+         The *index* parameter now defaults to zero
+
 
    .. rubric:: High level interface methods
 
    .. note::
 
       the following methods are part of the *high level* Python API and
-      do not have any correspondent function in the C API.
+      do not have any corresponding function in the C API.
 
-   .. method:: records
+   .. method:: records()
 
-      Return the list of records contained in the dataset
+      Return the list of :class:`Record`\ s contained in the :class:`Dataset`
 
 
    .. rubric:: Special methods
@@ -373,17 +393,53 @@ Record
    .. seealso:: :class:`Field`
 
 
+   .. rubric:: Attributes
+
+   .. attribute::  dataset_name
+
+      The name of the :class:`Dataset` to which this :class:`Record` belongs to
+
+      .. versionadded:: 0.9
+
+
+   .. attribute:: tot_size
+
+      The total size in bytes of the :class:`Record`
+
+      It includes all data elements of all :class:`Field`\ s of a
+      :class:`Record` in a :class:`Product` file.
+
+      *tot_size* is a derived variable, it is computed at run-time
+      and not stored in the DSD-DB.
+
+      .. versionadded:: 0.9
+
+
+   .. attribute:: index
+
+      Index of the :class:`Record` within the :class:`Dataset`
+
+      It is *None* for empty :class:`Record`\ s (created with
+      :meth:`Dataset.create_record` but still not read) and for *MPH*
+      (see :meth:`Product.get_mph`) and *SPH* (see :meth:`Product.get_sph`)
+      :class:`Record`\ s.
+
+     .. seealso:: :meth:`Dataset.read_record`
+
+     .. versionadded:: 0.9
+
+
    .. rubric:: Methods
 
    .. method:: get_field(name)
 
-      Gets a field specified by name
+      Gets a :class:`Field` specified by name
 
-      The field is here identified through the given name.
-      It contains the field info and all corresponding values.
+      The :class:`Field` is here identified through the given name.
+      It contains the :class:`Field` info and all corresponding values.
 
       :param name:
-            the the name of required field
+            the the name of required :class:`Field`
       :returns:
             the specified :class:`Field` or raises an exception
             (:exc:`EPRValueError`) if an error occurred
@@ -391,25 +447,27 @@ Record
 
    .. method:: get_field_at(index)
 
-      Gets a field at the specified position within the record
+      Gets a :class:`Field` at the specified position within the
+      :class:`Record`
 
       :param index:
-            the zero-based index (position within record) of the field
+            the zero-based index (position within :class:`Record`) of the
+            :class:`Field`
       :returns:
-            the field or raises and exception (:exc:`EPRValueError`)
+            the :class:`Field` or raises and exception (:exc:`EPRValueError`)
             if an error occurred
 
 
-   .. method:: get_num_fields
+   .. method:: get_num_fields()
 
-      Gets the number of fields contained in the record
+      Gets the number of :class:`Field`\ s contained in the :class:`Record`
 
 
    .. method:: print_([ostream])
 
-      Write the record to specified file (default: :data:`sys.stdout`)
+      Write the :class:`Record` to specified file (default: :data:`sys.stdout`)
 
-      This method writes formatted contents of the record to
+      This method writes formatted contents of the :class:`Record` to
       specified *ostream* text file or (default) the ASCII output
       is be printed to standard output (:data:`sys.stdout`)
 
@@ -427,14 +485,14 @@ Record
 
       Write the specified field element to file (default: :data:`sys.stdout`)
 
-      This method writes formatted contents of the specified field
+      This method writes formatted contents of the specified :class:`Field`
       element to the *ostream* text file or (default) the ASCII output
       will be printed to standard output (:data:`sys.stdout`)
 
       :param field_index:
-            the index of field in the record
+            the index of :class:`Field` in the :class:`Record`
       :param element_index:
-            the index of element in the specified field
+            the index of element in the specified :class:`Field`
       :param ostream:
             the (opened) output file object
 
@@ -445,21 +503,28 @@ Record
          instances
 
 
+   .. method:: get_offset()
+
+      :class:`Record` offset in bytes within the :class:`Dataset`
+
+      .. versionadded:: 0.9
+
+
    .. rubric:: High level interface methods
 
    .. note::
 
       the following methods are part of the *high level* Python API and
-      do not have any correspondent function in the C API.
+      do not have any corresponding function in the C API.
 
    .. method:: get_field_names
 
-      Return the list of names of the fields in the product
+      Return the list of names of the :class:`Field`\ s in the :class:`Record`
 
 
-   .. method:: fields
+   .. method:: fields()
 
-      Return the list of fields contained in the record
+      Return the list of :class:`Field`\ s contained in the :class:`Record`
 
 
    .. rubric:: Special methods
@@ -479,67 +544,122 @@ Field
 
    Represents a field within a record
 
-   A field is composed of one or more data elements of one of the
+   A :class:`Field` is composed of one or more data elements of one of the
    types defined in the internal ``field_info`` structure.
 
    .. seealso:: :class:`Record`
 
 
-   .. method:: get_description
+   .. rubric:: Attributes
+
+   .. attribute::  tot_size
+
+        The total size in bytes of all data elements of a :class:`Field`.
+
+        *tot_size* is a derived variable, it is computed at run-time and
+        not stored in the DSD-DB.
+
+      .. versionadded:: 0.9
+
+
+   .. method:: get_description()
 
-      Gets the description of the field
+      Gets the description of the :class:`Field`
 
 
-   .. method:: get_name
+   .. method:: get_name()
 
-      Gets the name of the field
+      Gets the name of the :class:`Field`
 
 
-   .. method:: get_num_elems
+   .. method:: get_num_elems()
 
-      Gets the number of elements of the field
+      Gets the number of elements of the :class:`Field`
 
 
-   .. method:: get_type
+   .. method:: get_type()
 
-      Gets the type of the field
+      Gets the type of the :class:`Field`
 
 
-   .. method:: get_unit
+   .. method:: get_unit()
 
-      Gets the unit of the field
+      Gets the unit of the :class:`Field`
 
 
    .. method:: get_elem([index])
 
-      Field single element access
+      :class:`Field` single element access
 
-      This function is for getting the elements of a field.
+      This function is for getting the elements of a :class:`Field`.
 
       :param index:
             the zero-based index of element to be returned, must not be
             negative. Default: 0.
       :returns:
-            the typed value from given field
+            the typed value from given :class:`Field`
 
 
-   .. method:: get_elems
+   .. method:: get_elems()
 
-      Field array element access
+      :class:`Field` array element access
 
       This function is for getting an array of field elements of the
-      field.
+      :class:`Field`.
 
       :returns:
             the data array (:class:`numpy.ndarray`) having the type of
-            the field
+            the :class:`Field`
+
+      .. versionchanged:: 0.9
+
+         the returned :class:`numpy.ndarray` shares the data buffer with
+         the C :c:type:`Field` structure so any change in its contents is
+         also reflected to the :class:`Filed` object
+
+
+   .. method:: set_elem(elem, [index])
+
+      Set :class:`Field` array element
+
+      This function is for setting an array of field element of the
+      :class:`Field`.
+
+      :param elem:
+            value of the element to set
+      :param index:
+            the zero-based index of element to be set, must not be
+            negative. Default: 0.
+
+      .. note::
+
+         this method does not have any corresponding function in the C API.
+
+      .. versionadded:: 0.9
+
+
+   .. method:: set_elems(elems)
+
+      Set :class:`Field` array elements
+
+      This function is for setting an array of :class:`Field` elements of
+      the :class:`Field`.
+
+      :param elems:
+            np.ndarray of elements to set
+
+      .. note::
+
+         this method does not have any corresponding function in the C API.
+
+      .. versionadded:: 0.9
 
 
    .. method:: print_([ostream])
 
-      Write the field to specified file (default: :data:`sys.stdout`)
+      Write the :class:`Field` to specified file (default: :data:`sys.stdout`)
 
-      This method writes formatted contents of the field to
+      This method writes formatted contents of the :class:`Field` to
       specified *ostream* text file or (default) the ASCII output
       is be printed to standard output (:data:`sys.stdout`)
 
@@ -553,6 +673,13 @@ Field
          instances
 
 
+   .. method:: get_offset()
+
+      Field offset in bytes within the :class:`Record`
+
+      .. versionadded:: 0.9
+
+
    .. rubric:: Special methods
 
    The :class:`Field` class provides a custom implementation of the
@@ -577,52 +704,52 @@ DSD
 
 .. class:: DSD
 
-   Dataset descriptor
+   :class:`Dataset` descriptor
 
    The DSD class contains information about the properties of a
-   dataset and its location within an ENVISAT product file
+   :class:`Dataset` and its location within an ENVISAT :class:`Product` file
 
 
    .. rubric:: Attributes
 
    .. attribute:: ds_name
 
-      The dataset name
+      The :class:`Dataset` name
 
 
    .. attribute:: ds_offset
 
-      The offset of dataset-information the product file
+      The offset of :class:`Dataset` in the :class:`Product` file
 
 
    .. attribute:: ds_size
 
-      The size of dataset-information in dataset product file
+      The size of :class:`Dataset` in the :class:`Product` file
 
 
    .. attribute:: ds_type
 
-      The dataset type descriptor
+      The :class:`Dataset` type descriptor
 
 
    .. attribute:: dsr_size
 
-      The size of dataset record for the given dataset name
+      The size of dataset record for the given :class:`Dataset` name
 
 
    .. attribute:: filename
 
-      The filename in the DDDB with the description of this dataset
+      The filename in the DDDB with the description of this :class:`Dataset`
 
 
    .. attribute:: index
 
-      The index of this DSD (zero-based)
+      The index of this :class:`DSD` (zero-based)
 
 
    .. attribute:: num_dsr
 
-      The number of dataset records for the given dataset name
+      The number of dataset records for the given :class:`Dataset` name
 
 
    .. rubric:: Special methods
@@ -640,10 +767,11 @@ Band
 
 .. class:: Band
 
-   The band of an ENVISAT product
+   The band of an ENVISAT :class:`Product`
 
    The Band class contains information about a band within an ENVISAT
-   product file which has been opened with the :func:`open` function.
+   :class:`Product` file which has been opened with the :func:`open`
+   function.
 
    A new Band instance can be obtained with the :meth:`Product.get_band`
    method.
@@ -660,7 +788,7 @@ Band
 
    .. attribute:: data_type
 
-      The data type of the band's pixels
+      The data type of the :class:`Band` pixels
 
       Possible values are:
 
@@ -672,7 +800,7 @@ Band
 
    .. attribute:: description
 
-      A short description of the band's contents
+      A short description of the :class:`Band` contents
 
 
    .. attribute:: lines_mirrored
@@ -686,14 +814,14 @@ Band
 
    .. attribute:: product
 
-      The :class:`Product` instance to which this band belongs to
+      The :class:`Product` instance to which this :class:`Band` belongs to
 
 
    .. attribute:: sample_model
 
       The sample model operation
 
-      The sample model operation applied to the source dataset for
+      The sample model operation applied to the source :class:`Dataset` for
       getting the correct samples from the MDS (for example MERIS L2).
 
       Possible values are:
@@ -713,7 +841,8 @@ Band
       * ``*`` --> no factor provided (implies scaling_method=*)
       * ``const`` --> a floating point constant
       * ``GADS.field[.field2]`` --> value is provided in global
-        annotation dataset with name `GADS` in field `field``.
+        annotation :class:`Dataset` with name `GADS` in :class:`Field`
+        `field`.
         Optionally a second element index for multiple-element fields
         can be given too
 
@@ -742,47 +871,60 @@ Band
       * ``*`` --> no offset provided (implies scaling_method=*)
       * ``const`` --> a floating point constant
       * ``GADS.field[.field2]` --> value is provided in global
-        annotation dataset with name ``GADS`` in field ``field``.
+        annotation :class:`Dataset` with name ``GADS`` in :class:`Field`
+        ``field``.
         Optionally a second element index for multiple-element fields
         can be given too
 
 
    .. attribute:: spectr_band_index
 
-      The (zero-based) spectral band index
+      The (zero-based) spectral :class:`Band` index
 
-       -1 if this is not a spectral band
+       -1 if this is not a spectral :class:`Band`
 
 
    .. attribute:: unit
 
-      The geophysical unit for the band's pixel values
+      The geophysical unit for the :class:`Band` pixel values
+
+
+   .. attribute:: dataset
+
+      The source :class:`Dataset`
+
+      The source :class:`Dataset` containing the raw data used to create the
+      :class:`Band` pixel values.
+
+      .. versionadded:: 0.9
 
 
    .. rubric:: Methods
 
-   .. method:: get_name
+   .. method:: get_name()
 
-      Gets the name of the band
+      Gets the name of the :class:`Band`
 
 
    .. method:: create_compatible_raster([src_width, src_height, xstep, ystep])
 
-      Creates a raster which is compatible with the data type of the band
+      Creates a :class:`Raster` which is compatible with the data type of
+      the :class:`Band`
 
-      The created raster is used to read the data in it (see
+      The created :class:`Raster` is used to read the data in it (see
       :meth:`Band.read_raster`).
 
-      The raster is defined on the grid of the product, from which the
-      data are read. Spatial subsets and under-sampling are possible)
-      through the parameter of the method.
-
-      A raster is an object that allows direct access to data of a
-      certain portion of the ENVISAT product that are read into the it.
-      Such a portion is called the source. The complete ENVISAT product
-      can be much greater than the source. One can move the raster over
-      the complete ENVISAT product and read in turn different parts
-      (always of the size of the source) of it into the raster.
+      The :class:`Raster` is defined on the grid of the :class:`Product`,
+      from which the data are read. Spatial subsets and under-sampling are
+      possible) through the parameter of the method.
+
+      A :class:`Raster` is an object that allows direct access to data of a
+      certain portion of the ENVISAT :class:`Product` that are read into the
+      it. Such a portion is called the source. The complete ENVISAT
+      :class:`Product` can be much greater than the source.
+      One can move the :class:`Raster` over the complete ENVISAT
+      :class:`Product` and read in turn different parts
+      (always of the size of the source) of it into the :class:`Raster`.
       The source is specified by the parameters *height* and *width*.
 
       A typical example is a processing in blocks. Lets say, a block
@@ -792,52 +934,53 @@ Band
       Another example is a processing of complete image lines. Then,
       my source has a widths of the complete product (for example 1121
       for a MERIS RR product), and a height of 1). One can loop over
-      all blocks read into the raster and process it.
+      all blocks read into the :clasS:`Raster` and process it.
 
       In addition, it is possible to defined a sub-sampling step for
-      a raster. This means, that the source is not read 1:1 into the
-      raster, but that only every 2nd or 3rd pixel is read. This step
-      can be set differently for the across track (source_step_x) and
-      along track (source_step_y) directions.
+      a :class:`Raster`. This means, that the source is not read 1:1 into
+      the :class:`Raster`, but that only every 2nd or 3rd pixel is read.
+      This step can be set differently for the across track (source_step_x)
+      and along track (source_step_y) directions.
 
       :param src_width:
             the width (across track dimension) of the source to be read
-            into the raster. Default: scene width (see
+            into the :class:`Raster`. Default: scene width (see
             :attr:`Product.get_scene_width`)
       :param src_height:
             the height (along track dimension) of the source to be read
-            into the raster. Default: scene height (see
+            into the :class:`Raster`. Default: scene height (see
             :attr:`Product.get_scene_height`)
       :param xstep:
             the sub-sampling step across track of the source when reading
-            into the raster. Default: 1.
+            into the :class:`Raster`. Default: 1.
       :param ystep:
             the sub-sampling step along track of the source when reading
-            into the raster. Default: 1.
+            into the :class:`Raster`. Default: 1.
       :returns:
-            the new raster instance or raises an exception
+            the new :class:`Raster` instance or raises an exception
             (:exc:`EPRValueError`) if an error occurred
 
       .. note::
 
          *src_width* and *src_height* are the dimantion of the of the source
-         area. If one specifies a *step* parameter the resulting raster
-         will have a size that is smaller that the specifies source size::
+         area. If one specifies a *step* parameter the resulting
+         :class:`Raster` will have a size that is smaller that the specifies
+         source size::
 
              raster_size = src_size // step
 
 
    .. method:: read_raster([xoffset, yoffset, raster])
 
-      Reads (geo-)physical values of the band of the specified
+      Reads (geo-)physical values of the :class:`Band` of the specified
       source-region
 
       The source-region is a defined part of the whole ENVISAT
-      product image, which shall be read into a raster.
+      :class:`Product` image, which shall be read into a :class:`Raster`.
       In this routine the co-ordinates are specified, where the
       source-region to be read starts.
       The dimension of the region and the sub-sampling are attributes
-      of the raster into which the data are read.
+      of the :class:`Raster` into which the data are read.
 
       :param xoffset:
             across-track source co-ordinate in pixel co-ordinates
@@ -866,25 +1009,27 @@ Band
    .. note::
 
       the following methods are part of the *high level* Python API and
-      do not have any correspondent function in the C API.
+      do not have any corresponding function in the C API.
 
    .. method:: read_as_array([width, height, xoffset, yoffset, xstep, ystep])
 
       Reads the specified source region as an :class:`numpy.ndarray`
 
       The source-region is a defined part of the whole ENVISAT
-      product image, which shall be read into a raster.
+      :class:`Product` image, which shall be read into a :class:`Raster`.
       In this routine the co-ordinates are specified, where the
       source-region to be read starts.
       The dimension of the region and the sub-sampling are attributes
-      of the raster into which the data are read.
+      of the :class:`Raster` into which the data are read.
 
       :param src_width:
             the width (across track dimension) of the source to be read
-            into the raster. If not provided reads as much as possible
+            into the :class:`Raster`. If not provided reads as much as
+            possible
       :param src_height:
             the height (along track dimension) of the source to be read
-            into the raster, If not provided reads as much as possible
+            into the :class:`Raster`, If not provided reads as much as
+            possible
       :param xoffset:
             across-track source co-ordinate in pixel co-ordinates
             (zero-based) of the upper right corner of the source-region.
@@ -895,10 +1040,10 @@ Band
             Default 0.
       :param xstep:
             the sub-sampling step across track of the source when
-            reading into the raster. Default: 1
+            reading into the :class:`Raster`. Default: 1
       :param ystep:
             the sub-sampling step along track of the source when
-            reading into the raster. Default: 1
+            reading into the :class:`Raster`. Default: 1
       :returns:
             the :class:`numpy.ndarray` instance in which data are read
 
@@ -931,7 +1076,7 @@ Raster
 
    .. attribute:: data_type
 
-      The data type of the band's pixels
+      The data type of the :class:`Band` pixels
 
       All ``E_TID_*`` types are possible
 
@@ -975,10 +1120,11 @@ Raster
 
       .. note::
 
-         the :class:`Raster` objects do not have a field named *data* in the
-         corresponding C structure. The *EPR_SRaster* C structure have a
-         field named *buffer* that is a raw pointer to the data buffer and
-         it is not exposed as such in the Python API.
+         the :class:`Raster` objects do not have a :class:`Field` named
+         *data* in the corresponding C structure. The *EPR_SRaster* C
+         structure have a :class:`Field` named *buffer* that is a raw
+         pointer to the data buffer and it is not exposed as such in the
+         Python API.
 
 
    .. rubric:: Methods
@@ -988,7 +1134,7 @@ Raster
       Single pixel access
 
       This function is for getting the values of the elements of a
-      raster (i.e. pixel)
+      :class:`Raster` (i.e. pixel)
 
       :param x:
             the (zero-based) X coordinate of the pixel
@@ -998,20 +1144,20 @@ Raster
             the typed value at the given co-ordinate
 
 
-   .. method:: get_elem_size
+   .. method:: get_elem_size()
 
-      The size in byte of a single element (sample) of this raster's
+      The size in byte of a single element (sample) of this :class:`Raster`
       buffer
 
 
-   .. method:: get_height
+   .. method:: get_height()
 
-      Gets the raster's height in pixels
+      Gets the :class:`Raster` height in pixels
 
 
-   .. method:: get_width
+   .. method:: get_width()
 
-      Gets the raster's width in pixels
+      Gets the :class:`Raster` width in pixels
 
 
    .. rubric:: Special methods
@@ -1039,16 +1185,20 @@ EPRTime
 Functions
 ---------
 
-.. function:: open(filename)
+.. function:: open(filename, mode='rb')
 
    Opens the ENVISAT product
 
-   Opens the ENVISAT product file with the given file path, reads MPH,
-   SPH and all DSDs, organized the table with parameter of line length
-   and tie points number.
+   Opens the ENVISAT :class:`Product` file with the given file path,
+   reads MPH, SPH and all :class:`DSD`\ s, organized the table with
+   parameter of line length and tie points number.
 
    :param product_file_path:
-        the path to the ENVISAT product file
+        the path to the ENVISAT :class:`Product` file
+   :param mode:
+        string that specifies the mode in which the file is opened.
+        Allowed values: `rb` for read-only mode, `rb+` for read-write
+        mode. Default: mode=`rb`.
    :returns:
         the :class:`Product` instance representing the specified
         product. An exception (:exc:`exceptions.ValueError`) is raised
@@ -1066,53 +1216,60 @@ Functions
    .. seealso :class:`Product`
 
 
-.. function:: data_type_id_to_str
+.. function:: data_type_id_to_str(type_id)
 
    Gets the 'C' data type string for the given data type
 
 
-.. function:: get_data_type_size
+.. function:: get_data_type_size(type_id)
 
    Gets the size in bytes for an element of the given data type
 
 
-.. function:: get_sample_model_name
+.. function:: get_numpy_dtype(type_id)
+
+   Return the numpy data-type specified EPR type ID
+
+   .. versionadded:: 0.9
+
+
+.. function:: get_sample_model_name(model)
 
    Return the name of the specified sample model
 
 
-.. function:: get_scaling_method_name
+.. function:: get_scaling_method_name(method)
 
    Return the name of the specified scaling method
 
 
 .. function:: create_raster(data_type, src_width, src_height[, xstep, ystep])
 
-   Creates a raster of the specified data type
+   Creates a :class:`Raster` of the specified data type
 
    This function can be used to create any type of raster, e.g. for
    later use as a bit-mask.
 
    :param data_type:
-        the type of the data to stored in the raster, must be one of
-        E_TID_*.
+        the type of the data to stored in the :class:`Raster`, must be one
+        of E_TID_*.
 
         .. seealso:: `Data type Identifiers`_
 
    :param src_width:
         the width (across track dimension) of the source to be read
-        into the raster.
+        into the :class:`Raster`.
         See description of :meth:`Band.create_compatible_raster`
    :param src_height:
         the height (along track dimension) of the source to be read
-        into the raster.
+        into the :class:`Raster`.
         See description of :meth:`Band.create_compatible_raster`
    :param xstep:
         the sub-sampling step across track of the source when reading
-        into the raster. Default: 1.
+        into the :class:`Raster`. Default: 1.
    :param ystep:
         the sub-sampling step along track of the source when reading
-        into the raster. Default: 1.
+        into the :class:`Raster`. Default: 1.
    :returns:
         the new :class:`Raster` instance
 
@@ -1121,24 +1278,24 @@ Functions
 
 .. function:: create_bitmask_raster(src_width, src_height[, xstep, ystep])
 
-   Creates a raster to be used for reading bitmasks
+   Creates a :class:`Raster` to be used for reading bitmasks
 
-   The raster returned always is of type ``byte``.
+   The :class:`Raster` returned always is of type ``byte``.
 
    :param src_width:
         the width (across track dimension) of the source to be read
-        into the raster
+        into the :class:`Raster`
    :param src_height:
         the height (along track dimension) of the source to be read
-        into the raster
+        into the :class:`Raster`
    :param xstep:
         the sub-sampling step across track of the source when reading
-        into the raster. Default: 1.
+        into the :class:`Raster`. Default: 1.
    :param ystep:
         the sub-sampling step along track of the source when reading
-        into the raster. Default: 1.
+        into the :class:`Raster`. Default: 1.
    :returns:
-        the new raster instance or raises an exception
+        the new :class:`Raster` instance or raises an exception
         (:exc:`EPRValueError`) if an error occurred
 
    .. seealso:: the description of :meth:`Band.create_compatible_raster`
@@ -1185,13 +1342,6 @@ Data
    Version string of PyEPR
 
 
-.. data:: __revision__
-
-   PyEPR revision (currently it is the same as :data:`__version__`)
-
-   .. deprecated:: 7.2
-
-
 .. data:: EPR_C_API_VERSION
 
    Version string of the wrapped `EPR API`_ C library
@@ -1240,3 +1390,4 @@ Scaling Methods
 .. data:: E_SMID_LOG
 
    Logarithmic pixel scaling
+
diff --git a/doc/tutorials.txt b/doc/tutorials.txt
index bb62cf7..e68d26b 100644
--- a/doc/tutorials.txt
+++ b/doc/tutorials.txt
@@ -8,4 +8,5 @@ Tutorials
    bands_example
    bitmask_example
    ndvi_example
+   update_example
 
diff --git a/doc/update_example.txt b/doc/update_example.txt
new file mode 100644
index 0000000..ad08e02
--- /dev/null
+++ b/doc/update_example.txt
@@ -0,0 +1,205 @@
+Update :class:`Field` elements
+------------------------------
+
+.. highlight:: ipython
+
+The EPR C API has been designed to provide read-only features.
+
+PyEPR_ provides and extra capability consisting in the possibility to
+modify (*update*) an existing ENVISAT_ :class:`Product`.
+
+Lets consider a MERIS Level 2 low resolution product (
+`MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1`).
+It has a :class:`Band` named `water_vapour` containing the water vapour
+content at a specific position.
+
+One can load water vapour and compute an histogram using the following
+instructions:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 10-15
+
+
+The resulting histogram can be plot using Matplotlib_:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 17-21
+
+
+.. figure:: images/water_vapour_histogram_01.*
+   :width: 60%
+
+   Histogram of the original water vapour content
+
+
+The actual values of the water vapour content :class:`Band` are computed
+starting form data stored in the `Vapour_Content` :class:`Dataset` using
+scaling factors contained in the `Scaling_Factor_GADS` :class:`Dataset`.
+In particular :class:`Field`\ s `sf_wvapour` and  `off_wvapour` are used::
+
+
+    In [21]: dataset = product.get_dataset('Scaling_Factor_GADS')
+
+    In [22]: print(dataset)
+    epr.Dataset(Scaling_Factor_GADS) 1 records
+
+    sf_cl_opt_thick = 1.000000
+    sf_cloud_top_press = 4.027559
+    sf_wvapour = 0.100000
+    off_cl_opt_thick = -1.000000
+    off_cloud_top_press = -4.027559
+    off_wvapour = -0.100000
+    spare_1 = <<unknown data type>>
+
+
+Now suppose that for some reason one needs to update the `sf_wvapour` scaling
+factor for the water vapour content.
+Changing the scaling factor, of course, will change all values in the
+`water_vapour` :class:`Band`.
+
+The change can be performed using the :meth:`Field.set_elem` and
+:meth:`Field.set_elems` methods of :class:`Field` objects:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 24-32
+
+
+Now the `sf_wvapour` scaling factor has been changed and it is possible to
+compute and display the histogram of modified data in the `water_vapour`
+:class:`Band`:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 34-48
+
+
+.. figure:: images/water_vapour_histogram_02.*
+   :width: 60%
+
+   Histogram of the water vapour content (original and modified)
+
+
+Figure above shows the two different histograms, original data in blue and
+modified data in red, demonstrating the effect of the change of the scaling
+factor.
+
+The new map of water vapour is showed in the following picture:
+
+
+.. figure:: images/modified_water_vapour.*
+
+   Modified water vapour content map
+
+
+.. important::
+
+   it is important to stress that it is necessary to close and re-open the
+   :class:`Product` in order to see changes in the scaling factors applied
+   to the `water_vapour`:class:`Band` data.
+
+   This is a limitation of the current implementation that could be removed
+   in future versions of the PyEPR_ package.
+
+
+It has been showed that changing the `sf_wvapour` scaling factor modifies
+all values of the `water_vapour` :class:`Band`.
+
+Now suppose that one needs to modify only a specific area.
+It can be done changing the contents of the `Vapour_Content` :class:`Dataset`.
+
+The :class:`Dataset` size can be read form the :class:`Product`::
+
+    In [44]: product.get_scene_height(), product.get_scene_width()
+    Out[44]: (149, 281)
+
+
+while information about the fields in each record can be retrieved
+introspecting the :class:`Record` object::
+
+    In [49]: record = dataset.read_record(0)
+
+    In [50]: record.get_field_names()
+    Out[50]: ['dsr_time', 'quality_flag', 'wvapour_cont_pix']
+
+    In [51]: record.get_field('wvapour_cont_pix')
+    Out[51]: epr.Field("wvapour_cont_pix") 281 uchar elements
+
+
+So the name of the :class:`Field` we need to change is the `wvapour_cont_pix`,
+and its index is `2`.
+
+It is possible to change a small box inside the :class:`Dataset` as follows:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 60-68
+
+
+Please note that when one modifies the content of a :class:`Dataset` he/she
+should also take into account id the corresponding band has lines mirrored
+or not::
+
+    In [59]: band = p.get_band('water_vapour')
+
+    In [60]: band.lines_mirrored
+    Out[60]: True
+
+
+Finally the :class:`Product` can be re-opened to load and display the
+modified :class:`Band`:
+
+
+.. raw:: latex
+
+    \fvset{fontsize=\footnotesize}
+
+.. literalinclude:: examples/update_elements.py
+   :language: python
+   :lines: 71-82
+
+
+.. figure:: images/modified_water_vapour_with_box.*
+
+   Modified water vapour content map with zeroed box
+
+
+Of course values in the box that has been set to zero in the :class:`Dataset`
+are transformed according to the scaling factor and offset parameters
+associated to `water_vapour` :class:`Band`.
+
+The complete code of the example can be found at
+:download:`examples/update_elements.py`.
+
+
+.. _PyEPR: https://github.com/avalentino/pyepr
+.. _ENVISAT: https://envisat.esa.int
+.. _Matplotlib: http://matplotlib.org
+
diff --git a/doc/usermanual.txt b/doc/usermanual.txt
index f7c3d97..21b05c9 100644
--- a/doc/usermanual.txt
+++ b/doc/usermanual.txt
@@ -32,11 +32,18 @@ Parameters" record to the standard output::
     print(record)
     product.close()
 
+Since version 0.9 PyEPR_ also include *update* features that are not
+available in the EPR C API.
+The user can open a product in update mode ('rb+') and call the
+:meth:`epr.Field.set_elem` and :meth:`epr.Field.set_elems` methods of
+:class:`epr.Field` class to update its elements and write changes to disk.
+
+
 .. _PyEPR: https://github.com/avalentino/pyepr
-.. _Python: http://www.python.org
+.. _Python: https://www.python.org
 .. _`EPR API`: https://github.com/bcdev/epr-api
-.. _ENVISAT: http://envisat.esa.int
-.. _ESA: http://earth.esa.int
+.. _ENVISAT: https://envisat.esa.int
+.. _ESA: https://earth.esa.int
 
 
 Requirements
@@ -50,11 +57,9 @@ correctly installed and configured:
 * `EPR API`_ >= 2.2 (optional, since PyEPR 0.7 the source tar-ball comes
   with a copy of the PER C API sources)
 * a reasonably updated C compiler [#]_ (build only)
-* Cython_ >= 0.13 [#]_ (optional and build only)
+* Cython_ >= 0.19 [#]_ (optional and build only)
 * unittest2_ (only required for Python < 2.7)
 
-.. note:: in order to build PyEPR_ for Python3_ it is required Cython_ >= 0.15
-
 
 .. [#] PyEPR_ has been developed and tested with gcc_ 4.
 .. [#] The source tarball of official releases also includes the C extension
@@ -159,29 +164,36 @@ with the system `EPR API`_ C library::
 
 .. _pip: https://pypi.python.org/pypi/pip
 .. _easy_install: https://pypi.python.org/pypi/setuptools#using-setuptools-and-easyinstall
-.. _distutils: http://docs.python.org/distutils
+.. _distutils: https://docs.python.org/3/distutils
 
 
 Testing
 -------
 
 PyEPR_ package comes with a complete test suite but in order to run it
-the ENVISAT sample product used for testing
-MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1__
-have to be downloaded from the ESA_ website, saved in the :file:`test`
+the ENVISAT sample product used for testing,
+MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1__,
+have to be downloaded from the ESA_ website, saved in the :file:`tests`
 directory and decompressed.
 
-__ http://earth.esa.int/services/sample_products/meris/LRC/L2/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz
+__ https://earth.esa.int/services/sample_products/meris/LRC/L2/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz
+
+.. note::
+
+    since PyEPR_ 0.9.0 the test product is downloaded and decompressed
+    automatically by the test script if not already available.
+    In this case a working Internet connection is expected to be available
+    when the test suite is run.
 
 On GNU Linux platforms the following shell commands can be used::
 
-    $ cd pyepr-0.X/test
-    $ wget http://earth.esa.int/services/sample_products/meris/LRC/L2/\
+    $ cd pyepr-X.Y.Z/tests
+    $ wget https://earth.esa.int/services/sample_products/meris/LRC/L2/\
       MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz
     $ gunzip MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz
 
 After installation the test suite can be run using the following command
-in the :file:`test` directory::
+in the :file:`tests` directory::
 
     $ python test_all.py
 
@@ -209,14 +221,14 @@ Memory management
 .. highlight:: python
 
 Being Python_ a very high level language uses have never to worry about
-memory allocation/deallocation. They simply have to instantiate objects::
+memory allocation/de-allocation. They simply have to instantiate objects::
 
     product = epr.Product('filename.N1')
 
 and use them freely.
 
 Objects are automatically destroyed when there are no more references to
-them and memory is deallocated automatically.
+them and memory is de-allocated automatically.
 
 Even better, each object holds a reference to other objects it depends
 on so the user never have to worry about identifiers validity or about
@@ -434,6 +446,45 @@ that ensure that the product is closed as soon as the program exits the
 ``with`` block.
 
 
-.. _`special method`: http://docs.python.org/reference/datamodel.html
+Update support
+--------------
+
+It is not possible to create new ENVISAT_ products for scratch with the
+EPR API. Indeed EPR means "**E**\ NVISAT **P**\ roduct **R**\ eaeder".
+Anyway, since version 0.9, PyEPR_ also include basic *update* features.
+This means that, while it is still not possible to create new
+:class:`Products`, the user can *update* existing ones changing the
+contents of any :class:`Field` in any record with the only exception of
+MPH and SPH :class:`Field`\s.
+
+The user can open a product in update mode ('rb+')::
+
+    product = epr.open('ASA_IMS_ ... _4650.N1', 'rb+')
+
+and update the :class:`epr.Field` element at a specific index::
+
+    field.set_elem(new_value, index)
+
+or also update all elements ol the :class:`epr.Field` in one shot::
+
+    field.set_elems(new_values)
+
+.. note::
+
+   unfortunately there are some limitations to the update support.
+   Many of the internal structures of the EPR C API are loaded when the
+   :class:`Product` is opened and are not automatically updated when the
+   :meth:`epr.Field.set_elem` and :meth:`epr.Field.set_elems` methods are
+   called.
+   In particular :class:`epr.Band`\ s contents may depend on several
+   :class:`epr.Field` values, e.g. the contents of `Scaling_Factor_GADS`
+   :class:`epr.Dataset`.
+   For this reason the user may need to close and re-open the
+   :class:`epr.Product` in order to have all changes effectively applied.
+
+   .. seealso:: :doc:`update_example`.
+
+.. _`special method`: https://docs.python.org/3/reference/datamodel.html
 .. _pythonic: http://www.cafepy.com/article/be_pythonic
-.. _`context manager`: http://docs.python.org/3/library/stdtypes.html#context-manager-types
+.. _`context manager`: https://docs.python.org/3/library/stdtypes.html#context-manager-types
+
diff --git a/requirements.txt b/requirements.txt
index 6798762..e110eac 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
 numpy>=1.5
-cython
+cython>=0.19
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index 41d4307..6b7ca9c
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2011-2014, Antonio Valentino <antonio.valentino at tiscali.it>
+# Copyright (C) 2011-2015, Antonio Valentino <antonio.valentino at tiscali.it>
 #
 # This file is part of PyEPR.
 #
@@ -25,67 +25,150 @@ import re
 import sys
 import glob
 
-from distutils.core import setup
-from distutils.extension import Extension
 
-source = []
-libraries = []
-include_dirs = []
-eprsrcdir = None
-
-
-try:
-    from Cython.Distutils import build_ext
-    sources = [os.path.join('src', 'epr.pyx')]
-except ImportError:
-    from distutils.command.build_ext import build_ext
-    sources = [os.path.join('src', 'epr.c')]
-
-
-from numpy.distutils.misc_util import get_numpy_include_dirs
-include_dirs.extend(get_numpy_include_dirs())
-
-
-# command line arguments management
-for arg in list(sys.argv):
-    if arg.startswith('--epr-api-src='):
-        eprsrcdir = os.path.expanduser(arg.split('=')[1])
-        if eprsrcdir.lower() == 'none':
-            eprsrcdir = False
-        sys.argv.remove(arg)
+def get_version(filename):
+    with open(filename) as fd:
+        data = fd.read()
 
+    mobj = re.search(
+        '''^__version__\s*=\s*(?P<q>['"])(?P<version>\d+(\.\d+)*.*)(?P=q)''',
+        data, re.MULTILINE)
 
-# check for local epr-api sources
-if eprsrcdir is None:
-    if os.path.isdir('epr-api-src'):
-        eprsrcdir = 'epr-api-src'
+    return mobj.group('version')
 
 
-if eprsrcdir:
-    include_dirs.append(eprsrcdir)
-    sources.extend(glob.glob(os.path.join(eprsrcdir, 'epr_*.c')))
-    #libraries.append('m')
-    print('using EPR C API sources at "{}"'.format(eprsrcdir))
-else:
-    libraries.append('epr_api')
-    print('using pre-built dynamic library for EPR C API')
+def get_use_setuptools():
+    use_setuptools = os.environ.get('USE_SETUPTOOLS', True)
+    if str(use_setuptools).lower() in ('false', 'off', 'n', 'no', '0'):
+        use_setuptools = False
+        print('USE_SETUPTOOLS: {}'.format(use_setuptools))
+    else:
+        use_setuptools = True
 
+    return use_setuptools
 
-def get_version():
-    filename = os.path.join('src', 'epr.pyx')
-    with open(filename) as fd:
-        data = fd.read()
 
-    mobj = re.search(
-        r"^__version__\s*=\s*\'(?P<version>\d+(\.\d+)*(\+|(\-)?dev)?)\'",
-        data, re.MULTILINE)
+try:
+    if not get_use_setuptools():
+        raise ImportError
 
-    return mobj.group('version')
+    from setuptools import setup, Extension
+    HAVE_SETUPTOOLS = True
+except ImportError:
+    from distutils.core import setup
+    from distutils.extension import Extension
+    HAVE_SETUPTOOLS = False
+print('HAVE_SETUPTOOLS: {0}'.format(HAVE_SETUPTOOLS))
 
 
-setup(
+try:
+    from Cython.Build import cythonize
+    HAVE_CYTHON = True
+except ImportError:
+    HAVE_CYTHON = False
+print('HAVE_CYTHON: {0}'.format(HAVE_CYTHON))
+
+
+# @COMPATIBILITY: Extension is an old style class in Python 2
+class PyEprExtension(Extension, object):
+    def __init__(self, *args, **kwargs):
+        self._include_dirs = []
+        eprsrcdir = kwargs.pop('eprsrcdir', None)
+
+        super(PyEprExtension, self).__init__(*args, **kwargs)
+
+        self.sources.extend(self._extra_sources(eprsrcdir))
+        self.setup_requires_cython = False
+
+    def _extra_sources(self, eprsrcdir=None):
+        sources = []
+
+        # check for local epr-api sources
+        if eprsrcdir is None:
+            default_eprapisrc = os.path.join('epr-api-src')
+            if os.path.isdir(default_eprapisrc):
+                eprsrcdir = default_eprapisrc
+
+        if eprsrcdir:
+            print('using EPR C API sources at "{0}"'.format(eprsrcdir))
+            self._include_dirs.append(eprsrcdir)
+            sources.extend(glob.glob(os.path.join(eprsrcdir, 'epr_*.c')))
+
+        else:
+            print('using pre-built dynamic libraray for EPR C API')
+            if 'epr_api' not in self.libraries:
+                self.libraries.append('epr_api')
+
+        sources = sorted(set(sources).difference(self.sources))
+
+        return sources
+
+    @property
+    def include_dirs(self):
+        from numpy.distutils.misc_util import get_numpy_include_dirs
+        includes = set(get_numpy_include_dirs()).difference(self._include_dirs)
+        return self._include_dirs + sorted(includes)
+
+    @include_dirs.setter
+    def include_dirs(self, value):
+        self._include_dirs = value
+
+    # disable setuptools automatic conversion
+    def _convert_pyx_sources_to_lang(self):
+        pass
+
+    def convert_pyx_sources_to_lang(self):
+        lang = self.language or ''
+        target_ext = '.cpp' if lang.lower() == 'c++' else '.c'
+
+        sources = []
+        for src in self.sources:
+            if src.endswith('.pyx'):
+                csrc = re.sub('.pyx$', target_ext, src)
+                if os.path.exists(csrc):
+                    sources.append(csrc)
+                else:
+                    self.setup_requires_cython = True
+                    sources.append(src)
+
+        if not self.setup_requires_cython:
+            self.sources = sources
+
+        return self.setup_requires_cython
+
+
+def get_extension():
+    # command line arguments management
+    eprsrcdir = None
+    for arg in list(sys.argv):
+        if arg.startswith('--epr-api-src='):
+            eprsrcdir = os.path.expanduser(arg.split('=')[1])
+            if eprsrcdir.lower() == 'none':
+                eprsrcdir = False
+            sys.argv.remove(arg)
+            break
+
+    ext = PyEprExtension(
+        'epr',
+        sources=[os.path.join('src', 'epr.pyx')],
+        # libraries=['m'],
+        # define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'),],
+        eprsrcdir=eprsrcdir,
+    )
+
+    # @NOTE: uses the HAVE_CYTHON global variable
+    if HAVE_CYTHON:
+        extlist = cythonize([ext])
+        ext = extlist[0]
+    else:
+        ext.convert_pyx_sources_to_lang()
+
+    return ext
+
+
+config = dict(
     name='pyepr',
-    version=get_version(),
+    version=get_version(os.path.join('src', 'epr.pyx')),
     author='Antonio Valentino',
     author_email='antonio.valentino at tiscali.it',
     url='http://avalentino.github.com/pyepr',
@@ -100,10 +183,10 @@ the data either on a geophysical (decoded, ready-to-use pixel samples)
 or on a raw data layer. The raw data access makes it possible to read
 any data field contained in a product file.
 
-.. _Python: http://www.python.org
+.. _Python: https://www.python.org
 .. _`EPR API`: https://github.com/bcdev/epr-api
-.. _ENVISAT: http://envisat.esa.int
-.. _ESA: http://earth.esa.int
+.. _ENVISAT: https://envisat.esa.int
+.. _ESA: https://earth.esa.int
 ''',
     download_url='http://pypi.python.org/pypi/pyepr',
     classifiers=[
@@ -117,7 +200,12 @@ any data field contained in a product file.
         'Operating System :: POSIX',
         'Programming Language :: Python',
         'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
         'Programming Language :: Cython',
         'Topic :: Software Development :: Libraries',
         'Topic :: Scientific/Engineering',
@@ -125,15 +213,23 @@ any data field contained in a product file.
     ],
     platforms=['any'],
     license='GPL3',
-    cmdclass={'build_ext': build_ext},
-    ext_modules=[
-        Extension(
-            'epr',
-            sources=sources,
-            include_dirs=include_dirs,
-            libraries=libraries,
-            #define_macros=[('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION'),],
-        ),
-    ],
-    requires=['numpy'],
+    requires=['numpy'],     # XXX: check
 )
+
+
+def setup_package():
+    ext = get_extension()
+    config['ext_modules'] = [ext]
+
+    if HAVE_SETUPTOOLS:
+        config['test_suite'] = 'tests'
+        config.setdefault('setup_requires', []).append('numpy>=1.5')
+        config.setdefault('install_requires', []).append('numpy>=1.5')
+        if ext.setup_requires_cython:
+            config['setup_requires'].append('cython>=0.19')
+
+    setup(**config)
+
+
+if __name__ == '__main__':
+    setup_package()
diff --git a/src/epr.pxd b/src/epr.pxd
new file mode 100644
index 0000000..999f8b3
--- /dev/null
+++ b/src/epr.pxd
@@ -0,0 +1,370 @@
+# -*- coding: utf-8 -*-
+
+# PyEPR - Python bindings for ENVISAT Product Reader API
+#
+# Copyright (C) 2011-2015, Antonio Valentino <antonio.valentino at tiscali.it>
+#
+# This file is part of PyEPR.
+#
+# PyEPR is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PyEPR is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PyEPR.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from libc.stdio cimport FILE
+
+
+cdef extern from 'epr_api.h' nogil:
+    char* EPR_PRODUCT_API_VERSION_STR
+
+    ctypedef int               epr_boolean
+    ctypedef unsigned char     uchar
+    ctypedef unsigned short    ushort
+    ctypedef unsigned int      uint
+    ctypedef unsigned long     ulong
+
+    ctypedef EPR_Time          EPR_STime
+    #ctypedef EPR_FlagDef       EPR_SFlagDef
+    ctypedef EPR_PtrArray      EPR_SPtrArray
+    ctypedef EPR_FieldInfo     EPR_SFieldInfo
+    ctypedef EPR_RecordInfo    EPR_SRecordInfo
+    ctypedef EPR_Field         EPR_SField
+    ctypedef EPR_Record        EPR_SRecord
+    ctypedef EPR_DSD           EPR_SDSD
+    ctypedef EPR_Raster        EPR_SRaster
+    ctypedef EPR_BandId        EPR_SBandId
+    ctypedef EPR_DatasetId     EPR_SDatasetId
+    ctypedef EPR_ProductId     EPR_SProductId
+    ctypedef EPR_ErrCode       EPR_EErrCode
+    ctypedef EPR_LogLevel      EPR_ELogLevel
+    ctypedef EPR_SampleModel   EPR_ESampleModel
+    ctypedef EPR_ScalingMethod EPR_EScalingMethod
+    ctypedef EPR_DataTypeId    EPR_EDataTypeId
+
+    ctypedef int EPR_Magic
+
+    int EPR_MAGIC_PRODUCT_ID
+    int EPR_MAGIC_DATASET_ID
+    int EPR_MAGIC_BAND_ID
+    int EPR_MAGIC_RECORD
+    int EPR_MAGIC_FIELD
+    int EPR_MAGIC_RASTER
+    int EPR_MAGIC_FLAG_DEF
+
+    enum EPR_ErrCode:
+        e_err_none = 0
+        e_err_null_pointer = 1
+        e_err_illegal_arg = 2
+        e_err_illegal_state = 3
+        e_err_out_of_memory = 4
+        e_err_index_out_of_range = 5
+        e_err_illegal_conversion = 6
+        e_err_illegal_data_type = 7
+        e_err_file_not_found = 101
+        e_err_file_access_denied = 102
+        e_err_file_read_error = 103
+        e_err_file_write_error = 104
+        e_err_file_open_failed = 105
+        e_err_file_close_failed = 106
+        e_err_api_not_initialized = 201
+        e_err_invalid_product_id = 203
+        e_err_invalid_record = 204
+        e_err_invalid_band = 205
+        e_err_invalid_raster = 206
+        e_err_invalid_dataset_name = 207
+        e_err_invalid_field_name = 208
+        e_err_invalid_record_name = 209
+        e_err_invalid_product_name = 210
+        e_err_invalid_band_name = 211
+        e_err_invalid_data_format = 212
+        e_err_invalid_value = 213
+        e_err_invalid_keyword_name = 214
+        e_err_unknown_endian_order = 216
+        e_err_flag_not_found = 301
+        e_err_invalid_ddbb_format = 402
+
+    enum EPR_DataTypeId:
+        e_tid_unknown = 0
+        e_tid_uchar = 1
+        e_tid_char = 2
+        e_tid_ushort = 3
+        e_tid_short = 4
+        e_tid_uint = 5
+        e_tid_int = 6
+        e_tid_float = 7
+        e_tid_double = 8
+        e_tid_string = 11
+        e_tid_spare = 13
+        e_tid_time = 21
+
+    enum EPR_LogLevel:
+        e_log_debug = -1
+        e_log_info = 0
+        e_log_warning = 1
+        e_log_error = 2
+
+    enum EPR_SampleModel:
+        e_smod_1OF1 = 0
+        e_smod_1OF2 = 1
+        e_smod_2OF2 = 2
+        e_smod_3TOI = 3
+        e_smod_2TOF = 4
+
+    enum EPR_ScalingMethod:
+        e_smid_non = 0
+        e_smid_lin = 1
+        e_smid_log = 2
+
+    struct EPR_Time:
+        int  days
+        uint seconds
+        uint microseconds
+
+    struct EPR_FlagDef:
+        EPR_Magic magic
+        char* name
+        uint bit_mask
+        char* description
+
+    struct EPR_PtrArray:
+        unsigned int capacity
+        unsigned int length
+        void** elems
+
+    struct EPR_Field:
+        EPR_Magic magic
+        EPR_FieldInfo* info
+        void* elems
+
+    struct EPR_Record:
+        EPR_Magic magic
+        EPR_RecordInfo* info
+        uint num_fields
+        EPR_Field** fields
+
+    struct EPR_DSD:
+        EPR_Magic magic
+        int index
+        char* ds_name
+        char* ds_type
+        char* filename
+        uint ds_offset
+        uint ds_size
+        uint num_dsr
+        uint dsr_size
+
+    struct EPR_Raster:
+        EPR_Magic magic
+        EPR_DataTypeId data_type
+        uint elem_size
+        uint source_width
+        uint source_height
+        uint source_step_x
+        uint source_step_y
+        uint raster_width
+        uint raster_height
+        void* buffer
+
+    struct EPR_ProductId:
+        EPR_Magic magic
+        char* file_path
+        FILE* istream
+        uint  tot_size
+        uint  scene_width
+        uint  scene_height
+        char* id_string
+        EPR_Record* mph_record
+        EPR_Record* sph_record
+        EPR_PtrArray* dsd_array
+        EPR_PtrArray* record_info_cache
+        EPR_PtrArray* param_table
+        EPR_PtrArray* dataset_ids
+        EPR_PtrArray* band_ids
+        int meris_iodd_version
+
+    struct EPR_DatasetId:
+        EPR_Magic magic
+        EPR_ProductId* product_id
+        char* dsd_name
+        EPR_DSD* dsd
+        char* dataset_name
+        #struct RecordDescriptor* record_descriptor
+        EPR_SRecordInfo* record_info
+        char* description
+
+    struct EPR_DatasetRef:
+        EPR_DatasetId* dataset_id
+        int field_index             # -1 if not used
+        int elem_index              # -1 if not used
+
+    struct EPR_BandId:
+        EPR_Magic magic
+        EPR_ProductId* product_id
+        char* band_name
+        int spectr_band_index
+        EPR_DatasetRef dataset_ref
+        EPR_SampleModel sample_model
+        EPR_DataTypeId data_type
+        EPR_ScalingMethod scaling_method
+        float scaling_offset
+        float scaling_factor
+        char* bm_expr
+        EPR_SPtrArray* flag_coding
+        char* unit
+        char* description
+        epr_boolean lines_mirrored
+
+    # @TODO: improve logging and error management (--> custom handlers)
+    # logging and error handling function pointers
+    ctypedef void (*EPR_FLogHandler)(EPR_ELogLevel, char*)
+    ctypedef void (*EPR_FErrHandler)(EPR_EErrCode, char*)
+
+    # logging
+    int epr_set_log_level(EPR_ELogLevel)
+    void epr_set_log_handler(EPR_FLogHandler)
+    void epr_log_message(EPR_ELogLevel, char*)
+
+    # error handling
+    void epr_set_err_handler(EPR_FErrHandler)
+    EPR_EErrCode epr_get_last_err_code()
+    const char* epr_get_last_err_message()
+    void epr_clear_err()
+
+    # API initialization/finalization
+    int epr_init_api(EPR_ELogLevel, EPR_FLogHandler, EPR_FErrHandler)
+    void epr_close_api()
+
+    # DATATYPE
+    uint epr_get_data_type_size(EPR_EDataTypeId)
+    const char* epr_data_type_id_to_str(EPR_EDataTypeId)
+
+    # open products
+    EPR_SProductId* epr_open_product(char*)
+
+    # PRODUCT
+    int epr_close_product(EPR_SProductId*)
+    uint epr_get_scene_width(EPR_SProductId*)
+    uint epr_get_scene_height(EPR_SProductId*)
+    uint epr_get_num_datasets(EPR_SProductId*)
+    EPR_SDatasetId* epr_get_dataset_id_at(EPR_SProductId*, uint)
+    EPR_SDatasetId* epr_get_dataset_id(EPR_SProductId*, char*)
+    uint epr_get_num_dsds(EPR_SProductId*)
+    EPR_SDSD* epr_get_dsd_at(EPR_SProductId*, uint)
+    EPR_SRecord* epr_get_mph(EPR_SProductId*)
+    EPR_SRecord* epr_get_sph(EPR_SProductId*)
+
+    uint epr_get_num_bands(EPR_SProductId*)
+    EPR_SBandId* epr_get_band_id_at(EPR_SProductId*, uint)
+    EPR_SBandId* epr_get_band_id(EPR_SProductId*, char*)
+    int epr_read_bitmask_raster(EPR_SProductId*, char*, int, int, EPR_SRaster*)
+
+    # DATASET
+    const char* epr_get_dataset_name(EPR_SDatasetId*)
+    const char* epr_get_dsd_name(EPR_SDatasetId*)
+    uint epr_get_num_records(EPR_SDatasetId*)
+    EPR_SDSD* epr_get_dsd(EPR_SDatasetId*)
+    EPR_SRecord* epr_create_record(EPR_SDatasetId*)
+    EPR_SRecord* epr_read_record(EPR_SDatasetId*, uint, EPR_SRecord*)
+
+    # RECORD
+    void epr_free_record(EPR_SRecord*)
+    uint epr_get_num_fields(EPR_SRecord*)
+    void epr_print_record(EPR_SRecord*, FILE*)
+    void epr_print_element(EPR_SRecord*, uint, uint, FILE*)
+    void epr_dump_record(EPR_SRecord*)
+    void epr_dump_element(EPR_SRecord*, uint, uint)
+    EPR_SField* epr_get_field(EPR_SRecord*, char*)
+    EPR_SField* epr_get_field_at(EPR_SRecord*, uint)
+
+    # FIELD
+    void epr_print_field(EPR_SField*, FILE*)
+    void epr_dump_field(EPR_SField*)
+
+    const char* epr_get_field_unit(EPR_SField*)
+    const char* epr_get_field_description(EPR_SField*)
+    uint epr_get_field_num_elems(EPR_SField*)
+    const char* epr_get_field_name(EPR_SField*)
+    EPR_EDataTypeId epr_get_field_type(EPR_SField*)
+
+    char epr_get_field_elem_as_char(EPR_SField*, uint)
+    uchar epr_get_field_elem_as_uchar(EPR_SField*, uint)
+    short epr_get_field_elem_as_short(EPR_SField*, uint)
+    ushort epr_get_field_elem_as_ushort(EPR_SField*, uint)
+    int epr_get_field_elem_as_int(EPR_SField*, uint)
+    uint epr_get_field_elem_as_uint(EPR_SField*, uint)
+    float epr_get_field_elem_as_float(EPR_SField*, uint)
+    double epr_get_field_elem_as_double(EPR_SField*, uint)
+    const char* epr_get_field_elem_as_str(EPR_SField*)
+    EPR_STime* epr_get_field_elem_as_mjd(EPR_SField*)
+
+    const char* epr_get_field_elems_char(EPR_SField*)
+    uchar* epr_get_field_elems_uchar(EPR_SField*)
+    short* epr_get_field_elems_short(EPR_SField*)
+    ushort* epr_get_field_elems_ushort(EPR_SField*)
+    int* epr_get_field_elems_int(EPR_SField*)
+    uint* epr_get_field_elems_uint(EPR_SField*)
+    float* epr_get_field_elems_float(EPR_SField*)
+    double* epr_get_field_elems_double(EPR_SField*)
+
+    uint epr_copy_field_elems_as_ints(EPR_SField*, int*, uint)
+    uint epr_copy_field_elems_as_uints(EPR_SField*, uint*, uint)
+    uint epr_copy_field_elems_as_floats(EPR_SField*, float*, uint)
+    uint epr_copy_field_elems_as_doubles(EPR_SField*, double*, uint)
+
+    # BAND
+    const char* epr_get_band_name(EPR_SBandId*)
+    EPR_SRaster* epr_create_compatible_raster(EPR_SBandId*, uint, uint, uint,
+                                              uint)
+    int epr_read_band_raster(EPR_SBandId*, int, int, EPR_SRaster*)
+
+    # RASTER
+    void epr_free_raster(EPR_SRaster*)
+    uint epr_get_raster_width(EPR_SRaster*)
+    uint epr_get_raster_height(EPR_SRaster*)
+    uint epr_get_raster_elem_size(EPR_SRaster*)
+
+    uint epr_get_pixel_as_uint(EPR_SRaster*, int, int)
+    int epr_get_pixel_as_int(EPR_SRaster*, int, int)
+    float epr_get_pixel_as_float(EPR_SRaster*, int, int)
+    double epr_get_pixel_as_double(EPR_SRaster*, int, int)
+
+    void* epr_get_raster_elem_addr(EPR_SRaster*, uint)
+    void* epr_get_raster_pixel_addr(EPR_SRaster*, uint, uint)
+    void* epr_get_raster_line_addr(EPR_SRaster*, uint)
+
+    EPR_SRaster* epr_create_raster(EPR_EDataTypeId, uint, uint, uint, uint)
+    EPR_SRaster* epr_create_bitmask_raster(uint, uint, uint, uint)
+
+
+# @IMPORTANT:
+#
+#   the following structures are not part of the public API.
+#   It is not ensured that relative header files are available at build time
+#   (e.g. debian does not install them), so structures are relìplicated here.
+#   It is fundamental to ensure that structures defined here are kept totally
+#   in sync with the one defined in EPR C API.
+
+# epr_field.h
+ctypedef struct EPR_FieldInfo:
+    char* name
+    EPR_EDataTypeId data_type_id
+    uint num_elems
+    char* unit
+    char* description
+    uint tot_size
+
+
+# epr_record.h
+ctypedef struct EPR_RecordInfo:
+    char* dataset_name
+    EPR_SPtrArray* field_infos
+    uint tot_size
diff --git a/src/epr.pyx b/src/epr.pyx
index 8eef770..8db8d94 100644
--- a/src/epr.pyx
+++ b/src/epr.pyx
@@ -2,7 +2,7 @@
 
 # PyEPR - Python bindings for ENVISAT Product Reader API
 #
-# Copyright (C) 2011-2014, Antonio Valentino <antonio.valentino at tiscali.it>
+# Copyright (C) 2011-2015, Antonio Valentino <antonio.valentino at tiscali.it>
 #
 # This file is part of PyEPR.
 #
@@ -41,339 +41,40 @@ in a product file.
 
 '''
 
-__version__ = '0.8.2'
-__revision__ = __version__  # deprecated
+__version__ = '0.9.1'
 
-
-cdef extern from *:
-    ctypedef char const_char 'const char'
-    ctypedef void const_void 'const void'
-
-
-from libc cimport string as cstring
+from libc cimport errno
 from libc cimport stdio
 from libc.stdio cimport FILE
+from libc cimport string as cstring
+
 
+IF UNAME_SYSNAME == 'Windows':
+    cdef extern from "stdio.h" nogil:
+        int fileno "_fileno" (FILE*)
+ELSE:
+    # posix
+    cdef extern from "stdio.h" nogil:
+        int fileno(FILE*)
 
-cdef extern from 'stdio.h' nogil:
-    FILE* fdopen(int, char *mode)
-
-
-cdef extern from 'epr_api.h' nogil:
-    char* EPR_PRODUCT_API_VERSION_STR
-
-    ctypedef int            epr_boolean
-    ctypedef unsigned char  uchar
-    ctypedef unsigned short ushort
-    ctypedef unsigned int   uint
-    ctypedef unsigned long  ulong
-
-    enum EPR_ErrCode:
-        e_err_none = 0
-        e_err_null_pointer = 1
-        e_err_illegal_arg = 2
-        e_err_illegal_state = 3
-        e_err_out_of_memory = 4
-        e_err_index_out_of_range = 5
-        e_err_illegal_conversion = 6
-        e_err_illegal_data_type = 7
-        e_err_file_not_found = 101
-        e_err_file_access_denied = 102
-        e_err_file_read_error = 103
-        e_err_file_write_error = 104
-        e_err_file_open_failed = 105
-        e_err_file_close_failed = 106
-        e_err_api_not_initialized = 201
-        e_err_invalid_product_id = 203
-        e_err_invalid_record = 204
-        e_err_invalid_band = 205
-        e_err_invalid_raster = 206
-        e_err_invalid_dataset_name = 207
-        e_err_invalid_field_name = 208
-        e_err_invalid_record_name = 209
-        e_err_invalid_product_name = 210
-        e_err_invalid_band_name = 211
-        e_err_invalid_data_format = 212
-        e_err_invalid_value = 213
-        e_err_invalid_keyword_name = 214
-        e_err_unknown_endian_order = 216
-        e_err_flag_not_found = 301
-        e_err_invalid_ddbb_format = 402
-
-    enum EPR_DataTypeId:
-        e_tid_unknown = 0
-        e_tid_uchar = 1
-        e_tid_char = 2
-        e_tid_ushort = 3
-        e_tid_short = 4
-        e_tid_uint = 5
-        e_tid_int = 6
-        e_tid_float = 7
-        e_tid_double = 8
-        e_tid_string = 11
-        e_tid_spare = 13
-        e_tid_time = 21
-
-    enum EPR_LogLevel:
-        e_log_debug = -1
-        e_log_info = 0
-        e_log_warning = 1
-        e_log_error = 2
-
-    enum EPR_SampleModel:
-        e_smod_1OF1 = 0
-        e_smod_1OF2 = 1
-        e_smod_2OF2 = 2
-        e_smod_3TOI = 3
-        e_smod_2TOF = 4
-
-    enum EPR_ScalingMethod:
-        e_smid_non = 0
-        e_smid_lin = 1
-        e_smid_log = 2
-
-    struct EPR_Time:
-        int  days
-        uint seconds
-        uint microseconds
-
-    struct EPR_FlagDef:
-        #EPR_Magic magic
-        char* name
-        uint bit_mask
-        char* description
-
-    struct EPR_Field:
-        #EPR_Magic magic
-        #EPR_FieldInfo* info
-        void* elems
-
-    struct EPR_Record:
-        #EPR_Magic magic
-        #EPR_RecordInfo* info
-        uint num_fields
-        EPR_Field** fields
-
-    struct EPR_DSD:
-        #EPR_Magic magic
-        int index
-        char* ds_name
-        char* ds_type
-        char* filename
-        uint ds_offset
-        uint ds_size
-        uint num_dsr
-        uint dsr_size
-
-    struct EPR_Raster:
-        #EPR_Magic magic
-        EPR_DataTypeId data_type
-        uint elem_size
-        uint source_width
-        uint source_height
-        uint source_step_x
-        uint source_step_y
-        uint raster_width
-        uint raster_height
-        void* buffer
-
-    struct EPR_ProductId:
-        #EPR_Magic magic
-        char* file_path
-        FILE* istream
-        uint  tot_size
-        uint  scene_width
-        uint  scene_height
-        char* id_string
-        EPR_Record* mph_record
-        EPR_Record* sph_record
-        #EPR_PtrArray* dsd_array
-        #EPR_PtrArray* record_info_cache
-        #EPR_PtrArray* param_table
-        #EPR_PtrArray* dataset_ids
-        #EPR_PtrArray* band_ids
-        int meris_iodd_version
-
-    struct EPR_DatasetId:
-        #EPR_Magic magic
-        EPR_ProductId* product_id
-        char* dsd_name
-        EPR_DSD* dsd
-        char* dataset_name
-        #struct RecordDescriptor* record_descriptor
-        #EPR_SRecordInfo* record_info
-        char* description
-
-    struct EPR_DatasetRef:
-        EPR_DatasetId* dataset_id
-        int field_index             # -1 if not used
-        int elem_index              # -1 if not used
-
-    struct EPR_BandId:
-        #EPR_Magic magic
-        EPR_ProductId* product_id
-        char* band_name
-        int spectr_band_index
-        EPR_DatasetRef dataset_ref
-        EPR_SampleModel sample_model
-        EPR_DataTypeId data_type
-        EPR_ScalingMethod scaling_method
-        float scaling_offset
-        float scaling_factor
-        char* bm_expr
-        #EPR_SPtrArray* flag_coding
-        char* unit
-        char* description
-        epr_boolean lines_mirrored
-
-    ctypedef EPR_ErrCode       EPR_EErrCode
-    ctypedef EPR_LogLevel      EPR_ELogLevel
-    ctypedef EPR_SampleModel   EPR_ESampleModel
-    ctypedef EPR_ScalingMethod EPR_EScalingMethod
-    ctypedef EPR_DataTypeId    EPR_EDataTypeId
-    ctypedef EPR_ProductId     EPR_SProductId
-    ctypedef EPR_DatasetId     EPR_SDatasetId
-    ctypedef EPR_BandId        EPR_SBandId
-    ctypedef EPR_Raster        EPR_SRaster
-    ctypedef EPR_Record        EPR_SRecord
-    ctypedef EPR_Field         EPR_SField
-    ctypedef EPR_DSD           EPR_SDSD
-    ctypedef EPR_Time          EPR_STime
-
-    # @TODO: improve logging and error management (--> custom handlers)
-    # logging and error handling function pointers
-    ctypedef void (*EPR_FLogHandler)(EPR_ELogLevel, char*)
-    ctypedef void (*EPR_FErrHandler)(EPR_EErrCode, char*)
-
-    # logging
-    int epr_set_log_level(EPR_ELogLevel)
-    void epr_set_log_handler(EPR_FLogHandler)
-    void epr_log_message(EPR_ELogLevel, char*)
-
-    # error handling
-    void epr_set_err_handler(EPR_FErrHandler)
-    EPR_EErrCode epr_get_last_err_code()
-    const_char* epr_get_last_err_message()
-    void epr_clear_err()
-
-    # API initialization/finalization
-    int epr_init_api(EPR_ELogLevel, EPR_FLogHandler, EPR_FErrHandler)
-    void epr_close_api()
-
-    # DATATYPE
-    uint epr_get_data_type_size(EPR_EDataTypeId)
-    const_char* epr_data_type_id_to_str(EPR_EDataTypeId)
-
-    # open products
-    EPR_SProductId* epr_open_product(char*)
-
-    # PRODUCT
-    int epr_close_product(EPR_SProductId*)
-    uint epr_get_scene_width(EPR_SProductId*)
-    uint epr_get_scene_height(EPR_SProductId*)
-    uint epr_get_num_datasets(EPR_SProductId*)
-    EPR_SDatasetId* epr_get_dataset_id_at(EPR_SProductId*, uint)
-    EPR_SDatasetId* epr_get_dataset_id(EPR_SProductId*, char*)
-    uint epr_get_num_dsds(EPR_SProductId*)
-    EPR_SDSD* epr_get_dsd_at(EPR_SProductId*, uint)
-    EPR_SRecord* epr_get_mph(EPR_SProductId*)
-    EPR_SRecord* epr_get_sph(EPR_SProductId*)
-
-    uint epr_get_num_bands(EPR_SProductId*)
-    EPR_SBandId* epr_get_band_id_at(EPR_SProductId*, uint)
-    EPR_SBandId* epr_get_band_id(EPR_SProductId*, char*)
-    int epr_read_bitmask_raster(EPR_SProductId*, char*, int, int, EPR_SRaster*)
-
-    # DATASET
-    const_char* epr_get_dataset_name(EPR_SDatasetId*)
-    const_char* epr_get_dsd_name(EPR_SDatasetId*)
-    uint epr_get_num_records(EPR_SDatasetId*)
-    EPR_SDSD* epr_get_dsd(EPR_SDatasetId*)
-    EPR_SRecord* epr_create_record(EPR_SDatasetId*)
-    EPR_SRecord* epr_read_record(EPR_SDatasetId*, uint, EPR_SRecord*)
-
-    # RECORD
-    void epr_free_record(EPR_SRecord*)
-    uint epr_get_num_fields(EPR_SRecord*)
-    void epr_print_record(EPR_SRecord*, FILE*)
-    void epr_print_element(EPR_SRecord*, uint, uint, FILE*)
-    void epr_dump_record(EPR_SRecord*)
-    void epr_dump_element(EPR_SRecord*, uint, uint)
-    EPR_SField* epr_get_field(EPR_SRecord*, char*)
-    EPR_SField* epr_get_field_at(EPR_SRecord*, uint)
-
-    # FIELD
-    void epr_print_field(EPR_SField*, FILE*)
-    void epr_dump_field(EPR_SField*)
-
-    const_char* epr_get_field_unit(EPR_SField*)
-    const_char* epr_get_field_description(EPR_SField*)
-    uint epr_get_field_num_elems(EPR_SField*)
-    const_char* epr_get_field_name(EPR_SField*)
-    EPR_EDataTypeId epr_get_field_type(EPR_SField*)
-
-    char epr_get_field_elem_as_char(EPR_SField*, uint)
-    uchar epr_get_field_elem_as_uchar(EPR_SField*, uint)
-    short epr_get_field_elem_as_short(EPR_SField*, uint)
-    ushort epr_get_field_elem_as_ushort(EPR_SField*, uint)
-    int epr_get_field_elem_as_int(EPR_SField*, uint)
-    uint epr_get_field_elem_as_uint(EPR_SField*, uint)
-    float epr_get_field_elem_as_float(EPR_SField*, uint)
-    double epr_get_field_elem_as_double(EPR_SField*, uint)
-    const_char* epr_get_field_elem_as_str(EPR_SField*)
-    EPR_STime* epr_get_field_elem_as_mjd(EPR_SField*)
-
-    const_char* epr_get_field_elems_char(EPR_SField*)
-    uchar* epr_get_field_elems_uchar(EPR_SField*)
-    short* epr_get_field_elems_short(EPR_SField*)
-    ushort* epr_get_field_elems_ushort(EPR_SField*)
-    int* epr_get_field_elems_int(EPR_SField*)
-    uint* epr_get_field_elems_uint(EPR_SField*)
-    float* epr_get_field_elems_float(EPR_SField*)
-    double* epr_get_field_elems_double(EPR_SField*)
-
-    uint epr_copy_field_elems_as_ints(EPR_SField*, int*, uint)
-    uint epr_copy_field_elems_as_uints(EPR_SField*, uint*, uint)
-    uint epr_copy_field_elems_as_floats(EPR_SField*, float*, uint)
-    uint epr_copy_field_elems_as_doubles(EPR_SField*, double*, uint)
-
-    # BAND
-    const_char* epr_get_band_name(EPR_SBandId*)
-    EPR_SRaster* epr_create_compatible_raster(EPR_SBandId*, uint, uint, uint,
-                                              uint)
-    int epr_read_band_raster(EPR_SBandId*, int, int, EPR_SRaster*)
-
-    # RASTER
-    void epr_free_raster(EPR_SRaster*)
-    uint epr_get_raster_width(EPR_SRaster*)
-    uint epr_get_raster_height(EPR_SRaster*)
-    uint epr_get_raster_elem_size(EPR_SRaster*)
-
-    uint epr_get_pixel_as_uint(EPR_SRaster*, int, int)
-    int epr_get_pixel_as_int(EPR_SRaster*, int, int)
-    float epr_get_pixel_as_float(EPR_SRaster*, int, int)
-    double epr_get_pixel_as_double(EPR_SRaster*, int, int)
-
-    void* epr_get_raster_elem_addr(EPR_SRaster*, uint)
-    void* epr_get_raster_pixel_addr(EPR_SRaster*, uint, uint)
-    void* epr_get_raster_line_addr(EPR_SRaster*, uint)
-
-    EPR_SRaster* epr_create_raster(EPR_EDataTypeId, uint, uint, uint, uint)
-    EPR_SRaster* epr_create_bitmask_raster(uint, uint, uint, uint)
 
+from epr cimport *
 
 from cpython.version cimport PY_MAJOR_VERSION
 from cpython.object cimport PyObject_AsFileDescriptor
+from cpython.weakref cimport PyWeakref_NewRef
+
 cimport numpy as np
 np.import_array()
 
+import os
 import sys
-import weakref
 from collections import namedtuple
 
 import numpy as np
 
-cdef int PY3 = (PY_MAJOR_VERSION >= 3)
+cdef bint PY3 = (PY_MAJOR_VERSION >= 3)
+cdef bint SWAP_BYTES = (sys.byteorder == 'little')
 
 # internal utils
 _DEFAULT_FS_ENCODING = sys.getfilesystemencoding()
@@ -395,6 +96,11 @@ cdef inline str _to_str(b, encoding='UTF-8'):
 
 # utils
 EPRTime = namedtuple('EPRTime', ('days', 'seconds', 'microseconds'))
+MJD = np.dtype([
+    ('days', 'i%d' % sizeof(int)),
+    ('seconds', 'u%d' % sizeof(uint)),
+    ('microseconds', 'u%d' % sizeof(uint)),
+])
 
 EPR_C_API_VERSION = _to_str(EPR_PRODUCT_API_VERSION_STR, 'ascii')
 
@@ -424,30 +130,71 @@ E_SMID_NON = e_smid_non
 E_SMID_LIN = e_smid_lin
 E_SMID_LOG = e_smid_log
 
+# EPR magic IDs
+_EPR_MAGIC_PRODUCT_ID = EPR_MAGIC_PRODUCT_ID
+_EPR_MAGIC_DATASET_ID = EPR_MAGIC_DATASET_ID
+_EPR_MAGIC_BAND_ID = EPR_MAGIC_BAND_ID
+_EPR_MAGIC_RECORD = EPR_MAGIC_RECORD
+_EPR_MAGIC_FIELD = EPR_MAGIC_FIELD
+_EPR_MAGIC_RASTER = EPR_MAGIC_RASTER
+_EPR_MAGIC_FLAG_DEF = EPR_MAGIC_FLAG_DEF
+
 
 cdef np.NPY_TYPES _epr_to_numpy_type_id(EPR_DataTypeId epr_type):
-    if epr_type == E_TID_UCHAR:
+    if epr_type == e_tid_uchar:
         return np.NPY_UBYTE
-    if epr_type == E_TID_CHAR:
+    if epr_type == e_tid_char:
         return np.NPY_BYTE
-    if epr_type == E_TID_USHORT:
+    if epr_type == e_tid_ushort:
         return np.NPY_USHORT
-    if epr_type == E_TID_SHORT:
+    if epr_type == e_tid_short:
         return np.NPY_SHORT
-    if epr_type == E_TID_UINT:
+    if epr_type == e_tid_uint:
         return np.NPY_UINT
-    if epr_type == E_TID_INT:
+    if epr_type == e_tid_int:
         return np.NPY_INT
-    if epr_type == E_TID_FLOAT:
+    if epr_type == e_tid_float:
         return np.NPY_FLOAT
-    if epr_type == E_TID_DOUBLE:
+    if epr_type == e_tid_double:
         return np.NPY_DOUBLE
-    if epr_type == E_TID_STRING:
+    if epr_type == e_tid_string:
         return np.NPY_STRING
 
     return np.NPY_NOTYPE
 
 
+_DTYPE_MAP = {
+    E_TID_UCHAR:   np.uint8,
+    E_TID_CHAR:    np.int8,
+    E_TID_USHORT:  np.uint16,
+    E_TID_SHORT:   np.int16,
+    E_TID_UINT:    np.uint32,
+    E_TID_INT:     np.int32,
+    E_TID_FLOAT:   np.float32,
+    E_TID_DOUBLE:  np.float64,
+    E_TID_STRING:  np.bytes_,
+    E_TID_TIME:    MJD,
+    E_TID_SPARE:   None,
+    E_TID_UNKNOWN: None,
+}
+
+
+_METHOD_MAP = {
+    E_SMID_NON: 'NONE',
+    E_SMID_LIN: 'LIN',
+    E_SMID_LOG: 'LOG',
+}
+
+
+_MODEL_MAP = {
+    E_SMOD_1OF1: '1OF1',
+    E_SMOD_1OF2: '1OF2',
+    E_SMOD_2OF2: '2OF2',
+    E_SMOD_3TOI: '3TOI',
+    E_SMOD_2TOF: '2TOF',
+}
+
+
 class EPRError(Exception):
     '''EPR API error'''
 
@@ -511,8 +258,9 @@ cdef FILE* pyepr_get_file_stream(object ostream) except NULL:
         if fileno == -1:
             raise TypeError('bad output stream')
         else:
-            fstream = fdopen(fileno, 'w')
+            fstream = stdio.fdopen(fileno, 'w')
             if fstream is NULL:
+                errno.errno = 0
                 raise TypeError('invalid ostream')
 
     return fstream
@@ -528,7 +276,7 @@ cdef class _CLib:
     def __cinit__(self, *args, **kwargs):
         cdef bytes msg
 
-        # @TODO:check
+        # @TODO: check
         #if EPR_C_API_VERSION != '2.2':
         #    raise ImportError('C library version not supported: "%s"' %
         #                                                    EPR_C_API_VERSION)
@@ -566,7 +314,20 @@ cdef class EprObject:
                                                     self.__class__.__name__)
 
 
-def get_data_type_size(EPR_EDataTypeId type_id):
+cpdef get_numpy_dtype(EPR_EDataTypeId type_id):
+    '''get_numpy_dtype(epr_type)
+
+    Return the numpy datatype specified EPR type ID
+
+    '''
+
+    try:
+        return _DTYPE_MAP[type_id]
+    except KeyError:
+        raise ValueError('invalid EPR type ID: %d' % type_id)
+
+
+cpdef uint get_data_type_size(EPR_EDataTypeId type_id):
     '''get_data_type_size(type_id)
 
     Gets the size in bytes for an element of the given data type
@@ -576,7 +337,7 @@ def get_data_type_size(EPR_EDataTypeId type_id):
     return epr_get_data_type_size(type_id)
 
 
-def data_type_id_to_str(EPR_EDataTypeId type_id):
+cpdef str data_type_id_to_str(EPR_EDataTypeId type_id):
     '''data_type_id_to_str(type_id)
 
     Gets the 'C' data type string for the given data type
@@ -588,42 +349,28 @@ def data_type_id_to_str(EPR_EDataTypeId type_id):
     return _to_str(type_id_str, 'ascii')
 
 
-def get_scaling_method_name(method):
+cpdef str get_scaling_method_name(EPR_ScalingMethod method):
     '''get_scaling_method_name(method)
 
     Return the name of the specified scaling method
 
     '''
 
-    mmap = {
-        E_SMID_NON: 'NONE',
-        E_SMID_LIN: 'LIN',
-        E_SMID_LOG: 'LOG',
-    }
-
     try:
-        return mmap[method]
+        return _METHOD_MAP[method]
     except KeyError:
         raise ValueError('invalid scaling method: "%s"' % method)
 
 
-def get_sample_model_name(model):
+cpdef str get_sample_model_name(EPR_SampleModel model):
     '''get_sample_model_name(model)
 
     Return the name of the specified sample model
 
     '''
 
-    mmap = {
-        E_SMOD_1OF1: '1OF1',
-        E_SMOD_1OF2: '1OF2',
-        E_SMOD_2OF2: '2OF2',
-        E_SMOD_3TOI: '3TOI',
-        E_SMOD_2TOF: '2TOF',
-    }
-
     try:
-        return mmap[model]
+        return _MODEL_MAP[model]
     except KeyError:
         raise ValueError('invalid sample model: "%s"' % model)
 
@@ -746,6 +493,14 @@ cdef class DSD(EprObject):
         else:
             return NotImplemented
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
 
 cdef new_dsd(EPR_SDSD* ptr, object parent=None):
     if ptr is NULL:
@@ -775,6 +530,39 @@ cdef class Field(EprObject):
     cdef inline check_closed_product(self):
         self._parent.check_closed_product()
 
+    cdef inline _check_write_mode(self):
+        self._parent._check_write_mode()
+
+    cdef long _get_offset(self, bint absolute=0):
+        cdef bint found = 0
+        cdef int i = 0
+        cdef int num_fields_in_record = 0
+        cdef long offset = 0
+        cdef const char* name = NULL
+        cdef const EPR_Field* field = NULL
+        cdef const EPR_FieldInfo* info = NULL
+        cdef const EPR_Record* record = NULL
+
+        info = <EPR_FieldInfo*>self._ptr.info
+        name = info.name
+        record = self._parent._ptr
+        num_fields_in_record = epr_get_num_fields(record)
+        for i in range(num_fields_in_record):
+            field = epr_get_field_at(record, i)
+            info = <EPR_FieldInfo*>field.info
+            if info.name == name:
+                found = 1
+                break
+            offset += info.tot_size
+
+        if not found:
+            offset = None
+        elif absolute:
+            offset += self._parent._get_offset(absolute)
+
+        return offset
+
+
     def print_(self, ostream=None):
         '''print_(self, ostream=None)
 
@@ -814,7 +602,7 @@ cdef class Field(EprObject):
 
         '''
 
-        cdef const_char* unit = NULL
+        cdef const char* unit = NULL
 
         self.check_closed_product()
 
@@ -947,93 +735,232 @@ cdef class Field(EprObject):
 
         '''
 
-        # @NOTE: internal C const pointer is not shared with numpy
-        cdef const_void* buf
-        cdef size_t num_elems
-        cdef size_t i
+        cdef void* buf = NULL
+        cdef int nd = 1
+        cdef np.npy_intp shape[1]
         cdef np.ndarray out
+        cdef EPR_Time* t = NULL
+        cdef np.NPY_TYPES dtype
 
         self.check_closed_product()
 
-        num_elems = epr_get_field_num_elems(self._ptr)
+        shape[0] = epr_get_field_num_elems(self._ptr)
         etype = epr_get_field_type(self._ptr)
         msg = 'Filed("%s") elems pointer is null' % self.get_name()
 
+        if etype == e_tid_time:
+            if shape[0] != 1:
+                raise ValueError(
+                    'unexpected number of elements: %d' % shape[0])
+            t = <EPR_Time*>epr_get_field_elem_as_mjd(self._ptr)
+            if t is NULL:
+                pyepr_null_ptr_error(msg)
+
+            out = np.ndarray(1, MJD)
+            out[0]['days'] = t.days
+            out[0]['seconds'] = t.seconds
+            out[0]['microseconds'] = t.microseconds
+
+            return out
+
         if etype == e_tid_uchar:
+            dtype = np.NPY_UBYTE
             buf = <uchar*>epr_get_field_elems_uchar(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.byte)
-            for i in range(num_elems):
-                out[i] = (<uchar*>buf)[i]
         elif etype == e_tid_char:
+            dtype = np.NPY_BYTE
             buf = <char*>epr_get_field_elems_char(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.byte)
-            for i in range(num_elems):
-                out[i] = (<char*>buf)[i]
         elif etype == e_tid_ushort:
+            dtype = np.NPY_USHORT
             buf = <ushort*>epr_get_field_elems_ushort(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.ushort)
-            for i in range(num_elems):
-                out[i] = (<ushort*>buf)[i]
         elif etype == e_tid_short:
+            dtype = np.NPY_SHORT
             buf = <short*>epr_get_field_elems_short(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.short)
-            for i in range(num_elems):
-                out[i] = (<short*>buf)[i]
         elif etype == e_tid_uint:
+            dtype = np.NPY_UINT
             buf = <uint*>epr_get_field_elems_uint(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.uint)
-            for i in range(num_elems):
-                out[i] = (<uint*>buf)[i]
         elif etype == e_tid_int:
+            dtype = np.NPY_INT
             buf = <int*>epr_get_field_elems_int(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.int)
-            for i in range(num_elems):
-                out[i] = (<int*>buf)[i]
         elif etype == e_tid_float:
+            dtype = np.NPY_FLOAT
             buf = <float*>epr_get_field_elems_float(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.float32)
-            for i in range(num_elems):
-                out[i] = (<float*>buf)[i]
         elif etype == e_tid_double:
+            dtype = np.NPY_DOUBLE
             buf = <double*>epr_get_field_elems_double(self._ptr)
             if buf is NULL:
                 pyepr_null_ptr_error(msg)
-            out = np.ndarray(num_elems, np.double)
-            for i in range(num_elems):
-                out[i] = (<double*>buf)[i]
+        elif etype == e_tid_string:
+            if shape[0] != 1:
+                raise ValueError(
+                    'unexpected number of elements: %d' % shape[0])
+            nd = 0
+            dtype = np.NPY_STRING
+            buf = <char*>epr_get_field_elem_as_str(self._ptr)
+            if buf is NULL:
+                pyepr_null_ptr_error(msg)
+        #elif etype == e_tid_unknown:
+        #    pass
+        #elif etype = e_tid_spare:
+        #    pass
         else:
             raise ValueError('invalid field type')
 
+        out = np.PyArray_SimpleNewFromData(nd, shape, dtype, <void*>buf)
+        #np.PyArray_CLEARFLAG(out, NPY_ARRAY_WRITEABLE)  # new in numpy 1.7
+        # Make the ndarray keep a reference to this object
+        np.set_array_base(out, self)
+
         return out
 
+    cdef _set_elems(self, np.ndarray elems, uint index=0):
+        cdef Record record
+        cdef Dataset dataset
+        cdef Product product
+        cdef FILE* istream
+        cdef size_t ret
+        cdef size_t nelems
+        cdef size_t elemsize
+        cdef size_t datasize
+        cdef long file_offset
+        cdef long field_offset
+        cdef char* buf
+        cdef EPR_DataTypeId etype = epr_get_field_type(self._ptr)
+
+        dtype = _DTYPE_MAP[etype]
+
+        elems = elems.astype(dtype)
+
+        record = self._parent
+        dataset = record._parent
+        product = dataset._parent
+        istream = product._ptr.istream
+
+        nelems = elems.size
+        elemsize = epr_get_data_type_size(etype)
+        datasize = elemsize * nelems
+        field_offset = index * elemsize
+        file_offset = self._get_offset(absolute=1)
+        buf = <char*>self._ptr.elems + field_offset
+
+        cstring.memcpy(<void*>buf, <const void*>elems.data, datasize)
+
+        if SWAP_BYTES:
+            elems = elems.byteswap()
+
+        with nogil:
+            stdio.fseek(istream, file_offset + field_offset, stdio.SEEK_SET)
+            ret = stdio.fwrite(elems.data, elemsize, nelems,
+                               product._ptr.istream)
+        if ret != nelems:
+            raise IOError(
+                'write error: %d of %d bytes written' % (ret, datasize))
+
+    def set_elem(self, elem, uint index=0):
+        '''set_elem(self, elem, index=0)
+
+        Set Field array element
+
+        This function is for setting an array of field element of the
+        field.
+
+        :param elem:
+            value of the element to set
+        :param index:
+            the zero-based index of element to be set, must not be
+            negative. Default: 0.
+
+        '''
+
+        self.check_closed_product()
+        self._check_write_mode()
+
+        if self._parent.index is None:
+            raise NotImplementedError(
+                'setting elements is not implemented on MPH/SPH records')
+
+        elem = np.asarray(elem)
+        if elem.size != 1:
+            raise ValueError(
+                'invalid shape "%s", scalar value expected' % elem.shape)
+
+        self._set_elems(elem, index)
+
+    def set_elems(self, elems):
+        '''set_elems(self, elems)
+
+        Set Field array elements
+
+        This function is for setting an array of field elements of the
+        field.
+
+        :param elems:
+            np.ndarray of elements to set
+
+        '''
+
+        cdef uint nelems
+
+        self.check_closed_product()
+        self._check_write_mode()
+
+        if self._parent._index is None:
+            raise NotImplementedError(
+                'setting elements is not implemented on MPH/SPH records')
+
+        elems = np.ascontiguousarray(elems)
+        nelems = epr_get_field_num_elems(self._ptr)
+        if elems.ndim > 1 or elems.size != nelems:
+            raise ValueError('invalid shape "%s", "(%s,)" value expected' % (
+                elems.shape, nelems))
+
+        self._set_elems(elems)
+
+    property tot_size:
+        '''The total size in bytes of all data elements of a field.
+
+        *tot_size* is a derived variable, it is computed at runtime and
+        not stored in the DSD-DB.
+
+        '''
+
+        def __get__(self):
+            cdef EPR_FieldInfo* info = <EPR_FieldInfo*>self._ptr.info
+            return info.tot_size
+
+
     # --- high level interface ------------------------------------------------
     def __repr__(self):
         return 'epr.Field("%s") %d %s elements' % (self.get_name(),
                     self.get_num_elems(), data_type_id_to_str(self.get_type()))
 
     def __str__(self):
+        cdef object name = self.get_name()
         cdef EPR_DataTypeId type_ = self.get_type()
+        cdef int num_elems = 0
+
         if type_ == e_tid_string:
-            return '%s = "%s"' % (self.get_name(), self.get_elem())
+            return '%s = "%s"' % (name, self.get_elem())
         elif type_ == e_tid_time:
             days, seconds, microseconds = self.get_elem()
-            return '%s = {d=%d, j=%d, m=%d}' % (self.get_name(),
+            return '%s = {d=%d, j=%d, m=%d}' % (name,
                                                 days, seconds, microseconds)
         else:
+            num_elems = epr_get_field_num_elems(self._ptr)
+
             if type_ == e_tid_uchar:
                 fmt = '%u'
             elif type_ == e_tid_char:
@@ -1051,18 +978,18 @@ cdef class Field(EprObject):
             elif type_ == e_tid_double:
                 fmt = '%f'
             else:
-                if self.get_num_elems() > 1:
-                    data = ['<<unknown data type>>'] * self.get_elems()
+                if num_elems > 1:
+                    data = ['<<unknown data type>>'] * num_elems
                     data = ', '.join(data)
-                    return '%s = {%s}' % (self.get_name(), data)
+                    return '%s = {%s}' % (name, data)
                 else:
-                    return '%s = <<unknown data type>>' % (self.get_name())
+                    return '%s = <<unknown data type>>' % name
 
-            if self.get_num_elems() > 1:
+            if num_elems > 1:
                 data = ', '.join([fmt % item for item in self.get_elems()])
-                return '%s = {%s}' % (self.get_name(), data)
+                return '%s = {%s}' % (name, data)
             else:
-                return '%s = %s' % (self.get_name(), fmt % self.get_elem())
+                return '%s = %s' % (name, fmt % self.get_elem())
 
     def __richcmp__(self, other, int op):
         cdef int ret
@@ -1136,8 +1063,8 @@ cdef class Field(EprObject):
                 return (cstring.memcmp(p1.elems, p2.elems, n) != 0)
 
             else:
-                raise TypeError('Field only implements "==" and '
-                                '"!=" operators')
+                raise TypeError(
+                    'Field only implements "==" and "!=" operators')
         else:
             return NotImplemented
 
@@ -1149,6 +1076,20 @@ cdef class Field(EprObject):
         else:
             return epr_get_field_num_elems(self._ptr)
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
+    def get_offset(self):
+        '''Field offset in bytes within the Record'''
+
+        self.check_closed_product()
+        return self._get_offset()
+
 
 cdef new_field(EPR_SField* ptr, Record parent=None):
     if ptr is NULL:
@@ -1174,6 +1115,7 @@ cdef class Record(EprObject):
     cdef EPR_SRecord* _ptr
     cdef object _parent     # Dataset or Product
     cdef bint _dealloc
+    cdef int _index
 
     def __dealloc__(self):
         if not self._dealloc:
@@ -1190,6 +1132,24 @@ cdef class Record(EprObject):
             #elif isinstance(self._parent, Product):
             (<Product>self._parent).check_closed_product()
 
+    cdef inline _check_write_mode(self):
+        if isinstance(self._parent, Dataset):
+            (<Dataset>self._parent)._check_write_mode()
+        else:
+            #elif isinstance(self._parent, Product):
+            (<Product>self._parent)._check_write_mode()
+
+    cdef inline uint _get_offset(self, bint absolure=0):
+        cdef EPR_RecordInfo* info = <EPR_RecordInfo*>self._ptr.info
+        cdef uint offset = self._index * info.tot_size
+
+        # assert self._index is not None
+
+        if absolure:
+            offset += (<Dataset>self._parent)._get_offset()
+
+        return offset
+
     def get_num_fields(self):
         '''get_num_fields(self)
 
@@ -1310,6 +1270,48 @@ cdef class Record(EprObject):
 
         return new_field(field_ptr, self)
 
+    property dataset_name:
+        '''The name of the dataset to which this record belongs to'''
+
+        def __get__(self):
+            self.check_closed_product()
+            cdef EPR_RecordInfo* info = <EPR_RecordInfo*>self._ptr.info
+            if info.dataset_name == NULL:
+                return ''
+            else:
+                return _to_str(info.dataset_name)
+
+    property tot_size:
+        '''The total size in bytes of the record
+
+        It includes all data elements of all fields of a record in a
+        product file.
+
+        *tot_size* is a derived variable, it is computed at runtime
+        and not stored in the DSD-DB.
+
+        '''
+
+        def __get__(self):
+            self.check_closed_product()
+            cdef EPR_RecordInfo* info = <EPR_RecordInfo*>self._ptr.info
+            return info.tot_size
+
+    property index:
+        '''Index of the record within the dataset
+
+        It is *None* for empty records (created with
+        :meth:`Dataset.create_record` but still not read) and for *MPH*
+        (see :meth:`epr.Product.get_mph`) and *SPH* (see
+        :meth:`epr.Product.get_sph`) records.
+
+        .. seealso:: :meth:`epr.Dataset.read_record`
+
+        '''
+
+        def __get__(self):
+            return self._index if self._index >= 0 else None
+
     # --- high level interface ------------------------------------------------
     def get_field_names(self):
         '''get_field_names(self)
@@ -1323,19 +1325,19 @@ cdef class Record(EprObject):
         cdef EPR_SField* field_ptr
         cdef int idx
         cdef char* name
+        cdef int num_fields
 
         self.check_closed_product()
+        num_fields = epr_get_num_fields(self._ptr)
 
         names = []
-        for idx in range(self.get_num_fields()):
+        for idx in range(num_fields):
             field_ptr = <EPR_SField*>epr_get_field_at(self._ptr, idx)
             name = <char*>epr_get_field_name(field_ptr)
             names.append(_to_str(name, 'ascii'))
 
         return names
 
-    # @NOTE: generator and generator expressions are not yet implemented in
-    #        cython. As a workaround a list is used
     def fields(self):
         '''fields(self)
 
@@ -1343,18 +1345,17 @@ cdef class Record(EprObject):
 
         '''
 
-        # @TODO: use __iter__ when generator expressions will be available
-        #return list(self)
+        return list(self)
+
+    def __iter__(self):
         cdef int idx
+        cdef int num_fields
+
         self.check_closed_product()
-        return [self.get_field_at(idx)
-                            for idx in range(epr_get_num_fields(self._ptr))]
 
-    def __iter__(self):
-        # @TODO: use generator expression when it will be available
-        #return (self.get_field_at(idx)
-        #                    for idx in range(epr_get_num_elems(self._ptr)))
-        return iter(self.fields())
+        num_fields = epr_get_num_fields(self._ptr)
+
+        return (self.get_field_at(idx) for idx in range(num_fields))
 
     def __str__(self):
         self.check_closed_product()
@@ -1365,6 +1366,23 @@ cdef class Record(EprObject):
         return '%s %d fields' % (super(Record, self).__repr__(),
                                  self.get_num_fields())
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
+    def get_offset(self):
+        '''Record offset in bytes within the Dataset'''
+
+        if self._index >= 0:
+            self.check_closed_product()
+            return self._get_offset()
+        else:
+            return None
+
 
 cdef new_record(EPR_SRecord* ptr, object parent=None, bint dealloc=False):
     if ptr is NULL:
@@ -1375,6 +1393,7 @@ cdef new_record(EPR_SRecord* ptr, object parent=None, bint dealloc=False):
     instance._ptr = ptr
     instance._parent = parent       # Dataset or Product
     instance._dealloc = dealloc
+    instance._index = -1
 
     return instance
 
@@ -1536,7 +1555,7 @@ cdef class Raster(EprObject):
                 return np.ndarray(())
 
             data = self.toarray()
-            self._data = weakref.ref(data)
+            self._data = PyWeakref_NewRef(data, None)
 
             return data
 
@@ -1545,6 +1564,13 @@ cdef class Raster(EprObject):
                                       data_type_id_to_str(self.data_type),
                                       self.get_height(), self.get_width())
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            return self._ptr.magic
+
 
 cdef new_raster(EPR_SRaster* ptr, Band parent=None):
     if ptr is NULL:
@@ -1811,6 +1837,19 @@ cdef class Band(EprObject):
         def __get__(self):
             return <bint>self._ptr.lines_mirrored
 
+    property dataset:
+        '''The source dataset
+
+        The source dataset containing the raw data used to create the
+        band's pixel values.
+
+        '''
+
+        def __get__(self):
+            cdef EPR_SDatasetId* dataset_id = self._ptr.dataset_ref.dataset_id
+            cdef const char* name = epr_get_dataset_name(dataset_id)
+            return self.product.get_dataset(name)
+
     def get_name(self):
         '''get_name(self)
 
@@ -1892,23 +1931,28 @@ cdef class Band(EprObject):
 
         '''
 
-        cdef EPR_SRaster* raster_ptr=NULL
+        cdef EPR_SRaster* raster_ptr = NULL
+        cdef int scene_width
+        cdef int scene_height
 
         self.check_closed_product()
 
+        scene_width = epr_get_scene_width(self._parent._ptr)
+        scene_height = epr_get_scene_height(self._parent._ptr)
+
         if src_width == 0:
-            src_width = self._parent.get_scene_width()
-        elif src_width > self._parent.get_scene_width():
-            raise ValueError('requeted raster width (%d) is too large for '
-                             'the Band (scene_width=%d)' % (
-                                src_width, self._parent.get_scene_width()))
+            src_width = scene_width
+        elif src_width > scene_width:
+            raise ValueError(
+                'requeted raster width (%d) is too large for the Band '
+                '(scene_width=%d)' % (src_width, scene_width))
 
         if src_height == 0:
-            src_height = self._parent.get_scene_height()
-        elif src_height > self._parent.get_scene_height():
-            raise ValueError('requeted raster height (%d) is too large for '
-                             'the Band (scene_height=%d)' % (
-                                src_height, self._parent.get_scene_height()))
+            src_height = scene_height
+        elif src_height > scene_height:
+            raise ValueError(
+                'requeted raster height (%d) is too large for the Band '
+                '(scene_height=%d)' % (src_height, scene_height))
 
         if xstep > src_width:
             raise ValueError('xstep (%d) too large for the requested width '
@@ -1963,15 +2007,19 @@ cdef class Band(EprObject):
         '''
 
         cdef int ret
+        cdef int scene_width
+        cdef int scene_height
 
         self.check_closed_product()
 
         if raster is None:
             raster = self.create_compatible_raster()
 
-        if (xoffset + raster.source_width > self.product.get_scene_width() or
-                yoffset + raster.source_height >
-                    self.product.get_scene_height()):
+        scene_width = epr_get_scene_width(self._parent._ptr)
+        scene_height = epr_get_scene_height(self._parent._ptr)
+
+        if (xoffset + raster._ptr.source_width > scene_width or
+                yoffset + raster._ptr.source_height > scene_height):
             raise ValueError(
                 'at lease part of the requested area is outside the scene')
 
@@ -2030,15 +2078,22 @@ cdef class Band(EprObject):
 
         '''
 
+        cdef int w
+        cdef int h
+        cdef EPR_ProductId* product_id
+
+        self.check_closed_product()
+        product_id = self._parent._ptr
+
         if width is None:
-            w = self.product.get_scene_width()
+            w = epr_get_scene_width(product_id)
             if w > xoffset:
                 width = w - xoffset
             else:
                 raise ValueError('xoffset os larger that he scene width')
 
         if height is None:
-            h = self.product.get_scene_height()
+            h = epr_get_scene_height(product_id)
             if h > yoffset:
                 height = h - yoffset
             else:
@@ -2053,6 +2108,37 @@ cdef class Band(EprObject):
         return 'epr.Band(%s) of epr.Product(%s)' % (self.get_name(),
                                                     self.product.id_string)
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
+
+    property _field_index:
+        '''Index or the field (within the dataset) containing the raw
+        data used to create the band's pixel values.
+
+        It is set to -1 if not used
+
+        '''
+
+        def __get__(self):
+            return self._ptr.dataset_ref.field_index
+
+    property _elem_index:
+        '''Index or the element (within the dataset field) containing
+        the raw data used to create the band's pixel values.
+
+        It is set to -1 if not used
+
+        '''
+
+        def __get__(self):
+            return self._ptr.dataset_ref.elem_index
+
 
 cdef new_band(EPR_SBandId* ptr, Product parent=None):
     if ptr is NULL:
@@ -2085,6 +2171,13 @@ cdef class Dataset(EprObject):
     cdef inline check_closed_product(self):
         self._parent.check_closed_product()
 
+    cdef inline _check_write_mode(self):
+        self._parent._check_write_mode()
+
+    cdef inline uint _get_offset(self):
+        cdef const EPR_SDSD* dsd = epr_get_dsd(self._ptr)
+        return dsd.ds_offset
+
     property product:
         '''The :class:`Product` instance to which this dataset belongs to'''
 
@@ -2172,8 +2265,7 @@ cdef class Dataset(EprObject):
 
         return new_record(epr_create_record(self._ptr), self, True)
 
-    # @TODO: default: index=0
-    def read_record(self, uint index, Record record=None):
+    def read_record(self, uint index=0, Record record=None):
         '''read_record(self, index, record=None)
 
         Reads specified record of the dataset
@@ -2191,7 +2283,7 @@ cdef class Dataset(EprObject):
         be  returned.
 
         :param index:
-            the zero-based record index
+            the zero-based record index (default: 0)
         :param record:
             a pre-created record to reduce memory reallocation, can be
             ``None`` (default) to let the function allocate a new
@@ -2200,6 +2292,10 @@ cdef class Dataset(EprObject):
             the record in which the data has been read into or raises
             an exception (:exc:`EPRValueError`) if an error occurred
 
+        .. versionchanged:: 0.9
+
+           The *index* parameter now defaults to zero
+
         '''
 
         cdef EPR_SRecord* record_ptr = NULL
@@ -2218,11 +2314,11 @@ cdef class Dataset(EprObject):
         if not record:
             record = new_record(record_ptr, self, True)
 
+        record._index = index
+
         return record
 
     # --- high level interface ------------------------------------------------
-    # @NOTE: generator and generator expressions are not yet implemented in
-    #        cython. As a workaround a list is used
     def records(self):
         '''records(self)
 
@@ -2230,20 +2326,13 @@ cdef class Dataset(EprObject):
 
         '''
 
-        # @TODO: use __iter__ when generator expressions will be available
-        #return list(self)
-        cdef int idx
-
-        self.check_closed_product()
-
-        return [self.read_record(idx)
-                            for idx in range(epr_get_num_records(self._ptr))]
+        return list(self)
 
     def __iter__(self):
-        # @TODO: use generator expression when it will be available
-        #return (self.get_field_at(idx)
-        #                    for idx in range(epr_get_num_elems(self._ptr)))
-        return iter(self.records())
+        cdef int idx
+        self.check_closed_product()
+        return (self.read_record(idx)
+                            for idx in range(epr_get_num_records(self._ptr)))
 
     def __str__(self):
         lines = [repr(self), '']
@@ -2254,6 +2343,14 @@ cdef class Dataset(EprObject):
         return 'epr.Dataset(%s) %d records' % (self.get_name(),
                                                self.get_num_records())
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
 
 cdef new_dataset(EPR_SDatasetId* ptr, Product parent=None):
     if ptr is NULL:
@@ -2278,14 +2375,37 @@ cdef class Product(EprObject):
     '''
 
     cdef EPR_SProductId* _ptr
+    cdef str _mode
 
-    def __cinit__(self, filename, *args, **kargs):
+    def __cinit__(self, filename, str mode='rb'):
         cdef bytes bfilename = _to_bytes(filename, _DEFAULT_FS_ENCODING)
         cdef char* cfilename = bfilename
+        cdef bytes bmode
+        cdef char* cmode
+        cdef int ret
+
+        if mode not in ('rb', 'rb+', 'r+b'):
+            raise ValueError('invalid open mode: "%s"' % mode)
+
+        self._mode = mode
 
         with nogil:
             self._ptr = epr_open_product(cfilename)
 
+        if '+' in mode:
+            # reopen in 'rb+ mode
+
+            bmode = _to_bytes(mode)
+            cmode = bmode
+
+            with nogil:
+                self._ptr.istream = stdio.freopen(cfilename, cmode,
+                                                  self._ptr.istream)
+            if self._ptr.istream is NULL:
+                errno.errno = 0
+                raise ValueError(
+                    'unable to open file "%s" in "%s" mode' % (filename, mode))
+
         if self._ptr is NULL:
             # try to get error info from the lib
             pyepr_check_errors()
@@ -2294,6 +2414,8 @@ cdef class Product(EprObject):
 
     def __dealloc__(self):
         if self._ptr is not NULL:
+            if '+' in self._mode:
+                stdio.fflush(self._ptr.istream)
             epr_close_product(self._ptr)
             pyepr_check_errors()
             self._ptr = NULL
@@ -2302,7 +2424,11 @@ cdef class Product(EprObject):
         if self._ptr is NULL:
             raise ValueError('I/O operation on closed file')
 
-    def __init__(self, filename):
+    cdef inline _check_write_mode(self):
+        if '+' not in self._mode:
+            raise TypeError('write operation on read-only file')
+
+    def __init__(self, filename, mode='rb'):
         # @NOTE: this method suppresses the default behavior of EprObject
         #        that is raising an exception when it is instantiated by
         #        the user.
@@ -2326,10 +2452,22 @@ cdef class Product(EprObject):
         '''
 
         if self._ptr is not NULL:
+            #if '+' in self.mode:
+            #    stdio.fflush(self._ptr.istream)
             epr_close_product(self._ptr)
             pyepr_check_errors()
             self._ptr = NULL
 
+    def flush(self):
+        '''Flush the file stream'''
+
+        cdef int ret
+        if '+' in self.mode:
+            ret = stdio.fflush(self._ptr.istream)
+            if ret != 0:
+                errno.errno = 0
+                raise IOError('flush error')
+
     property file_path:
         '''The file's path including the file name'''
 
@@ -2340,18 +2478,29 @@ cdef class Product(EprObject):
             else:
                 return _to_str(self._ptr.file_path, 'ascii')
 
-    # @TODO: check
-    #property istream:
-    #    '''The input stream as returned by the ANSI C :c:func:`fopen`
-    #       function for the given file path
-    #
-    #    '''
-    #
-    #    def __get__(self):
-    #        if self._ptr.istream is NULL:
-    #            return None
-    #        else:
-    #            return os.fdopen(self._ptr.istream)
+    property _fileno:
+        '''The fileno of the :class:`epr.Product` input stream
+
+        To be used with care.
+
+        '''
+
+        def __get__(self):
+            if self._ptr.istream is NULL:
+                return None
+            else:
+                return fileno(self._ptr.istream)
+
+    property mode:
+        def __get__(self):
+            '''String that specifies the mode in which the file is opened
+
+            Possible values: 'rb' for read-only mode, 'rb+' for read-write
+            mode.
+
+            '''
+
+            return self._mode
 
     property tot_size:
         '''The total size in bytes of the product file'''
@@ -2605,7 +2754,7 @@ cdef class Product(EprObject):
         :returns:
             zero for success, an error code otherwise
 
-        .. seealso: :func:`create_bitmask_raster`
+        .. seealso:: :func:`epr.create_bitmask_raster`
 
         '''
 
@@ -2641,9 +2790,14 @@ cdef class Product(EprObject):
         cdef EPR_SDatasetId* dataset_ptr
         cdef int idx
         cdef char* name
+        cdef int num_datasets
+
+        self.check_closed_product()
+
+        num_datasets = epr_get_num_datasets(self._ptr)
 
         names = []
-        for idx in range(self.get_num_datasets()):
+        for idx in range(num_datasets):
             dataset_ptr = epr_get_dataset_id_at(self._ptr, idx)
             name = <char*>epr_get_dataset_name(dataset_ptr)
             names.append(_to_str(name, 'ascii'))
@@ -2662,17 +2816,20 @@ cdef class Product(EprObject):
         cdef EPR_SBandId* band_ptr
         cdef int idx
         cdef char* name
+        cdef int num_bands
+
+        self.check_closed_product()
+
+        num_bands = epr_get_num_bands(self._ptr)
 
         names = []
-        for idx in range(self.get_num_bands()):
+        for idx in range(num_bands):
             band_ptr = epr_get_band_id_at(self._ptr, idx)
             name = <char*>epr_get_band_name(band_ptr)
             names.append(_to_str(name, 'ascii'))
 
         return names
 
-    # @NOTE: generator and generator expressions are not yet implemented in
-    #        cython. As a workaround a list is used
     def datasets(self):
         '''datasets(self)
 
@@ -2681,8 +2838,13 @@ cdef class Product(EprObject):
         '''
 
         cdef int idx
-        return [self.get_dataset_at(idx)
-                            for idx in range(epr_get_num_datasets(self._ptr))]
+        cdef int num_datasets
+
+        self.check_closed_product()
+
+        num_datasets = epr_get_num_datasets(self._ptr)
+
+        return [self.get_dataset_at(idx) for idx in range(num_datasets)]
 
     def bands(self):
         '''bands(self)
@@ -2691,8 +2853,13 @@ cdef class Product(EprObject):
 
         '''
 
-        return [self.get_band_at(idx)
-                            for idx in range(epr_get_num_bands(self._ptr))]
+        cdef int num_bands
+
+        self.check_closed_product()
+
+        num_bands = epr_get_num_bands(self._ptr)
+
+        return [self.get_band_at(idx) for idx in range(num_bands)]
 
     # @TODO: iter on both datasets and bands (??)
     #def __iter__(self):
@@ -2715,8 +2882,16 @@ cdef class Product(EprObject):
     def __exit__(self, *exc_info):
         self.close()
 
+    # --- low level interface -------------------------------------------------
+    property _magic:
+        '''The magic number for internal C structure'''
+
+        def __get__(self):
+            self.check_closed_product()
+            return self._ptr.magic
+
 
-def open(filename):
+def open(filename, mode='rb'):
     '''open(filename)
 
     Opens the ENVISAT product
@@ -2727,6 +2902,10 @@ def open(filename):
 
     :param product_file_path:
         the path to the ENVISAT product file
+    :param mode:
+        string that specifies the mode in which the file is opened.
+        Allowed values: 'rb', 'rb+' for read-write mode.
+        Default: mode='rb'.
     :returns:
         the :class:`Product` instance representing the specified
         product. An exception (:exc:`exceptions.ValueError`) is raised
@@ -2736,7 +2915,7 @@ def open(filename):
 
     '''
 
-    return Product(filename)
+    return Product(filename, mode)
 
 
 # library initialization/finalization
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/checksetup.mak b/tests/checksetup.mak
new file mode 100644
index 0000000..b0f952f
--- /dev/null
+++ b/tests/checksetup.mak
@@ -0,0 +1,202 @@
+#!/usr/bin/make -f
+
+ROOT = CHECKSETUP_ROOT
+
+VERSION = $(shell grep "__version__ =" ../src/epr.pyx | cut -d \' -f 2)
+PKGDIST = pyepr-$(VERSION).tar.gz
+PYTHON = python
+PYVER = 3.4.2
+CONDA = conda
+CONDAOPTS = --use-index-cache -m -y
+
+
+.PHONY: \
+	clean distclean cache check \
+	check-nosetuptools check-setuptoolsoff check-setuptools \
+	check-pip check-wheel
+
+
+check: \
+	check-nosetuptools \
+	check-setuptoolsoff \
+	check-setuptools \
+	check-pip \
+	check-wheel
+
+
+check-nosetuptools: \
+	check_nosetuptools_nonumpy_nocython \
+	check_nosetuptools_numpy_nocython_c \
+	check_nosetuptools_numpy_cython
+
+
+check-setuptoolsoff: \
+	check_setuptoolsoff_nonumpy_nocython \
+	check_setuptoolsoff_numpy_nocython_c \
+	check_setuptoolsoff_numpy_cython
+
+
+check-setuptools: \
+	check_setuptools_nonumpy_nocython \
+	#check_setuptools_nonumpy_nocython_c \
+	check_setuptools_numpy_cython_c
+
+
+check-pip: \
+	check_pip_nonumpy_nocython \
+	check_pip_nonumpy_nocython_c \
+	check_pip_numpy_cython_c
+
+
+check-wheel: check_wheel
+
+
+$(ROOT):
+	mkdir -p $(ROOT)
+
+
+$(ROOT)/cache-done:
+	$(CONDA) create -p $(ROOT)/dummyenv -m -y \
+		python=$(PYVER) setuptools pip numpy cython
+	$(ROOT)/dummyenv/bin/pip install --download $(ROOT) --use-wheel wheel
+	$(ROOT)/dummyenv/bin/pip install --download $(ROOT) numpy cython
+	$(RM) -r $(ROOT)/dummyenv
+	touch $@
+
+
+$(ROOT)/$(PKGDIST): $(ROOT)
+	#$(MAKE) -C .. distclean
+	$(MAKE) -C .. sdist
+	cp ../dist/$(PKGDIST) $(ROOT)
+
+
+cache: $(ROOT)/cache-done $(ROOT)/$(PKGDIST)
+
+
+clean:
+	$(RM) -r $(ROOT)/check_*
+
+
+distclean:
+	$(RM) -r $(ROOT)
+
+
+# no setuptools ###############################################################
+check_nosetuptools_nonumpy_nocython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER)
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py install;\
+	if [ ! $$? ]; then false; else true; fi
+	@echo "EXPECTED FAILURE"
+
+
+check_nosetuptools_numpy_nocython_c: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) numpy
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py install
+
+
+check_nosetuptools_numpy_cython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) numpy cython
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py install
+
+
+# setuptools off ##############################################################
+check_setuptoolsoff_nonumpy_nocython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	env USE_SETUPTOOLS=FALSE ../bin/$(PYTHON) setup.py install;\
+	if [ ! $$? ]; then false; else true; fi
+	@echo "EXPECTED FAILURE"
+
+
+check_setuptoolsoff_numpy_nocython_c: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools numpy
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	env USE_SETUPTOOLS=FALSE ../bin/$(PYTHON) setup.py install
+
+
+check_setuptoolsoff_numpy_cython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools numpy cython
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	env USE_SETUPTOOLS=FALSE ../bin/$(PYTHON) setup.py install
+
+
+# setuptools ##################################################################
+check_setuptools_nonumpy_nocython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py install;\
+	if [ ! $$? ]; then false; else true; fi
+	@echo "EXPECTED FAILURE"
+
+
+# @TODO: check
+#check_setuptools_nonumpy_nocython_c: $(ROOT) cache
+#	$(RM) -r $(ROOT)/$@
+#	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools
+#	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+#	cd $(ROOT)/$@/pyepr-$(VERSION);\
+#	../bin/$(PYTHON) setup.py install
+
+
+check_setuptools_numpy_cython_c: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) setuptools numpy cython
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py install
+
+
+# pip #########################################################################
+check_pip_nonumpy_nocython: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) pip
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	rm $(ROOT)/$@/pyepr-$(VERSION)/src/*.c
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	$(ROOT)/$@/bin/pip install -v --no-index --find-links=file://$(PWD)/$(ROOT) $(ROOT)/$@/pyepr-$(VERSION);\
+	if [ ! $$? ]; then false; else true; fi
+	@echo "EXPECTED FAILURE"
+
+
+check_pip_nonumpy_nocython_c: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) pip
+	$(ROOT)/$@/bin/pip install -v --no-index --find-links=file://$(PWD)/$(ROOT) $(ROOT)/$(PKGDIST)
+
+
+check_pip_numpy_cython_c: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) pip cython numpy
+	$(ROOT)/$@/bin/pip install -v --no-index --find-links=file://$(PWD)/$(ROOT) $(ROOT)/$(PKGDIST)
+
+
+# wheel #######################################################################
+check_wheel: $(ROOT) cache
+	$(RM) -r $(ROOT)/$@
+	$(CONDA) create $(CONDAOPTS) -p $(ROOT)/$@ python=$(PYVER) pip cython numpy
+	$(ROOT)/$@/bin/pip install -v --no-index --find-links=file://$(PWD)/$(ROOT) wheel
+	tar -C $(ROOT)/$@ -xvzf $(ROOT)/$(PKGDIST)
+	cd $(ROOT)/$@/pyepr-$(VERSION);\
+	../bin/$(PYTHON) setup.py bdist_wheel
diff --git a/test/test_all.py b/tests/test_all.py
similarity index 73%
rename from test/test_all.py
rename to tests/test_all.py
index f06da17..abffa0b 100755
--- a/test/test_all.py
+++ b/tests/test_all.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Copyright (C) 2011-2014, Antonio Valentino <antonio.valentino at tiscali.it>
+# Copyright (C) 2011-2015, Antonio Valentino <antonio.valentino at tiscali.it>
 #
 # This file is part of PyEPR.
 #
@@ -19,13 +19,17 @@
 # along with PyEPR.  If not, see <http://www.gnu.org/licenses/>.
 
 
+import io
 import os
 import re
 import sys
+import gzip
+import shutil
 import numbers
 import operator
 import tempfile
 import functools
+import contextlib
 from distutils.version import LooseVersion
 
 try:
@@ -41,6 +45,11 @@ else:
     del skipIf
     import unittest
 
+try:
+    from urllib.request import urlopen
+except ImportError:
+    from urllib2 import urlopen
+
 import numpy as np
 import numpy.testing as npt
 
@@ -116,12 +125,54 @@ def equal_products(product1, product2):
     return True
 
 
+def setUpModule():
+    filename = os.path.join(TESTDIR, TEST_PRODUCT)
+    url = 'http://earth.esa.int/services/sample_products/meris/LRC/L2/MER_LRC_2PTGMV20000620_104318_00000104X000_00000_00000_0001.N1.gz'
+    if not os.path.exists(filename):
+        with contextlib.closing(urlopen(url)) as src:
+            with open(filename + '.gz', 'wb') as dst:
+                for data in src:
+                    dst.write(data)
+
+        with contextlib.closing(gzip.GzipFile(filename + '.gz')) as src:
+            with open(filename, 'wb') as dst:
+                for data in src:
+                    dst.write(data)
+
+        os.remove(filename + '.gz')
+
+
 class TestOpenProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
 
     def test_open(self):
         product = epr.open(self.PRODUCT_FILE)
         self.assertTrue(isinstance(product, epr.Product))
+        self.assertEqual(product.mode, 'rb')
+
+    def test_open_rb(self):
+        product = epr.open(self.PRODUCT_FILE, 'rb')
+        self.assertTrue(isinstance(product, epr.Product))
+        self.assertEqual(product.mode, 'rb')
+
+    def test_open_rwb_01(self):
+        product = epr.open(self.PRODUCT_FILE, 'r+b')
+        self.assertTrue(isinstance(product, epr.Product))
+        self.assertTrue(product.mode in ('r+b', 'rb+'))
+
+    def test_open_rwb_02(self):
+        product = epr.open(self.PRODUCT_FILE, 'rb+')
+        self.assertTrue(isinstance(product, epr.Product))
+        self.assertTrue(product.mode in ('r+b', 'rb+'))
+
+    def test_open_invalid_mode_01(self):
+        self.assertRaises(ValueError, epr.open, self.PRODUCT_FILE, '')
+
+    def test_open_invalid_mode_02(self):
+        self.assertRaises(ValueError, epr.open, self.PRODUCT_FILE, 'rx')
+
+    def test_open_invalid_mode_03(self):
+        self.assertRaises(TypeError, epr.open, self.PRODUCT_FILE, 0)
 
     if 'unicode' in dir(__builtins__):
 
@@ -160,6 +211,7 @@ class TestOpenProduct(unittest.TestCase):
 
 class TestProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
     ID_STRING = 'MER_LRC_2PTGMV20000620_104318_00000104X000_00000'
     TOT_SIZE = 407461
 
@@ -183,17 +235,27 @@ class TestProduct(unittest.TestCase):
     MERIS_IODD_VERSION = 7
 
     def setUp(self):
-        self.product = epr.Product(self.PRODUCT_FILE)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
+
+    def tearDown(self):
+        self.product.close()
 
     def test_close(self):
         self.product.close()
 
+    def test_flush(self):
+        self.product.close()
+
     def test_double_close(self):
         self.product.close()
         self.product.close()
 
     def test_file_path_property(self):
-        self.assertEqual(self.product.file_path, self.PRODUCT_FILE)
+        self.assertEqual(self.product.file_path,
+                         self.PRODUCT_FILE.replace('\\', '/'))
+
+    def test_mode_property(self):
+        self.assertEqual(self.product.mode, self.OPEN_MODE)
 
     def test_tot_size_property(self):
         self.assertEqual(self.product.tot_size, self.TOT_SIZE)
@@ -376,6 +438,18 @@ class TestProduct(unittest.TestCase):
         except epr.EPRError as e:
             self.assertEqual(e.code, 7)
 
+    def test_fileno(self):
+        self.assertTrue(isinstance(self.product._fileno, int))
+
+    def test_fileno_read(self):
+        os.lseek(self.product._fileno, 0, os.SEEK_SET)
+        data = os.read(self.product._fileno, 7)
+        self.assertEqual(data, b'PRODUCT')
+
+
+class TestProductRW(TestProduct):
+    OPEN_MODE = 'rb+'
+
 
 class TestProductHighLevelAPI(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
@@ -405,6 +479,9 @@ class TestProductHighLevelAPI(unittest.TestCase):
     def setUp(self):
         self.product = epr.Product(self.PRODUCT_FILE)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_closed(self):
         self.assertFalse(self.product.closed)
         self.product.close()
@@ -437,7 +514,7 @@ class TestProductHighLevelAPI(unittest.TestCase):
             ref_band = self.product.get_band_at(index)
             self.assertEqual(band.get_name(), ref_band.get_name())
 
-    # @TODO: complete
+    # @TODO: not implemented
     #def test_iter(self):
     #    pass
 
@@ -477,6 +554,19 @@ class TestProductHighLevelAPI(unittest.TestCase):
         self.assertTrue(product.closed)
 
 
+class TestProductLowLevelAPI(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+
+    def setUp(self):
+        self.product = epr.Product(self.PRODUCT_FILE)
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_magic(self):
+        self.assertEqual(self.product._magic, epr._EPR_MAGIC_PRODUCT_ID)
+
+
 class TestClosedProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     DATASET_NAME = 'Vapour_Content'
@@ -558,15 +648,20 @@ class TestClosedProduct(unittest.TestCase):
 
 class TestDataset(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
     DATASET_NAME = 'Vapour_Content'
     DATASET_DESCRIPTION = 'Level 2 MDS Total Water vapour'
     NUM_RECORDS = 149
     DSD_NAME = 'MDS Vapour Content'
+    RECORD_INDEX = 0
 
     def setUp(self):
-        self.product = epr.Product(self.PRODUCT_FILE)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
         self.dataset = self.product.get_dataset(self.DATASET_NAME)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_product_property(self):
         self.assertTrue(equal_products(self.dataset.product, self.product))
 
@@ -589,19 +684,39 @@ class TestDataset(unittest.TestCase):
         record = self.dataset.create_record()
         self.assertTrue(isinstance(record, epr.Record))
 
+    def test_create_record_index(self):
+        record = self.dataset.create_record()
+        self.assertEqual(record.index, None)
+
     def test_read_record(self):
-        record = self.dataset.read_record(0)
+        record = self.dataset.read_record(self.RECORD_INDEX)
         self.assertTrue(isinstance(record, epr.Record))
 
+    def test_read_record_index(self):
+        record = self.dataset.read_record(self.RECORD_INDEX)
+        self.assertEqual(record.index, self.RECORD_INDEX)
+
     def test_read_record_passed(self):
         created_record = self.dataset.create_record()
-        read_record = self.dataset.read_record(0, created_record)
+        read_record = self.dataset.read_record(self.RECORD_INDEX,
+                                               created_record)
         self.assertTrue(created_record is read_record)
 
+    def test_read_record_passed_index(self):
+        created_record = self.dataset.create_record()
+        self.assertEqual(created_record.index, None)
+        read_record = self.dataset.read_record(self.RECORD_INDEX,
+                                               created_record)
+        self.assertEqual(read_record.index, self.RECORD_INDEX)
+
     def test_read_record_passed_invalid(self):
         self.assertRaises(TypeError, self.dataset.read_record, 0, 0)
 
 
+class TestDatasetRW(TestDataset):
+    OPEN_MODE = 'rb+'
+
+
 class TestDatasetHighLevelAPI(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     DATASET_NAME = TestDataset.DATASET_NAME
@@ -610,6 +725,9 @@ class TestDatasetHighLevelAPI(unittest.TestCase):
         self.product = epr.Product(self.PRODUCT_FILE)
         self.dataset = self.product.get_dataset(self.DATASET_NAME)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_records(self):
         records = self.dataset.records()
         self.assertTrue(records)
@@ -701,6 +819,8 @@ class TestDatasetOnClosedProduct(unittest.TestCase):
 
 class TestBand(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb+'
+    DATASET_NAME = 'Vapour_Content'
     BAND_NAMES = (
         'latitude',
         'longitude',
@@ -731,6 +851,7 @@ class TestBand(unittest.TestCase):
     SCALING_FACTOR = 0.10000000149011612
     SCALING_OFFSET = -0.10000000149011612
     UNIT = 'g/cm^2'
+    RTOL = 1e-7
     DATA_TYPE = np.float32
     TEST_DATA = np.asarray([
         [0.20000002, 0.20000002, 0.20000002, 0.20000002, 0.20000002,
@@ -756,16 +877,15 @@ class TestBand(unittest.TestCase):
     ])
 
     def setUp(self):
-        self.product = epr.Product(self.PRODUCT_FILE)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
         self.band = self.product.get_band(self.BAND_NAME)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_product_property(self):
         self.assertTrue(equal_products(self.band.product, self.product))
 
-    # @TODO: check
-    #def test_dataset_ref_property(self):
-    #    self.assertEqual(self.band.dataset_ref, ???)
-
     def test_spectr_band_index_property(self):
         self.assertEqual(self.band.spectr_band_index, -1)
 
@@ -798,6 +918,11 @@ class TestBand(unittest.TestCase):
         self.assertTrue(isinstance(self.band.lines_mirrored, bool))
         self.assertEqual(self.band.lines_mirrored, True)
 
+    def test_dataset_property(self):
+        dataset = self.band.dataset
+        self.assertTrue(isinstance(dataset, epr.Dataset))
+        self.assertEqual(dataset.get_name(), self.DATASET_NAME)
+
     def test_get_name(self):
         for index in range(len(self.BAND_NAMES)):
             b = self.product.get_band_at(index)
@@ -891,9 +1016,12 @@ class TestBand(unittest.TestCase):
         self.assertEqual(raster.data_type, epr.E_TID_FLOAT)
 
         h, w = self.TEST_DATA.shape
-        npt.assert_allclose(raster.get_pixel(0, 0), self.TEST_DATA[0, 0])
+        npt.assert_allclose(raster.get_pixel(0, 0),
+                            self.TEST_DATA[0, 0],
+                            rtol=self.RTOL)
         npt.assert_allclose(raster.get_pixel(w - 1, h - 1),
-                            self.TEST_DATA[h - 1, w - 1])
+                            self.TEST_DATA[h - 1, w - 1],
+                            rtol=self.RTOL)
 
     def test_read_raster_none(self):
         raster = self.band.read_raster()
@@ -906,10 +1034,13 @@ class TestBand(unittest.TestCase):
 
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
-            raster.get_pixel(self.XOFFSET, self.YOFFSET), self.TEST_DATA[0, 0])
+            raster.get_pixel(self.XOFFSET, self.YOFFSET),
+            self.TEST_DATA[0, 0],
+            rtol=self.RTOL)
         npt.assert_allclose(
             raster.get_pixel(self.XOFFSET + w - 1, self.YOFFSET + h - 1),
-            self.TEST_DATA[h - 1, w - 1])
+            self.TEST_DATA[h - 1, w - 1],
+            rtol=self.RTOL)
 
     def test_read_raster_default_offset(self):
         height = self.HEIGHT
@@ -930,10 +1061,12 @@ class TestBand(unittest.TestCase):
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
             raster1.get_pixel(self.XOFFSET, self.YOFFSET),
-            self.TEST_DATA[0, 0])
+            self.TEST_DATA[0, 0],
+            rtol=self.RTOL)
         npt.assert_allclose(
             raster1.get_pixel(self.XOFFSET + w - 1, self.YOFFSET + h - 1),
-            self.TEST_DATA[h - 1, w - 1])
+            self.TEST_DATA[h - 1, w - 1],
+            rtol=self.RTOL)
 
     def test_read_raster_with_invalid_raster(self):
         self.assertRaises(TypeError, self.band.read_raster, 0, 0, 0)
@@ -963,7 +1096,7 @@ class TestBand(unittest.TestCase):
         self.assertEqual(data.dtype, self.DATA_TYPE)
 
         h, w = self.TEST_DATA.shape
-        npt.assert_allclose(data[:h, :w], self.TEST_DATA)
+        npt.assert_allclose(data[:h, :w], self.TEST_DATA, rtol=self.RTOL)
 
     def test_read_as_array_cross(self):
         data = self.band.read_as_array()
@@ -987,7 +1120,8 @@ class TestBand(unittest.TestCase):
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
             data[self.YOFFSET:self.YOFFSET + h, self.XOFFSET:self.XOFFSET + w],
-            self.TEST_DATA)
+            self.TEST_DATA,
+            rtol=self.RTOL)
 
     # @SEEALSO: https://www.brockmann-consult.de/beam-jira/browse/EPR-2
     @unittest.skipIf(EPR_C_BUG_BCEPR002, 'buggy EPR_C_API detected')
@@ -1013,7 +1147,8 @@ class TestBand(unittest.TestCase):
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
             box[:(h-1)//step+1, :(w-1)//step+1],
-            self.TEST_DATA[::step, ::step])
+            self.TEST_DATA[::step, ::step],
+            rtol=self.RTOL)
 
     @unittest.skipIf(EPR_C_BUG_BCEPR002, 'buggy EPR_C_API detected')
     def test_read_as_array_with_step_3(self):
@@ -1038,7 +1173,8 @@ class TestBand(unittest.TestCase):
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
             box[:(h-1)//step+1, :(w-1)//step+1],
-            self.TEST_DATA[::step, ::step])
+            self.TEST_DATA[::step, ::step],
+            rtol=self.RTOL)
 
     @unittest.skipIf(EPR_C_BUG_BCEPR002, 'buggy EPR_C_API detected')
     def test_read_as_array_with_step_4(self):
@@ -1088,36 +1224,73 @@ class TestBand(unittest.TestCase):
         h, w = self.TEST_DATA.shape
         npt.assert_allclose(
             box[:(h-1)//step+1, :(w-1)//step+1],
-            self.TEST_DATA[::step, ::step])
+            self.TEST_DATA[::step, ::step],
+            rtol=self.RTOL)
+
+
+class TestBandRW(TestBand):
+    OPEN_MODE = 'rb+'
 
 
 class TestAnnotationBand(TestBand):
+    DATASET_NAME = 'Tie_points_ADS'
     BAND_NAME = 'sun_zenith'
     BAND_DESCTIPTION = 'Sun zenith angle'
     SCALING_FACTOR = 9.999999974752427e-07
     SCALING_OFFSET = 0.0
     UNIT = 'deg'
+    RTOL = 1e-6
     TEST_DATA = np.asarray([
-        [33.11141205, 33.07904434, 33.04668045, 33.01435089, 32.98202515,
-         32.94969559, 32.91736603, 32.88507462, 32.85278320, 32.82049179],
-        [33.08905792, 33.05667114, 33.02428818, 32.99193954, 32.95959473,
-         32.92724609, 32.89489746, 32.86258316, 32.83027649, 32.79796219],
-        [33.06670380, 33.03429794, 33.00189590, 32.96953201, 32.93716431,
-         32.90479279, 32.87242889, 32.84009933, 32.80776978, 32.77543640],
-        [33.04434967, 33.01192474, 32.97950363, 32.94711685, 32.91473389,
-         32.88234329, 32.84995651, 32.81761169, 32.78525925, 32.75291061],
-        [33.02199554, 32.98955536, 32.95711136, 32.92470551, 32.89229965,
-         32.85989380, 32.82748795, 32.79512024, 32.76274872, 32.73038101],
-        [32.99978256, 32.96732330, 32.93486023, 32.90243149, 32.87001038,
-         32.83758163, 32.80516052, 32.77277374, 32.74037933, 32.70799255],
-        [32.97756958, 32.94509125, 32.91260529, 32.88016129, 32.84771729,
-         32.81527328, 32.78282928, 32.75042343, 32.71801376, 32.68560410],
-        [32.95535278, 32.92285538, 32.89035416, 32.85789108, 32.82542419,
-         32.79296494, 32.76049805, 32.72807693, 32.69564438, 32.66321945],
-        [32.93313980, 32.90061951, 32.86810303, 32.83562088, 32.80313873,
-         32.77065277, 32.73817062, 32.70572281, 32.67327881, 32.64083099],
-        [32.91106796, 32.87852859, 32.84599304, 32.81349182, 32.78099060,
-         32.74848557, 32.71598434, 32.68351364, 32.65105438, 32.61858749],
+        [33.111412048339843750, 33.079044342041015625,
+         33.046680450439453125, 33.014350891113281250,
+         32.982025146484375000, 32.949695587158203125,
+         32.917366027832031250, 32.885074615478515625,
+         32.852783203125000000, 32.820491790771484375],
+        [33.089057922363281250, 33.056671142578125000,
+         33.024288177490234375, 32.991939544677734375,
+         32.959594726562500000, 32.927246093750000000,
+         32.894897460937500000, 32.862583160400390625,
+         32.830276489257812500, 32.797962188720703125],
+        [33.066703796386718750, 33.034297943115234375,
+         33.001895904541015625, 32.969532012939453125,
+         32.937164306640625000, 32.904792785644531250,
+         32.872428894042968750, 32.840099334716796875,
+         32.807769775390625000, 32.775436401367187500],
+        [33.044349670410156250, 33.011924743652343750,
+         32.979503631591796875, 32.947116851806640625,
+         32.914733886718750000, 32.882343292236328125,
+         32.849956512451171875, 32.817611694335937500,
+         32.785259246826171875, 32.752910614013671875],
+        [33.021995544433593750, 32.989555358886718750,
+         32.957111358642578125, 32.924705505371093750,
+         32.892299652099609375, 32.859893798828125000,
+         32.827487945556640625, 32.795120239257812500,
+         32.762748718261718750, 32.730381011962890625],
+        [32.999782562255859375, 32.967323303222656250,
+         32.934860229492187500, 32.902431488037109375,
+         32.870010375976562500, 32.837581634521484375,
+         32.805160522460937500, 32.772773742675781250,
+         32.740379333496093750, 32.707992553710937500],
+        [32.977569580078125000, 32.945091247558593750,
+         32.912605285644531250, 32.880161285400390625,
+         32.847717285156250000, 32.815273284912109375,
+         32.782829284667968750, 32.750423431396484375,
+         32.718013763427734375, 32.685604095458984375],
+        [32.955352783203125000, 32.922855377197265625,
+         32.890354156494140625, 32.857891082763671875,
+         32.825424194335937500, 32.792964935302734375,
+         32.760498046875000000, 32.728076934814453125,
+         32.695644378662109375, 32.663219451904296875],
+        [32.933139801025390625, 32.900619506835937500,
+         32.868103027343750000, 32.835620880126953125,
+         32.803138732910156250, 32.770652770996093750,
+         32.738170623779296875, 32.705722808837890625,
+         32.673278808593750000, 32.640830993652343750],
+        [32.911067962646484375, 32.878528594970703125,
+         32.845993041992187500, 32.813491821289062500,
+         32.780990600585937500, 32.748485565185546875,
+         32.715984344482421875, 32.683513641357421875,
+         32.651054382324218750, 32.618587493896484375],
     ])
 
     @unittest.skipIf(EPR_C_BUG_PYEPR009, 'buggy EPR_C_API detected')
@@ -1143,6 +1316,9 @@ class TestBandHighLevelAPI(unittest.TestCase):
     def setUp(self):
         self.product = epr.Product(self.PRODUCT_FILE)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_repr(self):
         pattern = ('epr.Band\((?P<name>\w+)\) of '
                    'epr.Product\((?P<product_id>\w+)\)')
@@ -1161,6 +1337,28 @@ class TestBandHighLevelAPI(unittest.TestCase):
         self.assertTrue(isinstance(str(band), str))
 
 
+class TestBandLowLevelAPI(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    FIELD_INDEX = 3
+    ELEM_INDEX = -1
+
+    def setUp(self):
+        self.product = epr.Product(self.PRODUCT_FILE)
+        self.band = self.product.get_band_at(0)
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_magic(self):
+        self.assertEqual(self.band._magic, epr._EPR_MAGIC_BAND_ID)
+
+    def test_field_index(self):
+        self.assertEqual(self.band._field_index, self.FIELD_INDEX)
+
+    def test_elem_index(self):
+        self.assertEqual(self.band._elem_index, self.ELEM_INDEX)
+
+
 class TestBandOnClosedProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     WIDTH = 12
@@ -1176,12 +1374,11 @@ class TestBandOnClosedProduct(unittest.TestCase):
     def test_product_property(self):
         self.assertTrue(isinstance(self.band.product, epr.Product))
 
-    # @TODO: check
-    #def test_properties(self):
-    #    for name in ('spectr_band_index', 'sample_model', 'data_type',
-    #                 'scaling_method', 'scaling_offset', 'scaling_factor',
-    #                 'bm_expr', 'unit', 'description', 'lines_mirrored'):
-    #        self.assertRaises(ValueError, getattr, self.band, name)
+    def test_properties(self):
+        # 'sample_model', 'data_type', 'scaling_method', 'scaling_offset',
+        # 'scaling_factor', 'bm_expr', 'unit', 'description', 'lines_mirrored'
+        for name in ('spectr_band_index',):
+            self.assertRaises(ValueError, getattr, self.band, name)
 
     def test_sample_model_property(self):
         self.assertEqual(self.band.sample_model, 0)
@@ -1210,10 +1407,6 @@ class TestBandOnClosedProduct(unittest.TestCase):
 
     def test_lines_mirrored_property(self):
         self.assertTrue(isinstance(self.band.lines_mirrored, bool))
-        # @TODO: check
-        #self.assertEqual(self.band.lines_mirrored, False)
-
-    # END: check
 
     def test_get_name(self):
         self.assertRaises(ValueError, self.product.get_band_at, 0)
@@ -1302,6 +1495,7 @@ class TestRaster(unittest.TestCase):
     RASTER_HEIGHT = TestBand.HEIGHT
     RASTER_DATA_TYPE = epr.E_TID_FLOAT
     RASTER_ELEM_SIZE = 4
+    RTOL = 1e-7
     TEST_DATA = np.zeros((10, 10))
 
     def setUp(self):
@@ -1373,7 +1567,7 @@ class TestRaster(unittest.TestCase):
         self.assertEqual(data.dtype, EPR_TO_NUMPY_TYPE[self.raster.data_type])
 
         ny, nx = self.TEST_DATA.shape
-        npt.assert_allclose(data[:ny, :nx], self.TEST_DATA)
+        npt.assert_allclose(data[:ny, :nx], self.TEST_DATA, rtol=self.RTOL)
 
     def test_data_property_two_times(self):
         data1 = self.raster.data
@@ -1401,7 +1595,7 @@ class TestRaster(unittest.TestCase):
         self.assertTrue(isinstance(data, np.ndarray))
 
         ny, nx = self.TEST_DATA.shape
-        npt.assert_allclose(data[:ny, :nx], self.TEST_DATA)
+        npt.assert_allclose(data[:ny, :nx], self.TEST_DATA, rtol=self.RTOL)
 
 
 class TestRasterRead(TestRaster):
@@ -1420,6 +1614,9 @@ class TestRasterRead(TestRaster):
         self.band.read_raster(self.RASTER_XOFFSET, self.RASTER_YOFFSET,
                               self.raster)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_data_property_shared_semantics_readload(self):
         data1 = self.raster.data
         data1[0, 0] *= 2
@@ -1436,6 +1633,7 @@ class TestAnnotatedRasterRead(TestRasterRead):
     RASTER_XOFFSET = TestAnnotationBand.XOFFSET
     RASTER_YOFFSET = TestAnnotationBand.YOFFSET
     TEST_DATA = TestAnnotationBand.TEST_DATA
+    RTOL = 1e-6
 
     @unittest.skipIf(EPR_C_BUG_PYEPR009, 'buggy EPR_C_API detected')
     def test_get_pixel(self):
@@ -1476,16 +1674,35 @@ class TestRasterHighLevelAPI(unittest.TestCase):
         self.assertTrue(isinstance(str(self.raster), str))
 
 
+class TestRasterLowLevelAPI(unittest.TestCase):
+    RASTER_WIDTH = 400
+    RASTER_HEIGHT = 300
+    RASTER_DATA_TYPE = epr.E_TID_FLOAT
+
+    def setUp(self):
+        self.raster = epr.create_raster(self.RASTER_DATA_TYPE,
+                                        self.RASTER_WIDTH, self.RASTER_HEIGHT)
+
+    def test_magic(self):
+        self.assertEqual(self.raster._magic, epr._EPR_MAGIC_RASTER)
+
+
 class TestRecord(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
     DATASET_NAME = 'Quality_ADS'
     NUM_FIELD = 21
     FIELD_NAME = 'perc_water_abs_aero'
+    TOT_SIZE = 32
+    RECORD_INDEX = 0
 
     def setUp(self):
-        product = epr.Product(self.PRODUCT_FILE)
-        dataset = product.get_dataset(self.DATASET_NAME)
-        self.record = dataset.read_record(0)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
+        self.dataset = self.product.get_dataset(self.DATASET_NAME)
+        self.record = self.dataset.read_record(self.RECORD_INDEX)
+
+    def tearDown(self):
+        self.product.close()
 
     def test_get_num_fields(self):
         self.assertEqual(self.record.get_num_fields(), self.NUM_FIELD)
@@ -1548,6 +1765,23 @@ class TestRecord(unittest.TestCase):
         index = self.record.get_num_fields() + 10
         self.assertRaises(ValueError, self.record.get_field_at, index)
 
+    def test_dataset_name(self):
+        self.assertEqual(self.record.dataset_name, self.DATASET_NAME)
+
+    def test_dataset_name_new(self):
+        record = self.dataset.create_record()
+        self.assertEqual(record.dataset_name, self.DATASET_NAME)
+
+    def test_tot_size(self):
+        self.assertEqual(self.record.tot_size, self.TOT_SIZE)
+
+    def test_index(self):
+        self.assertEqual(self.record.index, self.RECORD_INDEX)
+
+
+class TestRecordRW(TestRecord):
+    OPEN_MODE = 'rb+'
+
 
 class TestRecordHighLevelAPI(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
@@ -1577,10 +1811,13 @@ class TestRecordHighLevelAPI(unittest.TestCase):
     ]
 
     def setUp(self):
-        product = epr.Product(self.PRODUCT_FILE)
-        self.dataset = product.get_dataset(self.DATASET_NAME)
+        self.product = epr.Product(self.PRODUCT_FILE)
+        self.dataset = self.product.get_dataset(self.DATASET_NAME)
         self.record = self.dataset.read_record(0)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_get_field_names_number(self):
         self.assertEqual(len(self.record.get_field_names()),
                          self.record.get_num_fields())
@@ -1611,13 +1848,37 @@ class TestRecordHighLevelAPI(unittest.TestCase):
         self.assertTrue(isinstance(str(self.record), str))
 
 
+class TestRecordLowLevelAPI(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    RECORD_INDEX = 1
+    RECORD_SIZE = 32
+    RECORD_OFFSET = RECORD_INDEX * RECORD_SIZE
+
+    def setUp(self):
+        self.product = epr.Product(self.PRODUCT_FILE)
+        dataset = self.product.get_dataset_at(0)
+        self.record = dataset.read_record(self.RECORD_INDEX)
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_magic(self):
+        self.assertEqual(self.record._magic, epr._EPR_MAGIC_RECORD)
+
+    def test_get_offset(self):
+        self.assertEqual(self.record.get_offset(), self.RECORD_OFFSET)
+
+
 class TestMultipleRecordsHighLevelAPI(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     DATASET_NAME = TestProduct.DATASET_NAME
 
     def setUp(self):
-        product = epr.Product(self.PRODUCT_FILE)
-        self.dataset = product.get_dataset(self.DATASET_NAME)
+        self.product = epr.Product(self.PRODUCT_FILE)
+        self.dataset = self.product.get_dataset(self.DATASET_NAME)
+
+    def tearDown(self):
+        self.product.close()
 
     def test_repr(self):
         pattern = '<epr\.Record object at 0x\w+> (?P<num>\d+) fields'
@@ -1657,8 +1918,14 @@ class TestMphRecordHighLevelAPI(TestRecordHighLevelAPI):
     ]
 
     def setUp(self):
-        product = epr.Product(self.PRODUCT_FILE)
-        self.record = product.get_mph()
+        self.product = epr.Product(self.PRODUCT_FILE)
+        self.record = self.product.get_mph()
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_index(self):
+        self.assertEqual(self.record.index, None)
 
 
 class TestRecordOnClosedProduct(unittest.TestCase):
@@ -1672,11 +1939,14 @@ class TestRecordOnClosedProduct(unittest.TestCase):
         product = epr.Product(self.PRODUCT_FILE)
         dataset = product.get_dataset(self.DATASET_NAME)
         self.record = dataset.read_record(0)
+        #self.mph = product.get_mph()
         product.close()
 
-    # @TODO: check
-    #def test_get_num_fields(self):
-    #    self.assertEqual(self.record.get_num_fields(), self.NUM_FIELD)
+    def test_get_num_fields(self):
+        self.assertEqual(self.record.get_num_fields(), self.NUM_FIELD)
+
+    #def test_get_num_fields_mph(self):
+    #    self.assertEqual(self.mph.get_num_fields(), 34)
 
     def test_print_(self):
         self.assertRaises(ValueError, self.record.print_)
@@ -1708,6 +1978,7 @@ class TestRecordOnClosedProduct(unittest.TestCase):
 
 class TestField(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
     DATASET_NAME = 'Quality_ADS'
 
     FIELD_NAME = 'perc_water_abs_aero'
@@ -1717,13 +1988,17 @@ class TestField(unittest.TestCase):
     FIELD_NUM_ELEMS = 1
     FIELD_VALUES = (81,)
     FIELD_UNIT = '%'
+    #FIELD_OFFSET = 13
 
     def setUp(self):
-        product = epr.Product(self.PRODUCT_FILE)
-        dataset = product.get_dataset(self.DATASET_NAME)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
+        dataset = self.product.get_dataset(self.DATASET_NAME)
         record = dataset.read_record(0)
         self.field = record.get_field(self.FIELD_NAME)
 
+    def tearDown(self):
+        self.product.close()
+
     @quiet
     def test_print_field(self):
         self.field.print_()
@@ -1764,9 +2039,390 @@ class TestField(unittest.TestCase):
         vect = self.field.get_elems()
         self.assertTrue(isinstance(vect, np.ndarray))
         self.assertEqual(vect.shape, (self.field.get_num_elems(),))
-        self.assertEqual(vect.dtype, np.int8)
+        self.assertEqual(vect.dtype, epr.get_numpy_dtype(self.FIELD_TYPE))
         npt.assert_allclose(vect[:len(self.FIELD_VALUES)], self.FIELD_VALUES)
 
+    def test_tot_size(self):
+        elem_size = epr.get_data_type_size(self.FIELD_TYPE)
+        tot_size = elem_size * self.FIELD_NUM_ELEMS
+        self.assertEqual(self.field.tot_size, tot_size)
+
+
+class TestFieldRW(TestField):
+    OPEN_MODE = 'rb+'
+
+
+class TestFieldWriteOnReadOnly(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
+    DATASET_NAME = 'Vapour_Content'
+    RECORD_INDEX = 10
+
+    FIELD_NAME = 'wvapour_cont_pix'
+    FIELD_DESCRIPTION = 'Water Vapour Content pixel #1- #281'
+    FIELD_TYPE = epr.E_TID_UCHAR
+    FIELD_TYPE_NAME = 'uchar'
+    FIELD_UNIT = ''
+    FIELD_NUM_ELEMS = 281
+
+    def setUp(self):
+        self.filename = self.PRODUCT_FILE + '_'
+        shutil.copy(self.PRODUCT_FILE, self.filename)
+        self.product = epr.Product(self.filename, self.OPEN_MODE)
+        dataset = self.product.get_dataset(self.DATASET_NAME)
+        record = dataset.read_record(self.RECORD_INDEX)
+        self.field = record.get_field(self.FIELD_NAME)
+
+    def tearDown(self):
+        self.product.close()
+        os.unlink(self.filename)
+
+    def test_write_on_read_only_product(self):
+        value = self.field.get_elem() + 10
+        self.assertRaises(TypeError, self.field.set_elem, value)
+
+
+class TestFieldWrite(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb+'
+    REOPEN = False
+    DATASET_NAME = 'Vapour_Content'
+    RECORD_INDEX = 10
+
+    FIELD_NAME = 'wvapour_cont_pix'
+    FIELD_DESCRIPTION = 'Water Vapour Content pixel #1- #281'
+    FIELD_TYPE = epr.E_TID_UCHAR
+    FIELD_TYPE_NAME = 'uchar'
+    FIELD_UNIT = ''
+    FIELD_INDEX = 2
+    FIELD_NUM_ELEMS = 281
+    FIELD_VALUES = (
+        1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2,
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+        3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2,
+        2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 3, 3,
+        3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
+        1,
+    )
+
+    def setUp(self):
+        self.filename = self.PRODUCT_FILE + '_'
+        shutil.copy(self.PRODUCT_FILE, self.filename)
+        self.product = None
+        self.dataset = None
+        self.record = None
+        self.field = None
+        self.reopen(self.OPEN_MODE)
+        self.offset = self._get_offset()
+
+    def _get_offset(self):
+        offset = self.dataset.get_dsd().ds_offset
+        offset += self.record.index * self.record.tot_size
+        for i in range(self.FIELD_INDEX):
+            offset += self.record.get_field_at(i).tot_size
+
+        return offset
+
+    def tearDown(self):
+        self.product.close()
+        os.unlink(self.filename)
+
+    def reopen(self, mode='rb'):
+        if self.product is not None:
+            self.product.close()
+        self.product = epr.Product(self.filename, mode)
+        self.dataset = self.product.get_dataset(self.DATASET_NAME)
+        self.record = self.dataset.read_record(self.RECORD_INDEX)
+        self.field = self.record.get_field(self.FIELD_NAME)
+
+    def read(self, offset=0, size=None):
+        if size is None:
+            size = self.field.tot_size
+
+        os.lseek(self.product._fileno, self.offset + offset, os.SEEK_SET)
+
+        return os.read(self.product._fileno, size)
+
+    def test_set_elem_metadata(self):
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+        value = self.field.get_elem() + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+    def test_set_elem_data(self):
+        npt.assert_array_equal(self.field.get_elems(), self.FIELD_VALUES)
+        self.assertEqual(self.field.get_elem(), self.FIELD_VALUES[0])
+
+        value = self.field.get_elem() + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_elem(), value)
+
+        values = np.array(self.FIELD_VALUES)
+        values[0] = value
+        npt.assert_array_equal(self.field.get_elems(), values)
+
+    def test_set_elem_rawdata(self):
+        orig_data = self.read()
+
+        value = self.field.get_elem() + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        data = self.read()
+        self.assertNotEqual(data, orig_data)
+
+        dtype = epr.get_numpy_dtype(self.FIELD_TYPE)
+        data = np.fromstring(data, dtype)
+        orig_data = np.fromstring(orig_data, dtype)
+        orig_data[0] = value
+        npt.assert_array_equal(data, orig_data)
+
+    def test_set_elem0_metadata(self):
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+        value = self.field.get_elem(0) + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+    def test_set_elem0_data(self):
+        npt.assert_array_equal(self.field.get_elems(), self.FIELD_VALUES)
+        self.assertEqual(self.field.get_elem(0), self.FIELD_VALUES[0])
+
+        value = self.field.get_elem(0) + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_elem(0), value)
+
+        values = np.array(self.FIELD_VALUES)
+        values[0] = value
+        npt.assert_array_equal(self.field.get_elems(), values)
+
+    def test_set_elem0_rawdata(self):
+        orig_data = self.read()
+
+        value = self.field.get_elem(0) + 10
+        self.field.set_elem(value)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        data = self.read()
+        self.assertNotEqual(data, orig_data)
+
+        dtype = epr.get_numpy_dtype(self.FIELD_TYPE)
+        data = np.fromstring(data, dtype)
+        orig_data = np.fromstring(orig_data, dtype)
+        orig_data[0] = value
+        npt.assert_array_equal(data, orig_data)
+
+    def test_set_elem20_metadata(self):
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+        value = self.field.get_elem(20) + 1
+        self.field.set_elem(value, 20)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+    def test_set_elem20_data(self):
+        npt.assert_array_equal(self.field.get_elems(), self.FIELD_VALUES)
+        self.assertEqual(self.field.get_elem(20), self.FIELD_VALUES[20])
+
+        value = self.field.get_elem(20) + 1
+        self.field.set_elem(value, 20)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_elem(20), value)
+
+        values = np.array(self.FIELD_VALUES)
+        values[20] = value
+        npt.assert_array_equal(self.field.get_elems(), values)
+
+    def test_set_elem20_rawdata(self):
+        orig_data = self.read()
+
+        value = self.field.get_elem(20) + 1
+        self.field.set_elem(value, 20)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        data = self.read()
+        self.assertNotEqual(data, orig_data)
+
+        dtype = epr.get_numpy_dtype(self.FIELD_TYPE)
+        data = np.fromstring(data, dtype)
+        orig_data = np.fromstring(orig_data, dtype)
+        orig_data[20] = value
+        npt.assert_array_equal(data, orig_data)
+
+    def test_set_elems_metadata(self):
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+        values = self.field.get_elems() + 1
+        self.field.set_elems(values)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        self.assertEqual(self.field.get_description(), self.FIELD_DESCRIPTION)
+        self.assertEqual(self.field.get_num_elems(), self.FIELD_NUM_ELEMS)
+        self.assertEqual(self.field.get_type(), self.FIELD_TYPE)
+        self.assertEqual(epr.data_type_id_to_str(self.field.get_type()),
+                         self.FIELD_TYPE_NAME)
+        self.assertEqual(self.field.get_unit(), self.FIELD_UNIT)
+
+    def test_set_elems_data(self):
+        npt.assert_array_equal(self.field.get_elems(), self.FIELD_VALUES)
+
+        values = self.field.get_elems() + 1
+        self.field.set_elems(values)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        npt.assert_array_equal(self.field.get_elems(), values)
+
+    def test_set_elems_rawdata(self):
+        orig_data = self.read()
+
+        values = self.field.get_elems() + 1
+        self.field.set_elems(values)
+
+        if self.REOPEN:
+            self.reopen()
+        else:
+            self.product.flush()
+
+        data = self.read()
+        self.assertNotEqual(data, orig_data)
+
+        dtype = epr.get_numpy_dtype(self.FIELD_TYPE)
+        data = np.fromstring(data, dtype)
+        orig_data = np.fromstring(orig_data, dtype)
+        orig_data += 1
+        npt.assert_array_equal(data, orig_data)
+
+    def test_set_mph_elem(self):
+        mph = self.product.get_mph()
+        field = mph.get_field_at(3)
+        self.assertRaises(NotImplementedError, field.set_elem, 5)
+
+
+class TestFieldWriteReopen(TestFieldWrite):
+    REOPEN = True
+
+
+class TestTimeField(TestField):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    DATASET_NAME = 'Quality_ADS'
+
+    FIELD_NAME = 'dsr_time'
+    FIELD_DESCRIPTION = 'Start time of the measurement'
+    FIELD_TYPE = epr.E_TID_TIME
+    FIELD_TYPE_NAME = 'time'
+    FIELD_NUM_ELEMS = 1
+    FIELD_VALUES = (epr.EPRTime(days=171, seconds=38598, microseconds=260634),)
+    FIELD_UNIT = 'MJD'
+
+    def test_get_elems(self):
+        vect = self.field.get_elems()
+        self.assertTrue(isinstance(vect, np.ndarray))
+        self.assertEqual(vect.shape, (self.field.get_num_elems(),))
+        self.assertEqual(vect.dtype, epr.MJD)
+        value = self.FIELD_VALUES[0]
+        self.assertEqual(vect[0]['days'], value.days)
+        self.assertEqual(vect[0]['seconds'], value.seconds)
+        self.assertEqual(vect[0]['microseconds'], value.microseconds)
+
 
 class TestFieldWithMiltipleElems(TestField):
     DATASET_NAME = TestProduct.DATASET_NAME
@@ -1869,13 +2525,37 @@ class TestFieldHighLevelAPI2(unittest.TestCase):
         field = record.get_field('spare_1')
         self.assertEqual(len(field), field.get_num_elems())
 
+    # @TODO: no e_tid_string field available
     #def test_len_e_tid_string(self):
     #    dataset = self.product.get_dataset_at(0)
     #    record = dataset.read_record(0)
-    #    field = record.get_field('filter_window')
+    #    field = record.get_field('???')
     #    self.assertEqual(len(field), len(field.get_elem()))
 
 
+class TestFieldLowLevelAPI(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    DATASET_INDEX = 0
+    RECORD_INDEX = 0
+    FIELD_NAME = 'perc_water_abs_aero'
+    FIELD_OFFSET = 13
+
+    def setUp(self):
+        self.product = epr.Product(self.PRODUCT_FILE)
+        dataset = self.product.get_dataset_at(self.DATASET_INDEX)
+        record = dataset.read_record(self.RECORD_INDEX)
+        self.field = record.get_field(self.FIELD_NAME)
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_magic(self):
+        self.assertEqual(self.field._magic, epr._EPR_MAGIC_FIELD)
+
+    def test_get_offset(self):
+        self.assertEqual(self.field.get_offset(), self.FIELD_OFFSET)
+
+
 class TestFieldOnClosedProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     DATASET_NAME = 'Quality_ADS'
@@ -1931,6 +2611,7 @@ class TestFieldOnClosedProduct(unittest.TestCase):
 
 class TestDSD(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+    OPEN_MODE = 'rb'
     DSD_INDEX = 0
     DS_NAME = 'Quality ADS'
     DS_OFFSET = 12869
@@ -1940,9 +2621,12 @@ class TestDSD(unittest.TestCase):
     NUM_DSR = 5
 
     def setUp(self):
-        self.product = epr.Product(self.PRODUCT_FILE)
+        self.product = epr.Product(self.PRODUCT_FILE, self.OPEN_MODE)
         self.dsd = self.product.get_dsd_at(self.DSD_INDEX)
 
+    def tearDown(self):
+        self.product.close()
+
     def test_index(self):
         self.assertEqual(self.dsd.index, self.DSD_INDEX)
         self.assertTrue(isinstance(self.dsd.index, int))
@@ -1998,6 +2682,10 @@ class TestDSD(unittest.TestCase):
         self.assertTrue(self.dsd != self.product)
 
 
+class TestDSDRW(TestDSD):
+    OPEN_MODE = 'rb+'
+
+
 class TestDsdHighLevelAPI(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
 
@@ -2018,6 +2706,21 @@ class TestDsdHighLevelAPI(unittest.TestCase):
         self.assertTrue(isinstance(str(self.dsd), str))
 
 
+class TestDsdLowLevelAPI(unittest.TestCase):
+    PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
+
+    def setUp(self):
+        self.product = epr.Product(self.PRODUCT_FILE)
+        self.dsd = self.product.get_dsd_at(0)
+
+    def tearDown(self):
+        self.product.close()
+
+    def test_magic(self):
+        #self.assertEqual(self.dsd._magic, epr._EPR_MAGIC_DSD_ID)
+        self.assertTrue(isinstance(self.dsd._magic, int))
+
+
 class TestDSDOnCloserProduct(unittest.TestCase):
     PRODUCT_FILE = os.path.join(TESTDIR, TEST_PRODUCT)
     DSD_INDEX = 0
@@ -2096,6 +2799,21 @@ class TestDataypeFunctions(unittest.TestCase):
         epr.E_TID_TIME:    12,
     }
 
+    TYPE_MAP = {
+        epr.E_TID_UNKNOWN: None,
+        epr.E_TID_UCHAR:   np.uint8,
+        epr.E_TID_CHAR:    np.int8,
+        epr.E_TID_USHORT:  np.uint16,
+        epr.E_TID_SHORT:   np.int16,
+        epr.E_TID_UINT:    np.uint32,
+        epr.E_TID_INT:     np.int32,
+        epr.E_TID_FLOAT:   np.float32,
+        epr.E_TID_DOUBLE:  np.float64,
+        epr.E_TID_STRING:  np.bytes_,
+        epr.E_TID_SPARE:   None,
+        epr.E_TID_TIME:    epr.MJD,
+    }
+
     def test_data_type_id_to_str(self):
         for type_id, type_name in self.TYPE_NAMES.items():
             self.assertEqual(epr.data_type_id_to_str(type_id), type_name)
@@ -2110,6 +2828,14 @@ class TestDataypeFunctions(unittest.TestCase):
     def test_get_data_type_size_invalid(self):
         self.assertEqual(epr.get_data_type_size(500), 0)
 
+    def test_epr_to_numpy_dtype(self):
+        for epr_type in self.TYPE_MAP:
+            #with self.subTest(epr_type=epr_type): # TODO: update (new in 3.4)
+            #    self.assertEqual(
+            #        epr.get_numpy_dtype(epr_type), self.TYPE_MAP[epr_type])
+            self.assertEqual(
+                epr.get_numpy_dtype(epr_type), self.TYPE_MAP[epr_type])
+
 
 class TestScalingMethodFunctions(unittest.TestCase):
     METHOD_NAMES = {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pyepr.git



More information about the Pkg-grass-devel mailing list