Skip to content

Commit 5f4a3e6

Browse files
ENH montage: add 2d grid option
1 parent d295bfb commit 5f4a3e6

File tree

2 files changed

+54
-25
lines changed

2 files changed

+54
-25
lines changed

surfer/tests/test_viz.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ def test_image():
4848
brain.save_image(tmp_name)
4949
brain.save_imageset(tmp_name, ['med', 'lat'], 'jpg')
5050
brain.save_montage(tmp_name, ['l', 'v', 'm'], orientation='v')
51+
brain.save_montage(tmp_name, ['l', 'v', 'm'], orientation='h')
52+
brain.save_montage(tmp_name, [['l', 'v'], ['m', 'f']])
5153
brain.screenshot()
5254
brain.close()
5355

surfer/viz.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ def make_montage(filename, fnames, orientation='h', colorbar=None,
5656
fnames : list of str | list of array
5757
The images to make the montage of. Can be a list of filenames
5858
or a list of image data arrays.
59-
orientation : 'h' | 'v'
60-
The orientation of the montage: horizontal or vertical
59+
orientation : 'h' | 'v' | list
60+
The orientation of the montage: horizontal, vertical, or a nested
61+
list of int (indexes into fnames).
6162
colorbar : None | list of int
6263
If None remove colorbars, else keep the ones whose index
6364
is present.
@@ -102,13 +103,21 @@ def make_montage(filename, fnames, orientation='h', colorbar=None,
102103
# box = (left, top, width, height)
103104
boxes.append([s[1].start - border_size, s[0].start - border_size,
104105
s[1].stop + border_size, s[0].stop + border_size])
105-
if orientation == 'v':
106+
# convert orientation to nested list of int
107+
if orientation == 'h':
108+
orientation = [range(len(images))]
109+
elif orientation == 'v':
110+
orientation = [[i] for i in range(len(images))]
111+
# find bounding box
112+
n_rows = len(orientation)
113+
n_cols = max(len(row) for row in orientation)
114+
if n_rows > 1:
106115
min_left = min(box[0] for box in boxes)
107116
max_width = max(box[2] for box in boxes)
108117
for box in boxes:
109118
box[0] = min_left
110119
box[2] = max_width
111-
else:
120+
if n_cols > 1:
112121
min_top = min(box[1] for box in boxes)
113122
max_height = max(box[3] for box in boxes)
114123
for box in boxes:
@@ -120,22 +129,21 @@ def make_montage(filename, fnames, orientation='h', colorbar=None,
120129
cropped_images.append(im.crop(box))
121130
images = cropped_images
122131
# Get full image size
123-
if orientation == 'h':
124-
w = sum(i.size[0] for i in images)
125-
h = max(i.size[1] for i in images)
126-
else:
127-
h = sum(i.size[1] for i in images)
128-
w = max(i.size[0] for i in images)
129-
new = Image.new("RGBA", (w, h))
130-
x = 0
131-
for i in images:
132-
if orientation == 'h':
133-
pos = (x, 0)
134-
x += i.size[0]
135-
else:
136-
pos = (0, x)
137-
x += i.size[1]
138-
new.paste(i, pos)
132+
row_w = [sum(images[i].size[0] for i in row) for row in orientation]
133+
row_h = [max(images[i].size[1] for i in row) for row in orientation]
134+
out_w = max(row_w)
135+
out_h = sum(row_h)
136+
# compose image
137+
new = Image.new("RGBA", (out_w, out_h))
138+
y = 0
139+
for row, h in zip(orientation, row_h):
140+
x = 0
141+
for i in row:
142+
im = images[i]
143+
pos = (x, y)
144+
new.paste(im, pos)
145+
x += im.size[0]
146+
y += h
139147
if filename is not None:
140148
try:
141149
new.save(filename)
@@ -1881,9 +1889,12 @@ def save_montage(self, filename, order=['lat', 'ven', 'med'],
18811889
filename: string | None
18821890
path to final image. If None, the image will not be saved.
18831891
order: list
1884-
order of views to build montage
1892+
list of views: order of views to build montage (default ['lat',
1893+
'ven', 'med']; nested list of views to specify views in a
1894+
2-dimensional grid (e.g, [['lat', 'ven'], ['med', 'fro']])
18851895
orientation: {'h' | 'v'}
1886-
montage image orientation (horizontal of vertical alignment)
1896+
montage image orientation (horizontal of vertical alignment; only
1897+
applies if ``order`` is a flat list)
18871898
border_size: int
18881899
Size of image border (more or less space between images)
18891900
colorbar: None | 'auto' | [int], optional
@@ -1900,9 +1911,25 @@ def save_montage(self, filename, order=['lat', 'ven', 'med'],
19001911
out : array
19011912
The montage image, useable with matplotlib.imshow().
19021913
"""
1914+
# find flat list of views and nested list of view indexes
19031915
assert orientation in ['h', 'v']
1916+
if all(isinstance(x, (str, dict)) for x in order):
1917+
views = order
1918+
else:
1919+
views = []
1920+
orientation = []
1921+
for row_order in order:
1922+
if isinstance(row_order, (str, dict)):
1923+
orientation.append([len(views)])
1924+
views.append(row_order)
1925+
else:
1926+
orientation.append([])
1927+
for view in row_order:
1928+
orientation[-1].append(len(views))
1929+
views.append(view)
1930+
19041931
if colorbar == 'auto':
1905-
colorbar = [len(order) // 2]
1932+
colorbar = [len(views) // 2]
19061933
brain = self.brain_matrix[row, col]
19071934

19081935
# store current view + colorbar visibility
@@ -1912,8 +1939,8 @@ def save_montage(self, filename, order=['lat', 'ven', 'med'],
19121939
for cb in colorbars:
19131940
colorbars_visibility[cb] = cb.visible
19141941

1915-
images = self.save_imageset(None, order, colorbar=colorbar,
1916-
row=row, col=col)
1942+
images = self.save_imageset(None, views, colorbar=colorbar, row=row,
1943+
col=col)
19171944
out = make_montage(filename, images, orientation, colorbar,
19181945
border_size)
19191946

0 commit comments

Comments
 (0)