11from __future__ import division
22
33import multiprocessing
4- import time
54import queue
65
7- from dls_util .image import Image , Color
8- from dls_util import Beeper
9- from dls_barcode .scan import GeometryScanner , SlotScanner , OpenScanner
10- from dls_barcode .datamatrix import DataMatrix
11- from .overlay import PlateOverlay , TextOverlay
126from .capture_worker import CaptureWorker
7+ from .scanner_worker import ScannerWorker
138from .camera_position import CameraPosition
149from .stream_action import StreamAction
1510from .capture_command import CaptureCommand
1611
17- Q_LIMIT = 1
18- SCANNED_TAG = "Scan Complete"
19- NO_PUCK_TIME = 2
20-
21- EXIT_KEY = 'q'
22-
23- # Maximum frame rate to sample at (rate will be further limited by speed at which frames can be processed)
24- MAX_SAMPLE_RATE = 10.0
25- INTERVAL = 1.0 / MAX_SAMPLE_RATE
26-
2712
2813class CameraScanner :
2914 """ Manages the continuous scanning mode which takes a live feed from an attached camera and
@@ -33,7 +18,7 @@ class CameraScanner:
3318 Two separate processes are spawned, one to handle capturing and displaying images from the cameras,
3419 and the other to handle processing (scanning) of those images.
3520 """
36- def __init__ (self , result_queue , view_queue , config ):
21+ def __init__ (self , result_queue , view_queue , message_queue , config ):
3722 """ The task queue is used to store a queue of captured frames to be processed; the overlay
3823 queue stores Overlay objects which are drawn on to the image displayed to the user to highlight
3924 certain features; and the result queue is used to pass on the results of successful scans to
@@ -46,6 +31,7 @@ def __init__(self, result_queue, view_queue, config):
4631 self ._scanner_kill_q = multiprocessing .Queue ()
4732 self ._result_q = result_queue
4833 self ._view_q = view_queue
34+ self ._message_q = message_queue
4935
5036 self ._config = config
5137 self ._camera_configs = {CameraPosition .SIDE : self ._config .get_side_camera_config (),
@@ -55,7 +41,7 @@ def __init__(self, result_queue, view_queue, config):
5541 self ._camera_configs )
5642
5743 # The capture process is always running: we initialise the cameras only once because it's time consuming
58- self ._capture_process = multiprocessing .Process (target = _capture_worker , args = capture_args )
44+ self ._capture_process = multiprocessing .Process (target = CameraScanner . _capture_worker , args = capture_args )
5945 self ._capture_process .start ()
6046
6147 self ._scanner_process = None
@@ -64,8 +50,8 @@ def start_scan(self, cam_position):
6450 """ Spawn the processes that will continuously capture and process images from the camera.
6551 """
6652 print ("\n MAIN: start triggered" )
67- scanner_args = (self ._task_q , self ._overlay_q , self ._result_q , self ._scanner_kill_q , self ._config , cam_position )
68- self ._scanner_process = multiprocessing .Process (target = _scanner_worker , args = scanner_args )
53+ scanner_args = (self ._task_q , self ._overlay_q , self ._result_q , self ._message_q , self . _scanner_kill_q , self ._config , cam_position )
54+ self ._scanner_process = multiprocessing .Process (target = CameraScanner . _scanner_worker , args = scanner_args )
6955
7056 self ._capture_command_q .put (CaptureCommand (StreamAction .START , cam_position ))
7157 self ._scanner_process .start ()
@@ -95,7 +81,7 @@ def _terminate_scanner_process(self):
9581 if self ._scanner_process is not None :
9682 self ._scanner_kill_q .put (None )
9783 print ("MAIN: forcing scanner cleanup" )
98- self ._process_cleanup (self ._scanner_process , [self ._result_q , self ._overlay_q ])
84+ self ._process_cleanup (self ._scanner_process , [self ._result_q , self ._overlay_q , self . _message_q ])
9985 self ._scanner_process .join ()
10086 self ._flush_queue (self ._scanner_kill_q )
10187 self ._scanner_process = None
@@ -131,90 +117,14 @@ def _process_cleanup(self, process, queues):
131117
132118 print ("MAIN: sub-process terminated!" )
133119
120+ @staticmethod
121+ def _capture_worker (task_queue , view_queue , overlay_queue , command_queue , kill_queue , camera_configs ):
122+ """ Function used as the main loop of a worker process.
123+ """
124+ CaptureWorker (camera_configs ).run (task_queue , view_queue , overlay_queue , command_queue , kill_queue )
134125
135- def _capture_worker (task_queue , view_queue , overlay_queue , command_queue , kill_queue , camera_configs ):
136- """ Function used as the main loop of a worker process.
137- """
138- worker = CaptureWorker (camera_configs )
139- worker .run (task_queue , view_queue , overlay_queue , command_queue , kill_queue )
140-
141-
142- def _scanner_worker (task_queue , overlay_queue , result_queue , kill_queue , config , cam_position ):
143- """ Function used as the main loop of a worker process. Scan images for barcodes,
144- combining partial scans until a full puck is reached.
145-
146- Keep the record of the last scan which was at least partially successful (aligned geometry
147- and some barcodes scanned). For each new frame, we can attempt to merge the results with
148- this previous plates so that we don't have to re-read any of the previously captured barcodes
149- (because this is a relatively expensive operation).
150- """
151- print ("SCANNER start" )
152- last_plate_time = time .time ()
153-
154- SlotScanner .DEBUG = config .slot_images .value ()
155- SlotScanner .DEBUG_DIR = config .slot_image_directory .value ()
156-
157- if cam_position == CameraPosition .SIDE :
158- plate_type = "None"
159- barcode_sizes = DataMatrix .DEFAULT_SIDE_SIZES
160- else :
161- plate_type = config .plate_type .value ()
162- barcode_sizes = [config .top_barcode_size .value ()]
163-
164- if plate_type == "None" :
165- scanner = OpenScanner (barcode_sizes )
166- else :
167- scanner = GeometryScanner (plate_type , barcode_sizes )
168-
169- display = True
170- while kill_queue .empty ():
171- if display :
172- print ("--- scanner inside loop" )
173- display = False
174- if task_queue .empty ():
175- continue
176-
177- frame = task_queue .get (True )
178-
179- # Make grayscale version of image
180- image = Image (frame )
181- gray_image = image .to_grayscale ()
182-
183- # If we have an existing partial plate, merge the new plate with it and only try to read the
184- # barcodes which haven't already been read. This significantly increases efficiency because
185- # barcode read is expensive.
186- scan_result = scanner .scan_next_frame (gray_image )
187-
188- if config .console_frame .value ():
189- scan_result .print_summary ()
190-
191- if scan_result .success ():
192- # Record the time so we can see how long its been since we last saw a plate
193- last_plate_time = time .time ()
194-
195- plate = scan_result .plate ()
196-
197- if scan_result .already_scanned ():
198- overlay_queue .put (TextOverlay (SCANNED_TAG , Color .Green ()))
199- elif scan_result .any_valid_barcodes ():
200- overlay_queue .put (PlateOverlay (plate , config ))
201- _plate_beep (plate , config .scan_beep .value ())
202-
203- if scan_result .any_new_barcodes ():
204- result_queue .put ((plate , image ))
205- else :
206- time_since_plate = time .time () - last_plate_time
207- if time_since_plate > NO_PUCK_TIME :
208- overlay_queue .put (TextOverlay (scan_result .error (), Color .Red ()))
209-
210- print ("SCANNER stop & kill" )
211-
212-
213- def _plate_beep (plate , do_beep ):
214- if not do_beep :
215- return
216-
217- empty_fraction = (plate .num_slots - plate .num_valid_barcodes ()) / plate .num_slots
218- frequency = int (10000 * empty_fraction + 37 )
219- duration = 200
220- Beeper .beep (frequency , duration )
126+ @staticmethod
127+ def _scanner_worker (task_queue , overlay_queue , result_queue , message_queue , kill_queue , config , cam_position ):
128+ """ Function used as the main loop of a worker process.
129+ """
130+ ScannerWorker ().run (task_queue , overlay_queue , result_queue , message_queue , kill_queue , config , cam_position )
0 commit comments