ctao-calibpipe 0.1.0rc7__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.

Potentially problematic release.


This version of ctao-calibpipe might be problematic. Click here for more details.

Files changed (93) hide show
  1. calibpipe/__init__.py +5 -0
  2. calibpipe/_dev_version/__init__.py +9 -0
  3. calibpipe/_version.py +21 -0
  4. calibpipe/atmosphere/__init__.py +1 -0
  5. calibpipe/atmosphere/atmosphere_containers.py +109 -0
  6. calibpipe/atmosphere/meteo_data_handlers.py +485 -0
  7. calibpipe/atmosphere/models/README.md +14 -0
  8. calibpipe/atmosphere/models/__init__.py +1 -0
  9. calibpipe/atmosphere/models/macobac.ecsv +23 -0
  10. calibpipe/atmosphere/models/reference_MDPs/__init__.py +1 -0
  11. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_intermediate.ecsv +8 -0
  12. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_summer.ecsv +8 -0
  13. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_winter.ecsv +8 -0
  14. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_summer.ecsv +8 -0
  15. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_winter.ecsv +8 -0
  16. calibpipe/atmosphere/models/reference_atmospheres/__init__.py +1 -0
  17. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_intermediate.ecsv +73 -0
  18. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_summer.ecsv +73 -0
  19. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_winter.ecsv +73 -0
  20. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_summer.ecsv +73 -0
  21. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_winter.ecsv +73 -0
  22. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/__init__.py +1 -0
  23. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_intermediate.ecsv +857 -0
  24. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_summer.ecsv +857 -0
  25. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_winter.ecsv +857 -0
  26. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_summer.ecsv +857 -0
  27. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_winter.ecsv +857 -0
  28. calibpipe/atmosphere/templates/request_templates/__init__.py +1 -0
  29. calibpipe/atmosphere/templates/request_templates/copernicus.json +11 -0
  30. calibpipe/atmosphere/templates/request_templates/gdas.json +12 -0
  31. calibpipe/core/__init__.py +39 -0
  32. calibpipe/core/common_metadata_containers.py +195 -0
  33. calibpipe/core/exceptions.py +87 -0
  34. calibpipe/database/__init__.py +24 -0
  35. calibpipe/database/adapter/__init__.py +23 -0
  36. calibpipe/database/adapter/adapter.py +80 -0
  37. calibpipe/database/adapter/database_containers/__init__.py +61 -0
  38. calibpipe/database/adapter/database_containers/atmosphere.py +199 -0
  39. calibpipe/database/adapter/database_containers/common_metadata.py +148 -0
  40. calibpipe/database/adapter/database_containers/container_map.py +59 -0
  41. calibpipe/database/adapter/database_containers/observatory.py +61 -0
  42. calibpipe/database/adapter/database_containers/table_version_manager.py +39 -0
  43. calibpipe/database/adapter/database_containers/version_control.py +17 -0
  44. calibpipe/database/connections/__init__.py +28 -0
  45. calibpipe/database/connections/calibpipe_database.py +60 -0
  46. calibpipe/database/connections/postgres_utils.py +97 -0
  47. calibpipe/database/connections/sql_connection.py +103 -0
  48. calibpipe/database/connections/user_confirmation.py +19 -0
  49. calibpipe/database/interfaces/__init__.py +71 -0
  50. calibpipe/database/interfaces/hashable_row_data.py +54 -0
  51. calibpipe/database/interfaces/queries.py +180 -0
  52. calibpipe/database/interfaces/sql_column_info.py +67 -0
  53. calibpipe/database/interfaces/sql_metadata.py +6 -0
  54. calibpipe/database/interfaces/sql_table_info.py +131 -0
  55. calibpipe/database/interfaces/table_handler.py +351 -0
  56. calibpipe/database/interfaces/types.py +96 -0
  57. calibpipe/tests/data/atmosphere/molecular_atmosphere/__init__.py +0 -0
  58. calibpipe/tests/data/atmosphere/molecular_atmosphere/contemporary_MDP.ecsv +34 -0
  59. calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.csv +852 -0
  60. calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.ecsv +23 -0
  61. calibpipe/tests/data/atmosphere/molecular_atmosphere/merged_file.ecsv +1082 -0
  62. calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_copernicus.ecsv +1082 -0
  63. calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_gdas.ecsv +66 -0
  64. calibpipe/tests/data/atmosphere/molecular_atmosphere/observatory_configurations.json +71 -0
  65. calibpipe/tests/data/utils/__init__.py +0 -0
  66. calibpipe/tests/data/utils/meteo_data_winter_and_summer.ecsv +12992 -0
  67. calibpipe/tests/unittests/atmosphere/astral_testing.py +107 -0
  68. calibpipe/tests/unittests/atmosphere/test_meteo_data_handler.py +775 -0
  69. calibpipe/tests/unittests/atmosphere/test_molecular_atmosphere.py +327 -0
  70. calibpipe/tests/unittests/database/test_table_handler.py +66 -0
  71. calibpipe/tests/unittests/database/test_types.py +38 -0
  72. calibpipe/tests/unittests/test_bootstrap_db.py +79 -0
  73. calibpipe/tests/unittests/utils/test_observatory.py +309 -0
  74. calibpipe/tools/atmospheric_base_tool.py +78 -0
  75. calibpipe/tools/atmospheric_model_db_loader.py +181 -0
  76. calibpipe/tools/basic_tool_with_db.py +38 -0
  77. calibpipe/tools/contemporary_mdp_producer.py +87 -0
  78. calibpipe/tools/init_db.py +37 -0
  79. calibpipe/tools/macobac_calculator.py +82 -0
  80. calibpipe/tools/molecular_atmospheric_model_producer.py +197 -0
  81. calibpipe/tools/observatory_data_db_loader.py +71 -0
  82. calibpipe/tools/reference_atmospheric_model_selector.py +201 -0
  83. calibpipe/utils/__init__.py +10 -0
  84. calibpipe/utils/observatory.py +486 -0
  85. calibpipe/utils/observatory_containers.py +26 -0
  86. calibpipe/version.py +24 -0
  87. ctao_calibpipe-0.1.0rc7.dist-info/METADATA +86 -0
  88. ctao_calibpipe-0.1.0rc7.dist-info/RECORD +93 -0
  89. ctao_calibpipe-0.1.0rc7.dist-info/WHEEL +5 -0
  90. ctao_calibpipe-0.1.0rc7.dist-info/entry_points.txt +8 -0
  91. ctao_calibpipe-0.1.0rc7.dist-info/licenses/AUTHORS.md +13 -0
  92. ctao_calibpipe-0.1.0rc7.dist-info/licenses/LICENSE +21 -0
  93. ctao_calibpipe-0.1.0rc7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,486 @@
1
+ """Utility module to manage observatory data."""
2
+
3
+ # Python built-in imports
4
+ from calendar import month_abbr, monthrange
5
+ from datetime import date, datetime, timedelta
6
+ from enum import Enum
7
+ from functools import cached_property
8
+
9
+ # Third-party imports
10
+ import astral
11
+ import astropy.units as u
12
+ from astral.sun import sun
13
+ from astropy.coordinates import Latitude, Longitude
14
+
15
+ # CTA-related imports
16
+ from ctapipe.core.component import Component
17
+ from ctapipe.core.traits import (
18
+ CaselessStrEnum,
19
+ Float,
20
+ Int,
21
+ List,
22
+ )
23
+ from traitlets.config import Config
24
+
25
+ # Internal imports
26
+ from .observatory_containers import ObservatoryContainer, SeasonContainer
27
+
28
+
29
+ class SeasonAlias(Enum):
30
+ """Seasons aliases."""
31
+
32
+ SUMMER = "SUMMER"
33
+ WINTER = "WINTER"
34
+ SPRING = "INTERMEDIATE"
35
+ FALL = "INTERMEDIATE"
36
+ INTERMEDIATE = "INTERMEDIATE"
37
+
38
+
39
+ class Season(Component):
40
+ """Class, describing nature seasons."""
41
+
42
+ name = CaselessStrEnum(
43
+ values=["SPRING", "SUMMER", "FALL", "WINTER", "INTERMEDIATE"],
44
+ help="Season name (e.g. summer)",
45
+ ).tag(config=True)
46
+ start_month = Int(help="Start month of the season", allow_none=False).tag(
47
+ config=True
48
+ )
49
+ stop_month = Int(help="Stop month of the season", allow_none=False).tag(config=True)
50
+ start_day = Int(
51
+ default_value=None, help="Start day of the season", allow_none=True
52
+ ).tag(config=True)
53
+ stop_day = Int(
54
+ default_value=None, help="Stop day of the season", allow_none=True
55
+ ).tag(config=True)
56
+
57
+ _leap_year = (
58
+ 2000 # Arbitrary leap year. Do not change unless you know what you're doing.
59
+ )
60
+
61
+ def __init__(self, config=None, parent=None, **kwargs):
62
+ super().__init__(config=config, parent=parent, **kwargs)
63
+ self.name = self.name.upper()
64
+ start = date(
65
+ year=self._leap_year, month=self.start_month, day=self.start_day or 1
66
+ )
67
+ stop = date(
68
+ year=self._leap_year,
69
+ month=self.stop_month,
70
+ day=self.stop_day or monthrange(self._leap_year, self.stop_month)[1],
71
+ )
72
+ if start > stop:
73
+ self.log.debug(
74
+ "Season %s start date (%s) is greater than its stop date (%s), "
75
+ "rolling back start date year...",
76
+ self.name,
77
+ start,
78
+ stop,
79
+ )
80
+ try:
81
+ start = start.replace(year=start.year - 1)
82
+ except ValueError:
83
+ self.log.warning(
84
+ "29/02 is used as the season start date, changing it to 28/02..."
85
+ )
86
+ start = start.replace(year=start.year - 1, day=start.day - 1)
87
+ self._months = list(range(self.start_month, 13)) + list(
88
+ range(1, self.stop_month + 1)
89
+ )
90
+ self._carry_on = True
91
+ else:
92
+ self._months = list(range(self.start_month, self.stop_month + 1))
93
+ self._carry_on = False
94
+ self._start = start
95
+ self._stop = stop
96
+
97
+ @staticmethod
98
+ def cfg_from_record(record):
99
+ """Return configuration dictionary from the DB record."""
100
+ return {
101
+ "Season": {
102
+ "name": record["name"],
103
+ "start_month": record["start"].month,
104
+ "start_day": record["start"].day,
105
+ "stop_month": record["stop"].month,
106
+ "stop_day": record["stop"].day,
107
+ }
108
+ }
109
+
110
+ @classmethod
111
+ def from_record(cls, record):
112
+ """Create Season object from the DB record or container ``as_dict()`` representation.
113
+
114
+ Parameters
115
+ ----------
116
+ record : dict
117
+ Dictionary representation of a SeasonContainer.
118
+ """
119
+ return cls(config=Config(cls.cfg_from_record(record)))
120
+
121
+ def __contains__(self, timestamp):
122
+ """Check whether a timestamp is within a season."""
123
+ if isinstance(timestamp, datetime):
124
+ timestamp = timestamp.date()
125
+ cast_date = timestamp.replace(year=self._leap_year)
126
+ if self._carry_on:
127
+ if (cast_date.month == 2) and (cast_date.day == 29):
128
+ cast_date_start = cast_date.replace(
129
+ year=self._leap_year - 1, day=cast_date.day - 1
130
+ )
131
+ else:
132
+ cast_date_start = cast_date.replace(year=self._leap_year - 1)
133
+ return self._start <= cast_date_start or cast_date <= self._stop
134
+ return self._start <= cast_date <= self._stop
135
+
136
+ def __repr__(self):
137
+ """Return a string representation of the season."""
138
+ return (
139
+ f"Season {self.name}: "
140
+ f"from {month_abbr[self.start[0]]}, {self.start[1]} "
141
+ f"to {month_abbr[self.stop[0]]}, {self.stop[1]}."
142
+ )
143
+
144
+ @property
145
+ def start(self):
146
+ """
147
+ Start of the season.
148
+
149
+ Returns
150
+ -------
151
+ tuple(int, int)
152
+ Season start (month, day).
153
+ """
154
+ return (self._start.month, self._start.day)
155
+
156
+ @property
157
+ def stop(self):
158
+ """
159
+ End of the season.
160
+
161
+ Returns
162
+ -------
163
+ tuple(int, int)
164
+ Season stop (month, day).
165
+ """
166
+ return (self._stop.month, self._stop.day)
167
+
168
+ @property
169
+ def months(self):
170
+ """
171
+ List of months in the season.
172
+
173
+ Returns
174
+ -------
175
+ list(int)
176
+ List of month numbers in the season.
177
+ """
178
+ return self._months
179
+
180
+ @property
181
+ def reference_dates(self):
182
+ """
183
+ Reference season start and stop dates based on internal leap year.
184
+
185
+ Returns
186
+ -------
187
+ tuple(date, date)
188
+ Tuple of datetime.date objects (start, stop).
189
+ """
190
+ return (self._start, self._stop)
191
+
192
+ def container(self, observatory_name, observatory_version):
193
+ """
194
+ Season container.
195
+
196
+ Parameters
197
+ ----------
198
+ observatory_name : str
199
+ Name of the observatory, to which the season belongs.
200
+ observatory_version : int
201
+ Version of the observatory, to which the season belongs.
202
+
203
+ Returns
204
+ -------
205
+ SeasonContainer
206
+ """
207
+ season_container = SeasonContainer(
208
+ start=self.reference_dates[0],
209
+ stop=self.reference_dates[1],
210
+ name=self.name,
211
+ alias=SeasonAlias[self.name.upper()].value,
212
+ name_Observatory=observatory_name,
213
+ version_Observatory=observatory_version,
214
+ )
215
+ season_container.validate()
216
+ return season_container
217
+
218
+
219
+ class Observatory(Component):
220
+ """Class, defining an observatory object."""
221
+
222
+ name = CaselessStrEnum(
223
+ values=["CTAO-NORTH", "CTAO-SOUTH"],
224
+ default_value="CTAO-NORTH",
225
+ help="Observatory name",
226
+ ).tag(config=True)
227
+ latitude = Float(
228
+ default_value=28.7636, help="Observatory latitude in degrees", allow_none=False
229
+ ).tag(config=True)
230
+ longitude = Float(
231
+ default_value=17.8947, help="Observatory longitude in degrees", allow_none=False
232
+ ).tag(config=True)
233
+ elevation = Int(
234
+ default_value=2158, help="Observatory elevation in meters", allow_none=False
235
+ ).tag(config=True)
236
+ seasons = List(help="Observatory meteorological seasons", minlen=2).tag(config=True)
237
+ version = Int(default_value=1, help="Observatory configuration version").tag(
238
+ config=True
239
+ )
240
+
241
+ def __init__(self, config=None, parent=None, **kwargs):
242
+ super().__init__(config=config, parent=parent, **kwargs)
243
+ self.name = self.name.upper()
244
+ self._longitude = Longitude(
245
+ angle=self.longitude, unit=u.deg, wrap_angle=180 * u.deg
246
+ )
247
+ self._latitude = Latitude(angle=self.latitude, unit=u.deg)
248
+ self._elevation = self.elevation * u.m
249
+ self._seasons = [
250
+ Season(config=Config(key))
251
+ for key in self.get_current_config()["Observatory"]["seasons"]
252
+ ]
253
+ self.__check_seasons()
254
+ self.seasons_dict = {season.name: season for season in self._seasons}
255
+
256
+ @classmethod
257
+ def from_db(cls, database_configuration, site, version):
258
+ """Create Observatory object from the DB record."""
259
+ from ..database.connections import CalibPipeDatabase
260
+ from ..database.interfaces import TableHandler
261
+
262
+ with CalibPipeDatabase(
263
+ **database_configuration,
264
+ ) as connection:
265
+ observatory_table = TableHandler.read_table_from_database(
266
+ ObservatoryContainer,
267
+ connection,
268
+ condition=f"(c.name == '{site.upper()}') & (c.version == {version})",
269
+ )
270
+ if len(observatory_table) == 0:
271
+ raise ValueError(
272
+ f"There's no DB record for observatory {site} v{version}"
273
+ )
274
+ if len(observatory_table) > 1:
275
+ raise ValueError(
276
+ f"There're multiple DB records for observatory {site} v{version}"
277
+ )
278
+ seasons_table = TableHandler.read_table_from_database(
279
+ SeasonContainer,
280
+ connection,
281
+ condition=f"(c.name_Observatory == '{site.upper()}') & (c.version_Observatory == {version})",
282
+ )
283
+ observatory_record = observatory_table.to_pandas().to_dict(orient="records")[0]
284
+ seasons_records = seasons_table.to_pandas().to_dict(orient="records")
285
+ seasons_cfg = [Season.cfg_from_record(rec) for rec in seasons_records]
286
+ config_dict = {
287
+ "Observatory": {
288
+ "name": observatory_record["name"],
289
+ "latitude": observatory_record["latitude"],
290
+ "longitude": observatory_record["longitude"],
291
+ "elevation": int(observatory_record["elevation"]),
292
+ "seasons": seasons_cfg,
293
+ }
294
+ }
295
+
296
+ return cls(config=Config(config_dict))
297
+
298
+ def __check_seasons(self):
299
+ """
300
+ Check if provided seasons provide no-gaps and no-overlaps full coverage of a year.
301
+
302
+ Raises
303
+ ------
304
+ ValueError
305
+ If the seasons overlap, if there's a gap between the seasons or they don't cover a year.
306
+ """
307
+ dates = sorted(
308
+ [season.reference_dates for season in self._seasons], key=lambda x: x[0]
309
+ )
310
+ # check that there's one year between the first start and last end
311
+ if dates[0][0] + timedelta(days=365) != dates[-1][1]:
312
+ raise ValueError("The seasons don't cover a year")
313
+ diffs = [j[0] - i[1] for i, j in zip(dates[:-1], dates[1:])]
314
+ if not all(x == timedelta(days=1) for x in diffs):
315
+ raise ValueError("The season coverage has gaps or overlaps!")
316
+
317
+ @property
318
+ def coordinates(self):
319
+ """
320
+ Observatory coordinates.
321
+
322
+ Returns
323
+ -------
324
+ astropy.coordinates.Latitude
325
+ Observatory's latitude.
326
+ astropy.coordinates.Longitude
327
+ Observatory's longitude.
328
+ """
329
+ return self._latitude, self._longitude
330
+
331
+ @cached_property
332
+ def containers(self):
333
+ """
334
+ Observatory containers.
335
+
336
+ Returns
337
+ -------
338
+ tuple(ObservatoryContainer, SeasonContainer)
339
+ Containers with observatory and season configuration data
340
+ used to store the observatory configuration in the DB.
341
+ """
342
+ obs_container = ObservatoryContainer(
343
+ name=self.name,
344
+ latitude=self._latitude,
345
+ longitude=self._longitude,
346
+ elevation=self._elevation,
347
+ version=self.version,
348
+ )
349
+ obs_container.validate()
350
+ season_containers = [
351
+ season.container(self.name, self.version) for season in self._seasons
352
+ ]
353
+ return (obs_container, *season_containers)
354
+
355
+ def select_season_data(self, data, season_name):
356
+ """
357
+ Select data that belongs to a given season.
358
+
359
+ Parameters
360
+ ----------
361
+ data : astropy.table.Table
362
+ Astropy table with meteorological data. Must contain 'Timestamp' column with ``astropy.time.Time``
363
+ season_name : str
364
+ Season name.
365
+
366
+ Returns
367
+ -------
368
+ astropy.table.Table
369
+ Selected data table according to provided season.
370
+ """
371
+ if season_name.upper() not in self.seasons_dict.keys():
372
+ self.log.error(
373
+ "Requested season (%s) is not defined for the observatory %s\n"
374
+ "%s's seasons:\n%s",
375
+ season_name,
376
+ self.name,
377
+ self.name,
378
+ self.seasons_dict.keys(),
379
+ )
380
+ raise RuntimeError(
381
+ f"{season_name} is not present in {self.name}'s seasons."
382
+ )
383
+ mask = [
384
+ ts.date() in self.seasons_dict[season_name.upper()]
385
+ for ts in data["Timestamp"].tt.datetime
386
+ ]
387
+ return data[mask]
388
+
389
+ def get_astronomical_night(self, timestamp):
390
+ """
391
+ Calculate astronomical night.
392
+
393
+ Calculates the astronomical dusk and dawn (i.e. when the Sun is 18deg below the
394
+ horizon) for this observatory around a given timestamp. Returned values represent
395
+ the UTC timestamps of dusk and dawn.
396
+
397
+ Parameters
398
+ ----------
399
+ timestamp: datetime
400
+ The date for which we want to request for data.
401
+
402
+ Returns
403
+ -------
404
+ tuple(datetime, datetime)
405
+ The astronomical dusk and dawn.
406
+
407
+ Raises
408
+ ------
409
+ ValueError
410
+ If the provided timestamp corresponds to daytime.
411
+ """
412
+ observer = astral.Observer(
413
+ latitude=self.coordinates[0].to_value(u.deg),
414
+ longitude=self.coordinates[1].to_value(u.deg),
415
+ elevation=self._elevation.to_value(u.m),
416
+ )
417
+ try:
418
+ sun_today = sun(
419
+ observer, date=timestamp, dawn_dusk_depression=18
420
+ ) # corresponds to astronomical dusk/dawn
421
+ except ValueError:
422
+ sun_today = sun(
423
+ observer, date=timestamp - timedelta(days=1), dawn_dusk_depression=18
424
+ )
425
+ return sun_today["dusk"], sun_today["dawn"] + timedelta(days=1)
426
+
427
+ if (timestamp.time() < sun_today["dusk"].time()) and (
428
+ timestamp.time() > sun_today["dawn"].time()
429
+ ):
430
+ self.log.error(
431
+ "The provided timestamp %s corresponds to daytime.", timestamp
432
+ )
433
+ raise ValueError(
434
+ f"The provided timestamp {timestamp} corresponds to daytime."
435
+ )
436
+ if sun_today["dusk"].hour < 12 and (
437
+ (
438
+ (timestamp.time() > sun_today["dusk"].time())
439
+ and (timestamp.time() > sun_today["dawn"].time())
440
+ )
441
+ or (
442
+ (timestamp.time() < sun_today["dusk"].time())
443
+ and (timestamp.time() < sun_today["dawn"].time())
444
+ )
445
+ ):
446
+ self.log.error(
447
+ "The provided timestamp %s corresponds to daytime.", timestamp
448
+ )
449
+ raise ValueError(
450
+ f"The provided timestamp {timestamp} corresponds to daytime."
451
+ )
452
+
453
+ if timestamp.hour < sun_today["dusk"].hour:
454
+ sun_yesterday = sun(
455
+ observer,
456
+ date=timestamp - timedelta(days=1),
457
+ dawn_dusk_depression=18,
458
+ )
459
+ if (sun_today["dawn"] - sun_yesterday["dusk"]) > timedelta(days=1):
460
+ return sun_yesterday["dusk"] + timedelta(days=1), sun_today["dawn"]
461
+ return sun_yesterday["dusk"], sun_today["dawn"]
462
+
463
+ if timestamp.hour > sun_today["dawn"].hour:
464
+ sun_tomorrow = sun(
465
+ observer,
466
+ date=timestamp + timedelta(days=1),
467
+ dawn_dusk_depression=18,
468
+ )
469
+ return sun_today["dusk"], sun_tomorrow["dawn"]
470
+ return (sun_today["dusk"], sun_today["dawn"])
471
+
472
+ def get_season_from_timestamp(self, timestamp):
473
+ """Get the name of the season corresponding to the timestamp.
474
+
475
+ Parameters
476
+ ----------
477
+ timestamp : datetime
478
+
479
+ Returns
480
+ -------
481
+ str
482
+ Season name.
483
+ """
484
+ for season in self._seasons:
485
+ if timestamp in season:
486
+ return season.name
@@ -0,0 +1,26 @@
1
+ # Third-party imports # noqa: D100
2
+ import astropy.units as u
3
+
4
+ # CTA-related imports
5
+ from ctapipe.core import Container, Field
6
+
7
+
8
+ class ObservatoryContainer(Container):
9
+ """Observatory container."""
10
+
11
+ name = Field(None, "Observatory name")
12
+ latitude = Field(None, "Observatory latitude", unit=u.deg)
13
+ longitude = Field(None, "Observatory longitude", unit=u.deg)
14
+ elevation = Field(None, "Observatory elevation", unit=u.m)
15
+ version = Field(None, "Observatory configuration version")
16
+
17
+
18
+ class SeasonContainer(Container):
19
+ """Season container."""
20
+
21
+ start = Field(None, "Season start timestamp")
22
+ stop = Field(None, "Season stop timestamp")
23
+ name = Field(None, "Season name")
24
+ alias = Field(None, "Season alias")
25
+ name_Observatory = Field(None, "Reference observatory name") # noqa: N815
26
+ version_Observatory = Field(None, "Reference observatory configuration version") # noqa: N815
calibpipe/version.py ADDED
@@ -0,0 +1,24 @@
1
+ """Version information."""
2
+
3
+ # this is adapted from https://github.com/astropy/astropy/blob/main/astropy/version.py
4
+ # see https://github.com/astropy/astropy/pull/10774 for a discussion on why this needed.
5
+
6
+ try:
7
+ try:
8
+ from ._dev_version import version
9
+ except Exception:
10
+ from ._version import version
11
+ except Exception:
12
+ import warnings
13
+
14
+ warnings.warn(
15
+ "Could not determine version; this indicates a broken installation."
16
+ " Install from PyPI, using conda or from a local git repository."
17
+ " Installing github's autogenerated source release tarballs "
18
+ " does not include version information and should be avoided.",
19
+ )
20
+ del warnings
21
+ version = "0.0.0"
22
+
23
+ __version__ = version
24
+ __all__ = ["__version__"]
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctao-calibpipe
3
+ Version: 0.1.0rc7
4
+ Author: Leonid Burmistrov, Mykhailo Dalchenko, Antonio Di Pilato, Gabriel Emery, Tjark Miener, Gregoire Uhlrich, Georgios Voutsinas, Vadym Voitsekhovskyi
5
+ Classifier: License :: OSI Approved :: MIT License
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ License-File: AUTHORS.md
10
+ Requires-Dist: astral
11
+ Requires-Dist: astropy
12
+ Requires-Dist: cdsapi
13
+ Requires-Dist: ctapipe>=0.18
14
+ Requires-Dist: h5py
15
+ Requires-Dist: molecularprofiles>=2.1.0
16
+ Requires-Dist: numpy
17
+ Requires-Dist: pandas
18
+ Requires-Dist: psycopg[binary]
19
+ Requires-Dist: pygrib
20
+ Requires-Dist: pyrdams>=3.0.1
21
+ Requires-Dist: requests>=2.27
22
+ Requires-Dist: requests
23
+ Requires-Dist: sqlalchemy>=2.0.1
24
+ Requires-Dist: traitlets
25
+ Provides-Extra: test
26
+ Requires-Dist: black>=22.5; extra == "test"
27
+ Requires-Dist: coverage-badge; extra == "test"
28
+ Requires-Dist: cwltool; extra == "test"
29
+ Requires-Dist: pylint>=2.15; extra == "test"
30
+ Requires-Dist: pytest-cov; extra == "test"
31
+ Requires-Dist: pytest-order; extra == "test"
32
+ Requires-Dist: pytest; extra == "test"
33
+ Requires-Dist: pyyaml; extra == "test"
34
+ Requires-Dist: pytest-requirements; extra == "test"
35
+ Provides-Extra: doc
36
+ Requires-Dist: linkify-it-py; extra == "doc"
37
+ Requires-Dist: myst-parser; extra == "doc"
38
+ Requires-Dist: nbsphinx; extra == "doc"
39
+ Requires-Dist: numpydoc; extra == "doc"
40
+ Requires-Dist: ctao-sphinx-theme; extra == "doc"
41
+ Requires-Dist: sphinx-argparse; extra == "doc"
42
+ Requires-Dist: sphinx-paramlinks; extra == "doc"
43
+ Requires-Dist: sphinx>=8.2.1; extra == "doc"
44
+ Requires-Dist: sphinx-changelog; extra == "doc"
45
+ Provides-Extra: dev
46
+ Requires-Dist: setuptools_scm; extra == "dev"
47
+ Requires-Dist: towncrier; extra == "dev"
48
+ Requires-Dist: pre-commit; extra == "dev"
49
+ Provides-Extra: all
50
+ Requires-Dist: calibpipe[dev,doc,test]; extra == "all"
51
+ Dynamic: license-file
52
+
53
+ # DPPS Calibration Pipeline
54
+
55
+ Welcome to `calibpipe` project. The project provides a selection of calibration tools
56
+ for CTA raw data calibration. For full details see [project documentation][calibpipe-doc].
57
+
58
+ ## Installation
59
+
60
+ ### Installation for users
61
+
62
+ Currently the package is under active development. First, create and activate a fresh conda environment:
63
+
64
+ ```
65
+ mamba create -n calibpipe -c conda-forge python==3.12 ctapipe
66
+ mamba activate calibpipe
67
+ ```
68
+ and then install `calibpipe` using `pip` and TestPyPI:
69
+
70
+ ```
71
+ pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ calibpipe
72
+ ```
73
+
74
+ ### Installation for developers
75
+
76
+ ## Contributing
77
+
78
+ If you would like to contribute to this project please start from reading the [Contributing Guidelines][contributing].
79
+ Then you can configure the project locally for development as outlined in the [Development Instructions][developing], and start to contribute.
80
+ If you develop a new tool, don't forget to add a corresponding record to the `[project.scripts]` section of `pyproject.toml`
81
+
82
+ Enjoy!
83
+
84
+ [contributing]:http://cta-computing.gitlab-pages.cta-observatory.org/dpps/calibrationpipeline/calibpipe/latest/development/index.html
85
+ [developing]:http://cta-computing.gitlab-pages.cta-observatory.org/dpps/calibrationpipeline/calibpipe/latest/getting_started/index.html#development-setup
86
+ [calibpipe-doc]:http://cta-computing.gitlab-pages.cta-observatory.org/dpps/calibrationpipeline/calibpipe/latest/index.html