Noise calibration pipeline implemented#111
Conversation
|
|
||
|
|
||
| class CalibrationMatrixBase: | ||
| class CalibrationMatrix: |
There was a problem hiding this comment.
This is the new unified class for the calibration. At the moment the two implemented type of calibrations are for noise and gain, but in the future we're planning to add also pedestal calibration.
The two main attributes of the class are matrix and hits, which contain the information about the calibration results. We are thinking about adding new attributes, such as the uncertainty of the results.
| return self.matrix[row, col] | ||
|
|
||
|
|
||
| class CalibrationMatrixGain(CalibrationMatrixBase): |
There was a problem hiding this comment.
The old classes, that contained the info that are now in CalibrationMatrix, have been replaced by new classes that handles only the calibration process.
These classes take a CalibrationMatrix as an argument, and modify its attributes during the calibration process.
| """ | ||
| self._scalar_gain = isinstance(self.readout.gain, (int, float)) | ||
|
|
||
| def _gain(self, row: np.ndarray, col: np.ndarray) -> np.ndarray: |
There was a problem hiding this comment.
I have removed the old interface to select the exact value of the gain for a certain pixel/ROI. Now this can be done directly calling CalibrationMatrix(col, row)
|
|
||
|
|
||
|
|
||
| if kwargs.get("map_gain_file") is not None: | ||
| gain_file = CalibrationMatrixGain.from_hdf5(kwargs.get("map_gain_file")) | ||
| kwargs.update({"gain": gain_file.matrix}) | ||
| gain_matrix = CalibrationMatrix.from_hdf5(kwargs.get("map_gain_file")) | ||
| kwargs.update({"gain": gain_matrix}) | ||
| else: | ||
| default_gain = CalibrationMatrix(kwargs["num_cols"], kwargs["num_rows"]) | ||
| default_gain.set_value(kwargs["gain"]) | ||
| kwargs.update({"gain": default_gain}) | ||
| if kwargs.get("map_enc_file") is not None: | ||
| enc_matrix = CalibrationMatrix.from_hdf5(kwargs.get("map_enc_file")) | ||
| enc_matrix.fill(enc_matrix.mean(), max_hits=0) | ||
| kwargs.update({"enc": enc_matrix}) | ||
|
|
||
| else: | ||
| default_enc = CalibrationMatrix(kwargs["num_cols"], kwargs["num_rows"]) | ||
| default_enc.set_value(kwargs["enc"]) | ||
| kwargs.update({"enc": default_enc}) | ||
|
|
||
|
|
||
|
|
There was a problem hiding this comment.
We need to fix this, now it's completely nonsense. If we want to pass always a calibration file, this can be immediately solved. In the other case, we need to understand how to handle the creation of the matrix with a scalar value.
| gain_map = CalibrationMatrixGain.from_hdf5(map_gain_file).matrix | ||
| gain_map = CalibrationMatrix.from_hdf5(map_gain_file).matrix | ||
| else: | ||
| gain_map = None | ||
| noise_map_file = kwargs.get("map_enc_file") | ||
| if noise_map_file is not None: | ||
| noise_map = CalibrationMatrix.from_hdf5(noise_map_file).matrix | ||
| else: | ||
| noise_map = None |
There was a problem hiding this comment.
Same as before: do we always want to use a calibration file? If yes, the if/else clause is not necessary, but we just load the matrix from the path.
| enc: "CalibrationMatrix" = None | ||
| gain: "CalibrationMatrix" = None |
There was a problem hiding this comment.
I don't like this initialization, any idea on how to solve it?
| def digitize(self, pha: np.ndarray, | ||
| coords: Union[RegionOfInterest, list[Tuple[int, int]]]) -> np.ndarray: | ||
| """Digitize the actual signal. | ||
|
|
||
| Arguments | ||
| --------- | ||
| pha : array_like | ||
| The input array of pixel signals to be digitized. | ||
| roi : RegionOfInterest, optional | ||
| The region of interest to be read out, used to digitize rectangular readout events. | ||
| coords : sequence of (col, row) tuples or None, optional | ||
| The coordinates of the pixels to be read out, used to digitize circular readout events. | ||
| """ | ||
| # Note that the array type of the input pha argument is not guaranteed, here. | ||
| # Over the course of the calculation the pha is bound to be a float (the noise | ||
| # and the gain are floating-point numbere) before it is rounded to the nearest | ||
| # integer. In order to take advantage of the automatic type casting that | ||
| # numpy implements in multiplication and addition, we use the pha = pha +/* | ||
| # over the pha +/*= form. | ||
| # See https://stackoverflow.com/questions/38673531 | ||
| # | ||
| # Add the noise. | ||
| if self.enc > 0: | ||
| pha = pha + rng.generator.normal(0., self.enc, size=pha.shape) | ||
| # ... apply the conversion between electrons and ADC counts, using the gain matrix if | ||
| # provied, otherwise using the same gain parameter for all the pixels... | ||
| if isinstance(self.gain, float): | ||
| pha = pha * self.gain | ||
| # Create cols and rows arrays from the input coordinates, depending on the readout mode. | ||
| # In case of rectangular readout, we have a RegionOfInterest, otherwise we have a list with | ||
| # the coordinates of the pixels to be read out. | ||
| if isinstance(coords, RegionOfInterest): | ||
| cols, rows = coords.readout_slice() | ||
| else: | ||
| # If we are digitizing circular readout events, create the gain array to apply to | ||
| # the pha by accessing the gain matrix at the coordinates of the pixels to be read out. | ||
| if coords is not None: | ||
| gain_array = np.empty_like(pha, dtype=float) | ||
| for i, coord in enumerate(coords): | ||
| col, row = coord | ||
| gain_array[i] = self.gain[row, col] | ||
| pha = pha * gain_array | ||
| # If we are digitizing rectangular readout events, use the roi slices to access the | ||
| # gain matrix and create the gain array to apply to the pha. | ||
| elif roi is not None: | ||
| row_slice, col_slice = roi.readout_slice() | ||
| gain_array = self.gain[row_slice, col_slice] | ||
| pha = pha * gain_array | ||
| # ... round to the nearest integer... | ||
| cols, rows = np.array(coords).T | ||
| # Add the noise | ||
| noise = rng.generator.normal(0., scale=self.enc(rows, cols)) | ||
| pha = pha + noise | ||
| # Apply the conversion between electrons and ADC counts | ||
| pha = pha * self.gain(rows, cols) | ||
| # Round to the nearest integer | ||
| pha = np.round(pha).astype(int) | ||
| # ... if necessary, add the offset... | ||
| # Add the offset | ||
| pha += self.offset | ||
| # ... zero suppress the thing... | ||
| # Zero suppress the thing. | ||
| self.zero_suppress(pha, self.zero_sup_threshold) | ||
| # ... flatten the array to simulate the serial readout and return the | ||
| # Flatten the array to simulate the serial readout and return the | ||
| # array as the BEE would have. | ||
| return pha.flatten() |
There was a problem hiding this comment.
This section was modified a few weeks ago to use the gain matrix, but it became too complicated. I just recovered the older versions and to small changes to use the new CalibrationMatrix callable interface.
The only difference with respect to older versions is that now digitize needs a ROI or a set of coordinates related to the PHA array, because we need to access the calibration matrices in the right positions.
| gain_map: Optional[CalibrationMatrix] = None | ||
| noise_map: Optional[CalibrationMatrix] = None |
There was a problem hiding this comment.
Same problem as HexagonalReadout attributes, I don't like to initialize these arguments as None, but I don't have any idea.
| if gain_map is None: | ||
| gain_map = CalibrationMatrix(header["num_cols"], header["num_rows"]) | ||
| gain_map.set_value(header["gain"]) | ||
| if noise_map is None: | ||
| noise_map = CalibrationMatrix(header["num_cols"], header["num_rows"]) | ||
| noise_map.set_value(header["enc"]) |
There was a problem hiding this comment.
If we always use a calibration matrix file, these lines can be deleted.
|
We are converging to the decision of dropping the use of scalar values in the simulation and reconstruction. This can simplify a lot of things |
No description provided.