Image Feature Extraction
Transformers
Safetensors
skinmap
feature-extraction
dermatology
medical-imaging
embeddings
clip
custom_code
Instructions to use Digital-Dermatology/SkinMap with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Digital-Dermatology/SkinMap with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-feature-extraction", model="Digital-Dermatology/SkinMap", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Digital-Dermatology/SkinMap", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
| import gc | |
| import math | |
| import warnings | |
| from typing import Optional, Tuple, Union | |
| import matplotlib.cm as cm | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import scienceplots | |
| import scipy | |
| import seaborn as sns | |
| import torch | |
| import torch.nn.functional as F | |
| import umap | |
| from loguru import logger | |
| from matplotlib.gridspec import SubplotSpec | |
| from sklearn.metrics import auc | |
| from torchvision import transforms | |
| warnings.filterwarnings("ignore", category=FutureWarning) | |
| from ..models.mae.utils import unpatch_images | |
| MEAN = torch.tensor([0.485, 0.456, 0.406]).cpu() | |
| STD = torch.tensor([0.229, 0.224, 0.225]).cpu() | |
| def denormalize_image(image: Union[torch.Tensor, np.ndarray]) -> torch.Tensor: | |
| if type(image) == np.ndarray or type(image) == np.memmap: | |
| image = torch.Tensor(image) | |
| return image.cpu() * STD[:, None, None] + MEAN[:, None, None] | |
| def visualize_self_attention( | |
| model: torch.nn.Module, | |
| images: torch.Tensor, | |
| n_iter: Optional[int] = None, | |
| patch_size: int = 16, | |
| multi_gpu: bool = False, | |
| wandb_cat: str = "Attention", | |
| imgs_to_visualize: int = 10, | |
| remove_cls_token: bool = True, | |
| adapt_patch_size: bool = False, | |
| ): | |
| import wandb | |
| if multi_gpu: | |
| model = model.module | |
| if "backbone" in dir(model): | |
| if hasattr(model.backbone, "get_last_selfattention"): | |
| attentions = model.backbone.get_last_selfattention(images) | |
| else: | |
| return | |
| else: | |
| if hasattr(model, "get_last_selfattention"): | |
| attentions = model.get_last_selfattention(images) | |
| else: | |
| return | |
| w_featmap = images.shape[-2] // patch_size | |
| h_featmap = images.shape[-1] // patch_size | |
| if adapt_patch_size: | |
| patch_size = images.shape[-1] // int(math.sqrt(attentions.shape[-1])) | |
| w_featmap = int(math.sqrt(attentions.shape[-2])) | |
| h_featmap = int(math.sqrt(attentions.shape[-1])) | |
| # number of head | |
| nh = attentions.shape[1] | |
| # loop over the number of images to visualize | |
| for idx_img in range(imgs_to_visualize): | |
| # we keep only the output patch attention | |
| att = attentions[idx_img, :, 0, int(remove_cls_token) :].reshape(nh, -1) | |
| att = att.reshape(nh, w_featmap, h_featmap) | |
| att = F.interpolate(att.unsqueeze(0), scale_factor=patch_size, mode="nearest") | |
| att = att[0].cpu() | |
| att_img = sum(att[i] * 1 / att.shape[0] for i in range(att.shape[0])) | |
| # create the mean attention plot | |
| fig, axes = plt.subplots(1, 3, figsize=(15, 5)) | |
| axes[0].imshow(transforms.ToPILImage()(denormalize_image(images[idx_img]))) | |
| axes[1].imshow(att_img, cmap="inferno") | |
| axes[2].imshow(transforms.ToPILImage()(denormalize_image(images[idx_img]))) | |
| axes[2].imshow(att_img, cmap="inferno", alpha=0.4) | |
| [ax.set_axis_off() for ax in axes.ravel()] | |
| # visualize all heads | |
| mean_attention = wandb.Image(att_img, caption="mean_attention") | |
| in_img = wandb.Image(images[idx_img], caption="input") | |
| # make a grid of all attention heads | |
| l_att = [wandb.Image(att[i], caption=f"head_{i}") for i in range(att.shape[0])] | |
| l_att = [in_img, mean_attention] + l_att | |
| wandb.log( | |
| { | |
| f"{wandb_cat}/attentions_{idx_img}": l_att, | |
| f"{wandb_cat}/attention_mean_{idx_img}": fig, | |
| }, | |
| step=n_iter, | |
| ) | |
| fig.clf() | |
| plt.clf() | |
| def show_image(image: torch.Tensor, title: str = ""): | |
| # image is [H, W, 3] | |
| assert image.shape[2] == 3 | |
| plt.imshow(transforms.ToPILImage()(denormalize_image(image))) | |
| plt.title(title, fontsize=16) | |
| plt.axis("off") | |
| def visualize_mae( | |
| model: torch.nn.Module, | |
| images: torch.Tensor, | |
| n_iter: Optional[int] = None, | |
| patch_size: int = 16, | |
| multi_gpu: bool = False, | |
| wandb_cat: str = "MAE", | |
| imgs_to_visualize: int = 10, | |
| ): | |
| import wandb | |
| if multi_gpu: | |
| model = model.module | |
| # run MAE | |
| _, y, mask, _ = model(images.float(), mask_ratio=0.75) | |
| y = unpatch_images(y, patch_size=patch_size) | |
| y = y.detach().cpu() | |
| # visualize the mask | |
| mask = mask.detach() | |
| mask = mask.unsqueeze(-1).repeat(1, 1, patch_size**2 * 3) # (N, H*W, p*p*3) | |
| mask = unpatch_images(mask, patch_size=patch_size) # 1 is removing, 0 is keeping | |
| mask = mask.detach().cpu() | |
| images = images.detach().cpu() | |
| # masked image | |
| im_masked = images * (1 - mask) | |
| # MAE reconstruction pasted with visible patches | |
| im_paste = images * (1 - mask) + y * mask | |
| # create the MAE plot | |
| for idx_img in range(imgs_to_visualize): | |
| fig, axes = plt.subplots(1, 4, figsize=(20, 5)) | |
| axes[0].imshow(transforms.ToPILImage()(denormalize_image(images[idx_img]))) | |
| axes[0].title.set_text("original") | |
| axes[1].imshow(transforms.ToPILImage()(denormalize_image(im_masked[idx_img]))) | |
| axes[1].title.set_text("masked") | |
| axes[2].imshow(transforms.ToPILImage()(denormalize_image(y[idx_img]))) | |
| axes[2].title.set_text("reconstruction") | |
| axes[3].imshow(transforms.ToPILImage()(denormalize_image(im_paste[idx_img]))) | |
| axes[3].title.set_text("reconstruction + visible") | |
| [ax.set_axis_off() for ax in axes.ravel()] | |
| wandb.log( | |
| { | |
| f"{wandb_cat}/mae_visualization_{idx_img}": fig, | |
| }, | |
| step=n_iter, | |
| ) | |
| fig.clf() | |
| plt.clf() | |
| def visualize_worst_duplicate_ranking( | |
| ranking_target, | |
| pred_dups_scores, | |
| pred_dups_indices, | |
| images, | |
| paths: Optional[np.ndarray], | |
| imgs_to_visualize: int = 5, | |
| wandb_cat: str = "NearDuplicates", | |
| n_iter: Optional[int] = None, | |
| ): | |
| import wandb | |
| dup_indices = np.where(np.asarray(ranking_target) == 1)[0] | |
| imgs_to_visualize = min(len(dup_indices), imgs_to_visualize) | |
| fig, ax = plt.subplots(imgs_to_visualize, 2, figsize=(3, 10)) | |
| for n_worst in range(imgs_to_visualize): | |
| worst_idx = dup_indices[-(n_worst + 1)] | |
| sim = pred_dups_scores[worst_idx] | |
| (i, j) = pred_dups_indices[worst_idx] | |
| i, j = int(i), int(j) | |
| ax[n_worst, 0].imshow(transforms.ToPILImage()(denormalize_image(images[i]))) | |
| ax[n_worst, 1].imshow(transforms.ToPILImage()(denormalize_image(images[j]))) | |
| ax[n_worst, 0].set_title(f"Pair: ({i}, {j})", fontsize=10) | |
| ax[n_worst, 1].set_title(f"Score: {sim:.3f}", fontsize=10) | |
| ax[n_worst, 0].set_xticks([]) | |
| ax[n_worst, 0].set_yticks([]) | |
| ax[n_worst, 1].set_xticks([]) | |
| ax[n_worst, 1].set_yticks([]) | |
| wandb.log({f"{wandb_cat}/worst_alignment": fig}, step=n_iter) | |
| fig.clf() | |
| plt.clf() | |
| def visualize_worst_label_error_ranking( | |
| ranking_target, | |
| pred_le_indices, | |
| images, | |
| lbls: torch.Tensor, | |
| class_labels: list, | |
| imgs_to_visualize: int = 5, | |
| wandb_cat: str = "LabelErrors", | |
| n_iter: Optional[int] = None, | |
| ): | |
| import wandb | |
| true_le_indices = np.where(np.asarray(ranking_target) == 1)[0] | |
| imgs_to_visualize = min(len(true_le_indices), imgs_to_visualize) | |
| fig, ax = plt.subplots(1, imgs_to_visualize, figsize=(10, 3)) | |
| for n_worst in range(imgs_to_visualize): | |
| worst_idx = true_le_indices[-(n_worst + 1)] | |
| idx = int(pred_le_indices[worst_idx]) | |
| ax[n_worst].imshow(transforms.ToPILImage()(denormalize_image(images[idx]))) | |
| title = f"rank: {worst_idx}\nlabel: {class_labels[lbls[idx]]}" | |
| ax[n_worst].set_title(title) | |
| ax[n_worst].set_xticks([]) | |
| ax[n_worst].set_yticks([]) | |
| wandb.log({f"{wandb_cat}/worst_alignment": fig}, step=n_iter) | |
| fig.clf() | |
| plt.clf() | |
| def visualize_nearest_neighbors( | |
| embeddings: torch.Tensor, | |
| imgs: torch.Tensor, | |
| n_iter: Optional[int] = None, | |
| imgs_to_visualize: int = 10, | |
| wandb_cat: str = "", | |
| ): | |
| import wandb | |
| cos = torch.nn.CosineSimilarity(dim=0) | |
| # loop over the number of images to visualize | |
| for idx_img in range(imgs_to_visualize): | |
| cos_sim = torch.Tensor([cos(x, embeddings[idx_img]) for x in embeddings]) | |
| cos_top = torch.topk(cos_sim, 5) | |
| nn_imgs = [wandb.Image(imgs[idx_img], caption="Anchor")] | |
| nn_imgs += [ | |
| wandb.Image(imgs[idx], caption=f"Sim: {val:.4f}") | |
| for idx, val in zip(cos_top.indices, cos_top.values) | |
| ] | |
| wandb.log( | |
| {f"{wandb_cat}nearest_neighbors/imgs_{idx_img}": nn_imgs}, | |
| step=n_iter, | |
| ) | |
| del nn_imgs | |
| def log_segmentation_pred( | |
| img: torch.Tensor, | |
| mask: torch.Tensor, | |
| target: torch.Tensor, | |
| mode: str = "binary", | |
| n_imgs: int = 3, | |
| ): | |
| import wandb | |
| for idx in range(n_imgs): | |
| if mode == "binary": | |
| mask_img = (torch.sigmoid(mask[idx].squeeze()) > 0.5).int().cpu().numpy() | |
| tar_img = (torch.sigmoid(target[idx].squeeze()) > 0.5).int().cpu().numpy() | |
| elif mode == "multiclass": | |
| mask_img = mask.argmax(dim=1)[idx].int().cpu().numpy() | |
| tar_img = target[idx].int().cpu().numpy() | |
| else: | |
| raise ValueError("Unknown mode.") | |
| wandb_img = wandb.Image( | |
| img[idx].cpu(), | |
| masks={ | |
| "predictions": { | |
| "mask_data": mask_img, | |
| }, | |
| "ground_truth": { | |
| "mask_data": tar_img, | |
| }, | |
| }, | |
| ) | |
| wandb.log({f"valid_seg_prediction/img_{idx}": wandb_img}) | |
| def log_wandb_line_plot( | |
| x_values: list, | |
| y_values: list, | |
| title: str = "", | |
| wandb_id: str = "plot", | |
| ): | |
| import wandb | |
| data = [[x, y] for (x, y) in zip(x_values, y_values)] | |
| table = wandb.Table(data=data, columns=["x", "y"]) | |
| wandb.log({wandb_id: wandb.plot.line(table, "x", "y", title=title)}) | |
| def embedding_plot( | |
| X: np.ndarray, | |
| y: Optional[np.ndarray] = None, | |
| figsize: Tuple[int, int] = (5, 5), | |
| ax=None, | |
| ): | |
| x_min, x_max = np.min(X, axis=0), np.max(X, axis=0) | |
| X = (X - x_min) / (x_max - x_min) | |
| if ax is None: | |
| plt.figure(figsize=figsize) | |
| ax = plt.subplot() | |
| if y is not None: | |
| colors = cm.rainbow(np.linspace(0, 1, len(set(y)))) | |
| for id_cls, color in zip(set(y), colors): | |
| cls_idx = np.where(y == id_cls)[0] | |
| ax.scatter( | |
| X[cls_idx, 0], | |
| X[cls_idx, 1], | |
| label=id_cls, | |
| color=color, | |
| alpha=0.7, | |
| ) | |
| plt.legend() | |
| else: | |
| ax.scatter(X[:, 0], X[:, 1], alpha=0.7) | |
| if ax is None: | |
| plt.xticks([]), plt.yticks([]) | |
| plt.show() | |
| else: | |
| ax.set_xticks([]) | |
| ax.set_yticks([]) | |
| def calculate_scores_from_ranking( | |
| ranking: Union[list, np.ndarray], | |
| log_wandb: bool = False, | |
| wandb_cat: str = "", | |
| show_plots: bool = True, | |
| show_scores: bool = True, | |
| log_dict: dict = {}, | |
| path: Optional[str] = None, | |
| fig=None, | |
| axes=None, | |
| prefix_plot="", | |
| linestyle="solid", | |
| ): | |
| import wandb | |
| # vectorized implementation | |
| target = np.asarray(ranking) | |
| n_true = np.sum(target == 1) | |
| n_false = np.sum(target == 0) | |
| # used for precision-recall-gain | |
| # proportion of positives | |
| pi = n_true / len(target) | |
| n_t = np.cumsum(target) | |
| n_f = np.cumsum(1 - target) | |
| l_tpr = n_t / n_true | |
| l_fpr = n_f / n_false | |
| l_precision = n_t / (n_t + n_f) | |
| # precision-recall-gain | |
| with np.errstate(all="ignore"): | |
| l_precision_gain = (l_precision - pi) / ((1 - pi) * l_precision) | |
| l_precision_gain = l_precision_gain.clip(min=0, max=1) | |
| l_recall_gain = (l_tpr - pi) / ((1 - pi) * l_tpr) | |
| l_recall_gain = l_recall_gain.clip(min=0, max=1) | |
| for k in [1, 5, 10, 20, 50, 100, 500, 1000]: | |
| log_dict[f"{wandb_cat}evaluation/Recall@{k}"] = l_tpr[k - 1] | |
| log_dict[f"{wandb_cat}evaluation/Precision@{k}"] = l_precision[k - 1] | |
| if show_scores: | |
| logger.info( | |
| f"Recall@{k}: {l_tpr[k-1]*100:.1f}, \t" | |
| f"Precision@{k}: {l_precision[k-1]*100:.1f}" | |
| ) | |
| score_auc = auc(l_fpr, l_tpr) | |
| # Return the step function integral | |
| # The following works because the last entry of precision is | |
| # guaranteed to be 1, as returned by precision_recall_curve | |
| sl = slice(None, None, -1) | |
| # Average Precision | |
| l_fpr = np.append(l_fpr[sl], [0.0]) | |
| l_tpr = np.append(l_tpr[sl], [0.0]) | |
| l_precision = np.append(l_precision[sl], [1.0]) | |
| score_ap = -np.sum(np.diff(l_tpr) * l_precision[:-1]) | |
| # area under the Precision-Recall-Gain curve (AUPRG) | |
| l_recall_gain = np.append(l_recall_gain[sl], [0.0]) | |
| l_precision_gain = np.append(l_precision_gain[sl], [1.0]) | |
| score_auprg = -np.sum(np.diff(l_recall_gain) * l_precision_gain[:-1]) | |
| # save the metrics | |
| log_dict[f"{wandb_cat}evaluation/AUROC"] = score_auc | |
| log_dict[f"{wandb_cat}evaluation/AP"] = score_ap | |
| log_dict[f"{wandb_cat}evaluation/AUPRG"] = score_auprg | |
| if show_scores: | |
| logger.info(f"AUROC (%): {score_auc*100:.1f}") | |
| logger.info(f"AP (%): {score_ap*100:.1f}") | |
| logger.info(f"AUPRG (%): {score_auprg*100:.1f}") | |
| logger.info(f"Percentage Pos. (%): {pi*100:.1f}") | |
| if show_plots: | |
| with plt.style.context(["science", "std-colors", "grid"]): | |
| plot_existing = True | |
| if fig is None and axes is None: | |
| plot_existing = False | |
| fig, axes = plt.subplots(1, 3, figsize=(15, 5)) | |
| axes[0].plot( | |
| l_fpr, | |
| l_tpr, | |
| label=f"{prefix_plot}AUROC = {score_auc*100:.1f}", | |
| linestyle=linestyle, | |
| ) | |
| axes[0].plot([0, 1], ls="--", color="gray") | |
| axes[0].set_xlabel("False Positive Rate (1-specificity)") | |
| axes[0].set_ylabel("True Positive Rate (sensitivity)") | |
| axes[0].set_title("ROC curve") | |
| axes[0].set_xlim([-0.05, 1.05]) | |
| axes[0].set_ylim([-0.05, 1.05]) | |
| axes[0].legend() | |
| axes[1].plot( | |
| l_tpr, | |
| l_precision, | |
| label=f"{prefix_plot}AP = {score_ap*100:.1f}", | |
| drawstyle="steps-post", | |
| linestyle=linestyle, | |
| ) | |
| axes[1].set_title("Precision-Recall curve") | |
| axes[1].set_xlabel("Recall (sensitivity)") | |
| axes[1].set_ylabel("Precision (positive predicted value)") | |
| axes[1].set_xlim([-0.05, 1.05]) | |
| axes[1].set_ylim([-0.05, 1.05]) | |
| axes[1].legend() | |
| axes[2].plot( | |
| l_recall_gain, | |
| l_precision_gain, | |
| label=f"{prefix_plot}AUPRG = {score_auprg*100:.1f}", | |
| drawstyle="steps-post", | |
| linestyle=linestyle, | |
| ) | |
| axes[2].plot([1, 0], ls="--", color="gray") | |
| axes[2].set_title("Precision-Recall-Gain curve") | |
| axes[2].set_xlabel("Recall Gain") | |
| axes[2].set_ylabel("Precision Gain") | |
| axes[2].set_xlim([-0.05, 1.05]) | |
| axes[2].set_ylim([-0.05, 1.05]) | |
| axes[2].legend() | |
| fig.tight_layout() | |
| wandb_fig = wandb.Image(fig) | |
| log_dict[f"{wandb_cat}evaluation/evaluation"] = wandb_fig | |
| if path is not None: | |
| plt.savefig(path, bbox_inches="tight") | |
| if plot_existing: | |
| return log_dict | |
| plt.show() | |
| plt.close(fig) | |
| plt.figure().clear() | |
| plt.close("all") | |
| plt.close() | |
| plt.cla() | |
| plt.clf() | |
| del fig, axes, wandb_fig | |
| gc.collect() | |
| if log_wandb: | |
| wandb.log(log_dict) | |
| return log_dict | |
| def create_subtitle( | |
| fig: plt.Figure, | |
| grid: SubplotSpec, | |
| title: str, | |
| fontsize: int = 16, | |
| ): | |
| "Sign sets of subplots with title" | |
| row = fig.add_subplot(grid) | |
| # the '\n' is important | |
| row.set_title(f"{title}\n", fontweight="semibold", fontsize=fontsize, loc="left") | |
| # hide subplot | |
| row.set_frame_on(False) | |
| row.axis("off") | |
| def plot_dist( | |
| scores: np.ndarray, | |
| title: Optional[str] = None, | |
| test_if_normal: bool = False, | |
| ): | |
| if test_if_normal: | |
| stat, p = scipy.stats.shapiro(scores) | |
| logger.info( | |
| f"(Shapiro-Wilk test for normality) stat: {stat:.4f}, p-value: {p:.8f}, Gaussian: {p > 0.05}" | |
| ) | |
| stat, p = scipy.stats.kstest(scores, "norm") | |
| logger.info( | |
| f"(Kolmogorov-Smirnov test for normality) stat: {stat:.4f}, p-value: {p:.8f}, Gaussian: {p > 0.05}" | |
| ) | |
| # plot distribution | |
| fig, axes = plt.subplots(1, 4, figsize=(20, 5)) | |
| axes[0].hist(scores, bins="auto", density=True) | |
| axes[0].set_ylabel("Probability") | |
| scipy.stats.probplot(scores, dist="norm", plot=axes[1]) | |
| sns.ecdfplot(scores, ax=axes[2]) | |
| sns.boxplot(scores, ax=axes[3]) | |
| fig.suptitle(title) | |
| plt.show() | |
| def plot_prg( | |
| prg_curve, | |
| show_convex_hull=True, | |
| show_f_calibrated_scores=False, | |
| ax=None, | |
| label=None, | |
| ): | |
| """Plot the Precision-Recall-Gain curve | |
| This function plots the Precision-Recall-Gain curve resulting from the | |
| function create_prg_curve using ggplot. More information on | |
| Precision-Recall-Gain curves and how to cite this work is available at | |
| http://www.cs.bris.ac.uk/~flach/PRGcurves/. | |
| @param prg_curve the data structure resulting from the function create_prg_curve | |
| @param show_convex_hull whether to show the convex hull (default: TRUE) | |
| @param show_f_calibrated_scores whether to show the F-calibrated scores (default:TRUE) | |
| @return the ggplot object which can be plotted using print() | |
| @details This function plots the Precision-Recall-Gain curve, indicating | |
| for each point whether it is a crossing-point or not (see help on | |
| create_prg_curve). By default, only the part of the curve | |
| within the unit square [0,1]x[0,1] is plotted. | |
| @examples | |
| labels = c(1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,1,0,1) | |
| scores = (25:1)/25 | |
| plot_prg(create_prg_curve(labels,scores)) | |
| """ | |
| pg = prg_curve["precision_gain"] | |
| rg = prg_curve["recall_gain"] | |
| if ax is None: | |
| fig = plt.figure(figsize=(6, 5)) | |
| plt.clf() | |
| plt.axes(frameon=False) | |
| ax = fig.gca() | |
| ax.set_xlim((-0.05, 1.05)) | |
| ax.set_ylim((-0.05, 1.05)) | |
| indices = np.logical_or(prg_curve["is_crossing"], prg_curve["in_unit_square"]) | |
| ax.plot(rg[indices], pg[indices], linewidth=1, alpha=0.9, label=label) | |
| ax.set_xlabel("Recall-Gain") | |
| ax.set_ylabel("Precision-Gain") | |
| valid_points = np.logical_and(~np.isnan(rg), ~np.isnan(pg)) | |
| upper_hull = convex_hull(zip(rg[valid_points], pg[valid_points])) | |
| rg_hull, pg_hull = zip(*upper_hull) | |
| if show_convex_hull: | |
| ax.plot(rg_hull, pg_hull, "r--") | |
| if show_f_calibrated_scores: | |
| raise Exception("Show calibrated scores not implemented yet") | |
| def plot_pr(prg_curve): | |
| p = prg_curve["precision"] | |
| r = prg_curve["recall"] | |
| fig = plt.figure(figsize=(6, 5)) | |
| plt.clf() | |
| plt.axes(frameon=False) | |
| ax = fig.gca() | |
| ax.set_xticks(np.arange(0, 1.25, 0.25)) | |
| ax.set_yticks(np.arange(0, 1.25, 0.25)) | |
| ax.grid(b=True) | |
| ax.set_xlim((-0.05, 1.02)) | |
| ax.set_ylim((-0.05, 1.02)) | |
| ax.set_aspect("equal") | |
| # Plot vertical and horizontal lines crossing the 0 axis | |
| plt.axvline(x=0, ymin=-0.05, ymax=1, color="k") | |
| plt.axhline(y=0, xmin=-0.05, xmax=1, color="k") | |
| plt.axvline(x=1, ymin=0, ymax=1, color="k") | |
| plt.axhline(y=1, xmin=0, xmax=1, color="k") | |
| # Plot blue lines | |
| plt.plot(r, p, "ob-", linewidth=2) | |
| plt.xlabel("Recall") | |
| plt.ylabel("Precision") | |
| plt.show() | |
| return fig | |
| def embedding_plot( | |
| X: np.ndarray, | |
| y: Optional[np.ndarray] = None, | |
| label_dict: Optional[dict] = None, | |
| figsize: Tuple[int, int] = (10, 5), | |
| save_path: Optional[str] = None, | |
| return_figure: bool = False, | |
| max_samples: int = 5_000, | |
| debug: bool = False, | |
| ): | |
| # Subsample image embeddings if more than X samples (stratified by class) | |
| if y is not None and X.shape[0] > max_samples: | |
| logger.debug( | |
| f"Embeddings exceed {max_samples} samples: {X.shape[0]}. Subsampling..." | |
| ) | |
| unique_labels, counts = np.unique(y, return_counts=True) | |
| total_samples = X.shape[0] | |
| subsampled_indices = [] | |
| for lab, count in zip(unique_labels, counts): | |
| # Find indices for this class | |
| class_indices = np.where(y == lab)[0] | |
| # Compute number of samples for this class (proportional to its frequency) | |
| n_samples = int(np.round((count / total_samples) * max_samples)) | |
| # Ensure at least one sample is taken if that class is present | |
| if n_samples == 0 and count > 0: | |
| n_samples = 1 | |
| chosen = np.random.choice(class_indices, size=n_samples, replace=False) | |
| subsampled_indices.extend(chosen) | |
| subsampled_indices = np.array(subsampled_indices) | |
| X = X[subsampled_indices] | |
| y = y[subsampled_indices] | |
| elif X.shape[0] > max_samples: | |
| logger.debug( | |
| f"Embeddings exceed {max_samples} samples: {X.shape[0]}. Subsampling..." | |
| ) | |
| indices = np.random.choice( | |
| np.arange(X.shape[0]), size=max_samples, replace=False | |
| ) | |
| X = X[indices] | |
| if X.shape[0] > 2: | |
| # apply if the dimension is larger than 2 | |
| umap_transformer = umap.UMAP( | |
| n_components=2, n_neighbors=100, random_state=42, n_jobs=1 | |
| ) | |
| X = umap_transformer.fit_transform(X) | |
| x_min, x_max = np.min(X, axis=0), np.max(X, axis=0) | |
| X = (X - x_min) / (x_max - x_min) | |
| plt.figure(figsize=figsize) | |
| fig, ax = plt.subplots(1, 1) | |
| if y is not None: | |
| if label_dict is not None: | |
| colors = cm.rainbow(np.linspace(0, 1, len(set(y)))) | |
| for id_cls, color in zip(set(y), colors): | |
| cls_idx = np.where(y == id_cls)[0] | |
| id_cls = label_dict.get(id_cls, id_cls) | |
| ax.scatter( | |
| X[cls_idx, 0], | |
| X[cls_idx, 1], | |
| label=id_cls, | |
| color=color, | |
| s=20, | |
| alpha=0.5, | |
| ) | |
| plt.legend() | |
| else: | |
| scatterplot = ax.scatter( | |
| X[:, 0], | |
| X[:, 1], | |
| c=y, | |
| cmap="viridis", | |
| s=20, | |
| alpha=0.5, | |
| ) | |
| plt.colorbar(scatterplot, ax=ax) | |
| else: | |
| ax.scatter(X[:, 0], X[:, 1], alpha=0.7, s=20) | |
| plt.xticks([]), plt.yticks([]) | |
| if save_path is not None: | |
| plt.savefig(save_path, bbox_inches="tight") | |
| if debug: | |
| plt.show() | |
| if return_figure: | |
| return fig | |
| def embedding_plot_w_markers( | |
| X: np.ndarray, | |
| y: np.ndarray = None, | |
| label_dict: dict = None, | |
| figsize: tuple = (10, 5), | |
| text_X: np.ndarray = None, | |
| text_y: np.ndarray = None, | |
| text_marker: str = "^", | |
| text_legend_label: str = "Text", | |
| max_samples: int = 5_000, | |
| debug: bool = False, | |
| ): | |
| # Subsample image embeddings if more than X samples (stratified by class) | |
| if y is not None and X.shape[0] > max_samples: | |
| logger.debug( | |
| f"Embeddings exceed {max_samples} samples: {X.shape[0]}. Subsampling..." | |
| ) | |
| unique_labels, counts = np.unique(y, return_counts=True) | |
| total_samples = X.shape[0] | |
| subsampled_indices = [] | |
| for lab, count in zip(unique_labels, counts): | |
| # Find indices for this class | |
| class_indices = np.where(y == lab)[0] | |
| # Compute number of samples for this class (proportional to its frequency) | |
| n_samples = int(np.round((count / total_samples) * max_samples)) | |
| # Ensure at least one sample is taken if that class is present | |
| if n_samples == 0 and count > 0: | |
| n_samples = 1 | |
| chosen = np.random.choice(class_indices, size=n_samples, replace=False) | |
| subsampled_indices.extend(chosen) | |
| subsampled_indices = np.array(subsampled_indices) | |
| X = X[subsampled_indices] | |
| y = y[subsampled_indices] | |
| elif X.shape[0] > max_samples: | |
| logger.debug( | |
| f"Embeddings exceed {max_samples} samples: {X.shape[0]}. Subsampling..." | |
| ) | |
| indices = np.random.choice( | |
| np.arange(X.shape[0]), size=max_samples, replace=False | |
| ) | |
| X = X[indices] | |
| if X.shape[0] > 2: | |
| # apply if the dimension is larger than 2 | |
| if text_X is not None: | |
| X = np.concatenate([text_X, X]) | |
| umap_transformer = umap.UMAP( | |
| n_components=2, n_neighbors=100, random_state=42, n_jobs=1 | |
| ) | |
| X = umap_transformer.fit_transform(X) | |
| x_min, x_max = np.min(X, axis=0), np.max(X, axis=0) | |
| X = (X - x_min) / (x_max - x_min) | |
| if text_X is not None: | |
| text_X = X[: len(text_X)] | |
| X = X[len(text_X) :] | |
| plt.figure(figsize=figsize) | |
| fig, ax = plt.subplots(1, 1) | |
| # If no labels given, just do a single scatter | |
| if y is None: | |
| scatterplot = ax.scatter( | |
| X[:, 0], | |
| X[:, 1], | |
| c=y, | |
| cmap="viridis", | |
| s=20, | |
| alpha=0.5, | |
| ) | |
| plt.colorbar(scatterplot, ax=ax) | |
| else: | |
| unique_labels = np.unique(y) | |
| cmap = plt.cm.get_cmap("tab10", len(unique_labels)) | |
| # Plot the image embeddings by class | |
| for i, lab in enumerate(unique_labels): | |
| idx = y == lab | |
| color = cmap(i) | |
| if label_dict is not None and lab in label_dict: | |
| class_label = label_dict[lab] | |
| else: | |
| class_label = f"Class {lab}" | |
| plt.scatter( | |
| X[idx, 0], | |
| X[idx, 1], | |
| marker="o", | |
| color=color, | |
| label=class_label, | |
| s=20, | |
| alpha=0.5, | |
| ) | |
| # Plot text embeddings if provided | |
| if text_X is not None and text_y is not None: | |
| # Plot each class’s text points with the same color but different marker | |
| for i, lab in enumerate(unique_labels): | |
| idx = text_y == lab | |
| color = cmap(i) | |
| plt.scatter( | |
| text_X[idx, 0], | |
| text_X[idx, 1], | |
| marker=text_marker, | |
| color=color, | |
| alpha=0.9, | |
| ) | |
| # Add one dummy scatter for text marker to appear in legend | |
| text_handle = plt.scatter([], [], marker=text_marker, color="black") | |
| # Combine with existing legend | |
| handles, labels = plt.gca().get_legend_handles_labels() | |
| # Append our single text marker entry | |
| handles.append(text_handle) | |
| labels.append(text_legend_label) | |
| plt.legend(handles, labels, loc="best") | |
| else: | |
| plt.legend(loc="best") | |
| return fig | |