microScan / utils /general.py
crazyscientist1's picture
initial commit
d70f24c
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