cloudnetpy 1.49.9__py3-none-any.whl → 1.87.3__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 (116) hide show
  1. cloudnetpy/categorize/__init__.py +1 -2
  2. cloudnetpy/categorize/atmos_utils.py +297 -67
  3. cloudnetpy/categorize/attenuation.py +31 -0
  4. cloudnetpy/categorize/attenuations/__init__.py +37 -0
  5. cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
  6. cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
  7. cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
  8. cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
  9. cloudnetpy/categorize/categorize.py +332 -156
  10. cloudnetpy/categorize/classify.py +127 -125
  11. cloudnetpy/categorize/containers.py +107 -76
  12. cloudnetpy/categorize/disdrometer.py +40 -0
  13. cloudnetpy/categorize/droplet.py +23 -21
  14. cloudnetpy/categorize/falling.py +53 -24
  15. cloudnetpy/categorize/freezing.py +25 -12
  16. cloudnetpy/categorize/insects.py +35 -23
  17. cloudnetpy/categorize/itu.py +243 -0
  18. cloudnetpy/categorize/lidar.py +36 -41
  19. cloudnetpy/categorize/melting.py +34 -26
  20. cloudnetpy/categorize/model.py +84 -37
  21. cloudnetpy/categorize/mwr.py +18 -14
  22. cloudnetpy/categorize/radar.py +215 -102
  23. cloudnetpy/cli.py +578 -0
  24. cloudnetpy/cloudnetarray.py +43 -89
  25. cloudnetpy/concat_lib.py +218 -78
  26. cloudnetpy/constants.py +28 -10
  27. cloudnetpy/datasource.py +61 -86
  28. cloudnetpy/exceptions.py +49 -20
  29. cloudnetpy/instruments/__init__.py +5 -0
  30. cloudnetpy/instruments/basta.py +29 -12
  31. cloudnetpy/instruments/bowtie.py +135 -0
  32. cloudnetpy/instruments/ceilo.py +138 -115
  33. cloudnetpy/instruments/ceilometer.py +164 -80
  34. cloudnetpy/instruments/cl61d.py +21 -5
  35. cloudnetpy/instruments/cloudnet_instrument.py +74 -36
  36. cloudnetpy/instruments/copernicus.py +108 -30
  37. cloudnetpy/instruments/da10.py +54 -0
  38. cloudnetpy/instruments/disdrometer/common.py +126 -223
  39. cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
  40. cloudnetpy/instruments/disdrometer/thies.py +254 -87
  41. cloudnetpy/instruments/fd12p.py +201 -0
  42. cloudnetpy/instruments/galileo.py +65 -23
  43. cloudnetpy/instruments/hatpro.py +123 -49
  44. cloudnetpy/instruments/instruments.py +113 -1
  45. cloudnetpy/instruments/lufft.py +39 -17
  46. cloudnetpy/instruments/mira.py +268 -61
  47. cloudnetpy/instruments/mrr.py +187 -0
  48. cloudnetpy/instruments/nc_lidar.py +19 -8
  49. cloudnetpy/instruments/nc_radar.py +109 -55
  50. cloudnetpy/instruments/pollyxt.py +135 -51
  51. cloudnetpy/instruments/radiometrics.py +313 -59
  52. cloudnetpy/instruments/rain_e_h3.py +171 -0
  53. cloudnetpy/instruments/rpg.py +321 -189
  54. cloudnetpy/instruments/rpg_reader.py +74 -40
  55. cloudnetpy/instruments/toa5.py +49 -0
  56. cloudnetpy/instruments/vaisala.py +95 -343
  57. cloudnetpy/instruments/weather_station.py +774 -105
  58. cloudnetpy/metadata.py +90 -19
  59. cloudnetpy/model_evaluation/file_handler.py +55 -52
  60. cloudnetpy/model_evaluation/metadata.py +46 -20
  61. cloudnetpy/model_evaluation/model_metadata.py +1 -1
  62. cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
  63. cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
  64. cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
  65. cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
  66. cloudnetpy/model_evaluation/products/model_products.py +43 -35
  67. cloudnetpy/model_evaluation/products/observation_products.py +41 -35
  68. cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
  69. cloudnetpy/model_evaluation/products/tools.py +29 -20
  70. cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
  71. cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
  72. cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
  73. cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
  74. cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
  75. cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
  76. cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
  77. cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
  78. cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
  79. cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
  80. cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
  81. cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
  82. cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
  83. cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
  84. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
  85. cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
  86. cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
  87. cloudnetpy/model_evaluation/utils.py +2 -1
  88. cloudnetpy/output.py +170 -111
  89. cloudnetpy/plotting/__init__.py +2 -1
  90. cloudnetpy/plotting/plot_meta.py +562 -822
  91. cloudnetpy/plotting/plotting.py +1142 -704
  92. cloudnetpy/products/__init__.py +1 -0
  93. cloudnetpy/products/classification.py +370 -88
  94. cloudnetpy/products/der.py +85 -55
  95. cloudnetpy/products/drizzle.py +77 -34
  96. cloudnetpy/products/drizzle_error.py +15 -11
  97. cloudnetpy/products/drizzle_tools.py +79 -59
  98. cloudnetpy/products/epsilon.py +211 -0
  99. cloudnetpy/products/ier.py +27 -50
  100. cloudnetpy/products/iwc.py +55 -48
  101. cloudnetpy/products/lwc.py +96 -70
  102. cloudnetpy/products/mwr_tools.py +186 -0
  103. cloudnetpy/products/product_tools.py +170 -128
  104. cloudnetpy/utils.py +455 -240
  105. cloudnetpy/version.py +2 -2
  106. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
  107. cloudnetpy-1.87.3.dist-info/RECORD +127 -0
  108. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
  109. cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
  110. docs/source/conf.py +2 -2
  111. cloudnetpy/categorize/atmos.py +0 -361
  112. cloudnetpy/products/mwr_multi.py +0 -68
  113. cloudnetpy/products/mwr_single.py +0 -75
  114. cloudnetpy-1.49.9.dist-info/RECORD +0 -112
  115. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
  116. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,10 @@
1
1
  """Module for reading raw Galileo cloud radar data."""
2
+
3
+ import datetime
2
4
  import os
3
- from tempfile import TemporaryDirectory
5
+ from os import PathLike
6
+ from tempfile import NamedTemporaryFile, TemporaryDirectory
7
+ from uuid import UUID
4
8
 
5
9
  import numpy as np
6
10
 
@@ -11,12 +15,12 @@ from cloudnetpy.metadata import MetaData
11
15
 
12
16
 
13
17
  def galileo2nc(
14
- raw_files: str,
15
- output_file: str,
18
+ raw_files: str | PathLike,
19
+ output_file: str | PathLike,
16
20
  site_meta: dict,
17
- uuid: str | None = None,
18
- date: str | None = None,
19
- ) -> str:
21
+ uuid: str | UUID | None = None,
22
+ date: str | datetime.date | None = None,
23
+ ) -> UUID:
20
24
  """Converts 'Galileo' cloud radar data into Cloudnet Level 1b netCDF file.
21
25
 
22
26
  Args:
@@ -41,6 +45,10 @@ def galileo2nc(
41
45
  >>> galileo2nc('/one/day/of/galileo/files/', 'radar.nc', site_meta)
42
46
 
43
47
  """
48
+ if isinstance(date, str):
49
+ date = datetime.date.fromisoformat(date)
50
+ uuid = utils.get_uuid(uuid)
51
+
44
52
  keymap = {
45
53
  "ZED_HC": "Zh",
46
54
  "VEL_HC": "v",
@@ -55,15 +63,25 @@ def galileo2nc(
55
63
  "beamwidthH": "beamwidthH",
56
64
  }
57
65
 
66
+ nc_filename: str | PathLike
58
67
  with TemporaryDirectory() as temp_dir:
59
68
  if os.path.isdir(raw_files):
60
- nc_filename = f"{temp_dir}/tmp.nc"
61
- valid_filenames = utils.get_sorted_filenames(raw_files, ".nc")
62
- valid_filenames = utils.get_files_with_common_range(valid_filenames)
63
- variables = list(keymap.keys())
64
- concat_lib.concatenate_files(
65
- valid_filenames, nc_filename, variables=variables
66
- )
69
+ with NamedTemporaryFile(
70
+ dir=temp_dir,
71
+ suffix=".nc",
72
+ delete=False,
73
+ ) as temp_file:
74
+ nc_filename = temp_file.name
75
+ valid_filenames = utils.get_sorted_filenames(raw_files, ".nc")
76
+ valid_filenames = utils.get_files_with_variables(
77
+ valid_filenames, ["time", "ZED_HC"]
78
+ )
79
+ variables = list(keymap.keys())
80
+ concat_lib.concatenate_files(
81
+ valid_filenames,
82
+ nc_filename,
83
+ variables=variables,
84
+ )
67
85
  else:
68
86
  nc_filename = raw_files
69
87
 
@@ -74,20 +92,25 @@ def galileo2nc(
74
92
  galileo.check_date(date)
75
93
  galileo.sort_timestamps()
76
94
  galileo.remove_duplicate_timestamps()
95
+ galileo.screen_negative_altitudes()
77
96
  snr_limit = site_meta.get("snr_limit", 3)
78
97
  galileo.screen_by_snr(snr_limit=snr_limit)
79
98
  galileo.mask_clutter()
80
99
  galileo.mask_invalid_data()
81
- galileo.add_time_and_range()
82
100
  galileo.add_radar_specific_variables()
83
101
  galileo.add_nyquist_velocity(keymap)
84
102
  galileo.add_site_geolocation()
85
- valid_indices = galileo.add_zenith_and_azimuth_angles()
103
+ valid_indices = galileo.add_zenith_and_azimuth_angles(
104
+ elevation_threshold=1.1,
105
+ elevation_diff_threshold=0.1,
106
+ azimuth_diff_threshold=0.1,
107
+ )
86
108
  galileo.screen_time_indices(valid_indices)
87
109
  galileo.add_height()
110
+ galileo.test_if_all_masked()
88
111
  attributes = output.add_time_attribute(ATTRIBUTES, galileo.date)
89
112
  output.update_attributes(galileo.data, attributes)
90
- uuid = output.save_level1b(galileo, output_file, uuid)
113
+ output.save_level1b(galileo, output_file, uuid)
91
114
  return uuid
92
115
 
93
116
 
@@ -100,23 +123,42 @@ class Galileo(ChilboltonRadar):
100
123
 
101
124
  """
102
125
 
103
- def __init__(self, full_path: str, site_meta: dict):
126
+ def __init__(self, full_path: str | PathLike, site_meta: dict) -> None:
104
127
  super().__init__(full_path, site_meta)
105
128
  self.date = self._init_date()
106
129
  self.instrument = GALILEO
107
130
 
108
- def mask_clutter(self):
109
- """Masks clutter."""
131
+ def mask_clutter(self) -> None:
110
132
  # Only strong Z values are valid
111
133
  n_low_gates = 15
112
134
  ind = np.where(self.data["Zh"][:, :n_low_gates] < -15) and np.where(
113
- self.data["ldr"][:, :n_low_gates] > -5
135
+ self.data["ldr"][:, :n_low_gates] > -5,
114
136
  )
115
137
  self.data["v"].mask_indices(ind)
116
138
 
139
+ def screen_negative_altitudes(self) -> None:
140
+ range_var = self.data["range"][:]
141
+ valid_idx = np.where(range_var > 0)[0]
142
+ if valid_idx.size == 0:
143
+ msg = "No valid altitudes found."
144
+ raise ValueError(msg)
145
+ if valid_idx.size == range_var.shape[0]:
146
+ return
147
+ for arr in self.data.values():
148
+ if utils.isscalar(arr.data):
149
+ continue
150
+ if arr[:].shape[-1] == range_var.shape[0]:
151
+ arr.data = arr[..., valid_idx]
152
+
117
153
 
118
154
  ATTRIBUTES = {
119
- "antenna_diameter": MetaData(long_name="Antenna diameter", units="m"),
120
- "beamwidthV": MetaData(long_name="Vertical angular beamwidth", units="degree"),
121
- "beamwidthH": MetaData(long_name="Horizontal angular beamwidth", units="degree"),
155
+ "antenna_diameter": MetaData(
156
+ long_name="Antenna diameter", units="m", dimensions=("time",)
157
+ ),
158
+ "beamwidthV": MetaData(
159
+ long_name="Vertical angular beamwidth", units="degree", dimensions=("time",)
160
+ ),
161
+ "beamwidthH": MetaData(
162
+ long_name="Horizontal angular beamwidth", units="degree", dimensions=("time",)
163
+ ),
122
164
  }
@@ -1,21 +1,27 @@
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
6
+ from os import PathLike
5
7
  from pathlib import Path
8
+ from typing import Literal
9
+ from uuid import UUID
6
10
 
7
- import mwrpy
11
+ import mwrpy.rpg_mwr
8
12
  import netCDF4
9
13
  import numpy as np
14
+ from mwrpy.exceptions import MissingInputData
10
15
  from mwrpy.level1.lev1_meta_nc import ATTRIBUTES_1B01
16
+ from mwrpy.level1.write_lev1_nc import lev1_to_nc
11
17
  from mwrpy.version import __version__ as mwrpy_version
12
18
  from numpy import ma
13
19
 
14
20
  from cloudnetpy import output, utils
15
21
  from cloudnetpy.cloudnetarray import CloudnetArray
16
- from cloudnetpy.exceptions import ValidTimeStampError
22
+ from cloudnetpy.exceptions import HatproDataError, ValidTimeStampError
17
23
  from cloudnetpy.instruments import rpg
18
- from cloudnetpy.instruments.instruments import HATPRO
24
+ from cloudnetpy.instruments.instruments import HATPRO, LHATPRO, LHUMPRO_U90, Instrument
19
25
  from cloudnetpy.instruments.rpg_reader import (
20
26
  HatproBin,
21
27
  HatproBinCombined,
@@ -23,36 +29,83 @@ from cloudnetpy.instruments.rpg_reader import (
23
29
  HatproBinLwp,
24
30
  )
25
31
 
32
+ IType = Literal["hatpro", "lhatpro", "lhumpro_u90"]
33
+ ITYPE_MAP: dict[IType, Instrument] = {
34
+ "hatpro": HATPRO,
35
+ "lhatpro": LHATPRO,
36
+ "lhumpro_u90": LHUMPRO_U90,
37
+ }
38
+
26
39
 
27
40
  def hatpro2l1c(
28
- mwr_dir: str,
29
- output_file: str,
41
+ mwr_dir: str | PathLike,
42
+ output_file: str | PathLike,
30
43
  site_meta: dict,
31
- uuid: str | None = None,
32
- date: str | None = None,
33
- ) -> str:
44
+ instrument_type: IType = "hatpro",
45
+ lidar_file: str | PathLike | None = None,
46
+ uuid: str | UUID | None = None,
47
+ date: str | datetime.date | None = None,
48
+ ) -> UUID:
34
49
  """Converts RPG HATPRO microwave radiometer data into Cloudnet Level 1c netCDF file.
35
50
 
36
51
  Args:
37
52
  mwr_dir: Folder containing one day of HATPRO files.
38
53
  output_file: Output file name.
39
- site_meta: Dictionary containing information about the site
54
+ site_meta: Dictionary containing information about the site and instrument
55
+ instrument_type: Specific type of the RPG microwave radiometer.
56
+ lidar_file: Path to a lidar file.
40
57
  uuid: Set specific UUID for the file.
41
58
  date: Expected date in the input files.
42
59
 
43
60
  Returns:
44
61
  UUID of the generated file.
45
62
  """
46
-
47
- hatpro_raw = mwrpy.lev1_to_nc(site_meta["coeffs_dir"], "1C01", mwr_dir)
48
- hatpro = HatproL1c(hatpro_raw, site_meta)
63
+ if isinstance(date, str):
64
+ date = datetime.date.fromisoformat(date)
65
+ uuid = utils.get_uuid(uuid)
66
+
67
+ coeff_files = site_meta.get("coefficientFiles")
68
+ time_offset = site_meta.get("time_offset")
69
+
70
+ try:
71
+ hatpro_raw = lev1_to_nc(
72
+ "1C01",
73
+ str(mwr_dir),
74
+ instrument_type=instrument_type,
75
+ output_file=str(output_file),
76
+ lidar_path=lidar_file,
77
+ coeff_files=coeff_files,
78
+ instrument_config=site_meta,
79
+ date=date,
80
+ time_offset=time_offset,
81
+ )
82
+ except MissingInputData as err:
83
+ raise HatproDataError(str(err)) from err
84
+
85
+ hatpro = HatproL1c(hatpro_raw, site_meta, ITYPE_MAP[instrument_type])
86
+
87
+ flags = hatpro.data["quality_flag"][:]
88
+ bad_percentage = ma.sum(flags != 0) / flags.size * 100
89
+ if bad_percentage > 90:
90
+ msg = "More than 90% of brightness temperatures are flagged"
91
+ raise HatproDataError(msg)
49
92
 
50
93
  timestamps = hatpro.data["time"][:]
51
94
  if date is not None:
52
95
  # Screen timestamps if these assertions start to fail
53
- assert np.all(np.diff(timestamps) > 0)
54
- dates = [str(datetime.datetime.utcfromtimestamp(t).date()) for t in timestamps]
55
- assert len(set(dates)) == 1
96
+ if not np.all(np.diff(timestamps) > 0):
97
+ msg = "Timestamps are not increasing"
98
+ raise RuntimeError(msg)
99
+ dates = [
100
+ datetime.datetime.fromtimestamp(t, tz=datetime.timezone.utc).date()
101
+ for t in timestamps
102
+ ]
103
+ if len(set(dates)) != 1:
104
+ msg = f"Several dates, something is wrong: {set(dates)}"
105
+ raise RuntimeError(msg)
106
+ if date != dates[0]:
107
+ msg = f"Expected date {date}, got {dates[0]}"
108
+ raise RuntimeError(msg)
56
109
 
57
110
  decimal_hours = utils.seconds2hours(timestamps)
58
111
  hatpro.data["time"] = CloudnetArray(decimal_hours, "time", data_type="f8")
@@ -60,42 +113,55 @@ def hatpro2l1c(
60
113
  hatpro.data["t_amb"].dimensions = ("time", "t_amb_nb")
61
114
 
62
115
  for key in ("elevation_angle", "ir_elevation_angle"):
63
- zenith_angle = 90 - hatpro.data.pop(key)[:]
116
+ if key not in hatpro.data:
117
+ continue
118
+ zenith_angle = 90 - hatpro.data[key][:]
64
119
  new_key = key.replace("elevation", "zenith")
65
120
  hatpro.data[new_key] = CloudnetArray(zenith_angle, new_key)
66
121
 
67
- for key in ("latitude", "longitude", "altitude"):
68
- if key in site_meta:
69
- hatpro.data[key] = CloudnetArray(site_meta[key], key, data_type="f4")
122
+ if "ir_wavelength" in hatpro.data:
123
+ hatpro.data["ir_wavelength"].dimensions = ("ir_channel",)
124
+ if "irt" in hatpro.data:
125
+ hatpro.data["irt"].dimensions = ("time", "ir_channel")
126
+
127
+ utils.add_site_geolocation(hatpro.data, gps=True, site_meta=site_meta)
70
128
 
71
- attributes = output.add_time_attribute(ATTRIBUTES_1B01, hatpro.date)
129
+ attrs_copy = ATTRIBUTES_1B01.copy()
130
+ attributes = output.add_time_attribute(attrs_copy, hatpro.date)
72
131
  output.update_attributes(hatpro.data, attributes)
73
- uuid = output.save_level1b(hatpro, output_file, uuid)
132
+ output.save_level1b(hatpro, output_file, uuid)
74
133
  with netCDF4.Dataset(output_file, "a") as nc:
75
134
  nc.cloudnet_file_type = "mwr-l1c"
76
135
  nc.title = nc.title.replace("radiometer", "radiometer Level 1c")
77
136
  nc.mwrpy_version = mwrpy_version
78
- nc.mwrpy_coefficients = site_meta["coeffs_dir"]
79
-
137
+ nc.mwrpy_coefficients = ", ".join(site_meta["coefficientLinks"])
138
+ nc.history = nc.history.replace("mwr", "mwr-l1c")
139
+ if lidar_file is not None:
140
+ with netCDF4.Dataset(lidar_file) as lidar_nc:
141
+ nc.source = f"{nc.source}\n{lidar_nc.source}"
142
+ nc.history = f"{nc.history}\n{lidar_nc.history}"
80
143
  return uuid
81
144
 
82
145
 
83
146
  class HatproL1c:
84
- def __init__(self, hatpro: mwrpy.Rpg, site_meta: dict):
147
+ def __init__(
148
+ self, hatpro: mwrpy.rpg_mwr.Rpg, site_meta: dict, instrument: Instrument
149
+ ) -> None:
85
150
  self.raw_data = hatpro.raw_data
86
151
  self.data = hatpro.data
87
- self.date = hatpro.date.split("-")
152
+ self.date = hatpro.date
88
153
  self.site_meta = site_meta
89
- self.instrument = HATPRO
154
+ self.instrument = instrument
90
155
 
91
156
 
92
157
  def hatpro2nc(
93
- path_to_files: str,
94
- output_file: str,
158
+ path_to_files: str | PathLike,
159
+ output_file: str | PathLike,
95
160
  site_meta: dict,
96
- uuid: str | None = None,
97
- date: str | None = None,
98
- ) -> tuple[str, list]:
161
+ instrument_type: IType = "hatpro",
162
+ uuid: str | UUID | None = None,
163
+ date: str | datetime.date | None = None,
164
+ ) -> tuple[UUID, list[Path]]:
99
165
  """Converts RPG HATPRO microwave radiometer data into Cloudnet Level 1b
100
166
  netCDF file.
101
167
 
@@ -112,6 +178,7 @@ def hatpro2nc(
112
178
  - `latitude` (optional).
113
179
  - `longitude` (optional).
114
180
 
181
+ instrument_type: Specific type of the RPG microwave radiometer.
115
182
  uuid: Set specific UUID for the file.
116
183
  date: Expected date in the input files. If not set,
117
184
  all files will be used. This might cause unexpected behavior if
@@ -133,28 +200,32 @@ def hatpro2nc(
133
200
  >>> hatpro2nc('/path/to/files/', 'hatpro.nc', site_meta)
134
201
 
135
202
  """
203
+ if isinstance(date, str):
204
+ date = datetime.date.fromisoformat(date)
205
+ uuid = utils.get_uuid(uuid)
136
206
  hatpro_objects, valid_files = _get_hatpro_objects(Path(path_to_files), date)
137
- is_lwp_files = any(f.endswith(".LWP") for f in valid_files)
138
- is_iwv_files = any(f.endswith(".IWV") for f in valid_files)
207
+ is_lwp_files = any(f.suffix == ".LWP" for f in valid_files)
208
+ is_iwv_files = any(f.suffix == ".IWV" for f in valid_files)
139
209
  if not is_lwp_files:
140
210
  raise ValidTimeStampError
141
211
  if is_iwv_files:
142
212
  _add_missing_variables(hatpro_objects, ("lwp", "iwv"))
143
213
  one_day_of_data = rpg.create_one_day_data_record(hatpro_objects)
144
- hatpro = rpg.Hatpro(one_day_of_data, site_meta)
145
- hatpro.sort_timestamps()
146
- hatpro.convert_time_to_fraction_hour("float64")
214
+ hatpro = rpg.Hatpro(one_day_of_data, site_meta, ITYPE_MAP[instrument_type])
147
215
  hatpro.add_site_geolocation()
216
+ hatpro.convert_time_to_fraction_hour("float64")
217
+ hatpro.sort_timestamps()
148
218
  hatpro.remove_duplicate_timestamps()
149
219
  attributes = output.add_time_attribute({}, hatpro.date)
150
220
  output.update_attributes(hatpro.data, attributes)
151
- uuid = output.save_level1b(hatpro, output_file, uuid)
221
+ output.save_level1b(hatpro, output_file, uuid)
152
222
  return uuid, valid_files
153
223
 
154
224
 
155
225
  def _get_hatpro_objects(
156
- directory: Path, expected_date: str | None
157
- ) -> tuple[list[HatproBinCombined], list[str]]:
226
+ directory: Path,
227
+ expected_date: datetime.date | None,
228
+ ) -> tuple[list[HatproBinCombined], list[Path]]:
158
229
  objects = defaultdict(list)
159
230
  for filename in directory.iterdir():
160
231
  try:
@@ -170,40 +241,43 @@ def _get_hatpro_objects(
170
241
  if expected_date is not None:
171
242
  obj = _validate_date(obj, expected_date)
172
243
  objects[filename.stem].append(obj)
173
- except (TypeError, ValueError) as err:
174
- logging.warning(f"Ignoring file '{filename}': {err}")
244
+ except (TypeError, ValueError, ValidTimeStampError) as err:
245
+ logging.warning("Ignoring file '%s': %s", filename, err)
175
246
  continue
176
247
 
177
- valid_files: list[str] = []
248
+ valid_files: list[Path] = []
178
249
  combined_objs = []
179
250
  for _stem, objs in sorted(objects.items()):
180
251
  try:
181
252
  combined_objs.append(HatproBinCombined(objs))
182
- valid_files.extend(str(obj.filename) for obj in objs)
253
+ valid_files.extend(obj.filename for obj in objs)
183
254
  except (TypeError, ValueError) as err:
184
255
  files = "'" + "', '".join(str(obj.filename) for obj in objs) + "'"
185
- logging.warning(f"Ignoring files {files}: {err}")
256
+ logging.warning("Ignoring files %s: %s", files, err)
186
257
  continue
187
258
 
188
259
  return combined_objs, valid_files
189
260
 
190
261
 
191
- def _validate_date(obj: HatproBin, expected_date: str):
262
+ def _validate_date(obj: HatproBin, expected_date: datetime.date) -> HatproBin:
192
263
  if obj.header["_time_reference"] != 1:
193
- raise ValueError("Can not validate non-UTC dates")
264
+ msg = "Can not validate non-UTC dates"
265
+ raise ValueError(msg)
194
266
  inds = []
195
267
  for ind, timestamp in enumerate(obj.data["time"][:]):
196
- date = "-".join(utils.seconds2date(timestamp)[:3])
268
+ date = utils.seconds2date(timestamp).date()
197
269
  if date == expected_date:
198
270
  inds.append(ind)
199
271
  if not inds:
200
- raise ValueError("Timestamps not what expected")
272
+ msg = f"No valid timestamps found for date {expected_date}"
273
+ raise ValueError(msg)
201
274
  obj.data = obj.data[:][inds]
202
275
  return obj
203
276
 
204
277
 
205
278
  def _add_missing_variables(
206
- hatpro_objects: list[HatproBinCombined], keys: tuple
279
+ hatpro_objects: list[HatproBinCombined],
280
+ keys: tuple,
207
281
  ) -> list[HatproBinCombined]:
208
282
  for obj in hatpro_objects:
209
283
  for key in keys:
@@ -35,6 +35,14 @@ CL31 = Instrument(
35
35
  wavelength=910.0,
36
36
  )
37
37
 
38
+ CS135 = Instrument(
39
+ manufacturer="Campbell Scientific",
40
+ domain="lidar",
41
+ category="ceilometer",
42
+ model="CS135",
43
+ wavelength=905.0,
44
+ )
45
+
38
46
  CT25K = Instrument(
39
47
  manufacturer="Vaisala",
40
48
  domain="lidar",
@@ -67,6 +75,14 @@ CHM15KX = Instrument(
67
75
  wavelength=1064.0,
68
76
  )
69
77
 
78
+ MIRA10 = Instrument(
79
+ manufacturer="METEK",
80
+ domain="radar",
81
+ category="cloud radar",
82
+ model="MIRA-10",
83
+ frequency=9.4, # 9.2 - 9.6 GHz
84
+ )
85
+
70
86
  MIRA35 = Instrument(
71
87
  manufacturer="METEK",
72
88
  domain="radar",
@@ -108,7 +124,10 @@ FMCW35 = Instrument(
108
124
  )
109
125
 
110
126
  BASTA = Instrument(
111
- domain="radar", category="cloud radar", model="BASTA", frequency=95.0
127
+ domain="radar",
128
+ category="cloud radar",
129
+ model="BASTA",
130
+ frequency=95.0,
112
131
  )
113
132
 
114
133
  HATPRO = Instrument(
@@ -118,6 +137,20 @@ HATPRO = Instrument(
118
137
  model="HATPRO",
119
138
  )
120
139
 
140
+ LHATPRO = Instrument(
141
+ manufacturer="RPG-Radiometer Physics",
142
+ domain="mwr",
143
+ category="microwave radiometer",
144
+ model="LHATPRO",
145
+ )
146
+
147
+ LHUMPRO_U90 = Instrument(
148
+ manufacturer="RPG-Radiometer Physics",
149
+ domain="mwr",
150
+ category="microwave radiometer",
151
+ model="LHUMPRO-U90",
152
+ )
153
+
121
154
  RADIOMETRICS = Instrument(
122
155
  manufacturer="Radiometrics",
123
156
  domain="mwr",
@@ -131,6 +164,34 @@ HALO = Instrument(
131
164
  model="StreamLine",
132
165
  )
133
166
 
167
+ WINDCUBE_WLS100S = Instrument(
168
+ manufacturer="Vaisala",
169
+ domain="lidar",
170
+ category="Doppler lidar",
171
+ model="WindCube WLS100S",
172
+ )
173
+
174
+ WINDCUBE_WLS200S = Instrument(
175
+ manufacturer="Vaisala",
176
+ domain="lidar",
177
+ category="Doppler lidar",
178
+ model="WindCube WLS200S",
179
+ )
180
+
181
+ WINDCUBE_WLS400S = Instrument(
182
+ manufacturer="Vaisala",
183
+ domain="lidar",
184
+ category="Doppler lidar",
185
+ model="WindCube WLS400S",
186
+ )
187
+
188
+ WINDCUBE_WLS70 = Instrument(
189
+ manufacturer="Vaisala",
190
+ domain="lidar",
191
+ category="Doppler lidar",
192
+ model="WindCube WLS70",
193
+ )
194
+
134
195
  PARSIVEL2 = Instrument(
135
196
  manufacturer="OTT HydroMet",
136
197
  domain="disdrometer",
@@ -145,7 +206,58 @@ THIES = Instrument(
145
206
  model="LNM",
146
207
  )
147
208
 
209
+ PLUVIO2 = Instrument(
210
+ manufacturer="OTT HydroMet",
211
+ domain="rain-gauge",
212
+ category="rain-gauge",
213
+ model="Pluvio2",
214
+ )
215
+
216
+ PLUVIO2S = Instrument(
217
+ manufacturer="OTT HydroMet",
218
+ domain="rain-gauge",
219
+ category="rain-gauge",
220
+ model="Pluvio2S",
221
+ )
222
+
223
+ THIES_PT = Instrument(
224
+ manufacturer="Thies Clima",
225
+ domain="rain-gauge",
226
+ category="rain-gauge",
227
+ model="Precipitation Transmitter",
228
+ )
229
+
230
+ RAIN_E_H3 = Instrument(
231
+ manufacturer="LAMBRECHT meteo GmbH",
232
+ domain="rain-gauge",
233
+ category="rain-gauge",
234
+ model="rain[e]H3",
235
+ )
236
+
148
237
  GENERIC_WEATHER_STATION = Instrument(
149
238
  domain="weather-station",
150
239
  category="weather station",
151
240
  )
241
+
242
+ MRR_PRO = Instrument(
243
+ manufacturer="METEK",
244
+ domain="rain-radar",
245
+ category="rain radar",
246
+ model="MRR-PRO",
247
+ frequency=24.23,
248
+ )
249
+
250
+ FD12P = Instrument(
251
+ manufacturer="Vaisala",
252
+ domain="weather-station",
253
+ category="present weather sensor",
254
+ model="FD12P",
255
+ )
256
+
257
+ DA10 = Instrument(
258
+ manufacturer="Vaisala",
259
+ domain="lidar",
260
+ category="differential absorption lidar",
261
+ model="DA10",
262
+ wavelength=911.0,
263
+ )