cloudnetpy 1.82.0__py3-none-any.whl → 1.82.2__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.
@@ -115,10 +115,11 @@ def _fix_liquid_dominated_radar(
115
115
 
116
116
 
117
117
  def _is_z_missing_above_liquid(z: ma.MaskedArray, ind_top: int) -> bool:
118
- """Checks is z is masked right above the liquid layer top."""
118
+ """Checks if z is masked right above the liquid layer top."""
119
119
  if ind_top == len(z) - 1:
120
120
  return False
121
- return z.mask[ind_top + 1]
121
+ mask = ma.getmaskarray(z)
122
+ return bool(mask[ind_top + 1])
122
123
 
123
124
 
124
125
  def _is_z_increasing(z: ma.MaskedArray, ind_base: int, ind_top: int) -> bool:
@@ -76,7 +76,9 @@ def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
76
76
  ldr_prof = obs.ldr[ind, temp_indices]
77
77
  ldr_dprof = ldr_diff[ind, temp_indices]
78
78
 
79
- if ma.count(ldr_prof) > 3 or ma.count(v_prof) > 3:
79
+ if (ldr_prof is not None and ma.count(ldr_prof) > 3) or (
80
+ v_prof is not None and ma.count(v_prof) > 3
81
+ ):
80
82
  try:
81
83
  if ldr_prof is None or ldr_dprof is None:
82
84
  msg = "ldr_prof or ldr_dprof is None"
@@ -182,4 +182,4 @@ def _find_model_type(file_name: str | PathLike) -> str:
182
182
  def _find_number_of_valid_profiles(array: npt.NDArray) -> int:
183
183
  mask = ma.getmaskarray(array)
184
184
  all_masked_profiles = np.all(mask, axis=1)
185
- return np.count_nonzero(~all_masked_profiles)
185
+ return int(np.count_nonzero(~all_masked_profiles))
@@ -246,7 +246,8 @@ def _read_array_from_file_pair(
246
246
 
247
247
 
248
248
  def _only_zeros_or_masked(data: ma.MaskedArray) -> bool:
249
- return ma.sum(data) == 0 or data.mask.all()
249
+ mask = ma.getmaskarray(data)
250
+ return ma.sum(data) == 0 or mask.all()
250
251
 
251
252
 
252
253
  ATTRIBUTES = {
@@ -326,6 +326,11 @@ class HatproBinCombined:
326
326
  else:
327
327
  msg = "Only implemented up to 2 files"
328
328
  raise NotImplementedError(msg)
329
+
330
+ if arr.dtype.fields is None:
331
+ msg = "Data has no fields"
332
+ raise ValueError(msg)
333
+
329
334
  self.data = {field: arr[field] for field in arr.dtype.fields}
330
335
 
331
336
 
@@ -79,12 +79,14 @@ def ws2nc(
79
79
  ws.add_date()
80
80
  ws.add_site_geolocation()
81
81
  ws.add_data()
82
+ ws.remove_duplicate_timestamps()
82
83
  ws.convert_temperature_and_humidity()
83
84
  ws.convert_pressure()
84
85
  ws.convert_rainfall_rate()
85
86
  ws.convert_rainfall_amount()
86
87
  ws.normalize_cumulative_amount("rainfall_amount")
87
88
  ws.calculate_rainfall_amount()
89
+ ws.wrap_wind_direction()
88
90
  attributes = output.add_time_attribute({}, ws.date)
89
91
  output.update_attributes(ws.data, attributes)
90
92
  output.save_level1b(ws, output_file, uuid)
@@ -148,6 +150,16 @@ class WS(CSVFile):
148
150
  def convert_rainfall_amount(self) -> None:
149
151
  pass
150
152
 
153
+ def wrap_wind_direction(self) -> None:
154
+ if "wind_direction" not in self.data:
155
+ return
156
+ # Wrap values little outside of [0, 360), keep original values
157
+ # otherwise.
158
+ threshold = 2
159
+ values = self.data["wind_direction"].data
160
+ values[(values > -threshold) & (values < 0)] += 360
161
+ values[(values >= 360) & (values < 360 + threshold)] -= 360
162
+
151
163
 
152
164
  class PalaiseauWS(WS):
153
165
  def __init__(self, filenames: Sequence[str | PathLike], site_meta: dict) -> None:
cloudnetpy/output.py CHANGED
@@ -197,46 +197,52 @@ def get_source_uuids(data: Observations | list[netCDF4.Dataset | DataSource]) ->
197
197
  def merge_history(
198
198
  nc: netCDF4.Dataset, file_type: str, data: Observations | DataSource
199
199
  ) -> None:
200
- """Merges history fields from one or several files and creates a new record.
200
+ """Merges history fields from one or several files and creates a new record."""
201
+
202
+ def extract_history(obj: DataSource | Observations) -> list[str]:
203
+ if hasattr(obj, "dataset") and hasattr(obj.dataset, "history"):
204
+ history = obj.dataset.history
205
+ if isinstance(obj, Model):
206
+ return [history.split("\n")[-1]]
207
+ return history.split("\n")
208
+ return []
209
+
210
+ histories: list[str] = []
211
+ if isinstance(data, DataSource):
212
+ histories.extend(extract_history(data))
213
+ elif isinstance(data, Observations):
214
+ for field in fields(data):
215
+ histories.extend(extract_history(getattr(data, field.name)))
201
216
 
202
- Args:
203
- nc: The netCDF Dataset instance.
204
- file_type: Long description of the file.
205
- data: Dictionary of objects with history attribute.
217
+ # Remove duplicates
218
+ histories = list(dict.fromkeys(histories))
206
219
 
207
- """
220
+ def parse_time(line: str) -> datetime.datetime:
221
+ try:
222
+ return datetime.datetime.strptime(
223
+ line.split(" - ")[0].strip(), "%Y-%m-%d %H:%M:%S %z"
224
+ )
225
+ except ValueError:
226
+ return datetime.datetime.min.replace(
227
+ tzinfo=datetime.timezone.utc
228
+ ) # malformed lines to bottom
229
+
230
+ histories.sort(key=parse_time, reverse=True)
208
231
  new_record = f"{utils.get_time()} - {file_type} file created"
209
- histories = []
210
- if (
211
- isinstance(data, DataSource)
212
- and hasattr(data, "dataset")
213
- and hasattr(data.dataset, "history")
214
- ):
215
- history = data.dataset.history
216
- histories.append(history)
217
- if isinstance(data, Observations):
218
- for field in fields(data):
219
- obj = getattr(data, field.name)
220
- if hasattr(obj, "dataset") and hasattr(obj.dataset, "history"):
221
- history = obj.dataset.history
222
- history = history.split("\n")[-1] if isinstance(obj, Model) else history
223
- histories.append(history)
224
- histories.sort(reverse=True)
225
- old_history = [f"\n{history}" for history in histories]
226
- old_history_str = "".join(old_history)
227
- nc.history = f"{new_record}{old_history_str}"
232
+ nc.history = new_record + "".join(f"\n{h}" for h in histories)
228
233
 
229
234
 
230
235
  def add_source_instruments(nc: netCDF4.Dataset, data: Observations) -> None:
231
236
  """Adds source attribute to categorize file."""
232
- sources = []
233
- for field in fields(data):
234
- obj = getattr(data, field.name)
235
- if hasattr(obj, "source"):
236
- sources.append(obj.source)
237
+ sources = {
238
+ src
239
+ for field in fields(data)
240
+ for obj in [getattr(data, field.name)]
241
+ if hasattr(obj, "source")
242
+ for src in obj.source.split("\n")
243
+ }
237
244
  if sources:
238
- formatted_sources = [sources[0]] + [f"\n{source}" for source in sources[1:]]
239
- nc.source = "".join(formatted_sources)
245
+ nc.source = "\n".join(sorted(sources))
240
246
 
241
247
 
242
248
  def init_file(
@@ -137,6 +137,11 @@ def _horizontal_wind_from_doppler_lidar_file(
137
137
  raise ValidTimeStampError
138
138
  t = np.broadcast_to(time[:, None], mask.shape)[~mask]
139
139
  h = np.broadcast_to(height[None, :], mask.shape)[~mask]
140
+
141
+ if len(np.unique(t)) < 2 or len(np.unique(h)) < 2:
142
+ msg = "Not enough unique values for interpolation"
143
+ raise ValidTimeStampError(msg)
144
+
140
145
  interp_linear = LinearNDInterpolator(list(zip(t, h, strict=False)), V[~mask])
141
146
  interp_nearest = NearestNDInterpolator(list(zip(t, h, strict=False)), V[~mask])
142
147
  T, H = np.meshgrid(time, height, indexing="ij")
cloudnetpy/utils.py CHANGED
@@ -371,7 +371,7 @@ def interpolate_2d_mask(
371
371
  method="linear",
372
372
  )
373
373
  # Preserve mask:
374
- mask_fun = RectBivariateSpline(x, y, z.mask[:], kx=1, ky=1)
374
+ mask_fun = RectBivariateSpline(x, y, ma.getmaskarray(z), kx=1, ky=1)
375
375
  mask = mask_fun(x_new, y_new)
376
376
  mask[mask < 0.5] = 0
377
377
  masked_array = ma.array(data, mask=mask.astype(bool))
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 82
3
- PATCH = 0
3
+ PATCH = 2
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.82.0
3
+ Version: 1.82.2
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -6,10 +6,10 @@ cloudnetpy/constants.py,sha256=YnoSzZm35NDooJfhlulSJBc7g0eSchT3yGytRaTaJEI,845
6
6
  cloudnetpy/datasource.py,sha256=HzvqTTHLCH9GniUsV_IWwyrvvONnFJh0tmBM61hsqxM,6364
7
7
  cloudnetpy/exceptions.py,sha256=ZB3aUwjVRznR0CcZ5sZHrB0yz13URDf52Ksv7G7C7EA,1817
8
8
  cloudnetpy/metadata.py,sha256=CFpXmdEkVPzvLPv2xHIR-aMMQ-TR26KfESYw-98j7sk,7213
9
- cloudnetpy/output.py,sha256=bUp13wv5TVtfZ-wBPU_n2qvWZa-PviozrVUhJnonbYE,14830
9
+ cloudnetpy/output.py,sha256=0bybnILsgKHWIuw2GYkqTz2iMCJDZLUN25IQ9o_v3Cg,14968
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- cloudnetpy/utils.py,sha256=7PHfJo9iLMdePwEApLfYH4XiVC9DhlFQMdQTxesZylA,31797
12
- cloudnetpy/version.py,sha256=go0Zq4vIkrnCH45JYO1gxm5LNSQAgp0clDMvI3GQL7g,72
11
+ cloudnetpy/utils.py,sha256=Qv60_vxknB3f2S3EFtyoD2CBY3N6mgDRObNp2u1oYUc,31806
12
+ cloudnetpy/version.py,sha256=HExRLf0gQwTK6vqJy9VmjjceEsHaQ0syCstlLDhyoVE,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=gtvzWr0IDRn2oA6yHBvinEhTGTuub-JkrOv93lBsgrE,61
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=uWc9TABVYPI0sn4H5Az9Jf6NVRaWyEKIi17f0pAJQxE,10679
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
@@ -18,13 +18,13 @@ cloudnetpy/categorize/classify.py,sha256=skA9K6Bxh9mFZ_fM4d78zt09BPDzfHLttXle6mF
18
18
  cloudnetpy/categorize/containers.py,sha256=PIJwgQos3CxF9BG4hBNLTaZq252FTH0kdgagT31mFmc,5517
19
19
  cloudnetpy/categorize/disdrometer.py,sha256=XNL8kDtBAB12UmgRZ4ayxuVs8e3ghyLO0Hy5XpRgfMU,1966
20
20
  cloudnetpy/categorize/droplet.py,sha256=wnMN9rHNSMZLXNXuYEd-RAS_8eAIIo2vkE7pp3DSTKs,8725
21
- cloudnetpy/categorize/falling.py,sha256=Ykbl0dIaXHXgTd9wWUsw958qUxETCW_PfjtZcyWqHFA,4441
21
+ cloudnetpy/categorize/falling.py,sha256=ZscFGFPFz_Nlc7PwjVwFDSVOzMGOuCDVAFj7pLvYctQ,4475
22
22
  cloudnetpy/categorize/freezing.py,sha256=gigqpb4qfeQSlKXkrPUwCbMnMsxl74thJWSRW2iHJOg,3796
23
23
  cloudnetpy/categorize/insects.py,sha256=bAqm4kFRtU16RPttsRLedofPd-yfbALNqz26jKlMNUE,5357
24
24
  cloudnetpy/categorize/itu.py,sha256=ffXK27guyRS4d66VWQ2h4UEGjUIhGjPKbFmj7kh698c,10304
25
25
  cloudnetpy/categorize/lidar.py,sha256=CQsDEeQYiuQCfCmJQWrqQvCfmciN1NPZ6uRdt89CZLY,2685
26
- cloudnetpy/categorize/melting.py,sha256=CMBVRv9hJWjaEeHtuYOdCseyKIZeaA19hR0_q6C3cng,6255
27
- cloudnetpy/categorize/model.py,sha256=DcGLw8iSnYWAerW5X6BXHypN1jcmUD635pEMeQ-Hb84,6800
26
+ cloudnetpy/categorize/melting.py,sha256=vhc6zq3L4gp7oEPHMnQlT2m6YBE5-CS5gdTNA7gVHRg,6329
27
+ cloudnetpy/categorize/model.py,sha256=iFakrXy3npbg4qUrpUGEBEdwBnmlWsMgogPCtfWl7sw,6805
28
28
  cloudnetpy/categorize/mwr.py,sha256=kzSivQuKrsqmFInDLlSM1R2wAG5j-tQebOi_1IwUW_I,1690
29
29
  cloudnetpy/categorize/radar.py,sha256=2mTDa9BLxQeaORm-YPQ1lJyjAKew6NYzjtUvjpIvBYU,16044
30
30
  cloudnetpy/categorize/attenuations/__init__.py,sha256=kIyQEZ6VVO6jJOAndrt7jNU15pm0Cavh5GnDjFmIG1M,1040
@@ -49,14 +49,14 @@ cloudnetpy/instruments/mira.py,sha256=XqmbytpeCJ2-hNugxdsXSBUDB8SAUc97_6lo5mHFG8
49
49
  cloudnetpy/instruments/mrr.py,sha256=z50VYLOBW2o7enU7FHZYNFQRW2goEQpeGe7-iCBRQtg,6020
50
50
  cloudnetpy/instruments/nc_lidar.py,sha256=PtZauDdI3bX3bv4gIVvV6N53e2Co-ehBL_tByHM9hj8,1713
51
51
  cloudnetpy/instruments/nc_radar.py,sha256=NKsy0mF2Tdem0lNIYgd3Kbe2dOE-38t4f_rosdhBcy8,7368
52
- cloudnetpy/instruments/pollyxt.py,sha256=Xo2pYjqGxJbsUgStTnXNir4dIOGztU-G4RH9-NV5Olw,10538
52
+ cloudnetpy/instruments/pollyxt.py,sha256=IFq_RJrhgJ79OVyuo48PwYQK_zZ6VZFB_S5bEirRyzs,10566
53
53
  cloudnetpy/instruments/radiometrics.py,sha256=QKfnrZlQ0sFcFjmv1ShnCMTJQv64w4akjK-JAIY4gCg,16116
54
54
  cloudnetpy/instruments/rain_e_h3.py,sha256=fjv3SgeUNx9GisYqLrBnX9AjnO17VtouyoPh12VE9uo,5465
55
55
  cloudnetpy/instruments/rpg.py,sha256=R1rUdeSADvB1IMkGOF1S0rUEJDGEI_19SPrmErZpn5M,18825
56
- cloudnetpy/instruments/rpg_reader.py,sha256=VbF5MN94Bmxo6DTDoUUdRd7s-S1YmvFmum4ztc7KN2g,11539
56
+ cloudnetpy/instruments/rpg_reader.py,sha256=NaOtTxXx20PozNTj1xNvmbsEsAxuplFXRzBiM1_-Zg4,11651
57
57
  cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
58
58
  cloudnetpy/instruments/vaisala.py,sha256=tu7aljkMKep0uCWz-Sd-GuBXF_Yy421a4nHy0ffpMoc,4725
59
- cloudnetpy/instruments/weather_station.py,sha256=dRI5iN6I6lZ3zNeX4hgJly2qaieDQKCRs50bOkcGL5k,27119
59
+ cloudnetpy/instruments/weather_station.py,sha256=FuaGILEkd4MxXMpLrNGXNUjuuTkMIBf-J7y9oepIsdM,27586
60
60
  cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
61
61
  cloudnetpy/instruments/disdrometer/common.py,sha256=WCPRCfAlElUzZpllOSjjWrLG2jgkiRIy0rWz_omFoJQ,10815
62
62
  cloudnetpy/instruments/disdrometer/parsivel.py,sha256=1HIA52f1nGOvSd4SSTr2y3-JT3eKZWwdbMnIMRVvQ_U,25811
@@ -110,17 +110,17 @@ cloudnetpy/products/der.py,sha256=UXdAxmmwChVVWSI4QSGAXphfMnbymGRTtGdKWEvh-J4,13
110
110
  cloudnetpy/products/drizzle.py,sha256=0h1N_WVjC2GgIkAN-4ydOwl7WJn3psxeqmPHfX8WHhQ,11935
111
111
  cloudnetpy/products/drizzle_error.py,sha256=QN98Io9UsBoEYxKBqfwoS88OGBiK5U5RYnVQjyTWHCI,6220
112
112
  cloudnetpy/products/drizzle_tools.py,sha256=xYMB8Qxp-_wKzMv_XC6u6iMfRnEhEtmDpCHSQAbDToo,11201
113
- cloudnetpy/products/epsilon.py,sha256=ctD5BBuyM69y3YvBCkEqt2HsZ6JiX3cePT8fiuogWmY,7763
113
+ cloudnetpy/products/epsilon.py,sha256=a796W_OuHxbSiG7yL2pcTArnaMNwYG8eAA8sQ93dIrY,7930
114
114
  cloudnetpy/products/ier.py,sha256=Eb5AK-6l5mN_7vWP1cxcXQzj886zAwDDsHXueUju0N0,6262
115
115
  cloudnetpy/products/iwc.py,sha256=pXl0xOFDD6AzGaAp_GzD2yapjOc7hXKTno9Q5G6HCOo,9826
116
116
  cloudnetpy/products/lwc.py,sha256=xsNiiG6dGKIkWaFk0xWTabc1bZ4ULf6SqcqHs7itAUk,19339
117
117
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
118
118
  cloudnetpy/products/mwr_tools.py,sha256=MMWnp68U7bv157-CPB2VeTQvaR6zl7sexbBT_kJ_pn8,6734
119
119
  cloudnetpy/products/product_tools.py,sha256=eyqIw_0KhlpmmYQE69RpGdRIAOW7JVPlEgkTBp2kdps,11302
120
- cloudnetpy-1.82.0.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
+ cloudnetpy-1.82.2.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
121
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
122
- cloudnetpy-1.82.0.dist-info/METADATA,sha256=psXUo1YNPF33S6X7i1WnEGuaMY5V1XZlYJTguJl9-0I,5836
123
- cloudnetpy-1.82.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
- cloudnetpy-1.82.0.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
- cloudnetpy-1.82.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
- cloudnetpy-1.82.0.dist-info/RECORD,,
122
+ cloudnetpy-1.82.2.dist-info/METADATA,sha256=HJ72wMIwKafUkOwTO_Mt8kM3k-9mHdtFzjFMgeWm9vg,5836
123
+ cloudnetpy-1.82.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
+ cloudnetpy-1.82.2.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
+ cloudnetpy-1.82.2.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
+ cloudnetpy-1.82.2.dist-info/RECORD,,