[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