[Git][debian-gis-team/pyspectral][upstream] New upstream version 0.9.0+ds
Antonio Valentino
gitlab at salsa.debian.org
Mon Sep 2 07:14:29 BST 2019
Antonio Valentino pushed to branch upstream at Debian GIS Project / pyspectral
Commits:
bdb76ea7 by Antonio Valentino at 2019-09-02T05:46:16Z
New upstream version 0.9.0+ds
- - - - -
18 changed files:
- CHANGELOG.md
- README.md
- doc/37_reflectance.rst
- doc/rad_definitions.rst
- pyspectral/blackbody.py
- pyspectral/config.py
- pyspectral/etc/pyspectral.yaml
- pyspectral/rsr_reader.py
- pyspectral/tests/test_atm_correction_ir.py
- pyspectral/tests/test_blackbody.py
- pyspectral/tests/test_rad_tb_conversions.py
- pyspectral/tests/test_reflectance.py
- pyspectral/tests/test_utils.py
- pyspectral/utils.py
- pyspectral/version.py
- rsr_convert_scripts/README.rst
- + rsr_convert_scripts/agri_rsr.py
- + rsr_convert_scripts/virr_rsr.py
Changes:
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,27 @@
+## Version <RELEASE_VERSION> (2019/08/30)
+
+### Issues Closed
+
+* [Issue 73](https://github.com/pytroll/pyspectral/issues/73) - Fix blackbody code to work with dask arrays ([PR 74](https://github.com/pytroll/pyspectral/pull/74))
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 80](https://github.com/pytroll/pyspectral/pull/80) - Fix doc tests for python 2&3
+* [PR 79](https://github.com/pytroll/pyspectral/pull/79) - Fix rsr zenodo version
+* [PR 74](https://github.com/pytroll/pyspectral/pull/74) - Fix dask compatibility in blackbody functions ([73](https://github.com/pytroll/pyspectral/issues/73))
+
+#### Features added
+
+* [PR 78](https://github.com/pytroll/pyspectral/pull/78) - Add FY-3B VIRR and FY-3C VIRR RSRs
+* [PR 77](https://github.com/pytroll/pyspectral/pull/77) - Add FY-4A AGRI support
+
+In this release 5 pull requests were closed.
+
+
## Version <RELEASE_VERSION> (2019/06/07)
### Issues Closed
=====================================
README.md
=====================================
@@ -1,14 +1,11 @@
PySpectral
==========
-[![Codacy Badge](https://api.codacy.com/project/badge/Grade/9f039d7d640846ca89be8a78fa11e1f6)](https://www.codacy.com/app/adybbroe/pyspectral?utm_source=github.com&utm_medium=referral&utm_content=pytroll/pyspectral&utm_campaign=badger)
[![Build Status](https://travis-ci.org/pytroll/pyspectral.png?branch=master)](https://travis-ci.org/pytroll/pyspectral)
[![Build status](https://ci.appveyor.com/api/projects/status/5lm42n0l65l5o9xn?svg=true)](https://ci.appveyor.com/project/pytroll/pyspectral)
[![Coverage Status](https://coveralls.io/repos/github/pytroll/pyspectral/badge.svg?branch=master)](https://coveralls.io/github/pytroll/pyspectral?branch=master)
-[![Code Health](https://landscape.io/github/pytroll/pyspectral/master/landscape.png)](https://landscape.io/github/pytroll/pyspectral/master)
[![PyPI version](https://badge.fury.io/py/pyspectral.svg)](https://badge.fury.io/py/pyspectral)
[![Code Climate](https://codeclimate.com/github/pytroll/pyspectral/badges/gpa.svg)](https://codeclimate.com/github/pytroll/pyspectral)
-[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pytroll/pyspectral/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pytroll/pyspectral/?branch=master)
Given a passive sensor on a meteorological satellite PySpectral provides the
relative spectral response (rsr) function(s) and offer you some basic
=====================================
doc/37_reflectance.rst
=====================================
@@ -46,7 +46,7 @@ expressed in :math:`W/m^2 sr^{-1} \mu m^{-1}`, or using SI units :math:`W/m^2 sr
>>> viirs = RadTbConverter('Suomi-NPP', 'viirs', 'M12')
>>> rad37 = viirs.tb2radiance(tb37)
>>> print([np.round(rad, 7) for rad in rad37['radiance']])
- [369717.4765726, 355110.5207853, 314684.2788726, 173143.5424898, 116408.0007877]
+ [369717.4972296, 355110.6414922, 314684.3507084, 173143.4836477, 116408.0022674]
>>> rad37['unit']
'W/m^2 sr^-1 m^-1'
@@ -59,7 +59,7 @@ In order to get the total radiance over the band one has to multiply with the eq
>>> viirs = RadTbConverter('Suomi-NPP', 'viirs', 'M12')
>>> rad37 = viirs.tb2radiance(tb37, normalized=False)
>>> print([np.round(rad, 8) for rad in rad37['radiance']])
- [0.07037968, 0.06759909, 0.05990352, 0.03295972, 0.02215951]
+ [0.07037968, 0.06759911, 0.05990353, 0.03295971, 0.02215951]
>>> rad37['unit']
'W/m^2 sr^-1'
@@ -218,17 +218,17 @@ We can try decompose equation :eq:`refl37` above using the example of VIIRS M12
>>> print(np.isnan(nomin))
[False False False False False]
>>> print([np.round(val, 8) for val in nomin])
- [0.05083677, 0.04805618, 0.0404157, 0.01279279, 0.00204485]
+ [0.05083677, 0.0480562, 0.04041571, 0.01279277, 0.00204485]
>>> denom = np.cos(np.deg2rad(sunz))/np.pi * sflux - rad11['radiance']
>>> print(np.isnan(denom))
[False False False False False]
>>> print([np.round(val, 8) for val in denom])
- [0.23646313, 0.23645682, 0.23650559, 0.23582015, 0.2358661]
+ [0.23646312, 0.23645681, 0.23650559, 0.23582014, 0.23586609]
>>> res = nomin/denom
>>> print(np.isnan(res))
[False False False False False]
>>> print([np.round(val, 8) for val in res])
- [0.21498817, 0.2032345, 0.17088689, 0.05424807, 0.00866955]
+ [0.21498817, 0.20323458, 0.17088693, 0.05424801, 0.00866952]
Derive the emissive part of the 3.7 micron band
@@ -255,5 +255,5 @@ Using the example of the VIIRS M12 band from above this gives the following spec
>>> ['{tb:6.3f}'.format(tb=np.round(t, 4)) for t in tb]
['266.996', '267.262', '267.991', '271.033', '271.927']
>>> rad = refl_m12.emissive_part_3x(tb=False)
- >>> ['{rad:6.3f}'.format(rad=np.round(r, 3)) for r in rad]
- ['80285.149', '81458.022', '84749.639', '99761.400', '104582.030']
+ >>> ['{rad:6.3f}'.format(rad=np.round(r, 3)) for r in rad.compute()]
+ ['80285.150', '81458.022', '84749.638', '99761.401', '104582.031']
=====================================
doc/rad_definitions.rst
=====================================
@@ -229,7 +229,7 @@ And using wavelength representation:
>>> wvl = 1./wavenumber
>>> rad = blackbody(wvl, [300., 301])
>>> print("{0:10.3f} {1:10.3f}".format(rad[0], rad[1]))
- 9573178.886 9714689.259
+ 9573177.494 9714687.157
Which are the spectral radiances in SI units around :math:`11 \mu m` at
temperatures 300 and 301 Kelvin. In units of :math:`mW/m^2\ m^{-1} sr^{-1}` this becomes:
=====================================
pyspectral/blackbody.py
=====================================
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2013-2018 Adam.Dybbroe
+# Copyright (c) 2013-2019 Adam.Dybbroe
# Author(s):
@@ -23,8 +23,13 @@
"""Planck radiation equation"""
import numpy as np
-
import logging
+
+try:
+ import dask.array as da
+except ImportError:
+ da = np
+
LOG = logging.getLogger(__name__)
H_PLANCK = 6.62606957 * 1e-34 # SI-unit = [J*s]
@@ -84,38 +89,39 @@ def blackbody_wn_rad2temp(wavenumber, radiance):
function. Wavenumber space"""
if np.isscalar(radiance):
- rad = np.array([radiance, ], dtype='float64')
- else:
- rad = np.array(radiance, dtype='float64')
+ radiance = np.array([radiance], dtype='float64')
+ elif isinstance(radiance, (list, tuple)):
+ radiance = np.array(radiance, dtype='float64')
if np.isscalar(wavenumber):
- wavnum = np.array([wavenumber, ], dtype='float64')
- else:
+ wavnum = np.array([wavenumber], dtype='float64')
+ elif isinstance(wavenumber, (list, tuple)):
wavnum = np.array(wavenumber, dtype='float64')
const1 = H_PLANCK * C_SPEED / K_BOLTZMANN
const2 = 2 * H_PLANCK * C_SPEED**2
- res = const1 * wavnum / np.log(np.divide(const2 * wavnum**3, rad) + 1.0)
+ res = const1 * wavnum / np.log(
+ np.divide(const2 * wavnum**3, radiance) + 1.0)
- shape = rad.shape
+ shape = radiance.shape
resshape = res.shape
if wavnum.shape[0] == 1:
- if rad.shape[0] == 1:
+ if radiance.shape[0] == 1:
return res[0]
else:
return res[::].reshape(shape)
else:
- if rad.shape[0] == 1:
+ if radiance.shape[0] == 1:
return res[0, :]
else:
if len(shape) == 1:
- return np.reshape(res, (shape[0], resshape[1]))
+ return res.reshape((shape[0], resshape[1]))
else:
- return np.reshape(res, (shape[0], shape[1], resshape[1]))
+ return res.reshape((shape[0], shape[1], resshape[1]))
-def planck(wave, temp, wavelength=True):
- """The Planck radiation or Blackbody radiation as a function of wavelength
+def planck(wave, temperature, wavelength=True):
+ """The Planck radiation or Blackbody radiation as a function of wavelength
or wavenumber. SI units.
_planck(wave, temperature, wavelength=True)
wave = Wavelength/wavenumber or a sequence of wavelengths/wavenumbers (m or m^-1)
@@ -136,12 +142,12 @@ def planck(wave, temp, wavelength=True):
units = ['wavelengths', 'wavenumbers']
if wavelength:
LOG.debug("Using {0} when calculating the Blackbody radiance".format(
- units[(wavelength == True) - 1]))
+ units[(wavelength is True) - 1]))
- if np.isscalar(temp):
- temperature = np.array([temp, ], dtype='float64')
- else:
- temperature = np.array(temp, dtype='float64')
+ if np.isscalar(temperature):
+ temperature = np.array([temperature, ], dtype='float64')
+ elif isinstance(temperature, (list, tuple)):
+ temperature = np.array(temperature, dtype='float64')
shape = temperature.shape
if np.isscalar(wave):
@@ -157,13 +163,19 @@ def planck(wave, temp, wavelength=True):
nom = 2 * H_PLANCK * (C_SPEED ** 2) * (wln ** 3)
arg1 = H_PLANCK * C_SPEED * wln / K_BOLTZMANN
- arg2 = np.where(np.greater(np.abs(temperature), EPSILON),
- np.array(1. / temperature), -9).reshape(-1, 1)
- arg2 = np.ma.masked_array(arg2, mask=arg2 == -9)
- LOG.debug("Max and min - arg1: %s %s", str(arg1.max()), str(arg1.min()))
- LOG.debug("Max and min - arg2: %s %s", str(arg2.max()), str(arg2.min()))
+ # use dask functions when needed
+ np_ = np if isinstance(temperature, np.ndarray) else da
+ arg2 = np_.where(np.greater(np.abs(temperature), EPSILON),
+ (1. / temperature), np.nan).reshape(-1, 1)
+ if isinstance(arg2, np.ndarray):
+ # don't compute min/max if we have dask arrays
+ LOG.debug("Max and min - arg1: %s %s",
+ str(np.nanmax(arg1)), str(np.nanmin(arg1)))
+ LOG.debug("Max and min - arg2: %s %s",
+ str(np.nanmax(arg2)), str(np.nanmin(arg2)))
+
try:
- exp_arg = np.multiply(arg1.astype('float32'), arg2.astype('float32'))
+ exp_arg = np.multiply(arg1.astype('float64'), arg2.astype('float64'))
except MemoryError:
LOG.warning(("Dimensions used in numpy.multiply probably reached "
"limit!\n"
@@ -171,9 +183,9 @@ def planck(wave, temp, wavelength=True):
"and try running again"))
raise
- LOG.debug("Max and min before exp: %s %s", str(exp_arg.max()),
- str(exp_arg.min()))
- if exp_arg.min() < 0:
+ if isinstance(exp_arg, np.ndarray) and exp_arg.min() < 0:
+ LOG.debug("Max and min before exp: %s %s",
+ str(exp_arg.max()), str(exp_arg.min()))
LOG.warning("Something is fishy: \n" +
"\tDenominator might be zero or negative in radiance derivation:")
dubious = np.where(exp_arg < 0)[0]
@@ -182,7 +194,6 @@ def planck(wave, temp, wavelength=True):
denom = np.exp(exp_arg) - 1
rad = nom / denom
- rad = np.where(rad.mask, np.nan, rad.data)
radshape = rad.shape
if wln.shape[0] == 1:
if temperature.shape[0] == 1:
@@ -194,9 +205,9 @@ def planck(wave, temp, wavelength=True):
return rad[0, :]
else:
if len(shape) == 1:
- return np.reshape(rad, (shape[0], radshape[1]))
+ return rad.reshape((shape[0], radshape[1]))
else:
- return np.reshape(rad, (shape[0], shape[1], radshape[1]))
+ return rad.reshape((shape[0], shape[1], radshape[1]))
def blackbody_wn(wavenumber, temp):
=====================================
pyspectral/config.py
=====================================
@@ -28,7 +28,12 @@ import os
from os.path import expanduser
from appdirs import AppDirs
import yaml
-from collections import Mapping
+try:
+ # python 3.3+
+ from collections.abc import Mapping
+except ImportError:
+ # deprecated (above can't be done in 2.7)
+ from collections import Mapping
import pkg_resources
try:
=====================================
pyspectral/etc/pyspectral.yaml
=====================================
@@ -209,6 +209,46 @@ download_from_internet: True
# ch15: GOES-R_ABI_FM2_SRF_CWG_ch15.txt
# ch16: GOES-R_ABI_FM2_SRF_CWG_ch16.txt
+# FY-4A-agri:
+# path: /path/to/original/fy4a/agri/data
+# ch1: FY4A_AGRI_SRF_CH01.txt
+# ch2: FY4A_AGRI_SRF_CH02.txt
+# ch3: FY4A_AGRI_SRF_CH03.txt
+# ch4: FY4A_AGRI_SRF_CH04.txt
+# ch5: FY4A_AGRI_SRF_CH05.txt
+# ch6: FY4A_AGRI_SRF_CH06.txt
+# ch7: FY4A_AGRI_SRF_CH07.txt
+# ch8: FY4A_AGRI_SRF_CH08.txt
+# ch9: FY4A_AGRI_SRF_CH09.txt
+# ch10: FY4A_AGRI_SRF_CH10.txt
+# ch11: FY4A_AGRI_SRF_CH11.txt
+# ch12: FY4A_AGRI_SRF_CH12.txt
+# ch13: FY4A_AGRI_SRF_CH13.txt
+# ch14: FY4A_AGRI_SRF_CH14.txt
+
+#FY-3B-virr:
+# path: /Users/davidh/repos/git/pyspectral/virr_srf/FY3B-VIRR
+# ch1: ch1.prn
+# ch2: ch2.prn
+# ch3: ch3.prn
+# ch4: ch4.prn
+# ch5: ch5.prn
+# ch6: ch6.prn
+# ch7: ch7.prn
+# ch8: ch8.prn
+# ch9: ch9.prn
+# ch10: ch10.prn
+
+#FY-3C-virr:
+# path: /Users/davidh/repos/git/pyspectral/virr_srf/FY3C_VIRR_SRF
+# ch1: FY3C_VIRR_CH01.txt
+# ch2: FY3C_VIRR_CH02.txt
+# ch6: FY3C_VIRR_CH06.txt
+# ch7: FY3C_VIRR_CH07.txt
+# ch8: FY3C_VIRR_CH08.txt
+# ch9: FY3C_VIRR_CH09.txt
+# ch10: FY3C_VIRR_CH10.txt
+
# FY-3D-mersi-2:
# path: /path/to/original/fy3d/mersi2/data
# ch1: FY3D_MERSI_SRF_CH01_Pub.txt
=====================================
pyspectral/rsr_reader.py
=====================================
@@ -27,16 +27,15 @@ import os
import numpy as np
from glob import glob
from os.path import expanduser
-
-import logging
-LOG = logging.getLogger(__name__)
-
from pyspectral.config import get_config
from pyspectral.utils import WAVE_NUMBER
from pyspectral.utils import WAVE_LENGTH
from pyspectral.utils import (INSTRUMENTS, download_rsr)
from pyspectral.utils import (RSR_DATA_VERSION_FILENAME, RSR_DATA_VERSION)
+import logging
+LOG = logging.getLogger(__name__)
+
class RSRDataBaseClass(object):
@@ -168,11 +167,18 @@ class RelativeSpectralResponse(RSRDataBaseClass):
no_detectors_message = False
with h5py.File(self.filename, 'r') as h5f:
- self.band_names = [b.decode('utf-8') for b in h5f.attrs['band_names'].tolist()]
- self.description = h5f.attrs['description'].decode('utf-8')
+ self.band_names = h5f.attrs['band_names'].tolist()
+ self.description = h5f.attrs['description']
+ if not isinstance(self.band_names[0], str):
+ # byte array in python 3
+ self.band_names = [x.decode('utf-8') for x in self.band_names]
+ self.description = self.description.decode('utf-8')
+
if not self.platform_name:
try:
- self.platform_name = h5f.attrs['platform_name'].decode('utf-8')
+ self.platform_name = h5f.attrs['platform_name']
+ if not isinstance(self.platform_name, str):
+ self.platform_name = self.platform_name.decode('utf-8')
except KeyError:
LOG.warning("No platform_name in HDF5 file")
try:
@@ -186,6 +192,8 @@ class RelativeSpectralResponse(RSRDataBaseClass):
if not self.instrument:
try:
self.instrument = h5f.attrs['sensor'].decode('utf-8')
+ if not isinstance(self.instrument, str):
+ self.instrument = self.instrument.decode('utf-8')
except KeyError:
LOG.warning("No sensor name specified in HDF5 file")
self.instrument = INSTRUMENTS.get(self.platform_name)
=====================================
pyspectral/tests/test_atm_correction_ir.py
=====================================
@@ -1,11 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2017 Adam.Dybbroe
+# Copyright (c) 2017 - 2019 Pytroll
# Author(s):
-# Adam.Dybbroe <a000680 at c20671.ad.smhi.se>
+# Adam.Dybbroe <adam.dybbroe at smhi.se>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,27 +19,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""Unit tests of the atmospherical correction in the ir spectral range
-"""
+"""Unit tests of the atmospherical correction in the ir spectral range."""
+import numpy as np
+from pyspectral.atm_correction_ir import AtmosphericalCorrection
import sys
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
-import numpy as np
-from pyspectral.atm_correction_ir import AtmosphericalCorrection
-#from mock import patch
-
-from pyspectral.tests.unittest_helpers import assertNumpyArraysEqual
-
-# Mock some modules, so we don't need them for tests.
-
-#sys.modules['pyresample'] = MagicMock()
-
SATZ = np.ma.array([[48.03, 48.03002, 48.03004, 48.03006, 48.03008, 48.0301,
48.03012, 48.03014, 48.03016, 48.03018],
[48.09, 48.09002, 48.09004, 48.09006, 48.09008, 48.0901,
@@ -125,28 +115,18 @@ RES = np.ma.array([[286.03159412, 286.03162417, 286.03165421, 286.03168426,
class TestAtmCorrection(unittest.TestCase):
-
- """Class for testing pyspectral.atm_correction_ir"""
-
- def setUp(self):
- """Setup the test"""
- pass
+ """Class for testing pyspectral.atm_correction_ir."""
def test_get_correction(self):
"""Test getting the atm correction"""
this = AtmosphericalCorrection('EOS-Terra', 'modis')
atm_corr = this.get_correction(SATZ, None, TBS)
- assertNumpyArraysEqual(TBS, atm_corr)
-
- def tearDown(self):
- """Clean up"""
- pass
+ np.testing.assert_almost_equal(TBS, atm_corr)
def suite():
- """The test suite for test_atm_correction_ir.
- """
+ """Create the test suite for test_atm_correction_ir."""
loader = unittest.TestLoader()
mysuite = unittest.TestSuite()
mysuite.addTest(loader.loadTestsFromTestCase(TestAtmCorrection))
=====================================
pyspectral/tests/test_blackbody.py
=====================================
@@ -1,11 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2013, 2014, 2015, 2016, 2017 Adam.Dybbroe
+# Copyright (c) 2013 - 2019 Pytroll
# Author(s):
-# Adam.Dybbroe <a000680 at c14526.ad.smhi.se>
+# Adam.Dybbroe <adam.dybbroe at smhi.se>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,10 +31,8 @@ from pyspectral.tests.unittest_helpers import assertNumpyArraysEqual
import unittest
import numpy as np
-#RAD_11MICRON_300KELVIN = 9572498.1141643394
-RAD_11MICRON_300KELVIN = 9573177.8811719529
-#RAD_11MICRON_301KELVIN = 9713997.9623772576
-RAD_11MICRON_301KELVIN = 9714688.2959563732
+RAD_11MICRON_300KELVIN = 9573176.935507433
+RAD_11MICRON_301KELVIN = 9714686.576498277
# Radiances in wavenumber space (SI-units)
WN_RAD_11MICRON_300KELVIN = 0.00115835441353
@@ -43,18 +41,28 @@ WN_RAD_11MICRON_301KELVIN = 0.00117547716523
__unittest = True
-class TestBlackbody(unittest.TestCase):
+class CustomScheduler(object):
+ """Custom dask scheduler that raises an exception if dask is computed too many times."""
+
+ def __init__(self, max_computes=1):
+ """Set starting and maximum compute counts."""
+ self.max_computes = max_computes
+ self.total_computes = 0
+
+ def __call__(self, dsk, keys, **kwargs):
+ """Compute dask task and keep track of number of times we do so."""
+ import dask
+ self.total_computes += 1
+ if self.total_computes > self.max_computes:
+ raise RuntimeError("Too many dask computations were scheduled: {}".format(self.total_computes))
+ return dask.get(dsk, keys, **kwargs)
- """Unit testing the blackbody function"""
- def setUp(self):
- """Set up"""
- return
+class TestBlackbody(unittest.TestCase):
+ """Unit testing the blackbody function"""
def test_blackbody(self):
- """Calculate the blackbody radiation from wavelengths and
- temperatures
- """
+ """Calculate the blackbody radiation from wavelengths and temperatures."""
wavel = 11. * 1E-6
black = blackbody((wavel, ), [300., 301])
self.assertEqual(black.shape[0], 2)
@@ -71,14 +79,28 @@ class TestBlackbody(unittest.TestCase):
tb_therm = np.array([[300., 301], [299, 298], [279, 286]])
black = blackbody((10. * 1E-6, 11.e-6), tb_therm)
+ self.assertIsInstance(black, np.ndarray)
tb_therm = np.array([[300., 301], [0., 298], [279, 286]])
black = blackbody((10. * 1E-6, 11.e-6), tb_therm)
+ self.assertIsInstance(black, np.ndarray)
+
+ def test_blackbody_dask(self):
+ """Calculate the blackbody radiation from wavelengths and temperatures with dask arrays."""
+ import dask
+ import dask.array as da
+ tb_therm = da.from_array([[300., 301], [299, 298], [279, 286]], chunks=2)
+ with dask.config.set(scheduler=CustomScheduler(0)):
+ black = blackbody((10. * 1E-6, 11.e-6), tb_therm)
+ self.assertIsInstance(black, da.Array)
+
+ tb_therm = da.from_array([[300., 301], [0., 298], [279, 286]], chunks=2)
+ with dask.config.set(scheduler=CustomScheduler(0)):
+ black = blackbody((10. * 1E-6, 11.e-6), tb_therm)
+ self.assertIsInstance(black, da.Array)
def test_blackbody_wn(self):
- """Calculate the blackbody radiation from wavenumbers and
- temperatures
- """
+ """Calculate the blackbody radiation from wavenumbers and temperatures."""
wavenumber = 90909.1 # 11 micron band
black = blackbody_wn((wavenumber, ), [300., 301])
self.assertEqual(black.shape[0], 2)
@@ -106,9 +128,24 @@ class TestBlackbody(unittest.TestCase):
assertNumpyArraysEqual(t__, expected)
- def tearDown(self):
- """Clean up"""
- return
+ def test_blackbody_wn_dask(self):
+ """Test that blackbody rad2temp preserves dask arrays."""
+ import dask
+ import dask.array as da
+ wavenumber = 90909.1 # 11 micron band
+ radiances = da.from_array([0.001, 0.0009, 0.0012, 0.0018], chunks=2).reshape(2, 2)
+ with dask.config.set(scheduler=CustomScheduler(0)):
+ t__ = blackbody_wn_rad2temp(wavenumber, radiances)
+ self.assertIsInstance(t__, da.Array)
+ t__ = t__.compute()
+ expected = np.array([290.3276916, 283.76115441,
+ 302.4181330, 333.1414164]).reshape(2, 2)
+ self.assertAlmostEqual(t__[1, 1], expected[1, 1], 5)
+ self.assertAlmostEqual(t__[0, 0], expected[0, 0], 5)
+ self.assertAlmostEqual(t__[0, 1], expected[0, 1], 5)
+ self.assertAlmostEqual(t__[1, 0], expected[1, 0], 5)
+
+ assertNumpyArraysEqual(t__, expected)
def suite():
=====================================
pyspectral/tests/test_rad_tb_conversions.py
=====================================
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2014-2018 Adam.Dybbroe
+# Copyright (c) 2014-2019 Adam.Dybbroe
# Author(s):
@@ -347,10 +347,10 @@ class TestRadTbConversions(unittest.TestCase):
self.assertTrue(np.allclose(TRUE_RADS * integral, res['radiance']))
res = self.modis.tb2radiance(237., lut=False)
- self.assertAlmostEqual(16570.592171157, res['radiance'])
+ self.assertAlmostEqual(16570.579551068, res['radiance'])
res = self.modis.tb2radiance(277., lut=False)
- self.assertAlmostEqual(167544.3823631, res['radiance'])
+ self.assertAlmostEqual(167544.39368663222, res['radiance'])
res = self.modis.tb2radiance(1.1, lut=False)
self.assertAlmostEqual(0.0, res['radiance'])
@@ -362,7 +362,7 @@ class TestRadTbConversions(unittest.TestCase):
self.assertAlmostEqual(5.3940515573e-06, res['radiance'])
res = self.modis.tb2radiance(200.1, lut=False)
- self.assertAlmostEqual(865.09776189, res['radiance'])
+ self.assertAlmostEqual(865.09759706, res['radiance'])
def tearDown(self):
"""Clean up"""
=====================================
pyspectral/tests/test_reflectance.py
=====================================
@@ -139,7 +139,10 @@ class TestReflectance(unittest.TestCase):
with patch('pyspectral.radiance_tb_conversion.RelativeSpectralResponse') as mymock:
instance = mymock.return_value
- instance.rsr = TEST_RSR
+ # VIIRS doesn't have a channel '20' like MODIS so the generic
+ # mapping this test will end up using will find 'ch20' for VIIRS
+ viirs_rsr = {'ch20': TEST_RSR['20'], '99': TEST_RSR['99']}
+ instance.rsr = viirs_rsr
instance.unit = '1e-6 m'
instance.si_scale = 1e-6
@@ -148,7 +151,7 @@ class TestReflectance(unittest.TestCase):
refl37 = Calculator('Suomi-NPP', 'viirs', 3.7)
self.assertEqual(refl37.bandwavelength, 3.7)
- self.assertEqual(refl37.bandname, '20')
+ self.assertEqual(refl37.bandname, 'ch20')
with patch('pyspectral.radiance_tb_conversion.RelativeSpectralResponse') as mymock:
instance = mymock.return_value
=====================================
pyspectral/tests/test_utils.py
=====================================
@@ -115,8 +115,7 @@ class TestUtils(unittest.TestCase):
self.rsr = RsrTestData()
def test_convert2wavenumber(self):
- """Testing the conversion of rsr from wavelength to wavenumber
- """
+ """Testing the conversion of rsr from wavelength to wavenumber."""
newrsr, info = utils.convert2wavenumber(TEST_RSR)
unit = info['unit']
self.assertEqual(unit, 'cm-1')
@@ -127,11 +126,7 @@ class TestUtils(unittest.TestCase):
self.assertTrue(np.allclose(wvn_res, wvn))
def test_get_bandname_from_wavelength(self):
- """Test that it is possible to get the right bandname provided the wavelength
- in micro meters
-
- """
-
+ """Test the right bandname is found provided the wavelength in micro meters."""
x = utils.get_bandname_from_wavelength('abi', 0.4, self.rsr.rsr)
self.assertEqual(x, 'ch1')
with self.assertRaises(AttributeError):
@@ -146,19 +141,16 @@ class TestUtils(unittest.TestCase):
x = utils.get_bandname_from_wavelength('abi', 1.0, self.rsr.rsr)
self.assertEqual(x, None)
+ # uses generic channel mapping where '20' -> 'ch20'
bandname = utils.get_bandname_from_wavelength('abi', 3.7, TEST_RSR)
- self.assertEqual(bandname, '20')
+ self.assertEqual(bandname, 'ch20')
bandname = utils.get_bandname_from_wavelength('abi', 3.0, TEST_RSR)
self.assertIsNone(bandname)
- def tearDown(self):
- """Clean up"""
- pass
-
def suite():
- """The suite for test_utils."""
+ """Create the suite for test_utils."""
loader = unittest.TestLoader()
mysuite = unittest.TestSuite()
mysuite.addTest(loader.loadTestsFromTestCase(TestUtils))
=====================================
pyspectral/utils.py
=====================================
@@ -78,9 +78,19 @@ BANDNAMES['generic'] = {'VIS006': 'VIS0.6',
'C15': 'ch15',
'C16': 'ch16',
}
+# handle arbitrary channel numbers
+for chan_num in range(1, 37):
+ BANDNAMES['generic'][str(chan_num)] = 'ch{:d}'.format(chan_num)
-BANDNAMES['avhrr-3'] = {'3b': 'ch3b',
- '3a': 'ch3a'}
+# MODIS RSR files were made before 'chX' became standard in pyspectral
+BANDNAMES['modis'] = {str(chan_num): str(chan_num) for chan_num in range(1, 37)}
+
+BANDNAMES['avhrr-3'] = {'1': 'ch1',
+ '2': 'ch2',
+ '3b': 'ch3b',
+ '3a': 'ch3a',
+ '4': 'ch4',
+ '5': 'ch5'}
BANDNAMES['ahi'] = {'B01': 'ch1',
'B02': 'ch2',
@@ -120,12 +130,16 @@ INSTRUMENTS = {'NOAA-19': 'avhrr/3',
'Suomi-NPP': 'viirs',
'NOAA-20': 'viirs',
'FY-3D': 'mersi-2',
- 'Feng-Yun 3D': 'mersi-2'
+ 'FY-3C': 'virr',
+ 'FY-3B': 'virr',
+ 'Feng-Yun 3D': 'mersi-2',
+ 'FY-4A': 'agri'
}
-HTTP_PYSPECTRAL_RSR = "https://zenodo.org/record/2653487/files/pyspectral_rsr_data.tgz"
+
+HTTP_PYSPECTRAL_RSR = "https://zenodo.org/record/3381130/files/pyspectral_rsr_data.tgz"
RSR_DATA_VERSION_FILENAME = "PYSPECTRAL_RSR_VERSION"
-RSR_DATA_VERSION = "v1.0.6"
+RSR_DATA_VERSION = "v1.0.9"
ATM_CORRECTION_LUT_VERSION = {}
ATM_CORRECTION_LUT_VERSION['antarctic_aerosol'] = {'version': 'v1.0.1',
=====================================
pyspectral/version.py
=====================================
@@ -46,9 +46,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
- git_refnames = " (tag: v0.8.9)"
- git_full = "b3a1f58f49e2ab7c81c331e379065f24eb21f1ba"
- git_date = "2019-06-07 09:27:42 +0200"
+ git_refnames = " (HEAD -> master, tag: v0.9.0)"
+ git_full = "2372396547ecace7fbcefc50d5dcbaa32e4a5177"
+ git_date = "2019-08-30 18:04:19 +0200"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
rsr_convert_scripts/README.rst
=====================================
@@ -176,3 +176,12 @@ the pyspectral.yaml file:
Adam Dybbroe
Sat Dec 1 17:39:48 2018
+.. code::
+
+ %> python virr_rsr.py
+
+Converting the FY-3B or FY-3C VIRR spectral responses to HDF5. Original files
+for FY-3B come as ``.prn`` text files for each channel (ex. ``ch1.prn``). For
+FY-3C they come as ``.txt`` text files for channels 1, 2, 6, 7, 8, 9, and 10
+only with names like ``FY3C_VIRR_CH01.txt``.
+
=====================================
rsr_convert_scripts/agri_rsr.py
=====================================
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018, 2019 Pytroll
+
+# Author(s):
+
+# Xin.Zhang <xinzhang1215 at gmail.com>
+# Adam.Dybbroe <adam.dybbroe at smhi.se>
+
+# This program 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.
+
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Read the FY-4A AGRI relative spectral responses. Data from
+http://fy4.nsmc.org.cn/portal/cn/fycv/srf.html
+"""
+import os
+import numpy as np
+from pyspectral.utils import INSTRUMENTS
+from pyspectral.utils import convert2hdf5 as tohdf5
+from pyspectral.raw_reader import InstrumentRSR
+from pyspectral.utils import logging_on, get_logger
+
+FY4A_BAND_NAMES = ['ch1', 'ch2', 'ch3', 'ch4', 'ch5', 'ch6', 'ch7', 'ch8',
+ 'ch9', 'ch10', 'ch11', 'ch12', 'ch13', 'ch14']
+BANDNAME_SCALE2MICROMETERS = {'ch1': 0.001,
+ 'ch2': 0.001,
+ 'ch3': 0.001,
+ 'ch4': 1.0,
+ 'ch5': 1.0,
+ 'ch6': 1.0,
+ 'ch7': 1.0,
+ 'ch8': 1.0,
+ 'ch9': 1.0,
+ 'ch10': 1.0,
+ 'ch11': 1.0,
+ 'ch12': 1.0,
+ 'ch13': 1.0,
+ 'ch14': 1.0}
+
+
+class AGRIRSR(InstrumentRSR):
+ """Container for the FY-4 AGRI RSR data"""
+
+ def __init__(self, bandname, platform_name):
+ """Initialise the FY-4 AGRI relative spectral response data"""
+ super(AGRIRSR, self).__init__(bandname, platform_name, FY4A_BAND_NAMES)
+
+ self.instrument = INSTRUMENTS.get(platform_name, 'agri')
+
+ self._get_options_from_config()
+ self._get_bandfilenames()
+
+ LOG.debug("Filenames: %s", str(self.filenames))
+ if self.filenames[bandname] and os.path.exists(self.filenames[bandname]):
+ self.requested_band_filename = self.filenames[bandname]
+ scale = BANDNAME_SCALE2MICROMETERS.get(bandname)
+ if scale:
+ self._load(scale=scale)
+ else:
+ LOG.error(
+ "Failed determine the scale used to convert to wavelength in micrometers - channel = %s", bandname)
+ raise AttributeError('no scale for bandname %s', bandname)
+
+ else:
+ LOG.warning("Couldn't find an existing file for this band: %s",
+ str(self.bandname))
+
+ self.filename = self.requested_band_filename
+
+ def _load(self, scale=0.001):
+ """Load the AGRI RSR data for the band requested
+
+ Wavelength is given in nanometers.
+ """
+ data = np.genfromtxt(self.requested_band_filename,
+ unpack=True,
+ names=['wavelength',
+ 'response'],
+ skip_header=0)
+
+ wavelength = data['wavelength'] * scale
+ response = data['response']
+
+ self.rsr = {'wavelength': wavelength, 'response': response}
+
+
+def main():
+ """Main"""
+ for platform_name in ["FY-4A", ]:
+ tohdf5(AGRIRSR, platform_name, FY4A_BAND_NAMES)
+
+
+if __name__ == "__main__":
+ LOG = get_logger(__name__)
+ logging_on()
+
+ main()
=====================================
rsr_convert_scripts/virr_rsr.py
=====================================
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Adam.Dybbroe
+#
+# Author(s):
+#
+# David Hoese <david.hoese at ssec.wisc.edu>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+"""Read the VIRR relative spectral responses.
+
+Data from http://gsics.nsmc.org.cn/portal/en/fycv/srf.html
+
+"""
+
+import os
+import numpy as np
+from pyspectral.utils import INSTRUMENTS
+from pyspectral.utils import convert2hdf5 as tohdf5
+from pyspectral.raw_reader import InstrumentRSR
+
+import logging
+LOG = logging.getLogger(__name__)
+
+VIRR_BAND_NAMES = {
+ 'FY-3B': ['ch{:d}'.format(x) for x in range(1, 11)],
+ 'FY-3C': ['ch1', 'ch2'] + ['ch{:d}'.format(x) for x in range(6, 11)],
+}
+
+
+class VirrRSR(InstrumentRSR):
+ """Container for the FY-3B/FY-3C VIRR RSR data."""
+
+ def __init__(self, bandname, platform_name):
+ """Verify that file exists and can be read."""
+ super(VirrRSR, self).__init__(bandname, platform_name, VIRR_BAND_NAMES[platform_name])
+
+ self.instrument = INSTRUMENTS.get(platform_name, 'virr')
+ self._get_options_from_config()
+ self._get_bandfilenames()
+
+ LOG.debug("Filenames: %s", str(self.filenames))
+ if self.filenames[bandname] and os.path.exists(self.filenames[bandname]):
+ self.requested_band_filename = self.filenames[bandname]
+ self._load()
+ else:
+ LOG.warning("Couldn't find an existing file for this band: %s",
+ str(self.bandname))
+
+ # To be compatible with VIIRS....
+ self.filename = self.requested_band_filename
+
+ def _load(self, scale=0.001):
+ """Load the VIRR RSR data for the band requested.
+
+ Wavelength is given in nanometers.
+ """
+ data = np.genfromtxt(self.requested_band_filename,
+ unpack=True,
+ names=['wavelength',
+ 'response'],
+ skip_header=0)
+
+ wavelength = data['wavelength'] * scale
+ response = data['response']
+
+ self.rsr = {'wavelength': wavelength, 'response': response}
+
+
+def main():
+ """Main"""
+ for platform_name, band_names in VIRR_BAND_NAMES.items():
+ tohdf5(VirrRSR, platform_name, band_names)
+
+
+if __name__ == "__main__":
+ main()
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyspectral/commit/bdb76ea7177514603c82b55f5f96f170e21171a6
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyspectral/commit/bdb76ea7177514603c82b55f5f96f170e21171a6
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20190902/6cde039d/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list