cloudnetpy 1.80.7__py3-none-any.whl → 1.81.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 +1 -1
- cloudnetpy/categorize/atmos_utils.py +31 -27
- cloudnetpy/categorize/attenuations/__init__.py +4 -4
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +7 -5
- cloudnetpy/categorize/attenuations/melting_attenuation.py +3 -3
- cloudnetpy/categorize/attenuations/rain_attenuation.py +4 -4
- cloudnetpy/categorize/categorize.py +25 -11
- cloudnetpy/categorize/classify.py +9 -8
- cloudnetpy/categorize/containers.py +13 -10
- cloudnetpy/categorize/disdrometer.py +5 -3
- cloudnetpy/categorize/droplet.py +12 -9
- cloudnetpy/categorize/falling.py +9 -8
- cloudnetpy/categorize/freezing.py +10 -7
- cloudnetpy/categorize/insects.py +18 -17
- cloudnetpy/categorize/lidar.py +7 -3
- cloudnetpy/categorize/melting.py +16 -15
- cloudnetpy/categorize/model.py +17 -10
- cloudnetpy/categorize/mwr.py +5 -3
- cloudnetpy/categorize/radar.py +15 -13
- cloudnetpy/cli.py +10 -8
- cloudnetpy/cloudnetarray.py +8 -7
- cloudnetpy/concat_lib.py +29 -20
- cloudnetpy/datasource.py +26 -21
- cloudnetpy/exceptions.py +12 -10
- cloudnetpy/instruments/basta.py +19 -9
- cloudnetpy/instruments/bowtie.py +18 -11
- cloudnetpy/instruments/ceilo.py +22 -10
- cloudnetpy/instruments/ceilometer.py +33 -34
- cloudnetpy/instruments/cl61d.py +5 -3
- cloudnetpy/instruments/cloudnet_instrument.py +7 -7
- cloudnetpy/instruments/copernicus.py +16 -7
- cloudnetpy/instruments/disdrometer/common.py +5 -4
- cloudnetpy/instruments/disdrometer/parsivel.py +14 -9
- cloudnetpy/instruments/disdrometer/thies.py +11 -7
- cloudnetpy/instruments/fd12p.py +7 -6
- cloudnetpy/instruments/galileo.py +16 -7
- cloudnetpy/instruments/hatpro.py +33 -24
- cloudnetpy/instruments/lufft.py +6 -4
- cloudnetpy/instruments/mira.py +33 -19
- cloudnetpy/instruments/mrr.py +12 -12
- cloudnetpy/instruments/nc_lidar.py +1 -1
- cloudnetpy/instruments/nc_radar.py +8 -8
- cloudnetpy/instruments/pollyxt.py +19 -12
- cloudnetpy/instruments/radiometrics.py +17 -10
- cloudnetpy/instruments/rain_e_h3.py +9 -5
- cloudnetpy/instruments/rpg.py +32 -21
- cloudnetpy/instruments/rpg_reader.py +15 -12
- cloudnetpy/instruments/vaisala.py +32 -24
- cloudnetpy/instruments/weather_station.py +22 -19
- cloudnetpy/model_evaluation/file_handler.py +27 -29
- cloudnetpy/model_evaluation/plotting/plot_tools.py +7 -5
- cloudnetpy/model_evaluation/plotting/plotting.py +41 -32
- cloudnetpy/model_evaluation/products/advance_methods.py +38 -34
- cloudnetpy/model_evaluation/products/grid_methods.py +10 -9
- cloudnetpy/model_evaluation/products/model_products.py +15 -9
- cloudnetpy/model_evaluation/products/observation_products.py +12 -10
- cloudnetpy/model_evaluation/products/product_resampling.py +11 -7
- cloudnetpy/model_evaluation/products/tools.py +18 -14
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +6 -5
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +18 -25
- cloudnetpy/model_evaluation/utils.py +3 -3
- cloudnetpy/output.py +15 -32
- cloudnetpy/plotting/plotting.py +23 -13
- cloudnetpy/products/classification.py +15 -9
- cloudnetpy/products/der.py +24 -19
- cloudnetpy/products/drizzle.py +21 -13
- cloudnetpy/products/drizzle_error.py +8 -7
- cloudnetpy/products/drizzle_tools.py +27 -23
- cloudnetpy/products/epsilon.py +6 -5
- cloudnetpy/products/ier.py +11 -5
- cloudnetpy/products/iwc.py +18 -9
- cloudnetpy/products/lwc.py +41 -31
- cloudnetpy/products/mwr_tools.py +30 -19
- cloudnetpy/products/product_tools.py +23 -19
- cloudnetpy/utils.py +84 -98
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/METADATA +2 -1
- cloudnetpy-1.81.0.dist-info/RECORD +126 -0
- cloudnetpy-1.80.7.dist-info/RECORD +0 -126
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.80.7.dist-info → cloudnetpy-1.81.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,10 @@
|
|
1
1
|
"""Module for creating classification file."""
|
2
2
|
|
3
|
+
from os import PathLike
|
4
|
+
from uuid import UUID
|
5
|
+
|
3
6
|
import numpy as np
|
7
|
+
import numpy.typing as npt
|
4
8
|
from numpy import ma
|
5
9
|
|
6
10
|
from cloudnetpy import output, utils
|
@@ -11,10 +15,10 @@ from cloudnetpy.products.product_tools import CategorizeBits
|
|
11
15
|
|
12
16
|
|
13
17
|
def generate_classification(
|
14
|
-
categorize_file: str,
|
15
|
-
output_file: str,
|
16
|
-
uuid: str | None = None,
|
17
|
-
) ->
|
18
|
+
categorize_file: str | PathLike,
|
19
|
+
output_file: str | PathLike,
|
20
|
+
uuid: str | UUID | None = None,
|
21
|
+
) -> UUID:
|
18
22
|
"""Generates Cloudnet classification product.
|
19
23
|
|
20
24
|
This function reads the initial classification masks from a
|
@@ -35,6 +39,7 @@ def generate_classification(
|
|
35
39
|
>>> generate_classification('categorize.nc', 'classification.nc')
|
36
40
|
|
37
41
|
"""
|
42
|
+
uuid = utils.get_uuid(uuid)
|
38
43
|
with DataSource(categorize_file) as product_container:
|
39
44
|
categorize_bits = CategorizeBits(categorize_file)
|
40
45
|
classification = _get_target_classification(categorize_bits)
|
@@ -58,12 +63,13 @@ def generate_classification(
|
|
58
63
|
file_type = "classification"
|
59
64
|
if "liquid_prob" in product_container.dataset.variables:
|
60
65
|
file_type += "-voodoo"
|
61
|
-
|
66
|
+
output.save_product_file(
|
62
67
|
file_type,
|
63
68
|
product_container,
|
64
69
|
output_file,
|
65
70
|
uuid,
|
66
71
|
)
|
72
|
+
return uuid
|
67
73
|
|
68
74
|
|
69
75
|
def _get_target_classification(
|
@@ -86,7 +92,7 @@ def _get_target_classification(
|
|
86
92
|
return classification
|
87
93
|
|
88
94
|
|
89
|
-
def _get_detection_status(categorize_bits: CategorizeBits) ->
|
95
|
+
def _get_detection_status(categorize_bits: CategorizeBits) -> npt.NDArray:
|
90
96
|
bits = categorize_bits.quality_bits
|
91
97
|
|
92
98
|
is_attenuated = (
|
@@ -113,9 +119,9 @@ def _get_detection_status(categorize_bits: CategorizeBits) -> np.ndarray:
|
|
113
119
|
|
114
120
|
|
115
121
|
def _get_cloud_base_and_top_heights(
|
116
|
-
classification:
|
122
|
+
classification: npt.NDArray,
|
117
123
|
product_container: DataSource,
|
118
|
-
) -> tuple[
|
124
|
+
) -> tuple[npt.NDArray, npt.NDArray]:
|
119
125
|
height = product_container.getvar("height")
|
120
126
|
cloud_mask = _find_cloud_mask(classification)
|
121
127
|
if not cloud_mask.any():
|
@@ -128,7 +134,7 @@ def _get_cloud_base_and_top_heights(
|
|
128
134
|
return lowest_bases, highest_tops
|
129
135
|
|
130
136
|
|
131
|
-
def _find_cloud_mask(classification:
|
137
|
+
def _find_cloud_mask(classification: npt.NDArray) -> npt.NDArray:
|
132
138
|
cloud_mask = np.zeros(classification.shape, dtype=int)
|
133
139
|
for value in [1, 3, 4, 5]:
|
134
140
|
cloud_mask[classification == value] = 1
|
cloudnetpy/products/der.py
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
using the Frisch et al. 2002 method.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from os import PathLike
|
5
6
|
from typing import NamedTuple
|
7
|
+
from uuid import UUID
|
6
8
|
|
7
9
|
import numpy as np
|
10
|
+
import numpy.typing as npt
|
8
11
|
from numpy import ma
|
9
12
|
|
10
13
|
from cloudnetpy import output, utils
|
@@ -29,11 +32,11 @@ class Parameters(NamedTuple):
|
|
29
32
|
|
30
33
|
|
31
34
|
def generate_der(
|
32
|
-
categorize_file: str,
|
33
|
-
output_file: str,
|
34
|
-
uuid: str | None = None,
|
35
|
+
categorize_file: str | PathLike,
|
36
|
+
output_file: str | PathLike,
|
37
|
+
uuid: str | UUID | None = None,
|
35
38
|
parameters: Parameters | None = None,
|
36
|
-
) ->
|
39
|
+
) -> UUID:
|
37
40
|
"""Generates Cloudnet effective radius of liquid water droplets
|
38
41
|
product according to Frisch et al. 2002.
|
39
42
|
|
@@ -69,16 +72,16 @@ def generate_der(
|
|
69
72
|
https://doi.org/10.1175/1520-0426(2002)019%3C0835:TROSCD%3E2.0.CO;2
|
70
73
|
|
71
74
|
"""
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
uuid = utils.get_uuid(uuid)
|
76
|
+
with DerSource(categorize_file, parameters) as der_source:
|
77
|
+
droplet_classification = DropletClassification(categorize_file)
|
78
|
+
der_source.append_der()
|
79
|
+
der_source.append_retrieval_status(droplet_classification)
|
80
|
+
date = der_source.get_date()
|
81
|
+
attributes = output.add_time_attribute(REFF_ATTRIBUTES, date)
|
82
|
+
attributes = _add_der_error_comment(attributes, der_source)
|
83
|
+
output.update_attributes(der_source.data, attributes)
|
84
|
+
output.save_product_file("der", der_source, output_file, uuid)
|
82
85
|
return uuid
|
83
86
|
|
84
87
|
|
@@ -87,19 +90,19 @@ class DropletClassification(ProductClassification):
|
|
87
90
|
Child of ProductClassification().
|
88
91
|
"""
|
89
92
|
|
90
|
-
def __init__(self, categorize_file: str):
|
93
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
91
94
|
super().__init__(categorize_file)
|
92
95
|
self.is_mixed = self._find_mixed()
|
93
96
|
self.is_droplet = self._find_droplet()
|
94
97
|
self.is_ice = self._find_ice()
|
95
98
|
|
96
|
-
def _find_droplet(self) ->
|
99
|
+
def _find_droplet(self) -> npt.NDArray:
|
97
100
|
return self.category_bits.droplet
|
98
101
|
|
99
|
-
def _find_mixed(self) ->
|
102
|
+
def _find_mixed(self) -> npt.NDArray:
|
100
103
|
return self.category_bits.falling & self.category_bits.droplet
|
101
104
|
|
102
|
-
def _find_ice(self) ->
|
105
|
+
def _find_ice(self) -> npt.NDArray:
|
103
106
|
return (
|
104
107
|
self.category_bits.falling
|
105
108
|
& self.category_bits.freezing
|
@@ -112,7 +115,9 @@ class DropletClassification(ProductClassification):
|
|
112
115
|
class DerSource(DataSource):
|
113
116
|
"""Data container for effective radius calculations."""
|
114
117
|
|
115
|
-
def __init__(
|
118
|
+
def __init__(
|
119
|
+
self, categorize_file: str | PathLike, parameters: Parameters | None = None
|
120
|
+
) -> None:
|
116
121
|
super().__init__(categorize_file)
|
117
122
|
if "lwp" not in self.dataset.variables:
|
118
123
|
msg = "Liquid water path missing from the categorize file."
|
cloudnetpy/products/drizzle.py
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
"""Module for creating Cloudnet drizzle product."""
|
2
2
|
|
3
|
+
from os import PathLike
|
4
|
+
from uuid import UUID
|
5
|
+
|
3
6
|
import numpy as np
|
7
|
+
import numpy.typing as npt
|
4
8
|
from numpy import ma
|
5
9
|
from scipy.special import gamma
|
6
10
|
|
@@ -16,10 +20,10 @@ from cloudnetpy.products.drizzle_tools import (
|
|
16
20
|
|
17
21
|
|
18
22
|
def generate_drizzle(
|
19
|
-
categorize_file: str,
|
20
|
-
output_file: str,
|
21
|
-
uuid: str | None = None,
|
22
|
-
) ->
|
23
|
+
categorize_file: str | PathLike,
|
24
|
+
output_file: str | PathLike,
|
25
|
+
uuid: str | UUID | None = None,
|
26
|
+
) -> UUID:
|
23
27
|
"""Generates Cloudnet drizzle product.
|
24
28
|
|
25
29
|
This function calculates different drizzle properties from
|
@@ -43,6 +47,7 @@ def generate_drizzle(
|
|
43
47
|
J. Appl. Meteor., 44, 14–27, https://doi.org/10.1175/JAM-2181.1
|
44
48
|
|
45
49
|
"""
|
50
|
+
uuid = utils.get_uuid(uuid)
|
46
51
|
with DrizzleSource(categorize_file) as drizzle_source:
|
47
52
|
drizzle_class = DrizzleClassification(categorize_file)
|
48
53
|
spectral_width = SpectralWidth(categorize_file)
|
@@ -61,7 +66,8 @@ def generate_drizzle(
|
|
61
66
|
date = drizzle_source.get_date()
|
62
67
|
attributes = output.add_time_attribute(DRIZZLE_ATTRIBUTES, date)
|
63
68
|
output.update_attributes(drizzle_source.data, attributes)
|
64
|
-
|
69
|
+
output.save_product_file("drizzle", drizzle_source, output_file, uuid)
|
70
|
+
return uuid
|
65
71
|
|
66
72
|
|
67
73
|
class DrizzleProducts:
|
@@ -77,7 +83,9 @@ class DrizzleProducts:
|
|
77
83
|
|
78
84
|
"""
|
79
85
|
|
80
|
-
def __init__(
|
86
|
+
def __init__(
|
87
|
+
self, drizzle_source: DrizzleSource, drizzle_solver: DrizzleSolver
|
88
|
+
) -> None:
|
81
89
|
self._data = drizzle_source
|
82
90
|
self._params = drizzle_solver.params
|
83
91
|
self._ind_drizzle, self._ind_lut = self._find_indices()
|
@@ -106,13 +114,13 @@ class DrizzleProducts:
|
|
106
114
|
"v_air": v_air,
|
107
115
|
}
|
108
116
|
|
109
|
-
def _calc_density(self) ->
|
117
|
+
def _calc_density(self) -> npt.NDArray:
|
110
118
|
"""Calculates drizzle number density (m-3)."""
|
111
119
|
a = self._data.z * 3.67**6
|
112
120
|
b = self._params["Do"] ** 6
|
113
121
|
return np.divide(a, b, out=np.zeros_like(a), where=b != 0)
|
114
122
|
|
115
|
-
def _calc_lwc(self) ->
|
123
|
+
def _calc_lwc(self) -> npt.NDArray:
|
116
124
|
"""Calculates drizzle liquid water content (kg m-3)."""
|
117
125
|
rho_water = 1000
|
118
126
|
dia, mu, s = (self._params.get(key) for key in ("Do", "mu", "S"))
|
@@ -122,7 +130,7 @@ class DrizzleProducts:
|
|
122
130
|
gamma_ratio = gamma(4 + mu) / gamma(3 + mu) / (3.67 + mu)
|
123
131
|
return rho_water / 3 * self._data.beta * s * dia * gamma_ratio
|
124
132
|
|
125
|
-
def _calc_lwf(self, lwc_in) ->
|
133
|
+
def _calc_lwf(self, lwc_in: npt.NDArray) -> npt.NDArray:
|
126
134
|
"""Calculates drizzle liquid water flux."""
|
127
135
|
flux = ma.copy(lwc_in)
|
128
136
|
flux[self._ind_drizzle] *= (
|
@@ -131,13 +139,13 @@ class DrizzleProducts:
|
|
131
139
|
)
|
132
140
|
return flux
|
133
141
|
|
134
|
-
def _calc_fall_velocity(self) ->
|
142
|
+
def _calc_fall_velocity(self) -> npt.NDArray:
|
135
143
|
"""Calculates drizzle droplet fall velocity (m s-1)."""
|
136
144
|
velocity = np.zeros_like(self._params["Do"])
|
137
145
|
velocity[self._ind_drizzle] = -self._data.mie["v"][self._ind_lut]
|
138
146
|
return velocity
|
139
147
|
|
140
|
-
def _calc_v_air(self, droplet_velocity) ->
|
148
|
+
def _calc_v_air(self, droplet_velocity: npt.NDArray) -> npt.NDArray:
|
141
149
|
"""Calculates vertical air velocity."""
|
142
150
|
velocity = -np.copy(droplet_velocity)
|
143
151
|
velocity[self._ind_drizzle] += self._data.v[self._ind_drizzle]
|
@@ -156,9 +164,9 @@ class RetrievalStatus:
|
|
156
164
|
status information.
|
157
165
|
"""
|
158
166
|
|
159
|
-
def __init__(self, drizzle_class: DrizzleClassification):
|
167
|
+
def __init__(self, drizzle_class: DrizzleClassification) -> None:
|
160
168
|
self.drizzle_class = drizzle_class
|
161
|
-
self.retrieval_status:
|
169
|
+
self.retrieval_status: npt.NDArray = np.array([])
|
162
170
|
self._get_retrieval_status()
|
163
171
|
|
164
172
|
def _get_retrieval_status(self) -> None:
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import numpy as np
|
2
|
+
import numpy.typing as npt
|
2
3
|
from numpy import ma
|
3
4
|
|
4
5
|
from cloudnetpy import utils
|
@@ -33,7 +34,7 @@ def get_drizzle_error(
|
|
33
34
|
return _calc_errors(drizzle_indices, error_input, bias_input)
|
34
35
|
|
35
36
|
|
36
|
-
def _get_drizzle_indices(diameter:
|
37
|
+
def _get_drizzle_indices(diameter: npt.NDArray) -> dict:
|
37
38
|
return {
|
38
39
|
"drizzle": diameter > 0,
|
39
40
|
"small": np.logical_and(diameter <= 1e-4, diameter > 1e-5),
|
@@ -154,12 +155,12 @@ def _calc_error(
|
|
154
155
|
|
155
156
|
|
156
157
|
def _stack_errors(
|
157
|
-
error_in:
|
158
|
+
error_in: npt.NDArray,
|
158
159
|
drizzle_indices: dict,
|
159
|
-
error_small=None,
|
160
|
-
error_tiny=None,
|
160
|
+
error_small: npt.NDArray | None = None,
|
161
|
+
error_tiny: npt.NDArray | None = None,
|
161
162
|
) -> ma.MaskedArray:
|
162
|
-
def _add_error_component(source:
|
163
|
+
def _add_error_component(source: npt.NDArray, ind: tuple) -> None:
|
163
164
|
error[ind] = source[ind]
|
164
165
|
|
165
166
|
error = ma.zeros(error_in.shape)
|
@@ -174,14 +175,14 @@ def _stack_errors(
|
|
174
175
|
COR = 10 / np.log(10)
|
175
176
|
|
176
177
|
|
177
|
-
def db2lin(x_in:
|
178
|
+
def db2lin(x_in: npt.NDArray) -> ma.MaskedArray:
|
178
179
|
x = ma.copy(x_in)
|
179
180
|
threshold = 100
|
180
181
|
x[x > threshold] = threshold
|
181
182
|
return ma.exp(x / COR) - 1
|
182
183
|
|
183
184
|
|
184
|
-
def lin2db(x_in) -> ma.MaskedArray:
|
185
|
+
def lin2db(x_in: npt.NDArray) -> ma.MaskedArray:
|
185
186
|
x = ma.copy(x_in)
|
186
187
|
threshold = -0.9
|
187
188
|
x[x < threshold] = threshold
|
@@ -1,9 +1,11 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
from bisect import bisect_left
|
4
|
+
from os import PathLike
|
4
5
|
|
5
6
|
import netCDF4
|
6
7
|
import numpy as np
|
8
|
+
import numpy.typing as npt
|
7
9
|
from numpy import ma
|
8
10
|
from scipy.special import gamma
|
9
11
|
|
@@ -27,7 +29,7 @@ class DrizzleSource(DataSource):
|
|
27
29
|
|
28
30
|
"""
|
29
31
|
|
30
|
-
def __init__(self, categorize_file: str):
|
32
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
31
33
|
super().__init__(categorize_file)
|
32
34
|
self.mie = self._read_mie_lut()
|
33
35
|
self.height_vector = self.getvar("height")
|
@@ -35,7 +37,7 @@ class DrizzleSource(DataSource):
|
|
35
37
|
self.beta = self.getvar("beta")
|
36
38
|
self.v = self.getvar("v")
|
37
39
|
|
38
|
-
def _convert_z_units(self) ->
|
40
|
+
def _convert_z_units(self) -> npt.NDArray:
|
39
41
|
"""Converts reflectivity factor to SI units."""
|
40
42
|
z = self.getvar("Z") - 180
|
41
43
|
z[z > 0.0] = 0.0
|
@@ -96,7 +98,7 @@ class DrizzleClassification(ProductClassification):
|
|
96
98
|
|
97
99
|
"""
|
98
100
|
|
99
|
-
def __init__(self, categorize_file: str):
|
101
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
100
102
|
super().__init__(categorize_file)
|
101
103
|
self.is_v_sigma = self._find_v_sigma(categorize_file)
|
102
104
|
self.warm_liquid = self._find_warm_liquid()
|
@@ -105,14 +107,14 @@ class DrizzleClassification(ProductClassification):
|
|
105
107
|
self.cold_rain = self._find_cold_rain()
|
106
108
|
|
107
109
|
@staticmethod
|
108
|
-
def _find_v_sigma(cat_file: str) ->
|
110
|
+
def _find_v_sigma(cat_file: str | PathLike) -> npt.NDArray:
|
109
111
|
v_sigma = product_tools.read_nc_field(cat_file, "v_sigma")
|
110
112
|
return np.isfinite(v_sigma)
|
111
113
|
|
112
|
-
def _find_warm_liquid(self) ->
|
114
|
+
def _find_warm_liquid(self) -> npt.NDArray:
|
113
115
|
return self.category_bits.droplet & ~self.category_bits.freezing
|
114
116
|
|
115
|
-
def _find_drizzle(self) ->
|
117
|
+
def _find_drizzle(self) -> npt.NDArray:
|
116
118
|
return (
|
117
119
|
~utils.transpose(self.is_rain)
|
118
120
|
& self.category_bits.falling
|
@@ -129,7 +131,7 @@ class DrizzleClassification(ProductClassification):
|
|
129
131
|
& self.is_v_sigma
|
130
132
|
)
|
131
133
|
|
132
|
-
def _find_would_be_drizzle(self) ->
|
134
|
+
def _find_would_be_drizzle(self) -> npt.NDArray:
|
133
135
|
return (
|
134
136
|
~utils.transpose(self.is_rain)
|
135
137
|
& self.warm_liquid
|
@@ -141,7 +143,7 @@ class DrizzleClassification(ProductClassification):
|
|
141
143
|
& ~self.quality_bits.molecular
|
142
144
|
)
|
143
145
|
|
144
|
-
def _find_cold_rain(self) ->
|
146
|
+
def _find_cold_rain(self) -> npt.NDArray:
|
145
147
|
return np.any(self.category_bits.melting, axis=1)
|
146
148
|
|
147
149
|
|
@@ -161,11 +163,11 @@ class SpectralWidth:
|
|
161
163
|
|
162
164
|
"""
|
163
165
|
|
164
|
-
def __init__(self, categorize_file: str):
|
166
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
165
167
|
self.cat_file = categorize_file
|
166
168
|
self.width_ht = self._calculate_spectral_width()
|
167
169
|
|
168
|
-
def _calculate_spectral_width(self) ->
|
170
|
+
def _calculate_spectral_width(self) -> npt.NDArray:
|
169
171
|
v_sigma = product_tools.read_nc_field(self.cat_file, "v_sigma")
|
170
172
|
try:
|
171
173
|
width = product_tools.read_nc_field(self.cat_file, "width")
|
@@ -175,19 +177,19 @@ class SpectralWidth:
|
|
175
177
|
sigma_factor = self._calc_v_sigma_factor()
|
176
178
|
return width - sigma_factor * v_sigma
|
177
179
|
|
178
|
-
def _calc_v_sigma_factor(self) ->
|
180
|
+
def _calc_v_sigma_factor(self) -> npt.NDArray:
|
179
181
|
beam_divergence = self._calc_beam_divergence()
|
180
182
|
wind = self._calc_horizontal_wind()
|
181
183
|
actual_wind = (wind + beam_divergence) ** (2 / 3)
|
182
184
|
scaled_wind = (30 * wind + beam_divergence) ** (2 / 3)
|
183
185
|
return actual_wind / (scaled_wind - actual_wind)
|
184
186
|
|
185
|
-
def _calc_beam_divergence(self) ->
|
187
|
+
def _calc_beam_divergence(self) -> npt.NDArray:
|
186
188
|
beam_width = 0.5
|
187
189
|
height = product_tools.read_nc_field(self.cat_file, "height")
|
188
190
|
return height * np.deg2rad(beam_width)
|
189
191
|
|
190
|
-
def _calc_horizontal_wind(self) ->
|
192
|
+
def _calc_horizontal_wind(self) -> npt.NDArray:
|
191
193
|
"""Calculates magnitude of horizontal wind.
|
192
194
|
|
193
195
|
Returns:
|
@@ -219,7 +221,7 @@ class DrizzleSolver:
|
|
219
221
|
drizzle_source: DrizzleSource,
|
220
222
|
drizzle_class: DrizzleClassification,
|
221
223
|
spectral_width: SpectralWidth,
|
222
|
-
):
|
224
|
+
) -> None:
|
223
225
|
self._data = drizzle_source
|
224
226
|
self._drizzle_class = drizzle_class
|
225
227
|
self._width_ht = spectral_width.width_ht
|
@@ -228,7 +230,7 @@ class DrizzleSolver:
|
|
228
230
|
self._beta_z_ratio = self._calc_beta_z_ratio()
|
229
231
|
self._solve_drizzle(self._dia_init)
|
230
232
|
|
231
|
-
def _init_variables(self) -> tuple[dict,
|
233
|
+
def _init_variables(self) -> tuple[dict, npt.NDArray]:
|
232
234
|
shape = self._data.z.shape
|
233
235
|
res = {
|
234
236
|
"Do": np.zeros(shape),
|
@@ -238,10 +240,12 @@ class DrizzleSolver:
|
|
238
240
|
}
|
239
241
|
return res, np.zeros(shape)
|
240
242
|
|
241
|
-
def _calc_beta_z_ratio(self) ->
|
243
|
+
def _calc_beta_z_ratio(self) -> npt.NDArray:
|
242
244
|
return 2 / np.pi * self._data.beta / self._data.z
|
243
245
|
|
244
|
-
def _find_lut_indices(
|
246
|
+
def _find_lut_indices(
|
247
|
+
self, ind: tuple[int, ...], dia_init: npt.NDArray, n_dia: int, n_widths: int
|
248
|
+
) -> tuple[int, int]:
|
245
249
|
ind_dia = bisect_left(self._data.mie["Do"], dia_init[ind], hi=n_dia - 1)
|
246
250
|
ind_width = bisect_left(
|
247
251
|
self._width_lut[:, ind_dia],
|
@@ -250,7 +254,7 @@ class DrizzleSolver:
|
|
250
254
|
)
|
251
255
|
return ind_width, ind_dia
|
252
256
|
|
253
|
-
def _solve_drizzle(self, dia_init:
|
257
|
+
def _solve_drizzle(self, dia_init: npt.NDArray) -> None:
|
254
258
|
drizzle_ind = np.where(self._drizzle_class.drizzle == 1)
|
255
259
|
dia_init[drizzle_ind] = self._calc_dia(self._beta_z_ratio[drizzle_ind], k=18.8)
|
256
260
|
n_widths, n_dia = self._width_lut.shape[0], len(self._data.mie["Do"])
|
@@ -277,7 +281,7 @@ class DrizzleSolver:
|
|
277
281
|
def _update_result_tables(
|
278
282
|
self,
|
279
283
|
ind: tuple,
|
280
|
-
dia:
|
284
|
+
dia: npt.NDArray | float,
|
281
285
|
lut_ind: tuple,
|
282
286
|
) -> None:
|
283
287
|
self.params["Do"][ind] = dia
|
@@ -286,11 +290,11 @@ class DrizzleSolver:
|
|
286
290
|
|
287
291
|
@staticmethod
|
288
292
|
def _calc_dia(
|
289
|
-
beta_z_ratio:
|
293
|
+
beta_z_ratio: npt.NDArray | float,
|
290
294
|
mu: float = 0.0,
|
291
295
|
ray: float = 1.0,
|
292
296
|
k: float = 1.0,
|
293
|
-
) ->
|
297
|
+
) -> npt.NDArray | float:
|
294
298
|
"""Drizzle diameter calculation.
|
295
299
|
|
296
300
|
Args:
|
@@ -312,8 +316,8 @@ class DrizzleSolver:
|
|
312
316
|
@staticmethod
|
313
317
|
def _is_converged(
|
314
318
|
ind: tuple,
|
315
|
-
dia:
|
316
|
-
dia_init:
|
319
|
+
dia: npt.NDArray | float,
|
320
|
+
dia_init: npt.NDArray,
|
317
321
|
) -> bool:
|
318
322
|
threshold = 1e-3
|
319
323
|
return abs((dia - dia_init[ind]) / dia_init[ind]) < threshold
|
cloudnetpy/products/epsilon.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from os import PathLike
|
2
2
|
from pathlib import Path
|
3
|
+
from uuid import UUID
|
3
4
|
|
4
5
|
import doppy
|
5
6
|
import doppy.netcdf
|
@@ -20,10 +21,10 @@ def generate_epsilon_from_lidar(
|
|
20
21
|
doppler_lidar_file: str | PathLike,
|
21
22
|
doppler_lidar_wind_file: str | PathLike,
|
22
23
|
output_file: str | PathLike,
|
23
|
-
uuid: str | None,
|
24
|
-
):
|
24
|
+
uuid: str | UUID | None = None,
|
25
|
+
) -> UUID:
|
25
26
|
sliding_window_in_seconds = 3 * 60
|
26
|
-
uuid =
|
27
|
+
uuid = get_uuid(uuid)
|
27
28
|
opts = _get_options(doppler_lidar_file)
|
28
29
|
opts.period = sliding_window_in_seconds
|
29
30
|
vert = _vertical_wind_from_doppler_lidar_file(doppler_lidar_file)
|
@@ -77,7 +78,7 @@ def generate_epsilon_from_lidar(
|
|
77
78
|
dtype="f4",
|
78
79
|
)
|
79
80
|
|
80
|
-
nc.add_attribute("file_uuid", uuid)
|
81
|
+
nc.add_attribute("file_uuid", str(uuid))
|
81
82
|
nc.add_attribute("cloudnet_file_type", "epsilon-lidar")
|
82
83
|
nc.add_attribute("doppy_version", doppy.__version__)
|
83
84
|
nc.add_attribute("cloudnetpy_version", cloudnetpy.__version__)
|
@@ -163,7 +164,7 @@ def _get_options(doppler_lidar_file: str | PathLike) -> Options:
|
|
163
164
|
raise ValueError(msg)
|
164
165
|
|
165
166
|
|
166
|
-
def _infer_pulse_repetition_frequency(range_: npt.NDArray[np.float64]):
|
167
|
+
def _infer_pulse_repetition_frequency(range_: npt.NDArray[np.float64]) -> float:
|
167
168
|
c = scipy.constants.c
|
168
169
|
dist = range_.max() - range_.min()
|
169
170
|
round_trip_time = 2 * dist / c
|
cloudnetpy/products/ier.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
"""Module for creating Cloudnet ice effective radius file using Z-T method."""
|
2
2
|
|
3
|
+
from os import PathLike
|
4
|
+
from uuid import UUID
|
5
|
+
|
3
6
|
import numpy as np
|
4
7
|
from numpy import ma
|
5
8
|
|
@@ -7,13 +10,14 @@ from cloudnetpy import constants, output
|
|
7
10
|
from cloudnetpy.metadata import MetaData
|
8
11
|
from cloudnetpy.products.iwc import DEFINITIONS as IWC_DEFINITION
|
9
12
|
from cloudnetpy.products.product_tools import IceClassification, IceSource
|
13
|
+
from cloudnetpy.utils import get_uuid
|
10
14
|
|
11
15
|
|
12
16
|
def generate_ier(
|
13
|
-
categorize_file: str,
|
14
|
-
output_file: str,
|
15
|
-
uuid: str | None = None,
|
16
|
-
) ->
|
17
|
+
categorize_file: str | PathLike,
|
18
|
+
output_file: str | PathLike,
|
19
|
+
uuid: str | UUID | None = None,
|
20
|
+
) -> UUID:
|
17
21
|
"""Generates Cloudnet ice effective radius product.
|
18
22
|
|
19
23
|
This function calculates ice particle effective radius using the Grieche
|
@@ -55,6 +59,7 @@ def generate_ier(
|
|
55
59
|
from https://doi.org/10.5194/amt-13-5335-2020,
|
56
60
|
|
57
61
|
"""
|
62
|
+
uuid = get_uuid(uuid)
|
58
63
|
product = "ier"
|
59
64
|
with IerSource(categorize_file, product) as ier_source:
|
60
65
|
ice_classification = IceClassification(categorize_file)
|
@@ -66,7 +71,8 @@ def generate_ier(
|
|
66
71
|
attributes = output.add_time_attribute(IER_ATTRIBUTES, date)
|
67
72
|
attributes = _add_ier_comment(attributes, ier_source)
|
68
73
|
output.update_attributes(ier_source.data, attributes)
|
69
|
-
|
74
|
+
output.save_product_file(product, ier_source, output_file, uuid)
|
75
|
+
return uuid
|
70
76
|
|
71
77
|
|
72
78
|
class IerSource(IceSource):
|
cloudnetpy/products/iwc.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
"""Module for creating Cloudnet ice water content file using Z-T method."""
|
2
2
|
|
3
|
-
|
3
|
+
from os import PathLike
|
4
|
+
from uuid import UUID
|
5
|
+
|
6
|
+
import numpy.typing as npt
|
4
7
|
from numpy import ma
|
5
8
|
|
6
9
|
from cloudnetpy import output, utils
|
@@ -10,10 +13,10 @@ from cloudnetpy.products.product_tools import IceClassification, IceSource
|
|
10
13
|
|
11
14
|
|
12
15
|
def generate_iwc(
|
13
|
-
categorize_file: str,
|
14
|
-
output_file: str,
|
15
|
-
uuid: str | None = None,
|
16
|
-
) ->
|
16
|
+
categorize_file: str | PathLike,
|
17
|
+
output_file: str | PathLike,
|
18
|
+
uuid: str | UUID | None = None,
|
19
|
+
) -> UUID:
|
17
20
|
"""Generates Cloudnet ice water content product.
|
18
21
|
|
19
22
|
This function calculates ice water content using the so-called Z-T method.
|
@@ -40,6 +43,7 @@ def generate_iwc(
|
|
40
43
|
J. Appl. Meteor. Climatol., 45, 301–317, https://doi.org/10.1175/JAM2340.1
|
41
44
|
|
42
45
|
"""
|
46
|
+
uuid = utils.get_uuid(uuid)
|
43
47
|
product = "iwc"
|
44
48
|
with IwcSource(categorize_file, product) as iwc_source:
|
45
49
|
ice_classification = IceClassification(categorize_file)
|
@@ -53,7 +57,8 @@ def generate_iwc(
|
|
53
57
|
attributes = _add_iwc_comment(attributes, iwc_source)
|
54
58
|
attributes = _add_iwc_error_comment(attributes, lwp_prior, bias)
|
55
59
|
output.update_attributes(iwc_source.data, attributes)
|
56
|
-
|
60
|
+
output.save_product_file(product, iwc_source, output_file, uuid)
|
61
|
+
return uuid
|
57
62
|
|
58
63
|
|
59
64
|
class IwcSource(IceSource):
|
@@ -69,10 +74,12 @@ class IwcSource(IceSource):
|
|
69
74
|
bias = self.getvar("Z_bias") * self.coefficients.Z * 10
|
70
75
|
self.append_data(bias, f"{self.product}_bias")
|
71
76
|
|
72
|
-
def append_error(
|
77
|
+
def append_error(
|
78
|
+
self, ice_classification: IceClassification
|
79
|
+
) -> tuple[float, float]:
|
73
80
|
"""Estimates error of ice water content."""
|
74
81
|
|
75
|
-
def _calc_random_error() ->
|
82
|
+
def _calc_random_error() -> npt.NDArray:
|
76
83
|
scaled_temperature = self.coefficients.ZT * self.temperature
|
77
84
|
scaled_temperature += self.coefficients.Z
|
78
85
|
return self.getvar("Z_error") * scaled_temperature * 10
|
@@ -98,7 +105,9 @@ class IwcSource(IceSource):
|
|
98
105
|
return lwp_prior, retrieval_uncertainty
|
99
106
|
|
100
107
|
|
101
|
-
def _add_iwc_error_comment(
|
108
|
+
def _add_iwc_error_comment(
|
109
|
+
attributes: dict, lwp_prior: float, uncertainty: float
|
110
|
+
) -> dict:
|
102
111
|
attributes["iwc_error"] = attributes["iwc_error"]._replace(
|
103
112
|
comment="This variable is an estimate of the one-standard-deviation random\n"
|
104
113
|
"error in ice water content due to both the uncertainty of the retrieval\n"
|