Skip to content

Commit a7daca4

Browse files
Introduce bilinear demosaicing
1 parent a8f2cc5 commit a7daca4

File tree

1 file changed

+80
-3
lines changed

1 file changed

+80
-3
lines changed

src/labthings_picamera2/thing.py

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,70 @@ def from_arrays(cls, arrays: Mapping[str, np.ndarray]) -> Self:
172172
)
173173

174174

175+
GREEN_KERNEL = np.asarray(
176+
[[0, 0.25, 0],
177+
[0.25, 1, 0.25],
178+
[0, 0.25, 0]]
179+
)
180+
181+
RED_BLUE_KERNEL = np.asarray(
182+
[[0.25, 0.5, 0.25],
183+
[0.5, 1, 0.5],
184+
[0.25, 0.5, 0.25]]
185+
)
186+
187+
def raw_to_8bit_bayer(raw: np.ndarray, size: tuple[int, int]) -> np.ndarray:
188+
"""Convert packed 10 bit raw to 8 bit Raw bayer data"""
189+
raw = np.asarray(raw) # ensure it's an array
190+
output_shape = (size[1], size[0])
191+
bayer8bit = np.empty(output_shape, dtype=np.uint8)
192+
# raw_w is Raw data width in bytes which is:
193+
# pixel width * bits_per_pixel / bits_per_byte
194+
# This is calculated as below because the data is saved as:
195+
# [8-bit R pixel, 8-bit G pixel, 8-bit R pixel, 8-bit G pixel, extra bits]
196+
# where the extra bits are the 2 bits for the previous 4 pixels
197+
raw_w = bayer8bit.shape[1] // 4 * 5
198+
# Red
199+
bayer8bit[:,::4] = raw[:, : raw_w : 5]
200+
# Green 1
201+
bayer8bit[:,1::4] = raw[:, 1: raw_w+1 : 5]
202+
# Green 2
203+
bayer8bit[:,2::4] = raw[:, 2: raw_w+2 : 5]
204+
# Blue
205+
bayer8bit[:,3::4] = raw[:, 3: raw_w+3 : 5]
206+
return bayer8bit
207+
208+
def bayer_masks(
209+
shape: tuple[int, int],
210+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
211+
"""
212+
Return the Bayer red, green and blue masks
213+
"""
214+
215+
r = np.zeros(shape, dtype="bool")
216+
r[::2,::2] = 1
217+
g = np.zeros(shape, dtype="bool")
218+
g[1::2,::2] = 1
219+
g[::2,1::2] = 1
220+
b = np.zeros(shape, dtype="bool")
221+
b[1::2,1::2] = 1
222+
223+
return r,g,b
224+
225+
def demosaicing_bilinear(bayer8bit: np.ndarray) -> np.ndarray:
226+
"""
227+
Demosaic using a bilinear algorithm taken from the library
228+
colour_demosaicing
229+
"""
230+
bayer8bit.astype(np.double)
231+
232+
r_mask, g_mask, b_mask = bayer_masks(bayer8bit.shape)
233+
234+
r = convolve(bayer8bit * r_mask, RED_BLUE_KERNEL)
235+
g = convolve(bayer8bit * g_mask, GREEN_KERNEL)
236+
b = convolve(bayer8bit * b_mask, RED_BLUE_KERNEL)
237+
238+
return np.dstack((r,g,b))
175239

176240
def raw2rggb(raw: np.ndarray, size: tuple[int, int]) -> np.ndarray:
177241
"""Convert packed 10 bit raw to RGGB 8 bit"""
@@ -642,6 +706,7 @@ def process_raw_array(
642706
self,
643707
raw: RawImageModel,
644708
use_cache: bool = False,
709+
bilinear_demosaic: bool = True,
645710
)->NDArray:
646711
"""Convert a raw image to a processed array"""
647712
if not use_cache:
@@ -659,7 +724,10 @@ def process_raw_array(
659724
assert raw.format == "SBGGR10_CSI2P"
660725
buffer = np.frombuffer(raw.image_data.content, dtype=np.uint8)
661726
packed = buffer.reshape((-1, raw.stride))
662-
rgb = rggb2rgb(raw2rggb(packed, raw.size))
727+
if bilinear_demosaic:
728+
rgb = demosaicing_bilinear(raw_to_8bit_bayer(packed, raw.size))
729+
else:
730+
rgb = rggb2rgb(raw2rggb(packed, raw.size))
663731
normed = rgb / p.white_norm
664732
corrected = np.dot(
665733
p.ccm, normed.reshape((-1, 3)).T
@@ -670,9 +738,18 @@ def process_raw_array(
670738
return processed_image.astype(np.uint8)
671739

672740
@thing_action
673-
def raw_to_png(self, raw: RawImageModel, use_cache: bool = False)->PNGBlob:
741+
def raw_to_png(
742+
self,
743+
raw: RawImageModel,
744+
use_cache: bool = False,
745+
bilinear_demosaic: bool = True,
746+
)->PNGBlob:
674747
"""Process a raw image to a PNG"""
675-
arr = self.process_raw_array(raw=raw, use_cache=use_cache)
748+
arr = self.process_raw_array(
749+
raw=raw,
750+
use_cache=use_cache,
751+
bilinear_demosaic=bilinear_demosaic
752+
)
676753
image = Image.fromarray(arr.astype(np.uint8), mode="RGB")
677754
out = io.BytesIO()
678755
image.save(out, format="png")

0 commit comments

Comments
 (0)