Coverage for src/susi/reduc/hot_pixels/hot_pixels_correction.py: 81%
83 statements
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4hot_pixels module provides HotPixel correction class
6@author: iglesias
7"""
8from typing import Iterable
9import numpy as np
10from scipy import ndimage
12from ...base import Logging, Config, Globals
13from ...io import Fits
15logger = Logging.get_logger()
18class HOTPXCorrector:
19 """
20 Class to filter outlier pixels from the input images (they are replaced by they neithgbouring median).
21 Outliers are those beyod np.mean(diff)+-self.std_criterion*np.std(diff), where is the difference between
22 the input image and its smoothed version (using a median filter with window size self.median_win).
23 The main parameters controlling the algorithm are given in _load_corr_mode.
24 """
25 def __init__(self, config: Config, data: Fits):
26 self.config = config
27 # Expected data shape is (frames, x, y)
28 self.data = data.data.astype(float)
29 self.std_criterion = None
30 self.median_win = None
31 self.use_edges = None
33 def run(self) -> np.array:
34 if self._load_corr_mode():
35 logger.debug('Running hot pixel correction')
36 for i in range(self.data.shape[0]):
37 self._correct_frame(i)
38 return self.data
40 def _load_corr_mode(self) -> tuple:
41 """
42 :param self.mode defines the type of image to filter. Supported modes:
43 -"dark"
44 -"polcal"
45 -"slitjaw" TODO
46 -"spectra" TODO
47 Returns:
48 std_criterion: Criterion to identified statistical outliers
49 median_win: size of the median filter window [x,y]
50 use_edges: Set to also filter the edges (border row and col). It is slower
51 """
52 try:
53 self.std_criterion = Globals.CAM_HOT_PX_MODES[self.config.cam.hot_px_mode]['std']
54 self.median_win = Globals.CAM_HOT_PX_MODES[self.config.cam.hot_px_mode]['win']
55 self.use_edges = Globals.CAM_HOT_PX_MODES[self.config.cam.hot_px_mode]['use_edges']
56 except KeyError:
57 logger.warning('Value of config.cam_hot_px_mode not recognized, skipping hot px correction')
58 return False
59 return True
61 def _correct_frame(self, frame_no) -> np.array:
62 """
63 Filters out outlier pixels in frame number frame_no of self.data
65 INPUTS
66 :param img: numpy array with a single image [x,y]
67 """
68 img = self.data[frame_no]
69 blurred = ndimage.median_filter(img, size=self.median_win)
70 difference = img - blurred
71 threshold = np.mean(difference) + (self.std_criterion * np.std(difference))
72 # find the hot pixels, but ignore the edges
73 hot_pixels = np.array(np.nonzero((np.abs(difference[1:-1, 1:-1]) > threshold))) + 1
75 result = np.copy(img)
76 for y, x in zip(hot_pixels[0], hot_pixels[1]):
77 result[y, x] = blurred[y, x]
79 if self.use_edges:
80 height, width = np.shape(img)
82 # ## Now get the pixels on the edges (but not the corners) ## #
83 # left and right sides
84 for index in range(1, height - 1):
85 # left side:
86 med = np.median(img[index - 1:index + 2, 0:2])
87 diff = np.abs(img[index, 0] - med)
88 if diff > threshold:
89 hot_pixels = np.hstack((hot_pixels, [[index], [0]]))
90 result[index, 0] = med
92 # right side:
93 med = np.median(img[index - 1:index + 2, -2:])
94 diff = np.abs(img[index, -1] - med)
95 if diff > threshold:
96 hot_pixels = np.hstack((hot_pixels, [[index], [width - 1]]))
97 result[index, -1] = med
99 # Then the top and bottom
100 for index in range(1, width - 1):
101 # bottom:
102 med = np.median(img[0:2, index - 1:index + 2])
103 diff = np.abs(img[0, index] - med)
104 if diff > threshold:
105 hot_pixels = np.hstack((hot_pixels, [[0], [index]]))
106 result[0, index] = med
108 # top:
109 med = np.median(img[-2:, index - 1:index + 2])
110 diff = np.abs(img[-1, index] - med)
111 if diff > threshold:
112 hot_pixels = np.hstack((hot_pixels, [[height - 1], [index]]))
113 result[-1, index] = med
115 # ## Then the corners ## #
116 # bottom left
117 med = np.median(img[0:2, 0:2])
118 diff = np.abs(img[0, 0] - med)
119 if diff > threshold:
120 hot_pixels = np.hstack((hot_pixels, [[0], [0]]))
121 result[0, 0] = med
123 # bottom right
124 med = np.median(img[0:2, -2:])
125 diff = np.abs(img[0, -1] - med)
126 if diff > threshold:
127 hot_pixels = np.hstack((hot_pixels, [[0], [width - 1]]))
128 result[0, -1] = med
130 # top left
131 med = np.median(img[-2:, 0:2])
132 diff = np.abs(img[-1, 0] - med)
133 if diff > threshold:
134 hot_pixels = np.hstack((hot_pixels, [[height - 1], [0]]))
135 result[-1, 0] = med
137 # top right
138 med = np.median(img[-2:, -2:])
139 diff = np.abs(img[-1, -1] - med)
140 if diff > threshold:
141 hot_pixels = np.hstack((hot_pixels, [[height - 1], [width - 1]]))
142 result[-1, -1] = med
144 self.data[frame_no] = result