44import numpy as np
55import imageio
66import pandas as pd
7+ from matplotlib import pyplot as plt
8+ from scipy .ndimage import gaussian_filter
79from tqdm import tqdm
8- from PIL import Image
10+ from PIL import Image , ImageFilter
11+ from scipy .stats import beta
912
1013from bootplot .backend .base import Backend , create_backend
1114from bootplot .sorting import sort_images
15+ from collections import Counter
1216
1317
1418def plot (plot_function : callable ,
@@ -21,21 +25,42 @@ def plot(plot_function: callable,
2125 else :
2226 plot_function (data [indices ], data , * backend .plot_args , ** kwargs )
2327
28+ def symmetric_transformation_new (x , k = 0.5 , threshold = 0.3 ):
29+ y = beta .cdf (x , k , k )
30+ return (1 - 2 * threshold ) * y + threshold
31+
32+ def adjust_relative_frequencies_opt (relative_frequencies ):
33+ dominant_color = max (relative_frequencies , key = relative_frequencies .get )
34+ transformed_dominant = symmetric_transformation_new (relative_frequencies [dominant_color ])
35+ sum_other = 1 - relative_frequencies [dominant_color ]
36+ transformed_other = 1 - transformed_dominant
37+ return {
38+ color : transformed_other * rel_freq / sum_other if color != dominant_color else
39+ transformed_dominant
40+ for color , rel_freq in relative_frequencies .items ()
41+ }
42+
43+ def merge_images (images : np .ndarray ) -> np .ndarray :
44+ num_images , rows , cols , _ = images .shape
45+ new_image = np .zeros ((rows , cols , 3 ), dtype = np .uint8 )
46+
47+ # Iterate over each pixel location
48+ for i in range (rows ):
49+ for j in range (cols ):
50+ # Extract the colors at the current pixel location across all images
51+ pixel_colors = [tuple (images [img , i , j ]) for img in range (num_images )]
52+ # Count the occurrence of each color in this list of colors
53+ color_counts = Counter (pixel_colors )
54+ percentages_old = {color : count / sum (color_counts .values ()) for color , count in color_counts .items ()}
55+ if len (percentages_old ) > 1 :
56+ percentages = adjust_relative_frequencies_opt (percentages_old )
57+ new_color = np .sum ([np .array (c ) * p for c , p in percentages .items ()], axis = 0 )
58+ new_color = np .clip (new_color , 0 , 255 ).astype (np .uint8 )
59+ new_image [i , j ] = new_color
60+ else :
61+ new_image [i ,j ] = list (percentages_old .keys ())[0 ]
62+ return new_image
2463
25- def merge_images (images : np .ndarray , power : float = 1.0 ) -> np .ndarray :
26- """
27- Merge images into a static image (averaged image).
28- The shape of images is (batch_size, width, height, channels).
29- This operation overwrites input images.
30-
31- :param images: images corresponding to different bootstrap samples.
32- :param power: raise the merged image pixels to the specified power to increase contrast.
33- :return: merged image.
34- """
35- images = images .astype (np .float32 ) / 255 # Cast to float
36- merged = np .mean (images , axis = 0 ) ** power
37- merged = (merged * 255 ).astype (np .uint8 )
38- return merged
3964
4065
4166def decay_images (images : np .ndarray ,
@@ -67,7 +92,6 @@ def bootplot(f: callable,
6792 output_size_px : Tuple [int , int ] = (512 , 512 ),
6893 output_image_path : Union [str , Path ] = None ,
6994 output_animation_path : Union [str , Path ] = None ,
70- contrast_modifier : float = 1.0 ,
7195 sort_type : str = 'tsp' ,
7296 sort_kwargs : dict = None ,
7397 decay : int = 0 ,
@@ -104,10 +128,6 @@ def bootplot(f: callable,
104128 filename extension. If None, the animation is not created. Default: ``None``.
105129 :type output_animation_path: str or pathlib.Path
106130
107- :param contrast_modifier: modify the contrast in the static image. Setting this to 1 keeps the same contrast,
108- setting this to less than 1 reduces contrast, setting this to greater than 1 increases contrast. Default: ``1``.
109- :type contrast_modifier: float
110-
111131 :param sort_type: method to sort images when constructing the animation. Should be one of the following:
112132 "tsp" (traveling salesman method on the image similarity graph), "pca" (image projection onto the real line
113133 using PCA), "hm" (order using center mass in the horizontal direction), "none" (no sorting; random order).
@@ -183,7 +203,7 @@ def bootplot(f: callable,
183203 backend .close_figure ()
184204 images = np .stack (images )
185205
186- merged_image = merge_images (images , power = contrast_modifier ) [..., :3 ] # Do not use the alpha channel
206+ merged_image = merge_images (images [..., :3 ])
187207
188208 if output_image_path is not None :
189209 if verbose :
0 commit comments