import cv2 import numpy as np import torch import matplotlib.pyplot as plt import os plt.style.use('ggplot') # this class keeps track of the training and validation loss values... # ... and helps to get the average for each epoch as well class Averager: def __init__(self): self.current_total = 0.0 self.iterations = 0.0 def send(self, value): self.current_total += value self.iterations += 1 @property def value(self): if self.iterations == 0: return 0 else: return 1.0 * self.current_total / self.iterations def reset(self): self.current_total = 0.0 self.iterations = 0.0 class SaveBestModel: """ Class to save the best model while training. If the current epoch's validation mAP @0.5:0.95 IoU higher than the previous highest, then save the model state. """ def __init__( self, best_valid_map=float(0) ): self.best_valid_map = best_valid_map def __call__( self, model, current_valid_map, epoch, OUT_DIR, config, model_name ): if current_valid_map > self.best_valid_map: self.best_valid_map = current_valid_map print(f"\nBEST VALIDATION mAP: {self.best_valid_map}") print(f"\nSAVING BEST MODEL FOR EPOCH: {epoch+1}\n") torch.save({ 'epoch': epoch+1, 'model_state_dict': model.state_dict(), 'config': config, 'model_name': model_name }, f"{OUT_DIR}/best_model.pth") def show_tranformed_image(train_loader, device, classes, colors): """ This function shows the transformed images from the `train_loader`. Helps to check whether the tranformed images along with the corresponding labels are correct or not. """ if len(train_loader) > 0: for i in range(2): images, targets = next(iter(train_loader)) images = list(image.to(device) for image in images) targets = [{k: v.to(device) for k, v in t.items()} for t in targets] boxes = targets[i]['boxes'].cpu().numpy().astype(np.int32) labels = targets[i]['labels'].cpu().numpy().astype(np.int32) # Get all the predicited class names. pred_classes = [classes[i] for i in targets[i]['labels'].cpu().numpy()] sample = images[i].permute(1, 2, 0).cpu().numpy() sample = cv2.cvtColor(sample, cv2.COLOR_RGB2BGR) for box_num, box in enumerate(boxes): class_name = pred_classes[box_num] color = colors[classes.index(class_name)] cv2.rectangle(sample, (box[0], box[1]), (box[2], box[3]), color, 2, cv2.LINE_AA) cv2.putText(sample, classes[labels[box_num]], (box[0], box[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2, cv2.LINE_AA) cv2.imshow('Transformed image', sample) cv2.waitKey(0) cv2.destroyAllWindows() def save_loss_plot( OUT_DIR, train_loss_list, x_label='iterations', y_label='train loss', save_name='train_loss_iter' ): """ Function to save both train loss graph. :param OUT_DIR: Path to save the graphs. :param train_loss_list: List containing the training loss values. """ figure_1 = plt.figure(figsize=(10, 7), num=1, clear=True) train_ax = figure_1.add_subplot() train_ax.plot(train_loss_list, color='tab:blue') train_ax.set_xlabel(x_label) train_ax.set_ylabel(y_label) figure_1.savefig(f"{OUT_DIR}/{save_name}.png") print('SAVING PLOTS COMPLETE...') # plt.close('all') def save_mAP(OUT_DIR, map_05, map): """ Saves the mAP@0.5 and mAP@0.5:0.95 per epoch. :param OUT_DIR: Path to save the graphs. :param map_05: List containing mAP values at 0.5 IoU. :param map: List containing mAP values at 0.5:0.95 IoU. """ figure = plt.figure(figsize=(10, 7), num=1, clear=True) ax = figure.add_subplot() ax.plot( map_05, color='tab:orange', linestyle='-', label='mAP@0.5' ) ax.plot( map, color='tab:red', linestyle='-', label='mAP@0.5:0.95' ) ax.set_xlabel('Epochs') ax.set_ylabel('mAP') ax.legend() figure.savefig(f"{OUT_DIR}/map.png") # plt.close('all') def visualize_mosaic_images(boxes, labels, image_resized, classes): print(boxes) print(labels) image_resized = cv2.cvtColor(image_resized, cv2.COLOR_RGB2BGR) for j, box in enumerate(boxes): color = (0, 255, 0) classn = labels[j] cv2.rectangle(image_resized, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 2) cv2.putText(image_resized, classes[classn], (int(box[0]), int(box[1]-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2, lineType=cv2.LINE_AA) cv2.imshow('Mosaic', image_resized) cv2.waitKey(0) def save_model( epoch, model, optimizer, train_loss_list, train_loss_list_epoch, val_map, val_map_05, OUT_DIR, config, model_name ): """ Function to save the trained model till current epoch, or whenever called. Saves many other dictionaries and parameters as well helpful to resume training. May be larger in size. :param epoch: The epoch number. :param model: The neural network model. :param optimizer: The optimizer. :param optimizer: The train loss history. :param train_loss_list_epoch: List containing loss for each epoch. :param val_map: mAP for IoU 0.5:0.95. :param val_map_05: mAP for IoU 0.5. :param OUT_DIR: Output directory to save the model. """ torch.save({ 'epoch': epoch+1, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'train_loss_list': train_loss_list, 'train_loss_list_epoch': train_loss_list_epoch, 'val_map': val_map, 'val_map_05': val_map_05, 'config': config, 'model_name': model_name }, f"{OUT_DIR}/last_model.pth") def save_model_state(model, OUT_DIR, config, model_name): """ Saves the model state dictionary only. Has a smaller size compared to the the saved model with all other parameters and dictionaries. Preferable for inference and sharing. :param model: The neural network model. :param OUT_DIR: Output directory to save the model. """ torch.save({ 'model_state_dict': model.state_dict(), 'config': config, 'model_name': model_name }, f"{OUT_DIR}/last_model_state.pth") def denormalize(x, mean=None, std=None): # Shape of x here should be [B, 3, H, W]. for t, m, s in zip(x, mean, std): t.mul_(s).add_(m) # Returns tensor of shape [B, 3, H, W]. return torch.clamp(x, 0, 1) def save_validation_results(images, detections, counter, out_dir, classes, colors): """ Function to save validation results. :param images: All the images from the current batch. :param detections: All the detection results. :param counter: Step counter for saving with unique ID. """ IMG_MEAN = [0.485, 0.456, 0.406] IMG_STD = [0.229, 0.224, 0.225] image_list = [] # List to store predicted images to return. for i, detection in enumerate(detections): image_c = images[i].clone() # image_c = denormalize(image_c, IMG_MEAN, IMG_STD) image_c = image_c.detach().cpu().numpy().astype(np.float32) image = np.transpose(image_c, (1, 2, 0)) image = np.ascontiguousarray(image, dtype=np.float32) scores = detection['scores'].cpu().numpy() labels = detection['labels'] bboxes = detection['boxes'].detach().cpu().numpy() boxes = bboxes[scores >= 0.5].astype(np.int32) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # Get all the predicited class names. pred_classes = [classes[i] for i in labels.cpu().numpy()] for j, box in enumerate(boxes): class_name = pred_classes[j] color = colors[classes.index(class_name)] cv2.rectangle( image, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 2, lineType=cv2.LINE_AA ) cv2.putText(image, class_name, (int(box[0]), int(box[1]-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2, lineType=cv2.LINE_AA) cv2.imwrite(f"{out_dir}/image_{i}_{counter}.jpg", image*255.) image_list.append(image[:, :, ::-1]) return image_list def set_infer_dir(): """ This functions counts the number of inference directories already present and creates a new one in `outputs/inference/`. And returns the directory path. """ if not os.path.exists('outputs/inference'): os.makedirs('outputs/inference') num_infer_dirs_present = len(os.listdir('outputs/inference/')) next_dir_num = num_infer_dirs_present + 1 new_dir_name = f"outputs/inference/res_{next_dir_num}" os.makedirs(new_dir_name, exist_ok=True) return new_dir_name def set_training_dir(dir_name=None): """ This functions counts the number of training directories already present and creates a new one in `outputs/training/`. And returns the directory path. """ if not os.path.exists('outputs/training'): os.makedirs('outputs/training') if dir_name: new_dir_name = f"outputs/training/{dir_name}" os.makedirs(new_dir_name, exist_ok=True) return new_dir_name else: num_train_dirs_present = len(os.listdir('outputs/training/')) next_dir_num = num_train_dirs_present + 1 new_dir_name = f"outputs/training/res_{next_dir_num}" os.makedirs(new_dir_name, exist_ok=True) return new_dir_name