Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ultralytics Code Refactor https://ultralytics.com/actions #12

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<br>
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320">
<a href="https://ultralytics.com" target="_blank"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>

# Introduction :wave:

Expand Down Expand Up @@ -104,7 +104,7 @@ For bug reports, feature requests, and contributions, head to [GitHub Issues](ht
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
<a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
<a href="https://www.instagram.com/ultralytics/"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-instagram.png" width="3%" alt="Ultralytics Instagram"></a>
<a href="https://ultralytics.com/bilibili"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-bilibili.png" width="3%" alt="Ultralytics Instagram"></a>
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
<a href="https://ultralytics.com/discord"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-discord.png" width="3%" alt="Ultralytics Discord"></a>
</div>
3 changes: 3 additions & 0 deletions detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@


def detect(opt):
"""Perform object detection on images using the specified model and configurations."""
if opt.plot_flag:
os.system(f"rm -rf {opt.output_folder}_img")
os.makedirs(f"{opt.output_folder}_img", exist_ok=True)
Expand Down Expand Up @@ -215,6 +216,7 @@ def detect(opt):

class ConvNetb(nn.Module):
def __init__(self, num_classes=60):
"""Initializes the ConvNetb class with a specified number of output classes."""
super(ConvNetb, self).__init__()
n = 64 # initial convolution size
self.layer1 = nn.Sequential(
Expand Down Expand Up @@ -247,6 +249,7 @@ def __init__(self, num_classes=60):
self.fully_convolutional = nn.Conv2d(n * 16, 60, kernel_size=4, stride=1, padding=0, bias=True)

def forward(self, x): # 500 x 1 x 64 x 64
"""Performs the forward pass by sequentially applying model layers to input tensor x."""
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
Expand Down
9 changes: 9 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ class EmptyLayer(nn.Module):
"""Placeholder for 'route' and 'shortcut' layers."""

def __init__(self):
"""Initializes an EmptyLayer module."""
super(EmptyLayer, self).__init__()


class YOLOLayer(nn.Module):
# YOLO Layer 0

def __init__(self, anchors, nC, img_dim, anchor_idxs):
"""Initializes a YOLOLayer module with anchors, class count, image dimensions, and anchor indices."""
super(YOLOLayer, self).__init__()

anchors = list(anchors)
Expand Down Expand Up @@ -107,6 +109,9 @@ def __init__(self, anchors, nC, img_dim, anchor_idxs):

# @profile
def forward(self, p, targets=None, requestPrecision=False, weight=None, epoch=None):
"""Performs a forward pass in the model with given input tensors, target data, precision flag, weights, and
epoch info.
"""
FT = torch.cuda.FloatTensor if p.is_cuda else torch.FloatTensor
torch.device("cuda:0" if p.is_cuda else "cpu")
# weight = xview_class_weights(range(60)).to(device)
Expand Down Expand Up @@ -225,6 +230,7 @@ class Darknet(nn.Module):
"""YOLOv3 object detection model."""

def __init__(self, config_path, img_size=416):
"""Initialize the YOLOv3 model with configuration path and optional image size (default 416)."""
super(Darknet, self).__init__()
self.module_defs = parse_model_config(config_path)
self.module_defs[0]["height"] = img_size
Expand All @@ -233,6 +239,9 @@ def __init__(self, config_path, img_size=416):
self.loss_names = ["loss", "x", "y", "w", "h", "conf", "cls", "nGT", "TP", "FP", "FPe", "FN", "TC"]

def forward(self, x, targets=None, requestPrecision=False, weight=None, epoch=None):
"""Perform a forward pass through the network, optionally computing loss and returning outputs, targets, and
other metrics.
"""
is_training = targets is not None
output = []
self.losses = defaultdict(float)
Expand Down
15 changes: 15 additions & 0 deletions utils/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

class ImageFolder: # for eval-only
def __init__(self, path, batch_size=1, img_size=416):
"""Initialize ImageFolder with a path, batch size, and image size, setting up file paths for image data
loading.
"""
if os.path.isdir(path):
self.files = sorted(glob.glob(f"{path}/*.*"))
elif os.path.isfile(path):
Expand All @@ -30,10 +33,12 @@ def __init__(self, path, batch_size=1, img_size=416):
self.rgb_std = np.array([29.99, 24.498, 22.046], dtype=np.float32).reshape((3, 1, 1))

def __iter__(self):
"""Initialize and return the iterable object with a reset count."""
self.count = -1
return self

def __next__(self):
"""Advance to the next item in the iterable sequence, updating the counter and returning the next image path."""
self.count += 1
if self.count == self.nB:
raise StopIteration
Expand All @@ -51,11 +56,13 @@ def __next__(self):
return [img_path], img

def __len__(self):
"""Return the number of batches 'nB'."""
return self.nB # number of batches


class ListDataset: # for training
def __init__(self, path, batch_size=1, img_size=608, targets_path=""):
"""Initialize ListDataset with image path, batch size, image size, and targets path for training."""
self.path = path
self.files = sorted(glob.glob(f"{path}/*.bmp"))
self.nF = len(self.files) # number of image files
Expand Down Expand Up @@ -88,6 +95,7 @@ def __init__(self, path, batch_size=1, img_size=608, targets_path=""):
# self.rgb_std = np.array([69.095, 66.369, 64.236], dtype=np.float32).reshape((1, 3, 1, 1))

def __iter__(self):
"""Initialize shuffle vector and reset count for iterating over dataset."""
self.count = -1
# self.shuffled_vector = np.random.permutation(self.nF) # shuffled vector
self.shuffled_vector = np.random.choice(
Expand All @@ -97,6 +105,9 @@ def __iter__(self):

# @profile
def __next__(self):
"""Return the next item in the sequence, incrementing the count and stopping iteration if the count equals
nB.
"""
self.count += 1
if self.count == self.nB:
raise StopIteration
Expand Down Expand Up @@ -268,10 +279,12 @@ def __next__(self):
return torch.from_numpy(img_all), labels_all

def __len__(self):
"""Returns the total number of batches (self.nB)."""
return self.nB # number of batches


def xview_classes2indices(classes): # remap xview classes 11-94 to 0-61
"""Remaps xView classes (11-94) to indices (0-61) for the given list of classes."""
indices = [
-1,
-1,
Expand Down Expand Up @@ -373,6 +386,7 @@ def xview_classes2indices(classes): # remap xview classes 11-94 to 0-61


def resize_square(img, height=416, color=(0, 0, 0)): # resizes a rectangular image to a padded square
"""Resizes a rectangular image to a padded square with a specified height and color."""
shape = img.shape[:2] # shape = [height, width]
ratio = float(height) / max(shape)
new_shape = [round(shape[0] * ratio), round(shape[1] * ratio)]
Expand Down Expand Up @@ -455,6 +469,7 @@ def random_affine(


def convert_tif2bmp(p="/Users/glennjocher/Downloads/DATA/xview/val_images_bmp"):
"""Convert all .tif files to .bmp format in the specified directory path."""
import glob

import cv2
Expand Down
16 changes: 15 additions & 1 deletion utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def load_classes(path):


def modelinfo(model):
"""Displays model layer details including name, gradient status, number of parameters, shape, mean, and standard
deviation.
"""
nparams = sum(x.numel() for x in model.parameters())
ngradients = sum(x.numel() for x in model.parameters() if x.requires_grad)
print("\n%4s %70s %9s %12s %20s %12s %12s" % ("", "name", "gradient", "parameters", "shape", "mu", "sigma"))
Expand All @@ -29,12 +32,14 @@ def modelinfo(model):


def xview_class2name(classes):
"""Converts an xView class index to its corresponding name by reading from 'data/xview.names' file."""
with open("data/xview.names", "r") as f:
x = f.readlines()
return x[classes].replace("\n", "")


def xview_indices2classes(indices): # remap xview classes 11-94 to 0-61
"""Remaps xView class indices from 11-94 to 0-61."""
class_list = [
11,
12,
Expand Down Expand Up @@ -101,6 +106,7 @@ def xview_indices2classes(indices): # remap xview classes 11-94 to 0-61


def xview_class_weights(indices): # weights of each class in the training set, normalized to mu = 1
"""Returns normalized class weights for given indices in the xView dataset using torch.FloatTensor."""
weights = 1 / torch.FloatTensor(
[
74,
Expand Down Expand Up @@ -170,6 +176,7 @@ def xview_class_weights(indices): # weights of each class in the training set,


def xview_feedback_weights(indices):
"""Calculate normalization weights for given xView feedback indices using pre-defined values."""
weights = 1 / torch.FloatTensor(
[
0,
Expand Down Expand Up @@ -240,6 +247,7 @@ def xview_feedback_weights(indices):


def plot_one_box(x, im, color=None, label=None, line_thickness=None):
"""Draws a bounding box with optional label on an image using specified coordinates, color, and line thickness."""
tl = line_thickness or round(0.003 * max(im.shape[:2]))
color = color or [random.randint(0, 255) for _ in range(3)]
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
Expand All @@ -253,6 +261,7 @@ def plot_one_box(x, im, color=None, label=None, line_thickness=None):


def weights_init_normal(m):
"""Initialize the weights of convolutional and batch normalization layers with a normal distribution."""
classname = m.__class__.__name__
if classname.find("Conv") != -1:
torch.nn.init.normal_(m.weight.data, 0.0, 0.03)
Expand All @@ -262,6 +271,7 @@ def weights_init_normal(m):


def xyxy2xywh(box):
"""Convert bounding box format from (x1, y1, x2, y2) to (x_center, y_center, width, height)."""
xywh = np.zeros(box.shape)
xywh[:, 0] = (box[:, 0] + box[:, 2]) / 2
xywh[:, 1] = (box[:, 1] + box[:, 3]) / 2
Expand Down Expand Up @@ -574,7 +584,9 @@ def non_max_suppression(prediction, conf_thres=0.5, nms_thres=0.4, mat=None, img

# @profile
def secondary_class_detection(x, y, w, h, img, model, device):
# 1. create 48-pixel squares from each chip
"""Detect secondary classes from input image chips using a specified model and device, returning class
predictions.
"""
img = np.ascontiguousarray(img.transpose([1, 2, 0])) # torch to cv2
height = 64

Expand Down Expand Up @@ -626,6 +638,7 @@ def secondary_class_detection(x, y, w, h, img, model, device):


def createChips():
"""Generates image chips from unique images and saves them with labels into an HDF5 file."""
from sys import platform

import cv2
Expand Down Expand Up @@ -684,6 +697,7 @@ def createChips():


def plotResults():
"""Plot results from multiple text files for various metrics using matplotlib."""
import matplotlib.pyplot as plt
import numpy as np

Expand Down