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

Flat field #161

Merged
merged 20 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions src/nectarchain/data/container/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
get_array_keys,
merge_map_ArrayDataContainer,
)
from .flatfield_container import FlatFieldContainer
from .gain_container import GainContainer, SPEfitContainer
from .pedestal_container import (
NectarCAMPedestalContainer,
Expand All @@ -32,4 +33,5 @@
"NectarCAMPedestalContainer",
"NectarCAMPedestalContainers",
"PedestalFlagBits",
"FlatFieldContainer",
]
90 changes: 90 additions & 0 deletions src/nectarchain/data/container/flatfield_container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import logging

import numpy as np
from ctapipe.containers import Field

from .core import NectarCAMContainer

logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
log.handlers = logging.getLogger("__main__").handlers

__all__ = ["FlatFieldContainer"]


class FlatFieldContainer(NectarCAMContainer):
"""
Container that holds flat field coefficients and other useful information

Fields:
run_number (np.uint16): Number of the run
npixels (np.uint16): Number of pixels
pixels_id (np.ndarray): Array of pixel's ID
ucts_timestamp (np.ndarray) : Array of time stamps of each event (UTC)
event_type (np.ndarray): Array of trigger event types (should be all flat
field events)
event_id (np.ndarray): Array of the IDs of each event
amp_int_per_pix_per_event (np.ndarray): Array of integrated amplitude of each
pulse
t_peak_per_pix_per_event (np.ndarray): Array of samples containing the pulse
maximum
FF_coef (np.ndarray): Array of flat field coefficients
bad_pixels (List): List of pixel identified as outliers
"""

run_number = Field(
type=np.uint16,
description="run number associated to the waveforms",
)

npixels = Field(
type=np.uint16,
description="number of effective pixels",
)

pixels_id = Field(type=np.ndarray, dtype=np.uint16, ndim=1, description="pixel ids")

ucts_timestamp = Field(
type=np.ndarray, dtype=np.uint64, ndim=1, description="events ucts timestamp"
)

event_type = Field(
type=np.ndarray, dtype=np.uint8, ndim=1, description="trigger event type"
)

event_id = Field(type=np.ndarray, dtype=np.uint32, ndim=1, description="event ids")

amp_int_per_pix_per_event = Field(
type=np.ndarray,
dtype=np.float32,
ndim=3,
description="amplitude integrated over the window width, per pixel per event",
)

# t_peak_per_pix_per_event = Field(
# type=np.ndarray,
# dtype=np.float32,
# ndim=3,
# description="sample containing the pulse maximum, per pixel and per event",
# )

FF_coef = Field(
type=np.ndarray,
dtype=np.float32,
ndim=3,
description="the flat field coefficients, per event",
)

# masked_wfs = Field(
# type=np.ndarray,
# dtype=np.uint64,
# ndim=4,
# description="Masked array for amplitude integration",
# )

bad_pixels = Field(
type=np.ndarray,
dtype=np.uint16,
ndim=2,
description="pixels considered as bad in at least one gain channels",
)
29 changes: 22 additions & 7 deletions src/nectarchain/makers/calibration/flatfield_makers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import logging
import os
import pathlib

from .core import NectarCAMCalibrationTool
from ctapipe.core.traits import ComponentNameList

from nectarchain.makers import EventsLoopNectarCAMCalibrationTool
from nectarchain.makers.component import NectarCAMComponent

logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
log.handlers = logging.getLogger("__main__").handlers


__all__ = ["FlatfieldNectarCAMCalibrationTool"]


class FlatfieldNectarCAMCalibrationTool(NectarCAMCalibrationTool):
def start(self):
raise NotImplementedError(
"The computation of the flatfield calibration is not yet implemented, \
feel free to contribute !:)"
class FlatfieldNectarCAMCalibrationTool(EventsLoopNectarCAMCalibrationTool):
name = "FlatfieldNectarCAMCalibrationTool"

componentsList = ComponentNameList(
NectarCAMComponent,
default_value=["PreFlatFieldComponent"],
help="List of Component names to be apply, the order will be respected",
).tag(config=True)

def _init_output_path(self):
if self.max_events is None:
filename = f"{self.name}_run{self.run_number}.h5"

Check warning on line 28 in src/nectarchain/makers/calibration/flatfield_makers.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/calibration/flatfield_makers.py#L27-L28

Added lines #L27 - L28 were not covered by tests
else:
filename = f"{self.name}_run{self.run_number}_maxevents{self.max_events}.h5"
self.output_path = pathlib.Path(

Check warning on line 31 in src/nectarchain/makers/calibration/flatfield_makers.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/calibration/flatfield_makers.py#L30-L31

Added lines #L30 - L31 were not covered by tests
f"{os.environ.get('NECTARCAMDATA','/tmp')}/FlatFieldTests/{filename}"
)
2 changes: 2 additions & 0 deletions src/nectarchain/makers/component/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .pedestal_component import PedestalEstimationComponent
from .photostatistic_algorithm import PhotoStatisticAlgorithm
from .photostatistic_component import PhotoStatisticNectarCAMComponent
from .preflatfield_component import PreFlatFieldComponent
from .spe import (
SPECombinedalgorithm,
SPEHHValgorithm,
Expand Down Expand Up @@ -40,4 +41,5 @@
"PhotoStatisticNectarCAMComponent",
"PhotoStatisticAlgorithm",
"GainNectarCAMComponent",
"PreFlatFieldComponent",
]
194 changes: 194 additions & 0 deletions src/nectarchain/makers/component/preflatfield_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import logging

import numpy as np
from ctapipe.containers import EventType
from ctapipe.core.traits import Integer, List
from ctapipe_io_nectarcam import constants
from ctapipe_io_nectarcam.containers import NectarCAMDataContainer

from nectarchain.data.container import FlatFieldContainer
from nectarchain.makers.component import NectarCAMComponent

logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
log.handlers = logging.getLogger("__main__").handlers

__all__ = ["PreFlatFieldComponent"]


class PreFlatFieldComponent(NectarCAMComponent):
"""
Component that computes flat field coefficients from raw data.

Parameters
----------
window_shift: int
time in ns before the peak to integrate charge (default value = 5)

window_width: int
duration of the extraction window in ns (default value = 12)

gain: list
array of gain value

bad_pix: list
list of bad pixels (default value = [])

"""

window_shift = Integer(
default_value=5,
help="the time in ns before the peak to integrate charge",
).tag(config=True)

window_width = Integer(
default_value=12,
help="the duration of the extraction window in ns",
).tag(config=True)

gain = List(
default_value=None,
help="default gain value",
).tag(config=True)

# hi_lo_ratio = Float(
# default_value=13.0,
# help="default high gain to low gain ratio",
# ).tag(config=True)

bad_pix = List(
default_value=None,
help="list of bad pixels",
).tag(config=True)

def __init__(self, subarray, config=None, parent=None, *args, **kwargs):
super().__init__(

Check warning on line 65 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L65

Added line #L65 was not covered by tests
subarray=subarray, config=config, parent=parent, *args, **kwargs
)

self.__ucts_timestamp = []
self.__event_type = []
self.__event_id = []
self.__amp_int_per_pix_per_event = []
self.__FF_coef = []
self.__bad_pixels = []

Check warning on line 74 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L69-L74

Added lines #L69 - L74 were not covered by tests

print("gain")
print(type(self.gain))
print(self.gain)

Check warning on line 78 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L76-L78

Added lines #L76 - L78 were not covered by tests

print("bad_pixels")
print(type(self.bad_pix))
print(self.bad_pix)

Check warning on line 82 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L80-L82

Added lines #L80 - L82 were not covered by tests

def __call__(self, event: NectarCAMDataContainer, *args, **kwargs):
if event.trigger.event_type.value == EventType.FLATFIELD.value:

Check warning on line 85 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L85

Added line #L85 was not covered by tests
# print("event :", (self.__event_id, self.__event_type))
self.__event_id.append(np.uint32(event.index.event_id))
self.__event_type.append(event.trigger.event_type.value)
self.__ucts_timestamp.append(event.nectarcam.tel[0].evt.ucts_timestamp)

Check warning on line 89 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L87-L89

Added lines #L87 - L89 were not covered by tests

wfs = event.r0.tel[0].waveform

Check warning on line 91 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L91

Added line #L91 was not covered by tests

# subtract pedestal using the mean of the 20 first samples
wfs_pedsub = self.subtract_pedestal(wfs, 20)

Check warning on line 94 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L94

Added line #L94 was not covered by tests

# get the masked array for integration window
t_peak = np.argmax(wfs_pedsub, axis=-1)
masked_wfs = self.make_masked_array(

Check warning on line 98 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L97-L98

Added lines #L97 - L98 were not covered by tests
t_peak, self.window_shift, self.window_width
)

# mask bad pixels
self.__bad_pixels.append(self.bad_pix)
masked_wfs[:, self.bad_pix, :] = False

Check warning on line 104 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L103-L104

Added lines #L103 - L104 were not covered by tests

# get integrated amplitude and mean amplitude over all pixels per event
amp_int_per_pix_per_event = np.sum(

Check warning on line 107 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L107

Added line #L107 was not covered by tests
wfs_pedsub[0], axis=-1, where=masked_wfs.astype("bool")
)
self.__amp_int_per_pix_per_event.append(amp_int_per_pix_per_event)

Check warning on line 110 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L110

Added line #L110 was not covered by tests
# --< We could use ctapipe.image.extractor.LocalPeakWindowSum >--

amp_int_per_pix_per_event_pe = amp_int_per_pix_per_event[:] / self.gain[:]
mean_amp_cam_per_event_pe = np.mean(amp_int_per_pix_per_event_pe, axis=-1)

Check warning on line 114 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L113-L114

Added lines #L113 - L114 were not covered by tests

eff = np.divide(

Check warning on line 116 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L116

Added line #L116 was not covered by tests
amp_int_per_pix_per_event_pe,
np.expand_dims(mean_amp_cam_per_event_pe, axis=-1),
)

FF_coef = np.ma.array(1.0 / eff, mask=eff == 0)
self.__FF_coef.append(FF_coef)

Check warning on line 122 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L121-L122

Added lines #L121 - L122 were not covered by tests

@staticmethod
def subtract_pedestal(wfs, window=20):
"""
Subtract the pedestal defined as the average of the first samples of each trace

Args:
wfs: raw wavefroms
window: number of samples n to calculate the pedestal (default value is 20)

Returns:
wfs_pedsub: wavefroms subtracted from the pedestal
"""

ped_mean = np.mean(wfs[:, :, 0:window], axis=2)
wfs_pedsub = wfs - np.expand_dims(ped_mean, axis=-1)

Check warning on line 138 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L137-L138

Added lines #L137 - L138 were not covered by tests

return wfs_pedsub

Check warning on line 140 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L140

Added line #L140 was not covered by tests

@staticmethod
def make_masked_array(t_peak, window_shift, window_width):
"""
Define an array that will be used as a mask on the waveforms for the calculation
of the integrated amplitude of the signal

Args:
t_peak: sample corresponding the the highest peak of the trace
window_shift: number of samples before the peak to integrate charge
window_width: duration of the extraction window in samples

Returns:
masked_wfs: a mask array
"""

masked_wfs = np.zeros(

Check warning on line 157 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L157

Added line #L157 was not covered by tests
shape=(constants.N_GAINS, constants.N_PIXELS, constants.N_SAMPLES),
dtype=bool,
)

sample_times = np.expand_dims(np.arange(constants.N_SAMPLES), axis=(0, 1))
t_peak = np.expand_dims(t_peak, axis=-1)

Check warning on line 163 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L162-L163

Added lines #L162 - L163 were not covered by tests

t_signal_start = t_peak - window_shift
t_signal_stop = t_peak + window_width - window_shift

Check warning on line 166 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L165-L166

Added lines #L165 - L166 were not covered by tests

masked_wfs = (sample_times >= t_signal_start) & (sample_times < t_signal_stop)

Check warning on line 168 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L168

Added line #L168 was not covered by tests

return masked_wfs

Check warning on line 170 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L170

Added line #L170 was not covered by tests

def finish(self):
output = FlatFieldContainer(

Check warning on line 173 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L173

Added line #L173 was not covered by tests
run_number=FlatFieldContainer.fields["run_number"].type(self._run_number),
npixels=FlatFieldContainer.fields["npixels"].type(self._npixels),
pixels_id=FlatFieldContainer.fields["pixels_id"].dtype.type(
self._pixels_id
),
ucts_timestamp=FlatFieldContainer.fields["ucts_timestamp"].dtype.type(
self.__ucts_timestamp
),
event_type=FlatFieldContainer.fields["event_type"].dtype.type(
self.__event_type
),
event_id=FlatFieldContainer.fields["event_id"].dtype.type(self.__event_id),
amp_int_per_pix_per_event=FlatFieldContainer.fields[
"amp_int_per_pix_per_event"
].dtype.type(self.__amp_int_per_pix_per_event),
FF_coef=FlatFieldContainer.fields["FF_coef"].dtype.type(self.__FF_coef),
bad_pixels=FlatFieldContainer.fields["bad_pixels"].dtype.type(
self.__bad_pixels
),
)
return output

Check warning on line 194 in src/nectarchain/makers/component/preflatfield_component.py

View check run for this annotation

Codecov / codecov/patch

src/nectarchain/makers/component/preflatfield_component.py#L194

Added line #L194 was not covered by tests
Loading