jerlov 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jerlov/__init__.py +51 -0
- jerlov/_data.py +113 -0
- jerlov/colour.py +255 -0
- jerlov/data/__init__.py +1 -0
- jerlov/data/austin1986_kd.csv +91 -0
- jerlov/data/austin1986_model.csv +72 -0
- jerlov/data/cie1931_2deg_cmf.csv +472 -0
- jerlov/data/cie_d65.csv +98 -0
- jerlov/data/jerlov1976_kd.csv +4161 -0
- jerlov/data/smart2007_b_from_c.csv +31 -0
- jerlov/data/solonenko2015_iop.csv +851 -0
- jerlov/data/williamson2022_iop.csv +6013 -0
- jerlov/data/williamson2022_measured.csv +153 -0
- jerlov/scene.py +259 -0
- jerlov/sources.py +179 -0
- jerlov/water.py +337 -0
- jerlov-0.1.1.dist-info/METADATA +201 -0
- jerlov-0.1.1.dist-info/RECORD +22 -0
- jerlov-0.1.1.dist-info/WHEEL +5 -0
- jerlov-0.1.1.dist-info/licenses/LICENSE +201 -0
- jerlov-0.1.1.dist-info/licenses/NOTICE +13 -0
- jerlov-0.1.1.dist-info/top_level.txt +1 -0
jerlov/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Inherent optical properties of Jerlov water types.
|
|
2
|
+
|
|
3
|
+
Every coefficient carries its source, and values that a published table got
|
|
4
|
+
wrong are flagged rather than quietly repaired.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .colour import (
|
|
8
|
+
CoverageWarning,
|
|
9
|
+
GamutWarning,
|
|
10
|
+
cie_1931_cmf,
|
|
11
|
+
d65,
|
|
12
|
+
integrate_response,
|
|
13
|
+
spectrum_to_srgb,
|
|
14
|
+
spectrum_to_xyz,
|
|
15
|
+
xyz_to_srgb,
|
|
16
|
+
)
|
|
17
|
+
from .scene import Observation, Scene, veiling_radiance_estimate
|
|
18
|
+
from .sources import SOURCES, Source, get_source
|
|
19
|
+
from .water import (
|
|
20
|
+
MissingQuantityError,
|
|
21
|
+
ProvenanceWarning,
|
|
22
|
+
Water,
|
|
23
|
+
b_from_c,
|
|
24
|
+
kd_spectrum,
|
|
25
|
+
water,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"SOURCES",
|
|
30
|
+
"Scene",
|
|
31
|
+
"Observation",
|
|
32
|
+
"veiling_radiance_estimate",
|
|
33
|
+
"spectrum_to_xyz",
|
|
34
|
+
"spectrum_to_srgb",
|
|
35
|
+
"xyz_to_srgb",
|
|
36
|
+
"integrate_response",
|
|
37
|
+
"cie_1931_cmf",
|
|
38
|
+
"d65",
|
|
39
|
+
"GamutWarning",
|
|
40
|
+
"CoverageWarning",
|
|
41
|
+
"Source",
|
|
42
|
+
"get_source",
|
|
43
|
+
"Water",
|
|
44
|
+
"water",
|
|
45
|
+
"kd_spectrum",
|
|
46
|
+
"b_from_c",
|
|
47
|
+
"ProvenanceWarning",
|
|
48
|
+
"MissingQuantityError",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
__version__ = "0.1.1"
|
jerlov/_data.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Loading of the packaged coefficient tables.
|
|
2
|
+
|
|
3
|
+
Every table is a CSV shipped inside the package. Each row carries a ``status``
|
|
4
|
+
column recording what is known about that particular value; nothing is
|
|
5
|
+
silently cleaned up on the way in.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import csv
|
|
11
|
+
from functools import lru_cache
|
|
12
|
+
from importlib import resources
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
#: Values whose ``status`` is one of these should not be used without the
|
|
17
|
+
#: caller being told. See README sections 1-6.
|
|
18
|
+
QUESTIONABLE = frozenset({"suspect", "missing", "extrapolated",
|
|
19
|
+
"model_extrapolation", "reconstructed"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _read(name: str) -> list[dict[str, str]]:
|
|
23
|
+
path = resources.files("jerlov.data").joinpath(name)
|
|
24
|
+
with path.open("r", encoding="utf-8", newline="") as handle:
|
|
25
|
+
return list(csv.DictReader(handle))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@lru_cache(maxsize=None)
|
|
29
|
+
def _rows(name: str) -> tuple[dict[str, str], ...]:
|
|
30
|
+
return tuple(_read(name))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _to_float(text: str) -> float:
|
|
34
|
+
return float(text) if text.strip() else float("nan")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@lru_cache(maxsize=None)
|
|
38
|
+
def spectrum(
|
|
39
|
+
filename: str,
|
|
40
|
+
water_type: str,
|
|
41
|
+
quantity: str | None,
|
|
42
|
+
value_column: str,
|
|
43
|
+
) -> tuple[np.ndarray, np.ndarray, tuple[str, ...]]:
|
|
44
|
+
"""Return ``(wavelengths, values, statuses)`` for one series.
|
|
45
|
+
|
|
46
|
+
Rows whose value is empty are kept, with the value set to NaN, so that a
|
|
47
|
+
gap in the published table stays visible instead of being interpolated
|
|
48
|
+
across without comment.
|
|
49
|
+
"""
|
|
50
|
+
wl: list[float] = []
|
|
51
|
+
values: list[float] = []
|
|
52
|
+
statuses: list[str] = []
|
|
53
|
+
for row in _rows(filename):
|
|
54
|
+
if row.get("water_type") != water_type:
|
|
55
|
+
continue
|
|
56
|
+
if quantity is not None and row.get("quantity") != quantity:
|
|
57
|
+
continue
|
|
58
|
+
wl.append(float(row["wavelength_nm"]))
|
|
59
|
+
values.append(_to_float(row[value_column]))
|
|
60
|
+
statuses.append(row.get("status", ""))
|
|
61
|
+
if not wl:
|
|
62
|
+
raise KeyError(
|
|
63
|
+
f"no rows in {filename} for water_type={water_type!r}"
|
|
64
|
+
+ (f", quantity={quantity!r}" if quantity else "")
|
|
65
|
+
)
|
|
66
|
+
order = np.argsort(wl)
|
|
67
|
+
return (
|
|
68
|
+
np.asarray(wl, dtype=float)[order],
|
|
69
|
+
np.asarray(values, dtype=float)[order],
|
|
70
|
+
tuple(statuses[i] for i in order),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@lru_cache(maxsize=None)
|
|
75
|
+
def notes(filename: str, water_type: str, quantity: str | None) -> dict[float, str]:
|
|
76
|
+
"""Map wavelength to the ``note`` column, for rows that carry one."""
|
|
77
|
+
out: dict[float, str] = {}
|
|
78
|
+
for row in _rows(filename):
|
|
79
|
+
if row.get("water_type") != water_type:
|
|
80
|
+
continue
|
|
81
|
+
if quantity is not None and row.get("quantity") != quantity:
|
|
82
|
+
continue
|
|
83
|
+
note = row.get("note", "").strip()
|
|
84
|
+
if note:
|
|
85
|
+
out[float(row["wavelength_nm"])] = note
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@lru_cache(maxsize=None)
|
|
90
|
+
def austin_model() -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
91
|
+
"""Return ``(wavelengths, M, Kw)`` from Austin & Petzold (1986) Table IV."""
|
|
92
|
+
rows = _rows("austin1986_model.csv")
|
|
93
|
+
wl = np.array([float(r["wavelength_nm"]) for r in rows])
|
|
94
|
+
m = np.array([float(r["M_slope"]) for r in rows])
|
|
95
|
+
kw = np.array([float(r["Kw_pure_seawater_per_m"]) for r in rows])
|
|
96
|
+
order = np.argsort(wl)
|
|
97
|
+
return wl[order], m[order], kw[order]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@lru_cache(maxsize=None)
|
|
101
|
+
def b_from_c_ratio() -> tuple[np.ndarray, dict[str, np.ndarray]]:
|
|
102
|
+
"""Return ``(wavelengths, {statistic: ratio})`` from Smart (2007) Table 1."""
|
|
103
|
+
rows = _rows("smart2007_b_from_c.csv")
|
|
104
|
+
wl = sorted({float(r["wavelength_nm"]) for r in rows})
|
|
105
|
+
out: dict[str, np.ndarray] = {}
|
|
106
|
+
for stat in ("average", "min", "max"):
|
|
107
|
+
by_wl = {
|
|
108
|
+
float(r["wavelength_nm"]): float(r["b_minus_bw_over_c_minus_cw"])
|
|
109
|
+
for r in rows
|
|
110
|
+
if r["statistic"] == stat
|
|
111
|
+
}
|
|
112
|
+
out[stat] = np.array([by_wl[w] for w in wl])
|
|
113
|
+
return np.array(wl), out
|
jerlov/colour.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""From a spectrum to a colour, or to whatever a sensor or an eye would see.
|
|
2
|
+
|
|
3
|
+
Two things are easy to get wrong here and are checked rather than assumed.
|
|
4
|
+
|
|
5
|
+
**Coverage.** Integrating a spectrum that only spans 450-650 nm against
|
|
6
|
+
colour matching functions that span 360-830 nm silently drops the ends and
|
|
7
|
+
shifts the result. Every integration reports how much of the observer's
|
|
8
|
+
sensitivity the spectrum actually covered, and warns when the answer is not
|
|
9
|
+
essentially all of it.
|
|
10
|
+
|
|
11
|
+
**White.** A radiance spectrum has no colour until something is called white.
|
|
12
|
+
There is no default; the caller states the reference. Underwater the sensible
|
|
13
|
+
reference is usually the downwelling irradiance at that depth, which is what
|
|
14
|
+
makes a grey card look grey there rather than blue.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from . import _data
|
|
24
|
+
|
|
25
|
+
#: sRGB primaries and white point, IEC 61966-2-1.
|
|
26
|
+
SRGB_PRIMARIES = np.array([
|
|
27
|
+
[0.6400, 0.3300], # red
|
|
28
|
+
[0.3000, 0.6000], # green
|
|
29
|
+
[0.1500, 0.0600], # blue
|
|
30
|
+
])
|
|
31
|
+
SRGB_WHITEPOINT_XY = np.array([0.3127, 0.3290]) # D65
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _xy_to_xyz(xy: np.ndarray) -> np.ndarray:
|
|
35
|
+
"""Chromaticity to XYZ with Y = 1."""
|
|
36
|
+
x, y = xy
|
|
37
|
+
return np.array([x / y, 1.0, (1 - x - y) / y])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#: XYZ of the sRGB white, Y = 1. Adaptation maps a stated white onto this.
|
|
41
|
+
SRGB_WHITEPOINT_XYZ = _xy_to_xyz(SRGB_WHITEPOINT_XY)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class GamutWarning(UserWarning):
|
|
45
|
+
"""A colour fell outside the range the display can show.
|
|
46
|
+
|
|
47
|
+
Underwater colours often do. Clipping changes them, so the caller is told
|
|
48
|
+
rather than left to wonder.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CoverageWarning(UserWarning):
|
|
53
|
+
"""The spectrum did not span the observer's sensitivity.
|
|
54
|
+
|
|
55
|
+
The integral is then over the overlap only, and the result is biased by
|
|
56
|
+
however much was left out.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _rgb_matrix(primaries: np.ndarray, whitepoint_xy: np.ndarray) -> np.ndarray:
|
|
61
|
+
"""Derive the XYZ to linear RGB matrix from chromaticities."""
|
|
62
|
+
x, y = primaries[:, 0], primaries[:, 1]
|
|
63
|
+
m = np.vstack([x / y, np.ones(3), (1 - x - y) / y])
|
|
64
|
+
scale = np.linalg.solve(m, _xy_to_xyz(whitepoint_xy))
|
|
65
|
+
return np.linalg.inv(m * scale)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
#: Derived here rather than hard-coded, so the primaries stay the source of
|
|
69
|
+
#: truth. `tests/test_colour.py` checks it against the published matrix.
|
|
70
|
+
XYZ_TO_LINEAR_SRGB = _rgb_matrix(SRGB_PRIMARIES, SRGB_WHITEPOINT_XY)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def cie_1931_cmf() -> tuple[np.ndarray, np.ndarray]:
|
|
74
|
+
"""Return ``(wavelengths, cmf)`` for the CIE 1931 2-degree observer.
|
|
75
|
+
|
|
76
|
+
``cmf`` has shape ``(n, 3)``, columns x-bar, y-bar, z-bar, at 1 nm from
|
|
77
|
+
360 to 830 nm.
|
|
78
|
+
"""
|
|
79
|
+
rows = _data._rows("cie1931_2deg_cmf.csv")
|
|
80
|
+
wl = np.array([float(r["wavelength_nm"]) for r in rows])
|
|
81
|
+
cmf = np.array([
|
|
82
|
+
[float(r["x_bar"]), float(r["y_bar"]), float(r["z_bar"])] for r in rows
|
|
83
|
+
])
|
|
84
|
+
return wl, cmf
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def d65() -> tuple[np.ndarray, np.ndarray]:
|
|
88
|
+
"""Return ``(wavelengths, relative power)`` for CIE illuminant D65.
|
|
89
|
+
|
|
90
|
+
This is the sRGB reference white, tabulated at 5 nm from 300 to 780 nm.
|
|
91
|
+
|
|
92
|
+
It is a daylight phase, so it is a reasonable stand-in for the solar
|
|
93
|
+
spectrum above the surface, but it is not a measurement of the light at
|
|
94
|
+
any particular place or time: the real spectrum depends on solar
|
|
95
|
+
elevation, atmosphere and the state of the surface.
|
|
96
|
+
"""
|
|
97
|
+
rows = _data._rows("cie_d65.csv")
|
|
98
|
+
wl = np.array([float(r["wavelength_nm"]) for r in rows])
|
|
99
|
+
power = np.array([float(r["relative_spectral_power"]) for r in rows])
|
|
100
|
+
return wl, power
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _resample(values, source_wl, target_wl) -> np.ndarray:
|
|
104
|
+
"""Interpolate onto ``target_wl``, leaving zeros outside the source."""
|
|
105
|
+
return np.interp(target_wl, source_wl, values, left=0.0, right=0.0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _coverage(spectrum_wl, weight_wl, weight) -> float:
|
|
109
|
+
"""Fraction of the weight that the spectrum's range actually spans."""
|
|
110
|
+
total = np.trapezoid(weight, weight_wl)
|
|
111
|
+
if total == 0:
|
|
112
|
+
return 1.0
|
|
113
|
+
inside = (weight_wl >= spectrum_wl[0]) & (weight_wl <= spectrum_wl[-1])
|
|
114
|
+
if not inside.any():
|
|
115
|
+
return 0.0
|
|
116
|
+
return float(np.trapezoid(weight[inside], weight_wl[inside]) / total)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def integrate_response(spectrum, wavelengths, response, response_wavelengths,
|
|
120
|
+
*, name: str = "response") -> np.ndarray:
|
|
121
|
+
"""Integrate a spectrum against one or more spectral sensitivities.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
spectrum:
|
|
126
|
+
Spectral radiance or irradiance, on ``wavelengths``.
|
|
127
|
+
response:
|
|
128
|
+
Shape ``(m,)`` for a single channel or ``(m, k)`` for ``k`` channels,
|
|
129
|
+
on ``response_wavelengths``. Camera sensitivities and photoreceptor
|
|
130
|
+
absorbances both fit here.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
Array of shape ``(k,)``: the integral of spectrum times each channel over
|
|
135
|
+
wavelength, in the units of the spectrum times nm.
|
|
136
|
+
"""
|
|
137
|
+
spectrum = np.asarray(spectrum, dtype=float)
|
|
138
|
+
wavelengths = np.asarray(wavelengths, dtype=float)
|
|
139
|
+
response = np.atleast_2d(np.asarray(response, dtype=float))
|
|
140
|
+
if response.shape[0] != np.size(response_wavelengths):
|
|
141
|
+
response = response.T
|
|
142
|
+
response_wavelengths = np.asarray(response_wavelengths, dtype=float)
|
|
143
|
+
if spectrum.shape != wavelengths.shape:
|
|
144
|
+
raise ValueError("spectrum must have the same shape as wavelengths")
|
|
145
|
+
if response.shape[0] != response_wavelengths.shape[0]:
|
|
146
|
+
raise ValueError("response must be aligned with response_wavelengths")
|
|
147
|
+
if wavelengths.size < 2:
|
|
148
|
+
raise ValueError("at least two wavelengths are needed to integrate")
|
|
149
|
+
|
|
150
|
+
worst = min(
|
|
151
|
+
_coverage(wavelengths, response_wavelengths, response[:, k])
|
|
152
|
+
for k in range(response.shape[1])
|
|
153
|
+
)
|
|
154
|
+
if worst < 0.999:
|
|
155
|
+
warnings.warn(
|
|
156
|
+
f"the spectrum spans {wavelengths[0]:g}-{wavelengths[-1]:g} nm and "
|
|
157
|
+
f"covers only {worst:.1%} of the {name}; the integral is over the "
|
|
158
|
+
"overlap and is biased by what was left out",
|
|
159
|
+
CoverageWarning,
|
|
160
|
+
stacklevel=2,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
resampled = np.stack(
|
|
164
|
+
[_resample(response[:, k], response_wavelengths, wavelengths)
|
|
165
|
+
for k in range(response.shape[1])],
|
|
166
|
+
axis=1,
|
|
167
|
+
)
|
|
168
|
+
return np.trapezoid(spectrum[:, None] * resampled, wavelengths, axis=0)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def spectrum_to_xyz(spectrum, wavelengths) -> np.ndarray:
|
|
172
|
+
"""Integrate a spectrum against the CIE 1931 2-degree observer.
|
|
173
|
+
|
|
174
|
+
The result is unnormalised: it carries the units of the spectrum. Divide
|
|
175
|
+
by the XYZ of whatever counts as white before converting to a display
|
|
176
|
+
colour, or use :func:`spectrum_to_srgb`, which requires that reference.
|
|
177
|
+
"""
|
|
178
|
+
cmf_wl, cmf = cie_1931_cmf()
|
|
179
|
+
return integrate_response(
|
|
180
|
+
spectrum, wavelengths, cmf, cmf_wl, name="CIE 1931 2-degree observer"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def xyz_to_srgb(xyz, *, clip: bool = True) -> np.ndarray:
|
|
185
|
+
"""Convert XYZ, normalised so that white is Y = 1, to sRGB.
|
|
186
|
+
|
|
187
|
+
Applies the sRGB transfer function of IEC 61966-2-1. Warns if any channel
|
|
188
|
+
falls outside the display gamut, which underwater colours often do.
|
|
189
|
+
"""
|
|
190
|
+
xyz = np.asarray(xyz, dtype=float)
|
|
191
|
+
linear = np.tensordot(xyz, XYZ_TO_LINEAR_SRGB.T, axes=([-1], [0]))
|
|
192
|
+
|
|
193
|
+
outside = np.any(linear < -1e-9) or np.any(linear > 1 + 1e-9)
|
|
194
|
+
if outside:
|
|
195
|
+
warnings.warn(
|
|
196
|
+
"the colour lies outside the sRGB gamut"
|
|
197
|
+
+ (" and has been clipped" if clip else ""),
|
|
198
|
+
GamutWarning,
|
|
199
|
+
stacklevel=2,
|
|
200
|
+
)
|
|
201
|
+
if clip:
|
|
202
|
+
linear = np.clip(linear, 0.0, 1.0)
|
|
203
|
+
|
|
204
|
+
a = 0.055
|
|
205
|
+
with np.errstate(invalid="ignore"):
|
|
206
|
+
return np.where(
|
|
207
|
+
linear <= 0.0031308,
|
|
208
|
+
12.92 * linear,
|
|
209
|
+
(1 + a) * np.power(np.maximum(linear, 0.0), 1 / 2.4) - a,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def spectrum_to_srgb(spectrum, wavelengths, *, white, clip: bool = True):
|
|
214
|
+
"""Convert a spectrum to sRGB, relative to a stated white.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
white:
|
|
219
|
+
The spectrum that should come out white, on the same wavelengths.
|
|
220
|
+
There is no default: a radiance spectrum has no colour until
|
|
221
|
+
something is called white, and underwater the answer is usually the
|
|
222
|
+
downwelling irradiance at that depth rather than a surface daylight.
|
|
223
|
+
|
|
224
|
+
Notes
|
|
225
|
+
-----
|
|
226
|
+
The spectrum's XYZ is scaled component by component so that the stated
|
|
227
|
+
white lands on the sRGB white point, which makes it come out exactly
|
|
228
|
+
neutral. This is a von Kries-type adaptation carried out in XYZ rather
|
|
229
|
+
than in cone space: transparent, and less accurate than CAT02 or Bradford
|
|
230
|
+
for strongly coloured illumination.
|
|
231
|
+
Underwater illumination is strongly coloured, so treat the result as
|
|
232
|
+
"what a white-balanced camera would record" rather than as a prediction
|
|
233
|
+
of appearance.
|
|
234
|
+
|
|
235
|
+
Absolute brightness is lost: a perfect diffuser under the stated white
|
|
236
|
+
maps to white whatever the light level.
|
|
237
|
+
"""
|
|
238
|
+
if white is None:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
"white has no default: a spectrum has no colour until something "
|
|
241
|
+
"is called white. Underwater this is usually the downwelling "
|
|
242
|
+
"irradiance at that depth."
|
|
243
|
+
)
|
|
244
|
+
white = np.asarray(white, dtype=float)
|
|
245
|
+
if white.shape != np.shape(spectrum):
|
|
246
|
+
raise ValueError("white must have the same shape as the spectrum")
|
|
247
|
+
|
|
248
|
+
xyz = spectrum_to_xyz(spectrum, wavelengths)
|
|
249
|
+
white_xyz = spectrum_to_xyz(white, wavelengths)
|
|
250
|
+
if np.any(white_xyz <= 0):
|
|
251
|
+
raise ValueError(
|
|
252
|
+
"the white reference has no power in one of X, Y or Z, so it "
|
|
253
|
+
"cannot serve as a white"
|
|
254
|
+
)
|
|
255
|
+
return xyz_to_srgb(xyz / white_xyz * SRGB_WHITEPOINT_XYZ, clip=clip)
|
jerlov/data/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged coefficient tables. See the project README for provenance."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
water_type,wavelength_nm,Kd_downwelling_per_m,status,note
|
|
2
|
+
I,350,0.0510,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
3
|
+
I,375,0.0302,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
4
|
+
I,400,0.0217,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
5
|
+
I,425,0.0185,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
6
|
+
I,450,0.0176,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
7
|
+
I,475,0.0184,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
8
|
+
I,500,0.0280,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
9
|
+
I,525,0.0504,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
10
|
+
I,550,0.0640,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
11
|
+
I,575,0.0931,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
12
|
+
I,600,0.2408,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
13
|
+
I,625,0.3174,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
14
|
+
I,650,0.3559,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
15
|
+
I,675,0.4372,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
16
|
+
I,700,0.6513,pure_seawater,the type I row is pure sea water Kw; Jerlov's own values fall below it
|
|
17
|
+
IA,350,0.0632,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
18
|
+
IA,375,0.0412,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
19
|
+
IA,400,0.0316,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
20
|
+
IA,425,0.0280,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
21
|
+
IA,450,0.0257,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
22
|
+
IA,475,0.0250,jerlov_original,475 nm is identical to Jerlov (1976) Table XXVII
|
|
23
|
+
IA,500,0.0332,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
24
|
+
IA,525,0.0545,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
25
|
+
IA,550,0.0674,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
26
|
+
IA,575,0.0960,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
27
|
+
IA,600,0.2437,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
28
|
+
IA,625,0.3206,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
29
|
+
IA,650,0.3601,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
30
|
+
IA,675,0.4410,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
31
|
+
IA,700,0.6530,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
32
|
+
IB,350,0.0782,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
33
|
+
IB,375,0.0546,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
34
|
+
IB,400,0.0438,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
35
|
+
IB,425,0.0395,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
36
|
+
IB,450,0.0355,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
37
|
+
IB,475,0.0330,jerlov_original,475 nm is identical to Jerlov (1976) Table XXVII
|
|
38
|
+
IB,500,0.0396,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
39
|
+
IB,525,0.0596,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
40
|
+
IB,550,0.0715,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
41
|
+
IB,575,0.0995,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
42
|
+
IB,600,0.2471,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
43
|
+
IB,625,0.3245,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
44
|
+
IB,650,0.3652,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
45
|
+
IB,675,0.4457,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
46
|
+
IB,700,0.6550,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
47
|
+
II,350,0.1325,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
48
|
+
II,375,0.1031,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
49
|
+
II,400,0.0878,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
50
|
+
II,425,0.0814,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
51
|
+
II,450,0.0714,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
52
|
+
II,475,0.0620,jerlov_original,475 nm is identical to Jerlov (1976) Table XXVII
|
|
53
|
+
II,500,0.0627,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
54
|
+
II,525,0.0779,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
55
|
+
II,550,0.0863,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
56
|
+
II,575,0.1122,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
57
|
+
II,600,0.2595,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
58
|
+
II,625,0.3389,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
59
|
+
II,650,0.3837,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
60
|
+
II,675,0.4626,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
61
|
+
II,700,0.6623,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
62
|
+
III,350,0.2335,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
63
|
+
III,375,0.1935,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
64
|
+
III,400,0.1697,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
65
|
+
III,425,0.1594,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
66
|
+
III,450,0.1381,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
67
|
+
III,475,0.1160,jerlov_original,475 nm is identical to Jerlov (1976) Table XXVII
|
|
68
|
+
III,500,0.1056,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
69
|
+
III,525,0.1120,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
70
|
+
III,550,0.1139,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
71
|
+
III,575,0.1359,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
72
|
+
III,600,0.2826,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
73
|
+
III,625,0.3655,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
74
|
+
III,650,0.4181,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
75
|
+
III,675,0.4942,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
76
|
+
III,700,0.6760,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
77
|
+
1C,350,0.3345,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
78
|
+
1C,375,0.2839,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
79
|
+
1C,400,0.2516,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
80
|
+
1C,425,0.2374,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
81
|
+
1C,450,0.2048,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
82
|
+
1C,475,0.1700,jerlov_original,475 nm is identical to Jerlov (1976) Table XXVII
|
|
83
|
+
1C,500,0.1486,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
84
|
+
1C,525,0.1461,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
85
|
+
1C,550,0.1415,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
86
|
+
1C,575,0.1596,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
87
|
+
1C,600,0.3057,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
88
|
+
1C,625,0.3922,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
89
|
+
1C,650,0.4525,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
90
|
+
1C,675,0.5257,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
91
|
+
1C,700,0.6896,model,computed from K(475) by Austin & Petzold Eq. (6)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
wavelength_nm,M_slope,Kw_pure_seawater_per_m,status,note
|
|
2
|
+
350,2.1442,0.0510,extrapolated,the paper states this M was extrapolated and should be used with caution
|
|
3
|
+
355,2.0968,0.0453,extrapolated,the paper states this M was extrapolated and should be used with caution
|
|
4
|
+
360,2.0504,0.0405,extrapolated,the paper states this M was extrapolated and should be used with caution
|
|
5
|
+
365,2.0051,0.0365,ok,
|
|
6
|
+
370,1.9610,0.0331,ok,
|
|
7
|
+
375,1.9183,0.0302,ok,
|
|
8
|
+
380,1.8772,0.0278,ok,
|
|
9
|
+
385,1.8379,0.0258,ok,
|
|
10
|
+
390,1.8009,0.0242,ok,
|
|
11
|
+
395,1.7671,0.0228,ok,
|
|
12
|
+
400,1.7383,0.0217,ok,
|
|
13
|
+
405,1.7463,0.0208,ok,
|
|
14
|
+
410,1.7591,0.0200,ok,
|
|
15
|
+
415,1.7312,0.0194,ok,
|
|
16
|
+
420,1.6974,0.0189,ok,
|
|
17
|
+
425,1.6550,0.0185,ok,
|
|
18
|
+
430,1.6108,0.0182,ok,
|
|
19
|
+
435,1.5648,0.0180,ok,
|
|
20
|
+
440,1.5169,0.0178,ok,
|
|
21
|
+
445,1.4673,0.0176,ok,
|
|
22
|
+
450,1.4158,0.0176,ok,
|
|
23
|
+
455,1.3627,0.0175,ok,
|
|
24
|
+
460,1.3077,0.0176,ok,
|
|
25
|
+
465,1.2521,0.0177,ok,
|
|
26
|
+
470,1.1982,0.0179,ok,
|
|
27
|
+
475,1.1460,0.0184,ok,
|
|
28
|
+
480,1.0955,0.0193,ok,
|
|
29
|
+
485,1.0469,0.0206,ok,
|
|
30
|
+
490,1.0000,0.0224,ok,
|
|
31
|
+
495,0.9550,0.0248,ok,
|
|
32
|
+
500,0.9118,0.0280,ok,
|
|
33
|
+
505,0.8704,0.0320,ok,
|
|
34
|
+
510,0.8310,0.0369,ok,
|
|
35
|
+
515,0.7934,0.0428,ok,
|
|
36
|
+
520,0.7578,0.0498,ok,
|
|
37
|
+
525,0.7241,0.0504,ok,
|
|
38
|
+
530,0.6924,0.0526,ok,
|
|
39
|
+
535,0.6627,0.0550,ok,
|
|
40
|
+
540,0.6350,0.0577,ok,
|
|
41
|
+
545,0.6094,0.0607,ok,
|
|
42
|
+
550,0.5860,0.0640,ok,
|
|
43
|
+
555,0.5647,0.0678,ok,
|
|
44
|
+
560,0.5457,0.0723,ok,
|
|
45
|
+
565,0.5289,0.0776,ok,
|
|
46
|
+
570,0.5146,0.0842,ok,
|
|
47
|
+
575,0.5027,0.0931,ok,
|
|
48
|
+
580,0.4935,0.1065,ok,
|
|
49
|
+
585,0.4871,0.1341,ok,
|
|
50
|
+
590,0.4840,0.1578,ok,
|
|
51
|
+
595,0.4853,0.2043,ok,
|
|
52
|
+
600,0.4903,0.2409,ok,
|
|
53
|
+
605,0.4983,0.2688,ok,
|
|
54
|
+
610,0.5090,0.2892,ok,
|
|
55
|
+
615,0.5223,0.3040,ok,
|
|
56
|
+
620,0.5380,0.3124,ok,
|
|
57
|
+
625,0.5659,0.3174,ok,
|
|
58
|
+
630,0.6231,0.3196,ok,
|
|
59
|
+
635,0.6683,0.3227,ok,
|
|
60
|
+
640,0.7001,0.3290,ok,
|
|
61
|
+
645,0.7201,0.3397,ok,
|
|
62
|
+
650,0.7300,0.3559,ok,
|
|
63
|
+
655,0.7323,0.3789,ok,
|
|
64
|
+
660,0.7301,0.4105,ok,
|
|
65
|
+
665,0.7205,0.4208,ok,
|
|
66
|
+
670,0.7008,0.4278,ok,
|
|
67
|
+
675,0.6693,0.4372,ok,
|
|
68
|
+
680,0.6245,0.4521,ok,
|
|
69
|
+
685,0.5651,0.4755,ok,
|
|
70
|
+
690,0.4901,0.5116,ok,
|
|
71
|
+
695,0.3984,0.5671,ok,
|
|
72
|
+
700,0.2891,0.6514,ok,
|