Skip to content

Commit b1a14e9

Browse files
authored
Merge pull request #1578 from danforthcenter/1575-image-tiling
1575 image tiling
2 parents 248f983 + cf74ff1 commit b1a14e9

File tree

12 files changed

+158
-1
lines changed

12 files changed

+158
-1
lines changed
Loading
Loading
Loading
Loading
Loading

docs/updating.md

+5
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,11 @@ pages for more details on the input and output variable types.
13441344
* pre v4.0: NA
13451345
* post v4.0: fig, ax = **pcv.visualize.pixel_scatter_plot**(*paths_to_imgs, x_channel, y_channel*)
13461346

1347+
#### plantcv.visualize.tile
1348+
1349+
* pre v4.4: NA
1350+
* post v4.4: tile_img = **pcv.visualize.tile**(*img_list, ncol*)
1351+
13471352
#### plantcv.visualize.time_lapse_video
13481353

13491354
* pre v4.0: NA

docs/visualize_tile.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
## Visualize composite of image tiles
2+
3+
This is a plotting method used to examine several output versions, such as from different model fits with varying parameters, all at once.
4+
5+
**plantcv.visualize.tile**(*images, ncol*)
6+
7+
**returns** comp_img
8+
9+
- **Parameters:**
10+
- images - A list of numpy arrays to tile into a composite.
11+
- ncol - Number of columns in composite output. Number of rows is calculated from the number of input images.
12+
13+
- **Example use:**
14+
- Below
15+
16+
17+
```python
18+
19+
from plantcv import plantcv as pcv
20+
import os
21+
22+
# Read in a list of images
23+
images = []
24+
for i in os.listdir("./test_images/"):
25+
images.append(pcv.readimage("./test_images/"+i)[0])
26+
27+
# Examine all images at once
28+
composite = pcv.visualize.tile(images=images, ncol=2)
29+
30+
```
31+
32+
**Input images**
33+
34+
![Screenshot](img/documentation_images/visualize_tile/Tile_1.jpg)
35+
36+
![Screenshot](img/documentation_images/visualize_tile/Tile_2.jpg)
37+
38+
![Screenshot](img/documentation_images/visualize_tile/Tile_3.jpg)
39+
40+
![Screenshot](img/documentation_images/visualize_tile/Tile_4.jpg)
41+
42+
**Output**
43+
44+
![Screenshot](img/documentation_images/visualize_tile/Tile_output.jpg)
45+
46+
**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/main/plantcv/plantcv/visualize/tile.py)

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ nav:
209209
- 'Object Sizes': visualize_obj_sizes.md
210210
- 'Pixel Scatter Plot': 'visualize_pixel_scatter_vis.md'
211211
- 'Pseudocolor': visualize_pseudocolor.md
212+
- 'Tile': visualize_tile.md
212213
- 'Time Lapse Video': visualize_time_lapse_video.md
213214
- 'Watershed Segmentation': watershed.md
214215
- 'White balance': white_balance.md

plantcv/plantcv/visualize/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from plantcv.plantcv.visualize.hyper_histogram import hyper_histogram
1212
from plantcv.plantcv.visualize.pixel_scatter_vis import pixel_scatter_plot
1313
from plantcv.plantcv.visualize.chlorophyll_fluorescence import chlorophyll_fluorescence
14+
from plantcv.plantcv.visualize.tile import tile
1415

1516
__all__ = ["pseudocolor", "colorize_masks", "histogram", "colorspaces", "auto_threshold_methods",
1617
"overlay_two_imgs", "colorize_label_img", "obj_size_ecdf", "obj_sizes", "hyper_histogram",
17-
"pixel_scatter_plot", "time_lapse_video", "chlorophyll_fluorescence"]
18+
"pixel_scatter_plot", "time_lapse_video", "chlorophyll_fluorescence", "tile"]

plantcv/plantcv/visualize/tile.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Tile output images in a plot to visualize all at once
2+
3+
import cv2
4+
import numpy as np
5+
import os
6+
from plantcv.plantcv import params
7+
from plantcv.plantcv._debug import _debug
8+
9+
10+
def _row_resize(row, ncol):
11+
"""Resizes and concatenates objects in a row.
12+
13+
Parameters
14+
----------
15+
row : list of numpy.ndarray
16+
List of images to concatenate.
17+
ncol : int
18+
Number of columns in desired composite image.
19+
20+
Returns
21+
-------
22+
numpy.ndarray
23+
Image concatenated horizontally.
24+
"""
25+
h_min = min(img.shape[0] for img in row)
26+
# Resizing each image so they're the same
27+
row_resize = [cv2.resize(img, (int(img.shape[1] * h_min / img.shape[0]), h_min),
28+
interpolation=cv2.INTER_CUBIC) for img in row]
29+
# Add empty images to the end of the row so things still stay the same size
30+
while len(row_resize) < ncol:
31+
row_resize.append(np.zeros(row_resize[0].shape, dtype=np.uint8))
32+
# Concatenate horizontally
33+
return cv2.hconcat(row_resize)
34+
35+
36+
# Same as _row_resize but for columns
37+
def _col_resize(col):
38+
"""Resized and concatenates objects in a column.
39+
40+
Parameters
41+
----------
42+
col : list of numpy.ndarray
43+
List of images to concatenate vertically.
44+
45+
Returns
46+
-------
47+
numpy.ndarray
48+
Image concatenated vertically.
49+
"""
50+
w_min = min(img.shape[1] for img in col)
51+
col_resize = [cv2.resize(img, (w_min, int(img.shape[0] * w_min / img.shape[1])),
52+
interpolation=cv2.INTER_CUBIC) for img in col]
53+
return cv2.vconcat(col_resize)
54+
55+
56+
# The function that does the tiling
57+
def tile(images, ncol):
58+
"""Tile a list of images into a composite with given dimensions.
59+
60+
Parameters
61+
----------
62+
images : list of numpy.ndarray
63+
List of images to tile.
64+
ncol : int
65+
Number of columns in desired composite image.
66+
67+
Returns
68+
-------
69+
numpy.ndarray
70+
Tiled composite image.
71+
"""
72+
# Increment the device counter
73+
params.device += 1
74+
75+
# Calculate number of rows - always rounds up
76+
nrow = int(len(images) / ncol) + (len(images) % ncol > 0)
77+
tracker = 0
78+
mat = []
79+
for _ in range(nrow):
80+
row = []
81+
for _ in range(ncol):
82+
if tracker <= (len(images) - 1):
83+
row.append(images[tracker])
84+
tracker += 1
85+
mat.append(_row_resize(row, ncol))
86+
comp_img = _col_resize(mat)
87+
_debug(visual=comp_img, filename=os.path.join(params.debug_outdir, f"{params.device}_tile.png"))
88+
return comp_img

tests/plantcv/visualize/conftest.py

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ def __init__(self):
2222
self.small_composed_contours_file = os.path.join(self.datadir, "setaria_small_plant_composed_contours.npz")
2323
# PlantCV hyperspectral image object
2424
self.hsi_file = os.path.join(self.datadir, "hsi.pkl")
25+
# Tile image directory
26+
self.tile_dir = os.path.join(self.datadir, "visualize_tile/")
27+
# Tile image output
28+
self.tile_out = os.path.join(self.datadir, "Tile_output.jpg")
2529

2630
@staticmethod
2731
def load_hsi(pkl_file):

tests/plantcv/visualize/test_tile.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import cv2
2+
from plantcv.plantcv.visualize import tile
3+
4+
5+
def test_tile(visualize_test_data):
6+
"""Test for PlantCV."""
7+
# Read in image list
8+
images = []
9+
for _ in range(4):
10+
images.append(cv2.imread(visualize_test_data.small_rgb_img))
11+
composite = tile(images=images, ncol=3)
12+
assert composite.shape == (670, 1200, 3)

0 commit comments

Comments
 (0)