forked from scikit-image/scikit-image
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_montage.py
140 lines (117 loc) · 4.54 KB
/
_montage.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
import numpy as np
__all__ = ['montage']
def montage(arr_in, fill='mean', rescale_intensity=False, grid_shape=None,
padding_width=0, multichannel=False):
"""Create a montage of several single- or multichannel images.
Create a rectangular montage from an input array representing an ensemble
of equally shaped single- (gray) or multichannel (color) images.
For example, ``montage(arr_in)`` called with the following `arr_in`
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
will return
+---+---+
| 1 | 2 |
+---+---+
| 3 | * |
+---+---+
where the '*' patch will be determined by the `fill` parameter.
Parameters
----------
arr_in : (K, M, N[, C]) ndarray
An array representing an ensemble of `K` images of equal shape.
fill : float or array-like of floats or 'mean', optional
Value to fill the padding areas and/or the extra tiles in
the output array. Has to be `float` for single channel collections.
For multichannel collections has to be an array-like of shape of
number of channels. If `mean`, uses the mean value over all images.
rescale_intensity : bool, optional
Whether to rescale the intensity of each image to [0, 1].
grid_shape : tuple, optional
The desired grid shape for the montage `(ntiles_row, ntiles_column)`.
The default aspect ratio is square.
padding_width : int, optional
The size of the spacing between the tiles and between the tiles and
the borders. If non-zero, makes the boundaries of individual images
easier to perceive.
multichannel : boolean, optional
If True, the last `arr_in` dimension is threated as a color channel,
otherwise as spatial.
Returns
-------
arr_out : (K*(M+p)+p, K*(N+p)+p[, C]) ndarray
Output array with input images glued together (including padding `p`).
Examples
--------
>>> import numpy as np
>>> from skimage.util import montage
>>> arr_in = np.arange(3 * 2 * 2).reshape(3, 2, 2)
>>> arr_in # doctest: +NORMALIZE_WHITESPACE
array([[[ 0, 1],
[ 2, 3]],
[[ 4, 5],
[ 6, 7]],
[[ 8, 9],
[10, 11]]])
>>> arr_out = montage(arr_in)
>>> arr_out.shape
(4, 4)
>>> arr_out
array([[ 0, 1, 4, 5],
[ 2, 3, 6, 7],
[ 8, 9, 5, 5],
[10, 11, 5, 5]])
>>> arr_in.mean()
5.5
>>> arr_out_nonsquare = montage(arr_in, grid_shape=(1, 3))
>>> arr_out_nonsquare
array([[ 0, 1, 4, 5, 8, 9],
[ 2, 3, 6, 7, 10, 11]])
>>> arr_out_nonsquare.shape
(2, 6)
"""
# exposure imports scipy.linalg which is quite expensive.
# Since skimage.util is in the critical import path, we lazy import
# exposure to improve import time
from .. import exposure
if multichannel:
arr_in = np.asarray(arr_in)
else:
arr_in = np.asarray(arr_in)[..., np.newaxis]
if arr_in.ndim != 4:
raise ValueError('Input array has to be either 3- or 4-dimensional')
n_images, n_rows, n_cols, n_chan = arr_in.shape
if grid_shape:
ntiles_row, ntiles_col = [int(s) for s in grid_shape]
else:
ntiles_row = ntiles_col = int(np.ceil(np.sqrt(n_images)))
# Rescale intensity if necessary
if rescale_intensity:
for i in range(n_images):
arr_in[i] = exposure.rescale_intensity(arr_in[i])
# Calculate the fill value
if fill == 'mean':
fill = arr_in.mean(axis=(0, 1, 2))
fill = np.atleast_1d(fill).astype(arr_in.dtype)
# Pre-allocate an array with padding for montage
n_pad = padding_width
arr_out = np.empty(((n_rows + n_pad) * ntiles_row + n_pad,
(n_cols + n_pad) * ntiles_col + n_pad,
n_chan), dtype=arr_in.dtype)
for idx_chan in range(n_chan):
arr_out[..., idx_chan] = fill[idx_chan]
slices_row = [slice(n_pad + (n_rows + n_pad) * n,
n_pad + (n_rows + n_pad) * n + n_rows)
for n in range(ntiles_row)]
slices_col = [slice(n_pad + (n_cols + n_pad) * n,
n_pad + (n_cols + n_pad) * n + n_cols)
for n in range(ntiles_col)]
# Copy the data to the output array
for idx_image, image in enumerate(arr_in):
idx_sr = idx_image // ntiles_col
idx_sc = idx_image % ntiles_col
arr_out[slices_row[idx_sr], slices_col[idx_sc], :] = image
if multichannel:
return arr_out
else:
return arr_out[..., 0]