@@ -56,6 +56,7 @@ class RawImageModel(BaseModel):
56
56
stride : int
57
57
format : str
58
58
59
+
59
60
class PicameraControl (PropertyDescriptor ):
60
61
def __init__ (
61
62
self , control_name : str , model : type = float , description : Optional [str ] = None
@@ -91,7 +92,9 @@ def __init__(self, stream: MJPEGStream, portal: BlockingPortal):
91
92
self .stream = stream
92
93
self .portal = portal
93
94
94
- def outputframe (self , frame , _keyframe = True , _timestamp = None , _packet = None , _audio = False ):
95
+ def outputframe (
96
+ self , frame , _keyframe = True , _timestamp = None , _packet = None , _audio = False
97
+ ):
95
98
"""Add a frame to the stream's ringbuffer"""
96
99
self .stream .add_frame (frame , self .portal )
97
100
@@ -126,7 +129,9 @@ class ImageProcessingInputs(BaseModel):
126
129
colour_gains : tuple [float , float ]
127
130
white_norm_lores : NDArray
128
131
raw_size : tuple [int , int ]
129
- colour_correction_matrix : tuple [float , float , float , float , float , float , float , float , float ]
132
+ colour_correction_matrix : tuple [
133
+ float , float , float , float , float , float , float , float , float
134
+ ]
130
135
gamma : NDArray
131
136
132
137
@@ -137,7 +142,7 @@ class ImageProcessingCache:
137
142
ccm : np .ndarray
138
143
139
144
140
- class BlobNumpyDict (BlobBytes ):
145
+ class BlobNumpyDict (BlobBytes ):
141
146
def __init__ (self , arrays : Mapping [str , np .ndarray ]):
142
147
self ._arrays = arrays
143
148
self ._bytesio : Optional [io .BytesIO ] = None
@@ -148,7 +153,7 @@ def arrays(self) -> Mapping[str, np.ndarray]:
148
153
return self ._arrays
149
154
150
155
@property
151
- def _bytes (self ) -> bytes : # noqa mypy: override
156
+ def _bytes (self ) -> bytes : # noqa mypy: override
152
157
"""Generate binary content on-the-fly from numpy data"""
153
158
if not self ._bytesio :
154
159
out = io .BytesIO ()
@@ -164,18 +169,14 @@ class NumpyBlob(Blob):
164
169
def from_arrays (cls , arrays : Mapping [str , np .ndarray ]) -> Self :
165
170
return cls .model_construct ( # type: ignore[return-value]
166
171
href = "blob://local" ,
167
- _data = BlobNumpyDict (
168
- arrays ,
169
- media_type = cls .default_media_type ()
170
- ),
172
+ _data = BlobNumpyDict (arrays , media_type = cls .default_media_type ()),
171
173
)
172
174
173
175
174
-
175
176
def raw2rggb (raw : np .ndarray , size : tuple [int , int ]) -> np .ndarray :
176
177
"""Convert packed 10 bit raw to RGGB 8 bit"""
177
178
raw = np .asarray (raw ) # ensure it's an array
178
- output_shape = (size [1 ]// 2 , size [0 ]// 2 , 4 )
179
+ output_shape = (size [1 ] // 2 , size [0 ] // 2 , 4 )
179
180
rggb = np .empty (output_shape , dtype = np .uint8 )
180
181
raw_w = rggb .shape [1 ] // 2 * 5
181
182
for plane , offset in enumerate ([(1 , 1 ), (0 , 1 ), (1 , 0 ), (0 , 0 )]):
@@ -482,7 +483,7 @@ def start_streaming(self, main_resolution: tuple[int, int] = (820, 616)) -> None
482
483
stream_config ["buffer_count" ] = 4
483
484
picam .configure (stream_config )
484
485
logging .info ("Starting picamera MJPEG stream..." )
485
- stream_name = ' lores' if main_resolution [0 ] > 1280 else ' main'
486
+ stream_name = " lores" if main_resolution [0 ] > 1280 else " main"
486
487
picam .start_recording (
487
488
MJPEGEncoder (self .mjpeg_bitrate ),
488
489
PicameraStreamOutput (
@@ -507,7 +508,7 @@ def start_streaming(self, main_resolution: tuple[int, int] = (820, 616)) -> None
507
508
logging .debug (
508
509
"Started MJPEG stream at %s on port %s" , self .stream_resolution , 1
509
510
)
510
-
511
+
511
512
@thing_action
512
513
def stop_streaming (self , stop_web_stream = True ) -> None :
513
514
"""
@@ -589,15 +590,15 @@ def capture_array(
589
590
def capture_raw (
590
591
self ,
591
592
states_getter : GetThingStates ,
592
- get_states : bool = True ,
593
- get_processing_inputs : bool = True ,
593
+ get_states : bool = True ,
594
+ get_processing_inputs : bool = True ,
594
595
wait : Optional [float ] = 0.9 ,
595
596
) -> RawImageModel :
596
597
"""Capture a raw image
597
-
598
+
598
599
This function is intended to be as fast as possible, and will return
599
600
as soon as an image has been captured. The output format is not intended
600
- to be useful, except as input to `raw_to_png`.
601
+ to be useful, except as input to `raw_to_png`.
601
602
602
603
wait: (Optional, float) Set a timeout in seconds.
603
604
A TimeoutError is raised if this time is exceeded during capture.
@@ -608,18 +609,22 @@ def capture_raw(
608
609
transferring it over the network.
609
610
"""
610
611
with self .picamera () as cam :
611
- (buffer , ), parameters = cam .capture_buffers (["raw" ], wait = wait )
612
+ (buffer ,), parameters = cam .capture_buffers (["raw" ], wait = wait )
612
613
configuration = cam .camera_configuration ()
613
614
return RawImageModel (
614
- image_data = RawBlob .from_bytes (buffer .tobytes ()),
615
- thing_states = states_getter () if get_states else None ,
616
- metadata = { "parameters" : parameters , "sensor" : configuration ["sensor" ], "tuning" : self .tuning },
617
- processing_inputs = (
615
+ image_data = RawBlob .from_bytes (buffer .tobytes ()),
616
+ thing_states = states_getter () if get_states else None ,
617
+ metadata = {
618
+ "parameters" : parameters ,
619
+ "sensor" : configuration ["sensor" ],
620
+ "tuning" : self .tuning ,
621
+ },
622
+ processing_inputs = (
618
623
self .image_processing_inputs if get_processing_inputs else None
619
624
),
620
- size = configuration ["raw" ]["size" ],
621
- format = configuration ["raw" ]["format" ],
622
- stride = configuration ["raw" ]["stride" ],
625
+ size = configuration ["raw" ]["size" ],
626
+ format = configuration ["raw" ]["format" ],
627
+ stride = configuration ["raw" ]["stride" ],
623
628
)
624
629
625
630
@thing_property
@@ -657,32 +662,32 @@ def generate_image_processing_cache(
657
662
p : ImageProcessingInputs ,
658
663
) -> ImageProcessingCache :
659
664
"""Prepare to process raw images
660
-
665
+
661
666
This is a static method to ensure its outputs depend only on its
662
667
inputs."""
663
668
zoom_factors = [
664
669
i / 2 / n for i , n in zip (p .raw_size [::- 1 ], p .white_norm_lores .shape [:2 ])
665
670
] + [1 ]
666
671
white_norm = zoom (p .white_norm_lores , zoom_factors , order = 1 )[
667
- : (p .raw_size [1 ]// 2 ), : (p .raw_size [0 ]// 2 ), :
672
+ : (p .raw_size [1 ] // 2 ), : (p .raw_size [0 ] // 2 ), :
668
673
]
669
- ccm = np .array (p .colour_correction_matrix ).reshape ((3 ,3 ))
674
+ ccm = np .array (p .colour_correction_matrix ).reshape ((3 , 3 ))
670
675
gamma = interp1d (p .gamma [:, 0 ] / 255 , p .gamma [:, 1 ] / 255 )
671
676
return ImageProcessingCache (
672
677
white_norm = white_norm ,
673
- ccm = ccm ,
674
- gamma = gamma ,
678
+ ccm = ccm ,
679
+ gamma = gamma ,
675
680
)
676
681
677
682
_image_processing_cache : ImageProcessingCache | None = None
683
+
678
684
@thing_action
679
685
def prepare_image_normalisation (
680
- self ,
681
- inputs : ImageProcessingInputs | None = None
686
+ self , inputs : ImageProcessingInputs | None = None
682
687
) -> ImageProcessingInputs :
683
688
"""The parameters used to convert raw image data into processed images
684
-
685
- NB this method uses only information from `inputs` or
689
+
690
+ NB this method uses only information from `inputs` or
686
691
`self.image_processing_inputs`, to ensure repeatability
687
692
"""
688
693
p = inputs or self .image_processing_inputs
@@ -694,7 +699,7 @@ def process_raw_array(
694
699
self ,
695
700
raw : RawImageModel ,
696
701
use_cache : bool = False ,
697
- )-> NDArray :
702
+ ) -> NDArray :
698
703
"""Convert a raw image to a processed array"""
699
704
if not use_cache :
700
705
if raw .processing_inputs is None :
@@ -703,26 +708,22 @@ def process_raw_array(
703
708
"and we are not using the cache. This may be solved by "
704
709
"capturing with `get_processing_inputs=True`."
705
710
)
706
- self .prepare_image_normalisation (
707
- raw .processing_inputs
708
- )
711
+ self .prepare_image_normalisation (raw .processing_inputs )
709
712
p = self ._image_processing_cache
710
713
assert p is not None
711
714
assert raw .format == "SBGGR10_CSI2P"
712
715
buffer = np .frombuffer (raw .image_data .content , dtype = np .uint8 )
713
716
packed = buffer .reshape ((- 1 , raw .stride ))
714
717
rgb = rggb2rgb (raw2rggb (packed , raw .size ))
715
718
normed = rgb / p .white_norm
716
- corrected = np .dot (
717
- p .ccm , normed .reshape ((- 1 , 3 )).T
718
- ).T .reshape (normed .shape )
719
+ corrected = np .dot (p .ccm , normed .reshape ((- 1 , 3 )).T ).T .reshape (normed .shape )
719
720
corrected [corrected < 0 ] = 0
720
721
corrected [corrected > 255 ] = 255
721
722
processed_image = p .gamma (corrected )
722
723
return processed_image .astype (np .uint8 )
723
724
724
725
@thing_action
725
- def raw_to_png (self , raw : RawImageModel , use_cache : bool = False )-> PNGBlob :
726
+ def raw_to_png (self , raw : RawImageModel , use_cache : bool = False ) -> PNGBlob :
726
727
"""Process a raw image to a PNG"""
727
728
arr = self .process_raw_array (raw = raw , use_cache = use_cache )
728
729
image = Image .fromarray (arr .astype (np .uint8 ), mode = "RGB" )
@@ -838,15 +839,6 @@ def grab_jpeg_size(
838
839
)
839
840
return portal .call (stream .next_frame_size )
840
841
841
- # @thing_action
842
- # def capture_to_scan(
843
- # self,
844
- # scan_manager: ScanManager,
845
- # format: Literal["jpeg"] = "jpeg",
846
- # ) -> None:
847
- # with scan_manager.new_jpeg() as output, self.picamera() as cam:
848
- # cam.capture_file(output, format="jpeg")
849
-
850
842
@thing_property
851
843
def exposure (self ) -> float :
852
844
"""An alias for `exposure_time` to fit the micromanager API"""
0 commit comments