Completed
Push — develop ( f7f1cb...18ba86 )
by Adam
01:04
created

RelativeSpectralResponse.load()   F

Complexity

Conditions 15

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 15
c 3
b 0
f 0
dl 0
loc 67
rs 2.6334

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like RelativeSpectralResponse.load() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
# Copyright (c) 2014-2018 Adam.Dybbroe
5
6
# Author(s):
7
8
#   Adam.Dybbroe <[email protected]>
9
#   Panu Lahtinen <[email protected]>
10
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, either version 3 of the License, or
14
# (at your option) any later version.
15
16
# This program is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
# GNU General Public License for more details.
20
21
# You should have received a copy of the GNU General Public License
22
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
24
"""Reading the spectral responses in the internal pyspectral hdf5 format"""
25
26
import os
27
import numpy as np
28
from glob import glob
29
from os.path import expanduser
30
31
import logging
32
LOG = logging.getLogger(__name__)
33
34
from pyspectral.config import get_config
35
from pyspectral.utils import WAVE_NUMBER
36
from pyspectral.utils import WAVE_LENGTH
37
from pyspectral.utils import (INSTRUMENTS, download_rsr)
38
from pyspectral.utils import (RSR_DATA_VERSION_FILENAME, RSR_DATA_VERSION)
39
40
41
class RelativeSpectralResponse(object):
42
43
    """Container for the relative spectral response functions for various
44
    satellite imagers
45
    """
46
47
    def __init__(self, platform_name=None, instrument=None, **kwargs):
48
        """Create the instance either from platform name and instrument or from
49
        filename and load the data"""
50
        self.platform_name = platform_name
51
        self.instrument = instrument
52
        self.filename = None
53
        if not self.instrument or not self.platform_name:
54
            if 'filename' in kwargs:
55
                self.filename = kwargs['filename']
56
            else:
57
                raise AttributeError(
58
                    "platform name and sensor or filename must be specified")
59
        else:
60
            self._check_instrument()
61
62
        self.rsr = {}
63
        self.description = "Unknown"
64
        self.band_names = None
65
        self.unit = '1e-6 m'
66
        self.si_scale = 1e-6  # How to scale the wavelengths to become SI unit
67
        self._wavespace = WAVE_LENGTH
68
69
        options = get_config()
70
        self.rsr_dir = options['rsr_dir']
71
        self.do_download = False
72
        self._rsr_data_version_uptodate = False
73
74
        if 'download_from_internet' in options and options['download_from_internet']:
75
            self.do_download = True
76
77
        if self._get_rsr_data_version() == RSR_DATA_VERSION:
78
            self._rsr_data_version_uptodate = True
79
80
        if not self.filename:
81
            self._get_filename()
82
83
        if not os.path.exists(self.filename) or not os.path.isfile(self.filename):
84
            errmsg = ('pyspectral RSR file does not exist! Filename = ' +
85
                      str(self.filename))
86
            if self.instrument and self.platform_name:
87
                fmatch = glob(
88
                    os.path.join(self.rsr_dir, '*{0}*{1}*.h5'.format(self.instrument,
89
                                                                     self.platform_name)))
90
                errmsg = (errmsg +
91
                          '\nFiles matching instrument and satellite platform' +
92
                          ': ' + str(fmatch))
93
94
            raise IOError(errmsg)
95
96
        LOG.debug('Filename: %s', str(self.filename))
97
        self.load()
98
99
    def _get_rsr_data_version(self):
100
        """Check the version of the RSR data from the version file in the RSR
101
           directory
102
103
        """
104
105
        rsr_data_version_path = os.path.join(self.rsr_dir, RSR_DATA_VERSION_FILENAME)
106
        if not os.path.exists(rsr_data_version_path):
107
            return "v0.0.0"
108
109
        with open(rsr_data_version_path, 'r') as fpt:
110
            # Get the version from the file
111
            return fpt.readline().strip()
112
113
    def _check_instrument(self):
114
        """Check and try fix instrument name if needed"""
115
        instr = INSTRUMENTS.get(self.platform_name, self.instrument.lower())
116
        if instr != self.instrument:
117
            self.instrument = instr
118
            LOG.warning("Inconsistent instrument/satellite input - " +
119
                        "instrument set to %s", self.instrument)
120
121
        self.instrument = self.instrument.replace('/', '')
122
123
    def _get_filename(self):
124
        """Get the rsr filname from platform and instrument names, and download if not
125
           available.
126
127
        """
128
        self.filename = expanduser(
129
            os.path.join(self.rsr_dir, 'rsr_{0}_{1}.h5'.format(self.instrument,
130
                                                               self.platform_name)))
131
132
        LOG.debug('Filename: %s', str(self.filename))
133
        if not os.path.exists(self.filename) or not os.path.isfile(self.filename):
134
            # Try download from the internet!
135
            LOG.warning("No rsr file %s on disk", self.filename)
136
            if self.do_download:
137
                LOG.info("Will download from internet...")
138
                download_rsr()
139
140
        if not self._rsr_data_version_uptodate:
141
            LOG.warning("rsr data may not be up to date: %s", self.filename)
142
            if self.do_download:
143
                LOG.info("Will download from internet...")
144
                download_rsr()
145
146
    def load(self):
147
        """Read the internally formatet hdf5 relative spectral response data"""
148
        import h5py
149
150
        no_detectors_message = False
151
        with h5py.File(self.filename, 'r') as h5f:
152
            self.band_names = [b.decode('utf-8') for b in h5f.attrs['band_names'].tolist()]
153
            self.description = h5f.attrs['description'].decode('utf-8')
154
            if not self.platform_name:
155
                try:
156
                    self.platform_name = h5f.attrs['platform_name'].decode('utf-8')
157
                except KeyError:
158
                    LOG.warning("No platform_name in HDF5 file")
159
                    try:
160
                        self.platform_name = h5f.attrs[
161
                            'platform'] + '-' + h5f.attrs['satnum']
162
                    except KeyError:
163
                        LOG.warning(
164
                            "Unable to determine platform name from HDF5 file content")
165
                        self.platform_name = None
166
167
            if not self.instrument:
168
                try:
169
                    self.instrument = h5f.attrs['sensor'].decode('utf-8')
170
                except KeyError:
171
                    LOG.warning("No sensor name specified in HDF5 file")
172
                    self.instrument = INSTRUMENTS.get(self.platform_name)
173
174
            for bandname in self.band_names:
175
                self.rsr[bandname] = {}
176
                try:
177
                    num_of_det = h5f[bandname].attrs['number_of_detectors']
178
                except KeyError:
179
                    if not no_detectors_message:
180
                        LOG.debug("No detectors found - assume only one...")
181
                    num_of_det = 1
182
                    no_detectors_message = True
183
184
                for i in range(1, num_of_det + 1):
185
                    dname = 'det-{0:d}'.format(i)
186
                    self.rsr[bandname][dname] = {}
187
                    try:
188
                        resp = h5f[bandname][dname]['response'][:]
189
                    except KeyError:
190
                        resp = h5f[bandname]['response'][:]
191
192
                    self.rsr[bandname][dname]['response'] = resp
193
194
                    try:
195
                        wvl = (h5f[bandname][dname]['wavelength'][:] *
196
                               h5f[bandname][dname][
197
                                   'wavelength'].attrs['scale'])
198
                    except KeyError:
199
                        wvl = (h5f[bandname]['wavelength'][:] *
200
                               h5f[bandname]['wavelength'].attrs['scale'])
201
202
                    # The wavelength is given in micro meters!
203
                    self.rsr[bandname][dname]['wavelength'] = wvl * 1e6
204
205
                    try:
206
                        central_wvl = h5f[bandname][
207
                            dname].attrs['central_wavelength']
208
                    except KeyError:
209
                        central_wvl = h5f[bandname].attrs['central_wavelength']
210
211
                    self.rsr[bandname][dname][
212
                        'central_wavelength'] = central_wvl
213
214
    def integral(self, bandname):
215
        """Calculate the integral of the spectral response function for each
216
        detector.
217
        """
218
        intg = {}
219
        for det in self.rsr[bandname].keys():
220
            wvl = self.rsr[bandname][det]['wavelength']
221
            resp = self.rsr[bandname][det]['response']
222
            intg[det] = np.trapz(resp, wvl)
223
        return intg
224
225
    def convert(self):
226
        """Convert spectral response functions from wavelength to wavenumber"""
227
228
        from pyspectral.utils import (convert2wavenumber, get_central_wave)
229
        if self._wavespace == WAVE_LENGTH:
230
            rsr, info = convert2wavenumber(self.rsr)
231
            for band in rsr.keys():
232
                for det in rsr[band].keys():
233
                    self.rsr[band][det][WAVE_NUMBER] = rsr[
234
                        band][det][WAVE_NUMBER]
235
                    self.rsr[band][det]['response'] = rsr[
236
                        band][det]['response']
237
                    self.unit = info['unit']
238
                    self.si_scale = info['si_scale']
239
            self._wavespace = WAVE_NUMBER
240
            for band in rsr.keys():
241
                for det in rsr[band].keys():
242
                    self.rsr[band][det]['central_wavenumber'] = \
243
                        get_central_wave(self.rsr[band][det][WAVE_NUMBER], self.rsr[band][det]['response'])
244
                    del self.rsr[band][det][WAVE_LENGTH]
245
        else:
246
            errmsg = "Conversion from {wn} to {wl} not supported yet".format(wn=WAVE_NUMBER, wl=WAVE_LENGTH)
247
            raise NotImplementedError(errmsg)
248
249
250
def main():
251
    """Main"""
252
    modis = RelativeSpectralResponse('EOS-Terra', 'modis')
253
    del(modis)
254
255
if __name__ == "__main__":
256
    main()
257