-
Notifications
You must be signed in to change notification settings - Fork 268
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
Variance calibration #2636
base: main
Are you sure you want to change the base?
Variance calibration #2636
Conversation
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
thanks for putting this together @ctoennis! In case I'm not missing something, I think we can (maybe should) apply this |
@TjarkMiener Yes, that should work. The calibration events would only need the sqrt coeficcients. Only thing is that this needs to be documented somewhere. |
Though, the changes to the variance extractor should then still be kept so that it works with the calibrator. |
@@ -61,6 +62,11 @@ class CameraCalibrator(TelescopeComponent): | |||
image_extractor_type: str | |||
The name of the ImageExtractor subclass to be used for image extraction | |||
image_calibration_type: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this. Instead, you can check the type of the image extractor.
if isinstance(VarianceExtractor, extractor):
#apply squared gain calibration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll do that
@@ -73,6 +79,15 @@ class CameraCalibrator(TelescopeComponent): | |||
help="Name of the ImageExtractor subclass to be used.", | |||
).tag(config=True) | |||
|
|||
image_calibration_type = CaselessStrEnum( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see l.66
if ( | ||
self.apply_peak_time_shift.tel[tel_id] | ||
and remaining_shift is not None | ||
and self.image_calibration_type == "charge" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to alter this condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variance extractor has no peak time, so applying the peak time shift otherwise throws an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just check if dl1.peak_time is not None
instead of a specific extractor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maxnoe that makes sense
@@ -292,13 +311,21 @@ def _calibrate_dl1(self, event, tel_id): | |||
and dl1_calib.absolute_factor is not None | |||
): | |||
if selected_gain_channel is None: | |||
dl1.image *= dl1_calib.relative_factor / dl1_calib.absolute_factor | |||
if self.image_calibration_type == "charge": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See l.66
calibrator(example_event) | ||
image = example_event.dl1.tel[tel_id].image | ||
assert image is not None | ||
assert image.shape == (1764,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from where this shape comes? What telescope is this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is from the example event. Not sure what telescope this is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FlashCam from the number of pixels
src/ctapipe/image/extractor.py
Outdated
@@ -1308,7 +1308,7 @@ def __call__( | |||
self, waveforms, tel_id, selected_gain_channel, broken_pixels | |||
) -> DL1CameraContainer: | |||
container = DL1CameraContainer( | |||
image=np.nanvar(waveforms, dtype="float32", axis=2), | |||
image=np.nanvar(waveforms, dtype="float32", axis=2)[0], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shall be able to work with multiple gains, why do you introduce this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
np.nanvar was giving me an extra dimension on top of the gain. I think I can fix this with the keepdims parameter
if self.image_calibration_type == "charge": | ||
dl1.image *= dl1_calib.relative_factor / dl1_calib.absolute_factor | ||
elif self.image_calibration_type == "variance": | ||
dl1.image *= np.sqrt( | ||
dl1_calib.relative_factor / dl1_calib.absolute_factor | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could check here if the extractor.__class__.__name__
is VarianceExtractor
rather than passing the specific config parameter.
This comment has been minimized.
This comment has been minimized.
src/ctapipe/image/extractor.py
Outdated
@@ -1308,7 +1308,7 @@ def __call__( | |||
self, waveforms, tel_id, selected_gain_channel, broken_pixels | |||
) -> DL1CameraContainer: | |||
container = DL1CameraContainer( | |||
image=np.nanvar(waveforms, dtype="float32", axis=2), | |||
image=np.nanvar(waveforms, dtype="float32", keepdims=False, axis=2), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit weird that you need to set keepdims=False
explicitly, but ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will check again if i make it work without that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should work without it:
x = np.random.uniform(size=(2,1800, 1000))
In [1]: np.nanvar(x, axis=2).shape
Out[2]: (2, 1800)
In [2]: np.nanvar(x, axis=2, keepdims=True).shape
Out[2]: (2, 1800, 1)
In [3]: np.nanvar(x, axis=2, keepdims=False).shape
Out[3]: (2, 1800)
calibrator(example_event) | ||
image = example_event.dl1.tel[tel_id].image | ||
assert image is not None | ||
assert image.shape == ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test with the LST (two gains)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to add a test for other extractor with the LST?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use a parametrized test here to test all telescope types. (@pytest.mark.parametrized
). See for example the code in ctapipe/instrument/tests/test_telescope.py
@@ -292,13 +296,21 @@ def _calibrate_dl1(self, event, tel_id): | |||
and dl1_calib.absolute_factor is not None | |||
): | |||
if selected_gain_channel is None: | |||
dl1.image *= dl1_calib.relative_factor / dl1_calib.absolute_factor | |||
if extractor.__class__.__name__ == "VarianceExtractor": | |||
dl1.image *= np.sqrt( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be square, not square root
else: | ||
corr = ( | ||
dl1_calib.relative_factor[selected_gain_channel, pixel_index] | ||
/ dl1_calib.absolute_factor[selected_gain_channel, pixel_index] | ||
) | ||
dl1.image *= corr | ||
if extractor.__class__.__name__ == "VarianceExtractor": | ||
dl1.image *= np.sqrt(corr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as l.300
This comment has been minimized.
This comment has been minimized.
docs/changes/2636.features.rst
Outdated
@@ -0,0 +1 @@ | |||
Makes changes to the CameraCalibrator in ctapipe.calib.camera.calibrator that allows it to correctly variance images generated with the VarianceExtractor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please describe the actual change, not just say "there are changes". Also there is a verb missing here (calibrate?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i added some explanation
docs/changes/2636.features.rst
Outdated
@@ -0,0 +1,3 @@ | |||
Makes changes to the CameraCalibrator in ctapipe.calib.camera.calibrator that allows it to correctly calibrate variance images generated with the VarianceExtractor | |||
if the VarianceExtractor is used for the CameraCalibrator the element-wise square of the relative and absolute gain calibration factors are applied to the image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please format according to ReST syntax. This will render as a single line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1 similar comment
Analysis Details2 IssuesCoverage and DuplicationsProject ID: cta-observatory_ctapipe_AY52EYhuvuGcMFidNyUs |
I think this is the way to go, compute the appropriate calibration coefficients directly instead of adding special casing in the application. Also: shouldn't it be square and not square root for the variance? |
@maxnoe square is correct. If we apply the square operation when writing the calibration corrections then we will not need this PR |
You still need the peak_time check, right? |
Oh yes, that will be still needed |
I discussed with @mexanick offline yesterday. We might not want to run the tool for computing camera calibration coefficients twice and store two separated files (one for comics; one for pointing calibration), just to square one column here. What do you think? |
at least for now, I would avoid recomputation of the calibrations just for variance. With the current code, we can always have this possibility to test, if needed. |
else: | ||
corr = ( | ||
dl1_calib.relative_factor[selected_gain_channel, pixel_index] | ||
/ dl1_calib.absolute_factor[selected_gain_channel, pixel_index] | ||
) | ||
dl1.image *= corr | ||
if extractor.__class__.__name__ == "VarianceExtractor": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use this to check type, not a string comparison (which is slower and makes refactoring more difficult later)
if extractor.__class__.__name__ == "VarianceExtractor": | |
if isinstance(extractor, VarianceExtractor): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, im changing it now
@@ -292,13 +296,21 @@ def _calibrate_dl1(self, event, tel_id): | |||
and dl1_calib.absolute_factor is not None | |||
): | |||
if selected_gain_channel is None: | |||
dl1.image *= dl1_calib.relative_factor / dl1_calib.absolute_factor | |||
if extractor.__class__.__name__ == "VarianceExtractor": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if extractor.__class__.__name__ == "VarianceExtractor": | |
if isinstance(extractor, VarianceExtractor): |
calibrator(example_event) | ||
image = example_event.dl1.tel[tel_id].image | ||
assert image is not None | ||
assert image.shape == ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use a parametrized test here to test all telescope types. (@pytest.mark.parametrized
). See for example the code in ctapipe/instrument/tests/test_telescope.py
i will change the test to check all cameras |
@kosack I used the camera_geometry fixture instead to check for all cameras. In the end I just need the geometry, so that should work well. |
I think there is an issue with the recent change to the changelog check (#2637). |
i switched it to a parametrized test for the different cameras now and caught the other test for name and switched it to isinstance |
@@ -292,13 +296,21 @@ def _calibrate_dl1(self, event, tel_id): | |||
and dl1_calib.absolute_factor is not None | |||
): | |||
if selected_gain_channel is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this section could use some refactoring to make it easier to follow.
I think the logic is the same if we simplify to:
if selected_gain_channel is None:
calibration = dl1_calib.relative_factor / dl1_calib.absolute_factor
else:
calibration = (
dl1_calib.relative_factor[selected_gain_channel, pixel_index]
/ dl1_calib.absolute_factor[selected_gain_channel, pixel_index]
)
if isinstance(extractor, VarianceExtractor):
calibration = calibration**2
dl1.image *= calibration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll get to it when I arrive home
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i put it in
This PR adds a trait to the CameraCalibrator called "image_calibration_type". This trait allows to switch the method of calibration between "charge" and "variance", with the former being the calibration method for the existing image extractors and the latter being the method to calibrate variance images.
I also made some small adjustment to the variance extractor so that it works in the CameraCalibrator.