Skip to content

Commit ff59d69

Browse files
sanjanagjwmueller
andauthored
Added title info to visualization with optional show_id arg (#211)
* Added title info to visualization with optional show_id arg * Fixed tests * Fixed type annotations * Added docstring * Update src/cleanvision/imagelab.py Co-authored-by: Jonas Mueller <1390638+jwmueller@users.noreply.github.com> --------- Co-authored-by: Jonas Mueller <1390638+jwmueller@users.noreply.github.com>
1 parent 28c15f0 commit ff59d69

File tree

3 files changed

+94
-72
lines changed

3 files changed

+94
-72
lines changed

src/cleanvision/imagelab.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ def report(
367367
num_images: Optional[int] = None,
368368
verbosity: int = 1,
369369
print_summary: bool = True,
370+
show_id: bool = False,
370371
) -> None:
371372
"""Prints summary of the issues found in your dataset.
372373
By default, this method depicts the images representing top-most severe instances of each issue type.
@@ -391,6 +392,9 @@ def report(
391392
print_summary : bool, default=True
392393
If True, prints the summary of issues found in the dataset.
393394
395+
show_id: bool, default=False
396+
If True, prints the dataset ID of each image shown in the report.
397+
394398
Examples
395399
--------
396400
Default usage
@@ -446,6 +450,7 @@ def report(
446450
issue_type,
447451
report_args["num_images"],
448452
report_args["cell_size"],
453+
show_id,
449454
)
450455
else:
451456
print(
@@ -471,6 +476,7 @@ def _visualize(
471476
issue_type: str,
472477
num_images: int,
473478
cell_size: Tuple[int, int],
479+
show_id: bool,
474480
) -> None:
475481
# todo: remove dependency on issue manager
476482
issue_manager = self._get_issue_manager(issue_type)
@@ -484,22 +490,21 @@ def _visualize(
484490
indices = scores.index.tolist()
485491
images = [self._dataset[i] for i in indices]
486492

487-
titles = [f"score : {x:.4f}" for x in scores]
488-
489-
# Add size information for odd sized images
490-
additional_info = None
493+
# construct title info
494+
title_info = {"scores": [f"score : {x:.4f}" for x in scores]}
495+
if show_id:
496+
title_info["ids"] = [f"id : {i}" for i in indices]
491497
if issue_type == IssueType.ODD_SIZE.value:
492-
additional_info = []
493-
for image in images:
494-
additional_info.append(f"original size: {image.size}")
498+
title_info["size"] = [
499+
f"original size: {image.size}" for image in images
500+
]
495501

496502
if images:
497503
VizManager.individual_images(
498504
images=images,
499-
titles=titles,
505+
title_info=title_info,
500506
ncols=self._config["visualize_num_images_per_row"],
501507
cell_size=cell_size,
502-
additional_info=additional_info,
503508
)
504509

505510
elif viz_name == "image_sets":
@@ -511,15 +516,15 @@ def _visualize(
511516
for indices in image_sets_indices:
512517
image_sets.append([self._dataset[index] for index in indices])
513518

514-
title_sets = [
515-
[self._dataset.get_name(index) for index in s]
516-
for s in image_sets_indices
517-
]
519+
title_info_sets = []
520+
for s in image_sets_indices:
521+
title_info = {"name": [self._dataset.get_name(index) for index in s]}
522+
title_info_sets.append(title_info)
518523

519524
if image_sets:
520525
VizManager.image_sets(
521526
image_sets,
522-
title_sets,
527+
title_info_sets,
523528
ncols=self._config["visualize_num_images_per_row"],
524529
cell_size=cell_size,
525530
)
@@ -532,6 +537,7 @@ def visualize(
532537
issue_types: Optional[List[str]] = None,
533538
num_images: int = 4,
534539
cell_size: Tuple[int, int] = (2, 2),
540+
show_id: bool = False,
535541
) -> None:
536542
"""Show specific images.
537543
@@ -599,24 +605,24 @@ def visualize(
599605
if len(issue_types) == 0:
600606
raise ValueError("issue_types list is empty")
601607
for issue_type in issue_types:
602-
self._visualize(issue_type, num_images, cell_size)
608+
self._visualize(issue_type, num_images, cell_size, show_id)
603609
elif image_files is not None:
604610
if len(image_files) == 0:
605611
raise ValueError("image_files list is empty.")
606612
images = [Image.open(path) for path in image_files]
607-
titles = [path.split("/")[-1] for path in image_files]
613+
title_info = {"path": [path.split("/")[-1] for path in image_files]}
608614
VizManager.individual_images(
609615
images,
610-
titles,
616+
title_info,
611617
ncols=self._config["visualize_num_images_per_row"],
612618
cell_size=cell_size,
613619
)
614620
elif indices:
615621
images = [self._dataset[i] for i in indices]
616-
titles = [self._dataset.get_name(i) for i in indices]
622+
title_info = {"name": [self._dataset.get_name(i) for i in indices]}
617623
VizManager.individual_images(
618624
images,
619-
titles,
625+
title_info,
620626
ncols=self._config["visualize_num_images_per_row"],
621627
cell_size=cell_size,
622628
)
@@ -628,10 +634,12 @@ def visualize(
628634
self._dataset.index, min(num_images, len(self._dataset))
629635
)
630636
images = [self._dataset[i] for i in image_indices]
631-
titles = [self._dataset.get_name(i) for i in image_indices]
637+
title_info = {
638+
"name": [self._dataset.get_name(i) for i in image_indices]
639+
}
632640
VizManager.individual_images(
633641
images,
634-
titles,
642+
title_info,
635643
ncols=self._config["visualize_num_images_per_row"],
636644
cell_size=cell_size,
637645
)

src/cleanvision/utils/viz_manager.py

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Tuple, Optional
1+
from typing import List, Tuple, Dict
22

33
import math
44
import matplotlib.axes
@@ -10,24 +10,23 @@ class VizManager:
1010
@staticmethod
1111
def individual_images(
1212
images: List[Image.Image],
13-
titles: List[str],
13+
title_info: Dict[str, List[str]],
1414
ncols: int,
1515
cell_size: Tuple[int, int],
16-
additional_info: Optional[List[str]] = None,
1716
) -> None:
1817
"""Plots a list of images in a grid."""
19-
plot_image_grid(images, titles, ncols, cell_size, additional_info)
18+
plot_image_grid(images, title_info, ncols, cell_size)
2019

2120
@staticmethod
2221
def image_sets(
2322
image_sets: List[List[Image.Image]],
24-
title_sets: List[List[str]],
23+
title_info_sets: List[Dict[str, List[str]]],
2524
ncols: int,
2625
cell_size: Tuple[int, int],
2726
) -> None:
2827
for i, s in enumerate(image_sets):
2928
print(f"Set: {i}")
30-
plot_image_grid(s, title_sets[i], ncols, cell_size)
29+
plot_image_grid(s, title_info_sets[i], ncols, cell_size)
3130

3231

3332
def set_image_on_axes(image: Image.Image, ax: matplotlib.axes.Axes, title: str) -> None:
@@ -38,51 +37,66 @@ def set_image_on_axes(image: Image.Image, ax: matplotlib.axes.Axes, title: str)
3837
ax.imshow(image, cmap=cmap, vmin=0, vmax=255)
3938

4039

40+
def truncate_titles(cell_width: int, titles: List[str]) -> List[str]:
41+
"""Converts font size of 7 into inches"""
42+
CHARACTER_SIZE_INCHES = 7 * (1 / 72)
43+
44+
chars_allowed = math.ceil(cell_width / CHARACTER_SIZE_INCHES) - 4
45+
46+
k1 = 1
47+
while k1 <= chars_allowed and titles[0][:k1] == titles[1][:k1]:
48+
k1 += 1
49+
k2 = 1
50+
while (
51+
k2 <= chars_allowed
52+
and titles[0][(len(titles[0]) - k2) :] == titles[1][(len(titles[1]) - k2) :]
53+
):
54+
k2 += 1
55+
56+
if k1 > k2:
57+
truncate_from_front = True
58+
else:
59+
truncate_from_front = False
60+
61+
for i in range(len(titles)):
62+
title_width = len(titles[i]) * CHARACTER_SIZE_INCHES
63+
if title_width >= cell_width:
64+
titles[i] = (
65+
("..." + titles[i][len(titles[i]) - chars_allowed :])
66+
if truncate_from_front
67+
else (titles[i][:chars_allowed] + "...")
68+
)
69+
return titles
70+
71+
72+
def construct_titles(title_info: Dict[str, List[str]], cell_width: int) -> List[str]:
73+
keys = list(title_info.keys())
74+
nimages = len(title_info[keys[0]])
75+
76+
# truncate longer lines
77+
if nimages > 1:
78+
for key in keys:
79+
title_info[key] = truncate_titles(cell_width, title_info[key])
80+
81+
# join all keys
82+
titles = []
83+
for i in range(nimages):
84+
titles.append("\n".join(title_info[key][i] for key in keys))
85+
return titles
86+
87+
4188
def plot_image_grid(
4289
images: List[Image.Image],
43-
titles: List[str],
90+
title_info: Dict[str, List[str]],
4491
ncols: int,
4592
cell_size: Tuple[int, int],
46-
additional_info: Optional[List[str]] = None,
4793
) -> None:
4894
nrows = math.ceil(len(images) / ncols)
4995
ncols = min(ncols, len(images))
5096
fig, axes = plt.subplots(
5197
nrows, ncols, figsize=(cell_size[0] * ncols, cell_size[1] * nrows)
5298
)
53-
54-
"""Converts font size of 7 into inches"""
55-
CHARACTER_SIZE_INCHES = 7 * (1 / 72)
56-
57-
chars_allowed = math.ceil(cell_size[0] / CHARACTER_SIZE_INCHES) - 4
58-
59-
if len(images) > 1:
60-
k1 = 1
61-
while k1 <= chars_allowed and titles[0][:k1] == titles[1][:k1]:
62-
k1 += 1
63-
k2 = 1
64-
while (
65-
k2 <= chars_allowed
66-
and titles[0][(len(titles[0]) - k2) :] == titles[1][(len(titles[1]) - k2) :]
67-
):
68-
k2 += 1
69-
70-
if k1 > k2:
71-
truncate_from_front = True
72-
else:
73-
truncate_from_front = False
74-
75-
for i in range(len(images)):
76-
title_width = len(titles[i]) * CHARACTER_SIZE_INCHES
77-
if title_width >= cell_size[0]:
78-
titles[i] = (
79-
("..." + titles[i][len(titles[i]) - chars_allowed :])
80-
if truncate_from_front
81-
else (titles[i][:chars_allowed] + "...")
82-
)
83-
if additional_info is not None:
84-
for i in range(len(images)):
85-
titles[i] = f"{titles[i]}\n{additional_info[i]}"
99+
titles = construct_titles(title_info, cell_size[0])
86100
if nrows > 1:
87101
idx = 0
88102
for i in range(nrows):

tests/test_viz_manager.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,26 @@
77
class TestVizManager:
88
@pytest.mark.usefixtures("set_plt_show")
99
@pytest.mark.parametrize(
10-
("images", "titles"),
10+
("images", "title_info"),
1111
[
12-
([Image.new("L", (100, 100))], ["image_title"]),
13-
([Image.new("L", (100, 100))] * 2, ["image_title"] * 4),
14-
([Image.new("L", (100, 100))] * 6, ["imaxge_title"] * 6),
12+
([Image.new("L", (100, 100))], {"name": ["image_title"]}),
13+
([Image.new("L", (100, 100))] * 2, {"name": ["image_title"] * 4}),
14+
([Image.new("L", (100, 100))] * 6, {"name": ["imaxge_title"] * 6}),
1515
],
1616
ids=["plot single image", "plot <=4 images", "plt > 4 images"],
1717
)
18-
def test_individual_images(self, images, titles):
19-
VizManager.individual_images(images, titles, 4, (2, 2))
18+
def test_individual_images(self, images, title_info):
19+
VizManager.individual_images(images, title_info, 4, (2, 2))
2020

2121
@pytest.mark.usefixtures("set_plt_show")
2222
@pytest.mark.parametrize(
23-
("image_sets", "title_sets"),
23+
("image_sets", "title_info_sets"),
2424
[
2525
(
2626
[[Image.new("L", (100, 100))], [Image.new("L", (100, 100))] * 2],
27-
[["image_title"], ["image_title"] * 2],
27+
[{"name": ["image_title"]}, {"name": ["image_title"] * 2}],
2828
),
2929
],
3030
)
31-
def test_image_sets(self, image_sets, title_sets):
32-
VizManager.image_sets(image_sets, title_sets, 4, (2, 2))
31+
def test_image_sets(self, image_sets, title_info_sets):
32+
VizManager.image_sets(image_sets, title_info_sets, 4, (2, 2))

0 commit comments

Comments
 (0)