Download worldstrat/src/lightning_modules.py from hackvermin/Setellite_super_resolution: direct link, hf CLI and curl.
- Browser
- Download file 34.7 kB
-
https://huggingface.co/spaces/hackvermin/Setellite_super_resolution/resolve/main/worldstrat/src/lightning_modules.py
- Command line
-
hf download hf://spaces/hackvermin/Setellite_super_resolution/worldstrat/src/lightning_modules.py
-
curl -L -o lightning_modules.py https://huggingface.co/spaces/hackvermin/Setellite_super_resolution/resolve/main/worldstrat/src/lightning_modules.py
34.7 kB
| from numpy import sqrt | |
| import torch | |
| import pytorch_lightning as pl | |
| import wandb | |
| from argparse import ArgumentParser | |
| from kornia.contrib import extract_tensor_patches | |
| from torch.optim import Adam, SGD | |
| from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts | |
| from torchvision.utils import make_grid | |
| from src.datasources import S2_ALL_12BANDS | |
| from src.losses import mse_loss, mae_loss, ssim_loss, tv_loss, Shift | |
| from src.modules import BicubicUpscaledBaseline | |
| import pandas as pd | |
| def detach_dict(dict): | |
| """Detaches the tensors in a dictionary. | |
| Parameters | |
| ---------- | |
| dict : dict of Tensor | |
| Dictionary containing the tensors to detach. | |
| Returns | |
| ------- | |
| dict of Tensor | |
| Dictionary containing the detached tensors. | |
| """ | |
| for tensor_key in dict: | |
| if dict[tensor_key].requires_grad: | |
| dict[tensor_key] = dict[tensor_key].detach() | |
| return dict | |
| class LitModel(pl.LightningModule): | |
| """Lightning Module wrapper. | |
| Implements the forward, loss, train/val/test step functions and logging. | |
| Parses and adds default model arguments. | |
| """ | |
| def __init__(self, backbone, **kws) -> None: | |
| """Initialises the LightningModule with the backbone and the default arguments. | |
| The hyperparameters are fetchd from the hparams attribute. | |
| Parameters | |
| ---------- | |
| backbone : nn.Module | |
| Backbone model, as generated by train.py. | |
| """ | |
| super().__init__() | |
| self.save_hyperparameters() # save hyperparams in self.hparams | |
| self.backbone = backbone | |
| self.transform = self.hparams.transform # transform as a torch module | |
| output_channels = [channel for channel in range(self.backbone.out_channels)] | |
| self.baseline = BicubicUpscaledBaseline( | |
| input_size=self.hparams.input_size, | |
| output_size=self.backbone.output_size, | |
| output_channels=output_channels, | |
| interpolation="bicubic", | |
| chip_size=self.hparams.chip_size, | |
| ) | |
| self.check_loss_functions_weights_are_valid() | |
| self.create_shifters() | |
| self.create_metrics() | |
| self.enable_benchmark_logging() | |
| def enable_benchmark_logging(self): | |
| if self.hparams.benchmark: | |
| self.logging = {} | |
| self.logging['current_idx'] = 1 | |
| self.logging['log'] = pd.DataFrame() | |
| self.log_path = '../../log' | |
| def check_loss_functions_weights_are_valid(self): | |
| """Checks that the loss functions weights all sum to 1.""" | |
| assert self.hparams.w_mse + self.hparams.w_mae + self.hparams.w_ssim == 1 | |
| def create_shifters(self): | |
| """Creates a shifter for the training and validation steps, and a shifter for the baseline.""" | |
| self.shifter = Shift( | |
| shift_by_px=self.hparams.shift_px, | |
| mode=self.hparams.shift_mode, | |
| step=self.hparams.shift_step, | |
| use_cache=False, | |
| ) | |
| self.baseline_shifter = Shift( | |
| shift_by_px=self.hparams.shift_px, | |
| mode=self.hparams.shift_mode, | |
| step=self.hparams.shift_step, | |
| use_cache=False, | |
| ) | |
| def create_metrics(self): | |
| """Creates metrics that use shifters when computing the losses (MSE, MAE, SSIM. TV) for the train, validation and test sets.""" | |
| self.metrics_baseline = {} | |
| self.metrics_hr = {} | |
| # TODO: dict of train/val/test metrics | |
| self.train_metrics = self.metrics_factory("train", self.shifter) | |
| self.val_metrics = self.metrics_factory("val", self.shifter) | |
| self.test_metrics = self.metrics_factory("test", self.shifter) | |
| self.baseline_train_metrics = self.metrics_factory( | |
| "train", self.baseline_shifter | |
| ) | |
| self.baseline_val_metrics = self.metrics_factory("val", self.baseline_shifter) | |
| self.baseline_test_metrics = self.metrics_factory("test", self.baseline_shifter) | |
| # Create metrics for train, val, test | |
| def metrics_factory(self, prefix, shifter): | |
| """Creates a dictionary of metrics that use the shifter when computing the losses (MSE, MAE, SSIM. TV) for the train, validation and test sets. | |
| Parameters | |
| ---------- | |
| prefix : str | |
| Prefix for the metrics (train/val/test). | |
| shifter : losses.Shift | |
| Shifter to use when computing the losses. | |
| Returns | |
| ------- | |
| dict | |
| Dictionary of metrics. | |
| """ | |
| return { | |
| f"{prefix}/MSE": lambda y_hat, y: shifter.registered_loss(mse_loss)( | |
| y_hat, y | |
| ), | |
| f"{prefix}/MAE": lambda y_hat, y: shifter.registered_loss(mae_loss)( | |
| y_hat, y | |
| ), | |
| f"{prefix}/SSIM_loss": lambda y_hat, y,: shifter.registered_loss(ssim_loss)( | |
| y_hat, y, window_size=5 | |
| ), | |
| f"{prefix}/TV": lambda y_hat, y=None: tv_loss(y_hat), | |
| } | |
| def forward(self, x, y=None): | |
| """Forward pass of the model. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| Input tensor (LR) of dimensions (batch_size, channels, height, width). | |
| y : Tensor, optional | |
| Target tensor (HR) of dimensions (batch_size, channels, height, width). | |
| Returns | |
| ------- | |
| Tensor | |
| Output tensor (y_hat) of dimensions (batch_size, channels, height, width). | |
| """ | |
| return self.backbone(x, y) | |
| def validation_log(self, y, m, baseline_m, prefix): | |
| log = pd.DataFrame( | |
| columns=[ | |
| "MSE", | |
| "MAE", | |
| "SSIM_loss", | |
| "TV", | |
| "PSNR", | |
| "Baseline MSE", | |
| "Baseline PSNR", | |
| ] | |
| ) | |
| mse, mae, ssim, tv, psnr = ( | |
| m[f"{prefix}/MSE"], | |
| m[f"{prefix}/MAE"], | |
| m[f"{prefix}/SSIM_loss"], | |
| m[f"{prefix}/TV"], | |
| m[f"{prefix}/PSNR"], | |
| ) | |
| baseline_mse, baseline_psnr = ( | |
| baseline_m[f"{prefix}/MSE"], | |
| baseline_m[f"{prefix}/PSNR_baseline"], | |
| ) | |
| baseline_m = list(baseline_m.values()) | |
| for batch_item in range(y.shape[0]): # batch_size | |
| log.loc[self.logging["current_idx"]] = ( | |
| mse[batch_item].detach().item(), | |
| mae[batch_item].detach().item(), | |
| ssim[batch_item].detach().item(), | |
| tv[batch_item].detach().item(), | |
| psnr[batch_item].detach().item(), | |
| baseline_mse[batch_item].detach().item(), | |
| baseline_psnr[batch_item].detach().item(), | |
| ) | |
| self.logging["current_idx"] += 1 | |
| self.logging["log"] = pd.concat([self.logging["log"], log]) | |
| def loss(self, batch, metrics, baseline_metrics, prefix): | |
| """Computes the loss for a batch using the provided metrics. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| Dictionary with the batches for LR/HR/etc. | |
| metrics : dict of Callable | |
| Dictionary with the metrics to use. | |
| baseline_metrics : dict of Callable | |
| Dictionary with the metrics to use for the baseline. | |
| prefix : str | |
| Prefix for the metrics (train/val/test). | |
| Returns | |
| ------- | |
| dict | |
| Dictionary with the losses. | |
| """ | |
| batch = self.transform(batch) # GPU/Batched data augmentation | |
| x, y = batch["lr"], batch["hr"] | |
| # TODO: Run once and store results in a file. | |
| x = self.apply_masks_to_x(x, batch) | |
| y_hat = self(x, y) | |
| y, y_hat = y[:, 0], y_hat[:, 0] | |
| y_hat = self.bias_adjust(y, y_hat) | |
| self.initialise_shifters() | |
| m, mse, mae, ssim, tv = self.compute_metrics(y, y_hat, metrics, prefix) | |
| y_hat_base = self.baseline(x)[:, 0] | |
| y_hat_base = self.bias_adjust(y, y_hat_base) | |
| baseline_m = self.compute_baseline_metrics( | |
| y, y_hat_base, m, baseline_metrics, prefix | |
| ) | |
| loss = ( | |
| (self.hparams.w_mse * mse) | |
| + (self.hparams.w_mae * mae) | |
| + (self.hparams.w_tv * tv) | |
| + (self.hparams.w_ssim * ssim) | |
| ) | |
| if self.hparams.benchmark: | |
| self.validation_log(y, detach_dict(m), baseline_m, prefix) | |
| return { | |
| "y_hat": y_hat, | |
| "y_hat_base": y_hat_base, | |
| "y": y, | |
| "metrics": m, | |
| "baseline_metrics": baseline_m, | |
| "loss": loss, | |
| } | |
| def apply_masks_to_x(self, x, batch): | |
| """Applies the input masks to the input tensor. | |
| Parameters | |
| ---------- | |
| x : torch.Tensor | |
| Input tensor of dimensions (batch_size, channels, height, width). | |
| batch : dict | |
| Dictionary with the batches for LR/Masks/HR/etc. | |
| Returns | |
| ------- | |
| torch.Tensor | |
| Input tensor with the mask channels concatenated. | |
| """ | |
| if self.hparams.use_masks: | |
| masks = batch["lrc"] | |
| x = torch.cat([x, masks], dim=-3) # concat channels | |
| return x | |
| def bias_adjust(y, y_hat): | |
| """Applies the brightness correction to the output. | |
| Parameters | |
| ---------- | |
| y : torch.Tensor | |
| Target tensor (HR) of dimensions (batch_size, channels, height, width). | |
| y_hat : torch.Tensor | |
| Output tensor (SR) of dimensions (batch_size, channels, height, width). | |
| Returns | |
| ------- | |
| torch.Tensor | |
| Output tensor with the brightness correction applied. | |
| """ | |
| bias = (y - y_hat).mean(dim=(-1, -2), keepdim=True) # bias / zero-order | |
| return y_hat + bias | |
| def initialise_shifters(self): | |
| """Initialises the shifter for the HR/SR pairs and the HR/LR Baseline pairs.""" | |
| self.shifter.y, self.shifter.y_hat = None, None | |
| self.baseline_shifter.y, self.baseline_shifter.y_hat = None, None | |
| # NOTE: whatever gets cached early on, might be suboptimal. | |
| # So it would be good to update the cache periodically. | |
| # Clearing the cache every 50 epochs. | |
| if self.current_epoch % 50 == 0: | |
| self.clear_shifter_cache() | |
| def compute_metrics(self, y, y_hat, metrics, prefix): | |
| """Computes the metrics for a batch (MSE, MAE, SSIM, TV, PSNR). | |
| Parameters | |
| ---------- | |
| y : torch.Tensor | |
| Target tensor (HR) of dimensions (batch_size, channels, height, width). | |
| y_hat : torch.Tensor | |
| Output tensor (SR) of dimensions (batch_size, channels, height, width). | |
| metrics : dict of Callable | |
| Dictionary with the metric functions to use. | |
| prefix : str | |
| Prefix for the metrics (train/val/test). | |
| Returns | |
| ------- | |
| dict, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor | |
| Dictionary with the metrics (MSE, MAE, SSIM, TV, PSNR). | |
| The metrics themselves, the MSE, MAE, SSIM, TV. | |
| """ | |
| metrics = { | |
| metrics_function: metrics[metrics_function](y_hat, y) | |
| for metrics_function in metrics | |
| } | |
| mse = metrics[f"{prefix}/MSE"].clone() | |
| mae = metrics[f"{prefix}/MAE"].clone() | |
| ssim = metrics[f"{prefix}/SSIM_loss"].clone() | |
| tv = metrics[f"{prefix}/TV"].clone() | |
| metrics[f"{prefix}/PSNR"] = -10.0 * torch.log10(mse) | |
| return metrics, mse, mae, ssim, tv | |
| def compute_baseline_metrics( | |
| self, y, y_hat_base, metrics, baseline_metrics, prefix | |
| ): | |
| """Computes the baseline metrics for a batch (MSE, MAE, SSIM, TV, PSNR_baseline, PSNR_ratio). | |
| Parameters | |
| ---------- | |
| y : torch.Tensor | |
| Target tensor (HR) of dimensions (batch_size, channels, height, width). | |
| y_hat_base : torch.Tensor | |
| Baseline tensor (single upscaled LR) of dimensions (batch_size, channels, height, width). | |
| metrics : dict of Callable | |
| Dictionary with the metric functions to use. | |
| baseline_metrics : dict of Callable | |
| Dictionary with the baseline metric functions to use. | |
| prefix : str | |
| Prefix for the metrics (train/val/test). | |
| Returns | |
| ------- | |
| dict | |
| Dictionary with the baseline metrics (MSE, MAE, SSIM, TV, PSNR_baseline, PSNR_ratio). | |
| """ | |
| baseline_m = {k: baseline_metrics[k](y_hat_base, y) for k in baseline_metrics} | |
| baseline_psnr = -10.0 * torch.log10(mse_loss(y_hat_base, y)) | |
| baseline_m[f"{prefix}/PSNR_baseline"] = baseline_psnr | |
| baseline_m[f"{prefix}/PSNR_ratio"] = ( | |
| baseline_m[f"{prefix}/PSNR_baseline"] / metrics[f"{prefix}/PSNR"] | |
| ) | |
| baseline_m = detach_dict(baseline_m) | |
| return baseline_m | |
| def clear_shifter_cache(self): | |
| """Clears the cache of the output and baseline shifters.""" | |
| self.shifter.shift_cache = {} | |
| self.baseline_shifter.shift_cache = {} | |
| def training_step(self, batch): | |
| """Training step. | |
| Calls the forward pass, computes the loss and metrics for the training batch. | |
| Logs the reduced metrics. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| Dictionary with the batches for LR/HR/etc. | |
| Returns | |
| ------- | |
| dict | |
| Dictionary with the training loss and metrics. | |
| """ | |
| loss_output = self.loss( | |
| batch, self.train_metrics, self.baseline_train_metrics, prefix="train" | |
| ) | |
| metrics, metrics_reduced = self.unpack_and_reduce_metrics( | |
| loss_output, prefix="train" | |
| ) | |
| self.log_dict(metrics_reduced, on_step=True, on_epoch=True) | |
| return {"loss": metrics_reduced["train/loss"], "metrics": metrics} | |
| def unpack_and_reduce_metrics(self, loss_output, prefix): | |
| """Unpacks the metrics for a split (prefix) and reduces them (mean). | |
| Parameters | |
| ---------- | |
| loss_output : dict | |
| Dictionary with the loss and metrics. | |
| prefix : str | |
| Prefix for the metrics (train/val/test). | |
| Returns | |
| ------- | |
| dict | |
| Dictionary with the reduced (mean) metrics. | |
| """ | |
| metrics = loss_output["metrics"] | |
| metrics_reduced = { | |
| metric_name: metric.mean() for metric_name, metric in metrics.items() | |
| } | |
| metrics_reduced[f"{prefix}/loss"] = loss_output["loss"].mean() | |
| return metrics, metrics_reduced | |
| def validation_step(self, batch, batch_idx): | |
| """Validation step. | |
| Calls the forward pass, computes the loss and metrics for the validation batch. | |
| Logs the reduced metrics. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| Dictionary with the batches for LR/HR/etc. | |
| Returns | |
| ------- | |
| torch.Tensor | |
| Validation loss. | |
| """ | |
| loss_output = self.loss( | |
| batch, self.val_metrics, self.baseline_val_metrics, prefix="val" | |
| ) | |
| _, metrics_reduced = self.unpack_and_reduce_metrics(loss_output, prefix="val") | |
| self.log_dict(metrics_reduced) | |
| return loss_output["loss"] # Instance-wise loss | |
| def validation_epoch_end(self, validation_step_output): | |
| """Validation epoch end. | |
| Concatenates the validation losses and logs them as a histogram to WandB. | |
| Parameters | |
| ---------- | |
| validation_step_output : dict | |
| Dictionary with the validation losses. | |
| """ | |
| instance_loss = torch.cat(validation_step_output).to("cpu") | |
| try: | |
| self.logger.experiment.log( | |
| { | |
| "val/loss_hist": wandb.Histogram(instance_loss), | |
| "global_step": self.global_step, | |
| } | |
| ) | |
| except ValueError as e: | |
| print(e) | |
| def test_step(self, batch, batch_idx): | |
| """Test step.+ | |
| Calls the forward pass, computes the loss and metrics for the test batch. | |
| Logs the reduced metrics. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| Dictionary with the batches for LR/HR/etc. | |
| Returns | |
| ------- | |
| torch.Tensor | |
| Test loss. | |
| """ | |
| loss_output = self.loss( | |
| batch, self.test_metrics, self.baseline_test_metrics, prefix="test" | |
| ) | |
| _, metrics_reduced = self.unpack_and_reduce_metrics(loss_output, prefix="test") | |
| self.log_dict(metrics_reduced) | |
| return loss_output["loss"] # Instance-wise loss | |
| def configure_optimizers(self): | |
| """Configures the optimizers for the model based on the hyperparameters. | |
| Uses the learning rate and weight decay parameters for the Adam optimizer. | |
| Uses the learning rate, weight decay and momentum parameters for the SGD optimizer. | |
| Uses the cos_anneal_T_0 hyperparameter for the cosine annealing scheduler. | |
| See: https://arxiv.org/abs/1608.03983 | |
| Returns | |
| ------- | |
| dict | |
| Dictionary containing the optimizer, scheduler and monitor key (val/loss). | |
| """ | |
| if self.hparams.optimizer == "adam": | |
| optimizer = Adam( | |
| self.parameters(), | |
| lr=self.hparams.learning_rate, | |
| weight_decay=self.hparams.weight_decay, | |
| ) | |
| elif self.hparams.optimizer == "sgd": | |
| optimizer = SGD( | |
| self.parameters(), | |
| lr=self.hparams.learning_rate, | |
| weight_decay=self.hparams.weight_decay, | |
| momentum=self.hparams.momentum, | |
| ) | |
| scheduler = CosineAnnealingWarmRestarts( | |
| optimizer, T_0=self.hparams.cos_anneal_T_0 | |
| ) | |
| return { | |
| "optimizer": optimizer, | |
| "lr_scheduler": scheduler, | |
| "monitor": "val/loss", | |
| } | |
| def add_model_specific_args(parent_parser): | |
| """Add model-specific arguments (and their default values) to the parser. | |
| Parameters | |
| ---------- | |
| parent_parser : argparse.ArgumentParser | |
| argparse.ArgumentParser to add to. | |
| Returns | |
| ------- | |
| ArgumentParser | |
| ArgumentParser with the model-specific arguments added. | |
| """ | |
| parser = ArgumentParser(parents=[parent_parser], add_help=False) | |
| # Model arguments | |
| parser.add_argument("--model", default="srcnn", type=str) | |
| parser.add_argument("--hidden_channels", default=64, type=int) | |
| parser.add_argument("--residual_layers", default=4, type=int) | |
| parser.add_argument("--homography_fc_size", default=128, type=int) | |
| parser.add_argument("--kernel_size", default=3, type=int) | |
| parser.add_argument("--zoom_factor", default=2, type=int) | |
| parser.add_argument("--sr_kernel_size", default=1, type=int) | |
| # Optimizer arguments | |
| parser.add_argument("--optimizer", default="adam", type=str) | |
| parser.add_argument("--learning_rate", default=5e-4, type=float) | |
| parser.add_argument("--learning_rate_decay", default=0.97, type=float) | |
| parser.add_argument("--learning_rate_patience", default=3, type=int) | |
| parser.add_argument("--momentum", type=float, default=0.9) | |
| parser.add_argument("--weight_decay", type=float, default=1e-4) | |
| parser.add_argument("--cos_anneal_T_0", type=int, default=300) | |
| # Data arguments | |
| parser.add_argument("--input_size", nargs="+", default=(400, 400), type=int) | |
| parser.add_argument("--output_size", nargs="+", default=(1000, 1000), type=int) | |
| parser.add_argument("--chip_size", nargs="+", type=int, default=(50, 50)) | |
| parser.add_argument("--chip_stride", nargs="+", type=int, default=None) | |
| parser.add_argument("--revisits", default=8, type=int) | |
| parser.add_argument( | |
| "--use_masks", | |
| action="store_true", | |
| help="Include SCL cloud masks as extra input bands.", | |
| ) | |
| # Shifter arguments | |
| parser.add_argument("--registration_kind", default=None, type=str) | |
| parser.add_argument("--shift_px", default=2, type=int) | |
| parser.add_argument("--shift_mode", default="lanczos", type=str) | |
| parser.add_argument("--shift_step", default=0.5, type=float) | |
| parser.add_argument( | |
| "--use_reference_frame", | |
| action="store_true", | |
| help="Pair each revisit with a reference (median) frame.", | |
| ) | |
| # Loss weights: MSE, MAE, SSIM, TV | |
| parser.add_argument("--w_mse", default=1.0, type=float) | |
| parser.add_argument("--w_mae", default=0.0, type=float) | |
| parser.add_argument("--w_ssim", default=0.0, type=float) | |
| parser.add_argument("--w_tv", default=0.0, type=float) | |
| return parser | |
| class ImagePredictionLogger(pl.Callback): | |
| """Logs the model losses, inputs and outputs to WandB. | |
| Parameters | |
| ---------- | |
| pl : pl.LightningModule | |
| Lightning module containing the model. | |
| """ | |
| def __init__( | |
| self, | |
| train_dataloader, | |
| val_dataloader, | |
| test_dataloader, | |
| log_every_n_epochs=1, | |
| window_size=None, | |
| ): | |
| """Initialize the logger. | |
| Parameters | |
| ---------- | |
| train_dataloader : torch.utils.data.DataLoader | |
| Training dataloader. | |
| val_dataloader : torch.utils.data.DataLoader | |
| Validation dataloader. | |
| test_dataloader : torch.utils.data.DataLoader | |
| Test dataloader. | |
| log_every_n_epochs : int, optional | |
| Log every n epochs. | |
| window_size : tuple of int, optional | |
| Size of the window to use for the patches. | |
| """ | |
| super().__init__() | |
| self.train_dataloader = train_dataloader | |
| self.val_dataloader = val_dataloader | |
| self.test_dataloader = test_dataloader | |
| self.log_every_n_epochs = log_every_n_epochs | |
| self.window_size = window_size | |
| self.baseline_psnr = None | |
| def _on_epoch_end(self, prefix, batch, trainer, pl_module): | |
| """Log the current epoch's metrics and images. | |
| Parameters | |
| ---------- | |
| prefix : str | |
| The split prefix (train, val, test). | |
| batch : dict | |
| A batch of data. | |
| trainer : pytorch_lightning.trainer.Trainer | |
| The trainer. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| """ | |
| lr, hr = batch["lr"], batch["hr"] | |
| batch = self.move_batch_to_device(batch, pl_module.device) | |
| metrics, baseline_metrics = self.fetch_metrics_for_split(prefix, pl_module) | |
| loss_output, instance_psnr, baseline_psnr = self.get_losses_from_metrics( | |
| batch, metrics, baseline_metrics, pl_module, prefix | |
| ) | |
| sr, sr_base = self.get_output_and_baseline(loss_output) | |
| window_size, window_size_hr = self.calculate_window_size(lr, hr, pl_module) | |
| lr, hr, sr, sr_base = self.extract_patches_from_images( | |
| sr, sr_base, lr, hr, window_size, window_size_hr | |
| ) | |
| instance_psnr, baseline_psnr = self.extract_psnr_values_for_patches( | |
| sr, sr_base, lr, instance_psnr, baseline_psnr | |
| ) | |
| lr = self.convert_lr_to_RGB_only_for_logging(lr) | |
| self.log_image_previews_to_wandb( | |
| sr, | |
| sr_base, | |
| lr, | |
| hr, | |
| instance_psnr, | |
| baseline_psnr, | |
| pl_module, | |
| prefix, | |
| trainer, | |
| ) | |
| def move_batch_to_device(self, batch, device): | |
| """Move all tensors from batch to the specified device. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| A batch of data. | |
| device : torch.device | |
| The device to move the tensors to. | |
| Returns | |
| ------- | |
| dict | |
| The batch with all tensors moved to the specified device. | |
| """ | |
| return {key: value.to(device) for key, value in batch.items()} | |
| def fetch_metrics_for_split(self, prefix, pl_module): | |
| """Fetch the metrics for the split. | |
| Parameters | |
| ---------- | |
| prefix : str | |
| The prefix for the metrics. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| Returns | |
| ------- | |
| dict, dict | |
| The metrics and baseline metrics for the current batch. | |
| """ | |
| if prefix == "val": | |
| metrics = pl_module.val_metrics | |
| baseline_metrics = pl_module.baseline_val_metrics | |
| elif prefix == "train": | |
| metrics = pl_module.train_metrics | |
| baseline_metrics = pl_module.baseline_train_metrics | |
| elif prefix == "test": | |
| metrics = pl_module.test_metrics | |
| baseline_metrics = pl_module.baseline_test_metrics | |
| return metrics, baseline_metrics | |
| def get_losses_from_metrics( | |
| self, batch, metrics, baseline_metrics, pl_module, prefix | |
| ): | |
| """Get the losses that will be logged from the metrics. | |
| Parameters | |
| ---------- | |
| batch : dict | |
| The batch. | |
| metrics : dict | |
| The metrics for the current batch. | |
| baseline_metrics : dict | |
| The baseline metrics for the current batch. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| prefix : str | |
| The prefix for the metrics. | |
| Returns | |
| ------- | |
| torch.Tensor, torch.Tensor, torch.Tensor | |
| The loss output, the PSNR values for the SR and Baseline images in the batch. | |
| """ | |
| loss_output = pl_module.loss(batch, metrics, baseline_metrics, prefix=prefix) | |
| instance_psnr = loss_output["metrics"][f"{prefix}/PSNR"].to("cpu") | |
| baseline_psnr = loss_output["baseline_metrics"][f"{prefix}/PSNR_baseline"].to( | |
| "cpu" | |
| ) | |
| return loss_output, instance_psnr, baseline_psnr | |
| def get_output_and_baseline(self, loss_output): | |
| """Get the output and baseline images from the loss output. | |
| Parameters | |
| ---------- | |
| loss_output : dict | |
| The loss output. | |
| Returns | |
| ------- | |
| torch.Tensor, torch.Tensor | |
| The output and baseline images. | |
| """ | |
| sr = loss_output["y_hat"].to("cpu")[:, None] | |
| sr_base = loss_output["y_hat_base"].to("cpu")[:, None] | |
| return sr, sr_base | |
| def calculate_window_size(self, lr, hr, pl_module): | |
| """Calculate the window size for the patches. | |
| Parameters | |
| ---------- | |
| lr : torch.Tensor | |
| The LR image. | |
| hr : torch.Tensor | |
| The HR image. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| Returns | |
| ------- | |
| tuple of int, tuple of int | |
| The window size for lr and hr. | |
| """ | |
| _, _, _, lr_height, lr_width = lr.shape | |
| hr_height, hr_width = hr.shape[-2:] | |
| ratio_hr_lr_height, ratio_hr_lr_width = ( | |
| hr_height / lr_height, | |
| hr_width / lr_width, | |
| ) | |
| window_size = self.window_size or pl_module.hparams.input_size | |
| assert window_size <= tuple(pl_module.hparams.chip_size), "window_size too big" | |
| window_size_hr = ( | |
| round(ratio_hr_lr_height * window_size[0]), | |
| round(ratio_hr_lr_width * window_size[0]), | |
| ) | |
| return window_size, window_size_hr | |
| def extract_patches_from_images( | |
| self, sr, sr_base, lr, hr, window_size, window_size_hr | |
| ): | |
| """Extract the patches from the images. | |
| Parameters | |
| ---------- | |
| sr : torch.Tensor | |
| The SR image. | |
| sr_base : torch.Tensor | |
| The baseline SR image. | |
| hr : torch.Tensor | |
| The HR image. | |
| window_size : tuple of int | |
| The window size. | |
| window_size_hr : tuple of int | |
| The window size for the HR image. | |
| Returns | |
| ------- | |
| torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor | |
| The extracted SR, SR_base, LR, HR images. | |
| """ | |
| lr = self.batch_patch_extractor( | |
| lr, window_size | |
| ) # (batch*patches, tensors/revisits, channels, patch_height, patch_width) | |
| hr = self.batch_patch_extractor(hr, window_size_hr) | |
| sr = self.batch_patch_extractor(sr, window_size_hr) | |
| sr_base = self.batch_patch_extractor(sr_base, window_size_hr) | |
| return lr, hr, sr, sr_base | |
| def batch_patch_extractor(x, window_size): | |
| """Extract patches from a batch of images. | |
| Parameters | |
| ---------- | |
| x : Tensor | |
| A batch of images (batch, tensors/revisits, channels, height, width). | |
| window_size : tuple of int | |
| The size of the patch to extract. | |
| Returns | |
| ------- | |
| Tensor | |
| A batch of patches extracted from the images (batch*patches, tensors, channels, patch_height, patch_width), with patches extracted patches. | |
| """ | |
| batch, tensors, channels, height, width = x.shape | |
| x = extract_tensor_patches( | |
| x.view(batch, tensors * channels, height, width), | |
| window_size, | |
| stride=window_size, | |
| ) | |
| _, patches, _, patch_height, patch_width = x.shape | |
| return x.view( | |
| batch, patches, tensors, channels, patch_height, patch_width | |
| ).view(batch * patches, tensors, channels, patch_height, patch_width) | |
| def extract_psnr_values_for_patches( | |
| self, sr, sr_base, lr, instance_psnr, baseline_psnr | |
| ): | |
| """Extract the PSNR values for the patches. | |
| Parameters | |
| ---------- | |
| sr : torch.Tensor | |
| The SR image. | |
| sr_base : torch.Tensor | |
| The baseline SR image. | |
| lr : torch.Tensor | |
| The LR image. | |
| instance_psnr : torch.Tensor | |
| The instance PSNR values. | |
| baseline_psnr : torch.Tensor | |
| The baseline PSNR values. | |
| Returns | |
| ------- | |
| torch.Tensor, torch.Tensor | |
| The extracted instance and baseline PSNR values. | |
| """ | |
| instance_psnr = ( | |
| instance_psnr[None].expand(sr.shape[0] // lr.shape[0], -1).flatten() | |
| ) | |
| baseline_psnr = ( | |
| baseline_psnr[None].expand(sr_base.shape[0] // lr.shape[0], -1).flatten() | |
| ) | |
| return instance_psnr, baseline_psnr | |
| def convert_lr_to_RGB_only_for_logging(self, lr): | |
| """Convert the LR images to RGB only for logging. | |
| Parameters | |
| ---------- | |
| lr : torch.Tensor | |
| The LR image. | |
| Returns | |
| ------- | |
| torch.Tensor | |
| The LR image in RGB. | |
| """ | |
| if ( | |
| lr.shape[2] > 3 | |
| ): # If all bands are used, get only the RGB bands for WandB image logging | |
| lr = lr[:, :, S2_ALL_12BANDS["true_color_zero_index"]] | |
| return lr | |
| def log_image_previews_to_wandb( | |
| self, | |
| sr, | |
| sr_base, | |
| lr, | |
| hr, | |
| instance_psnr, | |
| baseline_psnr, | |
| pl_module, | |
| prefix, | |
| trainer, | |
| ): | |
| """Log the current epoch's images to WandB. | |
| Parameters | |
| ---------- | |
| sr : torch.Tensor | |
| The SR image. | |
| sr_base : torch.Tensor | |
| The baseline SR image. | |
| lr : torch.Tensor | |
| The LR image. | |
| hr : torch.Tensor | |
| The HR image. | |
| instance_psnr : torch.Tensor | |
| The instance PSNR values. | |
| baseline_psnr : torch.Tensor | |
| The baseline PSNR values. | |
| pl_module : pl.LightningModule | |
| The model. | |
| prefix : str | |
| The split prefix (train, val, test). | |
| trainer : pytorch_lightning.trainer.Trainer | |
| The trainer. | |
| """ | |
| number_of_rows_for_lr_revisits = int(sqrt(pl_module.hparams.revisits).round()) | |
| trainer.logger.experiment.log( | |
| { | |
| f"{prefix}-lr": [ | |
| wandb.Image( | |
| make_grid( | |
| lr_image, | |
| nrow=number_of_rows_for_lr_revisits, | |
| normalize=True, | |
| scale_each=True, | |
| ), | |
| caption=f"lr", | |
| ) | |
| for lr_image in lr | |
| ], | |
| f"{prefix}-hr": [ | |
| wandb.Image( | |
| make_grid(hr_image, nrow=1, normalize=True, scale_each=True), | |
| caption=f"hr", | |
| ) | |
| for hr_image in hr | |
| ], | |
| f"{prefix}-baseline_upscaled_bicubic": [ | |
| wandb.Image( | |
| make_grid( | |
| baseline_image, nrow=1, normalize=True, scale_each=True | |
| ), | |
| caption=f"baseline:{str(baseline_psnr)}", | |
| ) | |
| for baseline_image, baseline_psnr in zip(sr_base, baseline_psnr) | |
| ], | |
| f"{prefix}-sr": [ | |
| wandb.Image( | |
| make_grid(image, nrow=1, normalize=True, scale_each=True), | |
| caption=f"sr:{str(sr_psnr)}", | |
| ) | |
| for image, sr_psnr in zip(sr, instance_psnr) | |
| ], | |
| } | |
| ) | |
| def on_validation_epoch_end(self, trainer, pl_module): | |
| """Called at the end of the validation epoch. | |
| If the current epoch should be logged (i.e. the current epoch is a multiple of the log interval), | |
| then _on_epoch_end is called for the next batch of the validation set. | |
| Parameters | |
| ---------- | |
| trainer : pytorch_lightning.trainer.Trainer | |
| The trainer. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| """ | |
| if trainer.current_epoch % self.log_every_n_epochs == 0: | |
| batch = next(iter(self.val_dataloader)) | |
| self._on_epoch_end("val", batch, trainer, pl_module) | |
| def on_test_epoch_end(self, trainer, pl_module): | |
| """Called at the end of the test epoch. | |
| Parameters | |
| ---------- | |
| trainer : pytorch_lightning.trainer.Trainer | |
| The trainer. | |
| pl_module : pytorch_lightning.LightningModule | |
| The model. | |
| """ | |
| for batch in iter(self.test_dataloader): | |
| self._on_epoch_end("test", batch, trainer, pl_module) | |