Source code for Py6S.outputs
# This file is part of Py6S.
#
# Copyright 2012 Robin Wilson and contributors listed in the CONTRIBUTORS file.
#
# Py6S is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Py6S 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Py6S. If not, see <http://www.gnu.org/licenses/>.
import sys
from .sixs_exceptions import OutputParsingError
[docs]class Outputs(object):
"""Stores the output from a 6S run.
Attributes:
* ``fulltext`` -- The full output of the 6S executable. This can be written to a file with the write_output_file method.
* ``values`` -- The main outputs from the 6S run, stored in a dictionary. Accessible either via standard dictionary notation (``s.outputs.values['pixel_radiance']``) or as attributes (``s.outputs.pixel_radiance``)
Methods:
* :meth:`.__init__` -- Constructor which takes the stdout and stderr from the model and processes it into the numerical outputs.
* :meth:`.extract_results` -- Function called by the constructor to parse the output into individual variables
* :meth:`.to_int` -- Convert a string to an int, so that it works even if passed a float.
* :meth:`.write_output_file` -- Write the full textual output of the 6S model to a file.
"""
# Stores the full textual output from 6S
fulltext = ""
# Stores the numerical values extracted from the textual output as a dictionary
def __init__(self, stdout, stderr):
"""Initialise the class with the stdout output from the model, and process
it into the numerical outputs.
Arguments:
* ``stdout`` -- Standard output from the model run
* ``stderr`` -- Standard error from the model run
Will raise an :class:`.OutputParsingError` if the output cannot be parsed for any reason.
"""
self.values = {}
self.trans = {}
self.rat = {}
if len(stderr) > 0:
# Something on standard error - so there's been an error
if sys.version_info[0] >= 3:
stderr = stderr.decode("utf-8")
import platform
if platform.system() != "Darwin":
print(stderr)
raise OutputParsingError(
"6S returned an error (shown above) - check for invalid parameter inputs"
)
elif (
(platform.system() == "Darwin")
and (not ("IEEE_INVALID_FLAG" in stderr))
and (not ("IEEE_DENORMAL" in stderr))
and (not ("IEEE_UNDERFLOW_FLAG" in stderr))
): # Ignoring error on MacOS IEEE_INVALID_FLAG
print(stderr)
raise OutputParsingError(
"6S returned an error (shown above) - check for invalid parameter inputs"
)
self.fulltext = stdout
# For Python 3 need to decode to string
if sys.version_info[0] >= 3:
self.fulltext = self.fulltext.decode()
self.extract_results()
def __getattr__(self, name):
"""Executed when an attribute is referenced and not found. This method is overridden
to allow the user to access the outputs as ``outputs.variable`` rather than using the dictionary
explicity"""
if name == "__array_struct__" or name == "__array_interface__" or name == "__array__":
raise AttributeError()
# If there is a key with this name in the standard variables field then use it
if name in self.values:
return self.values[name]
else:
# If not, then split it by .'s
items = name.split("_")
if items[0] == "transmittance":
return self.trans["_".join(items[1:])]
else:
if name in self.rat:
return self.rat[name]
else:
raise OutputParsingError("The specifed output variable does not exist.")
def __dir__(self):
# Returns list of the attributes that I want to tab-complete on that aren't actually attributes, for IPython
trans_keys = ["transmittance_" + key for key in self.trans.keys()]
rat_keys = self.rat.keys()
all_keys = list(self.values.keys()) + list(trans_keys) + list(rat_keys)
return sorted(all_keys)
[docs] def extract_results(self):
"""Extract the results from the text output of the model and place them in the ``values`` dictionary."""
# Remove all of the *'s from the text as they just make it look pretty
# and get in the way of analysing the output
fulltext = self.fulltext.replace("*", "")
# Split into lines
lines = fulltext.splitlines()
# There should be hundreds of lines for a full 6S run - so if there are
# less than 10 then it suggests something has gone seriously wrong
if len(lines) < 10:
print(fulltext)
raise OutputParsingError(
"6S didn't return a full output. See raw 6S output above for "
"more information and check for invalid parameter inputs"
)
CURRENT = 0
WHOLE_LINE = (0, 30)
# fmt: off
# The dictionary below specifies how to extract each variable from the text output
# of 6S.
# The dictionary key is the text to search for. When this is found, the line corresponding
# to the first value in the tuple is found. If this is CURRENT (ie. 0) then it is the line on which
# the text was found, if it is 1 then it is the next line, 2 the one after that etc.
# The next item in the tuple is the index of the split line to extract the value from, and the
# third item is the key to store it in in the values dictionary. The final item is the type to convert
# it to - the type conversion function must be specified. More specific functions such as math.floor can
# be used here if desired.
# Search Term Line Index DictKey Type
extractors = {"6SV version": (CURRENT, 2, "version", str),
"month": (CURRENT, 1, "month", self.to_int),
"day": (CURRENT, 4, "day", self.to_int),
"solar zenith angle": (CURRENT, 3, "solar_z", self.to_int),
"solar azimuthal angle": (CURRENT, 8, "solar_a", self.to_int),
"view zenith angle": (CURRENT, 3, "view_z", self.to_int),
"view azimuthal angle": (CURRENT, 8, "view_a", self.to_int),
"scattering angle": (CURRENT, 2, "scattering_angle", float),
"azimuthal angle difference": (CURRENT, 7, "azimuthal_angle_difference", float),
"optical condition identity": (1, WHOLE_LINE, "visibility", self.extract_vis),
"optical condition": (1, WHOLE_LINE, "aot550", self.extract_aot),
"ground pressure": (CURRENT, 3, "ground_pressure", float),
"ground altitude": (CURRENT, 3, "ground_altitude", float),
"appar. rad.(w/m2/sr/mic)": (CURRENT, 2, "apparent_reflectance", float),
"appar. rad.": (CURRENT, 5, "apparent_radiance", float),
"total gaseous transmittance": (CURRENT, 3, "total_gaseous_transmittance", float),
"wv above aerosol": (CURRENT, 4, "wv_above_aerosol", float),
"wv mixed with aerosol": (CURRENT, 10, "wv_mixed_with_aerosol", float),
"wv under aerosol": (CURRENT, 4, "wv_under_aerosol", float),
"% of irradiance": (2, 0, "percent_direct_solar_irradiance", float),
"% of irradiance at": (2, 1, "percent_diffuse_solar_irradiance", float),
"% of irradiance at ground level": (2, 2, "percent_environmental_irradiance", float),
"reflectance at satellite level": (2, 0, "atmospheric_intrinsic_reflectance", float),
"reflectance at satellite lev": (2, 1, "background_reflectance", float),
"reflectance at satellite l": (2, 2, "pixel_reflectance", float),
"irr. at ground level": (2, 0, "direct_solar_irradiance", float),
"irr. at ground level (w/": (2, 1, "diffuse_solar_irradiance", float),
"irr. at ground level (w/m2/mic)": (2, 2, "environmental_irradiance", float),
"rad at satel. level": (2, 0, "atmospheric_intrinsic_radiance", float),
"rad at satel. level (w/m2/": (2, 1, "background_radiance", float),
"rad at satel. level (w/m2/sr/mic)": (2, 2, "pixel_radiance", float),
"sol. spect (in w/m2/mic)": (1, 0, "solar_spectrum", float),
"measured radiance [w/m2/sr/mic]": (CURRENT, 4, "measured_radiance", float),
"atmospherically corrected reflectance": (1, 3, "atmos_corrected_reflectance_lambertian", float),
"atmospherically corrected reflect": (2, 3, "atmos_corrected_reflectance_brdf", float),
"coefficients xa": (CURRENT, 5, "coef_xa", float),
"coefficients xa xb": (CURRENT, 6, "coef_xb", float),
"coefficients xa xb xc": (CURRENT, 7, "coef_xc", float),
"int. funct filter (in mic)": (1, 0, 'int_funct_filt', float),
"int. sol. spect (in w/m2)": (1, 1, 'int_solar_spectrum', float),
"Foam:": (CURRENT, 1, "water_component_foam", float),
"Water:": (CURRENT, 3, "water_component_water", float),
"Glint:": (CURRENT, 5, "water_component_glint", float),
"app. polarized refl.": (CURRENT, 3, "apparent_polarized_reflectance", float),
"app. pol. rad.": (CURRENT, 8, "apparent_polarized_radiance", float),
"direction of the plane of polarization": (CURRENT, -1, "direction_of_plane_of_polarization", lambda x: float(x.replace("polarization", ""))),
"total polarization ratio": (CURRENT, 3, "total_polarization_ratio", float)
}
# fmt: on
# Process most variables in the output
for index in range(len(lines)):
current_line = lines[index]
for label, details in extractors.items():
# If the label we're searching for is in the current line
if label.lower() in current_line.lower():
# See if the data is in the current line (as specified above)
if details[0] == CURRENT:
extracting_line = current_line
# Otherwise, work out which line to use and get it
else:
extracting_line = lines[index + details[0]]
funct = details[3]
items = extracting_line.split()
try:
a = details[1][0]
b = details[1][1]
except Exception:
a = details[1]
b = details[1] + 1
if a == -1:
data_for_func = items[a]
else:
data_for_func = items[a:b]
if len(data_for_func) == 1:
data_for_func = data_for_func[0]
try:
self.values[details[2]] = funct(data_for_func)
except Exception:
self.values[details[2]] = float("nan")
# Process big grid in the middle of the output for transmittances
grid_extractors = {
"global gas. trans. :": "global_gas",
'water " " :': "water",
'ozone " " :': "ozone",
'co2 " " :': "co2",
'oxyg " " :': "oxygen",
'no2 " " :': "no2",
'ch4 " " :': "ch4",
'co " " :': "co",
"rayl. sca. trans. :": "rayleigh_scattering",
'aeros. sca. " :': "aerosol_scattering",
'total sca. " :': "total_scattering",
}
for index in range(len(lines)):
current_line = lines[index]
for search, name in grid_extractors.items():
# If the label we're searching for is in the current line
if search in current_line:
items = current_line.split()
values = Transmittance()
try:
values.downward = float(items[4])
except ValueError:
values.downward = float("nan")
try:
values.upward = float(items[5])
except ValueError:
values.upward = float("nan")
try:
values.total = float(items[6])
except ValueError:
values.total = float("nan")
self.trans[name] = values
# Process big grid in the middle of the output for transmittances
bottom_grid_extractors = {
"spherical albedo :": "spherical_albedo",
"optical depth total:": "optical_depth_total",
"optical depth plane:": "optical_depth_plane",
"reflectance I :": "reflectance_I",
"reflectance Q :": "reflectance_Q",
"reflectance U :": "reflectance_U",
"polarized reflect. :": "polarized_reflectance",
# 'degree of polar. :' : "degree_of_polarization",
"dir. plane polar. :": "direction_of_plane_polarization",
"phase function I :": "phase_function_I",
"phase function Q :": "phase_function_Q",
"phase function U :": "phase_function_U",
"primary deg. of pol:": "primary_degree_of_polarization",
"sing. scat. albedo :": "single_scattering_albedo",
}
for index in range(len(lines)):
current_line = lines[index]
for search, name in bottom_grid_extractors.items():
# If the label we're searching for is in the current line
if search in current_line:
items = current_line.rsplit(None, 3)
values = RayleighAerosolTotal()
try:
values.total = float(items[3])
except ValueError:
values.total = float("nan")
try:
values.aerosol = float(items[2])
except ValueError:
values.aerosol = float("nan")
try:
values.rayleigh = float(items[1])
except ValueError:
values.rayleigh = float("nan")
self.rat[name] = values
[docs] def to_int(self, str):
"""Converts a string to an integer.
Does this by converting to float and then converting that to int, meaning that
converting "5.00" to an integer will actually work.
Arguments:
* ``str`` -- The string containing the number to convert to an integer
"""
return int(float(str))
[docs] def extract_vis(self, data):
"""Extracts the visibility from the visibility and AOT line in the output"""
s = " ".join(data)
spl = s.split(":")
spl2 = spl[1].split()
try:
value = float(spl2[0])
except ValueError:
value = float("Inf")
return value
[docs] def extract_aot(self, data):
"""Extracts the AOT from the visibility and AOT line in the output."""
s = " ".join(data)
spl = s.split(":")
return float(spl[2])
[docs] def write_output_file(self, filename):
"""Writes the full textual output of the 6S model run to the specified filename.
Arguments:
* ``filename`` -- The filename to write the output to
"""
with open(filename, "w") as f:
f.write(self.fulltext)
class Transmittance(object):
"""Stores transmittance values from the 6S output.
Basically a simple class storing three attributes:
* ``downward`` -- Transmittance downwards
* ``upward`` -- Transmittance upwards
* ``total`` -- Total transmittance
"""
downward = float("nan")
upward = float("nan")
total = float("nan")
def __str__(self):
return "Downward: %f, Upward: %f, Total: %f" % (
self.downward,
self.upward,
self.total,
)
class RayleighAerosolTotal(object):
rayleigh = float("nan")
aerosol = float("nan")
total = float("nan")
def __str__(self):
return "Rayleigh: %f, Aerosol: %f, Total: %f" % (
self.rayleigh,
self.aerosol,
self.total,
)