cloudnetpy 1.65.8__py3-none-any.whl → 1.66.0__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.
- cloudnetpy/categorize/__init__.py +0 -1
- cloudnetpy/categorize/atmos_utils.py +278 -59
- cloudnetpy/categorize/attenuation.py +31 -0
- cloudnetpy/categorize/attenuations/__init__.py +37 -0
- cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +80 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +75 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +140 -81
- cloudnetpy/categorize/classify.py +92 -128
- cloudnetpy/categorize/containers.py +45 -31
- cloudnetpy/categorize/droplet.py +2 -2
- cloudnetpy/categorize/falling.py +3 -3
- cloudnetpy/categorize/freezing.py +2 -2
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/melting.py +0 -3
- cloudnetpy/categorize/model.py +31 -14
- cloudnetpy/categorize/radar.py +28 -12
- cloudnetpy/constants.py +3 -6
- cloudnetpy/model_evaluation/file_handler.py +2 -2
- cloudnetpy/model_evaluation/products/observation_products.py +8 -8
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +5 -2
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +11 -11
- cloudnetpy/output.py +46 -26
- cloudnetpy/plotting/plot_meta.py +8 -2
- cloudnetpy/plotting/plotting.py +28 -6
- cloudnetpy/products/classification.py +39 -34
- cloudnetpy/products/der.py +15 -13
- cloudnetpy/products/drizzle_tools.py +22 -21
- cloudnetpy/products/ier.py +8 -45
- cloudnetpy/products/iwc.py +7 -22
- cloudnetpy/products/lwc.py +14 -15
- cloudnetpy/products/mwr_tools.py +15 -2
- cloudnetpy/products/product_tools.py +121 -119
- cloudnetpy/utils.py +4 -0
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.65.8.dist-info → cloudnetpy-1.66.0.dist-info}/METADATA +1 -1
- {cloudnetpy-1.65.8.dist-info → cloudnetpy-1.66.0.dist-info}/RECORD +41 -35
- {cloudnetpy-1.65.8.dist-info → cloudnetpy-1.66.0.dist-info}/WHEEL +1 -1
- cloudnetpy/categorize/atmos.py +0 -376
- {cloudnetpy-1.65.8.dist-info → cloudnetpy-1.66.0.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.65.8.dist-info → cloudnetpy-1.66.0.dist-info}/top_level.txt +0 -0
cloudnetpy/products/iwc.py
CHANGED
@@ -43,8 +43,7 @@ def generate_iwc(
|
|
43
43
|
product = "iwc"
|
44
44
|
with IwcSource(categorize_file, product) as iwc_source:
|
45
45
|
ice_classification = IceClassification(categorize_file)
|
46
|
-
iwc_source.
|
47
|
-
iwc_source.append_main_variable(ice_classification)
|
46
|
+
iwc_source.append_icy_data(ice_classification)
|
48
47
|
iwc_source.append_bias()
|
49
48
|
iwc_source.append_sensitivity()
|
50
49
|
lwp_prior, bias = iwc_source.append_error(ice_classification)
|
@@ -92,7 +91,7 @@ class IwcSource(IceSource):
|
|
92
91
|
retrieval_uncertainty,
|
93
92
|
error_uncorrected,
|
94
93
|
)
|
95
|
-
iwc_error[
|
94
|
+
iwc_error[~ice_classification.is_ice | ice_classification.uncorrected_ice] = (
|
96
95
|
ma.masked
|
97
96
|
)
|
98
97
|
self.append_data(iwc_error, f"{self.product}_error")
|
@@ -162,12 +161,6 @@ COMMENTS = {
|
|
162
161
|
"This variable describes whether a retrieval was performed\n"
|
163
162
|
"for each pixel, and its associated quality."
|
164
163
|
),
|
165
|
-
"iwc_inc_rain": (
|
166
|
-
"This variable is the same as iwc but it also contains iwc values\n"
|
167
|
-
"above rain. The iwc values above rain have been severely affected\n"
|
168
|
-
"by attenuation and should be used when the effect of attenuation\n"
|
169
|
-
"is being studied."
|
170
|
-
),
|
171
164
|
}
|
172
165
|
|
173
166
|
DEFINITIONS = {
|
@@ -175,15 +168,12 @@ DEFINITIONS = {
|
|
175
168
|
{
|
176
169
|
0: """No ice present.""",
|
177
170
|
1: """Reliable retrieval.""",
|
178
|
-
2: """Unreliable retrieval due to uncorrected
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
using radiometer liquid water path which is not always
|
183
|
-
accurate.""",
|
171
|
+
2: """Unreliable retrieval due to uncorrected liquid, rain or
|
172
|
+
melting attenuation.""",
|
173
|
+
3: """Retrieval performed with radar corrected for liquid, rain and
|
174
|
+
melting attenuation.""",
|
184
175
|
4: """Ice detected only by the lidar.""",
|
185
|
-
5: """
|
186
|
-
due to very uncertain attenuation.""",
|
176
|
+
5: """Uncorrected rain attenuation (deprecated).""",
|
187
177
|
6: """Clear sky above rain and wet-bulb temperature less than 0degC:
|
188
178
|
if rain attenuation is strong, ice could be present but
|
189
179
|
undetected.""",
|
@@ -200,11 +190,6 @@ IWC_ATTRIBUTES = {
|
|
200
190
|
units="kg m-3",
|
201
191
|
ancillary_variables="iwc_error iwc_sensitivity iwc_bias",
|
202
192
|
),
|
203
|
-
"iwc_inc_rain": MetaData(
|
204
|
-
long_name="Ice water content including rain",
|
205
|
-
units="kg m-3",
|
206
|
-
comment=COMMENTS["iwc_inc_rain"],
|
207
|
-
),
|
208
193
|
"iwc_error": MetaData(
|
209
194
|
long_name="Random error in ice water content",
|
210
195
|
units="dB",
|
cloudnetpy/products/lwc.py
CHANGED
@@ -6,7 +6,7 @@ import numpy as np
|
|
6
6
|
from numpy import ma
|
7
7
|
|
8
8
|
from cloudnetpy import output, utils
|
9
|
-
from cloudnetpy.categorize import
|
9
|
+
from cloudnetpy.categorize import atmos_utils
|
10
10
|
from cloudnetpy.datasource import DataSource
|
11
11
|
from cloudnetpy.exceptions import InvalidSourceFileError
|
12
12
|
from cloudnetpy.metadata import MetaData
|
@@ -80,7 +80,7 @@ class LwcSource(DataSource):
|
|
80
80
|
lwp (ndarray): 1D liquid water path.
|
81
81
|
lwp_error (ndarray): 1D error of liquid water path.
|
82
82
|
is_rain (ndarray): 1D array denoting presence of rain.
|
83
|
-
|
83
|
+
path_lengths (ndarray): 1D array of path lengths.
|
84
84
|
atmosphere (dict): Dictionary containing interpolated fields `temperature`
|
85
85
|
and `pressure`.
|
86
86
|
categorize_bits (CategorizeBits): The :class:`CategorizeBits` instance.
|
@@ -96,7 +96,7 @@ class LwcSource(DataSource):
|
|
96
96
|
self.lwp[self.lwp < 0] = 0
|
97
97
|
self.lwp_error = self.getvar("lwp_error")
|
98
98
|
self.is_rain = get_is_rain(categorize_file)
|
99
|
-
self.
|
99
|
+
self.path_lengths = utils.path_lengths_from_ground(self.getvar("height"))
|
100
100
|
self.atmosphere = self._get_atmosphere(categorize_file)
|
101
101
|
self.categorize_bits = CategorizeBits(categorize_file)
|
102
102
|
|
@@ -125,7 +125,6 @@ class Lwc:
|
|
125
125
|
|
126
126
|
Attributes:
|
127
127
|
lwc_source (LwcSource): The :class:`LwcSource` instance.
|
128
|
-
dheight (float): Median difference in height vector.
|
129
128
|
is_liquid (ndarray): 2D array denoting liquid.
|
130
129
|
lwc_adiabatic (ndarray): 2D array storing adiabatic lwc.
|
131
130
|
lwc (ndarray): 2D array of liquid water content (scaled with lwp).
|
@@ -134,7 +133,7 @@ class Lwc:
|
|
134
133
|
|
135
134
|
def __init__(self, lwc_source: LwcSource):
|
136
135
|
self.lwc_source = lwc_source
|
137
|
-
self.
|
136
|
+
self.height = lwc_source.getvar("height")
|
138
137
|
self.is_liquid = self._get_liquid()
|
139
138
|
self.lwc_adiabatic = self._init_lwc_adiabatic()
|
140
139
|
self.lwc = self._adiabatic_lwc_to_lwc()
|
@@ -142,26 +141,26 @@ class Lwc:
|
|
142
141
|
|
143
142
|
def _get_liquid(self) -> np.ndarray:
|
144
143
|
category_bits = self.lwc_source.categorize_bits.category_bits
|
145
|
-
return category_bits
|
144
|
+
return category_bits.droplet
|
146
145
|
|
147
146
|
def _init_lwc_adiabatic(self) -> np.ndarray:
|
148
147
|
"""Returns theoretical adiabatic lwc in liquid clouds (kg/m3)."""
|
149
|
-
lwc_dz =
|
150
|
-
self.lwc_source.atmosphere,
|
148
|
+
lwc_dz = atmos_utils.fill_clouds_with_lwc_dz(
|
149
|
+
*self.lwc_source.atmosphere,
|
151
150
|
self.is_liquid,
|
152
151
|
)
|
153
|
-
return
|
152
|
+
return atmos_utils.calc_adiabatic_lwc(lwc_dz, self.height)
|
154
153
|
|
155
154
|
def _adiabatic_lwc_to_lwc(self) -> np.ndarray:
|
156
155
|
"""Initialises liquid water content (kg/m3).
|
157
156
|
|
158
157
|
Calculates LWC for ALL profiles (rain, lwp > theoretical, etc.),
|
159
158
|
"""
|
160
|
-
|
159
|
+
return atmos_utils.normalize_lwc_by_lwp(
|
161
160
|
self.lwc_adiabatic,
|
162
161
|
self.lwc_source.lwp,
|
162
|
+
self.height,
|
163
163
|
)
|
164
|
-
return lwc_scaled / self.dheight
|
165
164
|
|
166
165
|
def _mask_rain(self) -> None:
|
167
166
|
is_rain = self.lwc_source.is_rain.astype(bool)
|
@@ -198,7 +197,7 @@ class CloudAdjustor:
|
|
198
197
|
|
199
198
|
def _get_echo(self) -> dict:
|
200
199
|
quality_bits = self.lwc_source.categorize_bits.quality_bits
|
201
|
-
return {
|
200
|
+
return {"radar": quality_bits.radar, "lidar": quality_bits.lidar}
|
202
201
|
|
203
202
|
def _init_status(self) -> ma.MaskedArray:
|
204
203
|
status = ma.zeros(self.is_liquid.shape, dtype=int)
|
@@ -232,8 +231,8 @@ class CloudAdjustor:
|
|
232
231
|
distance_from_base += 1
|
233
232
|
|
234
233
|
def _has_converged(self, ind: int) -> bool:
|
235
|
-
lwc_sum = ma.sum(self.lwc_adiabatic[ind, :])
|
236
|
-
return lwc_sum
|
234
|
+
lwc_sum = ma.sum(self.lwc_adiabatic[ind, :] * self.lwc_source.path_lengths)
|
235
|
+
return lwc_sum > self.lwc_source.lwp[ind]
|
237
236
|
|
238
237
|
def _out_of_bound(self, ind: int) -> bool:
|
239
238
|
return ind >= self.lwc.shape[1] - 1
|
@@ -288,7 +287,7 @@ class CloudAdjustor:
|
|
288
287
|
In theory, this difference should be always positive. Negative values
|
289
288
|
indicate missing (or too narrow) liquid clouds.
|
290
289
|
"""
|
291
|
-
lwc_sum = ma.sum(self.lwc_adiabatic
|
290
|
+
lwc_sum = ma.sum(self.lwc_adiabatic * self.lwc_source.path_lengths, axis=1)
|
292
291
|
return lwc_sum - self.lwc_source.lwp
|
293
292
|
|
294
293
|
def _mask_rain(self) -> None:
|
cloudnetpy/products/mwr_tools.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
import os
|
1
2
|
import tempfile
|
2
3
|
from typing import Literal
|
3
4
|
|
4
5
|
import netCDF4
|
5
6
|
import numpy as np
|
7
|
+
import requests
|
6
8
|
from mwrpy.level2.lev2_collocated import generate_lev2_multi as gen_multi
|
7
9
|
from mwrpy.level2.lev2_collocated import generate_lev2_single as gen_single
|
8
10
|
from mwrpy.level2.write_lev2_nc import MissingInputData
|
@@ -10,7 +12,6 @@ from mwrpy.version import __version__ as mwrpy_version
|
|
10
12
|
|
11
13
|
from cloudnetpy import output, utils
|
12
14
|
from cloudnetpy.exceptions import ValidTimeStampError
|
13
|
-
from cloudnetpy.products import product_tools
|
14
15
|
|
15
16
|
|
16
17
|
def generate_mwr_single(
|
@@ -59,7 +60,7 @@ def _generate_product(
|
|
59
60
|
) -> str:
|
60
61
|
fun = gen_multi if product == "multi" else gen_single
|
61
62
|
with tempfile.TemporaryDirectory() as temp_dir:
|
62
|
-
coeffs =
|
63
|
+
coeffs = _read_mwrpy_coeffs(mwr_l1c_file, temp_dir)
|
63
64
|
try:
|
64
65
|
fun(None, mwr_l1c_file, output_file, coeff_files=coeffs)
|
65
66
|
except MissingInputData as err:
|
@@ -136,3 +137,15 @@ class Mwr:
|
|
136
137
|
self.nc_l2.source_file_uuids = self.nc_l1c.file_uuid
|
137
138
|
self.nc_l2.mwrpy_version = mwrpy_version
|
138
139
|
self.nc_l2.instrument_pid = self.nc_l1c.instrument_pid
|
140
|
+
|
141
|
+
|
142
|
+
def _read_mwrpy_coeffs(mwr_l1c_file, folder: str) -> list:
|
143
|
+
with netCDF4.Dataset(mwr_l1c_file) as nc:
|
144
|
+
links = nc.mwrpy_coefficients.split(", ")
|
145
|
+
coeffs = []
|
146
|
+
for link in links:
|
147
|
+
full_path = os.path.join(folder, link.split("/")[-1])
|
148
|
+
with open(full_path, "wb") as f:
|
149
|
+
f.write(requests.get(link, timeout=10).content)
|
150
|
+
coeffs.append(full_path)
|
151
|
+
return coeffs
|
@@ -1,12 +1,12 @@
|
|
1
1
|
"""General helper classes and functions for all products."""
|
2
2
|
|
3
|
-
import
|
3
|
+
from dataclasses import dataclass
|
4
4
|
from typing import NamedTuple
|
5
5
|
|
6
6
|
import netCDF4
|
7
7
|
import numpy as np
|
8
|
-
import requests
|
9
8
|
from numpy import ma
|
9
|
+
from numpy.typing import NDArray
|
10
10
|
|
11
11
|
from cloudnetpy import constants, utils
|
12
12
|
from cloudnetpy.categorize import atmos_utils
|
@@ -23,53 +23,63 @@ class IceCoefficients(NamedTuple):
|
|
23
23
|
c: float
|
24
24
|
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
@dataclass
|
27
|
+
class CategoryBits:
|
28
|
+
droplet: NDArray[np.bool_]
|
29
|
+
falling: NDArray[np.bool_]
|
30
|
+
freezing: NDArray[np.bool_]
|
31
|
+
melting: NDArray[np.bool_]
|
32
|
+
aerosol: NDArray[np.bool_]
|
33
|
+
insect: NDArray[np.bool_]
|
28
34
|
|
29
|
-
Args:
|
30
|
-
categorize_file (str): Categorize file name.
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
@dataclass
|
37
|
+
class QualityBits:
|
38
|
+
radar: NDArray[np.bool_]
|
39
|
+
lidar: NDArray[np.bool_]
|
40
|
+
clutter: NDArray[np.bool_]
|
41
|
+
molecular: NDArray[np.bool_]
|
42
|
+
attenuated_liquid: NDArray[np.bool_]
|
43
|
+
corrected_liquid: NDArray[np.bool_]
|
44
|
+
attenuated_rain: NDArray[np.bool_]
|
45
|
+
corrected_rain: NDArray[np.bool_]
|
46
|
+
attenuated_melting: NDArray[np.bool_]
|
47
|
+
corrected_melting: NDArray[np.bool_]
|
40
48
|
|
41
|
-
category_keys = (
|
42
|
-
"droplet",
|
43
|
-
"falling",
|
44
|
-
"cold",
|
45
|
-
"melting",
|
46
|
-
"aerosol",
|
47
|
-
"insect",
|
48
|
-
)
|
49
|
-
|
50
|
-
quality_keys = (
|
51
|
-
"radar",
|
52
|
-
"lidar",
|
53
|
-
"clutter",
|
54
|
-
"molecular",
|
55
|
-
"attenuated",
|
56
|
-
"corrected",
|
57
|
-
)
|
58
49
|
|
50
|
+
class CategorizeBits:
|
59
51
|
def __init__(self, categorize_file: str):
|
60
52
|
self._categorize_file = categorize_file
|
61
|
-
self.category_bits = self.
|
62
|
-
self.quality_bits = self.
|
53
|
+
self.category_bits = self._read_category_bits()
|
54
|
+
self.quality_bits = self._read_quality_bits()
|
55
|
+
|
56
|
+
def _read_category_bits(self) -> CategoryBits:
|
57
|
+
with netCDF4.Dataset(self._categorize_file) as nc:
|
58
|
+
bits = nc.variables["category_bits"][:]
|
59
|
+
return CategoryBits(
|
60
|
+
droplet=utils.isbit(bits, 0),
|
61
|
+
falling=utils.isbit(bits, 1),
|
62
|
+
freezing=utils.isbit(bits, 2),
|
63
|
+
melting=utils.isbit(bits, 3),
|
64
|
+
aerosol=utils.isbit(bits, 4),
|
65
|
+
insect=utils.isbit(bits, 5),
|
66
|
+
)
|
63
67
|
|
64
|
-
def
|
65
|
-
"""Converts bitfield into dictionary."""
|
68
|
+
def _read_quality_bits(self) -> QualityBits:
|
66
69
|
with netCDF4.Dataset(self._categorize_file) as nc:
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
bits = nc.variables["quality_bits"][:]
|
71
|
+
return QualityBits(
|
72
|
+
radar=utils.isbit(bits, 0),
|
73
|
+
lidar=utils.isbit(bits, 1),
|
74
|
+
clutter=utils.isbit(bits, 2),
|
75
|
+
molecular=utils.isbit(bits, 3),
|
76
|
+
attenuated_liquid=utils.isbit(bits, 4),
|
77
|
+
corrected_liquid=utils.isbit(bits, 5),
|
78
|
+
attenuated_rain=utils.isbit(bits, 6),
|
79
|
+
corrected_rain=utils.isbit(bits, 7),
|
80
|
+
attenuated_melting=utils.isbit(bits, 8),
|
81
|
+
corrected_melting=utils.isbit(bits, 9),
|
82
|
+
)
|
73
83
|
|
74
84
|
|
75
85
|
class ProductClassification(CategorizeBits):
|
@@ -96,53 +106,75 @@ class IceClassification(ProductClassification):
|
|
96
106
|
|
97
107
|
def __init__(self, categorize_file: str):
|
98
108
|
super().__init__(categorize_file)
|
109
|
+
self._is_attenuated = self._find_attenuated()
|
110
|
+
self._is_corrected = self._find_corrected()
|
99
111
|
self.is_ice = self._find_ice()
|
100
112
|
self.would_be_ice = self._find_would_be_ice()
|
101
113
|
self.corrected_ice = self._find_corrected_ice()
|
102
114
|
self.uncorrected_ice = self._find_uncorrected_ice()
|
103
115
|
self.ice_above_rain = self._find_ice_above_rain()
|
104
|
-
self.
|
116
|
+
self.clear_above_rain = self._find_clear_above_rain()
|
117
|
+
|
118
|
+
def _find_clear_above_rain(self) -> np.ndarray:
|
119
|
+
return (
|
120
|
+
utils.transpose(self.is_rain) * ~self.is_ice
|
121
|
+
& self.category_bits.freezing
|
122
|
+
& ~self.category_bits.melting
|
123
|
+
)
|
124
|
+
|
125
|
+
def _find_attenuated(self) -> np.ndarray:
|
126
|
+
return (
|
127
|
+
self.quality_bits.attenuated_liquid
|
128
|
+
| self.quality_bits.attenuated_rain
|
129
|
+
| self.quality_bits.attenuated_melting
|
130
|
+
)
|
131
|
+
|
132
|
+
def _find_corrected(self) -> np.ndarray:
|
133
|
+
return (
|
134
|
+
self.quality_bits.corrected_liquid
|
135
|
+
| self.quality_bits.corrected_rain
|
136
|
+
| self.quality_bits.corrected_melting
|
137
|
+
)
|
105
138
|
|
106
139
|
def _find_ice(self) -> np.ndarray:
|
107
140
|
return (
|
108
|
-
self.category_bits
|
109
|
-
& self.category_bits
|
110
|
-
& ~self.category_bits
|
111
|
-
& ~self.category_bits
|
141
|
+
self.category_bits.falling
|
142
|
+
& self.category_bits.freezing
|
143
|
+
& ~self.category_bits.melting
|
144
|
+
& ~self.category_bits.insect
|
112
145
|
)
|
113
146
|
|
114
147
|
def _find_would_be_ice(self) -> np.ndarray:
|
115
148
|
warm_falling = (
|
116
|
-
self.category_bits
|
117
|
-
& ~self.category_bits
|
118
|
-
& ~self.category_bits
|
149
|
+
self.category_bits.falling
|
150
|
+
& ~self.category_bits.freezing
|
151
|
+
& ~self.category_bits.insect
|
119
152
|
)
|
120
|
-
return warm_falling | self.category_bits
|
153
|
+
return warm_falling | self.category_bits.melting
|
121
154
|
|
122
155
|
def _find_corrected_ice(self) -> np.ndarray:
|
123
|
-
return
|
124
|
-
self.is_ice
|
125
|
-
& self.quality_bits["attenuated"]
|
126
|
-
& self.quality_bits["corrected"]
|
127
|
-
)
|
156
|
+
return self.is_ice & self._is_attenuated & self._is_corrected
|
128
157
|
|
129
158
|
def _find_uncorrected_ice(self) -> np.ndarray:
|
159
|
+
uncorrected_melting = (
|
160
|
+
self.quality_bits.attenuated_melting & ~self.quality_bits.corrected_melting
|
161
|
+
)
|
162
|
+
uncorrected_rain = (
|
163
|
+
self.quality_bits.attenuated_rain & ~self.quality_bits.corrected_rain
|
164
|
+
)
|
165
|
+
uncorrected_liquid = (
|
166
|
+
self.quality_bits.attenuated_liquid & ~self.quality_bits.corrected_liquid
|
167
|
+
)
|
130
168
|
return (
|
131
169
|
self.is_ice
|
132
|
-
& self.
|
133
|
-
&
|
170
|
+
& self._is_attenuated
|
171
|
+
& (uncorrected_melting | uncorrected_rain | uncorrected_liquid)
|
134
172
|
)
|
135
173
|
|
136
174
|
def _find_ice_above_rain(self) -> np.ndarray:
|
137
175
|
is_rain = utils.transpose(self.is_rain)
|
138
176
|
return (self.is_ice * is_rain) == 1
|
139
177
|
|
140
|
-
def _find_cold_above_rain(self) -> np.ndarray:
|
141
|
-
is_cold = self.category_bits["cold"]
|
142
|
-
is_rain = utils.transpose(self.is_rain)
|
143
|
-
is_cold_rain = (is_cold * is_rain) == 1
|
144
|
-
return is_cold_rain & ~self.category_bits["melting"]
|
145
|
-
|
146
178
|
|
147
179
|
class IceSource(DataSource):
|
148
180
|
"""Base class for different ice products."""
|
@@ -150,24 +182,20 @@ class IceSource(DataSource):
|
|
150
182
|
def __init__(self, categorize_file: str, product: str):
|
151
183
|
super().__init__(categorize_file)
|
152
184
|
self.wl_band = utils.get_wl_band(float(self.getvar("radar_frequency")))
|
153
|
-
self.temperature =
|
185
|
+
self.temperature = _get_temperature(categorize_file)
|
154
186
|
self.product = product
|
155
187
|
self.coefficients = self._get_coefficients()
|
156
188
|
|
157
|
-
def
|
189
|
+
def append_icy_data(
|
158
190
|
self,
|
159
191
|
ice_classification: IceClassification,
|
160
192
|
) -> None:
|
161
193
|
"""Adds the main variable (including ice above rain)."""
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
"""Adds the main variable (excluding rain)."""
|
168
|
-
data = ma.copy(self.data[f"{self.product}_inc_rain"][:])
|
169
|
-
data[ice_classification.ice_above_rain] = ma.masked
|
170
|
-
self.append_data(data, self.product)
|
194
|
+
data = self._convert_z()
|
195
|
+
data[~ice_classification.is_ice | ice_classification.uncorrected_ice] = (
|
196
|
+
ma.masked
|
197
|
+
)
|
198
|
+
self.append_data(data, f"{self.product}")
|
171
199
|
|
172
200
|
def append_status(self, ice_classification: IceClassification) -> None:
|
173
201
|
"""Adds the status of retrieval."""
|
@@ -176,10 +204,9 @@ class IceSource(DataSource):
|
|
176
204
|
is_data = ~data.mask
|
177
205
|
retrieval_status[is_data] = 1
|
178
206
|
retrieval_status[is_data & ice_classification.corrected_ice] = 3
|
179
|
-
retrieval_status[is_data & ice_classification.uncorrected_ice] = 2
|
180
207
|
retrieval_status[~is_data & ice_classification.is_ice] = 4
|
181
|
-
retrieval_status[ice_classification.
|
182
|
-
retrieval_status[ice_classification.
|
208
|
+
retrieval_status[ice_classification.uncorrected_ice] = 2
|
209
|
+
retrieval_status[ice_classification.clear_above_rain] = 6
|
183
210
|
retrieval_status[ice_classification.would_be_ice & (retrieval_status == 0)] = 7
|
184
211
|
self.append_data(retrieval_status, f"{self.product}_retrieval_status")
|
185
212
|
|
@@ -232,16 +259,16 @@ class IceSource(DataSource):
|
|
232
259
|
|
233
260
|
|
234
261
|
def get_is_rain(filename: str) -> np.ndarray:
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
262
|
+
# TODO: Check that this is correct
|
263
|
+
with netCDF4.Dataset(filename) as nc:
|
264
|
+
for name in ["rain_detected", "rainfall_rate", "rain_rate"]:
|
265
|
+
if name in nc.variables:
|
266
|
+
data = nc.variables[name][:]
|
267
|
+
data = data != 0
|
268
|
+
data[data.mask] = True
|
269
|
+
return np.array(data)
|
270
|
+
msg = "No rain data found."
|
271
|
+
raise ValueError(msg)
|
245
272
|
|
246
273
|
|
247
274
|
def read_nc_field(nc_file: str, name: str) -> ma.MaskedArray:
|
@@ -249,21 +276,6 @@ def read_nc_field(nc_file: str, name: str) -> ma.MaskedArray:
|
|
249
276
|
return nc.variables[name][:]
|
250
277
|
|
251
278
|
|
252
|
-
def read_nc_fields(nc_file: str, names: list[str]) -> list[ma.MaskedArray]:
|
253
|
-
"""Reads selected variables from a netCDF file.
|
254
|
-
|
255
|
-
Args:
|
256
|
-
nc_file: netCDF file name.
|
257
|
-
names: Variables to be read, e.g. ['ldr', 'lwp'].
|
258
|
-
|
259
|
-
Returns:
|
260
|
-
List of numpy arrays.
|
261
|
-
|
262
|
-
"""
|
263
|
-
with netCDF4.Dataset(nc_file) as nc:
|
264
|
-
return [nc.variables[name][:] for name in names]
|
265
|
-
|
266
|
-
|
267
279
|
def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]:
|
268
280
|
"""Interpolates 2D model field into dense Cloudnet grid.
|
269
281
|
|
@@ -278,7 +290,7 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
|
|
278
290
|
"""
|
279
291
|
|
280
292
|
def _interp_field(var_name: str) -> np.ndarray:
|
281
|
-
values =
|
293
|
+
values = _read_nc_fields(
|
282
294
|
cat_file,
|
283
295
|
["model_time", "model_height", var_name, "time", "height"],
|
284
296
|
)
|
@@ -288,22 +300,12 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
|
|
288
300
|
return {name: _interp_field(name) for name in names}
|
289
301
|
|
290
302
|
|
291
|
-
def
|
292
|
-
"""Returns interpolated temperatures in Celsius."""
|
293
|
-
atmosphere = interpolate_model(categorize_file, "temperature")
|
294
|
-
return atmos_utils.k2c(atmosphere["temperature"])
|
295
|
-
|
296
|
-
|
297
|
-
def get_mwrpy_coeffs(nc_file: str) -> str:
|
303
|
+
def _read_nc_fields(nc_file: str, names: list[str]) -> list[ma.MaskedArray]:
|
298
304
|
with netCDF4.Dataset(nc_file) as nc:
|
299
|
-
return nc.
|
305
|
+
return [nc.variables[name][:] for name in names]
|
300
306
|
|
301
307
|
|
302
|
-
def
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
with open(full_path, "wb") as f:
|
307
|
-
f.write(requests.get(link, timeout=10).content)
|
308
|
-
coeffs.append(full_path)
|
309
|
-
return coeffs
|
308
|
+
def _get_temperature(categorize_file: str) -> np.ndarray:
|
309
|
+
"""Returns interpolated temperatures in Celsius."""
|
310
|
+
atmosphere = interpolate_model(categorize_file, "temperature")
|
311
|
+
return atmos_utils.k2c(atmosphere["temperature"])
|
cloudnetpy/utils.py
CHANGED
@@ -1013,3 +1013,7 @@ def status_field_definition(definitions: dict[T, str]) -> str:
|
|
1013
1013
|
|
1014
1014
|
def bit_field_definition(definitions: dict[T, str]) -> str:
|
1015
1015
|
return _format_definition("Bit", definitions)
|
1016
|
+
|
1017
|
+
|
1018
|
+
def path_lengths_from_ground(height: np.ndarray) -> np.ndarray:
|
1019
|
+
return np.diff(height, prepend=0)
|
cloudnetpy/version.py
CHANGED