Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write modified images from dl1ab in output DL1 files and fix add_config_metadata #1145

Merged
merged 24 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b9a3ca0
write modified images in dl1ab
vuillaut Jun 16, 2023
5901b0d
images will be saved
vuillaut Jun 16, 2023
7c5e7e5
images will be saved, really
vuillaut Jun 16, 2023
6d5a4ff
images will be saved, really
vuillaut Jun 16, 2023
7733fc3
images will be saved, really
vuillaut Jun 16, 2023
b54d133
images will be saved, really
vuillaut Jun 16, 2023
401ed76
write images unless specified not to
vuillaut Jul 21, 2023
8053972
fix bugs with no_image
vuillaut Jul 24, 2023
8984e8c
Merge branch 'main' of https://github.com/cta-observatory/cta-lstchai…
vuillaut Sep 4, 2023
03a968b
adding the image config in the metadata and use it to check of the im…
vuillaut Sep 4, 2023
f041555
dump config in dl1ab
vuillaut Sep 4, 2023
bdb3eab
rename function includes image modification
vuillaut Sep 4, 2023
d5cca47
formatting
vuillaut Sep 4, 2023
867872a
fix test
vuillaut Sep 4, 2023
54d20c4
fix config serialization and add unit test
vuillaut Sep 5, 2023
d8b354a
add standard config in test
vuillaut Sep 5, 2023
f478f0a
correct way of testing the failure of the function
vuillaut Sep 5, 2023
6b949fc
fix test dl1ab on modified images
vuillaut Sep 6, 2023
9b4888f
use importlib resources to find the tuned config file
vuillaut Sep 6, 2023
48ce6fe
Merge remote-tracking branch 'origin/run-summary-test' into write_ima…
vuillaut Sep 6, 2023
d9e75a7
remove unused import pathlib
vuillaut Sep 6, 2023
613a6d4
move includes_image_modification to io/config
vuillaut Sep 7, 2023
f7403ab
import includes_image_modfication from config
vuillaut Sep 14, 2023
432d24a
Merge branch 'main' of https://github.com/cta-observatory/cta-lstchai…
vuillaut Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lstchain/image/modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,6 @@ def calculate_noise_parameters(simtel_filename, data_dl1_filename,
pedestal_calibrator.image_extractors[ped_config['charge_product']].apply_integration_correction = True
shower_calibrator.image_extractors[shower_extractor_type].apply_integration_correction = True




# Pulse integration window width of the (biased) extractor for showers:
shower_extractor_window_width = config[config['image_extractor']]['window_width']

Expand Down
21 changes: 21 additions & 0 deletions lstchain/io/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,24 @@ def dump_config(config, filename, overwrite=False):
raise FileExistsError(f"File {filename} exists, use overwrite=True")
with open(filename, 'w') as file:
json.dump(config, file, indent=2)


def includes_image_modification(config):
"""
Check if the image modifier has been used in the given configuration.

Parameters
----------
config : `dict`
The configuration dictionary to check.

Returns
-------
`bool`
`True` if the image modifier has been used, `False` otherwise.
"""
imconfig = config.get('image_modifier', {})
increase_nsb = imconfig.get("increase_nsb", False)
increase_psf = imconfig.get("increase_psf", False)

return increase_nsb or increase_psf
63 changes: 51 additions & 12 deletions lstchain/io/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import tables
from tables import open_file
from tqdm import tqdm
import json
from traitlets.config.loader import DeferredConfigString, LazyConfigValue
from pathlib import PosixPath

import astropy.units as u
from astropy.table import Table, vstack, QTable
Expand All @@ -31,6 +34,8 @@
ThrownEventsHistogram,
)



log = logging.getLogger(__name__)

__all__ = [
Expand Down Expand Up @@ -657,6 +662,50 @@
container.meta[k] = item




def serialize_config(obj):
"""
Serialize an object to a JSON-serializable format.

Parameters
----------
obj : object
The object to serialize.

Returns
-------
object
The serialized object.

Raises
------
TypeError
If the object is not serializable.

Notes
-----
This function serializes an object to a JSON-serializable format. It supports the following types:
- LazyConfigValue
- DeferredConfigString
- PosixPath
- numpy.ndarray

If the object is not one of the above types, a TypeError is raised.

"""
if isinstance(obj, LazyConfigValue):
return obj.to_dict()
elif isinstance(obj, DeferredConfigString):
return str(obj)

Check warning on line 700 in lstchain/io/io.py

View check run for this annotation

Codecov / codecov/patch

lstchain/io/io.py#L700

Added line #L700 was not covered by tests
elif isinstance(obj, PosixPath):
return obj.as_posix()
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
raise TypeError(f"Type {type(obj).__name__} not serializable")

Check warning on line 706 in lstchain/io/io.py

View check run for this annotation

Codecov / codecov/patch

lstchain/io/io.py#L706

Added line #L706 was not covered by tests


def add_config_metadata(container, configuration):
"""
Add configuration parameters to a container in container.meta.config
Expand All @@ -666,18 +715,7 @@
container: `ctapipe.containers.Container`
configuration: config dict
"""
linted_config = str(configuration)
linted_config = linted_config.replace("<LazyConfigValue {}>", "None")
linted_config = re.sub(r"<LazyConfigValue\svalue=(.*?)>", "\\1", linted_config)
linted_config = re.sub(r"DeferredConfigString\((.*?)\)", "\\1", linted_config)
linted_config = re.sub(r"PosixPath\((.*?)\)", "\\1", linted_config)
linted_config = linted_config.replace("\'", "\"")
linted_config = linted_config.replace("None", "\"None\"")
linted_config = linted_config.replace("inf", "\"inf\"")
linted_config = linted_config.replace("True", "true")
linted_config = linted_config.replace("False", "false")

container.meta["config"] = linted_config
container.meta["config"] = json.dumps(configuration, default=serialize_config)


def write_subarray_tables(writer, event, metadata=None):
Expand Down Expand Up @@ -1261,3 +1299,4 @@
mean_offset = min_viewcone + 0.5 * (max_viewcone - min_viewcone)

return mean_offset

22 changes: 20 additions & 2 deletions lstchain/io/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from lstchain.io import config
from lstchain.io.config import get_cleaning_parameters
import tempfile
from lstchain.io import config
from lstchain.io.config import get_cleaning_parameters, includes_image_modification


def test_get_standard_config():
Expand Down Expand Up @@ -52,3 +52,21 @@ def test_dump_config():
config.dump_config(cfg, file.name, overwrite=True)
read_cfg = config.read_configuration_file(file.name)
assert read_cfg['myconf'] == 1


def test_includes_image_modification_no_modif():
cfg = {}
assert not includes_image_modification(cfg)
cfg = {"image_modifier": {}}
assert not includes_image_modification(cfg)


def test_includes_image_modification_with_modif():
cfg = {"image_modifier": {"increase_psf": True, "increase_nsb": False}}
assert includes_image_modification(cfg)
cfg = {"image_modifier": {"increase_nsb": True, "increase_psf": False}}
assert includes_image_modification(cfg)
cfg = {"image_modifier": {"increase_psf": True, "increase_nsb": True}}
assert includes_image_modification(cfg)
cfg = {"image_modifier": {"increase_psf": False, "increase_nsb": False}}
assert not includes_image_modification(cfg)
87 changes: 69 additions & 18 deletions lstchain/io/tests/test_io.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import tempfile

import json
import math
import numpy as np
import pandas as pd
import pytest
import tables
from astropy.table import Table, QTable
from ctapipe.instrument import SubarrayDescription
from lstchain.io import add_config_metadata
from pathlib import PosixPath
from traitlets.config.loader import DeferredConfigString, LazyConfigValue


@pytest.fixture
Expand Down Expand Up @@ -149,39 +153,86 @@ def test_trigger_type_in_dl1_params(simulated_dl1_file):

def test_extract_simulation_nsb(mc_gamma_testfile):
from lstchain.io.io import extract_simulation_nsb

nsb = extract_simulation_nsb(mc_gamma_testfile)
assert np.isclose(nsb[0], 0.246, rtol=0.1)
assert np.isclose(nsb[1], 0.217, rtol=0.1)


def test_remove_duplicated_events():
from lstchain.io.io import remove_duplicated_events

d = {'event_id': [1, 2, 3,
1, 2, 4,
1, 2, 3],
'gh_score': [0.1, 0.5, 0.7,
0.5, 0.8, 0.1,
0.9, 0.1, 0.5],
'alpha': range(9)
}

d = {
"event_id": [1, 2, 3, 1, 2, 4, 1, 2, 3],
"gh_score": [0.1, 0.5, 0.7, 0.5, 0.8, 0.1, 0.9, 0.1, 0.5],
"alpha": range(9),
}
df = pd.DataFrame(data=d)
data1 = QTable.from_pandas(df)
remove_duplicated_events(data1)

d2 = {'event_id': [3, 2, 4, 1],
'gh_score': [0.7, 0.8, 0.1, 0.9],
'alpha': [2, 4, 5, 6]
}
d2 = {
"event_id": [3, 2, 4, 1],
"gh_score": [0.7, 0.8, 0.1, 0.9],
"alpha": [2, 4, 5, 6],
}
df2 = pd.DataFrame(data=d2)
data2= QTable.from_pandas(df2)
data2 = QTable.from_pandas(df2)

assert np.all(data1==data2)
assert np.all(data1 == data2)


def test_check_mc_type(simulated_dl1_file):
from lstchain.io.io import check_mc_type

mc_type = check_mc_type(simulated_dl1_file)
assert mc_type == 'diffuse'

assert mc_type == "diffuse"


def test_add_config_metadata():
class Container:
meta = {}

lazy_value = LazyConfigValue()
lazy_value.update({"key": "new_value"})

config = {
"param1": 1,
"param2": "value2",
"param3": [1, 2, 3],
"param4": {"a": 1, "b": 2},
"param5": None,
"param6": lazy_value,
"param7": DeferredConfigString("some_string"),
"param8": PosixPath("/path/to/file"),
"param9": np.inf,
"param10": True,
"param11": False,
"param12": np.array([1, 2, 3]),
}

expected_config = {
"param1": 1,
"param2": "value2",
"param3": [1, 2, 3],
"param4": {"a": 1, "b": 2},
"param5": None,
"param6": {"update": {"key": "new_value"}},
"param7": "some_string",
"param8": "/path/to/file",
"param9": math.inf,
"param10": True,
"param11": False,
"param12": [1, 2, 3],
}

container = Container()
add_config_metadata(container, config)
assert json.loads(container.meta["config"]) == expected_config

# test also with standard config in case of future changes
from lstchain.io.config import get_standard_config
config = get_standard_config()
container = Container()
add_config_metadata(container, config)
assert json.loads(container.meta["config"]) == config
37 changes: 23 additions & 14 deletions lstchain/scripts/lstchain_dl1ab.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import argparse
import logging
from pathlib import Path
import json

import astropy.units as u
import numpy as np
Expand All @@ -31,13 +32,14 @@
from lstchain.calib.camera.pixel_threshold_estimation import get_threshold_from_dl1_file
from lstchain.image.cleaning import apply_dynamic_cleaning
from lstchain.image.modifier import random_psf_smearer, set_numba_seed, add_noise_in_pixels
from lstchain.io import get_dataset_keys, copy_h5_nodes, HDF5_ZSTD_FILTERS, add_source_filenames
from lstchain.io import get_dataset_keys, copy_h5_nodes, HDF5_ZSTD_FILTERS, add_source_filenames, add_config_metadata

from lstchain.io.config import (
get_cleaning_parameters,
get_standard_config,
read_configuration_file,
replace_config,
includes_image_modification,
)
from lstchain.io.io import (
dl1_images_lstcam_key,
Expand Down Expand Up @@ -82,7 +84,7 @@

parser.add_argument(
'--no-image', action='store_true',
help='Pass this argument to avoid writing the images in the new DL1 files. Beware, if `increase_nsb` or `increase_psf` are True in the config, the images will not be written.',
help='Pass this argument to avoid writing the images in the new DL1 files.',
)

parser.add_argument(
Expand Down Expand Up @@ -148,9 +150,9 @@ def main():

if increase_nsb or increase_psf:
log.info(f"image_modifier configuration: {imconfig}")
log.info("NOTE: Using the image_modifier options means images will "
"not be saved.")
args.no_image = True
if not args.no_image:
log.info("Modified images are saved in the output file.")

if increase_nsb:
extra_noise_in_dim_pixels = imconfig["extra_noise_in_dim_pixels"]
extra_bias_in_dim_pixels = imconfig["extra_bias_in_dim_pixels"]
Expand Down Expand Up @@ -243,6 +245,14 @@ def main():

with tables.open_file(args.input_file, mode='r') as infile:
image_table = read_table(infile, dl1_images_lstcam_key)
# if the image modifier has been used to produce these images, stop here
config_from_image_table = json.loads(image_table.meta['config'])
if includes_image_modification(config_from_image_table) and includes_image_modification(config):
log.critical(f"\nThe image modifier has already been used to produce the images in file {args.input_file}.\n"
"Re-applying the image modifier is not a good practice, start again from unmodified images please.")
sys.exit(1)

images = image_table['image']
params = read_table(infile, dl1_params_lstcam_key)
dl1_params_input = params.colnames

Expand Down Expand Up @@ -270,10 +280,6 @@ def main():
if increase_psf:
set_numba_seed(infile.root.dl1.event.subarray.trigger.col('obs_id')[0])

image_mask_save = not args.no_image and 'image_mask' in infile.root[dl1_images_lstcam_key].colnames
if image_mask_save:
image_mask = image_table['image_mask']

new_params = set(parameters_to_update.keys()) - set(params.colnames)
for p in new_params:
params[p] = np.empty(len(params), dtype=parameters_to_update[p])
Expand All @@ -282,7 +288,6 @@ def main():
copy_h5_nodes(infile, outfile, nodes=nodes_keys)
add_source_filenames(outfile, [args.input_file])


# need container to use lstchain.io.add_global_metadata and lstchain.io.add_config_metadata
for k, item in metadata.as_dict().items():
outfile.root[dl1_params_lstcam_key].attrs[k] = item
Expand Down Expand Up @@ -420,13 +425,17 @@ def main():
for p in parameters_to_update:
params[ii][p] = u.Quantity(dl1_container[p]).value

if image_mask_save:
image_mask[ii] = signal_pixels
images[ii] = image

if 'image_mask' in image_table.colnames:
image_table['image_mask'][ii] = signal_pixels


if image_mask_save or catB_calib:
# the image table has been modified and needs to be saved
add_config_metadata(image_table, config)
if not args.no_image:
write_table(image_table, outfile, dl1_images_lstcam_key, overwrite=True, filters=HDF5_ZSTD_FILTERS)

add_config_metadata(params, config)
write_table(params, outfile, dl1_params_lstcam_key, overwrite=True, filters=HDF5_ZSTD_FILTERS)

# write a cat-B calibrations in DL1b
Expand Down
Loading
Loading