scikit-image
158 строк · 4.7 Кб
1import numpy as np
2
3from .._shared import utils
4from .. import exposure
5
6__all__ = ['montage']
7
8
9@utils.channel_as_last_axis(multichannel_output=False)
10def montage(
11arr_in,
12fill='mean',
13rescale_intensity=False,
14grid_shape=None,
15padding_width=0,
16*,
17channel_axis=None,
18):
19"""Create a montage of several single- or multichannel images.
20
21Create a rectangular montage from an input array representing an ensemble
22of equally shaped single- (gray) or multichannel (color) images.
23
24For example, ``montage(arr_in)`` called with the following `arr_in`
25
26+---+---+---+
27| 1 | 2 | 3 |
28+---+---+---+
29
30will return
31
32+---+---+
33| 1 | 2 |
34+---+---+
35| 3 | * |
36+---+---+
37
38where the '*' patch will be determined by the `fill` parameter.
39
40Parameters
41----------
42arr_in : ndarray, shape (K, M, N[, C])
43An array representing an ensemble of `K` images of equal shape.
44fill : float or array-like of floats or 'mean', optional
45Value to fill the padding areas and/or the extra tiles in
46the output array. Has to be `float` for single channel collections.
47For multichannel collections has to be an array-like of shape of
48number of channels. If `mean`, uses the mean value over all images.
49rescale_intensity : bool, optional
50Whether to rescale the intensity of each image to [0, 1].
51grid_shape : tuple, optional
52The desired grid shape for the montage `(ntiles_row, ntiles_column)`.
53The default aspect ratio is square.
54padding_width : int, optional
55The size of the spacing between the tiles and between the tiles and
56the borders. If non-zero, makes the boundaries of individual images
57easier to perceive.
58channel_axis : int or None, optional
59If None, the image is assumed to be a grayscale (single channel) image.
60Otherwise, this parameter indicates which axis of the array corresponds
61to channels.
62
63Returns
64-------
65arr_out : (K*(M+p)+p, K*(N+p)+p[, C]) ndarray
66Output array with input images glued together (including padding `p`).
67
68Examples
69--------
70>>> import numpy as np
71>>> from skimage.util import montage
72>>> arr_in = np.arange(3 * 2 * 2).reshape(3, 2, 2)
73>>> arr_in # doctest: +NORMALIZE_WHITESPACE
74array([[[ 0, 1],
75[ 2, 3]],
76[[ 4, 5],
77[ 6, 7]],
78[[ 8, 9],
79[10, 11]]])
80>>> arr_out = montage(arr_in)
81>>> arr_out.shape
82(4, 4)
83>>> arr_out
84array([[ 0, 1, 4, 5],
85[ 2, 3, 6, 7],
86[ 8, 9, 5, 5],
87[10, 11, 5, 5]])
88>>> arr_in.mean()
895.5
90>>> arr_out_nonsquare = montage(arr_in, grid_shape=(1, 3))
91>>> arr_out_nonsquare
92array([[ 0, 1, 4, 5, 8, 9],
93[ 2, 3, 6, 7, 10, 11]])
94>>> arr_out_nonsquare.shape
95(2, 6)
96"""
97
98if channel_axis is not None:
99arr_in = np.asarray(arr_in)
100else:
101arr_in = np.asarray(arr_in)[..., np.newaxis]
102
103if arr_in.ndim != 4:
104raise ValueError(
105'Input array has to be 3-dimensional for grayscale '
106'images, or 4-dimensional with a `channel_axis` '
107'specified.'
108)
109
110n_images, n_rows, n_cols, n_chan = arr_in.shape
111
112if grid_shape:
113ntiles_row, ntiles_col = (int(s) for s in grid_shape)
114else:
115ntiles_row = ntiles_col = int(np.ceil(np.sqrt(n_images)))
116
117# Rescale intensity if necessary
118if rescale_intensity:
119for i in range(n_images):
120arr_in[i] = exposure.rescale_intensity(arr_in[i])
121
122# Calculate the fill value
123if fill == 'mean':
124fill = arr_in.mean(axis=(0, 1, 2))
125fill = np.atleast_1d(fill).astype(arr_in.dtype)
126
127# Pre-allocate an array with padding for montage
128n_pad = padding_width
129arr_out = np.empty(
130(
131(n_rows + n_pad) * ntiles_row + n_pad,
132(n_cols + n_pad) * ntiles_col + n_pad,
133n_chan,
134),
135dtype=arr_in.dtype,
136)
137for idx_chan in range(n_chan):
138arr_out[..., idx_chan] = fill[idx_chan]
139
140slices_row = [
141slice(n_pad + (n_rows + n_pad) * n, n_pad + (n_rows + n_pad) * n + n_rows)
142for n in range(ntiles_row)
143]
144slices_col = [
145slice(n_pad + (n_cols + n_pad) * n, n_pad + (n_cols + n_pad) * n + n_cols)
146for n in range(ntiles_col)
147]
148
149# Copy the data to the output array
150for idx_image, image in enumerate(arr_in):
151idx_sr = idx_image // ntiles_col
152idx_sc = idx_image % ntiles_col
153arr_out[slices_row[idx_sr], slices_col[idx_sc], :] = image
154
155if channel_axis is not None:
156return arr_out
157else:
158return arr_out[..., 0]
159