@@ -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
176240def 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