cloudnetpy 1.80.8__py3-none-any.whl → 1.81.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.
- 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 +28 -21
- 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 +22 -12
- 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.8.dist-info → cloudnetpy-1.81.1.dist-info}/METADATA +3 -2
- cloudnetpy-1.81.1.dist-info/RECORD +126 -0
- cloudnetpy-1.80.8.dist-info/RECORD +0 -126
- {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
|
|
1
1
|
import logging
|
2
2
|
from datetime import datetime, timezone
|
3
|
+
from os import PathLike
|
3
4
|
|
4
5
|
import numpy as np
|
6
|
+
import numpy.typing as npt
|
5
7
|
from numpy import ma
|
6
8
|
|
7
9
|
from cloudnetpy import utils
|
@@ -24,7 +26,7 @@ class ObservationManager(DataSource):
|
|
24
26
|
should be processed using CloudnetPy for this class to work properly.
|
25
27
|
"""
|
26
28
|
|
27
|
-
def __init__(self, obs: str, obs_file: str):
|
29
|
+
def __init__(self, obs: str, obs_file: str | PathLike) -> None:
|
28
30
|
super().__init__(obs_file)
|
29
31
|
self.obs = obs
|
30
32
|
self._file = obs_file
|
@@ -45,13 +47,13 @@ class ObservationManager(DataSource):
|
|
45
47
|
tzinfo=timezone.utc,
|
46
48
|
)
|
47
49
|
|
48
|
-
def _get_radar_frequency(self) ->
|
50
|
+
def _get_radar_frequency(self) -> npt.NDArray | None:
|
49
51
|
try:
|
50
52
|
return self.getvar("radar_frequency")
|
51
53
|
except (KeyError, RuntimeError):
|
52
54
|
return None
|
53
55
|
|
54
|
-
def _get_z_sensitivity(self) ->
|
56
|
+
def _get_z_sensitivity(self) -> npt.NDArray | None:
|
55
57
|
try:
|
56
58
|
return self.getvar("Z_sensitivity")
|
57
59
|
except (KeyError, RuntimeError):
|
@@ -72,14 +74,14 @@ class ObservationManager(DataSource):
|
|
72
74
|
logging.exception(msg)
|
73
75
|
raise
|
74
76
|
|
75
|
-
def _generate_cf(self) ->
|
77
|
+
def _generate_cf(self) -> npt.NDArray:
|
76
78
|
"""Generates cloud fractions using categorize bits and masking conditions."""
|
77
79
|
categorize_bits = CategorizeBits(self._file)
|
78
80
|
cloud_mask = self._classify_basic_mask(categorize_bits.category_bits)
|
79
81
|
return self._mask_cloud_bits(cloud_mask)
|
80
82
|
|
81
83
|
@staticmethod
|
82
|
-
def _classify_basic_mask(bits: CategoryBits) ->
|
84
|
+
def _classify_basic_mask(bits: CategoryBits) -> npt.NDArray:
|
83
85
|
cloud_mask = bits.droplet + bits.falling * 2
|
84
86
|
cloud_mask[bits.falling & bits.freezing] = (
|
85
87
|
cloud_mask[bits.falling & bits.freezing] + 2
|
@@ -90,7 +92,7 @@ class ObservationManager(DataSource):
|
|
90
92
|
return cloud_mask
|
91
93
|
|
92
94
|
@staticmethod
|
93
|
-
def _mask_cloud_bits(cloud_mask:
|
95
|
+
def _mask_cloud_bits(cloud_mask: npt.NDArray) -> npt.NDArray:
|
94
96
|
"""Creates cloud fraction."""
|
95
97
|
for i in [1, 3, 4, 5]:
|
96
98
|
cloud_mask[cloud_mask == i] = 1
|
@@ -116,7 +118,7 @@ class ObservationManager(DataSource):
|
|
116
118
|
rainrate_threshold = 2
|
117
119
|
return rainrate_threshold
|
118
120
|
|
119
|
-
def _rain_index(self) ->
|
121
|
+
def _rain_index(self) -> npt.NDArray:
|
120
122
|
rainrate = self.getvar("rainrate")
|
121
123
|
rainrate_threshold = self._get_rainrate_threshold()
|
122
124
|
return rainrate > rainrate_threshold
|
@@ -130,13 +132,13 @@ class ObservationManager(DataSource):
|
|
130
132
|
self._get_rain_iwc(iwc_status)
|
131
133
|
self._mask_iwc(iwc, iwc_status)
|
132
134
|
|
133
|
-
def _mask_iwc(self, iwc:
|
135
|
+
def _mask_iwc(self, iwc: npt.NDArray, iwc_status: npt.NDArray) -> None:
|
134
136
|
"""Leaves only reliable data and corrected liquid attenuation."""
|
135
137
|
iwc_mask = ma.copy(iwc)
|
136
138
|
iwc_mask[np.bitwise_and(iwc_status != 1, iwc_status != 2)] = ma.masked
|
137
139
|
self.append_data(iwc_mask, "iwc")
|
138
140
|
|
139
|
-
def _mask_iwc_att(self, iwc:
|
141
|
+
def _mask_iwc_att(self, iwc: npt.NDArray, iwc_status: npt.NDArray) -> None:
|
140
142
|
"""Leaves only where reliable data, corrected liquid attenuation
|
141
143
|
and uncorrected liquid attenuation.
|
142
144
|
"""
|
@@ -144,7 +146,7 @@ class ObservationManager(DataSource):
|
|
144
146
|
iwc_att[iwc_status > 3] = ma.masked
|
145
147
|
self.append_data(iwc_att, "iwc_att")
|
146
148
|
|
147
|
-
def _get_rain_iwc(self, iwc_status:
|
149
|
+
def _get_rain_iwc(self, iwc_status: npt.NDArray) -> None:
|
148
150
|
"""Finds columns where is rain, return boolean of x-axis shape."""
|
149
151
|
iwc_rain = np.zeros(iwc_status.shape, dtype=bool)
|
150
152
|
iwc_rain[iwc_status == 5] = 1
|
@@ -1,4 +1,6 @@
|
|
1
1
|
import logging
|
2
|
+
from os import PathLike
|
3
|
+
from uuid import UUID
|
2
4
|
|
3
5
|
import cloudnetpy.model_evaluation.products.tools as tl
|
4
6
|
from cloudnetpy.model_evaluation.file_handler import (
|
@@ -12,18 +14,19 @@ from cloudnetpy.model_evaluation.products.grid_methods import ProductGrid
|
|
12
14
|
from cloudnetpy.model_evaluation.products.model_products import ModelManager
|
13
15
|
from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
|
14
16
|
from cloudnetpy.model_evaluation.utils import file_exists
|
17
|
+
from cloudnetpy.utils import get_uuid
|
15
18
|
|
16
19
|
|
17
20
|
def process_L3_day_product(
|
18
21
|
model: str,
|
19
22
|
obs: str,
|
20
|
-
model_files: list,
|
21
|
-
product_file: str,
|
22
|
-
output_file: str,
|
23
|
-
uuid: str | None = None,
|
23
|
+
model_files: list[str | PathLike],
|
24
|
+
product_file: str | PathLike,
|
25
|
+
output_file: str | PathLike,
|
26
|
+
uuid: str | UUID | None = None,
|
24
27
|
*,
|
25
28
|
overwrite: bool = False,
|
26
|
-
) ->
|
29
|
+
) -> UUID:
|
27
30
|
"""Main function to generate downsample of observations to match model grid.
|
28
31
|
|
29
32
|
This function will generate a L3 product nc-file. It includes the information of
|
@@ -63,6 +66,7 @@ def process_L3_day_product(
|
|
63
66
|
>>> process_L3_day_product(model, product, [model_file], input_file,
|
64
67
|
output_file)
|
65
68
|
"""
|
69
|
+
uuid = get_uuid(uuid)
|
66
70
|
product_obj = ObservationManager(obs, product_file)
|
67
71
|
tl.check_model_file_list(model, model_files)
|
68
72
|
for m_file in model_files:
|
@@ -82,7 +86,7 @@ def process_L3_day_product(
|
|
82
86
|
update_attributes(model_obj.data, attributes)
|
83
87
|
if not file_exists(output_file) or overwrite:
|
84
88
|
tl.add_date(model_obj, product_obj)
|
85
|
-
|
89
|
+
save_downsampled_file(
|
86
90
|
f"{obs}_{model}",
|
87
91
|
output_file,
|
88
92
|
(model_obj, product_obj),
|
@@ -91,4 +95,4 @@ def process_L3_day_product(
|
|
91
95
|
)
|
92
96
|
else:
|
93
97
|
add_var2ncfile(model_obj, output_file)
|
94
|
-
return
|
98
|
+
return uuid
|
@@ -1,28 +1,32 @@
|
|
1
1
|
import datetime
|
2
2
|
import logging
|
3
|
+
import os.path
|
4
|
+
from collections.abc import Sequence
|
3
5
|
from datetime import timedelta
|
6
|
+
from os import PathLike
|
4
7
|
|
5
8
|
import numpy as np
|
9
|
+
import numpy.typing as npt
|
6
10
|
from numpy import ma
|
7
11
|
|
8
12
|
from cloudnetpy.model_evaluation.products.model_products import ModelManager
|
9
13
|
from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
|
10
14
|
|
11
15
|
|
12
|
-
def check_model_file_list(name: str, models:
|
16
|
+
def check_model_file_list(name: str, models: Sequence[str | PathLike]) -> None:
|
13
17
|
"""Check that files in models are from same model and date."""
|
14
18
|
for m in models:
|
15
|
-
if name not in m:
|
19
|
+
if name not in os.path.basename(m):
|
16
20
|
logging.error("Invalid model file set")
|
17
21
|
msg = f"{m} not from {name}"
|
18
22
|
raise AttributeError(msg)
|
19
23
|
|
20
24
|
|
21
|
-
def time2datetime(time:
|
25
|
+
def time2datetime(time: npt.NDArray, date: datetime.datetime) -> npt.NDArray:
|
22
26
|
return np.asarray([date + timedelta(hours=float(t)) for t in time])
|
23
27
|
|
24
28
|
|
25
|
-
def rebin_edges(arr:
|
29
|
+
def rebin_edges(arr: npt.NDArray) -> npt.NDArray:
|
26
30
|
"""Rebins array bins by half and adds boundaries."""
|
27
31
|
new_arr = [(arr[i] + arr[i + 1]) / 2 for i in range(len(arr) - 1)]
|
28
32
|
new_arr.insert(0, arr[0] - ((arr[0] + arr[1]) / 2))
|
@@ -34,7 +38,7 @@ def calculate_advection_time(
|
|
34
38
|
resolution: int,
|
35
39
|
wind: ma.MaskedArray,
|
36
40
|
sampling: int,
|
37
|
-
) ->
|
41
|
+
) -> npt.NDArray:
|
38
42
|
"""Calculates time which variable takes to go through the time window.
|
39
43
|
|
40
44
|
Notes:
|
@@ -52,10 +56,10 @@ def calculate_advection_time(
|
|
52
56
|
|
53
57
|
def get_1d_indices(
|
54
58
|
window: tuple,
|
55
|
-
data:
|
56
|
-
mask:
|
57
|
-
) ->
|
58
|
-
indices:
|
59
|
+
data: npt.NDArray,
|
60
|
+
mask: npt.NDArray | None = None,
|
61
|
+
) -> npt.NDArray:
|
62
|
+
indices: npt.NDArray = np.array((window[0] <= data) & (data < window[-1]))
|
59
63
|
if mask is not None:
|
60
64
|
indices[mask] = ma.masked
|
61
65
|
return indices
|
@@ -64,16 +68,16 @@ def get_1d_indices(
|
|
64
68
|
def get_adv_indices(
|
65
69
|
model_t: int,
|
66
70
|
adv_t: float,
|
67
|
-
data:
|
68
|
-
mask:
|
69
|
-
) ->
|
71
|
+
data: npt.NDArray,
|
72
|
+
mask: npt.NDArray | None = None,
|
73
|
+
) -> npt.NDArray:
|
70
74
|
adv_indices = ((model_t - adv_t / 2) <= data) & (data < (model_t + adv_t / 2))
|
71
75
|
if mask is not None:
|
72
76
|
adv_indices[mask] = ma.masked
|
73
77
|
return adv_indices
|
74
78
|
|
75
79
|
|
76
|
-
def get_obs_window_size(ind_x:
|
80
|
+
def get_obs_window_size(ind_x: npt.NDArray, ind_y: npt.NDArray) -> tuple | None:
|
77
81
|
"""Returns shape (tuple) of window area, where values are True."""
|
78
82
|
x = np.where(ind_x)[0]
|
79
83
|
y = np.where(ind_y)[0]
|
@@ -87,6 +91,6 @@ def add_date(model_obj: ModelManager, obs_obj: ObservationManager) -> None:
|
|
87
91
|
model_obj.date.append(getattr(obs_obj.dataset, a))
|
88
92
|
|
89
93
|
|
90
|
-
def average_column_sum(data:
|
94
|
+
def average_column_sum(data: npt.NDArray) -> npt.NDArray:
|
91
95
|
"""Returns average sum of columns which have any data."""
|
92
96
|
return np.nanmean(np.nansum(data, 1) > 0)
|
@@ -3,6 +3,7 @@ import os
|
|
3
3
|
import sys
|
4
4
|
|
5
5
|
import numpy as np
|
6
|
+
import numpy.typing as npt
|
6
7
|
from numpy import ma
|
7
8
|
|
8
9
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
@@ -20,8 +21,8 @@ class DayStatistics:
|
|
20
21
|
done with. A list includes observed product name (str), model variable (str)
|
21
22
|
name and a name of observation variable (str). Example: ['cf', 'ECMWF',
|
22
23
|
'Cloud fraction by volume']
|
23
|
-
model (
|
24
|
-
observation (
|
24
|
+
model (npt.NDArray): Ndarray of model simulation of product
|
25
|
+
observation (npt.NDArray): Ndrray of Downsampled observation of product
|
25
26
|
|
26
27
|
Raises:
|
27
28
|
RuntimeError: A function of given method not found
|
@@ -44,9 +45,9 @@ class DayStatistics:
|
|
44
45
|
self,
|
45
46
|
method: str,
|
46
47
|
product_info: list,
|
47
|
-
model:
|
48
|
-
observation:
|
49
|
-
):
|
48
|
+
model: npt.NDArray,
|
49
|
+
observation: npt.NDArray,
|
50
|
+
) -> None:
|
50
51
|
self.method = method
|
51
52
|
self.product = product_info
|
52
53
|
self.model_data = model
|
@@ -1,34 +1,35 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
3
|
from cloudnetpy.model_evaluation.plotting import plotting as pl
|
4
|
+
from cloudnetpy.model_evaluation.plotting.plot_meta import PlotMeta
|
4
5
|
|
5
6
|
MODEL = "ecmwf"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
VARIABLE_INFO = PlotMeta(
|
8
|
+
name="Product",
|
9
|
+
cbar="rainbow",
|
10
|
+
plot_range=(0, 1),
|
11
|
+
plot_scale="linear",
|
12
|
+
plot_type="mesh",
|
13
|
+
)
|
11
14
|
|
12
15
|
|
13
16
|
@pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
|
14
17
|
def test_get_cf_title(key) -> None:
|
15
|
-
var = VariableInfo()
|
16
18
|
field_name = key + "_" + MODEL
|
17
19
|
value = "Product, Volume"
|
18
20
|
if "A" in key:
|
19
21
|
value = "Product, Area"
|
20
|
-
x = pl._get_cf_title(field_name,
|
22
|
+
x = pl._get_cf_title(field_name, VARIABLE_INFO)
|
21
23
|
assert x == value
|
22
24
|
|
23
25
|
|
24
26
|
@pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
|
25
27
|
def test_get_cf_title_cycle(key) -> None:
|
26
|
-
var = VariableInfo()
|
27
28
|
field_name = key + "_" + MODEL + "_001"
|
28
29
|
value = "Product, Volume"
|
29
30
|
if "A" in key:
|
30
31
|
value = "Product, Area"
|
31
|
-
x = pl._get_cf_title(field_name,
|
32
|
+
x = pl._get_cf_title(field_name, VARIABLE_INFO)
|
32
33
|
assert x == value
|
33
34
|
|
34
35
|
|
@@ -44,9 +45,8 @@ def test_get_cf_title_cycle(key) -> None:
|
|
44
45
|
],
|
45
46
|
)
|
46
47
|
def test_get_iwc_title(key, value) -> None:
|
47
|
-
var = VariableInfo()
|
48
48
|
field_name = key + "_" + MODEL
|
49
|
-
x = pl._get_iwc_title(field_name,
|
49
|
+
x = pl._get_iwc_title(field_name, VARIABLE_INFO)
|
50
50
|
assert x == value
|
51
51
|
|
52
52
|
|
@@ -62,23 +62,20 @@ def test_get_iwc_title(key, value) -> None:
|
|
62
62
|
],
|
63
63
|
)
|
64
64
|
def test_get_iwc_title_cycle(key, value) -> None:
|
65
|
-
var = VariableInfo()
|
66
65
|
field_name = key + "_" + MODEL + "_001"
|
67
|
-
x = pl._get_iwc_title(field_name,
|
66
|
+
x = pl._get_iwc_title(field_name, VARIABLE_INFO)
|
68
67
|
assert x == value
|
69
68
|
|
70
69
|
|
71
70
|
def test_get_product_title() -> None:
|
72
|
-
var = VariableInfo()
|
73
71
|
value = "Product"
|
74
|
-
x = pl._get_product_title(
|
72
|
+
x = pl._get_product_title(VARIABLE_INFO)
|
75
73
|
assert x == value
|
76
74
|
|
77
75
|
|
78
76
|
def test_get_product_title_cycle() -> None:
|
79
|
-
var = VariableInfo()
|
80
77
|
value = "Product"
|
81
|
-
x = pl._get_product_title(
|
78
|
+
x = pl._get_product_title(VARIABLE_INFO)
|
82
79
|
assert x == value
|
83
80
|
|
84
81
|
|
@@ -88,16 +85,14 @@ def test_get_product_title_cycle() -> None:
|
|
88
85
|
)
|
89
86
|
def test_get_stat_titles(key, title) -> None:
|
90
87
|
field_name = key + "_" + MODEL
|
91
|
-
|
92
|
-
x = pl._get_stat_titles(field_name, key, var)
|
88
|
+
x = pl._get_stat_titles(field_name, key, VARIABLE_INFO)
|
93
89
|
assert x == title
|
94
90
|
|
95
91
|
|
96
92
|
@pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"])
|
97
93
|
def test_get_cf_title_stat(key) -> None:
|
98
94
|
field_name = key + "_" + MODEL
|
99
|
-
|
100
|
-
x = pl._get_cf_title_stat(field_name, var)
|
95
|
+
x = pl._get_cf_title_stat(field_name, VARIABLE_INFO)
|
101
96
|
value = "Product volume"
|
102
97
|
if "A" in key:
|
103
98
|
value = "Product area"
|
@@ -114,13 +109,11 @@ def test_get_cf_title_stat(key) -> None:
|
|
114
109
|
)
|
115
110
|
def test_get_iwc_title_stat(key, value) -> None:
|
116
111
|
field_name = key + "_" + MODEL
|
117
|
-
|
118
|
-
x = pl._get_iwc_title_stat(field_name, var)
|
112
|
+
x = pl._get_iwc_title_stat(field_name, VARIABLE_INFO)
|
119
113
|
assert x == value
|
120
114
|
|
121
115
|
|
122
116
|
@pytest.mark.parametrize("key", ["lwc"])
|
123
117
|
def test_get_product_title_stat(key) -> None:
|
124
|
-
|
125
|
-
x = pl._get_product_title_stat(var)
|
118
|
+
x = pl._get_product_title_stat(VARIABLE_INFO)
|
126
119
|
assert x == "Product"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
|
-
from
|
2
|
+
from os import PathLike
|
3
3
|
|
4
4
|
|
5
|
-
def file_exists(file_path: str) -> bool:
|
6
|
-
return
|
5
|
+
def file_exists(file_path: str | PathLike) -> bool:
|
6
|
+
return os.path.isfile(file_path) and os.path.getsize(file_path) > 0
|
cloudnetpy/output.py
CHANGED
@@ -18,26 +18,20 @@ from cloudnetpy.metadata import COMMON_ATTRIBUTES
|
|
18
18
|
|
19
19
|
|
20
20
|
def save_level1b(
|
21
|
-
obj,
|
21
|
+
obj, # noqa: ANN001
|
22
22
|
output_file: PathLike | str,
|
23
|
-
uuid: UUID
|
24
|
-
) ->
|
23
|
+
uuid: UUID,
|
24
|
+
) -> None:
|
25
25
|
"""Saves Cloudnet Level 1b file."""
|
26
26
|
dimensions = _get_netcdf_dimensions(obj)
|
27
27
|
with init_file(output_file, dimensions, obj.data, uuid) as nc:
|
28
|
-
file_uuid = nc.file_uuid
|
29
28
|
fix_attribute_name(nc)
|
30
29
|
location = obj.site_meta["name"]
|
31
30
|
nc.cloudnet_file_type = obj.instrument.domain
|
32
31
|
nc.title = get_l1b_title(obj.instrument, location)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
nc.year = str(obj.date.year)
|
37
|
-
nc.month = str(obj.date.month).zfill(2)
|
38
|
-
nc.day = str(obj.date.day).zfill(2)
|
39
|
-
else:
|
40
|
-
raise TypeError
|
32
|
+
nc.year = str(obj.date.year)
|
33
|
+
nc.month = str(obj.date.month).zfill(2)
|
34
|
+
nc.day = str(obj.date.day).zfill(2)
|
41
35
|
nc.location = location
|
42
36
|
nc.history = get_l1b_history(obj.instrument)
|
43
37
|
nc.source = get_l1b_source(obj.instrument)
|
@@ -47,10 +41,9 @@ def save_level1b(
|
|
47
41
|
for software, version in obj.software.items():
|
48
42
|
nc.setncattr(f"{software}_version", version)
|
49
43
|
nc.references = get_references()
|
50
|
-
return file_uuid
|
51
44
|
|
52
45
|
|
53
|
-
def _get_netcdf_dimensions(obj) -> dict:
|
46
|
+
def _get_netcdf_dimensions(obj) -> dict: # noqa: ANN001
|
54
47
|
dimensions = {
|
55
48
|
key: len(obj.data[key][:]) for key in ("time", "range") if key in obj.data
|
56
49
|
}
|
@@ -78,10 +71,10 @@ def _get_netcdf_dimensions(obj) -> dict:
|
|
78
71
|
def save_product_file(
|
79
72
|
short_id: str,
|
80
73
|
obj: DataSource,
|
81
|
-
file_name: str,
|
82
|
-
uuid:
|
74
|
+
file_name: str | PathLike,
|
75
|
+
uuid: UUID,
|
83
76
|
copy_from_cat: tuple = (),
|
84
|
-
) ->
|
77
|
+
) -> None:
|
85
78
|
"""Saves a standard Cloudnet product file.
|
86
79
|
|
87
80
|
Args:
|
@@ -98,7 +91,6 @@ def save_product_file(
|
|
98
91
|
"height": len(obj.dataset.variables["height"]),
|
99
92
|
}
|
100
93
|
with init_file(file_name, dimensions, obj.data, uuid) as nc:
|
101
|
-
file_uuid = nc.file_uuid
|
102
94
|
nc.cloudnet_file_type = short_id
|
103
95
|
vars_from_source = (
|
104
96
|
"altitude",
|
@@ -121,7 +113,6 @@ def save_product_file(
|
|
121
113
|
)
|
122
114
|
merge_history(nc, human_readable_file_type, obj)
|
123
115
|
nc.references = get_references(short_id)
|
124
|
-
return file_uuid
|
125
116
|
|
126
117
|
|
127
118
|
def get_l1b_source(instrument: Instrument) -> str:
|
@@ -252,7 +243,7 @@ def init_file(
|
|
252
243
|
file_name: PathLike | str,
|
253
244
|
dimensions: dict,
|
254
245
|
cloudnet_arrays: dict,
|
255
|
-
uuid: UUID
|
246
|
+
uuid: UUID,
|
256
247
|
) -> netCDF4.Dataset:
|
257
248
|
"""Initializes a Cloudnet file for writing.
|
258
249
|
|
@@ -325,16 +316,11 @@ def copy_global(
|
|
325
316
|
|
326
317
|
def add_time_attribute(
|
327
318
|
attributes: dict,
|
328
|
-
date:
|
319
|
+
date: datetime.date,
|
329
320
|
key: str = "time",
|
330
321
|
) -> dict:
|
331
322
|
"""Adds time attribute with correct units."""
|
332
|
-
|
333
|
-
date_str = "-".join(date)
|
334
|
-
elif isinstance(date, datetime.date):
|
335
|
-
date_str = date.isoformat()
|
336
|
-
else:
|
337
|
-
raise TypeError
|
323
|
+
date_str = date.isoformat()
|
338
324
|
units = f"hours since {date_str} 00:00:00 +00:00"
|
339
325
|
if key not in attributes:
|
340
326
|
attributes[key] = COMMON_ATTRIBUTES[key]
|
@@ -436,13 +422,10 @@ def _get_identifier(short_id: str) -> str:
|
|
436
422
|
return short_id
|
437
423
|
|
438
424
|
|
439
|
-
def add_standard_global_attributes(
|
440
|
-
nc: netCDF4.Dataset,
|
441
|
-
uuid: UUID | str | None = None,
|
442
|
-
) -> None:
|
425
|
+
def add_standard_global_attributes(nc: netCDF4.Dataset, uuid: UUID) -> None:
|
443
426
|
nc.Conventions = "CF-1.8"
|
444
427
|
nc.cloudnetpy_version = version.__version__
|
445
|
-
nc.file_uuid = str(uuid)
|
428
|
+
nc.file_uuid = str(uuid)
|
446
429
|
|
447
430
|
|
448
431
|
def fix_attribute_name(nc: netCDF4.Dataset) -> None:
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
"""Misc. plotting routines for Cloudnet products."""
|
2
2
|
|
3
|
-
import os.path
|
4
3
|
import re
|
5
4
|
import textwrap
|
6
5
|
from dataclasses import dataclass
|
7
6
|
from datetime import date
|
7
|
+
from os import PathLike
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
import matplotlib.pyplot as plt
|
11
11
|
import netCDF4
|
12
12
|
import numpy as np
|
13
|
+
import numpy.typing as npt
|
13
14
|
from matplotlib import rcParams
|
14
15
|
from matplotlib.axes import Axes
|
15
16
|
from matplotlib.colorbar import Colorbar
|
17
|
+
from matplotlib.colorizer import ColorizingArtist
|
16
18
|
from matplotlib.colors import ListedColormap
|
17
19
|
from matplotlib.pyplot import Figure
|
18
20
|
from matplotlib.ticker import AutoMinorLocator
|
@@ -83,12 +85,15 @@ class Dimensions:
|
|
83
85
|
margin_left (int): Space between left edge of image and plotted data in pixels.
|
84
86
|
"""
|
85
87
|
|
86
|
-
def __init__(
|
88
|
+
def __init__(
|
89
|
+
self, fig: Figure, axes: list[Axes], pad_inches: float | None = None
|
90
|
+
) -> None:
|
87
91
|
if pad_inches is None:
|
88
92
|
pad_inches = rcParams["savefig.pad_inches"]
|
89
93
|
|
94
|
+
renderer = fig.canvas.get_renderer() # type: ignore[attr-defined]
|
90
95
|
tightbbox = (
|
91
|
-
fig.get_tightbbox(
|
96
|
+
fig.get_tightbbox(renderer)
|
92
97
|
.padded(pad_inches)
|
93
98
|
.transformed(Affine2D().scale(fig.dpi))
|
94
99
|
)
|
@@ -112,7 +117,7 @@ class FigureData:
|
|
112
117
|
file: netCDF4.Dataset,
|
113
118
|
requested_variables: list[str],
|
114
119
|
options: PlotParameters,
|
115
|
-
):
|
120
|
+
) -> None:
|
116
121
|
self.file = file
|
117
122
|
self.variables, self.indices = self._get_valid_variables_and_indices(
|
118
123
|
requested_variables
|
@@ -231,7 +236,7 @@ class SubPlot:
|
|
231
236
|
variable: netCDF4.Variable,
|
232
237
|
options: PlotParameters,
|
233
238
|
file_type: str | None,
|
234
|
-
):
|
239
|
+
) -> None:
|
235
240
|
self.ax = ax
|
236
241
|
self.variable = variable
|
237
242
|
self.options = options
|
@@ -348,7 +353,7 @@ class SubPlot:
|
|
348
353
|
|
349
354
|
|
350
355
|
class Plot:
|
351
|
-
def __init__(self, sub_plot: SubPlot):
|
356
|
+
def __init__(self, sub_plot: SubPlot) -> None:
|
352
357
|
self.sub_plot = sub_plot
|
353
358
|
self._data = sub_plot.variable[:]
|
354
359
|
self._data_orig = self._data.copy()
|
@@ -389,7 +394,7 @@ class Plot:
|
|
389
394
|
def _get_y_limits(self) -> tuple[float, float]:
|
390
395
|
return 0, self.sub_plot.options.max_y
|
391
396
|
|
392
|
-
def _init_colorbar(self, plot) -> Colorbar:
|
397
|
+
def _init_colorbar(self, plot: ColorizingArtist) -> Colorbar:
|
393
398
|
divider = make_axes_locatable(self._ax)
|
394
399
|
cax = divider.append_axes("right", size="1%", pad=0.25)
|
395
400
|
return plt.colorbar(plot, fraction=1.0, ax=self._ax, cax=cax)
|
@@ -476,7 +481,7 @@ class Plot:
|
|
476
481
|
|
477
482
|
|
478
483
|
class Plot2D(Plot):
|
479
|
-
def plot(self, figure_data: FigureData):
|
484
|
+
def plot(self, figure_data: FigureData) -> None:
|
480
485
|
self._convert_units()
|
481
486
|
if figure_data.file_type == "cpr-simulation":
|
482
487
|
min_x, max_x = 0, EARTHCARE_MAX_X
|
@@ -616,7 +621,12 @@ class Plot2D(Plot):
|
|
616
621
|
linestyles="dashed",
|
617
622
|
)
|
618
623
|
|
619
|
-
def _plot_contour(
|
624
|
+
def _plot_contour(
|
625
|
+
self,
|
626
|
+
figure_data: FigureData,
|
627
|
+
alt: npt.NDArray,
|
628
|
+
**options, # noqa: ANN003
|
629
|
+
) -> None:
|
620
630
|
time_length = len(figure_data.time_including_gaps)
|
621
631
|
step = max(1, time_length // 200)
|
622
632
|
ind_time = np.arange(0, time_length, step)
|
@@ -882,11 +892,11 @@ class Plot1D(Plot):
|
|
882
892
|
|
883
893
|
|
884
894
|
def generate_figure(
|
885
|
-
filename:
|
895
|
+
filename: PathLike | str,
|
886
896
|
variables: list[str],
|
887
897
|
*,
|
888
898
|
show: bool = True,
|
889
|
-
output_filename:
|
899
|
+
output_filename: PathLike | str | None = None,
|
890
900
|
options: PlotParameters | None = None,
|
891
901
|
) -> Dimensions:
|
892
902
|
"""Generate a figure based on the given filename and variables.
|
@@ -960,7 +970,7 @@ def generate_figure(
|
|
960
970
|
plt.close(fig)
|
961
971
|
|
962
972
|
|
963
|
-
def lin2log(*args) -> list:
|
973
|
+
def lin2log(*args: npt.ArrayLike) -> list[ma.MaskedArray]:
|
964
974
|
return [ma.log10(x) for x in args]
|
965
975
|
|
966
976
|
|