-
Notifications
You must be signed in to change notification settings - Fork 1
/
add_scale_bar_BZ-9000.py
173 lines (144 loc) · 5.38 KB
/
add_scale_bar_BZ-9000.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from PIL import ImageFont, ImageDraw, Image
import cv2
import numpy as np
import argparse
import os
import sys
import re
import glob
# Keyence BZ-9000
DISPLAY_SCALE_NUMBER = True
# Constants from the calibration of the scopes
scale_pixel_4X_100_um = 37
scale_pixel_10X_100_um = 93
scale_pixel_20X_100_um = 186
scale_bar_location_x_offset = 50
scale_bar_location_y_offset = 70
scale_bar_font_size = 35
text_position_y_offset = 50
scale_bar_thickness = 25
scale_bar_color = (255, 255, 255)
def process_directory(directory_path, scope_type=None):
"""Process all .png images in the given directory."""
for image_file in glob.glob(os.path.join(directory_path, "*")):
if not "_scaled" in image_file:
add_scale_bar(image_file, scope_type)
def detect_scope_type_from_filename(image_path: str) -> str:
"""Detect the scope type from the filename."""
# Define a pattern for the scope type in the filename
pattern = r"4X|10X|20X"
match = re.search(pattern, image_path, flags=re.IGNORECASE)
return match.group() if match else ""
def add_scale_bar(image_path, scope_type) -> None:
"""Add a scale bar to an image based on the type of scope."""
# Check if the image exists
if not os.path.exists(image_path):
print("The image does not exist. Please provide a valid image.")
return
# Check the file extension
if not image_path.lower().endswith(".tif") and not image_path.lower().endswith(
".png"
):
print(
"The image must be in .tif format or .png format. Please provide a valid image."
)
return
# Read the image including the alpha channel
image = cv2.imread(image_path, -1)
# Check the image size
if image is None or image.shape[0] != 1024 or image.shape[1] != 1360:
print("The image must be 1360x1024 in size. Please provide a valid image.")
return
if not scope_type:
scope_type = detect_scope_type_from_filename(image_path)
if not scope_type:
print(
"Failed to detect scope type from filename. Please add magnification to the filename or use the proper command-line argument."
)
return
# Define the scale bar size based on the scope_type
scale_bar_size = 0
label = ""
if scope_type == "4X":
scale_bar_size = int(scale_pixel_4X_100_um * 5)
label = "500 μm"
elif scope_type == "10X":
scale_bar_size = int(scale_pixel_10X_100_um * 2)
label = "200 μm"
elif scope_type == "20X":
scale_bar_size = int(scale_pixel_20X_100_um * 1)
label = "100 μm"
else:
print("Invalid scope type. Please use 4X, 10X, or 20X.")
return
if not DISPLAY_SCALE_NUMBER:
label = ""
# Define the position and thickness of the scale bar
position = (
image.shape[1] - scale_bar_size - scale_bar_location_x_offset,
image.shape[0] - scale_bar_location_y_offset,
)
# Draw the scale bar using a rectangle
cv2.rectangle(
image,
position,
(position[0] + scale_bar_size, position[1] + scale_bar_thickness),
color=scale_bar_color,
thickness=-1,
)
# Convert from BGR to RGB and to PIL
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(image)
# Load a platform-independent font
if sys.platform == "darwin": # MacOS
font_path = os.path.join(os.path.dirname(__file__), "Arial.ttf")
elif sys.platform == "win32": # Windows
font_path = "arial.ttf"
elif sys.platform.startswith("linux"): # Linux
font_path = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
else:
print(
"Unsupported operating system. Please run this script on MacOS, Windows, or Linux."
)
return
font = ImageFont.truetype(font_path, scale_bar_font_size)
# Draw non-ascii text onto image
draw = ImageDraw.Draw(pil_image)
# Calculate text width to center it
text_width = draw.textlength(label, font=font)
text_position = (
position[0] + (scale_bar_size - text_width) // 2,
position[1] - text_position_y_offset,
)
draw.text(text_position, label, font=font, fill=scale_bar_color)
# Convert back to Numpy array and switch back from RGB to BGR
image = np.asarray(pil_image)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# Save the new image
output_path = image_path[:-4] + "_scaled.tif"
cv2.imwrite(output_path, image)
print(f"Scale bar added to {output_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Add a scale bar to an image or all images in a directory based on the type of scope."
)
parser.add_argument(
"path",
type=str,
help="Path to the .tif image or directory containing .png images.",
)
parser.add_argument(
"scope_type",
type=str,
nargs="?",
default=None,
help="Optional. Type of scope (4X, 10X, 20X). If omitted, will try to detect from filename.",
)
args = parser.parse_args()
# Check if the path is a directory or a file
if os.path.isdir(args.path):
process_directory(args.path, args.scope_type)
elif os.path.isfile(args.path):
add_scale_bar(args.path, args.scope_type)
else:
print("The provided path does not exist or is not a valid image or directory.")