mblt-vision-python 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mblt_vision/__init__.py +86 -0
- mblt_vision/_api.py +53 -0
- mblt_vision/_compat.py +227 -0
- mblt_vision/_model_paths.py +137 -0
- mblt_vision/_tasks.py +30 -0
- mblt_vision/benchmark/__init__.py +1 -0
- mblt_vision/benchmark/argparse_utils.py +106 -0
- mblt_vision/benchmark/chart_utils.py +146 -0
- mblt_vision/benchmark/io_utils.py +64 -0
- mblt_vision/benchmark/summary_utils.py +770 -0
- mblt_vision/cli/__init__.py +5 -0
- mblt_vision/cli/__main__.py +5 -0
- mblt_vision/cli/_vision.py +337 -0
- mblt_vision/cli/compile.py +117 -0
- mblt_vision/cli/main.py +35 -0
- mblt_vision/cli/predict.py +64 -0
- mblt_vision/cli/val.py +562 -0
- mblt_vision/compile/__init__.py +21 -0
- mblt_vision/compile/vision.py +1120 -0
- mblt_vision/datasets/__init__.py +15 -0
- mblt_vision/datasets/ade20k.yaml +164 -0
- mblt_vision/datasets/cityscapes.yaml +54 -0
- mblt_vision/datasets/coco.yaml +178 -0
- mblt_vision/datasets/dotav1.yaml +28 -0
- mblt_vision/datasets/imagenet.yaml +1019 -0
- mblt_vision/datasets/imagenet_synsets.txt +1000 -0
- mblt_vision/datasets/nyu-depth.yaml +7 -0
- mblt_vision/datasets/registry.py +158 -0
- mblt_vision/datasets/widerface.yaml +9 -0
- mblt_vision/depth_estimation/__init__.py +19 -0
- mblt_vision/face_detection/__init__.py +43 -0
- mblt_vision/image_classification/__init__.py +311 -0
- mblt_vision/instance_segmentation/__init__.py +65 -0
- mblt_vision/models/AlexNet.yaml +23 -0
- mblt_vision/models/CAFormer_B36.yaml +22 -0
- mblt_vision/models/CAFormer_M36.yaml +22 -0
- mblt_vision/models/CAFormer_S18.yaml +22 -0
- mblt_vision/models/CAFormer_S36.yaml +22 -0
- mblt_vision/models/CoAtNet_0_RW_224.yaml +22 -0
- mblt_vision/models/CoAtNet_1_RW_224.yaml +22 -0
- mblt_vision/models/CoAtNet_2_RW_224.yaml +22 -0
- mblt_vision/models/ConvFormer_B36.yaml +22 -0
- mblt_vision/models/ConvFormer_M36.yaml +22 -0
- mblt_vision/models/ConvFormer_S18.yaml +22 -0
- mblt_vision/models/ConvFormer_S36.yaml +22 -0
- mblt_vision/models/ConvNext_Base.yaml +23 -0
- mblt_vision/models/ConvNext_Large.yaml +23 -0
- mblt_vision/models/ConvNext_Small.yaml +23 -0
- mblt_vision/models/ConvNext_Tiny.yaml +23 -0
- mblt_vision/models/DeiT3_Base_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT3_Base_Patch16_384.yaml +22 -0
- mblt_vision/models/DeiT3_Large_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT3_Large_Patch16_384.yaml +22 -0
- mblt_vision/models/DeiT3_Medium_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT3_Small_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT3_Small_Patch16_384.yaml +22 -0
- mblt_vision/models/DeiT_Base_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT_Base_Patch16_384.yaml +22 -0
- mblt_vision/models/DeiT_Small_Patch16_224.yaml +22 -0
- mblt_vision/models/DeiT_Tiny_Patch16_224.yaml +22 -0
- mblt_vision/models/DenseNet121.yaml +23 -0
- mblt_vision/models/DenseNet161.yaml +23 -0
- mblt_vision/models/DenseNet169.yaml +23 -0
- mblt_vision/models/DenseNet201.yaml +23 -0
- mblt_vision/models/EfficientFormer_L1.yaml +22 -0
- mblt_vision/models/EfficientFormer_L3.yaml +22 -0
- mblt_vision/models/EfficientFormer_L7.yaml +22 -0
- mblt_vision/models/EfficientNet_B0.yaml +23 -0
- mblt_vision/models/EfficientNet_B1.yaml +32 -0
- mblt_vision/models/EfficientNet_B2.yaml +23 -0
- mblt_vision/models/EfficientNet_B3.yaml +23 -0
- mblt_vision/models/EfficientNet_B4.yaml +23 -0
- mblt_vision/models/EfficientNet_B5.yaml +23 -0
- mblt_vision/models/EfficientNet_B6.yaml +23 -0
- mblt_vision/models/EfficientNet_B7.yaml +23 -0
- mblt_vision/models/EfficientNet_V2_L.yaml +23 -0
- mblt_vision/models/EfficientNet_V2_M.yaml +23 -0
- mblt_vision/models/EfficientNet_V2_S.yaml +23 -0
- mblt_vision/models/FlexiVit_Base.yaml +22 -0
- mblt_vision/models/FlexiVit_Large.yaml +22 -0
- mblt_vision/models/FlexiVit_Small.yaml +22 -0
- mblt_vision/models/GELANc-seg.yaml +29 -0
- mblt_vision/models/GELANc.yaml +28 -0
- mblt_vision/models/GELANe.yaml +28 -0
- mblt_vision/models/GELANm.yaml +28 -0
- mblt_vision/models/GELANs.yaml +28 -0
- mblt_vision/models/GoogLeNet.yaml +23 -0
- mblt_vision/models/Inception_V3.yaml +23 -0
- mblt_vision/models/LeViT_Conv_128.yaml +22 -0
- mblt_vision/models/LeViT_Conv_128S.yaml +22 -0
- mblt_vision/models/LeViT_Conv_192.yaml +22 -0
- mblt_vision/models/LeViT_Conv_256.yaml +22 -0
- mblt_vision/models/LeViT_Conv_384.yaml +22 -0
- mblt_vision/models/MNASNet0_5.yaml +23 -0
- mblt_vision/models/MNASNet0_75.yaml +23 -0
- mblt_vision/models/MNASNet1_0.yaml +23 -0
- mblt_vision/models/MNASNet1_3.yaml +23 -0
- mblt_vision/models/MobileNet_V2.yaml +32 -0
- mblt_vision/models/MobileNet_V3_Large.yaml +32 -0
- mblt_vision/models/MobileNet_V3_Small.yaml +23 -0
- mblt_vision/models/RegNet_X_16GF.yaml +32 -0
- mblt_vision/models/RegNet_X_1_6GF.yaml +32 -0
- mblt_vision/models/RegNet_X_32GF.yaml +32 -0
- mblt_vision/models/RegNet_X_3_2GF.yaml +32 -0
- mblt_vision/models/RegNet_X_400MF.yaml +32 -0
- mblt_vision/models/RegNet_X_800MF.yaml +32 -0
- mblt_vision/models/RegNet_X_8GF.yaml +32 -0
- mblt_vision/models/RegNet_Y_16GF.yaml +76 -0
- mblt_vision/models/RegNet_Y_1_6GF.yaml +32 -0
- mblt_vision/models/RegNet_Y_32GF.yaml +76 -0
- mblt_vision/models/RegNet_Y_3_2GF.yaml +32 -0
- mblt_vision/models/RegNet_Y_400MF.yaml +32 -0
- mblt_vision/models/RegNet_Y_800MF.yaml +32 -0
- mblt_vision/models/RegNet_Y_8GF.yaml +32 -0
- mblt_vision/models/RepViT_M0_9.yaml +22 -0
- mblt_vision/models/RepViT_M1.yaml +22 -0
- mblt_vision/models/RepViT_M1_0.yaml +22 -0
- mblt_vision/models/RepViT_M1_1.yaml +22 -0
- mblt_vision/models/RepViT_M1_5.yaml +22 -0
- mblt_vision/models/RepViT_M2.yaml +22 -0
- mblt_vision/models/RepViT_M2_3.yaml +22 -0
- mblt_vision/models/RepViT_M3.yaml +22 -0
- mblt_vision/models/ResNeXt101_32X8D.yaml +32 -0
- mblt_vision/models/ResNeXt101_64X4D.yaml +23 -0
- mblt_vision/models/ResNeXt50_32X4D.yaml +32 -0
- mblt_vision/models/ResNet101.yaml +32 -0
- mblt_vision/models/ResNet152.yaml +32 -0
- mblt_vision/models/ResNet18.yaml +23 -0
- mblt_vision/models/ResNet34.yaml +23 -0
- mblt_vision/models/ResNet50.yaml +32 -0
- mblt_vision/models/ShuffleNet_V2_X0_5.yaml +23 -0
- mblt_vision/models/ShuffleNet_V2_X1_0.yaml +23 -0
- mblt_vision/models/ShuffleNet_V2_X1_5.yaml +23 -0
- mblt_vision/models/ShuffleNet_V2_X2_0.yaml +23 -0
- mblt_vision/models/SqueezeNet1_0.yaml +23 -0
- mblt_vision/models/SqueezeNet1_1.yaml +23 -0
- mblt_vision/models/Swin_B.yaml +23 -0
- mblt_vision/models/Swin_S.yaml +23 -0
- mblt_vision/models/Swin_T.yaml +23 -0
- mblt_vision/models/VGG11.yaml +23 -0
- mblt_vision/models/VGG11_BN.yaml +23 -0
- mblt_vision/models/VGG13.yaml +23 -0
- mblt_vision/models/VGG13_BN.yaml +23 -0
- mblt_vision/models/VGG16.yaml +23 -0
- mblt_vision/models/VGG16_BN.yaml +23 -0
- mblt_vision/models/VGG19.yaml +23 -0
- mblt_vision/models/VGG19_BN.yaml +23 -0
- mblt_vision/models/ViT_B_16.yaml +67 -0
- mblt_vision/models/ViT_B_32.yaml +23 -0
- mblt_vision/models/ViT_Base_Patch16_224.yaml +22 -0
- mblt_vision/models/ViT_Base_Patch16_384.yaml +22 -0
- mblt_vision/models/ViT_Base_Patch32_224.yaml +22 -0
- mblt_vision/models/ViT_Base_Patch32_384.yaml +22 -0
- mblt_vision/models/ViT_Base_Patch8_224.yaml +22 -0
- mblt_vision/models/ViT_L_16.yaml +67 -0
- mblt_vision/models/ViT_L_32.yaml +23 -0
- mblt_vision/models/ViT_Large_Patch16_224.yaml +22 -0
- mblt_vision/models/ViT_Large_Patch16_384.yaml +22 -0
- mblt_vision/models/ViT_Large_Patch32_384.yaml +22 -0
- mblt_vision/models/ViT_Small_Patch16_224.yaml +22 -0
- mblt_vision/models/ViT_Small_Patch16_384.yaml +22 -0
- mblt_vision/models/ViT_Small_Patch32_224.yaml +22 -0
- mblt_vision/models/ViT_Small_Patch32_384.yaml +22 -0
- mblt_vision/models/ViT_Tiny_Patch16_224.yaml +22 -0
- mblt_vision/models/ViT_Tiny_Patch16_384.yaml +22 -0
- mblt_vision/models/VisFormer_Small.yaml +22 -0
- mblt_vision/models/VisFormer_Tiny.yaml +22 -0
- mblt_vision/models/Wide_ResNet101_2.yaml +32 -0
- mblt_vision/models/Wide_ResNet50_2.yaml +32 -0
- mblt_vision/models/YOLO11l-cls.yaml +23 -0
- mblt_vision/models/YOLO11l-face.yaml +27 -0
- mblt_vision/models/YOLO11l-obb.yaml +28 -0
- mblt_vision/models/YOLO11l-pose.yaml +28 -0
- mblt_vision/models/YOLO11l-seg.yaml +28 -0
- mblt_vision/models/YOLO11l.yaml +27 -0
- mblt_vision/models/YOLO11m-cls.yaml +23 -0
- mblt_vision/models/YOLO11m-face.yaml +27 -0
- mblt_vision/models/YOLO11m-obb.yaml +28 -0
- mblt_vision/models/YOLO11m-pose.yaml +28 -0
- mblt_vision/models/YOLO11m-seg.yaml +28 -0
- mblt_vision/models/YOLO11m.yaml +27 -0
- mblt_vision/models/YOLO11n-cls.yaml +23 -0
- mblt_vision/models/YOLO11n-face.yaml +27 -0
- mblt_vision/models/YOLO11n-obb.yaml +28 -0
- mblt_vision/models/YOLO11n-pose.yaml +28 -0
- mblt_vision/models/YOLO11n-seg.yaml +28 -0
- mblt_vision/models/YOLO11n.yaml +27 -0
- mblt_vision/models/YOLO11s-cls.yaml +23 -0
- mblt_vision/models/YOLO11s-face.yaml +27 -0
- mblt_vision/models/YOLO11s-obb.yaml +28 -0
- mblt_vision/models/YOLO11s-pose.yaml +28 -0
- mblt_vision/models/YOLO11s-seg.yaml +28 -0
- mblt_vision/models/YOLO11s.yaml +27 -0
- mblt_vision/models/YOLO11x-cls.yaml +23 -0
- mblt_vision/models/YOLO11x-obb.yaml +28 -0
- mblt_vision/models/YOLO11x-pose.yaml +28 -0
- mblt_vision/models/YOLO11x-seg.yaml +28 -0
- mblt_vision/models/YOLO11x.yaml +27 -0
- mblt_vision/models/YOLO12l-face.yaml +27 -0
- mblt_vision/models/YOLO12l-seg.yaml +28 -0
- mblt_vision/models/YOLO12l.yaml +27 -0
- mblt_vision/models/YOLO12m-face.yaml +27 -0
- mblt_vision/models/YOLO12m-seg.yaml +28 -0
- mblt_vision/models/YOLO12m.yaml +27 -0
- mblt_vision/models/YOLO12n-face.yaml +27 -0
- mblt_vision/models/YOLO12n-seg.yaml +28 -0
- mblt_vision/models/YOLO12n.yaml +27 -0
- mblt_vision/models/YOLO12s-face.yaml +27 -0
- mblt_vision/models/YOLO12s-seg.yaml +28 -0
- mblt_vision/models/YOLO12s.yaml +27 -0
- mblt_vision/models/YOLO12x-seg.yaml +28 -0
- mblt_vision/models/YOLO12x.yaml +27 -0
- mblt_vision/models/YOLO26l-cls.yaml +23 -0
- mblt_vision/models/YOLO26l-depth.yaml +17 -0
- mblt_vision/models/YOLO26l-distill.yaml +27 -0
- mblt_vision/models/YOLO26l-obb.yaml +28 -0
- mblt_vision/models/YOLO26l-pose.yaml +28 -0
- mblt_vision/models/YOLO26l-seg.yaml +28 -0
- mblt_vision/models/YOLO26l-sem-ade20k.yaml +17 -0
- mblt_vision/models/YOLO26l-sem.yaml +17 -0
- mblt_vision/models/YOLO26l.yaml +27 -0
- mblt_vision/models/YOLO26m-cls.yaml +23 -0
- mblt_vision/models/YOLO26m-depth.yaml +17 -0
- mblt_vision/models/YOLO26m-distill.yaml +27 -0
- mblt_vision/models/YOLO26m-obb.yaml +28 -0
- mblt_vision/models/YOLO26m-pose.yaml +28 -0
- mblt_vision/models/YOLO26m-seg.yaml +28 -0
- mblt_vision/models/YOLO26m-sem-ade20k.yaml +17 -0
- mblt_vision/models/YOLO26m-sem.yaml +17 -0
- mblt_vision/models/YOLO26m.yaml +27 -0
- mblt_vision/models/YOLO26n-cls.yaml +23 -0
- mblt_vision/models/YOLO26n-depth.yaml +17 -0
- mblt_vision/models/YOLO26n-distill.yaml +27 -0
- mblt_vision/models/YOLO26n-obb.yaml +28 -0
- mblt_vision/models/YOLO26n-pose.yaml +28 -0
- mblt_vision/models/YOLO26n-seg.yaml +28 -0
- mblt_vision/models/YOLO26n-sem-ade20k.yaml +17 -0
- mblt_vision/models/YOLO26n-sem.yaml +17 -0
- mblt_vision/models/YOLO26n.yaml +27 -0
- mblt_vision/models/YOLO26s-cls.yaml +23 -0
- mblt_vision/models/YOLO26s-depth.yaml +17 -0
- mblt_vision/models/YOLO26s-distill.yaml +27 -0
- mblt_vision/models/YOLO26s-obb.yaml +28 -0
- mblt_vision/models/YOLO26s-pose.yaml +28 -0
- mblt_vision/models/YOLO26s-seg.yaml +28 -0
- mblt_vision/models/YOLO26s-sem-ade20k.yaml +17 -0
- mblt_vision/models/YOLO26s-sem.yaml +17 -0
- mblt_vision/models/YOLO26s.yaml +27 -0
- mblt_vision/models/YOLO26x-cls.yaml +23 -0
- mblt_vision/models/YOLO26x-depth.yaml +17 -0
- mblt_vision/models/YOLO26x-distill.yaml +27 -0
- mblt_vision/models/YOLO26x-obb.yaml +28 -0
- mblt_vision/models/YOLO26x-pose.yaml +28 -0
- mblt_vision/models/YOLO26x-seg.yaml +28 -0
- mblt_vision/models/YOLO26x-sem-ade20k.yaml +17 -0
- mblt_vision/models/YOLO26x-sem.yaml +17 -0
- mblt_vision/models/YOLO26x.yaml +27 -0
- mblt_vision/models/YOLOv10b.yaml +28 -0
- mblt_vision/models/YOLOv10l-face.yaml +28 -0
- mblt_vision/models/YOLOv10l.yaml +28 -0
- mblt_vision/models/YOLOv10m-face.yaml +28 -0
- mblt_vision/models/YOLOv10m.yaml +28 -0
- mblt_vision/models/YOLOv10n-face.yaml +28 -0
- mblt_vision/models/YOLOv10n.yaml +28 -0
- mblt_vision/models/YOLOv10s-face.yaml +28 -0
- mblt_vision/models/YOLOv10s.yaml +28 -0
- mblt_vision/models/YOLOv10x.yaml +28 -0
- mblt_vision/models/YOLOv3-spp.yaml +44 -0
- mblt_vision/models/YOLOv3-sppu.yaml +27 -0
- mblt_vision/models/YOLOv3-tiny.yaml +38 -0
- mblt_vision/models/YOLOv3-tinyu.yaml +27 -0
- mblt_vision/models/YOLOv3.yaml +44 -0
- mblt_vision/models/YOLOv3u.yaml +27 -0
- mblt_vision/models/YOLOv5l-cls.yaml +22 -0
- mblt_vision/models/YOLOv5l-seg.yaml +45 -0
- mblt_vision/models/YOLOv5l.yaml +44 -0
- mblt_vision/models/YOLOv5l6.yaml +50 -0
- mblt_vision/models/YOLOv5l6u.yaml +27 -0
- mblt_vision/models/YOLOv5lu.yaml +27 -0
- mblt_vision/models/YOLOv5m-cls.yaml +22 -0
- mblt_vision/models/YOLOv5m-seg.yaml +45 -0
- mblt_vision/models/YOLOv5m.yaml +45 -0
- mblt_vision/models/YOLOv5m6.yaml +50 -0
- mblt_vision/models/YOLOv5m6u.yaml +27 -0
- mblt_vision/models/YOLOv5mu.yaml +27 -0
- mblt_vision/models/YOLOv5n-cls.yaml +22 -0
- mblt_vision/models/YOLOv5n-seg.yaml +45 -0
- mblt_vision/models/YOLOv5n.yaml +44 -0
- mblt_vision/models/YOLOv5n6.yaml +50 -0
- mblt_vision/models/YOLOv5n6u.yaml +27 -0
- mblt_vision/models/YOLOv5nu.yaml +27 -0
- mblt_vision/models/YOLOv5s-cls.yaml +22 -0
- mblt_vision/models/YOLOv5s-seg.yaml +45 -0
- mblt_vision/models/YOLOv5s.yaml +44 -0
- mblt_vision/models/YOLOv5s6.yaml +50 -0
- mblt_vision/models/YOLOv5s6u.yaml +27 -0
- mblt_vision/models/YOLOv5su.yaml +27 -0
- mblt_vision/models/YOLOv5x-cls.yaml +22 -0
- mblt_vision/models/YOLOv5x-seg.yaml +45 -0
- mblt_vision/models/YOLOv5x.yaml +44 -0
- mblt_vision/models/YOLOv5x6.yaml +50 -0
- mblt_vision/models/YOLOv5x6u.yaml +27 -0
- mblt_vision/models/YOLOv5xu.yaml +27 -0
- mblt_vision/models/YOLOv6m-face.yaml +27 -0
- mblt_vision/models/YOLOv6n-face.yaml +27 -0
- mblt_vision/models/YOLOv7.yaml +44 -0
- mblt_vision/models/YOLOv7d6.yaml +50 -0
- mblt_vision/models/YOLOv7e6.yaml +50 -0
- mblt_vision/models/YOLOv7e6e.yaml +50 -0
- mblt_vision/models/YOLOv7w6.yaml +50 -0
- mblt_vision/models/YOLOv7x.yaml +44 -0
- mblt_vision/models/YOLOv8l-cls.yaml +23 -0
- mblt_vision/models/YOLOv8l-face.yaml +27 -0
- mblt_vision/models/YOLOv8l-obb.yaml +28 -0
- mblt_vision/models/YOLOv8l-pose.yaml +28 -0
- mblt_vision/models/YOLOv8l-seg.yaml +28 -0
- mblt_vision/models/YOLOv8l.yaml +27 -0
- mblt_vision/models/YOLOv8m-cls.yaml +23 -0
- mblt_vision/models/YOLOv8m-face.yaml +27 -0
- mblt_vision/models/YOLOv8m-obb.yaml +28 -0
- mblt_vision/models/YOLOv8m-pose.yaml +28 -0
- mblt_vision/models/YOLOv8m-seg.yaml +28 -0
- mblt_vision/models/YOLOv8m.yaml +27 -0
- mblt_vision/models/YOLOv8n-cls.yaml +23 -0
- mblt_vision/models/YOLOv8n-face.yaml +27 -0
- mblt_vision/models/YOLOv8n-obb.yaml +28 -0
- mblt_vision/models/YOLOv8n-pose.yaml +28 -0
- mblt_vision/models/YOLOv8n-seg.yaml +28 -0
- mblt_vision/models/YOLOv8n.yaml +27 -0
- mblt_vision/models/YOLOv8s-cls.yaml +23 -0
- mblt_vision/models/YOLOv8s-obb.yaml +28 -0
- mblt_vision/models/YOLOv8s-pose.yaml +28 -0
- mblt_vision/models/YOLOv8s-seg.yaml +28 -0
- mblt_vision/models/YOLOv8s.yaml +27 -0
- mblt_vision/models/YOLOv8x-cls.yaml +23 -0
- mblt_vision/models/YOLOv8x-obb.yaml +28 -0
- mblt_vision/models/YOLOv8x-pose-p6.yaml +28 -0
- mblt_vision/models/YOLOv8x-pose.yaml +28 -0
- mblt_vision/models/YOLOv8x-seg.yaml +28 -0
- mblt_vision/models/YOLOv8x.yaml +27 -0
- mblt_vision/models/YOLOv9c-seg.yaml +28 -0
- mblt_vision/models/YOLOv9c.yaml +27 -0
- mblt_vision/models/YOLOv9e-seg.yaml +28 -0
- mblt_vision/models/YOLOv9e.yaml +27 -0
- mblt_vision/models/YOLOv9m.yaml +27 -0
- mblt_vision/models/YOLOv9s.yaml +27 -0
- mblt_vision/models/YOLOv9t.yaml +27 -0
- mblt_vision/obb/__init__.py +39 -0
- mblt_vision/object_detection/__init__.py +153 -0
- mblt_vision/pose_estimation/__init__.py +41 -0
- mblt_vision/py.typed +1 -0
- mblt_vision/semantic_segmentation/__init__.py +29 -0
- mblt_vision/utils/__init__.py +0 -0
- mblt_vision/utils/datasets/__init__.py +85 -0
- mblt_vision/utils/datasets/ade20k.py +41 -0
- mblt_vision/utils/datasets/cityscapes.py +66 -0
- mblt_vision/utils/datasets/coco.py +243 -0
- mblt_vision/utils/datasets/dataloader.py +1016 -0
- mblt_vision/utils/datasets/dotav1.py +59 -0
- mblt_vision/utils/datasets/imagenet.py +29 -0
- mblt_vision/utils/datasets/organizer.py +2145 -0
- mblt_vision/utils/datasets/readiness.py +1063 -0
- mblt_vision/utils/evaluation/__init__.py +51 -0
- mblt_vision/utils/evaluation/_result.py +22 -0
- mblt_vision/utils/evaluation/eval_ade20k.py +302 -0
- mblt_vision/utils/evaluation/eval_cityscapes.py +32 -0
- mblt_vision/utils/evaluation/eval_coco.py +465 -0
- mblt_vision/utils/evaluation/eval_dota.py +878 -0
- mblt_vision/utils/evaluation/eval_imagenet.py +151 -0
- mblt_vision/utils/evaluation/eval_nyu_depth.py +189 -0
- mblt_vision/utils/evaluation/eval_widerface.py +440 -0
- mblt_vision/utils/letterbox.py +118 -0
- mblt_vision/utils/postprocess/__init__.py +9 -0
- mblt_vision/utils/postprocess/_letterbox.py +102 -0
- mblt_vision/utils/postprocess/base.py +743 -0
- mblt_vision/utils/postprocess/build_post.py +120 -0
- mblt_vision/utils/postprocess/cls_post.py +117 -0
- mblt_vision/utils/postprocess/common.py +1571 -0
- mblt_vision/utils/postprocess/depth_post.py +106 -0
- mblt_vision/utils/postprocess/semantic_seg_post.py +192 -0
- mblt_vision/utils/postprocess/yolo_anchor_post.py +469 -0
- mblt_vision/utils/postprocess/yolo_anchorless_post.py +952 -0
- mblt_vision/utils/postprocess/yolo_dflfree_post.py +983 -0
- mblt_vision/utils/postprocess/yolo_nmsfree_post.py +176 -0
- mblt_vision/utils/preprocess/__init__.py +27 -0
- mblt_vision/utils/preprocess/_validation.py +66 -0
- mblt_vision/utils/preprocess/base.py +157 -0
- mblt_vision/utils/preprocess/build_pre.py +48 -0
- mblt_vision/utils/preprocess/center_crop.py +75 -0
- mblt_vision/utils/preprocess/letterbox.py +123 -0
- mblt_vision/utils/preprocess/normalize.py +94 -0
- mblt_vision/utils/preprocess/order.py +74 -0
- mblt_vision/utils/preprocess/reader.py +94 -0
- mblt_vision/utils/preprocess/resize.py +202 -0
- mblt_vision/utils/preprocess/yolo_pre.py +7 -0
- mblt_vision/utils/results.py +725 -0
- mblt_vision/utils/types.py +15 -0
- mblt_vision/wrapper.py +1121 -0
- mblt_vision_python-0.0.0.dist-info/METADATA +152 -0
- mblt_vision_python-0.0.0.dist-info/RECORD +404 -0
- mblt_vision_python-0.0.0.dist-info/WHEEL +5 -0
- mblt_vision_python-0.0.0.dist-info/entry_points.txt +2 -0
- mblt_vision_python-0.0.0.dist-info/licenses/LICENSE +28 -0
- mblt_vision_python-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
"""Evaluation script for DOTAv1 OBB."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from time import time
|
|
10
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, cast
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import torch
|
|
14
|
+
from mblt_vision.utils.postprocess.common import (
|
|
15
|
+
batch_probiou,
|
|
16
|
+
rotated_nms,
|
|
17
|
+
xywhr2xyxyxyxy,
|
|
18
|
+
xyxyxyxy2xywhr,
|
|
19
|
+
)
|
|
20
|
+
from tqdm import tqdm
|
|
21
|
+
|
|
22
|
+
from ..._tasks import normalize_vision_task
|
|
23
|
+
from ..datasets import (
|
|
24
|
+
CustomDOTAv1,
|
|
25
|
+
get_dota_loader,
|
|
26
|
+
get_dotav1_class_num,
|
|
27
|
+
get_dotav1_label,
|
|
28
|
+
)
|
|
29
|
+
from ..datasets.readiness import (
|
|
30
|
+
_canonicalize_quadrilateral,
|
|
31
|
+
_polygon_has_positive_image_overlap,
|
|
32
|
+
)
|
|
33
|
+
from ..letterbox import RatioPad, resolve_ratio_pad
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from ...wrapper import MBLT_Engine
|
|
37
|
+
from ..results import Results
|
|
38
|
+
|
|
39
|
+
DOTAV1_CLASS_TO_IDX = {
|
|
40
|
+
get_dotav1_label(index): index for index in range(get_dotav1_class_num())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DOTAResult(NamedTuple):
|
|
45
|
+
"""DOTAv1 rotated detection metrics in the legacy tuple order."""
|
|
46
|
+
|
|
47
|
+
map50: float
|
|
48
|
+
map5095: float
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def primary_score(self) -> float:
|
|
52
|
+
"""Return the primary DOTAv1 validation metric."""
|
|
53
|
+
return self.map5095
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def secondary_score(self) -> float:
|
|
57
|
+
"""Return the secondary DOTAv1 validation metric."""
|
|
58
|
+
return self.map50
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _label_to_index(label: str) -> int:
|
|
62
|
+
"""Convert a DOTAv1 class token to a class index."""
|
|
63
|
+
try:
|
|
64
|
+
return int(label)
|
|
65
|
+
except ValueError:
|
|
66
|
+
return DOTAV1_CLASS_TO_IDX[label]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _validate_polygon_area(
|
|
70
|
+
coords: torch.Tensor, annotation_path: Path, line_number: int
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Require a non-degenerate quadrilateral before converting it to an OBB."""
|
|
73
|
+
|
|
74
|
+
shifted = torch.roll(coords, shifts=-1, dims=0)
|
|
75
|
+
area = 0.5 * torch.abs(
|
|
76
|
+
torch.sum(coords[:, 0] * shifted[:, 1] - coords[:, 1] * shifted[:, 0])
|
|
77
|
+
)
|
|
78
|
+
if not bool(area > 0):
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"DOTAv1 annotation polygon must have positive area at "
|
|
81
|
+
f"{annotation_path}:{line_number}."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _validate_polygon_vertices(
|
|
86
|
+
coords: torch.Tensor, annotation_path: Path, line_number: int
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Require four distinct, consistently ordered DOTAv1 quadrilateral vertices."""
|
|
89
|
+
|
|
90
|
+
if torch.unique(coords, dim=0).shape[0] != 4:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"DOTAv1 annotation polygon must contain four distinct vertices at "
|
|
93
|
+
f"{annotation_path}:{line_number}."
|
|
94
|
+
)
|
|
95
|
+
edges = torch.roll(coords, shifts=-1, dims=0) - coords
|
|
96
|
+
next_edges = torch.roll(edges, shifts=-1, dims=0)
|
|
97
|
+
turns = edges[:, 0] * next_edges[:, 1] - edges[:, 1] * next_edges[:, 0]
|
|
98
|
+
if not bool(torch.all(turns > 0) or torch.all(turns < 0)):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"DOTAv1 annotation polygon vertices must be consistently ordered at "
|
|
101
|
+
f"{annotation_path}:{line_number}."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _validate_polygon_image_overlap(
|
|
106
|
+
coords: torch.Tensor,
|
|
107
|
+
image_shape: tuple[int, int],
|
|
108
|
+
annotation_path: Path,
|
|
109
|
+
line_number: int,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Require a quadrilateral to cover non-zero area inside its source image."""
|
|
112
|
+
|
|
113
|
+
polygon = [coordinate for point in coords.tolist() for coordinate in point]
|
|
114
|
+
if not _polygon_has_positive_image_overlap(polygon, image_shape):
|
|
115
|
+
raise ValueError(
|
|
116
|
+
"DOTAv1 annotation polygon must overlap its source image at "
|
|
117
|
+
f"{annotation_path}:{line_number}."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _load_ground_truths(
|
|
122
|
+
data_path: str, dataset: CustomDOTAv1
|
|
123
|
+
) -> dict[str, dict[str, torch.Tensor]]:
|
|
124
|
+
"""Load DOTAv1 OBB ground-truth labels in original-image coordinates.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
data_path: DOTAv1 root directory.
|
|
128
|
+
dataset: Dataset containing image IDs and image paths.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Mapping from image ID to tensors for positive and ignored classes, polygons,
|
|
132
|
+
and ``xywhr`` boxes.
|
|
133
|
+
"""
|
|
134
|
+
label_dir = Path(data_path) / "labels" / "val"
|
|
135
|
+
original_label_dir = Path(data_path) / "labels" / "val_original"
|
|
136
|
+
image_ids = set(dataset.ids)
|
|
137
|
+
label_ids = {path.stem for path in label_dir.glob("*.txt")} | {
|
|
138
|
+
path.stem for path in original_label_dir.glob("*.txt")
|
|
139
|
+
}
|
|
140
|
+
orphan_label_ids = sorted(label_ids - image_ids)
|
|
141
|
+
if orphan_label_ids:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"DOTAv1 annotations have no corresponding validation image: "
|
|
144
|
+
f"{', '.join(orphan_label_ids[:5])}."
|
|
145
|
+
)
|
|
146
|
+
ground_truths: dict[str, dict[str, torch.Tensor]] = {}
|
|
147
|
+
for image_id, image_path in zip(dataset.ids, dataset.image_paths):
|
|
148
|
+
image = dataset._load_image(image_path)
|
|
149
|
+
height, width = image.shape[:2]
|
|
150
|
+
label_path = label_dir / f"{image_id}.txt"
|
|
151
|
+
original_label_path = original_label_dir / f"{image_id}.txt"
|
|
152
|
+
classes = []
|
|
153
|
+
polygons = []
|
|
154
|
+
ignore_classes = []
|
|
155
|
+
ignore_polygons = []
|
|
156
|
+
seen_targets: set[tuple[int, tuple[float, ...]]] = set()
|
|
157
|
+
|
|
158
|
+
# Organizer output keeps the official original annotation alongside the
|
|
159
|
+
# normalized convenience label. Prefer the authoritative original when
|
|
160
|
+
# both are present so a stale normalized file cannot hide ground truth.
|
|
161
|
+
if label_path.is_file() and not original_label_path.is_file():
|
|
162
|
+
for line_number, line in enumerate(
|
|
163
|
+
label_path.read_text(encoding="utf-8").splitlines(), start=1
|
|
164
|
+
):
|
|
165
|
+
parts = line.split()
|
|
166
|
+
if not parts:
|
|
167
|
+
continue
|
|
168
|
+
if len(parts) < 9:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"Malformed normalized DOTAv1 annotation at "
|
|
171
|
+
f"{label_path}:{line_number}: expected at least 9 fields, "
|
|
172
|
+
f"got {len(parts)}."
|
|
173
|
+
)
|
|
174
|
+
cls = _label_to_index(parts[0])
|
|
175
|
+
if not 0 <= cls < get_dotav1_class_num():
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Unsupported DOTAv1 class index {cls} in {label_path}."
|
|
178
|
+
)
|
|
179
|
+
coords = torch.tensor(
|
|
180
|
+
[float(value) for value in parts[1:9]], dtype=torch.float32
|
|
181
|
+
).reshape(4, 2)
|
|
182
|
+
if not bool(torch.isfinite(coords).all()):
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"DOTAv1 annotation coordinates must be finite at "
|
|
185
|
+
f"{label_path}:{line_number}."
|
|
186
|
+
)
|
|
187
|
+
coords[:, 0] *= width
|
|
188
|
+
coords[:, 1] *= height
|
|
189
|
+
_validate_polygon_area(coords, label_path, line_number)
|
|
190
|
+
_validate_polygon_vertices(coords, label_path, line_number)
|
|
191
|
+
_validate_polygon_image_overlap(
|
|
192
|
+
coords, (height, width), label_path, line_number
|
|
193
|
+
)
|
|
194
|
+
if len(parts) >= 10 and parts[9] not in {"0", "1", "2"}:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Unsupported DOTAv1 difficulty flag {parts[9]!r} at "
|
|
197
|
+
f"{label_path}:{line_number}."
|
|
198
|
+
)
|
|
199
|
+
target_key = (
|
|
200
|
+
cls,
|
|
201
|
+
_canonicalize_quadrilateral(coords.flatten().tolist()),
|
|
202
|
+
)
|
|
203
|
+
if target_key in seen_targets:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
"Duplicate DOTAv1 annotation target at "
|
|
206
|
+
f"{label_path}:{line_number}."
|
|
207
|
+
)
|
|
208
|
+
seen_targets.add(target_key)
|
|
209
|
+
if len(parts) >= 10 and parts[9] in {"1", "2"}:
|
|
210
|
+
ignore_classes.append(cls)
|
|
211
|
+
ignore_polygons.append(coords)
|
|
212
|
+
else:
|
|
213
|
+
classes.append(cls)
|
|
214
|
+
polygons.append(coords)
|
|
215
|
+
elif original_label_path.is_file():
|
|
216
|
+
for line_number, line in enumerate(
|
|
217
|
+
original_label_path.read_text(encoding="utf-8").splitlines(), start=1
|
|
218
|
+
):
|
|
219
|
+
parts = line.split()
|
|
220
|
+
if parts and (
|
|
221
|
+
parts[0].startswith("imagesource:") or parts[0].startswith("gsd:")
|
|
222
|
+
):
|
|
223
|
+
continue
|
|
224
|
+
if len(parts) < 10:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
"Malformed original DOTAv1 annotation at "
|
|
227
|
+
f"{original_label_path}:{line_number}: expected at least 10 fields, "
|
|
228
|
+
f"got {len(parts)}."
|
|
229
|
+
)
|
|
230
|
+
cls = _label_to_index(parts[8])
|
|
231
|
+
if not 0 <= cls < get_dotav1_class_num():
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"Unsupported DOTAv1 class index {cls} in {original_label_path}."
|
|
234
|
+
)
|
|
235
|
+
coords = torch.tensor(
|
|
236
|
+
[float(value) for value in parts[:8]], dtype=torch.float32
|
|
237
|
+
).reshape(4, 2)
|
|
238
|
+
if not bool(torch.isfinite(coords).all()):
|
|
239
|
+
raise ValueError(
|
|
240
|
+
"DOTAv1 annotation coordinates must be finite at "
|
|
241
|
+
f"{original_label_path}:{line_number}."
|
|
242
|
+
)
|
|
243
|
+
_validate_polygon_area(coords, original_label_path, line_number)
|
|
244
|
+
_validate_polygon_vertices(coords, original_label_path, line_number)
|
|
245
|
+
_validate_polygon_image_overlap(
|
|
246
|
+
coords, (height, width), original_label_path, line_number
|
|
247
|
+
)
|
|
248
|
+
if parts[9] not in {"0", "1", "2"}:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Unsupported DOTAv1 difficulty flag {parts[9]!r} at "
|
|
251
|
+
f"{original_label_path}:{line_number}."
|
|
252
|
+
)
|
|
253
|
+
target_key = (
|
|
254
|
+
cls,
|
|
255
|
+
_canonicalize_quadrilateral(coords.flatten().tolist()),
|
|
256
|
+
)
|
|
257
|
+
if target_key in seen_targets:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
"Duplicate DOTAv1 annotation target at "
|
|
260
|
+
f"{original_label_path}:{line_number}."
|
|
261
|
+
)
|
|
262
|
+
seen_targets.add(target_key)
|
|
263
|
+
if parts[9] in {"1", "2"}:
|
|
264
|
+
ignore_classes.append(cls)
|
|
265
|
+
ignore_polygons.append(coords)
|
|
266
|
+
else:
|
|
267
|
+
classes.append(cls)
|
|
268
|
+
polygons.append(coords)
|
|
269
|
+
|
|
270
|
+
else:
|
|
271
|
+
raise FileNotFoundError(
|
|
272
|
+
"DOTAv1 annotation not found for image "
|
|
273
|
+
f"{image_id!r}; expected {label_path} or {original_label_path}."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
polygon_tensor = (
|
|
277
|
+
torch.stack(polygons).to(torch.float32)
|
|
278
|
+
if polygons
|
|
279
|
+
else torch.zeros((0, 4, 2), dtype=torch.float32)
|
|
280
|
+
)
|
|
281
|
+
if polygon_tensor.numel():
|
|
282
|
+
boxes_xywhr = xyxyxyxy2xywhr(polygon_tensor)
|
|
283
|
+
boxes = cast(torch.Tensor, boxes_xywhr).to(torch.float32)
|
|
284
|
+
else:
|
|
285
|
+
boxes = torch.zeros((0, 5), dtype=torch.float32)
|
|
286
|
+
|
|
287
|
+
ignore_polygon_tensor = (
|
|
288
|
+
torch.stack(ignore_polygons).to(torch.float32)
|
|
289
|
+
if ignore_polygons
|
|
290
|
+
else torch.zeros((0, 4, 2), dtype=torch.float32)
|
|
291
|
+
)
|
|
292
|
+
if ignore_polygon_tensor.numel():
|
|
293
|
+
ignored_xywhr = xyxyxyxy2xywhr(ignore_polygon_tensor)
|
|
294
|
+
ignore_boxes = cast(torch.Tensor, ignored_xywhr).to(torch.float32)
|
|
295
|
+
else:
|
|
296
|
+
ignore_boxes = torch.zeros((0, 5), dtype=torch.float32)
|
|
297
|
+
|
|
298
|
+
ground_truths[image_id] = {
|
|
299
|
+
"cls": torch.tensor(classes, dtype=torch.int64),
|
|
300
|
+
"polygons": polygon_tensor,
|
|
301
|
+
"bboxes": boxes,
|
|
302
|
+
"ignore_cls": torch.tensor(ignore_classes, dtype=torch.int64),
|
|
303
|
+
"ignore_polygons": ignore_polygon_tensor,
|
|
304
|
+
"ignore_bboxes": ignore_boxes,
|
|
305
|
+
}
|
|
306
|
+
return ground_truths
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def format_dota_results(
|
|
310
|
+
nms_outs: Results,
|
|
311
|
+
input_shape: tuple[int, ...],
|
|
312
|
+
org_shape: list[tuple[int, int]],
|
|
313
|
+
ratio_pad: list[Any],
|
|
314
|
+
image_ids: tuple[str, ...],
|
|
315
|
+
postprocess: Any,
|
|
316
|
+
) -> list[dict[str, Any]]:
|
|
317
|
+
"""Format model outputs for DOTAv1 evaluation and export.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
nms_outs: Postprocessed model results.
|
|
321
|
+
input_shape: Preprocessed image shape.
|
|
322
|
+
org_shape: Original image shapes.
|
|
323
|
+
ratio_pad: Letterbox metadata.
|
|
324
|
+
image_ids: DOTAv1 image IDs.
|
|
325
|
+
postprocess: Postprocessor instance.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
List of formatted prediction dictionaries.
|
|
329
|
+
"""
|
|
330
|
+
labels_list, polygons_list, scores_list, xywhr_list = postprocess.nmsout2eval(
|
|
331
|
+
nms_outs.output,
|
|
332
|
+
input_shape,
|
|
333
|
+
org_shape,
|
|
334
|
+
ratio_pad=ratio_pad,
|
|
335
|
+
include_xywhr=True,
|
|
336
|
+
)
|
|
337
|
+
outer_lengths = {
|
|
338
|
+
"image IDs": len(image_ids),
|
|
339
|
+
"labels": len(labels_list),
|
|
340
|
+
"polygons": len(polygons_list),
|
|
341
|
+
"scores": len(scores_list),
|
|
342
|
+
"rotated boxes": len(xywhr_list),
|
|
343
|
+
}
|
|
344
|
+
if len(set(outer_lengths.values())) != 1:
|
|
345
|
+
details = ", ".join(
|
|
346
|
+
f"{name}={length}" for name, length in outer_lengths.items()
|
|
347
|
+
)
|
|
348
|
+
raise ValueError(f"DOTAv1 export batch length mismatch: {details}.")
|
|
349
|
+
results = []
|
|
350
|
+
for image_id, labels, polygons, scores, xywhrs in zip(
|
|
351
|
+
image_ids, labels_list, polygons_list, scores_list, xywhr_list, strict=True
|
|
352
|
+
):
|
|
353
|
+
detection_lengths = {
|
|
354
|
+
"labels": len(labels),
|
|
355
|
+
"polygons": len(polygons),
|
|
356
|
+
"scores": len(scores),
|
|
357
|
+
"rotated boxes": len(xywhrs),
|
|
358
|
+
}
|
|
359
|
+
if len(set(detection_lengths.values())) != 1:
|
|
360
|
+
details = ", ".join(
|
|
361
|
+
f"{name}={length}" for name, length in detection_lengths.items()
|
|
362
|
+
)
|
|
363
|
+
raise ValueError(
|
|
364
|
+
f"DOTAv1 export detection length mismatch for {image_id}: {details}."
|
|
365
|
+
)
|
|
366
|
+
for label, polygon, score, xywhr in zip(
|
|
367
|
+
labels, polygons, scores, xywhrs, strict=True
|
|
368
|
+
):
|
|
369
|
+
results.append(
|
|
370
|
+
{
|
|
371
|
+
"image_id": image_id,
|
|
372
|
+
"category_id": DOTAV1_CLASS_TO_IDX[label],
|
|
373
|
+
"category_name": label,
|
|
374
|
+
"poly": polygon,
|
|
375
|
+
"score": score,
|
|
376
|
+
"rbox": xywhr,
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
return results
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _compute_ap(
|
|
383
|
+
recall: np.ndarray, precision: np.ndarray
|
|
384
|
+
) -> tuple[float, np.ndarray, np.ndarray]:
|
|
385
|
+
"""Compute AP from recall and precision curves with Ultralytics interpolation."""
|
|
386
|
+
mrec = np.concatenate(([0.0], recall, [1.0]))
|
|
387
|
+
mpre = np.concatenate(([1.0], precision, [0.0]))
|
|
388
|
+
mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
|
|
389
|
+
grid = np.linspace(0, 1, 101)
|
|
390
|
+
integrate = getattr(np, "trapezoid", None)
|
|
391
|
+
if integrate is None:
|
|
392
|
+
integrate = np.trapz
|
|
393
|
+
return float(integrate(np.interp(grid, mrec, mpre), grid)), mpre, mrec
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _ap_per_class(
|
|
397
|
+
tp: np.ndarray,
|
|
398
|
+
ignore: np.ndarray,
|
|
399
|
+
conf: np.ndarray,
|
|
400
|
+
pred_cls: np.ndarray,
|
|
401
|
+
target_cls: np.ndarray,
|
|
402
|
+
eps: float = 1e-16,
|
|
403
|
+
) -> np.ndarray:
|
|
404
|
+
"""Compute AP per class using the Ultralytics object-detection metric policy."""
|
|
405
|
+
if target_cls.size == 0:
|
|
406
|
+
niou = tp.shape[1] if tp.ndim == 2 else 10
|
|
407
|
+
return np.zeros((0, niou), dtype=np.float64)
|
|
408
|
+
|
|
409
|
+
order = np.argsort(-conf)
|
|
410
|
+
tp = tp[order]
|
|
411
|
+
ignore = ignore[order]
|
|
412
|
+
conf = conf[order]
|
|
413
|
+
pred_cls = pred_cls[order]
|
|
414
|
+
|
|
415
|
+
unique_classes, target_count = np.unique(target_cls, return_counts=True)
|
|
416
|
+
ap = np.zeros((unique_classes.shape[0], tp.shape[1]), dtype=np.float64)
|
|
417
|
+
for class_index, class_id in enumerate(unique_classes):
|
|
418
|
+
pred_mask = pred_cls == class_id
|
|
419
|
+
num_labels = target_count[class_index]
|
|
420
|
+
if not pred_mask.any() or num_labels == 0:
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
class_tp = tp[pred_mask]
|
|
424
|
+
class_ignore = ignore[pred_mask]
|
|
425
|
+
for iou_index in range(tp.shape[1]):
|
|
426
|
+
keep = ~class_ignore[:, iou_index]
|
|
427
|
+
if not keep.any():
|
|
428
|
+
continue
|
|
429
|
+
true_positive = class_tp[keep, iou_index].cumsum(0)
|
|
430
|
+
false_positive = (1 - class_tp[keep, iou_index]).cumsum(0)
|
|
431
|
+
recall = true_positive / (num_labels + eps)
|
|
432
|
+
precision = true_positive / (true_positive + false_positive + eps)
|
|
433
|
+
ap[class_index, iou_index], _, _ = _compute_ap(recall, precision)
|
|
434
|
+
return ap
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _match_predictions(
|
|
438
|
+
pred_classes: torch.Tensor,
|
|
439
|
+
true_classes: torch.Tensor,
|
|
440
|
+
iou: torch.Tensor,
|
|
441
|
+
iouv: torch.Tensor,
|
|
442
|
+
) -> np.ndarray:
|
|
443
|
+
"""Match predictions to ground-truth boxes with Ultralytics one-to-one matching."""
|
|
444
|
+
correct = np.zeros((pred_classes.shape[0], iouv.shape[0]), dtype=bool)
|
|
445
|
+
if pred_classes.numel() == 0 or true_classes.numel() == 0:
|
|
446
|
+
return correct
|
|
447
|
+
|
|
448
|
+
correct_class = true_classes[:, None] == pred_classes
|
|
449
|
+
iou_np = (iou * correct_class).cpu().numpy()
|
|
450
|
+
for iou_index, threshold in enumerate(iouv.cpu().tolist()):
|
|
451
|
+
matches = np.array(np.nonzero(iou_np >= threshold)).T
|
|
452
|
+
if matches.shape[0] == 0:
|
|
453
|
+
continue
|
|
454
|
+
if matches.shape[0] > 1:
|
|
455
|
+
matches = matches[iou_np[matches[:, 0], matches[:, 1]].argsort()[::-1]]
|
|
456
|
+
# Preserve the IoU-ranked order while keeping the first match per prediction and target.
|
|
457
|
+
matches = matches[np.sort(np.unique(matches[:, 1], return_index=True)[1])]
|
|
458
|
+
matches = matches[np.sort(np.unique(matches[:, 0], return_index=True)[1])]
|
|
459
|
+
correct[matches[:, 1].astype(int), iou_index] = True
|
|
460
|
+
return correct
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _empty_stats() -> dict[str, list[np.ndarray]]:
|
|
464
|
+
"""Create an empty DOTAv1 metric statistics accumulator."""
|
|
465
|
+
return {"tp": [], "ignore": [], "conf": [], "pred_cls": [], "target_cls": []}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _append_stats(
|
|
469
|
+
stats: dict[str, list[np.ndarray]], image_stats: dict[str, np.ndarray]
|
|
470
|
+
) -> None:
|
|
471
|
+
"""Append one image's metric statistics to the accumulator."""
|
|
472
|
+
for key, value in image_stats.items():
|
|
473
|
+
stats[key].append(value)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _nms_output_to_predictions(nms_out: torch.Tensor) -> dict[str, torch.Tensor]:
|
|
477
|
+
"""Convert OBB NMS rows to the prediction dictionary used by metric matching."""
|
|
478
|
+
if nms_out.numel() == 0:
|
|
479
|
+
return {
|
|
480
|
+
"bboxes": torch.zeros((0, 5), dtype=torch.float32),
|
|
481
|
+
"conf": torch.zeros(0, dtype=torch.float32),
|
|
482
|
+
"cls": torch.zeros(0, dtype=torch.int64),
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
nms_out = nms_out.detach().cpu()
|
|
486
|
+
return {
|
|
487
|
+
"bboxes": torch.cat([nms_out[:, :4], nms_out[:, 6:7]], dim=-1).to(
|
|
488
|
+
torch.float32
|
|
489
|
+
),
|
|
490
|
+
"conf": nms_out[:, 4].to(torch.float32),
|
|
491
|
+
"cls": nms_out[:, 5].to(torch.int64),
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _ratio_pad_for_shape(
|
|
496
|
+
input_shape: tuple[int, ...],
|
|
497
|
+
org_shape: tuple[int, int],
|
|
498
|
+
ratio_pad: RatioPad | None,
|
|
499
|
+
) -> tuple[float, tuple[float, float]]:
|
|
500
|
+
"""Return letterbox gain and padding for an image."""
|
|
501
|
+
if len(input_shape) < 2:
|
|
502
|
+
raise ValueError(f"Expected at least 2 input dimensions, got {input_shape}.")
|
|
503
|
+
|
|
504
|
+
ratio, pad = resolve_ratio_pad(
|
|
505
|
+
(input_shape[0], input_shape[1]), org_shape, ratio_pad
|
|
506
|
+
)
|
|
507
|
+
return float(ratio[0]), (float(pad[0]), float(pad[1]))
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _ground_truth_to_input_space(
|
|
511
|
+
ground_truth: dict[str, torch.Tensor],
|
|
512
|
+
input_shape: tuple[int, ...],
|
|
513
|
+
org_shape: tuple[int, int],
|
|
514
|
+
ratio_pad: RatioPad | None,
|
|
515
|
+
) -> dict[str, torch.Tensor]:
|
|
516
|
+
"""Transform original-image DOTAv1 polygons to letterboxed ``xywhr`` boxes."""
|
|
517
|
+
gain, pad = _ratio_pad_for_shape(input_shape, org_shape, ratio_pad)
|
|
518
|
+
|
|
519
|
+
def transform(polygons: torch.Tensor | None, boxes: torch.Tensor) -> torch.Tensor:
|
|
520
|
+
if polygons is None:
|
|
521
|
+
return boxes
|
|
522
|
+
if polygons.numel() == 0:
|
|
523
|
+
return torch.zeros((0, 5), dtype=torch.float32)
|
|
524
|
+
transformed = polygons.clone().to(torch.float32)
|
|
525
|
+
transformed[..., 0] = transformed[..., 0] * gain + pad[0]
|
|
526
|
+
transformed[..., 1] = transformed[..., 1] * gain + pad[1]
|
|
527
|
+
return cast(torch.Tensor, xyxyxyxy2xywhr(transformed)).to(torch.float32)
|
|
528
|
+
|
|
529
|
+
transformed_boxes = transform(ground_truth.get("polygons"), ground_truth["bboxes"])
|
|
530
|
+
ignore_classes = ground_truth.get("ignore_cls", torch.zeros(0, dtype=torch.int64))
|
|
531
|
+
ignore_boxes = transform(
|
|
532
|
+
ground_truth.get("ignore_polygons"),
|
|
533
|
+
ground_truth.get("ignore_bboxes", torch.zeros((0, 5), dtype=torch.float32)),
|
|
534
|
+
)
|
|
535
|
+
return {
|
|
536
|
+
"cls": ground_truth["cls"],
|
|
537
|
+
"bboxes": transformed_boxes,
|
|
538
|
+
"ignore_cls": ignore_classes,
|
|
539
|
+
"ignore_bboxes": ignore_boxes,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _process_image_stats(
|
|
544
|
+
predictions: dict[str, torch.Tensor],
|
|
545
|
+
target: dict[str, torch.Tensor],
|
|
546
|
+
iouv: torch.Tensor,
|
|
547
|
+
) -> dict[str, np.ndarray]:
|
|
548
|
+
"""Build one image's true-positive, confidence, class, and target arrays."""
|
|
549
|
+
target_cls = target["cls"].cpu().numpy()
|
|
550
|
+
if target["cls"].numel() == 0 or predictions["cls"].numel() == 0:
|
|
551
|
+
true_positive = np.zeros(
|
|
552
|
+
(predictions["cls"].shape[0], iouv.numel()), dtype=bool
|
|
553
|
+
)
|
|
554
|
+
else:
|
|
555
|
+
iou = batch_probiou(target["bboxes"], predictions["bboxes"])
|
|
556
|
+
true_positive = _match_predictions(predictions["cls"], target["cls"], iou, iouv)
|
|
557
|
+
ignore = np.zeros_like(true_positive)
|
|
558
|
+
ignore_classes = target.get("ignore_cls", torch.zeros(0, dtype=torch.int64))
|
|
559
|
+
ignore_boxes = target.get("ignore_bboxes", torch.zeros((0, 5), dtype=torch.float32))
|
|
560
|
+
if ignore_classes.numel() and predictions["cls"].numel():
|
|
561
|
+
ignored_iou = batch_probiou(ignore_boxes, predictions["bboxes"])
|
|
562
|
+
same_class = ignore_classes[:, None] == predictions["cls"]
|
|
563
|
+
ignored_matches = (
|
|
564
|
+
(ignored_iou[:, :, None] >= iouv[None, None, :]) & same_class[:, :, None]
|
|
565
|
+
).any(dim=0)
|
|
566
|
+
ignore = ignored_matches.cpu().numpy() & ~true_positive
|
|
567
|
+
return {
|
|
568
|
+
"tp": true_positive,
|
|
569
|
+
"ignore": ignore,
|
|
570
|
+
"conf": predictions["conf"].cpu().numpy(),
|
|
571
|
+
"pred_cls": predictions["cls"].cpu().numpy(),
|
|
572
|
+
"target_cls": target_cls,
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _evaluate_stats(stats: dict[str, list[np.ndarray]], niou: int = 10) -> DOTAResult:
|
|
577
|
+
"""Compute DOTAv1 metrics in legacy tuple order: mAP50, then mAP50-95."""
|
|
578
|
+
target_cls = (
|
|
579
|
+
np.concatenate(stats["target_cls"], 0)
|
|
580
|
+
if stats["target_cls"]
|
|
581
|
+
else np.zeros(0, dtype=np.float64)
|
|
582
|
+
)
|
|
583
|
+
if target_cls.size == 0:
|
|
584
|
+
raise ValueError("DOTAv1 evaluation requires at least one non-ignored target.")
|
|
585
|
+
|
|
586
|
+
tp = (
|
|
587
|
+
np.concatenate(stats["tp"], 0)
|
|
588
|
+
if stats["tp"]
|
|
589
|
+
else np.zeros((0, niou), dtype=bool)
|
|
590
|
+
)
|
|
591
|
+
ignore = (
|
|
592
|
+
np.concatenate(stats["ignore"], 0)
|
|
593
|
+
if stats["ignore"]
|
|
594
|
+
else np.zeros((0, niou), dtype=bool)
|
|
595
|
+
)
|
|
596
|
+
conf = (
|
|
597
|
+
np.concatenate(stats["conf"], 0)
|
|
598
|
+
if stats["conf"]
|
|
599
|
+
else np.zeros(0, dtype=np.float64)
|
|
600
|
+
)
|
|
601
|
+
pred_cls = (
|
|
602
|
+
np.concatenate(stats["pred_cls"], 0)
|
|
603
|
+
if stats["pred_cls"]
|
|
604
|
+
else np.zeros(0, dtype=np.float64)
|
|
605
|
+
)
|
|
606
|
+
ap = _ap_per_class(tp, ignore, conf, pred_cls, target_cls)
|
|
607
|
+
if ap.size == 0:
|
|
608
|
+
return DOTAResult(map5095=0.0, map50=0.0)
|
|
609
|
+
return DOTAResult(map5095=float(ap.mean()), map50=float(ap[:, 0].mean()))
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def evaluate_dota_predictions(
|
|
613
|
+
ground_truths: dict[str, dict[str, torch.Tensor]],
|
|
614
|
+
predictions: list[dict[str, Any]],
|
|
615
|
+
) -> DOTAResult:
|
|
616
|
+
"""Evaluate DOTAv1 predictions with local rotated mAP.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
ground_truths: Mapping of image IDs to class and OBB tensors.
|
|
620
|
+
predictions: Formatted prediction dictionaries.
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Rotated mAP at IoU ``0.50`` followed by mAP averaged across ``0.50:0.95``.
|
|
624
|
+
The ``primary_score`` and ``secondary_score`` properties expose mAP50-95
|
|
625
|
+
and mAP50, respectively.
|
|
626
|
+
"""
|
|
627
|
+
iouv = torch.linspace(0.5, 0.95, 10)
|
|
628
|
+
stats = _empty_stats()
|
|
629
|
+
predictions_by_image: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
|
630
|
+
for prediction in predictions:
|
|
631
|
+
predictions_by_image[str(prediction["image_id"])].append(prediction)
|
|
632
|
+
|
|
633
|
+
image_ids = set(ground_truths) | set(predictions_by_image)
|
|
634
|
+
for image_id in sorted(image_ids):
|
|
635
|
+
rows = predictions_by_image.get(image_id, [])
|
|
636
|
+
if rows:
|
|
637
|
+
pred_dict = {
|
|
638
|
+
"bboxes": torch.tensor(
|
|
639
|
+
[row["rbox"] for row in rows], dtype=torch.float32
|
|
640
|
+
),
|
|
641
|
+
"conf": torch.tensor(
|
|
642
|
+
[row["score"] for row in rows], dtype=torch.float32
|
|
643
|
+
),
|
|
644
|
+
"cls": torch.tensor(
|
|
645
|
+
[row["category_id"] for row in rows], dtype=torch.int64
|
|
646
|
+
),
|
|
647
|
+
}
|
|
648
|
+
else:
|
|
649
|
+
pred_dict = _nms_output_to_predictions(
|
|
650
|
+
torch.zeros((0, 7), dtype=torch.float32)
|
|
651
|
+
)
|
|
652
|
+
target = ground_truths.get(
|
|
653
|
+
image_id,
|
|
654
|
+
{
|
|
655
|
+
"cls": torch.zeros(0, dtype=torch.int64),
|
|
656
|
+
"bboxes": torch.zeros((0, 5), dtype=torch.float32),
|
|
657
|
+
},
|
|
658
|
+
)
|
|
659
|
+
_append_stats(stats, _process_image_stats(pred_dict, target, iouv))
|
|
660
|
+
return _evaluate_stats(stats, niou=iouv.numel())
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def save_dota_task1_predictions(
|
|
664
|
+
predictions: list[dict[str, Any]], save_dir: str
|
|
665
|
+
) -> tuple[Path, Path]:
|
|
666
|
+
"""Save split and merged predictions in DOTA Task1 text format.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
predictions: Formatted prediction dictionaries.
|
|
670
|
+
save_dir: Directory where prediction folders are written.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
Tuple of split and merged prediction directories.
|
|
674
|
+
"""
|
|
675
|
+
root = Path(save_dir)
|
|
676
|
+
pred_txt = root / "predictions_txt"
|
|
677
|
+
pred_merged_txt = root / "predictions_merged_txt"
|
|
678
|
+
pred_txt.mkdir(parents=True, exist_ok=True)
|
|
679
|
+
pred_merged_txt.mkdir(parents=True, exist_ok=True)
|
|
680
|
+
|
|
681
|
+
for cls_idx in range(get_dotav1_class_num()):
|
|
682
|
+
(pred_txt / f"Task1_{get_dotav1_label(cls_idx)}.txt").write_text(
|
|
683
|
+
"", encoding="utf-8"
|
|
684
|
+
)
|
|
685
|
+
(pred_merged_txt / f"Task1_{get_dotav1_label(cls_idx)}.txt").write_text(
|
|
686
|
+
"", encoding="utf-8"
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
for pred in predictions:
|
|
690
|
+
class_name = pred["category_name"]
|
|
691
|
+
polygon = pred["poly"]
|
|
692
|
+
with (pred_txt / f"Task1_{class_name}.txt").open("a", encoding="utf-8") as file:
|
|
693
|
+
file.write(
|
|
694
|
+
f"{pred['image_id']} {pred['score']} "
|
|
695
|
+
f"{polygon[0]} {polygon[1]} {polygon[2]} {polygon[3]} "
|
|
696
|
+
f"{polygon[4]} {polygon[5]} {polygon[6]} {polygon[7]}\n"
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
merged_results: dict[str, list[list[float]]] = defaultdict(list)
|
|
700
|
+
offset_pattern = re.compile(r"(\d+)___(\d+)")
|
|
701
|
+
for pred in predictions:
|
|
702
|
+
image_id = pred["image_id"]
|
|
703
|
+
base_image_id = image_id.split("__", 1)[0]
|
|
704
|
+
offset_match = offset_pattern.search(image_id)
|
|
705
|
+
x_offset, y_offset = (0, 0)
|
|
706
|
+
if offset_match is not None:
|
|
707
|
+
x_offset, y_offset = (
|
|
708
|
+
int(offset_match.group(1)),
|
|
709
|
+
int(offset_match.group(2)),
|
|
710
|
+
)
|
|
711
|
+
rbox = list(pred["rbox"])
|
|
712
|
+
rbox[0] += x_offset
|
|
713
|
+
rbox[1] += y_offset
|
|
714
|
+
merged_results[base_image_id].append(
|
|
715
|
+
[*rbox, pred["score"], float(pred["category_id"])]
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
for image_id, rows in merged_results.items():
|
|
719
|
+
bbox = torch.tensor(rows, dtype=torch.float32)
|
|
720
|
+
if bbox.numel() == 0:
|
|
721
|
+
continue
|
|
722
|
+
max_wh = max(float(torch.max(bbox[:, :2]).item() * 2), 1.0)
|
|
723
|
+
class_offsets = bbox[:, 6:7] * max_wh
|
|
724
|
+
boxes = bbox[:, :5].clone()
|
|
725
|
+
boxes[:, :2] += class_offsets
|
|
726
|
+
keep = rotated_nms(boxes, bbox[:, 5], 0.3)
|
|
727
|
+
bbox = bbox[keep]
|
|
728
|
+
polygons = xywhr2xyxyxyxy(bbox[:, :5]).reshape(-1, 8)
|
|
729
|
+
for polygon, score, cls in zip(
|
|
730
|
+
polygons.tolist(), bbox[:, 5].tolist(), bbox[:, 6].tolist()
|
|
731
|
+
):
|
|
732
|
+
class_name = get_dotav1_label(int(cls))
|
|
733
|
+
rounded_polygon = [round(float(value), 3) for value in polygon]
|
|
734
|
+
with (pred_merged_txt / f"Task1_{class_name}.txt").open(
|
|
735
|
+
"a", encoding="utf-8"
|
|
736
|
+
) as file:
|
|
737
|
+
file.write(
|
|
738
|
+
f"{image_id} {round(float(score), 3)} "
|
|
739
|
+
f"{rounded_polygon[0]} {rounded_polygon[1]} {rounded_polygon[2]} {rounded_polygon[3]} "
|
|
740
|
+
f"{rounded_polygon[4]} {rounded_polygon[5]} {rounded_polygon[6]} {rounded_polygon[7]}\n"
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
return pred_txt, pred_merged_txt
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _nms_output_list(nms_output: Any) -> list[torch.Tensor]:
|
|
747
|
+
"""Normalize postprocess output to a per-image list of OBB tensors."""
|
|
748
|
+
if isinstance(nms_output, list):
|
|
749
|
+
return nms_output
|
|
750
|
+
if isinstance(nms_output, tuple):
|
|
751
|
+
return list(nms_output)
|
|
752
|
+
if isinstance(nms_output, torch.Tensor):
|
|
753
|
+
if nms_output.ndim == 3:
|
|
754
|
+
return [image[image[:, 4] > 0] for image in nms_output]
|
|
755
|
+
return [nms_output]
|
|
756
|
+
raise TypeError(f"Unsupported OBB NMS output type: {type(nms_output).__name__}.")
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def _validate_evaluation_batch_lengths(
|
|
760
|
+
nms_outputs: list[torch.Tensor],
|
|
761
|
+
input_batch_size: int,
|
|
762
|
+
org_shape: Any,
|
|
763
|
+
ratio_pad: Any,
|
|
764
|
+
image_ids: Any,
|
|
765
|
+
) -> None:
|
|
766
|
+
"""Reject batches whose output or loader metadata omits an image."""
|
|
767
|
+
|
|
768
|
+
batch_lengths = {
|
|
769
|
+
"model outputs": len(nms_outputs),
|
|
770
|
+
"input batch": input_batch_size,
|
|
771
|
+
"original shapes": len(org_shape),
|
|
772
|
+
"ratio pads": len(ratio_pad),
|
|
773
|
+
"image IDs": len(image_ids),
|
|
774
|
+
}
|
|
775
|
+
if len(set(batch_lengths.values())) != 1:
|
|
776
|
+
details = ", ".join(
|
|
777
|
+
f"{name}={length}" for name, length in batch_lengths.items()
|
|
778
|
+
)
|
|
779
|
+
raise ValueError(f"DOTAv1 evaluation batch length mismatch: {details}.")
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def eval_dota(
|
|
783
|
+
model: MBLT_Engine,
|
|
784
|
+
data_path: str,
|
|
785
|
+
batch_size: int,
|
|
786
|
+
conf_thres: float | None = None,
|
|
787
|
+
iou_thres: float | None = None,
|
|
788
|
+
save_dir: str | None = None,
|
|
789
|
+
) -> DOTAResult:
|
|
790
|
+
"""Evaluate a model on DOTAv1 validation.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
model: Model engine to evaluate.
|
|
794
|
+
data_path: DOTAv1 dataset root.
|
|
795
|
+
batch_size: Batch size for evaluation.
|
|
796
|
+
conf_thres: Optional confidence threshold override.
|
|
797
|
+
iou_thres: Optional IoU threshold override.
|
|
798
|
+
save_dir: Optional directory for DOTA Task1 prediction files.
|
|
799
|
+
|
|
800
|
+
Returns:
|
|
801
|
+
Local rotated mAP scores.
|
|
802
|
+
"""
|
|
803
|
+
if normalize_vision_task(model.post_cfg["task"]) != "obb":
|
|
804
|
+
raise NotImplementedError(
|
|
805
|
+
f"Task {model.post_cfg['task']} is not supported for DOTAv1 evaluation."
|
|
806
|
+
)
|
|
807
|
+
dataset_name = model.post_cfg.get("dataset")
|
|
808
|
+
if not isinstance(dataset_name, str) or dataset_name.lower() != "dotav1":
|
|
809
|
+
raise ValueError(
|
|
810
|
+
"DOTAv1 evaluation requires model post_cfg.dataset to be 'dotav1', "
|
|
811
|
+
f"got {dataset_name!r}."
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
dataset = CustomDOTAv1(data_path)
|
|
815
|
+
dataloader = get_dota_loader(dataset, batch_size, model.preprocess_with_metadata)
|
|
816
|
+
model.set_postprocess_thresholds(conf_thres=conf_thres, iou_thres=iou_thres)
|
|
817
|
+
ground_truths = _load_ground_truths(data_path, dataset)
|
|
818
|
+
iouv = torch.linspace(0.5, 0.95, 10)
|
|
819
|
+
stats = _empty_stats()
|
|
820
|
+
|
|
821
|
+
results = []
|
|
822
|
+
num_data = len(dataset)
|
|
823
|
+
total_iter = math.ceil(num_data / batch_size)
|
|
824
|
+
pbar = tqdm(dataloader, total=total_iter, desc="Evaluating DOTAv1")
|
|
825
|
+
inference_time = 0.0
|
|
826
|
+
cum_num_data = 0
|
|
827
|
+
|
|
828
|
+
for input_npu, org_shape, ratio_pad, image_ids in pbar:
|
|
829
|
+
cum_num_data += len(image_ids)
|
|
830
|
+
tic = time()
|
|
831
|
+
out_npu = model(input_npu)
|
|
832
|
+
inference_time += time() - tic
|
|
833
|
+
nms_outs = model.postprocess(out_npu)
|
|
834
|
+
input_shape = tuple(int(value) for value in input_npu.shape[1:-1])
|
|
835
|
+
nms_outputs = _nms_output_list(nms_outs.output)
|
|
836
|
+
_validate_evaluation_batch_lengths(
|
|
837
|
+
nms_outputs,
|
|
838
|
+
int(input_npu.shape[0]),
|
|
839
|
+
org_shape,
|
|
840
|
+
ratio_pad,
|
|
841
|
+
image_ids,
|
|
842
|
+
)
|
|
843
|
+
for nms_out, image_id, image_shape, image_ratio_pad in zip(
|
|
844
|
+
nms_outputs,
|
|
845
|
+
image_ids,
|
|
846
|
+
org_shape,
|
|
847
|
+
ratio_pad,
|
|
848
|
+
strict=True,
|
|
849
|
+
):
|
|
850
|
+
target = _ground_truth_to_input_space(
|
|
851
|
+
ground_truths[image_id],
|
|
852
|
+
input_shape,
|
|
853
|
+
(int(image_shape[0]), int(image_shape[1])),
|
|
854
|
+
image_ratio_pad,
|
|
855
|
+
)
|
|
856
|
+
_append_stats(
|
|
857
|
+
stats,
|
|
858
|
+
_process_image_stats(_nms_output_to_predictions(nms_out), target, iouv),
|
|
859
|
+
)
|
|
860
|
+
if save_dir is not None:
|
|
861
|
+
results.extend(
|
|
862
|
+
format_dota_results(
|
|
863
|
+
nms_outs,
|
|
864
|
+
input_shape,
|
|
865
|
+
org_shape,
|
|
866
|
+
ratio_pad,
|
|
867
|
+
image_ids,
|
|
868
|
+
model.postprocessor,
|
|
869
|
+
)
|
|
870
|
+
)
|
|
871
|
+
pbar.set_postfix_str(f"NPU FPS: {cum_num_data / inference_time:.3f}")
|
|
872
|
+
|
|
873
|
+
pbar.close()
|
|
874
|
+
map_score = _evaluate_stats(stats, niou=iouv.numel())
|
|
875
|
+
if save_dir is not None:
|
|
876
|
+
save_dota_task1_predictions(results, save_dir)
|
|
877
|
+
print("DOTAv1 evaluation completed")
|
|
878
|
+
return map_score
|