cloudnetpy 1.61.15__py3-none-any.whl → 1.61.16__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.
Files changed (67) hide show
  1. cloudnetpy/categorize/atmos.py +1 -0
  2. cloudnetpy/categorize/atmos_utils.py +1 -1
  3. cloudnetpy/categorize/categorize.py +1 -0
  4. cloudnetpy/categorize/classify.py +1 -0
  5. cloudnetpy/categorize/containers.py +3 -1
  6. cloudnetpy/categorize/disdrometer.py +1 -0
  7. cloudnetpy/categorize/droplet.py +2 -2
  8. cloudnetpy/categorize/falling.py +1 -0
  9. cloudnetpy/categorize/freezing.py +1 -0
  10. cloudnetpy/categorize/insects.py +1 -0
  11. cloudnetpy/categorize/lidar.py +1 -0
  12. cloudnetpy/categorize/melting.py +1 -0
  13. cloudnetpy/categorize/model.py +1 -0
  14. cloudnetpy/categorize/mwr.py +1 -0
  15. cloudnetpy/categorize/radar.py +1 -0
  16. cloudnetpy/cloudnetarray.py +2 -0
  17. cloudnetpy/concat_lib.py +1 -0
  18. cloudnetpy/constants.py +1 -0
  19. cloudnetpy/datasource.py +5 -3
  20. cloudnetpy/instruments/basta.py +1 -0
  21. cloudnetpy/instruments/ceilo.py +2 -1
  22. cloudnetpy/instruments/cl61d.py +1 -0
  23. cloudnetpy/instruments/copernicus.py +2 -1
  24. cloudnetpy/instruments/disdrometer/parsivel.py +6 -4
  25. cloudnetpy/instruments/galileo.py +1 -0
  26. cloudnetpy/instruments/hatpro.py +1 -0
  27. cloudnetpy/instruments/lufft.py +1 -0
  28. cloudnetpy/instruments/mira.py +1 -0
  29. cloudnetpy/instruments/mrr.py +1 -1
  30. cloudnetpy/instruments/nc_lidar.py +1 -0
  31. cloudnetpy/instruments/nc_radar.py +1 -0
  32. cloudnetpy/instruments/pollyxt.py +1 -0
  33. cloudnetpy/instruments/radiometrics.py +2 -1
  34. cloudnetpy/instruments/rpg.py +2 -1
  35. cloudnetpy/instruments/rpg_reader.py +1 -1
  36. cloudnetpy/instruments/toa5.py +1 -1
  37. cloudnetpy/instruments/vaisala.py +7 -6
  38. cloudnetpy/instruments/weather_station.py +2 -3
  39. cloudnetpy/model_evaluation/file_handler.py +2 -2
  40. cloudnetpy/model_evaluation/metadata.py +1 -1
  41. cloudnetpy/model_evaluation/plotting/plot_tools.py +2 -2
  42. cloudnetpy/model_evaluation/plotting/plotting.py +11 -8
  43. cloudnetpy/model_evaluation/products/grid_methods.py +1 -1
  44. cloudnetpy/model_evaluation/products/model_products.py +7 -7
  45. cloudnetpy/model_evaluation/products/observation_products.py +8 -8
  46. cloudnetpy/model_evaluation/products/tools.py +5 -7
  47. cloudnetpy/model_evaluation/statistics/statistical_methods.py +2 -2
  48. cloudnetpy/output.py +3 -1
  49. cloudnetpy/plotting/plot_meta.py +2 -2
  50. cloudnetpy/plotting/plotting.py +8 -11
  51. cloudnetpy/products/classification.py +10 -9
  52. cloudnetpy/products/der.py +3 -2
  53. cloudnetpy/products/drizzle.py +3 -3
  54. cloudnetpy/products/drizzle_tools.py +1 -1
  55. cloudnetpy/products/ier.py +1 -0
  56. cloudnetpy/products/iwc.py +4 -3
  57. cloudnetpy/products/lwc.py +2 -3
  58. cloudnetpy/products/mwr_tools.py +2 -4
  59. cloudnetpy/products/product_tools.py +2 -1
  60. cloudnetpy/utils.py +9 -14
  61. cloudnetpy/version.py +1 -1
  62. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.16.dist-info}/METADATA +1 -1
  63. cloudnetpy-1.61.16.dist-info/RECORD +115 -0
  64. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.16.dist-info}/WHEEL +1 -1
  65. cloudnetpy-1.61.15.dist-info/RECORD +0 -115
  66. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.16.dist-info}/LICENSE +0 -0
  67. {cloudnetpy-1.61.15.dist-info → cloudnetpy-1.61.16.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  """This module contains functions to calculate
2
2
  various atmospheric parameters.
3
3
  """
4
+
4
5
  from typing import Final
5
6
 
6
7
  import numpy as np
@@ -129,5 +129,5 @@ def k2c(temp: np.ndarray) -> np.ndarray:
129
129
 
130
130
 
131
131
  def mmh2ms(data: np.ndarray) -> np.ndarray:
132
- """Converts mm h-1 to m s-1"""
132
+ """Converts mm h-1 to m s-1."""
133
133
  return data / con.SEC_IN_HOUR * con.MM_TO_M
@@ -1,4 +1,5 @@
1
1
  """Module that generates Cloudnet categorize file."""
2
+
2
3
  import logging
3
4
 
4
5
  from cloudnetpy import output, utils
@@ -1,6 +1,7 @@
1
1
  """Module containing low-level functions to classify gridded
2
2
  radar / lidar measurements.
3
3
  """
4
+
4
5
  import numpy as np
5
6
  import skimage
6
7
  from numpy import ma
@@ -9,7 +9,7 @@ from cloudnetpy.constants import MM_H_TO_M_S
9
9
 
10
10
  @dataclass
11
11
  class ClassificationResult:
12
- """Result of classification"""
12
+ """Result of classification."""
13
13
 
14
14
  category_bits: np.ndarray
15
15
  is_rain: np.ndarray
@@ -103,6 +103,8 @@ def _find_clutter(
103
103
  """Estimates clutter from doppler velocity.
104
104
 
105
105
  Args:
106
+ v: 2D radar velocity.
107
+ is_rain: 2D boolean array denoting rain.
106
108
  n_gates: Number of range gates from the ground where clutter is expected
107
109
  to be found. Default is 10.
108
110
  v_lim: Velocity threshold. Smaller values are classified as clutter.
@@ -1,4 +1,5 @@
1
1
  """Mwr module, containing the :class:`Mwr` class."""
2
+
2
3
  import logging
3
4
 
4
5
  import numpy as np
@@ -1,5 +1,5 @@
1
- """This module has functions for liquid layer detection.
2
- """
1
+ """This module has functions for liquid layer detection."""
2
+
3
3
  import numpy as np
4
4
  import scipy.signal
5
5
  from numpy import ma
@@ -1,4 +1,5 @@
1
1
  """Module to find falling hydrometeors from data."""
2
+
2
3
  import numpy as np
3
4
  from numpy import ma
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Module to find freezing region from data."""
2
+
2
3
  import logging
3
4
 
4
5
  import numpy as np
@@ -1,4 +1,5 @@
1
1
  """Module to find insects from data."""
2
+
2
3
  import numpy as np
3
4
  from numpy import ma
4
5
  from scipy.ndimage import gaussian_filter
@@ -1,4 +1,5 @@
1
1
  """Lidar module, containing the :class:`Lidar` class."""
2
+
2
3
  import logging
3
4
  from typing import Literal
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Functions to find melting layer from data."""
2
+
2
3
  import numpy as np
3
4
  from numpy import ma
4
5
  from scipy.ndimage import gaussian_filter
@@ -1,4 +1,5 @@
1
1
  """Model module, containing the :class:`Model` class."""
2
+
2
3
  import numpy as np
3
4
  from numpy import ma
4
5
  from scipy.interpolate import interp1d
@@ -1,4 +1,5 @@
1
1
  """Mwr module, containing the :class:`Mwr` class."""
2
+
2
3
  import numpy as np
3
4
 
4
5
  from cloudnetpy import utils
@@ -1,4 +1,5 @@
1
1
  """Radar module, containing the :class:`Radar` class."""
2
+
2
3
  import logging
3
4
  import math
4
5
 
@@ -1,4 +1,5 @@
1
1
  """CloudnetArray class."""
2
+
2
3
  import math
3
4
  from collections.abc import Sequence
4
5
 
@@ -62,6 +63,7 @@ class CloudnetArray:
62
63
  Args:
63
64
  time: 1D time array.
64
65
  time_new: 1D new time array.
66
+ mask_zeros: Whether to mask 0 values in the returned array. Default is True.
65
67
 
66
68
  Returns:
67
69
  Time indices without data.
cloudnetpy/concat_lib.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Module for concatenating netCDF files."""
2
+
2
3
  import netCDF4
3
4
  import numpy as np
4
5
 
cloudnetpy/constants.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Constants used in Cloudnet processing."""
2
+
2
3
  from typing import Final
3
4
 
4
5
  # Triple point of water
cloudnetpy/datasource.py CHANGED
@@ -1,4 +1,5 @@
1
- """Datasource module, containing the :class:`DataSource class.`"""
1
+ """Datasource module, containing the :class:`DataSource` class."""
2
+
2
3
  import logging
3
4
  import os
4
5
  from collections.abc import Callable
@@ -94,6 +95,7 @@ class DataSource:
94
95
  attribute (dictionary).
95
96
  name: CloudnetArray.name attribute. Default value is *key*.
96
97
  units: CloudnetArray.units attribute.
98
+ dtype: CloudnetArray.data_type attribute.
97
99
 
98
100
  """
99
101
  self.data[key] = CloudnetArray(variable, name or key, units, data_type=dtype)
@@ -101,10 +103,10 @@ class DataSource:
101
103
  def get_date(self) -> list:
102
104
  """Returns date components.
103
105
 
104
- Returns
106
+ Returns:
105
107
  list: Date components [YYYY, MM, DD].
106
108
 
107
- Raises
109
+ Raises:
108
110
  RuntimeError: Not found or invalid date.
109
111
 
110
112
  """
@@ -1,4 +1,5 @@
1
1
  """Module for reading / converting BASTA radar data."""
2
+
2
3
  import numpy as np
3
4
 
4
5
  from cloudnetpy import output
@@ -1,4 +1,5 @@
1
1
  """Module for reading and processing Vaisala / Lufft ceilometers."""
2
+
2
3
  from itertools import islice
3
4
 
4
5
  import netCDF4
@@ -45,7 +46,7 @@ def ceilo2nc(
45
46
  site_meta: Dictionary containing information about the site and instrument.
46
47
  Required key value pairs are `name` and `altitude` (metres above mean
47
48
  sea level). Also, 'calibration_factor' is recommended because the default
48
- value is probably incorrect. If the backround noise is *not*
49
+ value is probably incorrect. If the background noise is *not*
49
50
  range-corrected, you must define: {'range_corrected': False}.
50
51
  You can also explicitly set the instrument model with
51
52
  e.g. {'model': 'cl61d'}.
@@ -1,4 +1,5 @@
1
1
  """Module with a class for Lufft chm15k ceilometer."""
2
+
2
3
  import logging
3
4
 
4
5
  import netCDF4
@@ -1,4 +1,5 @@
1
1
  """Module for reading raw cloud radar data."""
2
+
2
3
  import os
3
4
  import tempfile
4
5
  from tempfile import TemporaryDirectory
@@ -126,7 +127,7 @@ class Copernicus(ChilboltonRadar):
126
127
  def mask_corrupted_values(self) -> None:
127
128
  """Experimental masking of corrupted Copernicus data.
128
129
 
129
- Notes
130
+ Notes:
130
131
  This method is based on a few days of test data only. Should be improved
131
132
  and tested more carefully in the future.
132
133
  """
@@ -43,7 +43,7 @@ def parsivel2nc(
43
43
  the instrument's operating instructions. Unknown values are indicated
44
44
  with None. Telegram is required if the input file doesn't contain a
45
45
  header.
46
- timestamps:
46
+ timestamps: Specify list of timestamps if they are missing in the input file.
47
47
 
48
48
  Returns:
49
49
  UUID of the generated file.
@@ -450,7 +450,7 @@ def _parse_row(row_in: str, headers: list[str]) -> list:
450
450
  def _read_toa5(filename: str | PathLike) -> dict[str, list]:
451
451
  """Read ASCII data from Campbell Scientific datalogger such as CR1000.
452
452
 
453
- References
453
+ References:
454
454
  CR1000 Measurement and Control System.
455
455
  https://s.campbellsci.com/documents/us/manuals/cr1000.pdf
456
456
  """
@@ -616,8 +616,10 @@ def _read_typ_op4a(lines: list[str]) -> dict[str, Any]:
616
616
 
617
617
 
618
618
  def _read_fmi(content: str):
619
- """Read format used by Finnish Meteorological Institute and University of
620
- Helsinki:
619
+ r"""Read format used by Finnish Meteorological Institute and University of
620
+ Helsinki.
621
+
622
+ Format consists of sequence of the following:
621
623
  - "[YYYY-MM-DD HH:MM:SS\n"
622
624
  - output of "CS/PA" command without non-printable characters at the end
623
625
  - "]\n"
@@ -1,4 +1,5 @@
1
1
  """Module for reading raw Galileo cloud radar data."""
2
+
2
3
  import os
3
4
  from tempfile import NamedTemporaryFile, TemporaryDirectory
4
5
 
@@ -1,4 +1,5 @@
1
1
  """This module contains RPG Cloud Radar related functions."""
2
+
2
3
  import datetime
3
4
  import logging
4
5
  from collections import defaultdict
@@ -1,4 +1,5 @@
1
1
  """Module with a class for Lufft chm15k ceilometer."""
2
+
2
3
  import logging
3
4
 
4
5
  import netCDF4
@@ -1,4 +1,5 @@
1
1
  """Module for reading raw cloud radar data."""
2
+
2
3
  import logging
3
4
  import os
4
5
  from collections import OrderedDict
@@ -134,7 +134,7 @@ class MrrPro(NcRadar):
134
134
  if m := re.search(
135
135
  r"serial number:\s*(\w+)",
136
136
  self.dataset.instrument_name,
137
- re.I,
137
+ re.IGNORECASE,
138
138
  ):
139
139
  self.serial_number = m[1]
140
140
 
@@ -1,4 +1,5 @@
1
1
  """Module with a class for Lufft chm15k ceilometer."""
2
+
2
3
  import logging
3
4
  from typing import TYPE_CHECKING, Literal
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Module for reading raw cloud radar data."""
2
+
2
3
  import logging
3
4
  from os import PathLike
4
5
  from typing import TYPE_CHECKING
@@ -1,4 +1,5 @@
1
1
  """Module for reading / converting pollyxt data."""
2
+
2
3
  import glob
3
4
  import logging
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Module for reading Radiometrics MP3014 microwave radiometer data."""
2
+
2
3
  import csv
3
4
  import datetime
4
5
  import logging
@@ -80,7 +81,7 @@ class Record(NamedTuple):
80
81
  class Radiometrics:
81
82
  """Reader for level 2 files of Radiometrics microwave radiometers.
82
83
 
83
- References
84
+ References:
84
85
  Radiometrics (2008). Profiler Operator's Manual: MP-3000A, MP-2500A,
85
86
  MP-1500A, MP-183A.
86
87
  """
@@ -1,4 +1,5 @@
1
1
  """This module contains RPG Cloud Radar related functions."""
2
+
2
3
  import logging
3
4
  import math
4
5
  from collections.abc import Sequence
@@ -111,7 +112,7 @@ def create_one_day_data_record(rpg_objects: RpgObjects) -> dict:
111
112
  def _stack_rpg_data(rpg_objects: RpgObjects) -> tuple[dict, dict]:
112
113
  """Combines data from hourly RPG objects.
113
114
 
114
- Notes
115
+ Notes:
115
116
  Ignores variable names starting with an underscore.
116
117
 
117
118
  """
@@ -164,7 +164,7 @@ def _decode_angles(
164
164
  class HatproBin:
165
165
  """HATPRO binary file reader. Byte order is assumed to be little endian.
166
166
 
167
- References
167
+ References:
168
168
  Radiometer Physics (2014): Instrument Operation and Software Guide
169
169
  Operation Principles and Software Description for RPG standard single
170
170
  polarization radiometers (G5 series).
@@ -9,7 +9,7 @@ def read_toa5(
9
9
  ) -> tuple[dict[str, str], dict[str, str], list[dict[str, Any]]]:
10
10
  """Read ASCII data from Campbell Scientific datalogger such as CR1000.
11
11
 
12
- References
12
+ References:
13
13
  CR1000 Measurement and Control System.
14
14
  https://s.campbellsci.com/documents/us/manuals/cr1000.pdf
15
15
  """
@@ -1,4 +1,5 @@
1
1
  """Module with classes for Vaisala ceilometers."""
2
+
2
3
  import itertools
3
4
  import logging
4
5
 
@@ -139,7 +140,7 @@ class VaisalaCeilo(Ceilometer):
139
140
 
140
141
  @staticmethod
141
142
  def _calc_date(time_lines) -> list:
142
- """Returns the date [yyyy, mm, dd]"""
143
+ """Returns the date [yyyy, mm, dd]."""
143
144
  return time_lines[0].split()[0].strip("-").split("-")
144
145
 
145
146
  @classmethod
@@ -305,7 +306,7 @@ class ClCeilo(VaisalaCeilo):
305
306
  class Ct25k(VaisalaCeilo):
306
307
  """Class for Vaisala CT25k ceilometer.
307
308
 
308
- References
309
+ References:
309
310
  https://www.manualslib.com/manual/1414094/Vaisala-Ct25k.html
310
311
 
311
312
  """
@@ -367,11 +368,11 @@ class Ct25k(VaisalaCeilo):
367
368
  def split_string(string: str, indices: list) -> list:
368
369
  """Splits string between indices.
369
370
 
370
- Notes
371
+ Notes:
371
372
  It is possible to skip characters from the beginning and end of the
372
373
  string but not from the middle.
373
374
 
374
- Examples
375
+ Examples:
375
376
  >>> s = 'abcde'
376
377
  >>> indices = [1, 2, 4]
377
378
  >>> split_string(s, indices)
@@ -384,7 +385,7 @@ def split_string(string: str, indices: list) -> list:
384
385
  def values_to_dict(keys: tuple, values: list) -> dict:
385
386
  """Converts list elements to dictionary.
386
387
 
387
- Examples
388
+ Examples:
388
389
  >>> keys = ('a', 'b')
389
390
  >>> values = [[1, 2], [1, 2], [1, 2], [1, 2]]
390
391
  >>> values_to_dict(keys, values)
@@ -398,6 +399,6 @@ def values_to_dict(keys: tuple, values: list) -> dict:
398
399
 
399
400
 
400
401
  def time_to_fraction_hour(time: str) -> float:
401
- """Returns time (hh:mm:ss) as fraction hour"""
402
+ """Returns time (hh:mm:ss) as fraction hour."""
402
403
  hour, minute, sec = time.split(":")
403
404
  return int(hour) + (int(minute) * SEC_IN_MINUTE + int(sec)) / SEC_IN_HOUR
@@ -320,11 +320,10 @@ class KenttarovaWS(WS):
320
320
 
321
321
 
322
322
  class HyytialaWS(WS):
323
- """
324
- Hyytiälä rain-gauge variables: a = Pluvio400 and b = Pluvio200.
323
+ """Hyytiälä rain-gauge variables: a = Pluvio400 and b = Pluvio200.
325
324
  E.g.
326
325
  - AaRNRT/mm = amount of non-real-time rain total (Pluvio400) [mm]
327
- - BbRT/mm = Bucket content in real-time (Pluvio200) [mm]
326
+ - BbRT/mm = Bucket content in real-time (Pluvio200) [mm].
328
327
  """
329
328
 
330
329
  def __init__(self, filenames: list[str], site_meta: dict):
@@ -145,7 +145,7 @@ def _augment_global_attributes(root_group: netCDF4.Dataset) -> None:
145
145
 
146
146
 
147
147
  def _add_source(root_ground: netCDF4.Dataset, objects: tuple, files: tuple) -> None:
148
- """Generates source info for multiple files"""
148
+ """Generates source info for multiple files."""
149
149
  model, obs = objects
150
150
  model_files, obs_file = files
151
151
  source = f"Observation file: {os.path.basename(obs_file)}"
@@ -160,7 +160,7 @@ def _add_source(root_ground: netCDF4.Dataset, objects: tuple, files: tuple) -> N
160
160
 
161
161
 
162
162
  def add_time_attribute(date: datetime) -> dict:
163
- """ "Adds time attribute with correct units.
163
+ """Adds time attribute with correct units.
164
164
 
165
165
  Args:
166
166
  attributes: Attributes of variables.
@@ -114,7 +114,7 @@ REGRID_PRODUCT_ATTRIBUTES = {
114
114
  comment=(
115
115
  "Cloud fraction generated from observations and by volume,\n"
116
116
  "averaged onto the models grid with height and time. Volume is\n"
117
- "space withing four grid points."
117
+ "space within four grid points."
118
118
  ),
119
119
  ),
120
120
  "cf_A": MetaData(
@@ -13,7 +13,7 @@ def parse_wanted_names(
13
13
  *,
14
14
  advance: bool = False,
15
15
  ) -> tuple[list, list]:
16
- """Returns standard and advection lists of product types to plot"""
16
+ """Returns standard and advection lists of product types to plot."""
17
17
  if variables:
18
18
  names = variables
19
19
  else:
@@ -76,7 +76,7 @@ def sort_cycles(names: list, model: str) -> tuple[list, list]:
76
76
 
77
77
 
78
78
  def read_data_characters(nc_file: str, name: str, model: str) -> tuple:
79
- """Gets dimensions and data for plotting"""
79
+ """Gets dimensions and data for plotting."""
80
80
  nc = netCDF4.Dataset(nc_file)
81
81
  data = nc.variables[name][:]
82
82
  data = mask_small_values(data, name)
@@ -7,9 +7,11 @@ import matplotlib as mpl
7
7
  import matplotlib.pyplot as plt
8
8
  import netCDF4
9
9
  import numpy as np
10
+ from matplotlib.axes import Axes
10
11
  from matplotlib.colorbar import Colorbar
11
12
  from matplotlib.colors import ListedColormap
12
13
  from matplotlib.patches import Patch
14
+ from matplotlib.pyplot import Figure
13
15
  from mpl_toolkits.axes_grid1 import make_axes_locatable
14
16
  from numpy import ma
15
17
 
@@ -187,6 +189,7 @@ def get_group_plots(
187
189
  show (bool): Show figure before saving if True
188
190
  cycle (str): Name of cycle if exists
189
191
  title (bool): True or False if wanted to add title to subfig
192
+ include_xlimits (bool): Show labels at the ends of x-axis
190
193
  """
191
194
  fig, ax = initialize_figure(len(names))
192
195
  model_run = model
@@ -248,6 +251,7 @@ def get_pair_plots(
248
251
  show (bool): Show figure before saving if True
249
252
  cycle (str): Name of cycle if exists
250
253
  title (bool): True or False if wanted add title to subfig
254
+ include_xlimits (bool): Show labels at the ends of x-axis
251
255
  """
252
256
  variable_info = ATTRIBUTES[product]
253
257
  model_ax = names[0]
@@ -306,6 +310,7 @@ def get_single_plots(
306
310
  show (bool): Show figure before saving if True
307
311
  cycle (str): Name of cycle if exists
308
312
  title (bool): True or False if wanted to add title to subfig
313
+ include_xlimits (bool): Show labels at the ends of x-axis
309
314
  """
310
315
  figs = []
311
316
  axes = []
@@ -387,6 +392,7 @@ def get_statistic_plots(
387
392
  image_name (str, optional): Saving name of generated fig
388
393
  show (bool): Show figure before saving if True
389
394
  cycle (str): Name of cycle if exists
395
+ title (bool): True or False if wanted to add title to subfig
390
396
  """
391
397
  model_run = model
392
398
  name = ""
@@ -687,8 +693,8 @@ def plot_vertical_profile(
687
693
  ax.xaxis.grid(which="major")
688
694
 
689
695
 
690
- def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
691
- """Set up fig and ax object, if subplot"""
696
+ def initialize_figure(n_subplots: int, stat: str = "") -> tuple[Figure, list[Axes]]:
697
+ """Set up fig and ax object, if subplot."""
692
698
  if n_subplots <= 0:
693
699
  n_subplots = 1
694
700
  fig, axes = plt.subplots(n_subplots, 1, figsize=(16, 4 + (n_subplots - 1) * 4.8))
@@ -714,7 +720,6 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
714
720
  right=0.75,
715
721
  hspace=0.2,
716
722
  )
717
- axes = axes.flatten()
718
723
  if stat == "vertical" and n_subplots > 1:
719
724
  fig, axes = plt.subplots(
720
725
  int(n_subplots / 2),
@@ -731,10 +736,8 @@ def initialize_figure(n_subplots: int, stat: str = "") -> tuple:
731
736
  right=0.75,
732
737
  hspace=0.16,
733
738
  )
734
- axes = axes.flatten()
735
- if n_subplots == 1:
736
- axes = [axes]
737
- return fig, axes
739
+ axes_list = [axes] if isinstance(axes, Axes) else axes.flatten().tolist()
740
+ return fig, axes_list
738
741
 
739
742
 
740
743
  def init_colorbar(plot, axis) -> Colorbar:
@@ -750,7 +753,7 @@ def _set_title(
750
753
  variable_info,
751
754
  model_name: str = "",
752
755
  ) -> None:
753
- """Generates subtitles for different product types"""
756
+ """Generates subtitles for different product types."""
754
757
  parts = field_name.split("_")
755
758
  if parts[0] == product:
756
759
  title = _get_product_title(variable_info)
@@ -154,7 +154,7 @@ class ProductGrid:
154
154
  x_ind: np.ndarray,
155
155
  y_ind: np.ndarray,
156
156
  ) -> np.ndarray | None:
157
- """Reshapes True observation values to windows shape"""
157
+ """Reshapes True observation values to windows shape."""
158
158
  window_size = tl.get_obs_window_size(x_ind, y_ind)
159
159
  if window_size is not None:
160
160
  return self._obs_data[ind].reshape(window_size)
@@ -54,7 +54,7 @@ class ModelManager(DataSource):
54
54
  self.resolution_h = self._get_horizontal_resolution()
55
55
 
56
56
  def _read_cycle_name(self, model_file: str) -> str:
57
- """Get cycle name from model_metadata.py for saving variable name(s)"""
57
+ """Get cycle name from model_metadata.py for saving variable name(s)."""
58
58
  try:
59
59
  cycles = self.model_info.cycle
60
60
  if cycles is None:
@@ -68,7 +68,7 @@ class ModelManager(DataSource):
68
68
  return ""
69
69
 
70
70
  def _generate_products(self) -> None:
71
- """Process needed data of model to a ModelManager object"""
71
+ """Process needed data of model to a ModelManager object."""
72
72
  cls = importlib.import_module(__name__).ModelManager
73
73
  try:
74
74
  name = f"_get_{self._product}"
@@ -123,10 +123,10 @@ class ModelManager(DataSource):
123
123
  return q * p / (287 * t)
124
124
 
125
125
  def _add_variables(self) -> None:
126
- """Add basic variables off model and cycle"""
126
+ """Add basic variables off model and cycle."""
127
127
 
128
128
  def _add_common_variables() -> None:
129
- """Model variables that are always the same within cycles"""
129
+ """Model variables that are always the same within cycles."""
130
130
  wanted_vars = self.model_vars.common_var
131
131
  if wanted_vars is None:
132
132
  msg = f"Model {self.model} has no common variables"
@@ -140,7 +140,7 @@ class ModelManager(DataSource):
140
140
  self.append_data(data, f"{var}")
141
141
 
142
142
  def _add_cycle_variables() -> None:
143
- """Add cycle depending variables"""
143
+ """Add cycle depending variables."""
144
144
  wanted_vars = self.model_vars.cycle_var
145
145
  if wanted_vars is None:
146
146
  msg = f"Model {self.model} has no cycle variables"
@@ -160,7 +160,7 @@ class ModelManager(DataSource):
160
160
  _add_cycle_variables()
161
161
 
162
162
  def cut_off_extra_levels(self, data: np.ndarray) -> np.ndarray:
163
- """Remove unused levels (over 22km) from model data"""
163
+ """Remove unused levels (over 22km) from model data."""
164
164
  try:
165
165
  level = self.model_info.level
166
166
  except KeyError:
@@ -169,7 +169,7 @@ class ModelManager(DataSource):
169
169
  return data[:, :level] if data.ndim > 1 else data[:level]
170
170
 
171
171
  def _calculate_wind_speed(self) -> np.ndarray:
172
- """Real wind from x- and y-components"""
172
+ """Real wind from x- and y-components."""
173
173
  u = self.getvar("uwind")
174
174
  v = self.getvar("vwind")
175
175
  u = self.cut_off_extra_levels(u)