Skip to content

Commit da1c467

Browse files
committed
initial simplification
1 parent eb5b434 commit da1c467

File tree

6 files changed

+155
-278
lines changed

6 files changed

+155
-278
lines changed
Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1+
from dls_util.image.image import Image
12
from .locate import Locator
2-
from .read import DatamatrixSizeTable
3-
from .read import DatamatrixReaderError, ReedSolomonError
4-
from .read import DatamatrixBitReader
5-
from .read import DatamatrixByteExtractor
6-
from .read import ReedSolomonDecoder
7-
from .read import DatamatrixByteInterpreter
83
from pylibdmtx.pylibdmtx import decode
4+
import cv2
95

106

117
# We predict the location of the center of each square (pixel/bit) in the datamatrix based on the
@@ -29,10 +25,7 @@ class DataMatrix:
2925
DEFAULT_SIZE = 14
3026
DEFAULT_SIDE_SIZES = [12, 14]
3127

32-
_w = 0.25
33-
DIAG_WIGGLES = [[0, 0], [_w, _w], [-_w, -_w], [_w, -_w], [-_w, _w]]
34-
35-
def __init__(self, finder_pattern, image):
28+
def __init__(self, finder_pattern):
3629
""" Initialize the DataMatrix object with its finder pattern location in an image. To actually
3730
interpret the DataMatrix, the perform_read() function must be called, which will attempt to read
3831
the DM from the supplied image.
@@ -42,7 +35,6 @@ def __init__(self, finder_pattern, image):
4235
error correction bytes).
4336
"""
4437
self._finder_pattern = finder_pattern
45-
self._image = image.img
4638
self._matrix_sizes = [self.DEFAULT_SIZE]
4739

4840
self._data = None
@@ -54,14 +46,15 @@ def __init__(self, finder_pattern, image):
5446
def set_matrix_sizes(self, matrix_sizes):
5547
self._matrix_sizes = [int(v) for v in matrix_sizes]
5648

57-
def perform_read(self, offsets=wiggle_offsets, force_read=False):
49+
def perform_read(self, image, force_read=False):
5850
""" Attempt to read the DataMatrix from the image supplied in the constructor at the position
5951
given by the finder pattern. This is not performed automatically upon construction because the
6052
read operation is relatively expensive and might not always be needed.
6153
"""
6254
if not self._is_read_performed or force_read:
6355
# Read the data contained in the barcode from the image
64-
self._read(self._image, offsets)
56+
sub, _ = image.sub_image(self.center(), 1.2*self.radius())
57+
self._read(sub.img)
6558
self._is_read_performed = True
6659

6760
def is_read(self):
@@ -91,7 +84,7 @@ def data(self):
9184
return self._data
9285
else:
9386
return ''
94-
87+
9588
def bounds(self):
9689
""" A circle which bounds the data matrix (center, radius). """
9790
return self._finder_pattern.bounds()
@@ -103,65 +96,31 @@ def center(self):
10396
def radius(self):
10497
""" The radius (center-to-corner distance) of the DataMatrix finder pattern. """
10598
return self._finder_pattern.radius
106-
107-
def _read(self, gray_image, offsets):
108-
if self.radius() >= 0.2*(gray_image.shape[0]):
109-
self._read_single(gray_image)
110-
else:
111-
self._read_old(gray_image, offsets)
112-
113-
def _read_old(self, gray_image, offsets):
99+
100+
def _read(self, gray_image):
114101
""" From the supplied grayscale image, attempt to read the barcode at the location
115102
given by the datamatrix finder pattern.
116103
"""
117-
for matrix_size in self._matrix_sizes:
118-
bit_reader = DatamatrixBitReader(matrix_size)
119-
extractor = DatamatrixByteExtractor()
120-
decoder = ReedSolomonDecoder()
121-
interpreter = DatamatrixByteInterpreter()
122-
123-
message_length = DatamatrixSizeTable.num_data_bytes(matrix_size)
124-
125-
# Try a few different small offsets for the sample positions until we find one that works
126-
for offset in offsets:
127-
# Read the bit array at the target location (with offset)
128-
# If the bit array is valid, decode it and create a datamatrix object
129-
try:
130-
bit_array = bit_reader.read_bit_array(self._finder_pattern, offset, gray_image)
131-
encoded_bytes = extractor.extract_bytes(bit_array)
132-
decoded_bytes = decoder.decode(encoded_bytes, message_length)
133-
data = interpreter.interpret_bytes(decoded_bytes)
134-
135-
self._data = data
136-
self._read_ok = True
137-
self._error_message = ""
138-
break
139-
except (DatamatrixReaderError, ReedSolomonError) as ex:
140-
self._read_ok = False
141-
self._error_message = str(ex)
142-
143-
self._damaged_symbol = not self._read_ok
144-
145-
if self._read_ok:
146-
break
147-
148-
def _read_single(self, gray_image):
149104
try:
150-
result = decode(gray_image)
105+
106+
cv2.imshow("Erode", gray_image)
107+
cv2.waitKey(0)
108+
result = decode(gray_image, max_count = 1)
151109
if len(result) > 0:
152110
d = result[0].data
153111
decoded = d.decode('UTF-8')
154112
new_line_removed = decoded.replace("\n","")
155113
self._data = new_line_removed
156114
self._read_ok = True
157-
self._is_read_performed = True
158115
self._error_message = ""
159116
else:
160117
self._read_ok = False
161118
except(Exception) as ex:
162119
self._read_ok = False
163120
self._error_message = str(ex)
164121

122+
self._damaged_symbol = not self._read_ok
123+
165124
def draw(self, img, color):
166125
""" Draw the lines of the finder pattern on the specified image. """
167126
fp = self._finder_pattern
@@ -174,7 +133,7 @@ def locate_all_barcodes_in_image(grayscale_img, matrix_sizes=[DEFAULT_SIZE]):
174133
"""
175134
locator = Locator()
176135
finder_patterns = locator.locate_shallow(grayscale_img)
177-
unread_barcodes = DataMatrix._fps_to_barcodes(grayscale_img, finder_patterns, matrix_sizes)
136+
unread_barcodes = DataMatrix._fps_to_barcodes(finder_patterns, matrix_sizes)
178137
return unread_barcodes
179138

180139
@staticmethod
@@ -185,12 +144,12 @@ def locate_all_barcodes_in_image_deep(grayscale_img, matrix_sizes=[DEFAULT_SIDE_
185144
locator = Locator()
186145
locator.set_median_radius_tolerance(0.2)
187146
finder_patterns = locator.locate_deep(grayscale_img, expected_radius=None, filter_overlap=True)
188-
unread_barcodes = DataMatrix._fps_to_barcodes(grayscale_img, finder_patterns, matrix_sizes)
147+
unread_barcodes = DataMatrix._fps_to_barcodes(finder_patterns, matrix_sizes)
189148
return unread_barcodes
190149

191150
@staticmethod
192-
def _fps_to_barcodes(grayscale_img, finder_patterns, matrix_sizes):
193-
unread_barcodes = [DataMatrix(fp, grayscale_img) for fp in finder_patterns]
151+
def _fps_to_barcodes(finder_patterns, matrix_sizes):
152+
unread_barcodes = [DataMatrix(fp) for fp in finder_patterns]
194153
for bc in unread_barcodes:
195154
bc.set_matrix_sizes(matrix_sizes)
196155
return list(unread_barcodes)

dls_barcode/plate/slot.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def barcode(self):
3737

3838
def state(self):
3939
return self._state
40+
41+
def total_frames(self):
42+
return self._total_frames
4043

4144
def barcode_position(self):
4245
""" Get the position (x,y) of the center of the barcode (not exactly the same as the
@@ -77,3 +80,9 @@ def barcode_data(self):
7780
return self._barcode.data()
7881
else:
7982
return NOT_FOUND_SLOT_SYMBOL
83+
84+
def find_matching_barcode(self, barcodes):
85+
for bc in barcodes:
86+
if self._bounds.contains_point(bc.center()):
87+
return bc
88+
return None

dls_barcode/scan/open/open_scanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _perform_frame_scan(self):
5757
barcodes = self._locate_all_barcodes_in_image()
5858

5959
for barcode in barcodes:
60-
barcode.perform_read(DataMatrix.DIAG_WIGGLES)
60+
barcode.perform_read(self._frame_img)
6161

6262
if self._is_barcode_new(barcode):
6363
# todo: limit number of previous barcodes stored

dls_barcode/scan/with_geometry/geometry_scanner.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33

44

55
from dls_barcode.datamatrix import DataMatrix
6-
from dls_barcode.geometry.exception import GeometryAlignmentError
76
from dls_barcode.geometry.unipuck_locator import UnipuckLocator
87
from dls_barcode.plate import Plate, Slot
98
from dls_barcode.plate.geometry_adjuster import UnipuckGeometryAdjuster, GeometryAdjustmentError
109
from dls_barcode.geometry import Geometry, GeometryException
1110
from .empty_detector import EmptySlotDetector
1211
from .plate_scanner import PlateScanner
13-
from .slot_scanner import SlotScanner
1412
from ..scan_result import ScanResult
1513
from ..no_barcodes_detected_error import NoBarcodesDetectedError
1614

@@ -106,29 +104,16 @@ def _calculate_geometry(self):
106104
return geometry
107105

108106
def _initialize_plate_from_barcodes(self):
109-
110-
for bc in self._barcodes:
111-
bc.perform_read()
112-
113-
if self._any_valid_barcodes():
114-
slot_scanner = self._create_slot_scanner()
107+
if self._frame_img is not None:
115108
self._plate = Plate(self.plate_type)
116109
self._plate_scan = PlateScanner(self._plate, self._is_single_image)
117-
self._plate_scan.new_frame(self._geometry, self._barcodes, slot_scanner)
110+
self._plate_scan.new_frame(self._frame_img, self._geometry, self._barcodes)
118111

119112
def _merge_frame_into_plate(self):
120113
# If one of the barcodes matches the previous frame and is aligned in the same slot, then we can
121114
# be fairly sure we are dealing with the same plate. Copy all of the barcodes that we read in the
122115
# previous plate over to their slot in the new plate. Then read any that we haven't already read.
123-
slot_scanner = self._create_slot_scanner()
124-
self._plate_scan.new_frame(self._geometry, self._barcodes, slot_scanner)
125-
126-
def _any_valid_barcodes(self):
127-
return any([bc.is_read() and bc.is_valid() for bc in self._barcodes])
128-
129-
def _create_slot_scanner(self):
130-
slot_scanner = SlotScanner(self._frame_img, self._barcodes)
131-
return slot_scanner
116+
self._plate_scan.new_frame(self._geometry, self._barcodes)
132117

133118
def _find_common_barcode(self, geometry, barcodes):
134119
""" Determine if the set of finder patterns has any barcodes in common with the existing plate.
Lines changed: 55 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import random
1+
import numpy as np
2+
import cv2
23

3-
from dls_barcode.plate.slot import Slot
4+
from dls_barcode.scan.with_geometry.slot_scanner import SlotScanner
45

56

67
class BadGeometryException(Exception):
78
pass
89

910

1011
class PlateScanner:
11-
FRAMES_BEFORE_DEEP = 3
12+
BRIGHTNESS_RATIO = 5
1213

13-
def __init__(self, plate, single_frame=False):
14+
def __init__(self, plate, single_frame=False):
1415
self._plate = plate
15-
16-
self._frame_num = -1
1716
self._force_deep_scan = single_frame
1817

19-
def new_frame(self, geometry, barcodes, slot_scanner):
18+
def new_frame(self, frame_img, geometry, barcodes):
2019
""" Merge the set of barcodes from a new scan into the plate. The new set comes from a new image
2120
of the same plate, so will almost certainly contain many of the same barcodes. Actually reading a
2221
barcode is relatively expensive; we iterate through each slot in the plate and only attempt to
@@ -27,79 +26,61 @@ def new_frame(self, geometry, barcodes, slot_scanner):
2726
position is likely to be similar to, but not exactly the same as, the bound's center. This info
2827
is retained as it allows us to properly calculate the geometry for future frames.
2928
"""
30-
self._frame_num += 1
29+
self._frame_img = frame_img
30+
self._barcodes = barcodes
3131
self._plate.set_geometry(geometry)
32+
33+
self.radius_avg = self._calculate_average_radius()
34+
self.brightness_threshold = self._calculate_brightness_threshold()
3235

3336
# Fill each slot with the correct barcodes
3437
for slot in self._plate.slots():
35-
self._new_slot_frame(barcodes, slot, slot_scanner)
38+
self._new_slot_frame(slot)
3639

37-
def _new_slot_frame(self, barcodes, slot, slot_scanner):
40+
def _new_slot_frame(self, slot):
3841
slot.new_frame()
3942

4043
# Find the barcode from the new set that is in the slot position
41-
bounds = slot.bounds()
42-
barcode = self._find_matching_barcode(bounds, barcodes)
43-
44-
# Update the barcode position - use the actual position of the barcode if available,
45-
# otherwise use the slot center position (from geometry) as an approximation
46-
position = barcode.center() if barcode else bounds.center()
44+
barcode = slot.find_matching_barcode(self._barcodes)
45+
position = barcode.center() if barcode else slot.bounds().center()
4746
slot.set_barcode_position(position)
48-
49-
# If we haven't already found this barcode data, try to read it from the new barcode
50-
if slot.state() != Slot.VALID and barcode:
51-
barcode.perform_read()
52-
slot.set_barcode(barcode)
53-
54-
# If the barcode still hasn't been read, try a deeper slot scan
55-
self._slot_scan(slot, slot_scanner)
56-
57-
@staticmethod
58-
def _find_matching_barcode(slot_bounds, barcodes):
59-
for bc in barcodes:
60-
if slot_bounds.contains_point(bc.center()):
61-
return bc
62-
return None
63-
64-
def _slot_scan(self, slot, slot_scanner):
65-
# If the slot barcode has already been read correctly, skip it
66-
if slot.state() == Slot.VALID:
67-
return
68-
69-
# Check for empty slot
70-
if slot_scanner.is_slot_empty(slot):
71-
slot.set_empty()
72-
return
73-
74-
# Clear any previous (empty/unread) result
75-
slot.set_no_result()
76-
77-
if self._should_do_deep_scan():
78-
self._perform_deep_contour_slot_scan(slot, slot_scanner, self._force_deep_scan)
79-
self._perform_square_slot_scan(slot, slot_scanner)
80-
81-
def _should_do_deep_scan(self):
82-
return self._force_deep_scan or self._frame_num > self.FRAMES_BEFORE_DEEP
83-
84-
@staticmethod
85-
def _perform_deep_contour_slot_scan(slot, slot_scanner, force_all):
86-
if slot.state() != Slot.VALID:
87-
barcodes = slot_scanner.deep_scan(slot)
88-
89-
# Pick a random finder pattern from those returned
90-
if not force_all and barcodes:
91-
barcodes = [random.choice(barcodes)]
92-
93-
for barcode in barcodes:
94-
slot_scanner.wiggles_read(barcode, "DEEP CONTOUR")
95-
slot.set_barcode(barcode)
96-
if barcode.is_valid():
97-
break
98-
99-
@staticmethod
100-
def _perform_square_slot_scan(slot, slot_scanner):
101-
if slot.state() != Slot.VALID:
102-
barcode = slot_scanner.square_scan(slot)
103-
if barcode is not None:
104-
slot_scanner.wiggles_read(barcode, "SQUARE")
105-
slot.set_barcode(barcode)
47+
48+
#slot_image = self._slot_image(slot)
49+
#cv2.imshow("Slot image", slot_image.img)
50+
#cv2.waitKey(0)
51+
52+
slot_scanner = SlotScanner(self._frame_img, slot, barcode, self._force_deep_scan, self.radius_avg, self.brightness_threshold)
53+
slot_scanner.scan_slot()
54+
55+
def _calculate_average_radius(self):
56+
if self._barcodes:
57+
return np.mean([bc.radius() for bc in self._barcodes])
58+
else:
59+
return 0
60+
61+
def _image_contains_point(self, point, radius=0):
62+
h, w = self._frame_img.img.shape
63+
return (radius <= point.x <= w - radius - 1) and (radius <= point.y <= h - radius - 1)
64+
65+
def _calculate_brightness_threshold(self):
66+
""" Calculate the brightness of a small area at each barcode and return the average value.
67+
A barcode will be much brighter than an empty slot as it usually contains plenty of white
68+
pixels. This allows us to distinguish between an empty slot with no pin, and a slot with a pin
69+
where we just haven't been able to locate the barcode.
70+
"""
71+
pin_brights = []
72+
for bc in self._barcodes:
73+
size = bc.radius() / 2
74+
center_in_frame = self._image_contains_point(bc.center(), radius=size)
75+
if center_in_frame:
76+
brightness = self._frame_img.calculate_brightness(bc.center(), size, size)
77+
pin_brights.append(brightness)
78+
79+
if any(pin_brights):
80+
avg_brightness = np.mean(pin_brights)
81+
else:
82+
avg_brightness = 0
83+
84+
return avg_brightness / self.BRIGHTNESS_RATIO
85+
86+

0 commit comments

Comments
 (0)