sciapy.level1c.scia_solar   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Test Coverage

Coverage 62.16%

Importance

Changes 0
Metric Value
eloc 163
dl 0
loc 326
ccs 92
cts 148
cp 0.6216
rs 10
c 0
b 0
f 0
wmc 20

7 Methods

Rating   Name   Duplication   Size   Complexity  
A scia_solar.read_from_file() 0 23 2
A scia_solar.__init__() 0 10 1
B scia_solar.read_from_textfile() 0 38 5
A scia_solar.read_from_netcdf() 0 28 3
B scia_solar.write_to_textfile() 0 46 6
A scia_solar.write_to_netcdf() 0 34 2
B scia_solar.read_from_hdf5() 0 66 1
1
# -*- coding: utf-8 -*-
2
# vim:fileencoding=utf-8
3
#
4
# Copyright (c) 2014-2017 Stefan Bender
5
#
6
# This module is part of sciapy.
7
# sciapy is free software: you can redistribute it or modify
8
# it under the terms of the GNU General Public License as published
9
# by the Free Software Foundation, version 2.
10
# See accompanying LICENSE file or http://www.gnu.org/licenses/gpl-2.0.html.
11 1
"""SCIAMACHY level 1c solar spectra module
12
13
This module contains the python class for SCIAMACHY level 1c solar spectra.
14
It include some simple conversion routines, from and to ascii and from and to netcdf.
15
16
A simple import from SRON nadc_tools (https://github.com/rmvanhees/nadc_tools)
17
produced HDF5 is also supported.
18
"""
19 1
from __future__ import absolute_import, division, print_function
20
21 1
__all__ = ["scia_solar", "__doc__"]
22
23 1
import datetime
24 1
import logging
25
26 1
import numpy as np
27 1
try:
28 1
	from netCDF4 import Dataset as netcdf_file
29 1
	_fmtargs = {"format": "NETCDF4"}
30
except ImportError:
31
	try:
32
		from scipy.io.netcdf import netcdf_file
33
		_fmtargs = {"version": 1}
34
	except ImportError:
35
		from pupynere import netcdf_file
36
		_fmtargs = {"version": 1}
37
38 1
from ._types import _try_decode
39
40 1
logging.basicConfig(level=logging.INFO,
41
		format="[%(levelname)-8s] (%(asctime)s) "
42
		"%(filename)s:%(lineno)d %(message)s",
43
		datefmt="%Y-%m-%d %H:%M:%S %z")
44
45 1
class scia_solar(object):
46
	"""SCIAMACHY solar reference spectrum class
47
48
	Contains the SCIAMACHY level 1c solar reference spectrum.
49
	The format is inspired by the SCIAMACHY ascii data format.
50
51
	Attributes
52
	----------
53
	textheader_length : int
54
		The number of lines of the text header.
55
	textheader : str
56
		The header containing the solar spectrum meta data.
57
	npix : int
58
		The number of spectral points.
59
	solar_id : str
60
		The solar reference spectrum ID,
61
		choices: "D0", "D1", "D2", "E0", "E1", "A0", "A1",
62
		"N1", "N2", "N3", "N4", "N5".
63
	orbit : int
64
		The SCIAMACHY/Envisat orbit number.
65
	time : :class:`datetime.datetime`
66
		The sensing start time of the (semi-)orbit.
67
	wls : (M,) array_like
68
		The spectral wavelengths.
69
	rads : (M,) array_like
70
		The radiances at the spectral points, M = len(wls).
71
	errs : (M,) array_like
72
		The relative radiance uncertainties at the tangent points, M = len(wls).
73
	"""
74
75 1
	def __init__(self):
76 1
		self.textheader_length = 0
77 1
		self.textheader = ""
78 1
		self.npix = 0
79 1
		self.solar_id = ""
80 1
		self.orbit = -1
81 1
		self.time = None
82 1
		self.wls = np.array([])
83 1
		self.rads = np.array([])
84 1
		self.errs = None
85
86 1
	def read_from_netcdf(self, filename):
87
		"""SCIAMACHY level 1c solar reference netcdf import
88
89
		Parameters
90
		----------
91
		filename : str
92
			The netcdf filename to read the data from.
93
94
		Returns
95
		-------
96
		nothing
97
		"""
98 1
		ncf = netcdf_file(filename, 'r')
99 1
		self.textheader_length = ncf.textheader_length
100 1
		self.textheader = _try_decode(ncf.textheader)
101 1
		if self.textheader_length > 6:
102 1
			self.solar_id = _try_decode(ncf.solar_id)
103 1
			self.orbit = ncf.orbit
104 1
			_time = _try_decode(ncf.time)
105 1
			self.time = datetime.datetime.strptime(_time, '%Y-%m-%d %H:%M:%S %Z')
106 1
		self.wls = ncf.variables['wavelength'][:].copy()
107 1
		self.rads = ncf.variables['radiance'][:].copy()
108 1
		self.npix = self.wls.size
109 1
		try:
110 1
			self.errs = ncf.variables['radiance errors'][:].copy()
111 1
		except KeyError:
112 1
			self.errs = None
113 1
		ncf.close()
114
115 1
	def read_from_textfile(self, filename):
116
		"""SCIAMACHY level 1c solar reference text import
117
118
		Parameters
119
		----------
120
		filename : str
121
			The (plain) ascii table filename to read the data from.
122
123
		Returns
124
		-------
125
		nothing
126
		"""
127 1
		if hasattr(filename, 'seek'):
128
			f = filename
129
		else:
130 1
			f = open(filename, 'r')
131 1
		h_list = []
132 1
		try:
133 1
			nh = int(f.readline())
134
		except:
135
			nh = 6
136
			f.seek(0)
137 1
		for i in range(0, nh):
138 1
			h_list.append(f.readline().rstrip())
139 1
		self.textheader_length = nh
140 1
		self.textheader = '\n'.join(h_list)
141 1
		self.npix = int(f.readline())
142 1
		if nh > 6:
143 1
			self.solar_id = f.readline().rstrip()
144 1
			self.orbit = int(f.readline())
145 1
			self.time = datetime.datetime.strptime(
146
				f.readline().strip('\n') + " UTC",
147
				"%Y %m %d %H %M %S %Z",
148
			)
149 1
			self.wls, self.rads = np.genfromtxt(filename, skip_header=nh + 5, unpack=True)
150 1
			self.errs = None
151
		else:
152
			self.wls, self.rads, self.errs = np.genfromtxt(filename, skip_header=7, unpack=True)
153
154 1
	def read_from_hdf5(self, hf, ref="D0"):
155
		"""SCIAMACHY level 1c solar reference HDF5 import
156
157
		Parameters
158
		----------
159
		hf : opened file
160
			Pointer to the opened level 1c HDF5 file
161
		ref : str
162
			The solar reference spectra id name,
163
			choose from: "D0", "D1", "D2", "E0", "E1", "A0", "A1",
164
			"N1", "N2", "N3", "N4", "N5". Defaults to "D0".
165
166
		Returns
167
		-------
168
		success : int
169
			0 on success, 1 if an error occured.
170
		"""
171
		product = hf.get("/MPH")["product_name"][0].decode()
172
		soft_ver = hf.get("/MPH")["software_version"][0].decode()
173
		key_ver = hf.get("/SPH")["key_data_version"][0].decode()
174
		mf_ver = hf.get("/SPH")["m_factor_version"][0].decode()
175
		init_version = hf.get("/SPH")["init_version"][0].decode().strip()
176
		init_ver, decont = init_version.split(' ')
177
		decont = decont.lstrip("DECONT=")
178
		start_date = hf.get("/MPH")["sensing_start"][0].decode().rstrip('"')
179
		# fill some class variables
180
		self.time = (datetime.datetime.strptime(start_date, "%d-%b-%Y %H:%M:%S.%f")
181
					.replace(tzinfo=datetime.timezone.utc))
182
		self.orbit = hf.get("/MPH")["abs_orbit"][0]
183
		self.solar_id = ref
184
185
		logging.debug("product: %s, orbit: %s", product, self.orbit)
186
		logging.debug("soft_ver: %s, key_ver: %s, mf_ver: %s, init_ver: %s, "
187
				"decont_ver: %s", soft_ver, key_ver, mf_ver, init_ver, decont)
188
189
		# Prepare the header
190
		datatype_txt = "SCIAMACHY solar mean ref."
191
		n_header = 10
192
		line = n_header + 2
193
		header = ("#Data type          : {0}\n".format(datatype_txt))
194
		header += ("#L1b product        : {0}\n".format(product))
195
		header += ("#Orbit nr.          : {0:05d}\n".format(self.orbit))
196
		header += ("#Ver. Proc/Key/M/I/D: {0}  {1}  {2}  {3}  {4}\n"
197
				.format(soft_ver, key_ver, mf_ver, init_ver, decont))
198
		header += ("#Starttime          : {0}\n".format(start_date))
199
		header += ("#L.{0:2d} : Number_of_pixels\n".format(line))
200
		line += 1
201
		header += ("#L.{0:2d} : Solar ID\n".format(line))
202
		line += 1
203
		header += ("#L.{0:2d} : Orbit\n".format(line))
204
		line += 1
205
		header += ("#L.{0:2d} : Date Time : yyyy mm dd hh mm ss\n".format(line))
206
		line += 1
207
		header += ("#L.{0:2d} : Npix lines : wavelangth  irradiance  accuracy".format(line))
208
209
		sun_ref = hf.get("/GADS/SUN_REFERENCE")
210
		this_ref = sun_ref[sun_ref["sun_spec_id"] == ref]
211
212
		# fill the remaining class variables
213
		self.textheader_length = n_header
214
		self.textheader = header
215
		self.wls = this_ref["wvlen_sun"][:]
216
		self.npix = len(self.wls)
217
		self.rads = this_ref["mean_sun"][:]
218
		self.errs = this_ref["accuracy_sun"][:]
219
		return 0
220
221 1
	def read_from_file(self, filename):
222
		"""SCIAMACHY level 1c solar reference data import
223
224
		Convenience function to read the reference spectrum.
225
		Tries to detect the file format automatically trying netcdf first,
226
		and if that fails falls back to the text reader.
227
		Currently no HDF5 support.
228
229
		Parameters
230
		----------
231
		filename : str
232
			The filename to read from.
233
234
		Returns
235
		-------
236
		nothing
237
		"""
238 1
		try:
239
			# try netcdf first
240 1
			self.read_from_netcdf(filename)
241 1
		except:
242
			# fall back to text file
243 1
			self.read_from_textfile(filename)
244
245 1
	def write_to_netcdf(self, filename):
246
		"""SCIAMACHY level 1c solar reference netcdf export
247
248
		Parameters
249
		----------
250
		filename : str
251
			The netcdf filename to write the data to.
252
253
		Returns
254
		-------
255
		nothing
256
		"""
257 1
		ncf = netcdf_file(filename, 'w', **_fmtargs)
258 1
		ncf.textheader_length = self.textheader_length
259 1
		ncf.textheader = self.textheader
260 1
		ncf.solar_id = self.solar_id
261 1
		ncf.orbit = self.orbit
262 1
		ncf.time = self.time.strftime('%Y-%m-%d %H:%M:%S UTC')
263
264 1
		ncf.createDimension('wavelength', self.npix)
265 1
		wavs = ncf.createVariable('wavelength', np.dtype('float64').char, ('wavelength',))
266 1
		wavs.units = 'nm'
267 1
		wavs[:] = self.wls
268
269 1
		rads = ncf.createVariable('radiance', np.dtype('float64').char, ('wavelength',))
270 1
		rads.units = 'ph / s / cm^2 / nm'
271 1
		rads[:] = self.rads
272
273 1
		if self.errs is not None:
274
			errs = ncf.createVariable('radiance errors', np.dtype('float64').char, ('wavelength',))
275
			errs.units = 'ph / s / cm^2 / nm'
276
			errs[:] = self.errs
277
278 1
		ncf.close()
279
280 1
	def write_to_textfile(self, filename):
281
		"""SCIAMACHY level 1c solar reference text export
282
283
		Parameters
284
		----------
285
		filename : str
286
			The (plain) ascii table filename to write the data to.
287
			Passing sys.STDOUT writes to the console.
288
289
		Returns
290
		-------
291
		nothing
292
		"""
293 1
		if hasattr(filename, 'seek'):
294
			f = filename
295
		else:
296 1
			f = open(filename, 'w')
297 1
		if self.textheader_length > 6:
298 1
			print("{0:2d}".format(self.textheader_length), file=f)
299 1
		print(self.textheader, file=f)
300 1
		print(self.npix, file=f)
301 1
		if self.textheader_length > 6:
302 1
			print(self.solar_id, file=f)
303 1
			print(self.orbit, file=f)
304 1
			print("%4d %2d %2d %2d %2d %2d" % (self.time.year, self.time.month,
305
					self.time.day, self.time.hour, self.time.minute, self.time.second),
306
				file=f)
307 1
		for i in range(self.npix):
308
			#output = []
309
			#output.append(self.wls[i])
310
			#output.append(self.rads[i])
311
			#output.append(self.errs[i])
312
			#print('\t'.join(map(str, output)), file=f)
313 1
			if self.errs is not None:
314
				print(
315
					"{0:9.4f}  {1:12.5e}  {2:12.5e}".format(
316
						self.wls[i], self.rads[i], self.errs[i],
317
					),
318
					file=f,
319
				)
320
			else:
321 1
				print(
322
					"{0:9.4f}  {1:12.5e}".format(
323
						self.wls[i], self.rads[i],
324
					),
325
					file=f,
326
				)
327