Source code for Modules.ContourBoxplot.contour_boxplot_stats

import numpy as np
from uvisbox.Core.BandDepths.contour_banddepth import contour_banddepth
from uvisbox.Core.CommonInterface import BoxplotStyleConfig


[docs] def contour_boxplot_summary_statistics(ensemble_images, isovalue, boxplot_style=None, workers=11): """ Compute summary statistics for contour boxplot from ensemble scalar fields. This function extracts binary contours at the given isovalue, computes their band depths, and generates percentile bands, median, and outlier information. Parameters: ----------- ensemble_images : np.ndarray 3D array of shape (n_ensemble, height, width) containing ensemble scalar fields. isovalue : float Threshold value for creating binary images. Pixels with values < isovalue are set to 1 (inside), values >= isovalue are set to 0 (outside). boxplot_style : BoxplotStyleConfig, optional Configuration for percentiles and outlier detection. If None, uses default configuration. workers : int, optional Number of parallel workers for band depth computation. Default is 11. Returns: -------- dict Dictionary containing: - 'median': Binary image (np.ndarray) representing the median contour sublevel set. 1 = inside contour (value < isovalue), 0 = outside contour (value >= isovalue). - 'percentile_bands': List of tuples (percentile, band_image) where band_image is a 2D array with values in [0, percentile/100]. percentile/100 means inside the band, 0 means outside the band. - 'outliers': List of binary images representing outlier contours (same format as median). - 'sorted_contours': 3D array (n_ensemble, height, width) of binary images sorted by depth. - 'sorted_indices': Array of indices showing the original order of sorted contours. - 'depths': Array of computed band depth values for each contour. Examples: --------- >>> ensemble = np.random.randn(50, 100, 100) >>> stats = contour_boxplot_summary_statistics(ensemble, isovalue=0.5) >>> print(stats.keys()) dict_keys(['median', 'percentile_bands', 'outliers', 'sorted_contours', 'sorted_indices', 'depths']) """ # Use default config if none provided if boxplot_style is None: boxplot_style = BoxplotStyleConfig() # Validate input if not isinstance(ensemble_images, np.ndarray): ensemble_images = np.array(ensemble_images) if ensemble_images.ndim != 3: raise ValueError(f"ensemble_images must be 3D array of shape (n_ensemble, height, width). Got shape {ensemble_images.shape}") n_ensemble, height, width = ensemble_images.shape # Create binary images: 1 if value < isovalue (inside), 0 otherwise (outside) binary_images = (ensemble_images < isovalue).astype(np.bool_) # Compute band depths depths = contour_banddepth(binary_images, workers=workers) # Sort by depth in descending order (highest depth first = median) sorted_indices = np.argsort(depths)[::-1] sorted_contours = binary_images[sorted_indices] sorted_depths = depths[sorted_indices] # Get median (highest depth contour) median = sorted_contours[0].astype(np.uint8) # Compute percentile bands percentile_bands = [] for percentile in sorted(boxplot_style.percentiles, reverse=True): # Calculate how many contours to include n_include = int(np.ceil(n_ensemble * (percentile / 100.0))) n_include = max(1, min(n_include, n_ensemble)) # Get selected contours selected_contours = sorted_contours[:n_include] # Compute band envelope: union minus intersection union = np.any(selected_contours, axis=0) intersection = np.all(selected_contours, axis=0) band_envelope = (union & ~intersection) # Create band image: percentile/100 inside band, 0 outside band_image = np.zeros((height, width), dtype=np.float32) band_image[band_envelope] = percentile / 100.0 percentile_bands.append((percentile, band_image)) # Identify outliers (beyond the largest percentile) outliers = [] if boxplot_style.show_outliers and len(boxplot_style.percentiles) > 0: max_percentile = max(boxplot_style.percentiles) outlier_start_idx = int(np.ceil(n_ensemble * (max_percentile / 100.0))) for idx in range(outlier_start_idx, n_ensemble): outliers.append(sorted_contours[idx].astype(np.uint8)) return { 'median': median, 'percentile_bands': percentile_bands, 'outliers': outliers, 'sorted_contours': sorted_contours, 'sorted_indices': sorted_indices, 'depths': sorted_depths }