Diff of /test.py [000000] .. [190ca4]

Switch to unified view

a b/test.py
1
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
2
"""
3
Validate a trained YOLOv5 detection model on a detection dataset.
4
5
Usage:
6
    $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640
7
8
Usage - formats:
9
    $ python val.py --weights yolov5s.pt                 # PyTorch
10
                              yolov5s.torchscript        # TorchScript
11
                              yolov5s.onnx               # ONNX Runtime or OpenCV DNN with --dnn
12
                              yolov5s_openvino_model     # OpenVINO
13
                              yolov5s.engine             # TensorRT
14
                              yolov5s.mlmodel            # CoreML (macOS-only)
15
                              yolov5s_saved_model        # TensorFlow SavedModel
16
                              yolov5s.pb                 # TensorFlow GraphDef
17
                              yolov5s.tflite             # TensorFlow Lite
18
                              yolov5s_edgetpu.tflite     # TensorFlow Edge TPU
19
                              yolov5s_paddle_model       # PaddlePaddle
20
"""
21
22
import argparse
23
import json
24
import os
25
import subprocess
26
import sys
27
from pathlib import Path
28
29
import numpy as np
30
import torch
31
from tqdm import tqdm
32
import csv
33
import pandas as pd
34
35
FILE = Path(__file__).resolve()
36
ROOT = FILE.parents[0]  # YOLOv5 root directory
37
if str(ROOT) not in sys.path:
38
    sys.path.append(str(ROOT))  # add ROOT to PATH
39
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative
40
41
from models.common import DetectMultiBackend
42
from utils.callbacks import Callbacks
43
from utils.dataloaders import create_dataloader
44
from utils.general import (
45
    LOGGER,
46
    TQDM_BAR_FORMAT,
47
    Profile,
48
    check_dataset,
49
    check_img_size,
50
    check_requirements,
51
    check_yaml,
52
    coco80_to_coco91_class,
53
    colorstr,
54
    increment_path,
55
    non_max_suppression,
56
    print_args,
57
    scale_boxes,
58
    xywh2xyxy,
59
    xyxy2xywh,
60
    get_fixed_xyxy
61
)
62
from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
63
from utils.plots import output_to_target, plot_images, plot_val_study
64
from utils.torch_utils import select_device, smart_inference_mode
65
from utils.my_model import MyCNN
66
import torch.nn.functional as F
67
import re
68
from torchvision.ops import roi_align
69
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
70
from tabulate import tabulate
71
72
73
74
75
def save_one_txt(predn, save_conf, shape, file):
76
    # Save one txt result
77
    gn = torch.tensor(shape)[[1, 0, 1, 0]]  # normalization gain whwh
78
    for *xyxy, conf, cls in predn.tolist():
79
        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
80
        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
81
        with open(file, "a") as f:
82
            f.write(("%g " * len(line)).rstrip() % line + "\n")
83
84
85
def save_one_json(predn, jdict, path, class_map):
86
    # Save one JSON result {"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}
87
    image_id = int(path.stem) if path.stem.isnumeric() else path.stem
88
    box = xyxy2xywh(predn[:, :4])  # xywh
89
    box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner
90
    for p, b in zip(predn.tolist(), box.tolist()):
91
        jdict.append(
92
            {
93
                "image_id": image_id,
94
                "category_id": class_map[int(p[5])],
95
                "bbox": [round(x, 3) for x in b],
96
                "score": round(p[4], 5),
97
            }
98
        )
99
100
def my_process_batch(detections, labels, iouv):
101
    """
102
    Return correct prediction matrix and top indices.
103
104
    Arguments:
105
        detections (array[N, 6]), x1, y1, x2, y2, conf, class
106
        labels (array[M, 5]), class, x1, y1, x2, y2
107
        iouv (tensor[10]), IoU thresholds
108
    Returns:
109
        correct (array[N, 10]), for 10 IoU levels
110
        top_indices (tensor[M]), top IoU-gaining detection indices for each label
111
    """
112
    correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
113
    iou = box_iou(labels[:, 1:], detections[:, :4])
114
    correct_class = labels[:, 0:1] == detections[:, 5]
115
    
116
    top_indices = torch.argmax(iou, dim=1)
117
118
    for i in range(len(iouv)):
119
        x = torch.where((iou >= iouv[i]) & correct_class)  # IoU > threshold and classes match
120
        if x[0].shape[0]:
121
            matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()  # [label, detect, iou]
122
            if x[0].shape[0] > 1:
123
                matches = matches[matches[:, 2].argsort()[::-1]]
124
                matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
125
                # matches = matches[matches[:, 2].argsort()[::-1]]
126
                matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
127
            correct[matches[:, 1].astype(int), i] = True
128
129
    return torch.tensor(correct, dtype=torch.bool, device=iouv.device), top_indices
130
131
132
133
def process_batch(detections, labels, iouv):
134
    """
135
    Return correct prediction matrix.
136
137
    Arguments:
138
        detections (array[N, 6]), x1, y1, x2, y2, conf, class
139
        labels (array[M, 5]), class, x1, y1, x2, y2
140
    Returns:
141
        correct (array[N, 10]), for 10 IoU levels
142
    """
143
    correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
144
    iou = box_iou(labels[:, 1:], detections[:, :4])
145
    correct_class = labels[:, 0:1] == detections[:, 5]
146
    for i in range(len(iouv)):
147
        x = torch.where((iou >= iouv[i]) & correct_class)  # IoU > threshold and classes match
148
        if x[0].shape[0]:
149
            matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()  # [label, detect, iou]
150
            if x[0].shape[0] > 1:
151
                matches = matches[matches[:, 2].argsort()[::-1]]
152
                matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
153
                # matches = matches[matches[:, 2].argsort()[::-1]]
154
                matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
155
            correct[matches[:, 1].astype(int), i] = True
156
    return torch.tensor(correct, dtype=torch.bool, device=iouv.device)
157
158
159
@smart_inference_mode()
160
def run(
161
    data,
162
    weights=None,  # model.pt path(s)
163
    batch_size=32,  # batch size
164
    imgsz=640,  # inference size (pixels)
165
    conf_thres=0.001,  # confidence threshold
166
    iou_thres=0.6,  # NMS IoU threshold
167
    max_det=300,  # maximum detections per image
168
    task="val",  # train, val, test, speed or study
169
    device="",  # cuda device, i.e. 0 or 0,1,2,3 or cpu
170
    workers=8,  # max dataloader workers (per RANK in DDP mode)
171
    single_cls=False,  # treat as single-class dataset
172
    augment=False,  # augmented inference
173
    verbose=False,  # verbose output
174
    save_txt=False,  # save results to *.txt
175
    save_hybrid=False,  # save label+prediction hybrid results to *.txt
176
    save_conf=False,  # save confidences in --save-txt labels
177
    save_json=False,  # save a COCO-JSON results file
178
    project=ROOT / "runs/val",  # save to project/name
179
    name="exp",  # save to project/name
180
    exist_ok=False,  # existing project/name ok, do not increment
181
    half=True,  # use FP16 half-precision inference
182
    dnn=False,  # use OpenCV DNN for ONNX inference
183
    model=None,
184
    dataloader=None,
185
    save_dir=Path(""),
186
    plots=True,
187
    callbacks=Callbacks(),
188
    compute_loss=None,
189
):
190
    # Initialize/load model and set device
191
    training = model is not None
192
    if training:  # called by train.py
193
        device, pt, jit, engine = next(model.parameters()).device, True, False, False  # get model device, PyTorch model
194
        half &= device.type != "cpu"  # half precision only supported on CUDA
195
        model.half() if half else model.float()
196
    else:  # called directly
197
        device = select_device(device, batch_size=batch_size)
198
199
        # Directories
200
        save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
201
        (save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir
202
203
        # Load model
204
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
205
        stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
206
        imgsz = check_img_size(imgsz, s=stride)  # check image size
207
        half = model.fp16  # FP16 supported on limited backends with CUDA
208
        if engine:
209
            batch_size = model.batch_size
210
        else:
211
            device = model.device
212
            if not (pt or jit):
213
                batch_size = 1  # export.py models default to batch-size 1
214
                LOGGER.info(f"Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models")
215
216
        # Data
217
        data = check_dataset(data)  # check
218
219
    # Configure
220
    model.eval()
221
    cuda = device.type != "cpu"
222
    is_coco = isinstance(data.get("val"), str) and data["val"].endswith(f"coco{os.sep}val2017.txt")  # COCO dataset
223
    nc = 1 if single_cls else int(data["nc"])  # number of classes
224
    iouv = torch.linspace(0.5, 0.95, 10, device=device)  # iou vector for mAP@0.5:0.95
225
    niou = iouv.numel()
226
227
    # Dataloader
228
    if not training:
229
        if pt and not single_cls:  # check --weights are trained on --data
230
            ncm = model.model.nc
231
            assert ncm == nc, (
232
                f"{weights} ({ncm} classes) trained on different --data than what you passed ({nc} "
233
                f"classes). Pass correct combination of --weights and --data that are trained together."
234
            )
235
        model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup
236
        pad, rect = (0.0, False) if task == "speed" else (0.5, pt)  # square inference for benchmarks
237
        task = task if task in ("train", "val", "test") else "val"  # path to train/val/test images
238
        dataloader = create_dataloader(
239
            data[task],
240
            imgsz,
241
            batch_size,
242
            stride,
243
            single_cls,
244
            pad=pad,
245
            rect=rect,
246
            workers=workers,
247
            prefix=colorstr(f"{task}: "),
248
        )[0]
249
250
    seen = 0
251
    confusion_matrix = ConfusionMatrix(nc=nc)
252
    names = model.names if hasattr(model, "names") else model.module.names  # get class names
253
    if isinstance(names, (list, tuple)):  # old format
254
        names = dict(enumerate(names))
255
    class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
256
    s = ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "P", "R", "mAP50", "mAP50-95")
257
    tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
258
    dt = Profile(device=device), Profile(device=device), Profile(device=device)  # profiling times
259
    loss = torch.zeros(3, device=device)
260
    jdict, stats, ap, ap_class = [], [], [], []
261
    callbacks.run("on_val_start")
262
    pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)  # progress bar
263
    all_rows = []
264
265
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
266
        callbacks.run("on_val_batch_start")
267
        with dt[0]:
268
            if cuda:
269
                im = im.to(device, non_blocking=True)
270
                targets = targets.to(device)
271
            im = im.half() if half else im.float()  # uint8 to fp16/32
272
            im /= 255  # 0 - 255 to 0.0 - 1.0
273
            nb, _, height, width = im.shape  # batch size, channels, height, width
274
275
        # Inference
276
        with dt[1]:
277
            (preds,int_feats),_  = model(im) if compute_loss else (model(im, augment=augment), None) 
278
            train_out = preds[1]
279
            in_channels = int_feats[0].shape[1]
280
            attribute_model_path =  ROOT / "Attribute_model/last_weights.pth"
281
            custom_weights = torch.load(attribute_model_path)
282
            cell_model = MyCNN(num_classes=12, dropout_prob=0.5, in_channels=480).to(device)
283
            cell_model.load_state_dict(custom_weights)
284
285
286
287
        # Loss
288
        if compute_loss:
289
            loss += compute_loss(train_out, targets)[1]  # box, obj, cls
290
291
        # NMS
292
        attribute_targets = targets[:,7:13]
293
        targets = targets[:, 0:6] # I changed here    
294
        targets[:, 2:] *= torch.tensor((width, height, width, height), device=device)  # to pixels
295
        lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else []  # for autolabelling
296
        with dt[2]:
297
            preds = non_max_suppression(
298
                preds, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls, max_det=max_det
299
            )
300
301
        
302
        # Metrics
303
        for si, pred in enumerate(preds):
304
305
            labels = targets[targets[:, 0] == si, 1:]
306
            all_top_indices_cell_pred = []
307
            pred_Nuclear_Chromatin_array = []
308
            pred_Nuclear_Shape_array = []
309
            pred_Nucleus_array = []
310
            pred_Cytoplasm_array = []
311
            pred_Cytoplasmic_Basophilia_array = []
312
            pred_Cytoplasmic_Vacuoles_array = []
313
314
            if (len(pred)>0):
315
                boxes = torch.cat([si * torch.ones(pred.shape[0], 1).to(device), pred[:, 0:4].to(device)], dim=1)
316
317
318
                int_feats_p2 = int_feats[0][si].to(torch.float32).unsqueeze(0)
319
                int_feats_p3 = int_feats[1][si].to(torch.float32).unsqueeze(0)
320
321
322
                torch.cuda.empty_cache()
323
324
325
                # del int_feats
326
                all_top_indices_cell_pred = []
327
328
                for i in range(len(pred)):
329
330
                    pred_tensor = pred[i, 0:4]
331
                    img_shape_tensor = torch.tensor([im.shape[2], im.shape[3],im.shape[2],im.shape[3]]).to(device)
332
333
                    normalized_xyxy=pred_tensor / img_shape_tensor
334
                    p2_feature_shape_tensor = torch.tensor([int_feats[0][si].shape[1], int_feats[0][si].shape[2],int_feats[0][si].shape[1],int_feats[0][si].shape[2]]).to(device)                        # reduce_channels_layer = torch.nn.Conv2d(1280, 250, kernel_size=1).to(device)
335
                    p3_feature_shape_tensor = torch.tensor([int_feats[1][si].shape[1], int_feats[1][si].shape[2],int_feats[1][si].shape[1],int_feats[1][si].shape[2]]).to(device)             # reduce_channels_layer = torch.nn.Conv2d(1280, 250, kernel_size=1).to(device)
336
337
                    
338
                    p2_normalized_xyxy = normalized_xyxy*p2_feature_shape_tensor
339
                    p3_normalized_xyxy = normalized_xyxy*p3_feature_shape_tensor
340
341
                    
342
                    p2_x_min, p2_y_min, p2_x_max, p2_y_max = get_fixed_xyxy(p2_normalized_xyxy,int_feats_p2)
343
                    p3_x_min, p3_y_min, p3_x_max, p3_y_max = get_fixed_xyxy(p3_normalized_xyxy,int_feats_p3)
344
345
346
                    p2_roi = torch.tensor([p2_x_min, p2_y_min, p2_x_max, p2_y_max], device=device).float() 
347
                    p3_roi = torch.tensor([p3_x_min, p3_y_min, p3_x_max, p3_y_max], device=device).float() 
348
349
                    batch_index = torch.tensor([0], dtype=torch.float32, device = device)
350
351
                    # Concatenate the batch index to the bounding box coordinates
352
                    p2_roi_with_batch_index = torch.cat([batch_index, p2_roi])
353
                    p3_roi_with_batch_index = torch.cat([batch_index, p3_roi])
354
355
356
                    p2_resized_object = roi_align(int_feats_p2, p2_roi_with_batch_index.unsqueeze(0).to(device), output_size=(24, 30))
357
                    p3_resized_object = roi_align(int_feats_p3, p3_roi_with_batch_index.unsqueeze(0).to(device), output_size=(24, 30))
358
                    concat_box = torch.cat([p2_resized_object,p3_resized_object],dim=1)
359
360
                    in_channels = concat_box.size(1)
361
                    cell_model.eval().to(device)
362
                    output_cell_prediction= cell_model(concat_box)
363
                    output_cell_prediction_prob = F.softmax(output_cell_prediction.view(6,2), dim=1)
364
                    top_indices_cell_pred = torch.argmax(output_cell_prediction_prob, dim=1)
365
                    all_top_indices_cell_pred.append(top_indices_cell_pred)
366
                    pred_Nuclear_Chromatin_array.append(top_indices_cell_pred[0].item())
367
                    pred_Nuclear_Shape_array.append(top_indices_cell_pred[1].item())
368
                    pred_Nucleus_array.append(top_indices_cell_pred[2].item())
369
                    pred_Cytoplasm_array.append(top_indices_cell_pred[3].item())
370
                    pred_Cytoplasmic_Basophilia_array.append(top_indices_cell_pred[4].item())
371
                    pred_Cytoplasmic_Vacuoles_array.append(top_indices_cell_pred[5].item())
372
373
374
375
            nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions
376
            path, shape = Path(paths[si]), shapes[si][0]
377
            correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)  # init
378
            seen += 1
379
380
            if npr == 0:
381
                if nl:
382
                    stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
383
                    if plots:
384
                        # conf_,indices=my_process_batch(detections=None, labels=labels[:, 0])
385
                        confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
386
                continue
387
388
            # Predictions
389
            if single_cls:
390
                pred[:, 5] = 0
391
            predn = pred.clone()
392
            scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])  # native-space pred
393
394
            # Evaluate
395
            if nl:
396
                tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
397
                scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
398
                labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
399
                correct = process_batch(predn, labelsn, iouv)
400
                _,max_iou_indices = my_process_batch(predn, labelsn, iouv)
401
                max_iou_predicted_boxes=boxes[max_iou_indices]
402
                attributes_prediction=[]
403
                epoch = 0
404
                for i in range(len(max_iou_indices)):
405
406
                    attributes_pred = all_top_indices_cell_pred[max_iou_indices[i]].detach().cpu()
407
                    attributes_prediction.append(attributes_pred)
408
                
409
                path_object = Path(path)
410
                # Extracting the name
411
                file_name = path_object.name
412
                filenames =[]
413
414
                # Initialize lists for each attribute
415
                attribute_names = ['Nuclear Chromatin', 'Nuclear Shape', 'Nucleus', 'Cytoplasm', 'Cytoplasmic Basophilia', 'Cytoplasmic Vacuoles']
416
417
                all_labels_list = [[] for _ in range(6)]
418
                all_prediction_attributes_list = [[] for _ in range(6)]
419
  
420
421
422
                for i in range(len(attributes_prediction)):
423
                    count = 0
424
                    label = attribute_targets[i].detach().cpu().int()
425
426
                    if all(x != 2 for x in label):
427
                        filenames.append(file_name)
428
429
                        # Append the label and prediction to the corresponding attribute list
430
                        for j in range(6):
431
                            all_labels_list[j].append(label[j].item())
432
                            all_prediction_attributes_list[j].append(attributes_prediction[i][j].item())
433
434
                # Combine data into rows
435
                rows = zip(filenames, *all_labels_list, *all_prediction_attributes_list)
436
                all_rows.extend(rows)
437
438
                if plots:
439
                    cnf_,indices= my_process_batch(predn, labelsn,iouv=iouv)
440
                    confusion_matrix.process_batch(predn, labelsn)
441
            stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))  # (correct, conf, pcls, tcls)
442
443
            # Save/log
444
            if save_txt:
445
                (save_dir / 'labels').mkdir(parents=True, exist_ok=True)
446
                save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / f'{path.stem}.txt')
447
            if save_json:
448
                save_one_json(predn, jdict, path, class_map)  # append to COCO-JSON dictionary
449
            callbacks.run('on_val_image_end', pred, predn, path, names, im[si])
450
451
        # Plot images
452
        if plots and batch_i < 3:
453
            plot_images(im, targets, paths, save_dir / f'val_batch{batch_i}_labels.jpg', names)  # labels
454
            plot_images(im, output_to_target(preds), paths, save_dir / f'val_batch{batch_i}_pred.jpg', names)  # pred
455
456
        callbacks.run('on_val_batch_end', batch_i, im, targets, paths, shapes, preds)
457
458
459
    attribute_model_dir = ROOT / "Attribute_model/test/test"
460
    attribute_names = ['Nuclear Chromatin', 'Nuclear Shape', 'Nucleus', 'Cytoplasm', 'Cytoplasmic Basophilia', 'Cytoplasmic Vacuoles']
461
462
    # Create directory if it doesn't exist
463
    attribute_model_dir.mkdir(parents=True, exist_ok=True)
464
465
                # Write to the CSV file
466
    csv_file_path = f"{attribute_model_dir}.csv"
467
    with open(csv_file_path, 'a', newline='') as csvfile:
468
        csv_writer = csv.writer(csvfile)
469
470
                # Write header if the file is empty
471
        if os.path.getsize(csv_file_path) == 0:
472
            header = ['filename'] + [f'{attr}' for attr in attribute_names] + [f'pred_{attr}' for attr in attribute_names]
473
            csv_writer.writerow(header)
474
475
                    # Write all accumulated rows
476
        csv_writer.writerows(all_rows)
477
478
479
    # Read the CSV file into a DataFrame
480
    df = pd.read_csv(csv_file_path, header=None, names=['filename', 'Nuclear Chromatin','Nuclear Shape','Nucleus','Cytoplasm','Cytoplasmic Basophilia','Cytoplasmic Vacuoles','pred_Nuclear Chromatin','pred_Nuclear Shape','pred_Nucleus','pred_Cytoplasm','pred_Cytoplasmic Basophilia','pred_Cytoplasmic Vacuoles'])
481
482
    # Drop the first row if it contains headers
483
    df = df.iloc[1:]
484
485
    # Convert label columns to numeric, replace invalid literals with NaN
486
    Nuclear_Chromatin_array = pd.to_numeric(df['Nuclear Chromatin'], errors='coerce')
487
    Nuclear_Shape_array = pd.to_numeric(df['Nuclear Shape'], errors='coerce')
488
    Nucleus_array = pd.to_numeric(df['Nucleus'], errors='coerce')
489
    Cytoplasm_array = pd.to_numeric(df['Cytoplasm'], errors='coerce')
490
    Cytoplasmic_Basophilia_array = pd.to_numeric(df['Cytoplasmic Basophilia'], errors='coerce')
491
    Cytoplasmic_Vacuoles_array = pd.to_numeric(df['Cytoplasmic Vacuoles'], errors='coerce')
492
493
    pred_Nuclear_Chromatin_array = pd.to_numeric(df['pred_Nuclear Chromatin'], errors='coerce')
494
    pred_Nuclear_Shape_array = pd.to_numeric(df['pred_Nuclear Shape'], errors='coerce')
495
    pred_Nucleus_array = pd.to_numeric(df['pred_Nucleus'], errors='coerce')
496
    pred_Cytoplasm_array = pd.to_numeric(df['pred_Cytoplasm'], errors='coerce')
497
    pred_Cytoplasmic_Basophilia_array = pd.to_numeric(df['pred_Cytoplasmic Basophilia'], errors='coerce')
498
    pred_Cytoplasmic_Vacuoles_array = pd.to_numeric(df['pred_Cytoplasmic Vacuoles'], errors='coerce')
499
500
501
    # Exclude or replace non-numeric entries
502
    Nuclear_Chromatin_array = Nuclear_Chromatin_array[~np.isnan(Nuclear_Chromatin_array)].astype(int)
503
    Nuclear_Shape_array = Nuclear_Shape_array[~np.isnan(Nuclear_Shape_array)].astype(int)
504
    Nucleus_array = Nucleus_array[~np.isnan(Nucleus_array)].astype(int)
505
    Cytoplasm_array = Cytoplasm_array[~np.isnan(Cytoplasm_array)].astype(int)
506
    Cytoplasmic_Basophilia_array = Cytoplasmic_Basophilia_array[~np.isnan(Cytoplasmic_Basophilia_array)].astype(int)
507
    Cytoplasmic_Vacuoles_array = Cytoplasmic_Vacuoles_array[~np.isnan(Cytoplasmic_Vacuoles_array)].astype(int)
508
509
    # Exclude or replace non-numeric entries
510
    pred_Nuclear_Chromatin_array = pred_Nuclear_Chromatin_array[~np.isnan(pred_Nuclear_Chromatin_array)].astype(int)
511
    pred_Nuclear_Shape_array = pred_Nuclear_Shape_array[~np.isnan(pred_Nuclear_Shape_array)].astype(int)
512
    pred_Nucleus_array = pred_Nucleus_array[~np.isnan(pred_Nucleus_array)].astype(int)
513
    pred_Cytoplasm_array = pred_Cytoplasm_array[~np.isnan(pred_Cytoplasm_array)].astype(int)
514
    pred_Cytoplasmic_Basophilia_array = pred_Cytoplasmic_Basophilia_array[~np.isnan(pred_Cytoplasmic_Basophilia_array)].astype(int)
515
    pred_Cytoplasmic_Vacuoles_array = pred_Cytoplasmic_Vacuoles_array[~np.isnan(pred_Cytoplasmic_Vacuoles_array)].astype(int)
516
517
    # print(f"\nAccuracy: {accuracy:.3f}, Precision: {precision:.3f}, Recall: {recall:.3}, F1: {f1:.3}")
518
    def compute_and_print_metrics(attribute_name, true_labels, pred_labels):
519
        # Exclude or replace non-numeric entries
520
        true_labels = true_labels[~np.isnan(true_labels)].astype(int)
521
        pred_labels = pred_labels[~np.isnan(pred_labels)].astype(int)
522
523
        # Compute metrics
524
        accuracy = accuracy_score(true_labels, pred_labels)
525
        precision = precision_score(true_labels, pred_labels)
526
        recall = recall_score(true_labels, pred_labels)
527
        f1 = f1_score(true_labels, pred_labels)
528
529
        return [attribute_name, accuracy, precision, recall, f1]
530
531
532
533
    # Compute metrics
534
    stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy
535
    if len(stats) and stats[0].any():
536
        tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
537
        ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
538
        mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
539
    nt = np.bincount(stats[3].astype(int), minlength=nc)  # number of targets per class
540
541
    # Print results
542
    pf = "%22s" + "%11i" * 2 + "%11.3g" * 4  # print format
543
    LOGGER.info(pf % ("all", seen, nt.sum(), mp, mr, map50, map))
544
    if nt.sum() == 0:
545
        LOGGER.warning(f"WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels")
546
547
    # Print results per class
548
    if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
549
        for i, c in enumerate(ap_class):
550
            LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
551
    
552
    metrics = []
553
    NC_prec = compute_and_print_metrics('Nuclear_Chromatin', Nuclear_Chromatin_array, pred_Nuclear_Chromatin_array)
554
    NS_prec = compute_and_print_metrics('Nuclear_Shape', Nuclear_Shape_array, pred_Nuclear_Shape_array)
555
    N_prec = compute_and_print_metrics('Nucleus', Nucleus_array, pred_Nucleus_array)
556
    C_prec = compute_and_print_metrics('Cytoplasm', Cytoplasm_array, pred_Cytoplasm_array)
557
    CB_prec = compute_and_print_metrics('Cytoplasmic_Basophilia', Cytoplasmic_Basophilia_array, pred_Cytoplasmic_Basophilia_array)
558
    CV_prec = compute_and_print_metrics('Cytoplasmic_Vacuoles', Cytoplasmic_Vacuoles_array, pred_Cytoplasmic_Vacuoles_array)
559
560
    # Calculate average precision
561
    average_precision = np.mean([NC_prec[-1], NS_prec[-1], N_prec[-1], C_prec[-1], CB_prec[-1], CV_prec[-1]])
562
563
    # Append results to metrics list
564
    metrics.extend([NC_prec, NS_prec, N_prec, C_prec, CB_prec, CV_prec])
565
566
    # Print table
567
    headers = ["Attribute", "Accuracy", "Precision", "Recall", "F1"]
568
    print(tabulate(metrics, headers=headers, tablefmt="grid"))  
569
570
571
    # Print speeds
572
    t = tuple(x.t / seen * 1e3 for x in dt)  # speeds per image
573
    if not training:
574
        shape = (batch_size, 3, imgsz, imgsz)
575
        LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}" % t)
576
577
    # Plots
578
    if plots:
579
        confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
580
        callbacks.run("on_val_end", nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)
581
582
    # Save JSON
583
    if save_json and len(jdict):
584
        w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ""  # weights
585
        anno_json = str(Path("../datasets/coco/annotations/instances_val2017.json"))  # annotations
586
        if not os.path.exists(anno_json):
587
            anno_json = os.path.join(data["path"], "annotations", "instances_val2017.json")
588
        pred_json = str(save_dir / f"{w}_predictions.json")  # predictions
589
        LOGGER.info(f"\nEvaluating pycocotools mAP... saving {pred_json}...")
590
        with open(pred_json, "w") as f:
591
            json.dump(jdict, f)
592
593
        try:  # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
594
            check_requirements("pycocotools>=2.0.6")
595
            from pycocotools.coco import COCO
596
            from pycocotools.cocoeval import COCOeval
597
598
            anno = COCO(anno_json)  # init annotations api
599
            pred = anno.loadRes(pred_json)  # init predictions api
600
            eval = COCOeval(anno, pred, "bbox")
601
            if is_coco:
602
                eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files]  # image IDs to evaluate
603
            eval.evaluate()
604
            eval.accumulate()
605
            eval.summarize()
606
            map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)
607
        except Exception as e:
608
            LOGGER.info(f"pycocotools unable to run: {e}")
609
610
    # Return results
611
    model.float()  # for training
612
    if not training:
613
        s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ""
614
        LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
615
    maps = np.zeros(nc) + map
616
    for i, c in enumerate(ap_class):
617
        maps[c] = ap[i]
618
    return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
619
620
621
def parse_opt():
622
    parser = argparse.ArgumentParser()
623
    parser.add_argument('--data', type=str, default=ROOT / 'data/WBC_v1.yaml', help='(optional) dataset.yaml path')
624
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'runs/train/yolov5x_300Epochs_training/weights/best.pt', help='model path or triton URL')
625
    parser.add_argument("--batch-size", type=int, default=8, help="batch size")
626
    parser.add_argument("--imgsz", "--img", "--img-size", type=int, default=640, help="inference size (pixels)")
627
    parser.add_argument("--conf-thres", type=float, default=0.001, help="confidence threshold")
628
    parser.add_argument("--iou-thres", type=float, default=0.6, help="NMS IoU threshold")
629
    parser.add_argument("--max-det", type=int, default=300, help="maximum detections per image")
630
    parser.add_argument("--task", default="test", help="train, val, test, speed or study")
631
    parser.add_argument("--device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
632
    parser.add_argument("--workers", type=int, default=8, help="max dataloader workers (per RANK in DDP mode)")
633
    parser.add_argument("--single-cls", action="store_true", help="treat as single-class dataset")
634
    parser.add_argument("--augment", action="store_true", help="augmented inference")
635
    parser.add_argument("--verbose", action="store_true", help="report mAP by class")
636
    parser.add_argument("--save-txt", action="store_true", help="save results to *.txt")
637
    parser.add_argument("--save-hybrid", action="store_true", help="save label+prediction hybrid results to *.txt")
638
    parser.add_argument("--save-conf", action="store_true", help="save confidences in --save-txt labels")
639
    parser.add_argument("--save-json", action="store_true", help="save a COCO-JSON results file")
640
    parser.add_argument("--project", default=ROOT / "runs/val", help="save to project/name")
641
    parser.add_argument("--name", default="exp", help="save to project/name")
642
    parser.add_argument("--exist-ok", action="store_true", help="existing project/name ok, do not increment")
643
    parser.add_argument("--half", action="store_true", help="use FP16 half-precision inference")
644
    parser.add_argument("--dnn", action="store_true", help="use OpenCV DNN for ONNX inference")
645
646
    opt = parser.parse_args()
647
    opt.data = check_yaml(opt.data)  # check YAML
648
    opt.save_json |= opt.data.endswith("coco.yaml")
649
    opt.save_txt |= opt.save_hybrid
650
    print_args(vars(opt))
651
    return opt
652
653
654
def main(opt):
655
    check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))
656
657
    if opt.task in ("train", "val", "test"):  # run normally
658
        if opt.conf_thres > 0.001:  # https://github.com/ultralytics/yolov5/issues/1466
659
            LOGGER.info(f"WARNING ⚠️ confidence threshold {opt.conf_thres} > 0.001 produces invalid results")
660
        if opt.save_hybrid:
661
            LOGGER.info("WARNING ⚠️ --save-hybrid will return high mAP from hybrid labels, not from predictions alone")
662
        run(**vars(opt))
663
664
    else:
665
        weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
666
        opt.half = torch.cuda.is_available() and opt.device != "cpu"  # FP16 for fastest results
667
        if opt.task == "speed":  # speed benchmarks
668
            # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
669
            opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
670
            for opt.weights in weights:
671
                run(**vars(opt), plots=False)
672
673
        elif opt.task == "study":  # speed vs mAP benchmarks
674
            # python val.py --task study --data coco.yaml --iou 0.7 --weights yolov5n.pt yolov5s.pt...
675
            for opt.weights in weights:
676
                f = f"study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt"  # filename to save to
677
                x, y = list(range(256, 1536 + 128, 128)), []  # x axis (image sizes), y axis
678
                for opt.imgsz in x:  # img-size
679
                    LOGGER.info(f"\nRunning {f} --imgsz {opt.imgsz}...")
680
                    r, _, t = run(**vars(opt), plots=False)
681
                    y.append(r + t)  # results and times
682
                np.savetxt(f, y, fmt="%10.4g")  # save
683
            subprocess.run(["zip", "-r", "study.zip", "study_*.txt"])
684
            plot_val_study(x=x)  # plot
685
        else:
686
            raise NotImplementedError(f'--task {opt.task} not in ("train", "val", "test", "speed", "study")')
687
688
689
if __name__ == "__main__":
690
    opt = parse_opt()
691
    main(opt)