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