Completed
Push — develop ( f92b69...710c13 )
by Adam
55s
created

RelativeSpectralResponse   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 183
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
dl 0
loc 183
rs 8.2608
c 4
b 0
f 0
wmc 40

6 Methods

Rating   Name   Duplication   Size   Complexity  
A _check_instrument() 0 9 2
A _get_filename() 0 16 4
A integral() 0 10 2
F load() 0 67 15
B convert() 0 23 6
F __init__() 0 47 11

How to fix   Complexity   

Complex Class

Complex classes like RelativeSpectralResponse 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
39
40
class RelativeSpectralResponse(object):
41
42
    """Container for the relative spectral response functions for various
43
    satellite imagers
44
    """
45
46
    def __init__(self, platform_name=None, instrument=None, **kwargs):
47
        """Create the instance either from platform name and instrument or from
48
        filename and load the data"""
49
        self.platform_name = platform_name
50
        self.instrument = instrument
51
        self.filename = None
52
        if not self.instrument or not self.platform_name:
53
            if 'filename' in kwargs:
54
                self.filename = kwargs['filename']
55
            else:
56
                raise AttributeError(
57
                    "platform name and sensor or filename must be specified")
58
        else:
59
            self._check_instrument()
60
61
        self.rsr = {}
62
        self.description = "Unknown"
63
        self.band_names = None
64
        self.unit = '1e-6 m'
65
        self.si_scale = 1e-6  # How to scale the wavelengths to become SI unit
66
        self._wavespace = WAVE_LENGTH
67
68
        options = get_config()
69
        self.rsr_dir = options['rsr_dir']
70
        self.do_download = False
71
72
        if 'download_from_internet' in options and options['download_from_internet']:
73
            self.do_download = True
74
75
        if not self.filename:
76
            self._get_filename()
77
78
        if not os.path.exists(self.filename) or not os.path.isfile(self.filename):
79
            errmsg = ('pyspectral RSR file does not exist! Filename = ' +
80
                      str(self.filename))
81
            if self.instrument and self.platform_name:
82
                fmatch = glob(
83
                    os.path.join(self.rsr_dir, '*{0}*{1}*.h5'.format(self.instrument,
84
                                                                     self.platform_name)))
85
                errmsg = (errmsg +
86
                          '\nFiles matching instrument and satellite platform' +
87
                          ': ' + str(fmatch))
88
89
            raise IOError(errmsg)
90
91
        LOG.debug('Filename: %s', str(self.filename))
92
        self.load()
93
94
    def _check_instrument(self):
95
        """Check and try fix instrument name if needed"""
96
        instr = INSTRUMENTS.get(self.platform_name, self.instrument.lower())
97
        if instr != self.instrument:
98
            self.instrument = instr
99
            LOG.warning("Inconsistent instrument/satellite input - " +
100
                        "instrument set to %s", self.instrument)
101
102
        self.instrument = self.instrument.replace('/', '')
103
104
    def _get_filename(self):
105
        """Get the rsr filname from platform and instrument names, and download if not
106
           available.
107
108
        """
109
        self.filename = expanduser(
110
            os.path.join(self.rsr_dir, 'rsr_{0}_{1}.h5'.format(self.instrument,
111
                                                               self.platform_name)))
112
113
        LOG.debug('Filename: %s', str(self.filename))
114
        if not os.path.exists(self.filename) or not os.path.isfile(self.filename):
115
            # Try download from the internet!
116
            LOG.warning("No rsr file %s on disk", self.filename)
117
            if self.do_download:
118
                LOG.info("Will download from internet...")
119
                download_rsr()
120
121
    def load(self):
122
        """Read the internally formatet hdf5 relative spectral response data"""
123
        import h5py
124
125
        no_detectors_message = False
126
        with h5py.File(self.filename, 'r') as h5f:
127
            self.band_names = [b.decode('utf-8') for b in h5f.attrs['band_names'].tolist()]
128
            self.description = h5f.attrs['description'].decode('utf-8')
129
            if not self.platform_name:
130
                try:
131
                    self.platform_name = h5f.attrs['platform_name'].decode('utf-8')
132
                except KeyError:
133
                    LOG.warning("No platform_name in HDF5 file")
134
                    try:
135
                        self.platform_name = h5f.attrs[
136
                            'platform'] + '-' + h5f.attrs['satnum']
137
                    except KeyError:
138
                        LOG.warning(
139
                            "Unable to determine platform name from HDF5 file content")
140
                        self.platform_name = None
141
142
            if not self.instrument:
143
                try:
144
                    self.instrument = h5f.attrs['sensor'].decode('utf-8')
145
                except KeyError:
146
                    LOG.warning("No sensor name specified in HDF5 file")
147
                    self.instrument = INSTRUMENTS.get(self.platform_name)
148
149
            for bandname in self.band_names:
150
                self.rsr[bandname] = {}
151
                try:
152
                    num_of_det = h5f[bandname].attrs['number_of_detectors']
153
                except KeyError:
154
                    if not no_detectors_message:
155
                        LOG.debug("No detectors found - assume only one...")
156
                    num_of_det = 1
157
                    no_detectors_message = True
158
159
                for i in range(1, num_of_det + 1):
160
                    dname = 'det-{0:d}'.format(i)
161
                    self.rsr[bandname][dname] = {}
162
                    try:
163
                        resp = h5f[bandname][dname]['response'][:]
164
                    except KeyError:
165
                        resp = h5f[bandname]['response'][:]
166
167
                    self.rsr[bandname][dname]['response'] = resp
168
169
                    try:
170
                        wvl = (h5f[bandname][dname]['wavelength'][:] *
171
                               h5f[bandname][dname][
172
                                   'wavelength'].attrs['scale'])
173
                    except KeyError:
174
                        wvl = (h5f[bandname]['wavelength'][:] *
175
                               h5f[bandname]['wavelength'].attrs['scale'])
176
177
                    # The wavelength is given in micro meters!
178
                    self.rsr[bandname][dname]['wavelength'] = wvl * 1e6
179
180
                    try:
181
                        central_wvl = h5f[bandname][
182
                            dname].attrs['central_wavelength']
183
                    except KeyError:
184
                        central_wvl = h5f[bandname].attrs['central_wavelength']
185
186
                    self.rsr[bandname][dname][
187
                        'central_wavelength'] = central_wvl
188
189
    def integral(self, bandname):
190
        """Calculate the integral of the spectral response function for each
191
        detector.
192
        """
193
        intg = {}
194
        for det in self.rsr[bandname].keys():
195
            wvl = self.rsr[bandname][det]['wavelength']
196
            resp = self.rsr[bandname][det]['response']
197
            intg[det] = np.trapz(resp, wvl)
198
        return intg
199
200
    def convert(self):
201
        """Convert spectral response functions from wavelength to wavenumber"""
202
203
        from pyspectral.utils import (convert2wavenumber, get_central_wave)
204
        if self._wavespace == WAVE_LENGTH:
205
            rsr, info = convert2wavenumber(self.rsr)
206
            for band in rsr.keys():
207
                for det in rsr[band].keys():
208
                    self.rsr[band][det][WAVE_NUMBER] = rsr[
209
                        band][det][WAVE_NUMBER]
210
                    self.rsr[band][det]['response'] = rsr[
211
                        band][det]['response']
212
                    self.unit = info['unit']
213
                    self.si_scale = info['si_scale']
214
            self._wavespace = WAVE_NUMBER
215
            for band in rsr.keys():
216
                for det in rsr[band].keys():
217
                    self.rsr[band][det]['central_wavenumber'] = \
218
                        get_central_wave(self.rsr[band][det][WAVE_NUMBER], self.rsr[band][det]['response'])
219
                    del self.rsr[band][det][WAVE_LENGTH]
220
        else:
221
            errmsg = "Conversion from {wn} to {wl} not supported yet".format(wn=WAVE_NUMBER, wl=WAVE_LENGTH)
222
            raise NotImplementedError(errmsg)
223
224
225
def main():
226
    """Main"""
227
    modis = RelativeSpectralResponse('EOS-Terra', 'modis')
228
    del(modis)
229
230
if __name__ == "__main__":
231
    main()
232