Skip to content

Commit 75cb752

Browse files
committed
I04_1-156: moved scanner worker to own class
1 parent 8cc99e5 commit 75cb752

File tree

2 files changed

+103
-109
lines changed

2 files changed

+103
-109
lines changed
Lines changed: 13 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
from __future__ import division
22

33
import multiprocessing
4-
import time
54
import queue
65

7-
# TODO: tidy up
8-
from dls_util.image import Image
9-
from dls_util import Beeper
10-
from dls_util.message import MessageType, Message
11-
from dls_barcode.scan import GeometryScanner, SlotScanner, OpenScanner
12-
from dls_barcode.datamatrix import DataMatrix
136
from .capture_worker import CaptureWorker
7+
from .scanner_worker import ScannerWorker
148
from .camera_position import CameraPosition
159
from .stream_action import StreamAction
1610
from .capture_command import CaptureCommand
17-
from .plate_overlay import PlateOverlay
18-
19-
Q_LIMIT = 1
20-
SCANNED_TAG = "Scan Complete"
21-
NO_PUCK_TIME = 2
22-
23-
EXIT_KEY = 'q'
24-
25-
# Maximum frame rate to sample at (rate will be further limited by speed at which frames can be processed)
26-
MAX_SAMPLE_RATE = 10.0
27-
INTERVAL = 1.0 / MAX_SAMPLE_RATE
2811

2912

3013
class CameraScanner:
@@ -58,7 +41,7 @@ def __init__(self, result_queue, view_queue, message_queue, config):
5841
self._camera_configs)
5942

6043
# The capture process is always running: we initialise the cameras only once because it's time consuming
61-
self._capture_process = multiprocessing.Process(target=_capture_worker, args=capture_args)
44+
self._capture_process = multiprocessing.Process(target=CameraScanner._capture_worker, args=capture_args)
6245
self._capture_process.start()
6346

6447
self._scanner_process = None
@@ -68,7 +51,7 @@ def start_scan(self, cam_position):
6851
"""
6952
print("\nMAIN: start triggered")
7053
scanner_args = (self._task_q, self._overlay_q, self._result_q, self._message_q, self._scanner_kill_q, self._config, cam_position)
71-
self._scanner_process = multiprocessing.Process(target=_scanner_worker, args=scanner_args)
54+
self._scanner_process = multiprocessing.Process(target=CameraScanner._scanner_worker, args=scanner_args)
7255

7356
self._capture_command_q.put(CaptureCommand(StreamAction.START, cam_position))
7457
self._scanner_process.start()
@@ -134,93 +117,14 @@ def _process_cleanup(self, process, queues):
134117

135118
print("MAIN: sub-process terminated!")
136119

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)
137125

138-
def _capture_worker(task_queue, view_queue, overlay_queue, command_queue, kill_queue, camera_configs):
139-
""" Function used as the main loop of a worker process.
140-
"""
141-
worker = CaptureWorker(camera_configs)
142-
worker.run(task_queue, view_queue, overlay_queue, command_queue, kill_queue)
143-
144-
145-
def _scanner_worker(task_queue, overlay_queue, result_queue, message_queue, kill_queue, config, cam_position):
146-
""" Function used as the main loop of a worker process. Scan images for barcodes,
147-
combining partial scans until a full puck is reached.
148-
149-
Keep the record of the last scan which was at least partially successful (aligned geometry
150-
and some barcodes scanned). For each new frame, we can attempt to merge the results with
151-
this previous plates so that we don't have to re-read any of the previously captured barcodes
152-
(because this is a relatively expensive operation).
153-
"""
154-
print("SCANNER start")
155-
last_plate_time = time.time()
156-
157-
SlotScanner.DEBUG = config.slot_images.value()
158-
SlotScanner.DEBUG_DIR = config.slot_image_directory.value()
159-
160-
if cam_position == CameraPosition.SIDE:
161-
plate_type = "None"
162-
barcode_sizes = DataMatrix.DEFAULT_SIDE_SIZES
163-
else:
164-
plate_type = config.plate_type.value()
165-
barcode_sizes = [config.top_barcode_size.value()]
166-
167-
if plate_type == "None":
168-
scanner = OpenScanner(barcode_sizes)
169-
else:
170-
scanner = GeometryScanner(plate_type, barcode_sizes)
171-
172-
display = True
173-
while kill_queue.empty():
174-
if display:
175-
print("--- scanner inside loop")
176-
display = False
177-
if task_queue.empty():
178-
continue
179-
180-
frame = task_queue.get(True)
181-
182-
# Make grayscale version of image
183-
image = Image(frame)
184-
gray_image = image.to_grayscale()
185-
186-
# If we have an existing partial plate, merge the new plate with it and only try to read the
187-
# barcodes which haven't already been read. This significantly increases efficiency because
188-
# barcode read is expensive.
189-
scan_result = scanner.scan_next_frame(gray_image)
190-
191-
if config.console_frame.value():
192-
scan_result.print_summary()
193-
194-
if scan_result.success():
195-
# Record the time so we can see how long its been since we last saw a plate
196-
last_plate_time = time.time()
197-
198-
plate = scan_result.plate()
199-
200-
if scan_result.already_scanned():
201-
pass
202-
# message_queue.put(Message(MessageType.INFO, SCANNED_TAG))
203-
# overlay_queue.put(TextOverlay(SCANNED_TAG, Color.Green()))
204-
elif scan_result.any_valid_barcodes():
205-
overlay_queue.put(PlateOverlay(plate, config))
206-
_plate_beep(plate, config.scan_beep.value())
207-
208-
if scan_result.any_new_barcodes():
209-
result_queue.put((plate, image))
210-
elif scan_result.error() is not None:
211-
time_since_plate = time.time() - last_plate_time
212-
if time_since_plate > NO_PUCK_TIME:
213-
message_queue.put(Message(MessageType.WARNING, scan_result.error()))
214-
# overlay_queue.put(TextOverlay(scan_result.error(), Color.Red()))
215-
216-
print("SCANNER stop & kill")
217-
218-
219-
def _plate_beep(plate, do_beep):
220-
if not do_beep:
221-
return
222-
223-
empty_fraction = (plate.num_slots - plate.num_valid_barcodes()) / plate.num_slots
224-
frequency = int(10000 * empty_fraction + 37)
225-
duration = 200
226-
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)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import time
2+
3+
from dls_util.image import Image
4+
from dls_util.message import MessageType, Message
5+
from dls_util import Beeper
6+
from dls_barcode.scan import GeometryScanner, SlotScanner, OpenScanner
7+
from dls_barcode.datamatrix import DataMatrix
8+
from .camera_position import CameraPosition
9+
from .plate_overlay import PlateOverlay
10+
11+
NO_PUCK_TIME = 2
12+
13+
14+
class ScannerWorker:
15+
""" Scan images for barcodes, combining partial scans until a full puck is reached.
16+
Keep the record of the last scan which was at least partially successful (aligned geometry
17+
and some barcodes scanned). For each new frame, we can attempt to merge the results with
18+
this previous plates so that we don't have to re-read any of the previously captured barcodes
19+
(because this is a relatively expensive operation).
20+
"""
21+
def run(self, task_queue, overlay_queue, result_queue, message_queue, kill_queue, config, cam_position):
22+
print("SCANNER start")
23+
self._last_plate_time = time.time()
24+
25+
SlotScanner.DEBUG = config.slot_images.value()
26+
SlotScanner.DEBUG_DIR = config.slot_image_directory.value()
27+
28+
self._create_scanner(cam_position, config)
29+
30+
display = True
31+
while kill_queue.empty():
32+
if display:
33+
print("--- scanner inside loop")
34+
display = False
35+
36+
if task_queue.empty():
37+
continue
38+
39+
frame = task_queue.get(True)
40+
self._process_frame(frame, config, overlay_queue, result_queue, message_queue)
41+
42+
print("SCANNER stop & kill")
43+
44+
def _process_frame(self, frame, config, overlay_queue, result_queue, message_queue):
45+
image = Image(frame)
46+
gray_image = image.to_grayscale()
47+
48+
# If we have an existing partial plate, merge the new plate with it and only try to read the
49+
# barcodes which haven't already been read. This significantly increases efficiency because
50+
# barcode read is expensive.
51+
scan_result = self._scanner.scan_next_frame(gray_image)
52+
53+
if config.console_frame.value():
54+
scan_result.print_summary()
55+
56+
if scan_result.success():
57+
# Record the time so we can see how long its been since we last saw a plate
58+
self._last_plate_time = time.time()
59+
60+
plate = scan_result.plate()
61+
if scan_result.any_valid_barcodes():
62+
overlay_queue.put(PlateOverlay(plate, config))
63+
self._plate_beep(plate, config.scan_beep.value())
64+
65+
if scan_result.any_new_barcodes():
66+
result_queue.put((plate, image))
67+
elif scan_result.error() is not None and (time.time() - self._last_plate_time > NO_PUCK_TIME):
68+
message_queue.put(Message(MessageType.WARNING, scan_result.error(), lifetime=1))
69+
70+
def _create_scanner(self, cam_position, config):
71+
if cam_position == CameraPosition.SIDE:
72+
plate_type = "None"
73+
barcode_sizes = DataMatrix.DEFAULT_SIDE_SIZES
74+
else:
75+
plate_type = config.plate_type.value()
76+
barcode_sizes = [config.top_barcode_size.value()]
77+
78+
if plate_type == "None":
79+
self._scanner = OpenScanner(barcode_sizes)
80+
else:
81+
self._scanner = GeometryScanner(plate_type, barcode_sizes)
82+
83+
def _plate_beep(self, plate, do_beep):
84+
if not do_beep:
85+
return
86+
87+
empty_fraction = (plate.num_slots - plate.num_valid_barcodes()) / plate.num_slots
88+
frequency = int(10000 * empty_fraction + 37)
89+
duration = 200
90+
Beeper.beep(frequency, duration)

0 commit comments

Comments
 (0)