{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from ultralytics import YOLO\n", "#model = YOLO('yolov8x-segreg.yaml')\n", "model = YOLO('/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/runs/segment/train718/weights/best.pt') ## Pretrained model nano + design variable classic style trained. Dice loss with classic variables\n", "#model = YOLO('/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/runs/segment/yolov8x-segreg3/weights/best.pt')" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def freeze_except_regression_head(trainer):\n", " model = trainer.model\n", " print(\"Freezing layers except those with 'regression_head'\")\n", " for k, v in model.named_parameters(): \n", " if 'regression_head' not in k:\n", " print(f'freezing {k}')\n", " v.requires_grad = False \n", " else:\n", " v.requires_grad = True\n", " print(\"Layers with 'regression_head' are unfrozen.\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": true, "tags": [] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "New https://pypi.org/project/ultralytics/8.1.20 available 😃 Update with 'pip install -U ultralytics'\n", "Ultralytics YOLOv8.0.166 🚀 Python-3.11.7 torch-2.0.0+cu118 CUDA:0 (NVIDIA GeForce RTX 3090, 24268MiB)\n", "\u001b[34m\u001b[1mengine/trainer: \u001b[0mtask=segment, mode=train, model=yolov8n-segreg.yaml, data=/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/data.yaml, epochs=1000, patience=100, batch=6, imgsz=640, save=True, save_period=-1, cache=ram, device=None, workers=12, project=None, name=None, exist_ok=False, pretrained=False, optimizer=AdamW, verbose=True, seed=0, deterministic=True, single_cls=True, rect=False, cos_lr=True, close_mosaic=10, resume=None, amp=True, fraction=1.0, profile=False, freeze=None, overlap_mask=False, mask_ratio=1, dropout=0.0, val=True, split=val, save_json=False, save_hybrid=False, conf=None, iou=0.7, max_det=300, half=False, dnn=False, plots=True, source=None, show=False, save_txt=False, save_conf=False, save_crop=False, show_labels=True, show_conf=True, vid_stride=1, stream_buffer=False, line_width=None, visualize=False, augment=False, agnostic_nms=False, classes=None, retina_masks=False, boxes=True, format=torchscript, keras=False, optimize=False, int8=False, dynamic=False, simplify=False, opset=None, workspace=4, nms=False, lr0=0.0003, lrf=0.01, momentum=0.937, weight_decay=0.01, warmup_epochs=0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=7.5, cls=0.5, dfl=1.5, pose=12.0, reg_gain=1.0, kobj=1.0, label_smoothing=0.0, nbs=64, hsv_h=0.5, hsv_s=0.5, hsv_v=0.5, degrees=25.0, translate=0.2, scale=0.75, shear=10.0, perspective=0.001, flipud=0.5, fliplr=0.5, mosaic=1.0, mixup=0.0, copy_paste=0.0, cfg=None, tracker=botsort.yaml, save_dir=runs/segment/train727\n", "Overriding model.yaml nc=80 with nc=1\n", "\n", " from n params module arguments \n", " 0 -1 1 464 ultralytics.nn.modules.conv.Conv [3, 16, 3, 2] \n", " 1 -1 1 4672 ultralytics.nn.modules.conv.Conv [16, 32, 3, 2] \n", " 2 -1 1 7360 ultralytics.nn.modules.block.C2f [32, 32, 1, True] \n", " 3 -1 1 18560 ultralytics.nn.modules.conv.Conv [32, 64, 3, 2] \n", " 4 -1 2 49664 ultralytics.nn.modules.block.C2f [64, 64, 2, True] \n", " 5 -1 1 73984 ultralytics.nn.modules.conv.Conv [64, 128, 3, 2] \n", " 6 -1 2 197632 ultralytics.nn.modules.block.C2f [128, 128, 2, True] \n", " 7 -1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2] \n", " 8 -1 1 460288 ultralytics.nn.modules.block.C2f [256, 256, 1, True] \n", " 9 -1 1 164608 ultralytics.nn.modules.block.SPPF [256, 256, 5] \n", " 10 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", " 11 [-1, 6] 1 0 ultralytics.nn.modules.conv.Concat [1] \n", " 12 -1 1 148224 ultralytics.nn.modules.block.C2f [384, 128, 1] \n", " 13 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n", " 14 [-1, 4] 1 0 ultralytics.nn.modules.conv.Concat [1] \n", " 15 -1 1 37248 ultralytics.nn.modules.block.C2f [192, 64, 1] \n", " 16 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2] \n", " 17 [-1, 12] 1 0 ultralytics.nn.modules.conv.Concat [1] \n", " 18 -1 1 123648 ultralytics.nn.modules.block.C2f [192, 128, 1] \n", " 19 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2] \n", " 20 [-1, 9] 1 0 ultralytics.nn.modules.conv.Concat [1] \n", " 21 -1 1 493056 ultralytics.nn.modules.block.C2f [384, 256, 1] \n", " 22 [15, 18, 21] 1 2439490 ultralytics.nn.modules.head.ExtendedSegment [1, 32, 256, [64, 128, 256]] \n", "YOLOv8n-segreg summary: 280 layers, 4699026 parameters, 4699010 gradients\n", "\n", "Freezing layer 'model.22.dfl.conv.weight'\n", "\u001b[34m\u001b[1mAMP: \u001b[0mrunning Automatic Mixed Precision (AMP) checks with YOLOv8n...\n", "\u001b[34m\u001b[1mAMP: \u001b[0mchecks passed ✅\n", "\u001b[34m\u001b[1mtrain: \u001b[0mScanning /scratch/thomas/yolov8_dataset/dataset_combined_feb2/train.cache... 16620 images, 0 backgrounds, 0 corrupt: 100%|██████████| 16620/16620 [00:00 2\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrain\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/data.yaml\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[43mmosaic\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1.0\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43mhsv_h\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image HSV-Hue augmentation (fraction)\u001b[39;49;00m\n\u001b[1;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43mhsv_s\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image HSV-Saturation augmentation (fraction)\u001b[39;49;00m\n\u001b[1;32m 6\u001b[0m \u001b[43m \u001b[49m\u001b[43mhsv_v\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image HSV-Value augmentation (fraction)\u001b[39;49;00m\n\u001b[1;32m 7\u001b[0m \u001b[43m \u001b[49m\u001b[43mdegrees\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m25.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image rotation (+/- deg)\u001b[39;49;00m\n\u001b[1;32m 8\u001b[0m \u001b[43m \u001b[49m\u001b[43mtranslate\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image translation (+/- fraction)\u001b[39;49;00m\n\u001b[1;32m 9\u001b[0m \u001b[43m \u001b[49m\u001b[43mscale\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.75\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image scale (+/- gain)\u001b[39;49;00m\n\u001b[1;32m 10\u001b[0m \u001b[43m \u001b[49m\u001b[43mshear\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m10.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image shear (+/- deg)\u001b[39;49;00m\n\u001b[1;32m 11\u001b[0m \u001b[43m \u001b[49m\u001b[43mperspective\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.001\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image perspective (+/- fraction), range 0-0.001\u001b[39;49;00m\n\u001b[1;32m 12\u001b[0m \u001b[43m \u001b[49m\u001b[43mflipud\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# (float) image flip up-down (probability)\u001b[39;49;00m\n\u001b[1;32m 13\u001b[0m \u001b[43m \u001b[49m\u001b[43mfliplr\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.5\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 14\u001b[0m \u001b[43m \u001b[49m\u001b[43mepochs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m 15\u001b[0m \u001b[43m \u001b[49m\u001b[43mbatch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m6\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 16\u001b[0m \u001b[43m \u001b[49m\u001b[43mreg_gain\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1.0\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 17\u001b[0m \u001b[43m \u001b[49m\u001b[43mamp\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 18\u001b[0m \u001b[43m \u001b[49m\u001b[43mwarmup_epochs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 19\u001b[0m \u001b[43m \u001b[49m\u001b[43mimgsz\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m640\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 20\u001b[0m \u001b[43m \u001b[49m\u001b[43mworkers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m12\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 21\u001b[0m \u001b[43m \u001b[49m\u001b[43mlr0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m3e-4\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 22\u001b[0m \u001b[43m \u001b[49m\u001b[43mcache\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mram\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 23\u001b[0m \u001b[43m \u001b[49m\u001b[43mcos_lr\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 24\u001b[0m \u001b[43m \u001b[49m\u001b[43msingle_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 25\u001b[0m \u001b[43m \u001b[49m\u001b[43mrect\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 26\u001b[0m \u001b[43m \u001b[49m\u001b[43moverlap_mask\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 27\u001b[0m \u001b[43m \u001b[49m\u001b[43mmask_ratio\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 28\u001b[0m \u001b[43m \u001b[49m\u001b[43moptimizer\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mAdamW\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 29\u001b[0m \u001b[43m \u001b[49m\u001b[43mpretrained\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 30\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatience\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m100\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 31\u001b[0m \u001b[43m \u001b[49m\u001b[43mweight_decay\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1e-2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m 32\u001b[0m \u001b[43m \u001b[49m\u001b[43mval\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 33\u001b[0m \u001b[43m \u001b[49m\u001b[43mresume\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 34\u001b[0m \u001b[43m \u001b[49m\u001b[43mplots\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\n\u001b[1;32m 35\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/model.py:341\u001b[0m, in \u001b[0;36mModel.train\u001b[0;34m(self, trainer, **kwargs)\u001b[0m\n\u001b[1;32m 339\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtrainer\u001b[38;5;241m.\u001b[39mmodel\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtrainer\u001b[38;5;241m.\u001b[39mhub_session \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msession \u001b[38;5;66;03m# attach optional HUB session\u001b[39;00m\n\u001b[0;32m--> 341\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrainer\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrain\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 342\u001b[0m \u001b[38;5;66;03m# Update model and cfg after training\u001b[39;00m\n\u001b[1;32m 343\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m RANK \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m0\u001b[39m):\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/trainer.py:207\u001b[0m, in \u001b[0;36mBaseTrainer.train\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 204\u001b[0m ddp_cleanup(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28mstr\u001b[39m(file))\n\u001b[1;32m 206\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 207\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_do_train\u001b[49m\u001b[43m(\u001b[49m\u001b[43mworld_size\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/trainer.py:368\u001b[0m, in \u001b[0;36mBaseTrainer._do_train\u001b[0;34m(self, world_size)\u001b[0m\n\u001b[1;32m 364\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtloss \u001b[38;5;241m=\u001b[39m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtloss \u001b[38;5;241m*\u001b[39m i \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mloss_items) \u001b[38;5;241m/\u001b[39m (i \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m) \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtloss \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \\\n\u001b[1;32m 365\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mloss_items\n\u001b[1;32m 367\u001b[0m \u001b[38;5;66;03m# Backward\u001b[39;00m\n\u001b[0;32m--> 368\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mscaler\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mscale\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloss\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 370\u001b[0m \u001b[38;5;66;03m# Optimize - https://pytorch.org/docs/master/notes/amp_examples.html\u001b[39;00m\n\u001b[1;32m 371\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ni \u001b[38;5;241m-\u001b[39m last_opt_step \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maccumulate:\n", "File \u001b[0;32m~/anaconda3/envs/customyolo/lib/python3.11/site-packages/torch/_tensor.py:487\u001b[0m, in \u001b[0;36mTensor.backward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 477\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m has_torch_function_unary(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 478\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m handle_torch_function(\n\u001b[1;32m 479\u001b[0m Tensor\u001b[38;5;241m.\u001b[39mbackward,\n\u001b[1;32m 480\u001b[0m (\u001b[38;5;28mself\u001b[39m,),\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 485\u001b[0m inputs\u001b[38;5;241m=\u001b[39minputs,\n\u001b[1;32m 486\u001b[0m )\n\u001b[0;32m--> 487\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mautograd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 488\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgradient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minputs\u001b[49m\n\u001b[1;32m 489\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m~/anaconda3/envs/customyolo/lib/python3.11/site-packages/torch/autograd/__init__.py:200\u001b[0m, in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 195\u001b[0m retain_graph \u001b[38;5;241m=\u001b[39m create_graph\n\u001b[1;32m 197\u001b[0m \u001b[38;5;66;03m# The reason we repeat same the comment below is that\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;66;03m# some Python versions print out the first line of a multi-line function\u001b[39;00m\n\u001b[1;32m 199\u001b[0m \u001b[38;5;66;03m# calls in the traceback and some print out the last line\u001b[39;00m\n\u001b[0;32m--> 200\u001b[0m \u001b[43mVariable\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_execution_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun_backward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Calls into the C++ engine to run the backward pass\u001b[39;49;00m\n\u001b[1;32m 201\u001b[0m \u001b[43m \u001b[49m\u001b[43mtensors\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgrad_tensors_\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 202\u001b[0m \u001b[43m \u001b[49m\u001b[43mallow_unreachable\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maccumulate_grad\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ], "source": [ "\n", "# model.add_callback(\"on_train_start\", freeze_except_regression_head)\n", "model.train(data = '/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/data.yaml', \n", " mosaic = 1.0,\n", " hsv_h= 0.5, # (float) image HSV-Hue augmentation (fraction)\n", " hsv_s= 0.5, # (float) image HSV-Saturation augmentation (fraction)\n", " hsv_v= 0.5, # (float) image HSV-Value augmentation (fraction)\n", " degrees= 25.0, # (float) image rotation (+/- deg)\n", " translate= 0.2, # (float) image translation (+/- fraction)\n", " scale= 0.75, # (float) image scale (+/- gain)\n", " shear= 10.0, # (float) image shear (+/- deg)\n", " perspective= 0.001, # (float) image perspective (+/- fraction), range 0-0.001\n", " flipud= 0.5, # (float) image flip up-down (probability)\n", " fliplr= 0.5,\n", " epochs=1000, \n", " batch=6,\n", " reg_gain = 1.0,\n", " amp = True,\n", " warmup_epochs=0,\n", " imgsz=int(640),\n", " workers=12,\n", " lr0=3e-4,\n", " cache = \"ram\",\n", " cos_lr = True,\n", " single_cls=True,\n", " rect=False,\n", " overlap_mask=False,\n", " mask_ratio=1,\n", " optimizer = \"AdamW\",\n", " pretrained=False,\n", " patience=100,\n", " weight_decay=1e-2, \n", " val=True,\n", " resume=True,\n", " plots=True\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model.val(data = '/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/data.yaml',imgsz=640)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# I want to test the A.ElasticTransform from albumentations on the /home/thomas/Documents/GitHub/ultralytics-custom/ultralytics/Screenshot 2023-11-22 at 10.00.19 AM.png\n", "\n", "import albumentations as A\n", "import cv2\n", "\n", "image = cv2.imread(\"/home/thomas/Documents/GitHub/ultralytics-custom/ultralytics/images (6).png\")\n", "\n", "transform = A.ElasticTransform(p=1,sigma=25,alpha=400,alpha_affine=0,border_mode=0,value=[255,255,255],approximate=True,same_dxdy=True)#num_steps=30,interpolation = cv2.INTER_CUBIC, distort_limit = 0.7, border_mode=0,normalized=True,value=[255,255,255])\n", "transform = A.HueSaturationValue (hue_shift_limit=50, sat_shift_limit=50, val_shift_limit=50, always_apply=False, p=1.0)\n", "transformed = transform(image=image)\n", "\n", "transformed_image = transformed[\"image\"]\n", "\n", "#plot the two images side by side:\n", "import matplotlib.pyplot as plt\n", "\n", "f, axarr = plt.subplots(1,2)\n", "axarr[0].imshow(image)\n", "axarr[1].imshow(transformed_image)\n", "#dont show axis:\n", "axarr[0].axis('off')\n", "axarr[1].axis('off')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Show the results\n", "from PIL import Image\n", "results = model(\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/porous_right_topo_padded.png\", conf=0.05, iou=0.9, imgsz=640)\n", "\n", "for r in results:\n", " im_array = r.plot(boxes=True,labels=False,line_width=1) # plot a BGR numpy array of predictions\n", " im = Image.fromarray(im_array[..., ::-1]) # RGB PIL image\n", " im.show() # show image\n", " im.save('results.jpg') # save image\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Load masks.npy:\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "masks = np.load(\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/masks.npy\")\n", "\n", "plt.imshow(masks[10])" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Image size: (760, 602)\n" ] } ], "source": [ "# Show the results\n", "from PIL import Image\n", "from pillow_heif import register_heif_opener\n", "\n", "from PIL import Image, ImageOps\n", "import math\n", "\n", "# Load the image\n", "# image_path = '/home/thomas/Documents/GitHub/ultralytics-custom/ultralytics/dataset_simp_nov28/20231121-055317-649044.png'\n", "\n", "image_path = '/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/porous_right_topo_padded.png'\n", "#image_path = '/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/compmech_topo_padded.png'\n", "#image_path = '/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/porous_left_topo_padded.png'\n", "image_path = \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/levelset_20_topo_padded.png\"\n", "#image_path = \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/Screenshot 2024-03-07 at 5.04.07 PM.png\"\n", "#image_path = '/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/test/20231120-134257-113740.png'\n", "#image_path = \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_simp_nov28/20231121-060417-268796.png\"\n", "# image_path = '/home/thomas/Documents/GitHub/ultralytics-custom/ultralytics/Screenshot 2023-10-24 at 12.22.18 PM.png'\n", "image_path = \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/test_handdrawing.jpeg\"\n", "image_path = \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/Screenshot 2023-12-04 at 11.32.41 AM.png\"\n", "image = Image.open(image_path)\n", "\n", "register_heif_opener()\n", "\n", "#image = Image.open('/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/IMG_6475.HEIC')\n", "\n", "#Threshold the image pixels to 0 and 255\n", "\n", "# Print the size of the image\n", "print(f\"Image size: {image.size}\")\n", "\n", "\n", "# # Function to calculate padding required to make width or height a multiple of 'stride'\n", "# def calculate_padding(current, stride):\n", "# return (stride - (current % stride)) % stride\n", "\n", "# # Get current dimensions\n", "# width, height = image.size\n", "\n", "# # Calculate required padding\n", "# padding_right = calculate_padding(width, 32)\n", "# padding_bottom = calculate_padding(height, 32)\n", "\n", "# # Note: ImageOps.expand adds padding equally to all sides, so we double the right and bottom padding and then crop\n", "# padded_image = ImageOps.expand(image, border=(0, 0, padding_right * 2, padding_bottom * 2), fill='white')\n", "# padded_image = padded_image.crop((0, 0, width + padding_right, height + padding_bottom))\n", "\n", "# print(\"Image size:\", padded_image.size)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "# Convert the image to grayscale\n", "gray_image = image.convert('L')\n", "# Set a threshold value\n", "threshold_value = 0.4*255# You can adjust this value\n", "# Apply thresholding\n", "binary_image = gray_image.point(lambda x: 255 if x > threshold_value else 0, '1')\n", "# Print the size of the image\n", "plt.imshow(np.array(binary_image),cmap = 'gray')\n", "plt.colorbar()\n" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Upscaled image size: (760, 602)\n" ] } ], "source": [ "# Upscale the image by 2x:\n", "\n", "# Upscale the image by 2x\n", "upscale_factor = 1.0\n", "upscaled_image = binary_image.resize(\n", " (int(image.width * upscale_factor), int(image.height * upscale_factor)),\n", " resample=Image.BICUBIC\n", ")\n", "# upscaled_image = up\n", "\n", "# Print the size of the upscaled image\n", "print(f\"Upscaled image size: {upscaled_image.size}\")\n", "#show the upscaled image:\n", "#upscaled_image.save(\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/binary_image.png\")" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\n", "0: 512x640 24 mmc_components, 132.4ms\n", "Speed: 17.3ms preprocess, 132.4ms inference, 258.2ms postprocess per image at shape (1, 3, 512, 640)\n" ] } ], "source": [ "\n", "# Process the image with your model\n", "#results = model(image, conf=0.05,iou=1.0,imgsz = 640,max_det = 2000)\n", "#results = model(upscaled_image, conf= 0.05, iou=0.3, imgsz=(upscaled_image.height, upscaled_image.width),max_det=500)\n", "results = model(upscaled_image, conf=0.05,iou=0.5,imgsz =640)\n", "\n", "for r in results:\n", " im_array = r.plot(boxes=True,labels=False,line_width=1) # plot a BGR numpy array of predictions\n", " im = Image.fromarray(im_array[..., ::-1]) # RGB PIL image\n", " im.show() # show image\n", " im.save('results.jpg') # save image\n", "\n", "prediction_tensor = results[0].regression_preds.to('cpu').detach()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## Inference code:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import numpy as np\n", "import torch.nn.functional as F\n", "import torch.nn as nn\n", "class CustomTverskyLoss(nn.Module):\n", " def __init__(self, alpha=0.1, beta=0.9, size_average=True):\n", " super(CustomTverskyLoss, self).__init__()\n", " self.alpha = alpha\n", " self.beta = beta\n", " self.size_average = size_average\n", "\n", " def forward(self, inputs, targets, smooth=1):\n", " # If your model contains a sigmoid or equivalent activation layer, comment this line\n", " # inputs = F.sigmoid(inputs)\n", "\n", " # Check if the input tensors are of expected shape\n", " if inputs.shape != targets.shape:\n", " raise ValueError(\"Shape mismatch: inputs and targets must have the same shape\")\n", "\n", " # Compute Tversky loss for each sample in the batch\n", " tversky_loss_values = []\n", " for input_sample, target_sample in zip(inputs, targets):\n", " # Flatten tensors for each sample\n", " input_sample = input_sample.view(-1)\n", " target_sample = target_sample.view(-1)\n", "\n", " # Calculate the true positives, false positives, and false negatives\n", " true_positives = (input_sample * target_sample).sum()\n", " false_positives = (input_sample * (1 - target_sample)).sum()\n", " false_negatives = ((1 - input_sample) * target_sample).sum()\n", "\n", " # Compute the Tversky index for each sample\n", " tversky_index = (true_positives + smooth) / (true_positives + self.alpha * false_positives + self.beta * false_negatives + smooth)\n", "\n", " tversky_loss_values.append(1 - tversky_index)\n", "\n", " # Convert list of Tversky loss values to a tensor\n", " tversky_loss_values = torch.stack(tversky_loss_values)\n", "\n", " # If you want the average loss over the batch to be returned\n", " if self.size_average:\n", " return tversky_loss_values.mean()\n", " else:\n", " # If you want individual losses for each sample in the batch\n", " return tversky_loss_values\n", "\n", "class CustomDiceLoss(nn.Module):\n", " def __init__(self, weight=None, size_average=True):\n", " super(CustomDiceLoss, self).__init__()\n", " self.size_average = size_average\n", " def forward(self, inputs, targets, smooth=1):\n", " \n", " # If your model contains a sigmoid or equivalent activation layer, comment this line\n", " #inputs = F.sigmoid(inputs) \n", " \n", " # Check if the input tensors are of expected shape\n", " if inputs.shape != targets.shape:\n", " raise ValueError(\"Shape mismatch: inputs and targets must have the same shape\")\n", "\n", " # Compute Dice loss for each sample in the batch\n", " dice_loss_values = []\n", " for input_sample, target_sample in zip(inputs, targets):\n", " \n", " # Flatten tensors for each sample\n", " input_sample = input_sample.view(-1)\n", " target_sample = target_sample.view(-1)\n", "\n", " intersection = (input_sample * target_sample).sum()\n", " dice = (2. * intersection + smooth) / (input_sample.sum() + target_sample.sum() + smooth)\n", " \n", " dice_loss_values.append(1 - dice)\n", "\n", " # Convert list of Dice loss values to a tensor\n", " dice_loss_values = torch.stack(dice_loss_values)\n", "\n", " # If you want the average loss over the batch to be returned\n", " if self.size_average:\n", " return dice_loss_values.mean()\n", " else:\n", " # If you want individual losses for each sample in the batch\n", " return dice_loss_values\n", "\n", "def smooth_heaviside(phi, alpha, epsilon):\n", " # Scale and shift phi for the sigmoid function\n", " scaled_phi = (phi - alpha) / epsilon\n", " \n", " # Apply the sigmoid function\n", " H = torch.sigmoid(scaled_phi)\n", "\n", " return H\n", "def calc_Phi(variable, LSgrid):\n", " device = variable.device # Get the device of the variable\n", "\n", " x0 = variable[0]\n", " y0 = variable[1]\n", " L = variable[2]\n", " t = variable[3] # Constant thickness\n", " angle = variable[4]\n", "\n", " # Rotation\n", " st = torch.sin(angle)\n", " ct = torch.cos(angle)\n", " x1 = ct * (LSgrid[0][:, None].to(device) - x0) + st * (LSgrid[1][:, None].to(device) - y0) \n", " y1 = -st * (LSgrid[0][:, None].to(device) - x0) + ct * (LSgrid[1][:, None].to(device) - y0)\n", "\n", " # Regularized hyperellipse equation\n", " a = L / 2 # Semi-major axis\n", " b = t / 2 # Constant semi-minor axis\n", " small_constant = 1e-9 # To avoid division by zero\n", " temp = ((x1 / (a + small_constant))**6) + ((y1 / (b + small_constant))**6)\n", "\n", " # # Ensuring the hyperellipse shape\n", " allPhi = 1 - (temp + small_constant)**(1/6)\n", " \n", " # # Call Heaviside function with allPhi\n", " alpha = torch.tensor(0.0, device=device, dtype=torch.float32)\n", " epsilon = torch.tensor(0.001, device=device, dtype=torch.float32)\n", " H_phi = smooth_heaviside(allPhi, alpha, epsilon)\n", " return allPhi, H_phi" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "image/png": "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zRpx7tw5MXnyZOHu7q4zotlaTZ06Vbi5uYn9+/eLu3fvah95eXnabV544QXRpEkTsXfvXnH8+HEREhIiQkJCtOtVKpUICgoS/fv3F6dOnRI7d+4Unp6eYvbs2VLsUp1w/yh7IXgMq+LYsWPC1tZWzJ8/X1y6dEl8//33wtHRUaxfv167zUcffSTc3d3Fb7/9Jk6fPi2eeOIJERAQIPLz87XbDBgwQHTs2FHExMSIQ4cOiRYtWogxY8ZIsUtERFSLzC6QCiHEkiVLRJMmTYRCoRBdu3YVR48elbqkOgGA3sfq1au12+Tn54sXX3xR1KtXTzg6Oophw4aJu3fv6rzP9evXxcCBA4WDg4No0KCBePXVV0VxcbGJ96bueDCQ8hhWzdatW0VQUJBQKpWiVatWYtWqVTrrNRqNeOedd4SXl5dQKpWiX79+IiEhQWebe/fuiTFjxghnZ2fh6uoqJkyYILKzs025G0REZAIyIYSQsoeWiIiIiKybWV1DSkRERESWh4GUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZCS0TVt2hTjx4/XPt+/fz9kMhn2799vtM+QyWR49913jfZ+dZFU+/juu+9CJpOZ/HOJ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rVpVZt1bb72FDz74QIKqjK+oqAhz587FwoUL9faUubm54aeffkL//v0lqK52JSQk4JFHHtEbSJVKJbZu3YrHHntMgsqMIykpCd27d8f169f1rre1tcV3332H0aNHm7awB5iyfbu/TQNgte1bhafmLaQHtaqB01J7VC09kMqASsOoOe+foQzJazW6hrS08SydoiUuLg7FxcU6d41p1aoVmjRpgujoaABAdHQ02rZtq22sASA8PBxZWVk4e/as3s8pLCxEVlaWzsNayWQy9OzZU2/vUUJCAoqLiyWoyvgUCgXeeOMNdOnSRe/6zMxMPP3002Y1mKsq8vLy8M4775TbO9q1a1f07NnTxFUZV2ZmJu7evVvu+nbt2mHQoEEmrEg/U7dvAKy+fauQmVxHWhlrvp7U0sMo1Uy1A6lGo8GMGTPQo0cPBAUFASiZnkehUMDd3V1nWy8vL+0UL4mJiTqNden60nX6LFiwAG5ubtqHn59fdcu2CF27doWLi0uZ5bGxsSgsLJSgotrh7u6O77//Hh06dNC7PiUlBc8++yzi4uJMW1gt2rNnD37//fdy1584cQI7duwwYUXGV9lJmZdeeknynkFTt2+lbZqfn591t28W0gtaGWsOpffbhz5lHmaPp+qrrdqBNDIyEmfOnMHGjRuNWY9es2fPRmZmpvZx8+bNWv/Muqxly5Zo165dmeWJiYkVDhYxR82aNcMvv/yid3+BkgnW3377baSmppq4MuMrKCjAZ599VuGXitzcXCxevNisB3X9/vvv5e5jhw4dMGzYMBNXVJap27fSNu3mzZtW375ZC2sLpff3jlYUPvfvN9+ecPYA10y1Aum0adOwbds27Nu3T2eKHm9vbxQVFSEjI0Nn+6SkJO3deby9vcuMSi19ru8OPkDJdXOurq46D2smk8n03ka0sLAQ6enpElRUux566CH897//hYODg971O3fuxIQJE3Dv3j0TV2ZcUVFROHz4cKXbHT9+vNxpk+q648eP47PPPtO7ztnZGQsXLpT891uq9g0A2zcrY22h1GJ6QalWGBRIhRCYNm0afv31V+zduxcBAQE664ODg2FnZ4c9e/ZolyUkJODGjRsICQkBAISEhCA+Pl7nVpdRUVFwdXVFYGBgTfbFqgwYMAD29vZllt9/7C1Bbm4upk6diq+//hp2dnblbrdt2zZMnz7dLO95n5eXh59//hnTpk2r0jXAHTp0gL+/vwkqMy6NRoPPPvus3FPXQ4YMQaiEvQds38jkNsjweYsZ2oel2r9fZvFBlL2jNWfQKPsXX3wRGzZswG+//aZzm0c3Nzdt79XUqVOxfft2rFmzBq6urpg+fToA4MiRIwBKpkXp0KEDfH19sXDhQiQmJuKZZ57BpEmT8OGHH1apDmseZV8qOzsbXbp0KTNPYp8+fbB3716JqjK+Y8eOoWfPnlUKavb29vjiiy8wadIkyOV1/54PN2/exIYNG7BlyxYcP3680rkq69Wrh6eeegpvvPGGWV5nuHHjRjz33HN6b2zg5OSEffv2lTuIzRSkbN84yv4BVjDaHkCVBmq9cmmR9v/NbeR9dU+/h4aa38+4skBqrWHUkLymf/bxcixfvhwAyvRirF69GuPHjwcAfP7555DL5RgxYgQKCwsRHh6OZcuWabe1sbHBtm3bMHXqVISEhMDJyQnjxo3De++9Z0gpVs/FxQURERFlAqml2b59e5VnDigoKMCMGTOQkZGB8PBwtGrVCgqFwuTzWVZECIEbN27g22+/xTfffIPbt29X+hp7e3sMGTIE77zzDtq0aWMWYftBWVlZ+Pjjj8u9y9bjjz+O4OBgE1eli+0bmVQVZw3Q6TndMMMsArk1BVGAE90bS43mIZUKe0hLxMXFITQ0FDk5OQBKelVeeukli/rjN378eKxdu9bg17m5ucHb2xsDBgxAy5YtMWjQIHh4eEj67+XGjRv46quv8M0331Q47dH9evbsifnz56Nbt25mfX/3t99+GwsWLCj3kooNGzZgzJgxJq6q7mAP6QOsoYfUWNNY1ZHjUdPBSJYcRq21dxSoxR5Sqls6duyIRYsWYdeuXXj88cfRpk0btG3bFkDJtZepqalo3LgxbGxsJK60+oYPH44dO3YgJSXFoHubl87IkJCQAJlMBmdnZ7Ro0QKtW7fGqFGj4O/vjzZt2sDW1rZWe1BVKhViY2Px008/YdOmTVXqES3l6uqKRYsWSd5zWFOXL1/GqlWrKry+ty71YlMd8JTQH9jqSPiqUx48TiY+RtYaRMn42ENqgTIzMzFhwgRER0ejdevWmDVrFsLDw83yVC8AXL9+Hbt27cKOHTtw+PBho0zx5OzsjKZNmyI0NBRBQUEYMGAAvL29jdYLWVRUhMOHD2PRokX4888/DZ6mSSaT4YUXXsDSpUvNOqwVFBRgwoQJFU6f5O3tjePHj6NRo0YmrKxuYQ9pOUrD1n0hSzZP9/dBzDW7P2H/U9uT/ddiOK1pEO2DfWbfc1ilU/WhoTDjf6E1ZkheYyC1MEIIfPrpp5g9e7Z2kIyLiwueffZZvPfee9q7zpijoqIi3L17F3v27MHOnTsRHx+PhIQEg3pOy+Pu7o6QkBC0bt0aI0aMgL+/P3x8fHS2EULg5s2b5Y4SL5WVlYVly5Zh165dVQqi7u7uaNWqFS5fvqwN261atcLBgwfRoEGD6u9UHbB9+3aMGDGiwuPg5+eHM2fOWPXvMgNp1T0YSAEzD6WA6e5CZYSAWpMg2gf7tP9v7mEUYCCtCgZSK/b333+jV69eem8/GBQUhE8//RR9+vSpcAolcyCEQGZmJq5cuYJdu3ZhyZIllQZFQzRu3BgNGzYss/zOnTtG/Ryg5E4+x44dQ2FhIb7++mukp6dj6tSp6Nixo1E/x9QKCwsxcOBA7Nu3r8Lt+vTpg+3bt+udxsxaMJBWjb4wej+zD6aAaW+Ruqbqm+7/b/U/5v4gWsrcA2lVwygABlJeQ2qdPDw84OPjozeQnjlzBsOHD8fkyZMxa9asMj2A5kQmk8Hd3R3BwcEIDg5Gt27dMHbs2DKTklfXrVu3cOvWLaO8V2UyMzNRVFSEFi1a4P/+7/9M8pmmsHTpUhw8eLDS7bp27WrVYZSMRzZPZv6h9MFezNoMqOP//e+a8jepSRAN/XemM/FnqE6As6YwSlVnnhcVUrn8/Pzw3nvv6b3XPVAy2Onzzz9HeHg4tm/fbpYTyevTr18/bNiwQec+4uHh4diyZQs8PT0lrMw63b17F0uWLKl0blU7OzsMGjTIRFWRNZDNk1Xak2pWnhJ45dIi7aNWjNe/uLphNPTD/4XRUiI0VPuwJmb+9cikGEgt0MiRI7Fp0yYEBQWVu018fDxGjhyJpUuXak8Tmru+ffti8+bNGDZsGMaMGYPvv/8eoaGhUCgUUpemV6NGjdCmTRu8/PLLei8PMFdqtRpvvfUWrl+/Xum2zZo1q/DfKdH9DOn9tKRQev+E+DrhdE3tfWZ1wqi+IGpp2Dtae3gNqQVLSEjAk08+ib///rvcbWQyGQYMGIBVq1bp3LfbnKnVagAlk5Tn5+dj8ODBOHbsGNRqtd6J2RUKhTa0lreNg4ODdvosJycndO/eHXZ2diguLsaePXu0c8GWcnFxQc+ePeHs7IyuXbvqvR60ZcuW8Pb2rvWpp0zt2LFj6Nu3L3Jzc8usUyqV+O9//wtHR0fExsZi9OjRGDZsmARV1i28htQwhoZNsz+N/6/772n/+dyXgf56jsP4arzxmrKLDAmkFYbQPy3j2JcyNJBa1t4bjoOaSOvWrVuYP39+pfNANm3aFIsWLcKAAQPMegJ2fTIzM5GRkYHc3Fzs2LFDJ3DK5XJ06NABbdq0AQCkp6djx44dSE9Px9atW6FWq+Hn54dFixZp/63Z2dnB29sbcrkcGo0Gr732Gj7//HPY2dmhWbNmGDt2LMaMGYMmTZqY/eAxQ2VnZ+OJJ54odyDTM888g9WrV5v13Li1gYHUcNYaSnXoC6QPGl+F91mjf3FlodTa5hCt6iT4pT8V6zo6+jGQko7CwkIsWLAAy5cvR3JycrnbKRQKTJkyBR9++CGcnZ1NWGHdI4RAYWEhgJLQWtFp/4yMDBw5cgQODg7o1q0bHBwcLKrH0xCrV6/G5MmT9V476urqin379qFTp04SVFa3MZBWD0NpNRhwwwF9UzxZWwi9H+9XbzgGUtJr3759mDJlCi5dulTuNjKZDMHBwVi7di0CAwNNWB2Zu8zMTPTu3VvvJSIymQwvvvgiFi9ebLY3aKhNDKQ1w2BKtY23CK0eQ/Ia/zJYkT59+uDQoUN4/PHHy+3BE0Lg+PHjGDJkCFauXIni4mITV0nmat68eeVer9y8eXPMmTOHYZRqhaEB05IGPFHtYxg1Df51sDINGzbE+vXrsWzZMri5uZW73dWrV/Hyyy9j1KhRRpvbkyxXQkIC1q5dq3ednZ0dXnrpJYuaSYDqHoZSIvPGQGqFXFxcMHnyZGzbtg0dOnQod7vCwkL89ttvePzxx7Fr1y7TFUhmpbCwEDNmzEBaWpre9X379sWUKVNMXBVZIzFXGDw1FIMpVYS9o6bDQGql5HI5evbsiZ07d+L111+HrW35N+06duwYnnrqKSxZsgR5eXkmrJLMwZ9//om9e/fqXefm5obXXnvN6mYbIGmxt5TI/DCQWjkvLy+8//77WLlyJRo1alTudmlpaXj55Zcxfvx4XLhwwYQVUl127949zJkzB0VFRXrXT5kyBf369TNxVUTVC6UMpnQ/9o6aFgMpQaFQ4LnnnsNPP/2E0Ap+uYQQ2LRpE5544gn88ssv2gnoyXqtXLkSp06d0rvO29sbU6dOtdopsEh6hp7CB9hbSiUYRk2PgZS0HnnkEfz666+YN28enJycyt3u4sWLGDt2LD799FOkp6ebsEKqS+7cuYOvvvpK7zpbW1tERkaiadOmpi2KSA+GUqK6j4GUdLi7u+Odd97BV199VeE9xgsLC/Hmm29i+PDhuHHjBsxwOluqAY1Gg5kzZ5Z7v/rSO2AR1RU8hU9Vxd5RaTCQUhkymQxjxozBtm3bEBoaWu6AJyEE9u/fj549e+LHH3/Uew94skzHjh3D77//Xu764uJi5OTkmLAiosrxFD5R3cVASuXy9/fHjh078NZbb1V4Cv/mzZt45plnEBkZqb3TDFmu8+fP4/nnn6/wC0hwcDD69u1rwqqIqo6hlMpTtd7RPrVfiBViIKUK2dvb4+2338a2bdvQunXrcrdTqVRYs2YNevXqhRMnTpiwQjIVjUaD48ePY8SIEThz5ky523Xo0AEbN27kRPhUp3HOUiqr8p8vw2jtYSClStna2iI0NBSHDh3CmDFjyr39oxACp0+fxrBhw7Bo0SKoVCoTV0q1RaPRYNmyZejbty/Onz9f7nYdOnTATz/9hGbNmpmwOqLqY28plZBBtn9fFbflmInawEBKVebh4YGvvvoKq1evrvC2ozdu3MDrr7+OyMhI3L5924QVUm1Qq9VYtmwZZs2ahezs7HK369ChAzZt2oQWLVqYsDqimuOAJ2smQ1XDaEnvKMNobWEgJYM4OTnhmWeewZYtW9C9e/dytysuLsaqVaswePBg/PbbbyaskIwpNTUVkydPxqxZs1BQUFDudvb29vj444/RvHlzE1ZHZDwc8GSNDP35MYzWJgZSMphMJkNoaCi2bt2KOXPmVHjb0VOnTmHcuHH45JNPcOvWLZ7GNyPJycl4+umn8e2331YYRgFg8ODB6N27t4kqI6o9DKXW4n8/t6r1jobWYi0EMJBSDTRo0ABjxoypcAQ+AGRmZmLWrFlo164dxowZg3Xr1uHq1aucu7QOO3fuHMaNG4ddu3ZVuq23tzcWLlzI+9WTxajuKfylsvIH+1FdYtiXCIZR0yi/a4uoChQKBVxdXas03VN6ejp+/vln/Pzzz2jYsCE6deqE4cOHo127dggMDISLi4sJKqbK7NixA+PGjUNKSkql28rlckyePBkBAQEmqIzIdMRcgaWyM5j2btsqv2bau20BWbz2eaQo/+YiJBXdMFr1gUxU22TCDLupsrKy4ObmhszMTLi6ukpdjtU7fvw4Pv30U2zduhW5ubkGv97FxQXNmzfHgAEDMGrUKAQGBkKpVNZCpVSZnTt34tlnn61SGAWAbt26Yc+ePZX2klPF7m/TALB9qyNKezwNCaUA8OW78WWWMZzWBWV7RisLpOwdrRlD8hoDKRlF6RyVX3/9Nfbs2YPbt2+jsLDQ4Pexs7NDhw4dMHjwYDz22GNo06YNnJ2dy51qiowjLy8P7777Lr7++mukp6dX6TVubm7Yu3cvOnXqVMvVWT4G0rrr/tPwhgRTfaG0FMOpqZV/ip6BtHYZktcM/iv/119/YciQIfD19YVMJsOWLVt01gshMGfOHPj4+MDBwQFhYWG4dOmSzjZpaWkYO3YsXF1d4e7ujokTJ/I2g2ZOLpeja9euWLVqFWJiYrB582ZMnjwZCoXCoPcpLi5GbGws5s6di/79+6Nt27aYMmUKtmzZgrt379ZS9datoKAAr7/+Oj799NMqh1GlUon3338f7du3r+XqTIdtG+kTKYK0AbKikPmgisLrUtkZnQfVpuoPOmMYNS2DA2lubi7at2+PpUuX6l2/cOFCLF68GCtWrEBMTAycnJwQHh6uM0p37NixOHv2LKKiorBt2zb89ddfmDx5cvX3guqUBg0aYNCgQVi6dCk+/vhj+Pv7V+t9cnJycOPGDXz99dcYMWIEunfvjjFjxmDLli24d+8e1Gq1kSu3Prdu3cKMGTOwbNkyaDSacrcbPHgwhg8fDnt7eygUCrzyyit48cUXYWNjY8JqaxfbNqpIaTA1ZMBTVXtUGU5rC8OoOanRKXuZTIZff/0VQ4cOBVDSg+Dr64tXX30Vr732GoCSEdZeXl5Ys2YNRo8ejfPnzyMwMBCxsbHo3LkzgJLr1gYNGoRbt27B19e3zOcUFhbqnP7NysqCn58fT2mZib///hvvvfcefv31V6ONrPfz88Njjz2GoKAgDB48GE2bNuUobwOdOXMGY8aMqfA2oPb29hg5ciQWL14MBwcH7Ny5E/n5+Rg2bBjs7e1NWK1pmaptK8VT9uapKlM+GdKr+iCe2q+JqofRB0/bM4waT62esq/ItWvXkJiYiLCwMO0yNzc3dOvWDdHR0QCA6OhouLu7axtsAAgLC4NcLkdMTIze912wYAHc3Ny0Dz8/P2OWTbWsffv2WLduHb7++mu0a9cOMllJQ+Hu7o6pU6eiV69eBoebmzdv4ttvv8XMmTPRsWNHDBgwAEuWLEF8fDyKiopqYzcsypkzZzBq1KgKw6iTkxOWLVuG1atXo169erC3t8fQoUMxZswYiw6j+tRW2waUfOHOysoCUNJ4l/4/1X2GTg9lKPacVpchPaMCIjRU50HSMGogTUxMBAB4eXnpLPfy8tKuS0xMRMOGDXXW29rawsPDQ7vNg2bPno3MzEzt4+bNm8Ysm0zAyckJzz33HA4cOIBVq1ahV69eWLFiBZYtW4aoqCicOHEC8+fPR0hICNzd3Q1679zcXOzduxcvvfQSevXqhb59++Kzzz7DxYsXqzWwypKp1Wr88MMPGDlyJC5cuFDuds7Ozvjyyy8xfvz4Cm98YC1qq20DSr5wl37J9vPz4xduM1PRHZ5q0jv6IIbTqjIsjFLdYRZ/aZRKJacBshDu7u6YNGkSnn76ae3PVKFQoHXr1mjdujVeeeUVJCQkYN++fVi9ejXi4w1r0DMyMnD48GEcPnwYc+fOxSOPPIKBAweiQ4cO2m0aN24Mf39/bU+tXC63ilH8Go0GX375Jd58880K77zUsWNHLFy4EP369dMeI6o9s2fPxqRJk+Dn56f9ss1Qan5KQ2npaXwxVwBz/7femEHy/vfiaf37MYyaM6MGUm9vbwBAUlISfHx8tMuTkpK0gcDb2xvJyck6r1OpVEhLS9O+nixfead8HRwc0KFDB3To0EEbXisabFORnJwc/Pnnn/jzzz91lnt6eur0dAUGBiIkJET73N/fH927d9eptV69etWqoa5Qq9X48ssv8cYbb1TYa9y5c2f89NNPnOj+AbXZtimVSu21Vbxm1PyV11t6f3CsjXDaZl/FA6hCQy05gPGe9JbAqIE0ICAA3t7e2LNnj7aRzsrKQkxMDKZOnQoACAkJQUZGBuLi4hAcHAwA2Lt3LzQaDbp162bMcsjM5eTkVDuMViQlJUVn4vczZ87gp59+0j5XKBRwdHTUPm/UqBG6dOmifd6pUyed535+fjohRSaT1YmexdJjp9FosGzZMrz55pvlhlFbW1uEhoZi5cqVDKN6sG0jOB4/IQAAHsFJREFUY4oUQdi/v6SNONvHOKf1z/aJrzCUln5eZcwvuDKMWgqDA2lOTg4uX76sfX7t2jWcOnUKHh4eaNKkCWbMmIEPPvgALVq0QEBAAN555x34+vpqR6u2bt0aAwYMwPPPP48VK1aguLgY06ZNw+jRoyschUrWZ8iQITh48CD27NmDtLQ0k31uUVGRzsCojIwMnD17Vvt8zZo1Otv7+fnB09NT+zw4OBjBwcHaUNqsWTOd+TodHBxqfGej3Nxc5Ofna5/fu3cPBw4cgBACMpkMQgj88ccfuH37NjQaDc6fP19hGH3jjTcwZ84cg+eNtSRs20gK94dIY4XTmqjzwfXA8f/9f+8u5W9XBoNoXWfwtE/79+9Hnz59yiwfN24c1qxZAyEE5s6di1WrViEjIwM9e/bEsmXL0LJlS+22aWlpmDZtGrZu3Qq5XI4RI0Zg8eLFcHZ2rlINvFOT9SgqKkJycjKioqKQlZWFn3/+Gbm5uSgoKMCFCxeMNo1UbbK3t4eLi4v2ecuWLdGuXTvt8/79+6NJkyYASu5U1axZM1y9elUnFG/btk1nYEx8fDwSEhK0z4uLi5GRkWFwbUqlEjNnzsS7775r1WEUkLZt47RP1qey4FedcFrZaXtTq3JovT9kGqLKgbTu/52wVLx1KFms0snwCwoKcPr0aajVamg0Gvzyyy/auwylp6fj0KFD2tfk5+fX6dH295/iVygUaNWqFS5duqTTA1obly40atQIX331FcLCwjiHq8QYSMkYAbWuBVIACJXF1s4bM4yaBUPymlmMsicqVXpnICcnJ52BSL169dL+f3Fxsc41oqdOndKZ4ujvv/9GXFyc9nlycrLO9qYmhND29BYUFODUqVMm+dw333wTAwcONMlnEVHFKupN3L9fVudO7Vek1kKowRhGzQkDKVkcOzs7nWv2fH19MWjQIO1zjUaj7WkVQuCff/7BjRs3tOt37tyJ69eva5/HxcXpBNaCggKoVKpa3IPaY2dnBz8/Pzz66KMYPHiw1OUQURU8GFbLy67799d+LeaDYdTcMJCS1Xlw3tEWLVqgRYsW2uf9+vXT2T4lJQV5eXna59HR0TqDX44fP45z587pbF+d6zlrys/PT2c6rc6dOyMwMBAajQZbtmyBq6srZsyYgR49eugMwiIiy1CVazarOmipzjsQq/+0/YFYoHfnssupzmMgJarEg+HN399f57lKpdL2mAohcPHiRe0E50IIbN++XefuYidOnNBe71pUVFTj60NDQkLwxhtvICQkROcaHTs7O+0lDm+88QZkMpnVD1wisnZVHWhkHsFVQHfaJwH0lqoWqikGUqIasrW11bm9Zvv27XWmeRoyZIjO9rdv30Z+fj6EEIiNjcX58+d11icnJ5eZzF+lUuHOnTtlwmu9evUwePBgPPHEExXWyDudEZEhpAyu+4Vuz2fFtfDUvKVgICUysUaNGmn///5LBUppNBqdSwQAoLCwEEeOHCkzW0BgYCCaN29eO4USEVWitoOr+U3UT9XFQEpUx8jl8jLzVjo7O5fpaSUiMhcMllQZeeWbEBERERHVHgZSIiIiIpIUAykRERERSYqBlIiIiIgkxUBKRERERJJiICUiIiIiSTGQEhEREZGkGEiJiIiISFIMpEREREQkKQZSIiIiIpIUAykRERERSYqBlIiIiIgkxUBKRERERJJiICUiIiIiSTGQEhEREZGkGEiJiIiISFIMpEREREQkKQZSIiIiIpIUAykRERERSYqBlIiIiIgkxUBKRERERJJiICUiIiIiSUkWSJcuXYqmTZvC3t4e3bp1w7Fjx6QqhYjIqNi+EREZRpJA+uOPP2LmzJmYO3cuTpw4gfbt2yM8PBzJyclSlENEZDRs34iIDCdJIP3ss8/w/PPPY8KECQgMDMSKFSvg6OiIb7/9VopyiIiMhu0bEZHhbE39gUVFRYiLi8Ps2bO1y+RyOcLCwhAdHa33NYWFhSgsLNQ+z8zMBABkZWXVbrFERAYobd9efvllnfapd+/eOHjwIF588cUyryksLERqaiqA/7VtANs3IjJ/pe2YEKLSbU0eSFNTU6FWq+Hl5aWz3MvLCxcuXND7mgULFmDevHlllvv5+dVKjURENTF27Fi9y93c3Cp8XZMmTbT/z/aNiCxFdnZ2pe2fyQNpdcyePRszZ87UPs/IyIC/vz9u3LhR6Q6SfllZWfDz88PNmzfh6uoqdTlmi8ex5izpGN69exetWrVCVFQUunbtql3+zjvv4PDhw9i7d2+Z1xQWFqKgoAA5OTnw8fFBVlYW27casqR/U1LhMaw5HsOSntHs7Gz4+vpWuq3JA2mDBg1gY2ODpKQkneVJSUnw9vbW+xqlUgmlUllmuZubm9X+kI3F1dWVx9AIeBxrzhKOob29PWxsbJCTk6OzLxkZGWjUqFGV9k8uL7m0n+1bzVnCvymp8RjWnLUfw6p+sTb5oCaFQoHg4GDs2bNHu0yj0WDPnj0ICQkxdTlEREbD9o2IqHokOWU/c+ZMjBs3Dp07d0bXrl2xaNEi5ObmYsKECVKUQ0RkNGzfiIgMJ0kgffLJJ5GSkoI5c+YgMTERHTp0wM6dO8sMdCqPUqnE3Llz9Z7Gp6rhMTQOHseas7RjyPZNejyGNcdjWHM8hoaRiaqMxSciIiIiqiW8lz0RERERSYqBlIiIiIgkxUBKRERERJJiICUiIiIiSTGQEhEREZGkzDKQLl26FE2bNoW9vT26deuGY8eOSV1SnbBgwQJ06dIFLi4uaNiwIYYOHYqEhASdbQoKChAZGYn69evD2dkZI0aMKHPXrBs3biAiIgKOjo5o2LAhZs2aBZVKZcpdqTM++ugjyGQyzJgxQ7uMx7Bqbt++jaeffhr169eHg4MD2rZti+PHj2vXCyEwZ84c+Pj4wMHBAWFhYbh06ZLOe6SlpWHs2LFwdXWFu7s7Jk6ciJycHFPvismwbSsf2zfjY/tWPWzbaokwMxs3bhQKhUJ8++234uzZs+L5558X7u7uIikpSerSJBceHi5Wr14tzpw5I06dOiUGDRokmjRpInJycrTbvPDCC8LPz0/s2bNHHD9+XHTv3l088sgj2vUqlUoEBQWJsLAwcfLkSbF9+3bRoEEDMXv2bCl2SVLHjh0TTZs2Fe3atRMvv/yydjmPYeXS0tKEv7+/GD9+vIiJiRFXr14Vu3btEpcvX9Zu89FHHwk3NzexZcsW8ffff4vHH39cBAQEiPz8fO02AwYMEO3btxdHjx4VBw8eFM2bNxdjxoyRYpdqHdu2irF9My62b9XDtq32mF0g7dq1q4iMjNQ+V6vVwtfXVyxYsEDCquqm5ORkAUAcOHBACCFERkaGsLOzE5s2bdJuc/78eQFAREdHCyGE2L59u5DL5SIxMVG7zfLly4Wrq6soLCw07Q5IKDs7W7Ro0UJERUWJ3r17axtsHsOqeeONN0TPnj3LXa/RaIS3t7f4+OOPtcsyMjKEUqkUP/zwgxBCiHPnzgkAIjY2VrvNjh07hEwmE7dv36694iXCts0wbN+qj+1b9bFtqz1mdcq+qKgIcXFxCAsL0y6Ty+UICwtDdHS0hJXVTZmZmQAADw8PAEBcXByKi4t1jl+rVq3QpEkT7fGLjo5G27Ztde4qEx4ejqysLJw9e9aE1UsrMjISEREROscK4DGsqt9//x2dO3fGqFGj0LBhQ3Ts2BFfffWVdv21a9eQmJiocxzd3NzQrVs3nePo7u6Ozp07a7cJCwuDXC5HTEyM6XbGBNi2GY7tW/Wxfas+tm21x6wCaWpqKtRqdZlb8Hl5eSExMVGiquomjUaDGTNmoEePHggKCgIAJCYmQqFQwN3dXWfb+49fYmKi3uNbus4abNy4ESdOnMCCBQvKrOMxrJqrV69i+fLlaNGiBXbt2oWpU6fipZdewtq1awH87zhU9LucmJiIhg0b6qy3tbWFh4eHxR1Htm2GYftWfWzfaoZtW+2R5F72VPsiIyNx5swZHDp0SOpSzMrNmzfx8ssvIyoqCvb29lKXY7Y0Gg06d+6MDz/8EADQsWNHnDlzBitWrMC4ceMkro7MHdu36mH7VnNs22qPWfWQNmjQADY2NmVG/CUlJcHb21uiquqeadOmYdu2bdi3bx8aN26sXe7t7Y2ioiJkZGTobH//8fP29tZ7fEvXWbq4uDgkJyejU6dOsLW1ha2tLQ4cOIDFixfD1tYWXl5ePIZV4OPjg8DAQJ1lrVu3xo0bNwD87zhU9Lvs7e2N5ORknfUqlQppaWkWdxzZtlUd27fqY/tWc2zbao9ZBVKFQoHg4GDs2bNHu0yj0WDPnj0ICQmRsLK6QQiBadOm4ddff8XevXsREBCgsz44OBh2dnY6xy8hIQE3btzQHr+QkBDEx8fr/LJERUXB1dW1zC+hJerXrx/i4+Nx6tQp7aNz584YO3as9v95DCvXo0ePMlPyXLx4Ef7+/gCAgIAAeHt76xzHrKwsxMTE6BzHjIwMxMXFabfZu3cvNBoNunXrZoK9MB22bZVj+1ZzbN9qjm1bLZJ6VJWhNm7cKJRKpVizZo04d+6cmDx5snB3d9cZ8Wetpk6dKtzc3MT+/fvF3bt3tY+8vDztNi+88IJo0qSJ2Lt3rzh+/LgICQkRISEh2vWlU3r0799fnDp1SuzcuVN4enpazZQe+tw/ClUIHsOqOHbsmLC1tRXz588Xly5dEt9//71wdHQU69ev127z0UcfCXd3d/Hbb7+J06dPiyeeeELv1CgdO3YUMTEx4tChQ6JFixYWOzUK27aKsX2rHWzfDMO2rfaYXSAVQoglS5aIJk2aCIVCIbp27SqOHj0qdUl1AgC9j9WrV2u3yc/PFy+++KKoV6+ecHR0FMOGDRN3797VeZ/r16+LgQMHCgcHB9GgQQPx6quviuLiYhPvTd3xYIPNY1g1W7duFUFBQUKpVIpWrVqJVatW6azXaDTinXfeEV5eXkKpVIp+/fqJhIQEnW3u3bsnxowZI5ydnYWrq6uYMGGCyM7ONuVumBTbtvKxfasdbN8Mx7atdsiEEEKavlkiIiIiIjO7hpSIiIiILA8DKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSImIiIhIUgykRERERCQpBlIiIiIikhQDKRERERFJioGUiIiIiCTFQEpEREREkmIgJSIiIiJJMZASERERkaQYSIksXNOmTSGTybQPuVwOFxcXNG7cGH369MFrr72GY8eOVfgeoaGhkMlk2L9/v2mKNsC7774LmUyG0NBQqUuRjEajwcqVK9GtWze4uLjAxcUF3bp1w6pVqyCEMPj9bt68iZUrV2Ly5MkIDg6GUqmETCbDpEmTKnzdvXv3sGbNGkyfPh2PPPIIHB0dIZPJEBYWVulnqlQqLFu2DN27d4erqyscHR3Rtm1bvP/++8jPz6/wtT/88AP69OmDevXqwd7eHi1btsSsWbOQnp5u0H4TkXRspS6AiEyjR48eaN68OQAgPz8fqampOHnyJPbv349PP/0UvXv3xrfffouHHnpI4krJEGq1Gv/5z3+wefNmODo6ol+/fgCA3bt3Y8qUKdi9ezc2btwIubzq/Q+//PILXnnlFYNrOXjwICZMmGDw6woLCzF48GDs3r0bSqVSG0pjYmIwZ84c/PLLL9i/fz/c3d11XieEwPjx47Fu3TrY2Niga9eu8Pb2xvHjx/HJJ5/gxx9/xKFDh9CkSRODayIiExNEZNH8/f0FALF69eoy6zQajfjjjz9EixYtBADh5eUlrl69Wma7f/75R5w/f17k5uaaoGLDzJ07VwAQvXv3lroUSXz++ecCgGjUqJHOz+7q1avC19dXABBLliwx6D23bNkipk+fLlavXi3+/vtv8dZbbwkAYuLEiRW+7siRI2LKlCli5cqVIjY2VqxYsUIAEP369avwdbNmzdLuQ3x8vHZ5VlaWiIiIEADEU089VeZ1S5cuFQCEi4uLOHDggHZ5UVGRmDRpkgAgevToYdC+E5E0GEiJLFxFgbRUenq6NpT27dvXdMUZgTUHUrVaLby9vQUAsX79+jLrv/vuOwFA+Pr6CrVaXe3PKT3GlQXSB61evbrSQFpUVCScnZ3L/Td69+5d4eDgIGQymbh06ZLOupYtWwoAYu7cuWVel5eXpw3ku3fvNqhuIjI9XkNKRHB3d8eiRYsAAHv37kVcXJzO+squId27dy9GjRqFxo0bQ6lUwtPTE126dMHcuXNx7969MttfvHgRU6ZMQbNmzWBvbw83Nzf06tUL69evN/au6aVSqbBixQo88sgjcHNzg729PVq0aIGXXnoJt2/f1vuaS5cu4bnnnkNAQACUSiWcnZ3h7++PiIgIrF69usz2mzZtQlhYGOrXrw87OzvUr18fgYGBeP7553H69Gmj7Ed0dDQSExOhVCoxYsSIMutHjBgBhUKBO3fuICYmxiifaWznz59HTk4OAOi91tTb2xtBQUEQQuCXX37RLs/KysLFixfLfZ2DgwN69OgBAPj5559ro3QiMiIGUiICAAwcOBAeHh4AgKioqCq/7qWXXkK/fv3w888/w9PTE8OHD0eXLl2QlpaG9957D/Hx8Trbb9q0Ce3bt8eqVaugUCgwaNAgdO7cGSdOnMAzzzyD5557zqj79aDCwkIMHDgQU6dOxcmTJ9GjRw8MHToUhYWFWLJkCTp06IATJ07ovObMmTPo3LkzVq9eDaVSicGDB2PQoEFo1KgR/vrrL3zxxRc627/33nv4z3/+gwMHDiAoKAijRo1C9+7dYWNjg2+++QZ79+7V2f769evaQWfXr1+v8r6cPHkSANCmTRvY29uXWe/g4IA2bdrobFvXlIZRAKhfv77ebRo0aAAAOl+Uqvs6IqqbOKiJiAAAMpkMnTp1wu7du3H27NkqvWbJkiVYsmQJ6tevj02bNqFPnz46648dOwYfHx/t8/j4eDzzzDOQyWT45ZdfMHz4cO26f/75B0OGDMHq1asRGhqKZ5991jg79oC5c+di9+7daNasGXbv3o2mTZsCAIqLizF16lR88803GDlyJC5cuACFQgEA+Oyzz5CVlYUPPvgAb731ls775efnIzY2Vvu8sLAQH330EZydnXH8+P+3d/8xUdd/AMefFxyCB6jEIYlMUfMgcRMGRG2JNSWTpXNeo62mjOYvzDFHjNoQImtrWdJcMUJps7Xmmr+tyFmjtBHIJa0fekRyYDi1Q1xEHD+S9/eP232+XHfAofg9vuv12G7j7vN6373fH47txfunBZPJ5Bbf3t4+5qpxX9lsNoBRF+3ExsbS1NSkxU42UVFR2s+tra1aAj1ca2srgFsbIiIiCAgI4NatW7S2tpKQkOBTOSHE5CQ9pEIIjatHydsw+z/9/fff7Nq1C4CqqiqPZBQgLS2N2NhY7flrr71Gf38/r776qlsyCjBnzhyqq6sB2Lt37223YTR9fX28++67AJSXl2vJKIBer2fv3r3MnDkTm83mNsx7/fp1AFatWuXxniEhISxdulR73t3djcPhYN68eR7JKDjbGR8f7/aaXq/HZDJhMpnQ6/U+t+fPP/8EwGAwjBgTGhqq1WsyWrBggZZQ79u3z+P6V199RXNzM+DehuDgYB5++OERy7W0tGg90ZO17UKI/5KEVAihGRoaApy9pWP57rvvsNvtREZGsnbtWp/eu6amBoDs7GyvMSkpKYSGhtLU1ERfX984au4bi8VCT08PERERPPnkkx7Xp06dytNPPw1AbW2t9npaWhoAW7du5dSpU6PWzWg0MnfuXH744QcKCgq4cOHCmPWKiYnBarVitVqJiYkZb7P+75WWlgLOHveSkhLa29u5efMmhw4dIjs7W0vS/7l1VUlJCTqdjuPHj7NlyxZaWlro7u7m9OnTPPHEE1rceLa8EkL4h/yVCiE0nZ2dANpc0tG0t7cDYDKZfEpgb9y4ofVUxcbGum3WP3zT/p6eHoaGhnzqpR0v14KluLi4EWPmz5/vFgtQWFjI8uXLaWhoYOXKlYSHh5OamkpBQYHbcL3LBx98QFRUFHv27GHRokXce++9rFq1ivLycu0eT4SwsDAA/vrrrxFjXHMtw8PDJ+xzJ1pubi5lZWXodDp27drF3LlziYiI4KmnniIqKorCwkLA83u5fPly9u3bR3BwMO+99x4LFy5k2rRpZGZmMjAwoPXg+/J9FkL4l8whFUIAzk3GXQtfFi9ePOHv7+p9BdiwYcOY8VOmTJnwOtyuqVOncvr0aRobG/n888+pq6ujrq4Oi8XCnj17yMvL06YCADzyyCO0tbXx6aef8vXXX1NXV8epU6eoqamhtLSUo0ePahvY3wnXlIPLly+PGPPbb7+5xU5WJSUlPPvssxw5coRLly4RFBREeno669at4+WXXwa8fy+fe+45srKyOHToEBcvXkSn05GUlER2djYfffTRiOWEEJOLJKRCCAA+++wz7ajFzMzMMeNd8/5++eUXlFJj9pJGRkYSEhKCw+HgzTff1Oar/i+5hsNHW+TiWgjjbeg8NTWV1NRUwDmH9tixY6xfv56KigrMZrPbPNqQkBDMZjNmsxkAu91OcXExVVVV5Obmaj3MdyI5ORmAn3/+mb6+Po+V9g6HQ1ug5oqdzObNm8cLL7zg8frZs2cBWLFihddy0dHRPP/88+MuJ4SYPGTIXgjBH3/8oR0VuWLFCpYsWTJmmZSUFCIjI7Hb7Rw7dmzM+ICAAC0x+Pjjj++kurfNNUe1q6uLEydOeFx3OBwcPHgQwOsireECAwMxm808/vjjAHz//fejxhuNRt544w3A2aM5EeesP/TQQ0RHR9Pf3++2R6fL4cOHGRgYYNasWTz44IN3/Hn+UF9fzzfffENsbCxr1qzxuVxbWxuHDx8mNDSUnJycu1dBIcSEkIRUiH8xpRQ1NTWkpaXR0tLCfffd53XFsjeBgYHaFkibNm3izJkzHjGNjY10dHRoz0tLSwkKCqKwsJADBw64DeO7/PTTTxw5cuQ2WzS64OBgtm3bBkBBQYFbL+Xg4CD5+flcu3aNuLg4rWcToKKiQlvpPdy1a9ewWCyAc/U8OOfW7t+/3+vK7pMnTwIwY8YMtzmdV65cIT4+nvj4+BE35vfmnnvuoaioCICioiK3nl+bzcaLL74IwEsvveSxsOedd94hPj7+rm2vNR43b970en/r6+tZt24dOp2OqqoqAgPdB/UGBga87q9qtVrJysrC4XDw1ltvjbhPqRBi8pAheyH+Jfbv36+dtNTf309nZyfnz5+nq6sLcJ7G9P7772uJlS/y8/Npbm6msrKSjIwMkpKSMJlMdHd3Y7VaaW1tpba2ltmzZwPOYeMPP/yQnJwccnJyKC4u5oEHHsBoNNLV1cWPP/5IR0cH2dnZHttCjeX8+fOkp6ePeD0rK4udO3dSVlaGxWLhyy+/JCEhgUcffZSwsDC+/fZbLl++rO2p6tqDFJzbWm3bto24uDgSExMJDw/Hbrdz9uxZHA4Hjz32GKtXrwacydXGjRvJy8tjyZIl2gKqlpYWmpqa0Ol07N69m4CAAO39BwcHtYRscHBwXO3evn07Z86c4ejRoyQmJmqnFn3xxRf09vZiNpvJy8vzKNfZ2UlzczPR0dEe165eveq2c4Lrn4oTJ0643eOKigqPqQDDr9vtdsD5j8nw13fu3ElWVpb2vL29naSkJBISEliwYAEGg4Hm5maamprQ6/VUV1ezcuVKj3r29vaSnJysbbE1ffp02traOHfuHENDQ7zyyits2rRp9BsohJgc/HtyqRDibnOdZT/8YTAY1KxZs1RGRoYqKChQ586dG/U9MjIyFKBqa2u9Xq+pqVFr1qxRM2fOVHq9XhmNRpWWlqbKysrUjRs3POJtNpvasWOHSkxMVAaDQQUHB6s5c+aoZcuWqddff139+uuvPrfPdc76WI8NGzZoZQYHB1VFRYVKT09XYWFhKigoSM2fP19t375ddXR0eHzGJ598orZu3aqSkpKU0WhUQUFBavbs2WrZsmXqwIEDamBgQIvt7u5Wb7/9tlq7dq26//77VWhoqDIYDGrhwoVq/fr1ymKxeL0frnrabDaf2+5y69YtVVlZqVJSUpTBYFAGg0GlpqaqyspKNTQ0NOp9y8jIGLU+oz28fR98KffPM+t///13tXnzZrVo0SIVHh6upkyZouLi4tTGjRuV1Wodsd39/f0qPz9fJScnqxkzZmi/l2eeeUY1NDSM5xYKIfxMp5RSdznnFUIIIYQQYkQyh1QIIYQQQviVJKRCCCGEEMKvJCEVQgghhBB+JQmpEEIIIYTwK0lIhRBCCCGEX0lCKoQQQggh/EoSUiGEEEII4VeSkAohhBBCCL+ShFQIIYQQQviVJKRCCCGEEMKvJCEVQgghhBB+JQmpEEIIIYTwq/8A71fWmZ1WbvMAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "\n", "from matplotlib.colors import TwoSlopeNorm\n", "\n", "\n", "input_image = upscaled_image\n", "# The rest of the processing steps as in the original code\n", "input_image_array = np.array(input_image.convert('L'))\n", "\n", "input_image_array_tensor = torch.tensor(input_image_array)\n", "input_image_array_tensor = input_image_array_tensor / 255.0\n", "input_image_array_tensor = 1.0 - input_image_array_tensor\n", "input_image_array_tensor = torch.flip(input_image_array_tensor, [0])\n", "\n", "# Get the segmentation result\n", "for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", "DH = input_image_array.shape[0] / min(input_image_array.shape[1], input_image_array.shape[0])\n", "DW = input_image_array.shape[1] / min(input_image_array.shape[1], input_image_array.shape[0])\n", "aspect_ratio = DH / DW\n", "nelx = input_image_array.shape[1]-1\n", "nely = input_image_array.shape[0]-1\n", "\n", "\n", "x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", "LSgrid = torch.stack((x.flatten(), y.flatten()), dim=0)\n", "# Create the constant tensors on the specified device\n", "pred_bboxes = results[0].boxes.xyxyn.to('cpu').detach()\n", "constant_tensor_02 = torch.full((pred_bboxes.shape[0],), 0.2)\n", "constant_tensor_00 = torch.full((pred_bboxes.shape[0],), 0.001)\n", "# Stack the tensors and move them to the specified device\n", "xmax = torch.stack([pred_bboxes[:,2]*(DW*1.0), pred_bboxes[:,3]*(DH*1.0), pred_bboxes[:,2]*(DW*1.0), pred_bboxes[:,3]*(DH*1.0), constant_tensor_02], dim=1)\n", "xmin = torch.stack([pred_bboxes[:,0]*(DW*1.0), pred_bboxes[:,1]*(DH*1.0), pred_bboxes[:,0]*(DW*1.0), pred_bboxes[:,1]*(DH*1.0), constant_tensor_00], dim=1)\n", "\n", "unnormalized_preds = prediction_tensor * (xmax - xmin) + xmin\n", "\n", "# # # The design variables are infered from the two endpoints and the two thicknesses:\n", "x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", "y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", "L = torch.sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", "L = L+1e-4\n", "t_1 = unnormalized_preds[:, 4]\n", "\n", "epsilon = 1e-10\n", "y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] + epsilon\n", "x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] + epsilon\n", "theta = torch.atan2(y_diff, x_diff)\n", "formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "\n", "pred_Phi,pred_H = calc_Phi(formatted_variables.T,LSgrid)\n", "\n", "\n", "sum_pred_H = torch.sum(pred_H.detach().cpu(), dim=1)\n", "\n", "# # Rearrange H_phi to the shape ([batch_size, channels, height, width])\n", "# # Use interpolate to resize\n", "\n", "diceloss = CustomDiceLoss()\n", "tverskyloss = CustomTverskyLoss()\n", "sum_pred_H[sum_pred_H> 1] = 1\n", "\n", "#Final H:\n", "final_H = np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F'))\n", "# Calculate the dice loss:\n", "dice_loss = diceloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", "tversky_loss = tverskyloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", "\n", "# Create a combined 2x2 plot\n", "fig, axes = plt.subplots(2, 2,figsize=(8,8) ) # 2x2 subplot\n", "\n", "# Top-left: Input image\n", "axes[0, 0].imshow(input_image_array_tensor.squeeze(),origin='lower', cmap='gray_r')\n", "axes[0, 0].set_title('Input Image')\n", "#add grid:\n", "axes[0, 0].axis('on')\n", "#add a colorbar\n", "\n", "# Top-right: Segmentation Result\n", "axes[0, 1].imshow(seg_result)\n", "axes[0, 1].set_title('Segmentation Result')\n", "axes[0, 1].axis('off')\n", "\n", "# # Bottom-right: pred_Phi contour\n", "render_colors1 = ['yellow', 'g', 'r', 'c', 'm', 'y', 'black', 'orange', 'pink', 'cyan', 'slategrey', 'wheat', 'purple', 'mediumturquoise', 'darkviolet', 'orangered']\n", "for i, color in zip(range(0, pred_Phi.shape[1]), render_colors1*100):\n", " axes[1, 1].contourf(np.flipud(pred_Phi[:, i].numpy().reshape((nely+1,nelx+1),order='F')), [0,1], colors=color)\n", "axes[1, 1].set_title('Prediction contours')\n", "axes[1, 1].set_aspect('equal') # Set the aspect ratio to be equal\n", "\n", "# Bottom-left: Prediction H\n", "axes[1, 0].imshow(np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F')), origin='lower', cmap='gray_r')\n", "axes[1, 0].set_title('Prediction Projection')\n", "plt.subplots_adjust(hspace=0.3, wspace=0.01) # Adjust hspace to reduce vertical space\n", "\n", "plt.figtext(0.5, 0.05, f'Dice Loss: {dice_loss.item():.4f}', ha='center', fontsize=16)\n", "# plt.figtext(0.5, 0.01, f'Tversky Loss: {tversky_loss.item():.4f}', ha='center', fontsize=16)\n", "\n", "# Save the combined plot\n", "fig.savefig('combined_plots.png',dpi = 600)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\n", "WARNING ⚠️ imgsz=[594, 1182] must be multiple of max stride 32, updating to [608, 1184]\n", "0: 608x1184 28 mmc_components, 34.0ms\n", "Speed: 5.4ms preprocess, 34.0ms inference, 396.9ms postprocess per image at shape (1, 3, 608, 1184)\n", "/home/thomas/anaconda3/envs/customyolo/lib/python3.11/site-packages/torch/functional.py:504: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at ../aten/src/ATen/native/TensorShape.cpp:3483.)\n", " return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined]\n", "\n", "WARNING ⚠️ imgsz=[840, 1120] must be multiple of max stride 32, updating to [864, 1120]\n", "0: 864x1120 80 mmc_components, 116.6ms\n", "Speed: 2.4ms preprocess, 116.6ms inference, 1875.8ms postprocess per image at shape (1, 3, 864, 1120)\n", "\n", "WARNING ⚠️ imgsz=[840, 1120] must be multiple of max stride 32, updating to [864, 1120]\n", "0: 864x1120 47 mmc_components, 42.9ms\n", "Speed: 2.8ms preprocess, 42.9ms inference, 1039.9ms postprocess per image at shape (1, 3, 864, 1120)\n", "\n", "WARNING ⚠️ imgsz=[720, 626] must be multiple of max stride 32, updating to [736, 640]\n", "0: 736x640 47 mmc_components, 117.0ms\n", "Speed: 1.9ms preprocess, 117.0ms inference, 526.9ms postprocess per image at shape (1, 3, 736, 640)\n", "\n", "WARNING ⚠️ imgsz=[722, 656] must be multiple of max stride 32, updating to [736, 672]\n", "0: 736x672 194 mmc_components, 115.3ms\n", "Speed: 1.9ms preprocess, 115.3ms inference, 4158.2ms postprocess per image at shape (1, 3, 736, 672)\n" ] } ], "source": [ "import glob\n", "import os\n", "import json\n", "from PIL import Image\n", "import numpy as np\n", "import torch\n", "import matplotlib.pyplot as plt\n", "from matplotlib.colors import TwoSlopeNorm\n", "# Import other necessary libraries and functions\n", "import gc # Import garbage collection module\n", "from PIL import Image\n", "import math\n", "from PIL import Image,ImageFilter\n", "from PIL import Image, ImageOps\n", "\n", "from skeletonization import load_and_crop\n", "\n", "diceloss = CustomDiceLoss()\n", "tverskyloss = CustomTverskyLoss()\n", "paths = [#\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_simp_tiny/\",\n", " #\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_simp_nov28/\",\n", " \"/home/thomas/Documents/scratch_thomas/GitHub/TOP-YOLOv8/dataset/ood_test/\",\n", " #\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_dehomo/\"\n", " #\"/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/test/\"\n", " ]\n", " \n", "for path in paths:\n", "\n", " # Create a subdirectory for the results\n", " # output_dir = os.path.join(path, \"test3\")\n", "\n", " output_dir = os.path.join(path, \"yolo_results_default_apr09_xlarge_dicenms_conf01_iou50\")\n", " if not os.path.exists(output_dir):\n", " os.makedirs(output_dir)\n", "\n", " # List all .png files in the directory\n", " test_images = glob.glob(path + \"*.png\")\n", " # Loop through all the image files\n", " for image_path in test_images:\n", " \n", "\n", " # #Extract filename prefix (before .png):\n", " # filename_prefix = os.path.basename(image_path).split('.')[0]\n", " # input_img,_ = load_and_crop(\"/home/thomas/Documents/scratch_thomas/yolov8_evaluation/dataset/ood/datapoints/skeletonization_results/{}_padded.png\".format(filename_prefix), \"/home/thomas/Documents/scratch_thomas/yolov8_evaluation/dataset/ood/datapoints/skeletonization_results/{}_contour.png\".format(filename_prefix))\n", " # image = Image.fromarray(input_img)\n", " # image = ImageOps.invert(image)\n", "\n", "\n", " test_path = image_path.strip('.png')\n", "\n", " # Load image and convert to grayscale\n", " img = Image.open(image_path)\n", " # img = image\n", " # Convert the image to grayscale\n", " gray_image = img.convert('L')\n", "\n", " # Set a threshold value\n", " threshold_value = 0.9*255 # You can adjust this value\n", "\n", " # Apply thresholding\n", " binary_image = gray_image.point(lambda x: 255 if x > threshold_value else 0, '1')\n", " \n", " upscale_factor = 2\n", " # Use LANCZOS filter for better quality\n", " upscaled_image = binary_image.resize(\n", " (int(binary_image.width * upscale_factor), int(binary_image.height * upscale_factor)),\n", " resample=Image.BICUBIC # LANCZOS is an improvement over BICUBIC\n", " )\n", " upscaled_gray_image = upscaled_image.convert('L')\n", "\n", " blurred_image = upscaled_gray_image.filter(ImageFilter.GaussianBlur(radius=2))\n", " final_image = binary_image\n", "\n", " \n", " with torch.no_grad():\n", " # Run model inference on the image\n", " results = model(final_image,conf= 0.1, iou=0.5, imgsz=(final_image.height, final_image.width),max_det=300)\n", " #results = model(binary_image, conf=0.05, iou=0.5, imgsz=640)\n", " \n", " prediction_tensor = results[0].regression_preds.to('cpu').detach()\n", "\n", " # Process the input image for comparison\n", " input_image_array = np.array(binary_image)\n", " #Threshold also input_image_array:\n", " input_image_array_tensor = torch.tensor(input_image_array)\n", " #convert from boolean to uint8:\n", " input_image_array_tensor = input_image_array_tensor.type(torch.uint8)\n", "\n", " input_image_array_tensor = 1.0 - input_image_array_tensor\n", " #Threshold with 0.5:\n", " input_image_array_tensor[input_image_array_tensor > 0.5].fill_(1) # In-place thresholding\n", " input_image_array_tensor[input_image_array_tensor <= 0.5].fill_(0)\n", "\n", " input_image_array_tensor = torch.flip(input_image_array_tensor, [0])\n", "\n", " # Get the segmentation result\n", " for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", " #Check dimensions of the input image:\n", " DH = input_image_array.shape[0] / min(input_image_array.shape[1], input_image_array.shape[0])\n", " DW = input_image_array.shape[1] / min(input_image_array.shape[1], input_image_array.shape[0])\n", " aspect_ratio = DH / DW\n", " nelx = input_image_array.shape[1]-1\n", " nely = input_image_array.shape[0]-1\n", " x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", " LSgrid = torch.stack((x.flatten(), y.flatten()), dim=0)\n", "\n", " #Dimensions of the original mesh:\n", " nelx_original = int(200*DW)\n", " nely_original = int(200*DH)\n", " x_original, y_original = torch.meshgrid(torch.linspace(0, DW, nelx_original+1), torch.linspace(0, DH, nely_original+1))\n", " LSgrid_original = torch.stack((x_original.flatten(), y_original.flatten()), dim=0)\n", "\n", " # Create the constant tensors on the specified device\n", " pred_bboxes = results[0].boxes.xyxyn.to('cpu').detach()\n", " constant_tensor_02 = torch.full((pred_bboxes.shape[0],), 0.2)\n", " constant_tensor_00 = torch.full((pred_bboxes.shape[0],), 0.001)\n", " # Stack the tensors and move them to the specified device\n", " xmax = torch.stack([pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, constant_tensor_02], dim=1)\n", " xmin = torch.stack([pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, constant_tensor_00], dim=1)\n", "\n", " unnormalized_preds = prediction_tensor * (xmax - xmin) + xmin\n", "\n", " # # # The design variables are infered from the two endpoints and the two thicknesses:\n", " x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", " y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", " L = torch.sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", " L = L+1e-4\n", " t_1 = unnormalized_preds[:, 4]\n", "\n", " epsilon = 1e-10\n", " y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] + epsilon\n", " x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] + epsilon\n", " theta = torch.atan2(y_diff, x_diff)\n", " formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "\n", "\n", " pred_Phi,pred_H = calc_Phi(formatted_variables.T,LSgrid)\n", " sum_pred_H = torch.sum(pred_H, dim=1)\n", " sum_pred_H[sum_pred_H> 1] = 1\n", " #Final H:\n", " final_H = np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F'))\n", "\n", " pred_Phi_originalres, pred_Phi_originalres_H = calc_Phi(formatted_variables.T,LSgrid_original)\n", " sum_pred_H_originalres = torch.sum(pred_Phi_originalres_H, dim=1)\n", " sum_pred_H_originalres[sum_pred_H_originalres> 1] = 1\n", " final_H_originalres = np.flipud(sum_pred_H_originalres.detach().numpy().reshape((nely_original+1,nelx_original+1),order='F'))\n", " \n", " # Calculate the dice loss:\n", " dice_loss = diceloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", " tversky_loss = tverskyloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", " fig, axes = plt.subplots(2, 2,figsize=(8,8) ) # 2x2 subplot\n", "\n", " # Top-left: Input image\n", " axes[0, 0].imshow(input_image_array_tensor.squeeze(),origin='lower', cmap='gray_r')\n", " axes[0, 0].set_title('Input Image')\n", " axes[0, 0].axis('on')\n", " #add a colorbar\n", "\n", " # Top-right: Segmentation Result\n", " axes[0, 1].imshow(seg_result)\n", " axes[0, 1].set_title('Segmentation Result')\n", " axes[0, 1].axis('off')\n", " # # Bottom-right: pred_Phi contour\n", " render_colors1 = ['yellow', 'g', 'r', 'c', 'm', 'y', 'black', 'orange', 'pink', 'cyan', 'slategrey', 'wheat', 'purple', 'mediumturquoise', 'darkviolet', 'orangered']\n", " for i, color in zip(range(0, pred_Phi.shape[1]), render_colors1*100):\n", " axes[1, 1].contourf(np.flipud(pred_Phi[:, i].numpy().reshape((nely+1,nelx+1),order='F')), [0,1], colors=color)\n", " axes[1, 1].set_title('Prediction contours')\n", " axes[1, 1].set_aspect('equal') # Set the aspect ratio to be equal\n", "\n", "\n", " # Bottom-left: Prediction H\n", " axes[1, 0].imshow(np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F')), origin='lower', cmap='gray_r')\n", " axes[1, 0].set_title('Prediction Projection')\n", " plt.subplots_adjust(hspace=0.3, wspace=0.01) # Adjust hspace to reduce vertical space\n", "\n", " plt.figtext(0.5, 0.05, f'Dice Loss: {dice_loss.item():.4f}', ha='center', fontsize=16)\n", " # plt.figtext(0.5, 0.01, f'Tversky Loss: {tversky_loss.item():.4f}', ha='center', fontsize=16)\n", "\n", " # Convert formatted_variables and prediction_tensor to lists for JSON serialization\n", " formatted_variables_list = formatted_variables.tolist()\n", " prediction_tensor_list = prediction_tensor.tolist()\n", "\n", " # Save the final_H, dice_loss, formatted_variables, and prediction_tensor in a JSON file\n", " output_json_path = os.path.join(output_dir, os.path.basename(test_path) + \"_results.json\")\n", " with open(output_json_path, 'w') as f:\n", " json.dump({\n", " 'final_H': final_H.tolist(), \n", " 'final_H_originalres': final_H_originalres.tolist(),\n", " 'dice_loss': dice_loss.item(),\n", " 'formatted_variables': formatted_variables_list,\n", " 'prediction_tensor': prediction_tensor_list\n", " }, f)\n", "\n", " # # # # Save the combined plot\n", " output_image_path = os.path.join(output_dir, os.path.basename(test_path) + \"_combined_plot.png\")\n", " fig.savefig(output_image_path)\n", "\n", " plt.close(fig) # Close the figure to free memory# Create a combined 2x2 plot\n", "\n", " \n", " # Memory management\n", " del img, input_image_array, input_image_array_tensor, results, prediction_tensor\n", " del seg_result, pred_bboxes, constant_tensor_02, constant_tensor_00, xmax, xmin\n", " del unnormalized_preds, x_center, y_center, L, t_1, theta, formatted_variables\n", " del pred_Phi, pred_H, sum_pred_H, final_H, pred_Phi_originalres, pred_Phi_originalres_H\n", " del sum_pred_H_originalres, final_H_originalres, dice_loss\n", " gc.collect() # Explicitly invoke garbage collection\n", " if torch.cuda.is_available():\n", " torch.cuda.empty_cache() # Clear unused memory from the GPU" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\n", "WARNING ⚠️ imgsz=[720, 626] must be multiple of max stride 32, updating to [736, 640]\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "0: 736x640 36 mmc_components, 325.3ms\n", "Speed: 8.5ms preprocess, 325.3ms inference, 1894.8ms postprocess per image at shape (1, 3, 736, 640)\n", "\n", "WARNING ⚠️ imgsz=[594, 1182] must be multiple of max stride 32, updating to [608, 1184]\n", "0: 608x1184 16 mmc_components, 119.5ms\n", "Speed: 2.9ms preprocess, 119.5ms inference, 1095.3ms postprocess per image at shape (1, 3, 608, 1184)\n", "\n", "WARNING ⚠️ imgsz=[722, 656] must be multiple of max stride 32, updating to [736, 672]\n" ] }, { "ename": "KeyboardInterrupt", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[8], line 49\u001b[0m\n\u001b[1;32m 42\u001b[0m binary_image \u001b[38;5;241m=\u001b[39m binary_image\u001b[38;5;241m.\u001b[39mresize(\n\u001b[1;32m 43\u001b[0m (\u001b[38;5;28mint\u001b[39m(binary_image\u001b[38;5;241m.\u001b[39mwidth \u001b[38;5;241m*\u001b[39m upscale_factor), \u001b[38;5;28mint\u001b[39m(binary_image\u001b[38;5;241m.\u001b[39mheight \u001b[38;5;241m*\u001b[39m upscale_factor)),\n\u001b[1;32m 44\u001b[0m resample\u001b[38;5;241m=\u001b[39mImage\u001b[38;5;241m.\u001b[39mBICUBIC\n\u001b[1;32m 45\u001b[0m )\n\u001b[1;32m 48\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m torch\u001b[38;5;241m.\u001b[39mno_grad():\n\u001b[0;32m---> 49\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbinary_image\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconf\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43miou\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43miou\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mimgsz\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mbinary_image\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbinary_image\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwidth\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmax_det\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m600\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 50\u001b[0m prediction_tensor \u001b[38;5;241m=\u001b[39m results[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mregression_preds\u001b[38;5;241m.\u001b[39mto(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mcpu\u001b[39m\u001b[38;5;124m'\u001b[39m)\u001b[38;5;241m.\u001b[39mdetach()\n\u001b[1;32m 55\u001b[0m \u001b[38;5;66;03m# Process the input image for comparison\u001b[39;00m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/model.py:96\u001b[0m, in \u001b[0;36mModel.__call__\u001b[0;34m(self, source, stream, **kwargs)\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__call__\u001b[39m(\u001b[38;5;28mself\u001b[39m, source\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, stream\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 95\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Calls the 'predict' function with given arguments to perform object detection.\"\"\"\u001b[39;00m\n\u001b[0;32m---> 96\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredict\u001b[49m\u001b[43m(\u001b[49m\u001b[43msource\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m~/anaconda3/envs/customyolo/lib/python3.11/site-packages/torch/utils/_contextlib.py:115\u001b[0m, in \u001b[0;36mcontext_decorator..decorate_context\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 112\u001b[0m \u001b[38;5;129m@functools\u001b[39m\u001b[38;5;241m.\u001b[39mwraps(func)\n\u001b[1;32m 113\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdecorate_context\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 114\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ctx_factory():\n\u001b[0;32m--> 115\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/model.py:238\u001b[0m, in \u001b[0;36mModel.predict\u001b[0;34m(self, source, stream, predictor, **kwargs)\u001b[0m\n\u001b[1;32m 236\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m prompts \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpredictor, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mset_prompts\u001b[39m\u001b[38;5;124m'\u001b[39m): \u001b[38;5;66;03m# for SAM-type models\u001b[39;00m\n\u001b[1;32m 237\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpredictor\u001b[38;5;241m.\u001b[39mset_prompts(prompts)\n\u001b[0;32m--> 238\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpredictor\u001b[38;5;241m.\u001b[39mpredict_cli(source\u001b[38;5;241m=\u001b[39msource) \u001b[38;5;28;01mif\u001b[39;00m is_cli \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredictor\u001b[49m\u001b[43m(\u001b[49m\u001b[43msource\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msource\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/predictor.py:194\u001b[0m, in \u001b[0;36mBasePredictor.__call__\u001b[0;34m(self, source, model, stream, *args, **kwargs)\u001b[0m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstream_inference(source, model, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 194\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream_inference\u001b[49m\u001b[43m(\u001b[49m\u001b[43msource\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m~/anaconda3/envs/customyolo/lib/python3.11/site-packages/torch/utils/_contextlib.py:35\u001b[0m, in \u001b[0;36m_wrap_generator..generator_context\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 33\u001b[0m \u001b[38;5;66;03m# Issuing `None` to a generator fires it up\u001b[39;00m\n\u001b[1;32m 34\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ctx_factory():\n\u001b[0;32m---> 35\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[43mgen\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 37\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m 38\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 39\u001b[0m \u001b[38;5;66;03m# Forward the response to our caller and get its next request\u001b[39;00m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/engine/predictor.py:257\u001b[0m, in \u001b[0;36mBasePredictor.stream_inference\u001b[0;34m(self, source, model, *args, **kwargs)\u001b[0m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# Postprocess\u001b[39;00m\n\u001b[1;32m 256\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m profilers[\u001b[38;5;241m2\u001b[39m]:\n\u001b[0;32m--> 257\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresults \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpostprocess\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpreds\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mim0s\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 258\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrun_callbacks(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mon_predict_postprocess_end\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 260\u001b[0m \u001b[38;5;66;03m# Visualize, save, write results\u001b[39;00m\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/models/yolo/segment/predict.py:48\u001b[0m, in \u001b[0;36mSegmentationPredictor.postprocess\u001b[0;34m(self, preds, img, orig_imgs)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mpostprocess\u001b[39m(\u001b[38;5;28mself\u001b[39m, preds, img, orig_imgs):\n\u001b[1;32m 46\u001b[0m \u001b[38;5;66;03m#print(preds[0].shape)\u001b[39;00m\n\u001b[1;32m 47\u001b[0m regression_preds \u001b[38;5;241m=\u001b[39m preds[\u001b[38;5;241m1\u001b[39m][\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n\u001b[0;32m---> 48\u001b[0m p, final_reg \u001b[38;5;241m=\u001b[39m \u001b[43mops\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnon_max_suppression\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprediction\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpreds\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 49\u001b[0m \u001b[43m \u001b[49m\u001b[43mmask_coef\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mpreds\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 50\u001b[0m \u001b[43m \u001b[49m\u001b[43mproto\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mpreds\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 51\u001b[0m \u001b[43m \u001b[49m\u001b[43mimg_shape\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mimg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshape\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 52\u001b[0m \u001b[43m \u001b[49m\u001b[43mconf_thres\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconf\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 53\u001b[0m \u001b[43m \u001b[49m\u001b[43miou_thres\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43miou\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 54\u001b[0m \u001b[43m \u001b[49m\u001b[43magnostic\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43magnostic_nms\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 55\u001b[0m \u001b[43m \u001b[49m\u001b[43mmax_det\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmax_det\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 56\u001b[0m \u001b[43m \u001b[49m\u001b[43mnc\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnames\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 57\u001b[0m \u001b[43m \u001b[49m\u001b[43mregression_var\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mregression_preds\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 58\u001b[0m \u001b[43m \u001b[49m\u001b[43mclasses\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mclasses\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m#print(p[0].shape)\u001b[39;00m\n\u001b[1;32m 60\u001b[0m results \u001b[38;5;241m=\u001b[39m []\n", "File \u001b[0;32m/scratch/thomas/GitHub/ultralytics-custom/ultralytics/utils/ops.py:328\u001b[0m, in \u001b[0;36mnon_max_suppression\u001b[0;34m(prediction, mask_coef, proto, img_shape, regression_var, conf_thres, iou_thres, classes, agnostic, multi_label, labels, max_det, nc, max_time_img, max_nms, max_wh)\u001b[0m\n\u001b[1;32m 325\u001b[0m masks_bool_batch \u001b[38;5;241m=\u001b[39m masks_bool[batch_start:batch_end]\n\u001b[1;32m 327\u001b[0m \u001b[38;5;66;03m# Compute intersection and union for the current batch\u001b[39;00m\n\u001b[0;32m--> 328\u001b[0m intersection_batch \u001b[38;5;241m=\u001b[39m \u001b[43m(\u001b[49m\u001b[43mmasks_bool_batch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43munsqueeze\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m&\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mmasks_bool\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43munsqueeze\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfloat\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241m.\u001b[39msum(dim\u001b[38;5;241m=\u001b[39m(\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m2\u001b[39m))\n\u001b[1;32m 329\u001b[0m union_batch \u001b[38;5;241m=\u001b[39m (masks_bool_batch\u001b[38;5;241m.\u001b[39munsqueeze(\u001b[38;5;241m1\u001b[39m) \u001b[38;5;241m|\u001b[39m masks_bool\u001b[38;5;241m.\u001b[39munsqueeze(\u001b[38;5;241m0\u001b[39m))\u001b[38;5;241m.\u001b[39mfloat()\u001b[38;5;241m.\u001b[39msum(dim\u001b[38;5;241m=\u001b[39m(\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m2\u001b[39m))\n\u001b[1;32m 331\u001b[0m \u001b[38;5;66;03m# Calculate areas for IoMin\u001b[39;00m\n", "\u001b[0;31mKeyboardInterrupt\u001b[0m: " ] } ], "source": [ "import numpy as np\n", "import torch\n", "import os\n", "import json\n", "from PIL import Image\n", "import glob\n", "import gc # Import garbage collection module\n", "\n", "diceloss = CustomDiceLoss() # Assuming CustomDiceLoss is defined elsewhere\n", "tverskyloss = CustomTverskyLoss() # Assuming CustomTverskyLoss is defined elsewhere\n", "conf_values = [0.01, 0.025 ,0.05, 0.1]\n", "iou_values = [0.2,0.25,0.3,0.35,0.4,0.45, 0.5,0.55,0.60,0.65,0.7]\n", "threshold_values = [0.9*255]\n", "\n", "mean_dice_scores = {} # To store the mean dice coefficient for each configuration\n", "# Path to the dataset\n", "paths = [\n", " \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_ood_test/\",\n", "#\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_porour_right\"\n", " #\"/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/test\",\n", " #\"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_dehomo/\",\n", "\n", "# \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_simp_nov28\",\n", " # \"/home/thomas/Documents/scratch_thomas/GitHub/ultralytics-custom/ultralytics/dataset_simp_tiny\"\n", "]\n", "\n", "for path in paths:\n", " output_dir = os.path.join(path, \"yolo_results_sweep\")\n", " os.makedirs(output_dir, exist_ok=True)\n", "\n", " test_images = glob.glob(os.path.join(path, \"*.png\"))\n", "\n", " for threshold in threshold_values:\n", " for conf in conf_values:\n", " for iou in iou_values:\n", " dice_scores = [] # To store dice scores for the current configuration\n", "\n", " for image_path in test_images[:10]: # Limit to the first 1000 images\n", " img = Image.open(image_path).convert(\"L\")\n", " binary_image = img.point(lambda x: 255 if x > threshold else 0, '1')\n", " upscale_factor = 1.0\n", " binary_image = binary_image.resize(\n", " (int(binary_image.width * upscale_factor), int(binary_image.height * upscale_factor)),\n", " resample=Image.BICUBIC\n", " )\n", "\n", "\n", " with torch.no_grad():\n", " results = model(binary_image, conf=conf, iou=iou, imgsz=(binary_image.height, binary_image.width), max_det=600)\n", " prediction_tensor = results[0].regression_preds.to('cpu').detach()\n", "\n", " \n", "\n", "\n", " # Process the input image for comparison\n", " input_image_array = np.array(binary_image)\n", " #Threshold also input_image_array:\n", " input_image_array_tensor = torch.tensor(input_image_array)\n", " #convert from boolean to uint8:\n", " input_image_array_tensor = input_image_array_tensor.type(torch.uint8)\n", "\n", " input_image_array_tensor = 1.0 - input_image_array_tensor\n", " #Threshold with 0.5:\n", " input_image_array_tensor[input_image_array_tensor > 0.5] = 1\n", " input_image_array_tensor[input_image_array_tensor <= 0.5] = 0\n", "\n", " input_image_array_tensor = torch.flip(input_image_array_tensor, [0])\n", " # Assume calc_Phi and other necessary functions are defined and correct\n", " # Your processing logic here\n", " # Get the segmentation result\n", " for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", " #Check dimensions of the input image:\n", " DH = input_image_array.shape[0] / min(input_image_array.shape[1], input_image_array.shape[0])\n", " DW = input_image_array.shape[1] / min(input_image_array.shape[1], input_image_array.shape[0])\n", " aspect_ratio = DH / DW\n", " nelx = input_image_array.shape[1]-1\n", " nely = input_image_array.shape[0]-1\n", " x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", " LSgrid = torch.stack((x.flatten(), y.flatten()), dim=0)\n", "\n", " #Dimensions of the original mesh:\n", " nelx_original = int(200*DW)\n", " nely_original = int(200*DH)\n", " x_original, y_original = torch.meshgrid(torch.linspace(0, DW, nelx_original+1), torch.linspace(0, DH, nely_original+1))\n", " LSgrid_original = torch.stack((x_original.flatten(), y_original.flatten()), dim=0)\n", "\n", " # Create the constant tensors on the specified device\n", " pred_bboxes = results[0].boxes.xyxyn.to('cpu').detach()\n", " constant_tensor_02 = torch.full((pred_bboxes.shape[0],), 0.2)\n", " constant_tensor_00 = torch.full((pred_bboxes.shape[0],), 0.001)\n", " # Stack the tensors and move them to the specified device\n", " xmax = torch.stack([pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, constant_tensor_02], dim=1)\n", " xmin = torch.stack([pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, constant_tensor_00], dim=1)\n", "\n", " unnormalized_preds = prediction_tensor * (xmax - xmin) + xmin\n", "\n", " # # # The design variables are infered from the two endpoints and the two thicknesses:\n", " x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", " y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", " L = torch.sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", " L = L+1e-4\n", " t_1 = unnormalized_preds[:, 4]\n", "\n", " epsilon = 1e-10\n", " y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] + epsilon\n", " x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] + epsilon\n", " theta = torch.atan2(y_diff, x_diff)\n", " formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "\n", "\n", " pred_Phi,pred_H = calc_Phi(formatted_variables.T,LSgrid)\n", " sum_pred_H = torch.sum(pred_H, dim=1)\n", " sum_pred_H[sum_pred_H> 1] = 1\n", " #Final H:\n", " # Final H with positive strides\n", " final_H = np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F'))\n", "\n", " # Calculate the dice loss\n", " dice_loss = diceloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", " dice_scores.append(1 - dice_loss.item()) # Store the complement of dice loss as the dice score\n", "\n", "\n", " mean_dice_score = np.mean(dice_scores)\n", " mean_dice_scores[(conf, iou, threshold)] = mean_dice_score\n", " print(f\"Conf: {conf}, IOU: {iou}, Threshold: {threshold}, Mean Dice Score: {mean_dice_score}\")\n", "\n", " # Clear memory\n", " gc.collect()\n", " if torch.cuda.is_available():\n", " torch.cuda.empty_cache()\n", "\n", "# Determine the best configuration\n", "best_conf_iou_threshold = max(mean_dice_scores, key=mean_dice_scores.get)\n", "best_score = mean_dice_scores[best_conf_iou_threshold]\n", "print(f\"Best configuration: Conf: {best_conf_iou_threshold[0]}, IOU: {best_conf_iou_threshold[1]}, Threshold: {best_conf_iou_threshold[2]} with Mean Dice Score: {best_score}\")\n", "\n", "# Save results\n", "mean_dice_scores_str_keys = {f\"conf_{conf}_iou_{iou}_threshold_{threshold}\": score for (conf, iou, threshold), score in mean_dice_scores.items()}\n", "with open(os.path.join(output_dir, \"mean_dice_scores_with_thresholds.json\"), 'w') as f:\n", " json.dump(mean_dice_scores_str_keys, f)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "path = \"/home/thomas/Documents/scratch_thomas/yolov8_dataset/dataset_combined_feb2/test/\"\n", "json_files = glob.glob(path + \"*.png\")\n", "len(json_files)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "\n", "from matplotlib.colors import TwoSlopeNorm\n", "\n", "norm = TwoSlopeNorm(vmin=-10, vcenter=0, vmax=10)\n", "# Load the input image\n", "#input_image_path = test_path + '.png'\n", "input_image_path = image_path\n", "input_image = Image.open(input_image_path)\n", "# input_image = upscaled_image\n", "#I want to create a numpy array of the grayscale input image where black is 1 and white is 0:\n", "input_image_array = np.array(input_image.convert('L'))\n", "# input_image_array is of shape (480,640) and pred_H_resized is of shape (1,1,160,160):\n", "# I want to calculate the dice loss between the two:\n", "# I first need to resize the input_image_array to 160,160 using F.interpolate\n", "input_image_array_tensor = torch.tensor(input_image_array)\n", "\n", "resized_input_image_array_tensor = F.interpolate(input_image_array_tensor.unsqueeze(0).unsqueeze(0), size=torch.Size([640,640]), mode='nearest')\n", "\n", "# Normalize the resized_input_image_array_tensor:\n", "resized_input_image_array_tensor = resized_input_image_array_tensor / 255.0\n", "\n", "#Now the input_image has pixel values of 1.0 for white and 0.0 for black. I need to inverse that:\n", "resized_input_image_array_tensor = 1.0 - resized_input_image_array_tensor\n", "\n", "\n", "#I need to flip upside down the resized_input_image_array_tensor:\n", "resized_input_image_array_tensor = torch.flip(resized_input_image_array_tensor, [2])\n", "\n", "results = model(input_image, imgsz=640)\n", "\n", "# Get the segmentation result\n", "for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", "DH = 1.0\n", "DW = 1.0\n", "\n", "nelx = int(400 * DW)\n", "nely = int(400 * DH)\n", "\n", "x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", "LSgrid = torch.stack((y.flatten(), x.flatten()), dim=0)\n", "\n", "#xmax = torch.tensor([DW, DH, np.sqrt(DW**2 + DH**2), 0.05 * min(DW, DH), 0.05 * min(DW, DH), np.pi])\n", "xmax = torch.tensor([1.0, 1.0, 1.0, 1.0, 0.2, 0.2])\n", "\n", "xmin = torch.tensor([0.0, 0.0, 0.0, 0.0, 0.001, 0.001])\n", "\n", "\n", "xmax_preds = xmax.unsqueeze(0).expand(prediction_tensor.shape[0],-1) \n", "xmin_preds = xmin.unsqueeze(0).expand(prediction_tensor.shape[0],-1)\n", "\n", "xmax = xmax.unsqueeze(0).expand(8, -1) \n", "xmin = xmin.unsqueeze(0).expand(8, -1) \n", "unnormalized_preds = prediction_tensor * (xmax_preds - xmin_preds) + xmin_preds\n", "# # # The design variables are infered from the two endpoints and the two thicknesses:\n", "x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", "y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", "L = torch.sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", "L = L+1e-4\n", "t_1 = unnormalized_preds[:, 4]\n", "t_2 = unnormalized_preds[:, 5]\n", "\n", "epsilon = 1e-10\n", "y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] + epsilon\n", "x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] + epsilon\n", "theta = torch.atan2(y_diff, x_diff)\n", "formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " t_2.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "\n", "pred_Phi,pred_H = calc_Phi(formatted_variables.T,LSgrid)\n", "\n", "\n", "sum_pred_H = torch.sum(pred_H, dim=1)\n", "sum_pred_H= torch.reshape(sum_pred_H,(1,1,nely+1,nelx+1))\n", "\n", "# # Rearrange H_phi to the shape ([batch_size, channels, height, width])\n", "# # Use interpolate to resize\n", "pred_H_resized = F.interpolate(sum_pred_H, size=torch.Size([640,640]), mode='nearest')\n", "\n", "diceloss = CustomDiceLoss()\n", "pred_H_resized_flipped = torch.flip(pred_H_resized, [2])\n", "\n", "#threshold both the prediction and the label with 0.5:\n", "pred_H_resized_flipped[pred_H_resized_flipped > 0.5] = 1\n", "pred_H_resized_flipped[pred_H_resized_flipped <= 0.5] = 0\n", "# Calculate the dice loss:\n", "dice_loss = diceloss(pred_H_resized_flipped, resized_input_image_array_tensor)\n", "\n", "\n", "# Create a combined 2x2 plot\n", "fig, axes = plt.subplots(2, 2, figsize=(12, 12)) # 2x2 subplot\n", "\n", "# Top-left: Input image\n", "axes[0, 0].imshow(resized_input_image_array_tensor.squeeze(),origin='lower', cmap='gray_r')\n", "axes[0, 0].set_title('Input Image')\n", "axes[0, 0].axis('on')\n", "\n", "# Top-right: Segmentation Result\n", "axes[0, 1].imshow(seg_result)\n", "axes[0, 1].set_title('Segmentation Result')\n", "axes[0, 1].axis('off')\n", "\n", "# # Bottom-right: pred_Phi contour\n", "render_colors1 = ['yellow', 'g', 'r', 'c', 'm', 'y', 'black', 'orange', 'pink', 'cyan', 'slategrey', 'wheat', 'purple', 'mediumturquoise', 'darkviolet', 'orangered']\n", "for i, color in zip(range(0, pred_Phi.shape[1]), render_colors1*10):\n", " axes[1, 1].contourf(np.flipud(pred_Phi[:, i].reshape((nely+1, nelx+1))), [-0.1,0,1], colors=color)\n", "axes[1, 1].set_title('Prediction contours')\n", "\n", "# Bottom-left: Prediction H\n", "axes[1, 0].imshow(pred_H_resized_flipped.squeeze().detach().numpy(), origin='lower', cmap='gray_r')\n", "axes[1, 0].set_title('Prediction Projection')\n", "plt.subplots_adjust(hspace=0.3, wspace=0.01) # Adjust hspace to reduce vertical space\n", "\n", "plt.figtext(0.5, 0.05, f'Dice Loss: {dice_loss.item():.4f}', ha='center', fontsize=16)\n", "\n", "\n", "# Save the combined plot\n", "fig.savefig('combined_plots.png')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def Heaviside(phi, alpha, epsilon):\n", " device = phi.device # Get the device of phi\n", "\n", " # For values outside of [-epsilon, epsilon]\n", " H_positive = torch.ones_like(phi, device=device) \n", " H_negative = alpha * torch.ones_like(phi, device=device)\n", "\n", " # For values inside [-epsilon, epsilon]\n", " default = 3 * (1 - alpha) / 4 * (phi / epsilon - phi**3 / (3 * epsilon**3)) + (1 + alpha) / 2\n", "\n", " # Construct Heavisidve using conditions\n", " H = torch.where(phi > epsilon, H_positive, torch.where(phi < -epsilon, H_negative, default))\n", " return H\n", "\n", "def smooth_heaviside(phi, alpha, epsilon):\n", " # Scale and shift phi for the sigmoid function\n", " scaled_phi = (phi - alpha) / epsilon\n", " \n", " # Apply the sigmoid function\n", " H = torch.sigmoid(scaled_phi)\n", "\n", " return H\n", "\n", "def calc_Phi(variable, LSgrid):\n", " device = variable.device # Get the device of the variable\n", "\n", " x0 = variable[0]\n", " y0 = variable[1]\n", " L = variable[2]\n", " t1 = variable[3]\n", " t2 = variable[4]\n", " angle = variable[5]\n", "\n", " # Rotation\n", " st = torch.sin(angle)\n", " ct = torch.cos(angle)\n", " x1 = ct * (LSgrid[0][:, None].to(device) - x0) + st * (LSgrid[1][:, None].to(device) - y0) \n", " y1 = -st * (LSgrid[0][:, None].to(device) - x0) + ct * (LSgrid[1][:, None].to(device) - y0)\n", "\n", " # Regularized hyperellipse equation\n", " a = L / 2 # Semi-major axis\n", " b = (t1 + t2) / 2 # Semi-minor axis\n", " small_constant = 1e-9 # To avoid division by zero\n", " temp = ((x1 / (a + small_constant))**6) + ((y1 / (b + small_constant))**6)\n", "\n", " # # Ensuring the hyperellipse shape\n", " allPhi = 1 - (temp + small_constant)**(1/6)\n", "\n", " threshold = 0.01\n", " # # Call Heaviside function with allPhi\n", " alpha = torch.tensor(-threshold, device=device, dtype=torch.float32)\n", " epsilon = torch.tensor(threshold, device=device, dtype=torch.float32)\n", " H_phi = smooth_heaviside(allPhi, alpha, epsilon)\n", " return allPhi, H_phi\n", "\n", "\n", "import torch\n", "from torch.autograd import gradcheck\n", "import numpy as np\n", "# Your calc_Phi function here\n", "\n", "# Preparing the grid as in your example\n", "DW = 1.0\n", "DH = 1.0\n", "nelx = int(200 * DW)\n", "nely = int(200 * DH)\n", "x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1), indexing='xy')\n", "LSgrid = torch.stack((y.flatten(), x.flatten()), dim=0).to(torch.float64)\n", "\n", "# Ensure the test_tensor and p require gradients\n", "test_tensor = torch.tensor([0.0, 0.0, 0.99, 0.99, 0.3, 0.3], requires_grad=True,dtype=torch.float64).unsqueeze(0)\n", "\n", "\n", "# xmax = torch.tensor([1.0, 1.0, 0.75, 0.2, 0.2, np.pi])\n", "\n", "# xmin = torch.tensor([0.0, 0.0, 0.01, 0.01, 0.00, 0.0])\n", "\n", "xmax = torch.tensor([0.75, 0.75, 0.75, 0.75, 0.2, 0.2])\n", "\n", "xmin = torch.tensor([0.25, 0.25, 0.25, 0.25, 0.001, 0.001])\n", "\n", "unnormalized_preds = test_tensor * (xmax - xmin) + xmin\n", "# The design variables are infered from the two endpoints and the two thicknesses:\n", "x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", "y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", "def safe_sqrt(x, eps=1e-6):\n", " return torch.sqrt(x + eps)\n", "\n", "L = safe_sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", "# gradcheck_result = gradcheck(safe_sqrt, ((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", "# (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2))\n", "# print(\"Gradcheck passed:\", gradcheck_result)\n", "\n", "L = L+1e-4\n", "t_1 = unnormalized_preds[:, 4]\n", "t_2 = unnormalized_preds[:, 5]\n", "\n", "y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] \n", "x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] \n", "theta = torch.atan2(y_diff, x_diff)\n", "\n", "formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " t_2.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "# # # # # # # Call gradcheck\n", "# gradcheck_result = gradcheck(calc_Phi, (unnormalized_preds.T, LSgrid))\n", "\n", "# print(\"Gradcheck passed:\", gradcheck_result)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "print(unnormalized_preds)\n", "# pred_Phi, H_phi = calc_Phi(unnormalized_preds.T, LSgrid)\n", "pred_phi, H_phi = calc_Phi(formatted_variables.T, LSgrid)\n", "\n", "H_phi = H_phi.detach()\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "H_phi = H_phi.detach().reshape((nely+1, nelx+1)) + np.flipud(H_phi.detach().reshape((nely+1, nelx+1)))\n", "\n", "#Threshold everything over 1.0:\n", "H_phi[H_phi > 1.0] = 1.0\n", "\n", "plt.imshow(H_phi, cmap='gray_r', origin='lower')\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import numpy as np\n", "DW = 1.0\n", "DH = 1.0\n", "\n", "test_tensor = torch.tensor([0.5, 0.5, 0.5, 1.0, 1.0, 1.0]).unsqueeze(0)\n", "nelx = int(200 * DW)\n", "nely = int(200 * DH)\n", "\n", "x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", "LSgrid = torch.stack((y.flatten(), x.flatten()), dim=0)\n", "\n", "#xmax = torch.tensor([DW, DH, np.sqrt(DW**2 + DH**2), 0.05 * min(DW, DH), 0.05 * min(DW, DH), np.pi])\n", "xmax = torch.tensor([1.0, 1.0, 0.75, 0.05, 0.05, np.pi])\n", "\n", "xmin = torch.tensor([0.0, 0.0, 0.001, 0.0, 0.0, 0.0])\n", "\n", "\n", "\n", "xmax_preds = xmax.unsqueeze(0).expand(test_tensor.shape[0],-1) \n", "xmin_preds = xmin.unsqueeze(0).expand(test_tensor.shape[0],-1)\n", "\n", "xmax = xmax.unsqueeze(0).expand(8, -1) \n", "xmin = xmin.unsqueeze(0).expand(8, -1) \n", "\n", "unnormalized_preds = test_tensor * (xmax_preds - xmin_preds) + xmin_preds\n", "\n", "\n", "pred_Phi,pred_H = calc_Phi(unnormalized_preds.T,LSgrid,6,epsilon=0.2)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Assuming pred_Phi is defined somewhere in your code\n", "# normalized = (pred_Phi[:,2] - pred_Phi[:,2].min()) / (pred_Phi[:,2].max() - pred_Phi[:,2].min())\n", "\n", "# Create a meshgrid for plotting\n", "x = np.linspace(0, 1, nelx+1)\n", "y = np.linspace(0, 1, nely+1)\n", "X, Y = np.meshgrid(x, y)\n", "\n", "# Create a figure and 3D axis\n", "fig = plt.figure()\n", "ax = fig.add_subplot(111, projection='3d')\n", "\n", "# Plot the surface\n", "surf = ax.plot_surface(X, Y, np.minimum(pred_Phi[:,1],0.0).reshape((nely+1, nelx+1)), cmap='viridis')\n", "\n", "# Add colorbar\n", "fig.colorbar(surf)\n", "\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import plotly.graph_objects as go\n", "import numpy as np\n", "\n", "# Assuming you've defined and normalized your data as before...\n", "\n", "\n", "\n", "# Create the 3D surface plot\n", "fig = go.Figure(data=[go.Surface(z=np.minimum(pred_Phi[:,2],0.0).reshape((nely+1, nelx+1)), x=X, y=Y, colorscale='viridis')])\n", "\n", "fig.update_layout(title='3D Surface Plot', scene=dict(zaxis=dict(range=[-10,1])),height=800)\n", "\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Assuming pred_H_resized_flipped and label_H_resized are thresholded to contain values 0 or 1.\n", "\n", "# 1. Compute the intersection\n", "intersection = pred_H_resized_flipped * label_H_resized\n", "\n", "# 2. Plot the original images and their intersection\n", "\n", "# Create a combined 3x1 plot\n", "fig, axes = plt.subplots(1, 3, figsize=(20, 12)) # 3x1 subplot to account for colorbars\n", "\n", "# Left: label_H_resized\n", "im0 = axes[0].imshow(label_H_resized[0,0,:,:], origin='lower')\n", "axes[0].set_title('Ground Truth')\n", "#fig.colorbar(im0, ax=axes[0])\n", "\n", "# Middle: pred_H_resized_flipped\n", "im1 = axes[1].imshow(pred_H_resized_flipped[0,0,:,:], origin='lower')\n", "axes[1].set_title('Prediction')\n", "#fig.colorbar(im1, ax=axes[1])\n", "\n", "# Right: Intersection\n", "im2 = axes[2].imshow(intersection[0,0,:,:], origin='lower')\n", "axes[2].set_title('Intersection')\n", "#fig.colorbar(im2, ax=axes[2])\n", "\n", "# Show the plot\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "\n", "# Load the input image\n", "input_image_path = test_path + '.png'\n", "input_image = Image.open(input_image_path)\n", "\n", "# Get the segmentation result\n", "results = model(input_image_path, conf=0.25)\n", "for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", "# Create a combined 1x2 plot\n", "fig, axes = plt.subplots(1, 2, figsize=(12, 6)) # 1x2 subplot\n", "\n", "# Left: Input image\n", "axes[0].imshow(input_image)\n", "axes[0].set_title('Input Image')\n", "axes[0].axis('off')\n", "\n", "# Right: Segmentation Result\n", "axes[1].imshow(seg_result)\n", "axes[1].set_title('Segmentation Result')\n", "axes[1].axis('off')\n", "\n", "plt.subplots_adjust(hspace=0.01)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## Paper inference code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import glob\n", "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "import json\n", "from skimage.transform import resize\n", "import numpy as np\n", "\n", "path = \"/home/thomas/Documents/GitHub/ultralytics-custom/ultralytics/dataset_mmconly_dec4/test/\"\n", "selected_filename = [\"20231121-064941-226506\", \"20231121-102533-047413\"]\n", "\n", "test_images = path + selected_filename[0] + \".png\"\n", "\n", "#Load the corresponding .json file:\n", "\n", "with open('dataset_mmconly_dec4/test/{}.json'.format(selected_filename[0])) as f:\n", " data = json.load(f)\n", "\n", "#Load the corresponding .png file:\n", "\n", "original_H = np.array(data['h']).reshape((data['nely']+1, data['nelx']+1), order='F')\n", "original_H[original_H > 0.1] = 1\n", "original_H[original_H <= 0.1] = 0\n", "# Save original_H as a .png:\n", "img = Image.fromarray((1-original_H).astype(np.uint8)*255)\n", "\n", "results = model(test_images, conf=0.25, iou=0.7, imgsz=640)\n", "\n", "for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1) # plot a BGR numpy array of predictions\n", " plt.imshow(im_array[..., ::-1]) # Convert BGR to RGB and display using matplotlib\n", " #plt.imshow(resize(im_array[..., ::-1], (data['nely'],data['nelx']),preserve_range=False, anti_aliasing=False))\n", " plt.axis('on') # Turn off axis\n", " plt.grid()\n", " plt.show() # Display the image in the notebook\n", "\n", " # Save the image if needed\n", " im = Image.fromarray(im_array[..., ::-1]) # Convert to RGB PIL image\n", " #im.save('results.jpg', #dpi=(1200, 1200)) # Save image\n", "\n", "prediction_tensor = results[0].regression_preds.to('cpu').detach()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import numpy as np\n", "import torch.nn.functional as F\n", "import torch.nn as nn\n", "class CustomDiceLoss(nn.Module):\n", " def __init__(self, weight=None, size_average=True):\n", " super(CustomDiceLoss, self).__init__()\n", " self.size_average = size_average\n", " def forward(self, inputs, targets, smooth=1):\n", " \n", " # If your model contains a sigmoid or equivalent activation layer, comment this line\n", " #inputs = F.sigmoid(inputs) \n", " \n", " # Check if the input tensors are of expected shape\n", " if inputs.shape != targets.shape:\n", " raise ValueError(\"Shape mismatch: inputs and targets must have the same shape\")\n", "\n", " # Compute Dice loss for each sample in the batch\n", " dice_loss_values = []\n", " for input_sample, target_sample in zip(inputs, targets):\n", " \n", " # Flatten tensors for each sample\n", " input_sample = input_sample.view(-1)\n", " target_sample = target_sample.view(-1)\n", "\n", " intersection = (input_sample * target_sample).sum()\n", " dice = (2. * intersection + smooth) / (input_sample.sum() + target_sample.sum() + smooth)\n", " \n", " dice_loss_values.append(1 - dice)\n", "\n", " # Convert list of Dice loss values to a tensor\n", " dice_loss_values = torch.stack(dice_loss_values)\n", "\n", " # If you want the average loss over the batch to be returned\n", " if self.size_average:\n", " return dice_loss_values.mean()\n", " else:\n", " # If you want individual losses for each sample in the batch\n", " return dice_loss_values\n", "\n", "def smooth_heaviside(phi, alpha, epsilon):\n", " # Scale and shift phi for the sigmoid function\n", " scaled_phi = (phi - alpha) / epsilon\n", " \n", " # Apply the sigmoid function\n", " H = torch.sigmoid(scaled_phi)\n", "\n", " return H\n", "def calc_Phi(variable, LSgrid):\n", " device = variable.device # Get the device of the variable\n", "\n", " x0 = variable[0]\n", " y0 = variable[1]\n", " L = variable[2]\n", " t1 = variable[3]\n", " t2 = variable[4]\n", " angle = variable[5]\n", "\n", " # Rotation\n", " st = torch.sin(angle)\n", " ct = torch.cos(angle)\n", " x1 = ct * (LSgrid[0][:, None].to(device) - x0) + st * (LSgrid[1][:, None].to(device) - y0) \n", " y1 = -st * (LSgrid[0][:, None].to(device) - x0) + ct * (LSgrid[1][:, None].to(device) - y0)\n", "\n", " # Regularized hyperellipse equation\n", " a = L / 2 # Semi-major axis\n", " b = (t1 + t2) / 2 # Semi-minor axis\n", " small_constant = 1e-9 # To avoid division by zero\n", " temp = ((x1 / (a + small_constant))**6) + ((y1 / (b + small_constant))**6)\n", "\n", " # # Ensuring the hyperellipse shape\n", " allPhi = 1 - (temp + small_constant)**(1/6)\n", "\n", " # # Call Heaviside function with allPhi\n", " alpha = torch.tensor(1e-9, device=device, dtype=torch.float32)\n", " epsilon = torch.tensor(0.05, device=device, dtype=torch.float32)\n", " H_phi = smooth_heaviside(allPhi, alpha, epsilon)\n", " return allPhi, H_phi\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import torch\n", "\n", "# Assuming the necessary functions and variables are defined elsewhere\n", "# (e.g., `calc_Phi`, `CustomDiceLoss`, `results`, `prediction_tensor`)\n", "\n", "\n", "# Load the input image\n", "input_image_path = test_images\n", "input_image = Image.open(input_image_path)\n", "input_image_array = np.array(input_image.convert('L'))\n", "\n", "# Preprocess the input image array as required\n", "input_image_array_tensor = torch.tensor(input_image_array) / 255.0\n", "input_image_array_tensor = 1.0 - input_image_array_tensor\n", "input_image_array_tensor = torch.flip(input_image_array_tensor, [0])\n", "\n", "# Get the segmentation result\n", "for r in results:\n", " im_array = r.plot(boxes=True, labels=False, line_width=1)\n", " seg_result = Image.fromarray(im_array[..., ::-1])\n", "\n", "DH = input_image_array.shape[0] / min(input_image_array.shape[1], input_image_array.shape[0])\n", "DW = input_image_array.shape[1] / min(input_image_array.shape[1], input_image_array.shape[0])\n", "aspect_ratio = DH / DW\n", "nelx = input_image_array.shape[1]-1\n", "nely = input_image_array.shape[0]-1\n", "\n", "x, y = torch.meshgrid(torch.linspace(0, DW, nelx+1), torch.linspace(0, DH, nely+1))\n", "LSgrid = torch.stack((x.flatten(), y.flatten()), dim=0)\n", "# Create the constant tensors on the specified device\n", "pred_bboxes = results[0].boxes.xyxyn.to('cpu').detach()\n", "constant_tensor_02 = torch.full((pred_bboxes.shape[0],), 0.2)\n", "constant_tensor_00 = torch.full((pred_bboxes.shape[0],), 0.001)\n", "# Stack the tensors and move them to the specified device\n", "xmax = torch.stack([pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, pred_bboxes[:,2]*DW, pred_bboxes[:,3]*DH, constant_tensor_02, constant_tensor_02], dim=1)\n", "xmin = torch.stack([pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, pred_bboxes[:,0]*DW, pred_bboxes[:,1]*DH, constant_tensor_00, constant_tensor_00], dim=1)\n", "\n", "unnormalized_preds = prediction_tensor * (xmax - xmin) + xmin\n", "\n", "# # # The design variables are infered from the two endpoints and the two thicknesses:\n", "x_center = (unnormalized_preds[:, 0] + unnormalized_preds[:, 2]) / 2\n", "y_center = (unnormalized_preds[:, 1] + unnormalized_preds[:, 3]) / 2\n", "\n", "L = torch.sqrt((unnormalized_preds[:, 0] - unnormalized_preds[:, 2])**2 + \n", " (unnormalized_preds[:, 1] - unnormalized_preds[:, 3])**2)\n", "\n", "L = L+1e-4\n", "t_1 = unnormalized_preds[:, 4]\n", "t_2 = unnormalized_preds[:, 5]\n", "\n", "epsilon = 1e-10\n", "y_diff = unnormalized_preds[:, 3] - unnormalized_preds[:, 1] + epsilon\n", "x_diff = unnormalized_preds[:, 2] - unnormalized_preds[:, 0] + epsilon\n", "theta = torch.atan2(y_diff, x_diff)\n", "formatted_variables = torch.cat((x_center.unsqueeze(1), \n", " y_center.unsqueeze(1), \n", " L.unsqueeze(1), \n", " t_1.unsqueeze(1), \n", " t_2.unsqueeze(1), \n", " theta.unsqueeze(1)), dim=1)\n", "\n", "pred_Phi,pred_H = calc_Phi(formatted_variables.T,LSgrid)\n", "\n", "sum_pred_H = torch.sum(pred_H, dim=1)\n", "\n", "# # Rearrange H_phi to the shape ([batch_size, channels, height, width])\n", "# # Use interpolate to resize\n", "\n", "diceloss = CustomDiceLoss()\n", "#sum_pred_H = torch.flip(sum_pred_H, [0])\n", "#threshold both the prediction and the label with 0.5:\n", "sum_pred_H[sum_pred_H> 1e-1] = 1\n", "sum_pred_H[sum_pred_H <= 1e-1] = 0\n", "\n", "\n", "#Final H:\n", "final_H = np.flipud(sum_pred_H.detach().numpy().reshape((nely+1,nelx+1),order='F'))\n", "# Calculate the dice loss:\n", "dice_loss = diceloss(torch.tensor(final_H.copy()), input_image_array_tensor)\n", "\n", "# Create a plot for Prediction contours\n", "fig, ax = plt.subplots(figsize=(6, 6))\n", "\n", "render_colors1 = ['yellow', 'g', 'r', 'c', 'm', 'y', 'black', 'orange', 'pink', 'cyan', 'slategrey', 'wheat', 'purple', 'mediumturquoise', 'darkviolet', 'orangered']\n", "for i, color in zip(range(0, pred_Phi.shape[1]), render_colors1*10):\n", " ax.contourf((pred_Phi[:, i].numpy().reshape((nely+1,nelx+1),order='F')), [-0.1,0,1], colors=color)\n", "\n", "# ax.set_title('Prediction contours')\n", "ax.set_aspect('equal') # Set the aspect ratio to be equal\n", "ax.grid()\n", "ax.invert_yaxis()\n", "ax.axis('on')\n", "\n", "# Save the plot\n", "fig.savefig('prediction_contours.png')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(1-dice_loss)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.imshow(original_top)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from evaluation import calculate_compliance_simp\n", "compliance_original, _, _ = calculate_compliance_simp(original_top, data['nelx'], data['nely'], data['boundary_conditions'])\n", "compliance_original" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "prediction_H_resized = resize(np.flipud(final_H), original_top.shape, anti_aliasing=False)\n", "\n", "plt.imshow(prediction_H_resized)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "compliance_pred, _, _ = calculate_compliance_simp(prediction_H_resized, data['nelx'], data['nely'], data['boundary_conditions'])\n", "compliance_pred" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_volume = np.mean(original_top)\n", "pred_volume = np.mean(prediction_H_resized)\n", "\n", "print((pred_volume - original_volume)/original_volume)\n", "print((compliance_pred-compliance_original)/compliance_original) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.colors as mcolors\n", "\n", "# Create a figure and axis\n", "fig, ax = plt.subplots()\n", "\n", "# Set the background color of the axis\n", "ax.set_facecolor('white')\n", "\n", "# Display the grayscale image\n", "plt.imshow(1-original_top, cmap='gray')\n", "\n", "# Create a red colormap\n", "cmap = mcolors.LinearSegmentedColormap.from_list(\"\", [\"white\", \"red\"])\n", "\n", "# Overlay 'interp_image' with red color and some transparency\n", "plt.imshow(prediction_H_resized, cmap=cmap, alpha=0.5) # Adjust alpha for transparency\n", "\n", "# If you want to disable the axis grid\n", "plt.grid()\n", "\n", "plt.savefig('test.png', dpi=300)\n", "# Show the plot\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "customyolo", "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.11.7" } }, "nbformat": 4, "nbformat_minor": 4 }