ctao-calibpipe 0.1.0rc8__py3-none-any.whl → 0.2.0__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 (24) hide show
  1. calibpipe/_version.py +2 -2
  2. calibpipe/core/common_metadata_containers.py +3 -0
  3. calibpipe/database/adapter/adapter.py +1 -1
  4. calibpipe/database/adapter/database_containers/__init__.py +2 -0
  5. calibpipe/database/adapter/database_containers/common_metadata.py +2 -0
  6. calibpipe/database/adapter/database_containers/throughput.py +30 -0
  7. calibpipe/database/interfaces/table_handler.py +79 -97
  8. calibpipe/telescope/throughput/containers.py +59 -0
  9. calibpipe/tests/unittests/array/test_cross_calibration.py +417 -0
  10. calibpipe/tests/unittests/database/test_table_handler.py +95 -0
  11. calibpipe/tests/unittests/telescope/camera/test_calculate_camcalib_coefficients.py +347 -0
  12. calibpipe/tests/unittests/telescope/camera/test_produce_camcalib_test_data.py +42 -0
  13. calibpipe/tests/unittests/telescope/throughput/test_muon_throughput_calibrator.py +189 -0
  14. calibpipe/tools/camcalib_test_data.py +361 -0
  15. calibpipe/tools/camera_calibrator.py +558 -0
  16. calibpipe/tools/muon_throughput_calculator.py +239 -0
  17. calibpipe/tools/telescope_cross_calibration_calculator.py +721 -0
  18. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/METADATA +3 -2
  19. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/RECORD +24 -14
  20. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/WHEEL +1 -1
  21. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/entry_points.txt +4 -0
  22. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  23. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/licenses/LICENSE +0 -0
  24. {ctao_calibpipe-0.1.0rc8.dist-info → ctao_calibpipe-0.2.0.dist-info}/top_level.txt +0 -0
calibpipe/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.0rc8'
21
- __version_tuple__ = version_tuple = (0, 1, 0, 'rc8')
20
+ __version__ = version = '0.2.0'
21
+ __version_tuple__ = version_tuple = (0, 2, 0)
@@ -22,6 +22,9 @@ class ReferenceMetadataContainer(Container):
22
22
  description="Version of the reference metadata schema used in the data product",
23
23
  type=int,
24
24
  )
25
+ ID_optical_throughput = Field(
26
+ None, description="Optical throughput ID to serve as a foreign key", type=int
27
+ )
25
28
 
26
29
 
27
30
  class ProductReferenceMetadataContainer(Container):
@@ -36,7 +36,7 @@ class Adapter:
36
36
  Returns
37
37
  -------
38
38
  Optional[tuple[sa.Table, dict[str, Any]]]
39
- None if to DB table information is associated to the container.
39
+ None if no DB table information is associated to the container.
40
40
  A tuple containing the SQL table corresponding to the container,
41
41
  and a dictionary with the valued attributes contained in the
42
42
  CalibPipe container.
@@ -37,6 +37,7 @@ from .common_metadata import (
37
37
  from .container_map import ContainerMap
38
38
  from .observatory import observatory_sql_info, season_sql_info
39
39
  from .table_version_manager import TableVersionManager
40
+ from .throughput import optical_throughput_sql_info
40
41
  from .version_control import version_control_sql_info
41
42
 
42
43
  __all__ = [
@@ -58,4 +59,5 @@ __all__ = [
58
59
  "activity_reference_metadata_sql_info",
59
60
  "process_reference_metadata_sql_info",
60
61
  "instrument_reference_metadata_sql_info",
62
+ "optical_throughput_sql_info",
61
63
  ]
@@ -21,10 +21,12 @@ reference_metadata_sql_info = SQLTableInfo(
21
21
  metadata=sql_metadata,
22
22
  columns=[
23
23
  SQLColumnInfo("ID", Integer, unique=True, primary_key=True, autoincrement=True),
24
+ SQLColumnInfo("ID_optical_throughput", Integer),
24
25
  SQLColumnInfo("version_atmospheric_model", String),
25
26
  SQLColumnInfo("version", String),
26
27
  ],
27
28
  constraints=[
29
+ ForeignKeyConstraint(["ID_optical_throughput"], ["optical_throughput.ID"]),
28
30
  ForeignKeyConstraint(
29
31
  ["version_atmospheric_model"], ["AtmosphericModel.version"]
30
32
  ),
@@ -0,0 +1,30 @@
1
+ """OpticalThroughput SQL info."""
2
+
3
+ from calibpipe.telescope.throughput.containers import OpticalThoughtputContainer
4
+
5
+ from ...interfaces.sql_column_info import SQLColumnInfo
6
+ from ...interfaces.sql_metadata import sql_metadata
7
+ from ...interfaces.sql_table_info import SQLTableInfo
8
+ from ...interfaces.types import DateTime, Float, Integer, String
9
+ from .container_map import ContainerMap
10
+
11
+ optical_throughput_sql_info = SQLTableInfo(
12
+ table_name="optical_throughput",
13
+ metadata=sql_metadata,
14
+ columns=[
15
+ SQLColumnInfo("ID", Integer, primary_key=True, autoincrement=True),
16
+ SQLColumnInfo("tel_id", Integer),
17
+ SQLColumnInfo("obs_id", Integer),
18
+ SQLColumnInfo("validity_start", DateTime(timezone=True)),
19
+ SQLColumnInfo("validity_end", DateTime(timezone=True)),
20
+ SQLColumnInfo("optical_throughput_coefficient", Float),
21
+ SQLColumnInfo("optical_throughput_coefficient_std", Float),
22
+ SQLColumnInfo("method", String),
23
+ SQLColumnInfo("n_events", Integer),
24
+ ],
25
+ )
26
+
27
+ ContainerMap.register_container_pair(
28
+ cp_container=OpticalThoughtputContainer,
29
+ db_container=optical_throughput_sql_info,
30
+ )
@@ -9,8 +9,6 @@ import sqlalchemy as sa
9
9
  from astropy.table import QTable
10
10
  from ctapipe.core import Container
11
11
 
12
- import calibpipe.core.common_metadata_containers as common_metadata_module
13
-
14
12
  from ...core.exceptions import DBStorageError
15
13
  from ..adapter.adapter import Adapter
16
14
  from ..adapter.database_containers.container_map import ContainerMap
@@ -235,36 +233,38 @@ class TableHandler:
235
233
  raise DBStorageError("Issues with connection to the CalibPipe DB")
236
234
 
237
235
  @staticmethod
238
- def upload_data(calibpipe_data_container, config_data):
236
+ def upload_data(
237
+ calibpipe_data_container: Container,
238
+ metadata: list[Container] | None,
239
+ connection: CalibPipeDatabase,
240
+ ) -> None:
239
241
  """
240
- Universal function to upload data and metadata to the DB.
242
+ Upload data and optional metadata to the database.
241
243
 
242
- Metadata is uploaded based on values in the dictionary config_data.
243
- It is possible to update fields in the dictionary while performing calibration,
244
- and transfer the final metadata collection to this function.
244
+ This method uploads the provided data to the main database table and,
245
+ if provided, associates the metadata with the inserted data row.
245
246
 
246
247
  Parameters
247
248
  ----------
248
- calibpipe_data_container : ctapipe.container
249
- calibpipe container with data that will be uploaded to the main table of DB
249
+ calibpipe_data_container : ctapipe.core.Container
250
+ The data container with the data to be uploaded to the main table of the database.
250
251
 
251
- config_data : dict
252
- dictionary with configurable values,
253
- should contain at least DB configuration
254
- and metadata information for each metadata table.
255
-
256
- Returns
257
- -------
258
- insertion_list : list
259
- list of metadata dictionaries that were uploaded to DB
260
- """
261
- insertion_list = []
262
- metadata_dict = {
263
- container: values
264
- for container, values in config_data.items()
265
- if "Reference" in container
266
- }
252
+ metadata : list[Container] or None
253
+ Optional list of metadata containers to be uploaded. Should include
254
+ a "ReferenceMetadataContainer" if metadata is provided.
255
+
256
+ connection : CalibPipeDatabase
257
+ An active database connection to the CalibPipe database.
267
258
 
259
+ Raises
260
+ ------
261
+ DBStorageError
262
+ If there are issues with the database connection.
263
+
264
+ ValueError
265
+ If the main table does not contain a single autoincremented primary key
266
+ or if ReferenceMetadataContainer is missing when metadata is provided.
267
+ """
268
268
  data_db_container = ContainerMap.map_to_db_container(
269
269
  type(calibpipe_data_container)
270
270
  )
@@ -272,80 +272,62 @@ class TableHandler:
272
272
  col.autoincrement for col in data_db_container.get_table().c
273
273
  )
274
274
  is_single_pk = len(data_db_container.get_primary_keys()) == 1
275
- # Check if there are only one autoincremented pk in the table
276
- if has_autoincrement_pk and is_single_pk:
277
- pk_name = data_db_container.get_primary_keys()[0].name
278
- try:
279
- with CalibPipeDatabase(
280
- **config_data["database_configuration"]
281
- ) as connection:
282
- TableHandler.insert_row_in_database(
283
- data_db_container.get_table(),
284
- calibpipe_data_container,
285
- connection,
286
- )
287
- # Get the last uploaded DB record,
288
- # to which all metadata will be attached
289
- stmt = (
290
- sa.select(data_db_container.get_table())
291
- .order_by(sa.desc(data_db_container.get_table().c[pk_name]))
292
- .limit(1)
293
- )
294
- last_db_record = connection.execute(stmt).fetchone()
295
- data_pk_value = last_db_record._asdict()[pk_name]
296
-
297
- # We should process Reference metadata separately,
298
- # because it contains autoincremented PK
299
- # to which all other metadata are connected
300
- cp_container = getattr(
301
- common_metadata_module, "ReferenceMetadataContainer"
302
- )
303
- db_container = ContainerMap.map_to_db_container(cp_container)
304
- reference_meta_insertion = cp_container(
305
- ID_optical_throughput=data_pk_value,
306
- **config_data["ReferenceMetadataContainer"],
307
- )
308
- TableHandler.insert_row_in_database(
309
- db_container.get_table(), reference_meta_insertion, connection
310
- )
311
-
312
- # Extract value of the Reference metadata PK,
313
- # and connect to it all other metadata tables
314
- stmt = (
315
- sa.select(db_container.get_table())
316
- .order_by(sa.desc(db_container.get_table().c.ID))
317
- .limit(1)
318
- )
319
- metadata_id = connection.execute(stmt).fetchone()
320
-
321
- # Remove Reference metadata from the dict to not process it second time
322
- metadata_dict.pop("ReferenceMetadataContainer", None)
323
-
324
- # Create list with values that should be inserted
325
- # to the metadata tables in the DB
326
- for container in metadata_dict.keys():
327
- cp_container = getattr(common_metadata_module, container)
328
- insertion_list.append(
329
- cp_container(ID=metadata_id.ID, **config_data[container])
330
- )
331
275
 
332
- # Upload metadata values to the DB
333
- for insertion, container in zip(
334
- insertion_list, metadata_dict.keys()
335
- ):
336
- cp_container = getattr(common_metadata_module, container)
337
- db_container = ContainerMap.map_to_db_container(cp_container)
338
- TableHandler.insert_row_in_database(
339
- db_container.get_table(), insertion, connection
340
- )
341
-
342
- insertion_list = [reference_meta_insertion] + insertion_list
343
- except sa.exc.DatabaseError:
344
- raise DBStorageError("Issues with connection to the CalibPipe DB")
345
- else:
276
+ if not (has_autoincrement_pk and is_single_pk):
346
277
  raise ValueError(
347
278
  f"Table '{data_db_container.table_name}' "
348
- "doesn't contain single autoincremented primary key."
279
+ "doesn't contain a single autoincremented primary key."
349
280
  )
350
281
 
351
- return insertion_list
282
+ pk_name = data_db_container.get_primary_keys()[0].name
283
+
284
+ try:
285
+ # Insert main data
286
+ table, values = TableHandler.get_database_table_insertion(
287
+ calibpipe_data_container
288
+ )
289
+ TableHandler.insert_row_in_database(table, values, connection)
290
+
291
+ # No metadata to upload
292
+ if not metadata:
293
+ return
294
+
295
+ # Get the last inserted primary key
296
+ stmt = sa.select(table).order_by(sa.desc(table.c[pk_name])).limit(1)
297
+ last_db_record = connection.execute(stmt).fetchone()
298
+ data_pk_value = last_db_record._asdict()[pk_name]
299
+
300
+ # Handle ReferenceMetadataContainer
301
+ reference_meta_container = next(
302
+ (
303
+ c
304
+ for c in metadata
305
+ if c.__class__.__name__ == "ReferenceMetadataContainer"
306
+ ),
307
+ None,
308
+ )
309
+ if reference_meta_container is None:
310
+ raise ValueError("ReferenceMetadataContainer is required in metadata.")
311
+
312
+ reference_meta_container.ID_optical_throughput = data_pk_value
313
+ ref_table, ref_values = TableHandler.get_database_table_insertion(
314
+ reference_meta_container
315
+ )
316
+ TableHandler.insert_row_in_database(ref_table, ref_values, connection)
317
+
318
+ # Get ReferenceMetadata ID to link to other metadata
319
+ stmt = sa.select(ref_table).order_by(sa.desc(ref_table.c.ID)).limit(1)
320
+ metadata_id = connection.execute(stmt).fetchone().ID
321
+
322
+ # Upload other metadata
323
+ for container in metadata:
324
+ if container.__class__.__name__ == "ReferenceMetadataContainer":
325
+ continue
326
+ container.ID = metadata_id
327
+ meta_table, meta_values = TableHandler.get_database_table_insertion(
328
+ container
329
+ )
330
+ TableHandler.insert_row_in_database(meta_table, meta_values, connection)
331
+
332
+ except sa.exc.DatabaseError:
333
+ raise DBStorageError("Issues with connection to the CalibPipe DB")
@@ -0,0 +1,59 @@
1
+ """Containers to keep optical throughput data and metadata."""
2
+
3
+ import datetime
4
+
5
+ import numpy as np
6
+ from ctapipe.core import Container, Field
7
+
8
+ # UNIX_TIME_ZERO value corresponds to "epoch" special time from PostgreSQL documentation:
9
+ # https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE
10
+
11
+ UNIX_TIME_ZERO = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
12
+
13
+
14
+ class OpticalThoughtputContainer(Container):
15
+ """Optical throughput calibration coefficient and analysis results for a single telescope."""
16
+
17
+ optical_throughput_coefficient = Field(
18
+ np.nan,
19
+ "Optical throughput from the selected calibration method",
20
+ type=np.float64,
21
+ allow_none=False,
22
+ )
23
+ optical_throughput_coefficient_std = Field(
24
+ np.nan,
25
+ "Optical throughput from the selected calibration method",
26
+ type=np.float64,
27
+ allow_none=False,
28
+ )
29
+ method = Field(
30
+ "None",
31
+ "Calibration method used to fill optical_throughput_coefficient",
32
+ type=str,
33
+ allow_none=False,
34
+ )
35
+ validity_start = Field(
36
+ UNIX_TIME_ZERO,
37
+ description="Starting timestamp of validity for the selected throughput.",
38
+ type=datetime.datetime,
39
+ allow_none=False,
40
+ )
41
+ validity_end = Field(
42
+ UNIX_TIME_ZERO,
43
+ description="Ending timestamp of validity for the selected throughput.",
44
+ type=datetime.datetime,
45
+ allow_none=False,
46
+ )
47
+ obs_id = Field(
48
+ -1,
49
+ description="ID of the observation block for validity",
50
+ type=np.int32,
51
+ allow_none=False,
52
+ )
53
+ tel_id = Field(-1, description="Telescope ID", type=np.int32, allow_none=False)
54
+ n_events = Field(
55
+ 0,
56
+ description="Number of muon rings used to calculate the throughput",
57
+ type=np.int32,
58
+ allow_none=False,
59
+ )