Skip to content

Commit f117274

Browse files
committed
ENH: added resize and row and col titles in image_montage script
1 parent e2f9ebe commit f117274

File tree

3 files changed

+117
-22
lines changed

3 files changed

+117
-22
lines changed

src/pyrad_proc/pyrad/io/read_data_radar.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,12 @@ def get_data(voltime, datatypesdescr, cfg):
14421442
radar.radar_calibration["dBADU_to_dBm_vv"]["data"][0] = cfg["dBADUtodBmv"][
14431443
ind_rad
14441444
]
1445+
1446+
# If radar name is empty substitude the one from the config file
1447+
if not radar.metadata["instrument_name"]:
1448+
if "RadarName" in cfg:
1449+
radar.metadata["instrument_name"] = cfg["RadarName"][ind_rad]
1450+
14451451
return radar
14461452

14471453

@@ -1993,7 +1999,6 @@ def merge_scans_odim(
19931999
azmin = cfg["azmin"][ind_rad]
19942000
if cfg["azmax"] is not None:
19952001
azmax = cfg["azmax"][ind_rad]
1996-
19972002
return pyart.util.subset_radar(
19982003
radar,
19992004
radar.fields.keys(),

src/pyrad_proc/scripts/image_montage.py

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from PIL import Image, ImageDraw, ImageFont
1010
import math
1111

12-
DATE_REGEX_1 = re.compile(r"(\d{4}-\d{2}-\d{2})")
13-
DATE_REGEX_2 = re.compile(r"(\d{4}\d{2}\d{2})")
12+
DATE_REGEX_1 = re.compile(r"(\d{4}\d{2}\d{2}\d{2}\d{2}\d{2})")
13+
DATE_REGEX_2 = re.compile(r"(\d{4}-\d{2}-\d{2})")
14+
DATE_REGEX_3 = re.compile(r"(\d{4}\d{2}\d{2})")
1415

1516

1617
def parse_args():
@@ -34,6 +35,29 @@ def parse_args():
3435
type=int,
3536
help="Font size for the title (in pixels). Defaults to ~3%% of image width.",
3637
)
38+
parser.add_argument(
39+
"--coltitles",
40+
help="Optional title to place at the top of every column. Separate titles should be comma-separated.",
41+
)
42+
parser.add_argument(
43+
"--coltitles-size",
44+
type=int,
45+
help="Font size for the column titles (in pixels). Defaults to ~2%% of image width.",
46+
)
47+
parser.add_argument(
48+
"--rowtitles",
49+
help="Optional title to place at the top of every row. Separate titles should be comma-separated.",
50+
)
51+
parser.add_argument(
52+
"--rowtitles-size",
53+
type=int,
54+
help="Font size for the row titles (in pixels). Defaults to ~2%% of image width.",
55+
)
56+
parser.add_argument(
57+
"--resize",
58+
type=int,
59+
help="Resize factor of the final image (in percent), default is 100%",
60+
)
3761
parser.add_argument(
3862
"--output",
3963
default="composite.jpg",
@@ -48,6 +72,9 @@ def extract_date_from_filename(filename):
4872
if match:
4973
return match.group(1)
5074
match = DATE_REGEX_2.search(filename)
75+
if match:
76+
return match.group(1)
77+
match = DATE_REGEX_3.search(filename)
5178
if match:
5279
return match.group(1)
5380
return "unknown"
@@ -69,56 +96,106 @@ def load_images(image_paths):
6996
return [Image.open(p).convert("RGB") for p in image_paths]
7097

7198

72-
def create_composite(image_groups, date_order, title=None, title_size=None):
73-
from PIL import ImageFont
74-
99+
def create_composite(
100+
image_groups,
101+
date_order,
102+
title=None,
103+
title_size=None,
104+
col_titles=None,
105+
coltitle_size=None,
106+
row_titles=None,
107+
rowtitle_size=None,
108+
resize_percent=100,
109+
):
75110
date_keys = sorted(image_groups)
76111
grouped_images = [load_images(image_groups[date]) for date in date_keys]
77112

78113
max_w = max(img.width for group in grouped_images for img in group)
79114
max_h = max(img.height for group in grouped_images for img in group)
80115
pad = 10
81116
pad_title = 20
82-
117+
# Determine layout
83118
if date_order == "row":
84119
rows = len(grouped_images)
85120
cols = max(len(group) for group in grouped_images)
86121
else:
87122
cols = len(grouped_images)
88123
rows = max(len(group) for group in grouped_images)
89124

125+
# Font setup
90126
canvas_w = cols * (max_w + pad) + pad
91-
92-
# Choose title font size
93-
auto_font_size = max(16, int(canvas_w * 0.02)) # 3% of width or minimum 16px
94-
font_size = title_size if title_size else auto_font_size
95-
127+
auto_font_size = max(16, int(canvas_w * 0.03))
128+
font_size = int(title_size) if title_size else auto_font_size
96129
font = ImageFont.load_default(font_size)
97130

98-
title_h = font_size + pad_title if title else 0
99-
canvas_h = rows * (max_h + pad) + pad + title_h
131+
auto_font_size = max(14, int(canvas_w * 0.02))
132+
coltitle_size = int(coltitle_size) if coltitle_size else auto_font_size
133+
rowtitle_size = int(rowtitle_size) if rowtitle_size else auto_font_size
134+
rowfont = ImageFont.load_default(rowtitle_size)
135+
colfont = ImageFont.load_default(coltitle_size)
136+
137+
draw_dummy = ImageDraw.Draw(Image.new("RGB", (1, 1)))
138+
row_title_width = (
139+
max(draw_dummy.textlength(title, font=rowfont) for title in row_titles) + pad
140+
if row_titles
141+
else 0
142+
)
143+
col_title_height = coltitle_size + pad_title if col_titles else 0
144+
main_title_height = font_size + pad_title if title else 0
145+
146+
# Canvas dimensions
147+
canvas_w += row_title_width
148+
canvas_w = int(canvas_w)
149+
canvas_h = int(rows * (max_h + pad) + pad + main_title_height + col_title_height)
100150

101151
composite = Image.new("RGB", (canvas_w, canvas_h), color="white")
102152
draw = ImageDraw.Draw(composite)
103153

104-
# Draw title centered
154+
# Draw main title
105155
if title:
106156
text_w = draw.textlength(title, font=font)
107157
draw.text(((canvas_w - text_w) // 2, pad), title, font=font, fill="black")
108158

109-
y_offset = title_h
159+
y_offset = main_title_height + col_title_height
110160

111161
for i, (date, images) in enumerate(zip(date_keys, grouped_images)):
112162
for j, img in enumerate(images):
113163
if date_order == "row":
114-
x = pad + j * (max_w + pad)
115-
y = y_offset + pad + i * (max_h + pad)
164+
row_idx, col_idx = i, j
116165
else:
117-
x = pad + i * (max_w + pad)
118-
y = y_offset + pad + j * (max_h + pad)
166+
row_idx, col_idx = j, i
167+
168+
x = int(pad + row_title_width + col_idx * (max_w + pad))
169+
y = int(y_offset + row_idx * (max_h + pad))
119170

120171
composite.paste(img.resize((max_w, max_h)), (x, y))
121172

173+
# Draw column titles
174+
if col_titles:
175+
for j in range(cols):
176+
col_title = col_titles[j] if j < len(col_titles) else ""
177+
x = pad + row_title_width + j * (max_w + pad)
178+
y = main_title_height
179+
draw.text(
180+
(x + max_w // 2 - draw.textlength(col_title, font=font) // 2, y),
181+
col_title,
182+
font=colfont,
183+
fill="black",
184+
)
185+
186+
# Draw row titles
187+
if row_titles:
188+
for i in range(rows):
189+
row_title = row_titles[i] if i < len(row_titles) else ""
190+
x = pad
191+
y = y_offset + i * (max_h + pad) + max_h // 2 - font_size // 2
192+
draw.text((x, y), row_title, font=rowfont, fill="black")
193+
# Resize if needed
194+
if resize_percent != 100:
195+
new_w = int(composite.width * resize_percent / 100)
196+
new_h = int(composite.height * resize_percent / 100)
197+
composite = composite.resize((new_w, new_h))
198+
122199
return composite
123200

124201

@@ -130,8 +207,21 @@ def main():
130207
print("No images found.")
131208
return
132209

210+
if args.rowtitles:
211+
args.rowtitles = args.rowtitles.split(",")
212+
if args.coltitles:
213+
args.coltitles = args.coltitles.split(",")
214+
133215
composite = create_composite(
134-
image_groups, args.date_order, title=args.title, title_size=args.title_size
216+
image_groups,
217+
args.date_order,
218+
title=args.title,
219+
title_size=args.title_size,
220+
row_titles=args.rowtitles,
221+
rowtitle_size=args.rowtitles_size,
222+
col_titles=args.coltitles,
223+
coltitle_size=args.coltitles_size,
224+
resize_percent=args.resize,
135225
)
136226
composite.save(args.output)
137227
print(f"Composite image saved to {args.output}")

0 commit comments

Comments
 (0)