Move code into different folder for packaging
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import torch
|
||||
import cv2
|
||||
import onnxruntime
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import albumentations as A
|
||||
|
||||
from torchvision import transforms, ops
|
||||
from albumentations.pytorch import ToTensorV2
|
||||
|
||||
from utils.conversions import scale_bboxes
|
||||
from utils.manipulations import get_cutout
|
||||
|
||||
def detect(img_path: str, yolo_path: str, resnet_path: str):
|
||||
"""Load an image, detect individual plants and label them as
|
||||
healthy or wilted.
|
||||
|
||||
:param str img_path: path to image
|
||||
:param str yolo_path: path to yolo weights
|
||||
:param str resnet_path: path to resnet weights
|
||||
:returns: tuple of recent image and dict of bounding boxes and
|
||||
their predictions
|
||||
|
||||
"""
|
||||
img = cv2.imread(img_path)
|
||||
|
||||
# Get bounding boxes from object detection model
|
||||
box_coords = get_boxes(yolo_path, img.copy())
|
||||
|
||||
box_coords.sort_values(by=['xmin'], ignore_index=True, inplace=True)
|
||||
|
||||
predictions = []
|
||||
for _, row in box_coords.iterrows():
|
||||
xmin, xmax = int(row['xmin']), int(row['xmax'])
|
||||
ymin, ymax = int(row['ymin']), int(row['ymax'])
|
||||
|
||||
# Get tensor of ROI in BGR
|
||||
cropped_image = get_cutout(img.copy(), xmin, xmax, ymin, ymax)
|
||||
|
||||
# Classify ROI in RGB
|
||||
predictions.append(classify(resnet_path, cropped_image[..., ::-1]))
|
||||
|
||||
# Gather top class and confidence values
|
||||
cls = []
|
||||
cls_conf = []
|
||||
for pred in predictions:
|
||||
ans, index = torch.topk(pred, 1)
|
||||
cls.append(index.int().item())
|
||||
cls_conf.append(round(ans.double().item(), 6))
|
||||
|
||||
# Add predicted classes and confidence values to pandas dataframe
|
||||
box_coords['cls'] = cls
|
||||
box_coords['cls_conf'] = cls_conf
|
||||
|
||||
return box_coords
|
||||
|
||||
|
||||
def classify(resnet_path, img):
|
||||
"""Classify img with object classification model.
|
||||
|
||||
:param model: object classification model
|
||||
:param img: opencv2 image object in RGB
|
||||
:returns: tensor of class predictions
|
||||
"""
|
||||
|
||||
# Transform image for ResNet
|
||||
transform = transforms.Compose([
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406],
|
||||
std=[0.229, 0.224, 0.225]),
|
||||
transforms.Resize((224, 224))
|
||||
])
|
||||
|
||||
img = transform(img.copy())
|
||||
batch = img.unsqueeze(0)
|
||||
|
||||
# Do inference
|
||||
session = onnxruntime.InferenceSession(resnet_path)
|
||||
outname = [i.name for i in session.get_outputs()]
|
||||
inname = [i.name for i in session.get_inputs()]
|
||||
inp = {inname[0]: batch.numpy()}
|
||||
out = torch.tensor(np.array(session.run(outname, inp)))[0]
|
||||
|
||||
# Apply softmax to get percentage confidence of classes
|
||||
out = torch.nn.functional.softmax(out, dim=1)[0] * 100
|
||||
return out
|
||||
|
||||
|
||||
def apply_nms(predictions,
|
||||
confidence_threshold: float = 0.3,
|
||||
nms_threshold: float = 0.65):
|
||||
"""Apply Non Maximum Suppression to a list of bboxes.
|
||||
|
||||
:param predictions List[Tensor[N, 7]]: predicted bboxes
|
||||
:param confidence_threshold float: discard all bboxes with lower
|
||||
confidence
|
||||
:param nms_threshold float: discard all overlapping bboxes with
|
||||
higher IoU
|
||||
:returns List[Tensor[N, 7]]: filtered bboxes
|
||||
"""
|
||||
preds_nms = []
|
||||
for pred in predictions:
|
||||
pred = pred[pred[:, 6] > confidence_threshold]
|
||||
|
||||
nms_idx = ops.batched_nms(
|
||||
boxes=pred[:, 1:5],
|
||||
scores=pred[:, 6],
|
||||
idxs=pred[:, 5],
|
||||
iou_threshold=nms_threshold,
|
||||
)
|
||||
preds_nms.append(pred[nms_idx])
|
||||
|
||||
return preds_nms
|
||||
|
||||
|
||||
def get_boxes(yolo_path, image):
|
||||
"""Run object detection model on an image and get the bounding box
|
||||
coordinates of all matches.
|
||||
|
||||
:param model: path to onnx object detection model (YOLO)
|
||||
:param img: opencv2 image object
|
||||
:returns: pandas dataframe of matches
|
||||
"""
|
||||
# Convert from BGR to RGB
|
||||
img = image[..., ::-1].copy()
|
||||
|
||||
resized_hw = (640, 640)
|
||||
original_hw = (image.shape[0], image.shape[1])
|
||||
|
||||
transform = [
|
||||
A.LongestMaxSize(max(resized_hw)),
|
||||
A.PadIfNeeded(
|
||||
resized_hw[0],
|
||||
resized_hw[1],
|
||||
border_mode=0,
|
||||
value=(114, 114, 114),
|
||||
),
|
||||
A.ToFloat(max_value=255),
|
||||
ToTensorV2(transpose_mask=True),
|
||||
]
|
||||
|
||||
# Pad (letterbox) and transform image to correct dims
|
||||
transform = A.Compose(transform)
|
||||
img = transform(image=img)
|
||||
|
||||
# Add batch dimension
|
||||
img['image'] = img['image'].unsqueeze(0)
|
||||
|
||||
# Do inference
|
||||
session = onnxruntime.InferenceSession(yolo_path)
|
||||
outname = [i.name for i in session.get_outputs()]
|
||||
inname = [i.name for i in session.get_inputs()]
|
||||
inp = {inname[0]: img['image'].numpy()}
|
||||
out = torch.tensor(np.array(session.run(outname, inp)))[0]
|
||||
|
||||
# Apply NMS to results
|
||||
preds_nms = apply_nms([out])[0]
|
||||
|
||||
# Convert boxes from resized img to original img
|
||||
xyxy_boxes = preds_nms[:, [1, 2, 3, 4]] # xmin, ymin, xmax, ymax
|
||||
bboxes = scale_bboxes(xyxy_boxes, resized_hw, original_hw).int().numpy()
|
||||
|
||||
# Construct DataFrame with bboxes and their confidence
|
||||
box_coords = pd.DataFrame(np.c_[bboxes, preds_nms[:, 6]])
|
||||
box_coords.columns = ['xmin', 'ymin', 'xmax', 'ymax', 'box_conf']
|
||||
|
||||
return box_coords
|
||||
@@ -0,0 +1,500 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "3fe8177c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import fiftyone as fo\n",
|
||||
"from PIL import Image\n",
|
||||
"from detection import detect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "32f0f8ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"name = \"dataset\"\n",
|
||||
"dataset_dir = \"dataset\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "6343aa55",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" 100% |█████████████████| 640/640 [716.7ms elapsed, 0s remaining, 894.6 samples/s] \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# The splits to load\n",
|
||||
"splits = [\"val\"]\n",
|
||||
"\n",
|
||||
"# Load the dataset, using tags to mark the samples in each split\n",
|
||||
"dataset = fo.Dataset(name)\n",
|
||||
"for split in splits:\n",
|
||||
" dataset.add_dir(\n",
|
||||
" dataset_dir=dataset_dir,\n",
|
||||
" dataset_type=fo.types.YOLOv5Dataset,\n",
|
||||
" split=split,\n",
|
||||
" tags=split,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"classes = dataset.default_classes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "29827e3f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" 100% |█████████████████| 640/640 [8.8m elapsed, 0s remaining, 1.5 samples/s] \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Do detections with model and save bounding boxes\n",
|
||||
"with fo.ProgressBar() as pb:\n",
|
||||
" for sample in pb(dataset.view()):\n",
|
||||
" image = Image.open(sample.filepath)\n",
|
||||
" w, h = image.size\n",
|
||||
" pred = detect(sample.filepath, '../weights/yolo.onnx', '../weights/resnet.onnx')\n",
|
||||
"\n",
|
||||
" detections = []\n",
|
||||
" for _, row in pred.iterrows():\n",
|
||||
" xmin, xmax = int(row['xmin']), int(row['xmax'])\n",
|
||||
" ymin, ymax = int(row['ymin']), int(row['ymax'])\n",
|
||||
" rel_box = [\n",
|
||||
" xmin / w, ymin / h, (xmax - xmin) / w, (ymax - ymin) / h\n",
|
||||
" ]\n",
|
||||
" detections.append(\n",
|
||||
" fo.Detection(label=classes[int(row['cls'])],\n",
|
||||
" bounding_box=rel_box,\n",
|
||||
" confidence=int(row['cls_conf'])))\n",
|
||||
"\n",
|
||||
" sample[\"predictions\"] = fo.Detections(detections=detections)\n",
|
||||
" sample.save()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "06e1b4c0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dataset.persistent = True"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "8ad67806",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Evaluating detections...\n",
|
||||
" 100% |█████████████████| 640/640 [2.0s elapsed, 0s remaining, 319.5 samples/s] \n",
|
||||
"Performing IoU sweep...\n",
|
||||
" 100% |█████████████████| 640/640 [2.1s elapsed, 0s remaining, 297.3 samples/s] \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"results = dataset.view().evaluate_detections(\n",
|
||||
" \"predictions\",\n",
|
||||
" gt_field=\"ground_truth\",\n",
|
||||
" eval_key=\"eval\",\n",
|
||||
" compute_mAP=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "b180420b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" precision recall f1-score support\n",
|
||||
"\n",
|
||||
" Healthy 0.82 0.74 0.78 662\n",
|
||||
" Stressed 0.71 0.78 0.74 488\n",
|
||||
"\n",
|
||||
" micro avg 0.77 0.76 0.76 1150\n",
|
||||
" macro avg 0.77 0.76 0.76 1150\n",
|
||||
"weighted avg 0.77 0.76 0.77 1150\n",
|
||||
"\n",
|
||||
"0.6225828327927432\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": []
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": []
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"application/vnd.jupyter.widget-view+json": {
|
||||
"model_id": "0e0a5d23d3f148419bd62ecf4a07dce9",
|
||||
"version_major": 2,
|
||||
"version_minor": 0
|
||||
},
|
||||
"text/plain": [
|
||||
"FigureWidget({\n",
|
||||
" 'data': [{'mode': 'markers',\n",
|
||||
" 'opacity': 0.1,\n",
|
||||
" 'type': 'scatter',\n",
|
||||
" 'uid': 'c432eebe-8bf5-4b15-834c-abdc3af7c18b',\n",
|
||||
" 'x': array([0, 1, 2, 0, 1, 2, 0, 1, 2]),\n",
|
||||
" 'y': array([0, 0, 0, 1, 1, 1, 2, 2, 2])},\n",
|
||||
" {'colorscale': [[0.0, 'rgb(255,245,235)'], [0.125,\n",
|
||||
" 'rgb(254,230,206)'], [0.25, 'rgb(253,208,162)'],\n",
|
||||
" [0.375, 'rgb(253,174,107)'], [0.5, 'rgb(253,141,60)'],\n",
|
||||
" [0.625, 'rgb(241,105,19)'], [0.75, 'rgb(217,72,1)'],\n",
|
||||
" [0.875, 'rgb(166,54,3)'], [1.0, 'rgb(127,39,4)']],\n",
|
||||
" 'hoverinfo': 'skip',\n",
|
||||
" 'showscale': False,\n",
|
||||
" 'type': 'heatmap',\n",
|
||||
" 'uid': '01498ed3-dbe3-4c9e-baf9-49d814522d20',\n",
|
||||
" 'z': array([[105, 158, 0],\n",
|
||||
" [ 0, 382, 106],\n",
|
||||
" [493, 0, 169]]),\n",
|
||||
" 'zmax': 493,\n",
|
||||
" 'zmin': 0},\n",
|
||||
" {'colorbar': {'len': 1, 'lenmode': 'fraction'},\n",
|
||||
" 'colorscale': [[0.0, 'rgb(255,245,235)'], [0.125,\n",
|
||||
" 'rgb(254,230,206)'], [0.25, 'rgb(253,208,162)'],\n",
|
||||
" [0.375, 'rgb(253,174,107)'], [0.5, 'rgb(253,141,60)'],\n",
|
||||
" [0.625, 'rgb(241,105,19)'], [0.75, 'rgb(217,72,1)'],\n",
|
||||
" [0.875, 'rgb(166,54,3)'], [1.0, 'rgb(127,39,4)']],\n",
|
||||
" 'hovertemplate': '<b>count: %{z}</b><br>truth: %{y}<br>predicted: %{x}<extra></extra>',\n",
|
||||
" 'opacity': 0.25,\n",
|
||||
" 'type': 'heatmap',\n",
|
||||
" 'uid': '10618ef9-35c5-451a-b915-32b70d7bfb5e',\n",
|
||||
" 'z': array([[105, 158, 0],\n",
|
||||
" [ 0, 382, 106],\n",
|
||||
" [493, 0, 169]]),\n",
|
||||
" 'zmax': 493,\n",
|
||||
" 'zmin': 0}],\n",
|
||||
" 'layout': {'clickmode': 'event',\n",
|
||||
" 'margin': {'b': 0, 'l': 0, 'r': 0, 't': 30},\n",
|
||||
" 'template': '...',\n",
|
||||
" 'title': {},\n",
|
||||
" 'xaxis': {'constrain': 'domain',\n",
|
||||
" 'range': [-0.5, 2.5],\n",
|
||||
" 'tickmode': 'array',\n",
|
||||
" 'ticktext': [Healthy, Stressed, (none)],\n",
|
||||
" 'tickvals': array([0, 1, 2])},\n",
|
||||
" 'yaxis': {'constrain': 'domain',\n",
|
||||
" 'range': [-0.5, 2.5],\n",
|
||||
" 'scaleanchor': 'x',\n",
|
||||
" 'scaleratio': 1,\n",
|
||||
" 'tickmode': 'array',\n",
|
||||
" 'ticktext': array(['(none)', 'Stressed', 'Healthy'], dtype=object),\n",
|
||||
" 'tickvals': array([0, 1, 2])}}\n",
|
||||
"})"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": []
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": []
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"application/vnd.jupyter.widget-view+json": {
|
||||
"model_id": "915261c4504943ec9b07813fbe75b8c9",
|
||||
"version_major": 2,
|
||||
"version_minor": 0
|
||||
},
|
||||
"text/plain": [
|
||||
"FigureWidget({\n",
|
||||
" 'data': [{'customdata': array([99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 98.8,\n",
|
||||
" 98. , 98. , 97.7, 97. , 97. , 97. , 96.9, 96.4, 96. , 95.7, 94.8, 94.3,\n",
|
||||
" 93.8, 92.8, 92.2, 91.5, 90.9, 90.7, 89.3, 88.3, 87.8, 87.1, 86.8, 86.3,\n",
|
||||
" 85.8, 85.1, 84.6, 84.1, 83.2, 82. , 81.7, 80.8, 79.7, 79.4, 78.6, 77.8,\n",
|
||||
" 77.4, 76.3, 75.5, 74.6, 73.6, 72.3, 71. , 69.9, 68.6, 67.5, 66.4, 65.4,\n",
|
||||
" 64.3, 63.2, 62.2, 61. , 60.2, 59.1, 58.1, 56.9, 50.4, 49. , 47.3, 46.4,\n",
|
||||
" 40.8, 25.3, 10. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ]),\n",
|
||||
" 'hovertemplate': ('<b>class: %{text}</b><br>recal' ... 'customdata:.3f}<extra></extra>'),\n",
|
||||
" 'line': {'color': '#3366CC'},\n",
|
||||
" 'mode': 'lines',\n",
|
||||
" 'name': 'Healthy (AP = 0.631)',\n",
|
||||
" 'text': array(['Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy',\n",
|
||||
" 'Healthy', 'Healthy', 'Healthy', 'Healthy', 'Healthy'], dtype='<U7'),\n",
|
||||
" 'type': 'scatter',\n",
|
||||
" 'uid': '7d365acd-11ac-471f-95c1-0c45b4af1e64',\n",
|
||||
" 'x': array([0. , 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1 , 0.11,\n",
|
||||
" 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2 , 0.21, 0.22, 0.23,\n",
|
||||
" 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3 , 0.31, 0.32, 0.33, 0.34, 0.35,\n",
|
||||
" 0.36, 0.37, 0.38, 0.39, 0.4 , 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47,\n",
|
||||
" 0.48, 0.49, 0.5 , 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59,\n",
|
||||
" 0.6 , 0.61, 0.62, 0.63, 0.64, 0.65, 0.66, 0.67, 0.68, 0.69, 0.7 , 0.71,\n",
|
||||
" 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79, 0.8 , 0.81, 0.82, 0.83,\n",
|
||||
" 0.84, 0.85, 0.86, 0.87, 0.88, 0.89, 0.9 , 0.91, 0.92, 0.93, 0.94, 0.95,\n",
|
||||
" 0.96, 0.97, 0.98, 0.99, 1. ]),\n",
|
||||
" 'y': array([1. , 1. , 1. , 1. , 1. , 1. ,\n",
|
||||
" 1. , 1. , 1. , 1. , 1. , 0.97197452,\n",
|
||||
" 0.88968238, 0.88968238, 0.88968238, 0.88968238, 0.88968238, 0.88968238,\n",
|
||||
" 0.88968238, 0.88962774, 0.88906313, 0.88406656, 0.87159671, 0.86864529,\n",
|
||||
" 0.86658584, 0.86482334, 0.86414826, 0.86302011, 0.86087578, 0.8598177 ,\n",
|
||||
" 0.85442276, 0.84805184, 0.84805184, 0.84805184, 0.84805184, 0.84805184,\n",
|
||||
" 0.84805184, 0.84805184, 0.84805184, 0.84805184, 0.84805184, 0.84805184,\n",
|
||||
" 0.84805184, 0.84805184, 0.84805184, 0.84805184, 0.84805184, 0.84727236,\n",
|
||||
" 0.84615682, 0.84387252, 0.84024854, 0.83876528, 0.8378955 , 0.83308277,\n",
|
||||
" 0.82724578, 0.82424648, 0.82113648, 0.8193439 , 0.81764747, 0.81764747,\n",
|
||||
" 0.81764747, 0.81716972, 0.81710833, 0.8164769 , 0.81554506, 0.81418573,\n",
|
||||
" 0.81247877, 0.81064826, 0.73478697, 0.73331137, 0.73195383, 0.73122457,\n",
|
||||
" 0.65219773, 0.41090604, 0.16526846, 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ])},\n",
|
||||
" {'customdata': array([99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. , 99. ,\n",
|
||||
" 98. , 98. , 98. , 98. , 98. , 98. , 97.7, 97. , 97. , 96.7, 96.5, 96. ,\n",
|
||||
" 95.7, 95.7, 95.4, 94.7, 94.6, 93.6, 93.3, 92.4, 91.6, 91.1, 90.5, 90. ,\n",
|
||||
" 89.3, 88.6, 88.2, 87.4, 87.1, 86.2, 85.4, 84.9, 84. , 83.4, 82. , 81.5,\n",
|
||||
" 80.5, 79.4, 78.5, 77.5, 76.4, 75.6, 74.6, 73.7, 72.9, 71.8, 70.9, 70.2,\n",
|
||||
" 69.5, 68.5, 67.1, 66.6, 65.5, 63.8, 62.3, 61.1, 60.1, 53.5, 52.6, 51.2,\n",
|
||||
" 45. , 43.9, 38.2, 37.4, 31.3, 25.5, 10. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ]),\n",
|
||||
" 'hovertemplate': ('<b>class: %{text}</b><br>recal' ... 'customdata:.3f}<extra></extra>'),\n",
|
||||
" 'line': {'color': '#DC3912'},\n",
|
||||
" 'mode': 'lines',\n",
|
||||
" 'name': 'Stressed (AP = 0.614)',\n",
|
||||
" 'text': array(['Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed',\n",
|
||||
" 'Stressed', 'Stressed', 'Stressed', 'Stressed', 'Stressed'], dtype='<U8'),\n",
|
||||
" 'type': 'scatter',\n",
|
||||
" 'uid': 'c00a4fd2-8937-4000-bfbe-6429b1528d2d',\n",
|
||||
" 'x': array([0. , 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1 , 0.11,\n",
|
||||
" 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2 , 0.21, 0.22, 0.23,\n",
|
||||
" 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3 , 0.31, 0.32, 0.33, 0.34, 0.35,\n",
|
||||
" 0.36, 0.37, 0.38, 0.39, 0.4 , 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47,\n",
|
||||
" 0.48, 0.49, 0.5 , 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59,\n",
|
||||
" 0.6 , 0.61, 0.62, 0.63, 0.64, 0.65, 0.66, 0.67, 0.68, 0.69, 0.7 , 0.71,\n",
|
||||
" 0.72, 0.73, 0.74, 0.75, 0.76, 0.77, 0.78, 0.79, 0.8 , 0.81, 0.82, 0.83,\n",
|
||||
" 0.84, 0.85, 0.86, 0.87, 0.88, 0.89, 0.9 , 0.91, 0.92, 0.93, 0.94, 0.95,\n",
|
||||
" 0.96, 0.97, 0.98, 0.99, 1. ]),\n",
|
||||
" 'y': array([1. , 1. , 1. , 1. , 1. , 1. ,\n",
|
||||
" 1. , 1. , 1. , 1. , 1. , 1. ,\n",
|
||||
" 0.91667218, 0.91667218, 0.91667218, 0.91667218, 0.91667218, 0.91667218,\n",
|
||||
" 0.89886256, 0.8706589 , 0.8706589 , 0.86176273, 0.8566061 , 0.8426063 ,\n",
|
||||
" 0.8414663 , 0.8414663 , 0.8414663 , 0.83779842, 0.83730636, 0.82590162,\n",
|
||||
" 0.82226179, 0.81571494, 0.80136981, 0.79562115, 0.79030661, 0.78516763,\n",
|
||||
" 0.77779393, 0.77779393, 0.7766316 , 0.77013304, 0.76981741, 0.7687736 ,\n",
|
||||
" 0.76706332, 0.76615004, 0.76345747, 0.76120645, 0.75633043, 0.75442522,\n",
|
||||
" 0.75216493, 0.74937749, 0.74462707, 0.73753607, 0.73544297, 0.73534102,\n",
|
||||
" 0.73534102, 0.73491603, 0.73330167, 0.73209432, 0.73192653, 0.73073596,\n",
|
||||
" 0.7298439 , 0.72819654, 0.72615935, 0.72491458, 0.72298246, 0.71613728,\n",
|
||||
" 0.71363144, 0.71260932, 0.71076601, 0.64219247, 0.63885976, 0.636554 ,\n",
|
||||
" 0.57072089, 0.56905625, 0.49989485, 0.4972456 , 0.42550943, 0.35394558,\n",
|
||||
" 0.14235075, 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ])}],\n",
|
||||
" 'layout': {'margin': {'b': 0, 'l': 0, 'r': 0, 't': 30},\n",
|
||||
" 'shapes': [{'line': {'dash': 'dash'}, 'type': 'line', 'x0': 0, 'x1': 1, 'y0': 1, 'y1': 0}],\n",
|
||||
" 'template': '...',\n",
|
||||
" 'xaxis': {'constrain': 'domain', 'range': [0, 1], 'title': {'text': 'Recall'}},\n",
|
||||
" 'yaxis': {'constrain': 'domain',\n",
|
||||
" 'range': [0, 1],\n",
|
||||
" 'scaleanchor': 'x',\n",
|
||||
" 'scaleratio': 1,\n",
|
||||
" 'title': {'text': 'Precision'}}}\n",
|
||||
"})"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Print a classification report for all classes\n",
|
||||
"results.print_report()\n",
|
||||
"\n",
|
||||
"print(results.mAP())\n",
|
||||
"\n",
|
||||
"# Plot confusion matrix\n",
|
||||
"matrix = results.plot_confusion_matrix(classes=classes)\n",
|
||||
"matrix.show()\n",
|
||||
"\n",
|
||||
"pr_curves = results.plot_pr_curves(classes=classes)\n",
|
||||
"pr_curves.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "d1137788",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Session launched. Run `session.show()` to open the App in a cell output.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"application/javascript": [
|
||||
"window.open('http://localhost:5151/');"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.Javascript object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"session = fo.launch_app(dataset, auto=False)\n",
|
||||
"session.view = dataset.view()\n",
|
||||
"session.plots.attach(matrix)\n",
|
||||
"session.open_tab()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "6eed9a86",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def export_dataset(dataset, export_dir):\n",
|
||||
" label_field = \"ground_truth\"\n",
|
||||
"\n",
|
||||
" # The splits to export\n",
|
||||
" splits = [\"val\"]\n",
|
||||
"\n",
|
||||
" classes = [\"Healthy\", \"Stressed\"]\n",
|
||||
"\n",
|
||||
" # Export the splits\n",
|
||||
" for split in splits:\n",
|
||||
" split_view = dataset.match_tags(split)\n",
|
||||
" split_view.export(\n",
|
||||
" export_dir=export_dir,\n",
|
||||
" dataset_type=fo.types.YOLOv5Dataset,\n",
|
||||
" label_field=label_field,\n",
|
||||
" split=split,\n",
|
||||
" classes=classes,\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "19c5b271",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ebdde519",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.15"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import fiftyone as fo
|
||||
from PIL import Image
|
||||
from detection import detect
|
||||
|
||||
name = "dataset-small"
|
||||
dataset_dir = "/home/zenon/Documents/master-thesis/classification/evaluation/dataset-small"
|
||||
|
||||
# The splits to load
|
||||
splits = ["val"]
|
||||
|
||||
# Load the dataset, using tags to mark the samples in each split
|
||||
dataset = fo.Dataset(name)
|
||||
for split in splits:
|
||||
dataset.add_dir(
|
||||
dataset_dir=dataset_dir,
|
||||
dataset_type=fo.types.YOLOv5Dataset,
|
||||
split=split,
|
||||
tags=split,
|
||||
)
|
||||
|
||||
classes = dataset.default_classes
|
||||
predictions_view = dataset.view()
|
||||
|
||||
with fo.ProgressBar() as pb:
|
||||
for sample in pb(predictions_view):
|
||||
image = Image.open(sample.filepath)
|
||||
w, h = image.size
|
||||
pred = detect(sample.filepath, '../weights/yolo.onnx', '../weights/resnet.onnx')
|
||||
|
||||
detections = []
|
||||
for _, row in pred.iterrows():
|
||||
xmin, xmax = int(row['xmin']), int(row['xmax'])
|
||||
ymin, ymax = int(row['ymin']), int(row['ymax'])
|
||||
rel_box = [
|
||||
xmin / w, ymin / h, (xmax - xmin) / w, (ymax - ymin) / h
|
||||
]
|
||||
detections.append(
|
||||
fo.Detection(label=classes[int(row['cls'])],
|
||||
bounding_box=rel_box,
|
||||
confidence=int(row['cls_conf'])))
|
||||
|
||||
sample["yolo_resnet"] = fo.Detections(detections=detections)
|
||||
sample.save()
|
||||
|
||||
results = predictions_view.evaluate_detections(
|
||||
"yolo_resnet",
|
||||
gt_field="ground_truth",
|
||||
eval_key="eval",
|
||||
compute_mAP=True,
|
||||
)
|
||||
|
||||
# Get the 10 most common classes in the dataset
|
||||
counts = dataset.count_values("ground_truth.detections.label")
|
||||
classes_top10 = sorted(counts, key=counts.get, reverse=True)[:10]
|
||||
|
||||
# Print a classification report for the top-10 classes
|
||||
results.print_report(classes=classes_top10)
|
||||
|
||||
plot = results.plot_pr_curves(classes=["Healthy", "Stressed"])
|
||||
plot.show()
|
||||
|
||||
session = fo.launch_app(dataset)
|
||||
session.view = predictions_view
|
||||
session.wait()
|
||||
@@ -0,0 +1,135 @@
|
||||
import logging
|
||||
import argparse
|
||||
import cv2
|
||||
import json
|
||||
import os
|
||||
|
||||
from utils.conversions import convert_to_yolo
|
||||
from detection import detect
|
||||
|
||||
template = [{
|
||||
"data": {
|
||||
"image": "/data/local-files/?d=evaluation/images/0.jpg"
|
||||
},
|
||||
"predictions": [{
|
||||
"model_version":
|
||||
"one",
|
||||
"score":
|
||||
0.0,
|
||||
"result": [{
|
||||
"id": "result1",
|
||||
"type": "rectanglelabels",
|
||||
"from_name": "label",
|
||||
"to_name": "image",
|
||||
"original_width": 474,
|
||||
"original_height": 266,
|
||||
"image_rotation": 0,
|
||||
"confidence": 0,
|
||||
"value": {
|
||||
"rotation": 0,
|
||||
"x": 19.62,
|
||||
"y": 15.04,
|
||||
"width": 55.06,
|
||||
"height": 78.2,
|
||||
"rectanglelabels": ["Stressed"]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
|
||||
|
||||
def create_json_annotation(image_path, yolo_path, resnet_path):
|
||||
"""Create a JSON representation of identified bounding boxes.
|
||||
|
||||
:param image_path str: path to image
|
||||
:param yolo_path str: path to YOLO model in ONNX format
|
||||
:param resnet_path str: path to ResNet model in ONNX format
|
||||
:returns Dict: bounding boxes in labelstudio JSON format
|
||||
"""
|
||||
template[0]['data'][
|
||||
'image'] = "/data/local-files/?d=evaluation/" + image_path
|
||||
img = cv2.imread(image_path)
|
||||
(height, width) = img.shape[0], img.shape[1]
|
||||
bboxes = detect(image_path, yolo_path, resnet_path)
|
||||
result = template[0]['predictions'][0]['result']
|
||||
|
||||
results = []
|
||||
for idx, row in bboxes.iterrows():
|
||||
modified = convert_to_yolo(row, width, height)
|
||||
json_result = {}
|
||||
json_result['id'] = 'result' + str(idx + 1)
|
||||
json_result['type'] = 'rectanglelabels'
|
||||
json_result['from_name'] = 'label'
|
||||
json_result['to_name'] = 'image'
|
||||
json_result['original_width'] = width
|
||||
json_result['original_height'] = height
|
||||
json_result['image_rotation'] = 0
|
||||
json_result['value'] = {}
|
||||
json_result['value']['rotation'] = 0
|
||||
json_result['value']['x'] = modified['xmin%']
|
||||
json_result['value']['y'] = modified['ymin%']
|
||||
json_result['value']['width'] = modified['width%']
|
||||
json_result['value']['height'] = modified['height%']
|
||||
if modified['cls'] == 0:
|
||||
json_result['value']['rectanglelabels'] = ['Healthy']
|
||||
else:
|
||||
json_result['value']['rectanglelabels'] = ['Stressed']
|
||||
results.append(json_result)
|
||||
|
||||
template[0]['predictions'][0]['result'] = results
|
||||
return template
|
||||
|
||||
|
||||
def write_labels_to_disk(image_dir, output_dir, yolo_path, resnet_path):
|
||||
"""Read images from disk, classify them and output bounding boxes
|
||||
in labelstudio JSON format.
|
||||
|
||||
:param image_dir str: directory containing images to label
|
||||
:param output_dir str: directory to save JSON files to
|
||||
:param yolo_path str: path to YOLO model in ONNX format
|
||||
:param resnet_path str: path to ResNet model in ONNX format
|
||||
:returns: None
|
||||
"""
|
||||
image_dir = os.path.join(image_dir, '')
|
||||
for file in os.listdir(image_dir):
|
||||
filename = os.fsdecode(file)
|
||||
filename_wo_ext = os.path.splitext(filename)[0]
|
||||
rel_output_path = os.path.join(output_dir, filename_wo_ext + '.json')
|
||||
json_data = create_json_annotation(image_dir + filename, yolo_path,
|
||||
resnet_path)
|
||||
os.makedirs(os.path.dirname(os.path.join(output_dir, filename)),
|
||||
exist_ok=True)
|
||||
logging.info('Writing json file for %s', filename)
|
||||
with open(rel_output_path, 'w') as f:
|
||||
json.dump(json_data, f)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--source',
|
||||
type=str,
|
||||
help='source folder with images',
|
||||
required=True)
|
||||
parser.add_argument('--output',
|
||||
type=str,
|
||||
help='output folder for json files',
|
||||
required=True)
|
||||
parser.add_argument('--yolo',
|
||||
type=str,
|
||||
help='path to YOLO model in ONNX format',
|
||||
required=True)
|
||||
parser.add_argument('--resnet',
|
||||
type=str,
|
||||
help='path to ResNet model in ONNX format',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--log',
|
||||
type=str,
|
||||
help='log level (debug, info, warning, error, critical)',
|
||||
default='warning')
|
||||
opt = parser.parse_args()
|
||||
numeric_level = getattr(logging, opt.log.upper(), None)
|
||||
logging.basicConfig(format='%(levelname)s::%(asctime)s::%(message)s',
|
||||
datefmt='%Y-%m-%dT%H:%M:%S',
|
||||
level=numeric_level)
|
||||
write_labels_to_disk(opt.source, opt.output, opt.yolo, opt.resnet)
|
||||
Reference in New Issue
Block a user