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,952 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YOLO anchorless postprocessing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
from .base import YOLODetectionPostBase
|
|
13
|
+
from .common import (
|
|
14
|
+
YOLOOBBPostMixin,
|
|
15
|
+
YOLOPosePostMixin,
|
|
16
|
+
YOLOSegPostMixin,
|
|
17
|
+
concat_converted_obb_outputs,
|
|
18
|
+
decode_split_converted_obb_outputs,
|
|
19
|
+
dist2bbox,
|
|
20
|
+
dist2rbox,
|
|
21
|
+
non_max_suppression,
|
|
22
|
+
rotated_nms,
|
|
23
|
+
xywh2xyxy,
|
|
24
|
+
yolo_multilabel_candidates,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
AnchorlessOutputLayout = Literal["channels_first", "candidates_first"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class _AnchorlessNMSInput:
|
|
32
|
+
"""Decoded anchorless detections together with their source layout."""
|
|
33
|
+
|
|
34
|
+
detections: torch.Tensor | list[torch.Tensor]
|
|
35
|
+
layout: AnchorlessOutputLayout
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class YOLOAnchorlessDetectionPost(YOLODetectionPostBase):
|
|
39
|
+
"""Postprocessing for YOLO models without anchors."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, pre_cfg: dict, post_cfg: dict, **kwargs: object) -> None:
|
|
42
|
+
"""Initialize the anchorless YOLO postprocessor.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
pre_cfg: Preprocessing configuration.
|
|
46
|
+
post_cfg: Postprocessing configuration.
|
|
47
|
+
**kwargs: Optional runtime overrides for postprocess behavior.
|
|
48
|
+
"""
|
|
49
|
+
super().__init__(pre_cfg, post_cfg, **kwargs)
|
|
50
|
+
self.reg_max = post_cfg.get("reg_max", 0) # DFL channels
|
|
51
|
+
self.no = self.nc + self.reg_max * 4 # number of outputs per anchor (144)
|
|
52
|
+
self.dfl_weight = torch.arange(
|
|
53
|
+
self.reg_max, dtype=torch.float32, device=self.device
|
|
54
|
+
).reshape(1, -1, 1, 1)
|
|
55
|
+
|
|
56
|
+
def non_e2e(self, x: list[torch.Tensor]) -> torch.Tensor | list[torch.Tensor]:
|
|
57
|
+
"""Return the export-style output tensor for anchorless YOLO models."""
|
|
58
|
+
if len(x) == 1:
|
|
59
|
+
converted = self.conversion(x)
|
|
60
|
+
if isinstance(converted, torch.Tensor):
|
|
61
|
+
return self._converted_to_batch_output(converted)
|
|
62
|
+
det_out, proto_out = converted
|
|
63
|
+
return [self._converted_to_batch_output(det_out), proto_out]
|
|
64
|
+
|
|
65
|
+
rearranged = self.rearrange(x)
|
|
66
|
+
if isinstance(rearranged, tuple):
|
|
67
|
+
det_out, proto_out = rearranged
|
|
68
|
+
return [self.decode_batch(det_out), proto_out.permute(0, 3, 1, 2)]
|
|
69
|
+
return self.decode_batch(rearranged)
|
|
70
|
+
|
|
71
|
+
def _converted_to_batch_output(self, x: torch.Tensor) -> torch.Tensor:
|
|
72
|
+
"""Normalize converted outputs to the export-style batched layout."""
|
|
73
|
+
if x.ndim == 4 and x.shape[1] == 1:
|
|
74
|
+
x = x.squeeze(1)
|
|
75
|
+
if x.ndim != 3:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Expected 3D converted tensor, got shape {tuple(x.shape)}."
|
|
78
|
+
)
|
|
79
|
+
if x.shape[1] == 4 + self.nc + self.n_extra:
|
|
80
|
+
return x
|
|
81
|
+
if x.shape[-1] == 4 + self.nc + self.n_extra:
|
|
82
|
+
return x.transpose(1, 2)
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Unsupported converted tensor shape {tuple(x.shape)} for non-e2e output."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def decode_batch(self, x: torch.Tensor) -> torch.Tensor:
|
|
88
|
+
"""Decode every anchor without confidence filtering for export-style output."""
|
|
89
|
+
box, scores, extra = torch.split(
|
|
90
|
+
x, [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
91
|
+
)
|
|
92
|
+
anchors = self.anchors_as_tensor().unsqueeze(0)
|
|
93
|
+
stride = self.stride_as_tensor().unsqueeze(0)
|
|
94
|
+
dbox = dist2bbox(self.dfl(box), anchors, xywh=False, dim=1) * stride
|
|
95
|
+
return torch.cat([dbox, scores.sigmoid(), extra], dim=1)
|
|
96
|
+
|
|
97
|
+
def rearrange(
|
|
98
|
+
self, x: list[torch.Tensor]
|
|
99
|
+
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
|
100
|
+
"""Rearranges raw model output tensors into a concatenated decode input.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
x (list[torch.Tensor]): List of raw output tensors from the model detection heads.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
torch.Tensor | tuple[torch.Tensor, torch.Tensor]: Concatenated tensor in
|
|
107
|
+
``(batch, channels, anchors)`` format, optionally paired with prototype masks.
|
|
108
|
+
"""
|
|
109
|
+
y_det = []
|
|
110
|
+
y_cls = []
|
|
111
|
+
for xi in x: # list of bchw outputs
|
|
112
|
+
if xi.ndim == 3:
|
|
113
|
+
xi = xi[None]
|
|
114
|
+
elif xi.ndim == 4:
|
|
115
|
+
pass
|
|
116
|
+
else:
|
|
117
|
+
raise NotImplementedError(f"Got unsupported ndim for input: {xi.ndim}.")
|
|
118
|
+
if xi.shape[-1] == self.reg_max * 4:
|
|
119
|
+
y_det.append(
|
|
120
|
+
xi.permute(0, 3, 1, 2)
|
|
121
|
+
) # (b, 64, 80, 80), (b, 64 ,40, 40), ...
|
|
122
|
+
elif xi.shape[-1] == self.nc:
|
|
123
|
+
y_cls.append(
|
|
124
|
+
xi.permute(0, 3, 1, 2)
|
|
125
|
+
) # (b, 80, 80, 80), (b, 80, 40, 40), ...
|
|
126
|
+
else:
|
|
127
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
128
|
+
# sort as box, scores
|
|
129
|
+
y_det = sorted(y_det, key=lambda x: x.numel(), reverse=True)
|
|
130
|
+
y_cls = sorted(y_cls, key=lambda x: x.numel(), reverse=True)
|
|
131
|
+
self.validate_split_head_counts(detection=y_det, classification=y_cls)
|
|
132
|
+
return torch.cat(
|
|
133
|
+
[
|
|
134
|
+
torch.cat((yi_det, yi_cls), dim=1).flatten(2)
|
|
135
|
+
for yi_det, yi_cls in zip(y_det, y_cls)
|
|
136
|
+
],
|
|
137
|
+
dim=-1,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def decode(self, x: torch.Tensor) -> list[torch.Tensor]:
|
|
141
|
+
"""Decodes model outputs into box coordinates and class scores.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
x (torch.Tensor): Concatenated output tensor from `rearrange`.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
list[torch.Tensor]: Per-image decoded detections in ``(channels, anchors)`` format.
|
|
148
|
+
"""
|
|
149
|
+
return [self.process_box_cls(box_cls) for box_cls in x]
|
|
150
|
+
|
|
151
|
+
def _pre_process(
|
|
152
|
+
self,
|
|
153
|
+
x: list[torch.Tensor],
|
|
154
|
+
) -> tuple[_AnchorlessNMSInput, torch.Tensor | None]:
|
|
155
|
+
"""Decode detections while retaining whether the source was raw or converted.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
x: Checked model output tensors.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Layout-aware detections and an optional prototype output.
|
|
162
|
+
"""
|
|
163
|
+
if len(x) == 1:
|
|
164
|
+
converted = self.conversion(x)
|
|
165
|
+
if not isinstance(converted, torch.Tensor):
|
|
166
|
+
raise TypeError(
|
|
167
|
+
"conversion should return a tensor for single-output YOLO postprocessing."
|
|
168
|
+
)
|
|
169
|
+
detections = self.filter_conversion(converted)
|
|
170
|
+
return _AnchorlessNMSInput(detections, "candidates_first"), None
|
|
171
|
+
rearranged = self.rearrange(x)
|
|
172
|
+
if not isinstance(rearranged, torch.Tensor):
|
|
173
|
+
raise TypeError(
|
|
174
|
+
"rearrange should return a tensor for non-segmentation YOLO postprocessing."
|
|
175
|
+
)
|
|
176
|
+
detections = self.decode(rearranged)
|
|
177
|
+
return _AnchorlessNMSInput(detections, "channels_first"), None
|
|
178
|
+
|
|
179
|
+
def process_box_cls(self, box_cls: torch.Tensor) -> torch.Tensor:
|
|
180
|
+
"""Processes detection results for a single image.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
box_cls: Raw detections for one image.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Decoded boxes, scores, and extra data.
|
|
187
|
+
"""
|
|
188
|
+
if self.n_extra == 0:
|
|
189
|
+
ic = torch.amax(box_cls[-self.nc :, :], dim=0) > self.inv_conf_thres
|
|
190
|
+
else:
|
|
191
|
+
ic = (
|
|
192
|
+
torch.amax(box_cls[-self.nc - self.n_extra : -self.n_extra, :], dim=0)
|
|
193
|
+
> self.inv_conf_thres
|
|
194
|
+
)
|
|
195
|
+
box_cls = box_cls[:, ic] # (144, *)
|
|
196
|
+
if box_cls.numel() == 0:
|
|
197
|
+
return torch.zeros(
|
|
198
|
+
(4 + self.nc + self.n_extra, 0), dtype=torch.float32
|
|
199
|
+
) # (84, 0)
|
|
200
|
+
anchors = self.anchors_as_tensor()
|
|
201
|
+
stride = self.stride_as_tensor()
|
|
202
|
+
box, scores, extra = torch.split(
|
|
203
|
+
box_cls[None], [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
204
|
+
) # (1, 64, *), (1, 80, *), (1, 32, *)
|
|
205
|
+
dbox = (
|
|
206
|
+
dist2bbox(
|
|
207
|
+
self.dfl(box),
|
|
208
|
+
anchors[:, ic],
|
|
209
|
+
xywh=False,
|
|
210
|
+
dim=1,
|
|
211
|
+
)
|
|
212
|
+
* stride[:, ic]
|
|
213
|
+
)
|
|
214
|
+
return torch.cat([dbox, scores.sigmoid(), extra], dim=1).squeeze(0)
|
|
215
|
+
|
|
216
|
+
def filter_conversion(self, x: torch.Tensor) -> list[torch.Tensor]:
|
|
217
|
+
"""Filters out low-confidence detections from a single concatenated output tensor.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
x (torch.Tensor): Concatenated output tensor from the model.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
list[torch.Tensor]: Filtered detections for each image in the batch.
|
|
224
|
+
"""
|
|
225
|
+
while x.ndim == 4 and 1 in (x.shape[0], x.shape[1]):
|
|
226
|
+
if x.shape[0] == 1:
|
|
227
|
+
x = x.squeeze(0)
|
|
228
|
+
elif x.shape[1] == 1:
|
|
229
|
+
x = x.squeeze(1)
|
|
230
|
+
if x.ndim != 3:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Expected 3D converted tensor, got shape {tuple(x.shape)}."
|
|
233
|
+
)
|
|
234
|
+
expected_dim = 4 + self.nc + self.n_extra
|
|
235
|
+
if x.shape[-1] == expected_dim:
|
|
236
|
+
normalized = x
|
|
237
|
+
elif x.shape[1] == expected_dim:
|
|
238
|
+
normalized = x.transpose(1, 2)
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError(f"Unsupported converted tensor shape {tuple(x.shape)}.")
|
|
241
|
+
x_list = torch.split(
|
|
242
|
+
normalized, 1, dim=0
|
|
243
|
+
) # [(1, 8400, 84), (1, 8400, 84), ...]
|
|
244
|
+
|
|
245
|
+
def process_conversion(x: torch.Tensor) -> torch.Tensor:
|
|
246
|
+
x = x.squeeze(0) # (8400, 84)
|
|
247
|
+
if self.n_extra == 0:
|
|
248
|
+
ic = torch.amax(x[:, -self.nc :], dim=1) > self.conf_thres
|
|
249
|
+
else:
|
|
250
|
+
ic = (
|
|
251
|
+
torch.amax(x[:, -self.nc - self.n_extra : -self.n_extra], dim=1)
|
|
252
|
+
> self.conf_thres
|
|
253
|
+
)
|
|
254
|
+
x = x[ic]
|
|
255
|
+
if x.numel() == 0:
|
|
256
|
+
return torch.zeros((0, 4 + self.nc + self.n_extra), dtype=torch.float32)
|
|
257
|
+
x = xywh2xyxy(x)
|
|
258
|
+
return x
|
|
259
|
+
|
|
260
|
+
return [process_conversion(xi) for xi in x_list]
|
|
261
|
+
|
|
262
|
+
def _nms_single(
|
|
263
|
+
self,
|
|
264
|
+
xi: torch.Tensor,
|
|
265
|
+
max_det: int,
|
|
266
|
+
max_nms: int,
|
|
267
|
+
max_wh: int,
|
|
268
|
+
multi_label: bool,
|
|
269
|
+
) -> torch.Tensor:
|
|
270
|
+
"""Apply anchorless NMS to a single decoded image tensor."""
|
|
271
|
+
return self._nms_single_legacy_rows(
|
|
272
|
+
self._normalize_nms_input(xi, "channels_first"),
|
|
273
|
+
max_det=max_det,
|
|
274
|
+
max_nms=max_nms,
|
|
275
|
+
max_wh=max_wh,
|
|
276
|
+
multi_label=multi_label,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def _normalize_nms_input(
|
|
280
|
+
self,
|
|
281
|
+
xi: torch.Tensor,
|
|
282
|
+
layout: AnchorlessOutputLayout | None = None,
|
|
283
|
+
) -> torch.Tensor:
|
|
284
|
+
"""Normalize one decoded tensor to ``(candidates, channels)``.
|
|
285
|
+
|
|
286
|
+
Source provenance resolves square tensors. Shape inference remains available
|
|
287
|
+
for callers that pass decoded tensors directly, with raw channel-first
|
|
288
|
+
layout taking precedence when both dimensions match.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
xi: One image's decoded detections.
|
|
292
|
+
layout: Known source layout, if available.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Detections in canonical ``(candidates, channels)`` layout.
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
ValueError: If the tensor shape conflicts with the expected channel count
|
|
299
|
+
or with the supplied source layout.
|
|
300
|
+
"""
|
|
301
|
+
if xi.ndim != 2:
|
|
302
|
+
raise ValueError(
|
|
303
|
+
f"Expected 2D decoded tensor, got shape {tuple(xi.shape)}."
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
expected_dim = 4 + self.nc + self.n_extra
|
|
307
|
+
if layout == "channels_first":
|
|
308
|
+
if xi.shape[0] != expected_dim:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
f"Expected channel-first decoded tensor with {expected_dim} channels, got shape {tuple(xi.shape)}."
|
|
311
|
+
)
|
|
312
|
+
return xi.transpose(0, 1)
|
|
313
|
+
if layout == "candidates_first":
|
|
314
|
+
if xi.shape[1] != expected_dim:
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"Expected candidates-first decoded tensor "
|
|
317
|
+
f"with {expected_dim} channels, got shape {tuple(xi.shape)}."
|
|
318
|
+
)
|
|
319
|
+
return xi
|
|
320
|
+
|
|
321
|
+
if xi.shape[0] == expected_dim:
|
|
322
|
+
return xi.transpose(0, 1)
|
|
323
|
+
if xi.shape[1] == expected_dim:
|
|
324
|
+
return xi
|
|
325
|
+
raise ValueError(f"Unsupported decoded tensor shape {tuple(xi.shape)}.")
|
|
326
|
+
|
|
327
|
+
def _nms_single_legacy_rows(
|
|
328
|
+
self,
|
|
329
|
+
xi: torch.Tensor,
|
|
330
|
+
max_det: int,
|
|
331
|
+
max_nms: int,
|
|
332
|
+
max_wh: int,
|
|
333
|
+
multi_label: bool,
|
|
334
|
+
) -> torch.Tensor:
|
|
335
|
+
"""Apply anchorless NMS to a single decoded image in row-major ``(anchors, channels)`` form."""
|
|
336
|
+
if xi.numel() == 0:
|
|
337
|
+
return torch.zeros(
|
|
338
|
+
(0, 6 + self.n_extra), dtype=torch.float32, device=self.device
|
|
339
|
+
)
|
|
340
|
+
if multi_label:
|
|
341
|
+
xi_out = yolo_multilabel_candidates(
|
|
342
|
+
xi, self.nc, self.n_extra, self.conf_thres
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
box, score, extra = xi[:, :4], xi[:, 4 : 4 + self.nc], xi[:, 4 + self.nc :]
|
|
346
|
+
conf, cls_idx = score.max(dim=1)
|
|
347
|
+
filt = conf > self.conf_thres
|
|
348
|
+
if not torch.any(filt):
|
|
349
|
+
return torch.zeros(
|
|
350
|
+
(0, 6 + self.n_extra), dtype=torch.float32, device=self.device
|
|
351
|
+
)
|
|
352
|
+
box = box[filt]
|
|
353
|
+
conf = conf[filt]
|
|
354
|
+
cls_idx = cls_idx[filt]
|
|
355
|
+
extra = extra[filt]
|
|
356
|
+
xi_out = torch.empty(
|
|
357
|
+
(box.shape[0], 6 + self.n_extra), dtype=xi.dtype, device=xi.device
|
|
358
|
+
)
|
|
359
|
+
xi_out[:, :4] = box
|
|
360
|
+
xi_out[:, 4] = conf
|
|
361
|
+
xi_out[:, 5] = cls_idx.to(xi.dtype)
|
|
362
|
+
if self.n_extra > 0:
|
|
363
|
+
xi_out[:, 6:] = extra
|
|
364
|
+
xi_out = xi_out[torch.argsort(xi_out[:, 4], descending=True)[:max_nms]]
|
|
365
|
+
c = xi_out[:, 5:6] * max_wh
|
|
366
|
+
boxes, scores = xi_out[:, :4] + c, xi_out[:, 4]
|
|
367
|
+
i_idx = non_max_suppression(boxes, scores, self.iou_thres, max_det)
|
|
368
|
+
return xi_out[i_idx]
|
|
369
|
+
|
|
370
|
+
def nms(
|
|
371
|
+
self,
|
|
372
|
+
x: _AnchorlessNMSInput | torch.Tensor | list[torch.Tensor],
|
|
373
|
+
max_det: int = 300,
|
|
374
|
+
max_nms: int = 30000,
|
|
375
|
+
max_wh: int = 7680,
|
|
376
|
+
multi_label: bool = False,
|
|
377
|
+
) -> list[torch.Tensor]:
|
|
378
|
+
"""Performs Non-Maximum Suppression (NMS) on the decoded detections.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
x: Decoded detections for each image, optionally with source-layout
|
|
382
|
+
provenance.
|
|
383
|
+
max_det (int, optional): Maximum number of detections to keep. Defaults to 300.
|
|
384
|
+
max_nms (int, optional): Maximum number of candidates to consider for NMS.
|
|
385
|
+
Defaults to 30000.
|
|
386
|
+
max_wh (int, optional): Maximum box width/height for offset calculation.
|
|
387
|
+
Defaults to 7680.
|
|
388
|
+
multi_label: Whether to retain every class candidate above threshold.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
list[torch.Tensor]: Post-NMS detections for each image.
|
|
392
|
+
"""
|
|
393
|
+
layout: AnchorlessOutputLayout | None = None
|
|
394
|
+
if isinstance(x, _AnchorlessNMSInput):
|
|
395
|
+
layout = x.layout
|
|
396
|
+
x = x.detections
|
|
397
|
+
|
|
398
|
+
tensors = x if isinstance(x, list) else list(x)
|
|
399
|
+
return [
|
|
400
|
+
self._nms_single_legacy_rows(
|
|
401
|
+
self._normalize_nms_input(xi, layout),
|
|
402
|
+
max_det=max_det,
|
|
403
|
+
max_nms=max_nms,
|
|
404
|
+
max_wh=max_wh,
|
|
405
|
+
multi_label=multi_label,
|
|
406
|
+
)
|
|
407
|
+
for xi in tensors
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
def nms_multilabel(
|
|
411
|
+
self,
|
|
412
|
+
x: _AnchorlessNMSInput | torch.Tensor | list[torch.Tensor],
|
|
413
|
+
) -> list[torch.Tensor]:
|
|
414
|
+
"""Perform Ultralytics-compatible multi-label NMS for validation."""
|
|
415
|
+
return self.nms(x, multi_label=True)
|
|
416
|
+
|
|
417
|
+
def dfl(self, x: torch.Tensor) -> torch.Tensor:
|
|
418
|
+
"""Applies Distribution Focal Loss projection.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
x: Tensor with shape ``(B, 4 * reg_max, A)``.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Tensor with shape ``(B, 4, A)`` containing projected distances.
|
|
425
|
+
"""
|
|
426
|
+
if self.reg_max == 0: # skip dfl for yolov6 s, n models
|
|
427
|
+
return x
|
|
428
|
+
if x.ndim != 3:
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"DFL input must have shape (B, 4 * reg_max, A), got {tuple(x.shape)}."
|
|
431
|
+
)
|
|
432
|
+
B, _, A = x.shape
|
|
433
|
+
x = x.view(B, 4, self.reg_max, A).softmax(dim=2)
|
|
434
|
+
return (x * self.dfl_weight.view(1, 1, self.reg_max, 1)).sum(dim=2)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class YOLOAnchorlessSegPost(YOLOSegPostMixin, YOLOAnchorlessDetectionPost):
|
|
438
|
+
"""Postprocessing for YOLO segmentation models without anchors."""
|
|
439
|
+
|
|
440
|
+
def non_e2e(self, x: list[torch.Tensor]) -> torch.Tensor | list[torch.Tensor]:
|
|
441
|
+
"""Return the export-style output tensor for anchorless YOLO segmentation models.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
x: Checked raw model outputs.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
A detection tensor, or detections paired with prototype masks.
|
|
448
|
+
"""
|
|
449
|
+
if len(x) == 2:
|
|
450
|
+
converted, proto_outs = self.conversion(x)
|
|
451
|
+
return [
|
|
452
|
+
self._converted_to_batch_output(converted),
|
|
453
|
+
self._proto_to_nchw(proto_outs),
|
|
454
|
+
]
|
|
455
|
+
return super().non_e2e(x)
|
|
456
|
+
|
|
457
|
+
def _proto_to_nchw(self, proto: torch.Tensor) -> torch.Tensor:
|
|
458
|
+
"""Convert prototype tensors to ``(B, C, H, W)`` if needed.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
proto: Prototype tensor from a model runtime.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Prototype tensor in channel-first batch layout.
|
|
465
|
+
"""
|
|
466
|
+
if proto.ndim == 4 and proto.shape[1] == self.n_extra:
|
|
467
|
+
return proto
|
|
468
|
+
if proto.ndim == 4 and proto.shape[-1] == self.n_extra:
|
|
469
|
+
return proto.permute(0, 3, 1, 2)
|
|
470
|
+
raise ValueError(
|
|
471
|
+
f"Unsupported proto tensor shape {tuple(proto.shape)} for non-e2e output."
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
def _pre_process(
|
|
475
|
+
self, x: list[torch.Tensor]
|
|
476
|
+
) -> tuple[_AnchorlessNMSInput, torch.Tensor]:
|
|
477
|
+
"""Preprocesses intermediate inputs into (boxes, proto) format.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
x (list[torch.Tensor]): Raw model output tensors.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
tuple: (decoded_detections, prototype_masks).
|
|
484
|
+
"""
|
|
485
|
+
if len(x) == 2:
|
|
486
|
+
converted, proto_outs = self.conversion(x)
|
|
487
|
+
detections = self.filter_conversion(converted)
|
|
488
|
+
return _AnchorlessNMSInput(detections, "candidates_first"), proto_outs
|
|
489
|
+
rearranged, proto_outs = self.rearrange(x)
|
|
490
|
+
return _AnchorlessNMSInput(
|
|
491
|
+
self.decode(rearranged), "channels_first"
|
|
492
|
+
), proto_outs
|
|
493
|
+
|
|
494
|
+
def conversion(self, x: list[torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]:
|
|
495
|
+
"""Converts raw model output tensors into detections and prototypes.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
x (list[torch.Tensor]): List of raw output tensors.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
tuple: (detections, prototypes)
|
|
502
|
+
"""
|
|
503
|
+
if (self.nc + self.n_extra + 4) in x[0].shape[1:] and self.n_extra in x[
|
|
504
|
+
1
|
|
505
|
+
].shape[1:]:
|
|
506
|
+
return (
|
|
507
|
+
x[0],
|
|
508
|
+
x[1],
|
|
509
|
+
)
|
|
510
|
+
if (self.nc + self.n_extra + 4) in x[1].shape[1:] and self.n_extra in x[
|
|
511
|
+
0
|
|
512
|
+
].shape[1:]:
|
|
513
|
+
return (
|
|
514
|
+
x[1],
|
|
515
|
+
x[0],
|
|
516
|
+
)
|
|
517
|
+
raise ValueError(f"Wrong shape of input: {x[0].shape}, {x[1].shape}")
|
|
518
|
+
|
|
519
|
+
def rearrange(self, x: list[torch.Tensor]) -> tuple[torch.Tensor, torch.Tensor]:
|
|
520
|
+
"""
|
|
521
|
+
Rearrange model output tensors for segmentation tasks.
|
|
522
|
+
Args:
|
|
523
|
+
x (list[torch.Tensor]): Raw output tensors.
|
|
524
|
+
Returns:
|
|
525
|
+
tuple: (concatenated_detections, prototype_masks)
|
|
526
|
+
"""
|
|
527
|
+
y_det: list[torch.Tensor] = []
|
|
528
|
+
y_cls: list[torch.Tensor] = []
|
|
529
|
+
y_ext: list[torch.Tensor] = []
|
|
530
|
+
for xi in x:
|
|
531
|
+
if xi.shape[-1] == self.n_extra:
|
|
532
|
+
y_ext.append(
|
|
533
|
+
xi.permute(0, 3, 1, 2)
|
|
534
|
+
) # (b, 32, 160, 160), (b, 32, 80, 80), ...
|
|
535
|
+
elif xi.shape[-1] == self.reg_max * 4:
|
|
536
|
+
y_det.append(
|
|
537
|
+
xi.permute(0, 3, 1, 2)
|
|
538
|
+
) # (b, 64, 80, 80), (b, 64 ,40, 40), ...
|
|
539
|
+
elif xi.shape[-1] == self.nc:
|
|
540
|
+
y_cls.append(
|
|
541
|
+
xi.permute(0, 3, 1, 2)
|
|
542
|
+
) # (b, 80, 80, 80), (b, 80, 40, 40), ...
|
|
543
|
+
else:
|
|
544
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
545
|
+
# sort as box, scores
|
|
546
|
+
y_ext = sorted(y_ext, key=lambda x: x.numel(), reverse=True)
|
|
547
|
+
proto = y_ext.pop(0).permute(0, 2, 3, 1)
|
|
548
|
+
y_det = sorted(y_det, key=lambda x: x.numel(), reverse=True)
|
|
549
|
+
y_cls = sorted(y_cls, key=lambda x: x.numel(), reverse=True)
|
|
550
|
+
self.validate_split_head_counts(
|
|
551
|
+
detection=y_det, classification=y_cls, extra=y_ext
|
|
552
|
+
)
|
|
553
|
+
y = torch.cat(
|
|
554
|
+
[
|
|
555
|
+
torch.cat((yi_det, yi_cls, yi_ext), dim=1).flatten(2)
|
|
556
|
+
for yi_det, yi_cls, yi_ext in zip(y_det, y_cls, y_ext)
|
|
557
|
+
],
|
|
558
|
+
dim=-1,
|
|
559
|
+
)
|
|
560
|
+
return y, proto
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class YOLOAnchorlessPosePost(YOLOPosePostMixin, YOLOAnchorlessDetectionPost):
|
|
564
|
+
"""Postprocessing for YOLO pose estimation models without anchors."""
|
|
565
|
+
|
|
566
|
+
def rearrange(self, x: list[torch.Tensor]) -> torch.Tensor:
|
|
567
|
+
"""Rearranges model output tensors for pose estimation tasks.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
x (list[torch.Tensor]): Raw output tensors.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
torch.Tensor: Concatenated tensor for decode.
|
|
574
|
+
"""
|
|
575
|
+
y_det = []
|
|
576
|
+
y_cls = []
|
|
577
|
+
y_kpt = []
|
|
578
|
+
for xi in x: # list of bchw outputs
|
|
579
|
+
if xi.ndim == 3:
|
|
580
|
+
xi = xi[None]
|
|
581
|
+
elif xi.ndim == 4:
|
|
582
|
+
pass
|
|
583
|
+
else:
|
|
584
|
+
raise NotImplementedError(f"Got unsupported ndim for input: {xi.ndim}.")
|
|
585
|
+
if xi.shape[-1] == self.reg_max * 4:
|
|
586
|
+
y_det.append(
|
|
587
|
+
xi.permute(0, 3, 1, 2)
|
|
588
|
+
) # (b, 64, 80, 80), (b, 64 ,40, 40), ...
|
|
589
|
+
elif xi.shape[-1] == self.nc:
|
|
590
|
+
y_cls.append(
|
|
591
|
+
xi.permute(0, 3, 1, 2)
|
|
592
|
+
) # (b, 1, 80, 80), (b, 1, 40, 40), ...
|
|
593
|
+
elif xi.shape[-1] == self.n_extra:
|
|
594
|
+
y_kpt.append(
|
|
595
|
+
xi.permute(0, 3, 1, 2).flatten(2)
|
|
596
|
+
) # (b, 51, 80, 80), (b, 1, 40, 40), ...
|
|
597
|
+
else:
|
|
598
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
599
|
+
# sort as box, scores
|
|
600
|
+
y_det = sorted(y_det, key=lambda x: x.numel(), reverse=True)
|
|
601
|
+
y_cls = sorted(y_cls, key=lambda x: x.numel(), reverse=True)
|
|
602
|
+
y_kpt = sorted(
|
|
603
|
+
y_kpt, key=lambda x: x.numel(), reverse=True
|
|
604
|
+
) # (b, 51, 6400), (b, 51, 1600), (b, 51, 400)
|
|
605
|
+
self.validate_split_head_counts(
|
|
606
|
+
detection=y_det, classification=y_cls, keypoint=y_kpt
|
|
607
|
+
)
|
|
608
|
+
y_tmp = [
|
|
609
|
+
torch.cat((yi_det, yi_cls), dim=1).flatten(2)
|
|
610
|
+
for (yi_det, yi_cls) in zip(
|
|
611
|
+
y_det, y_cls
|
|
612
|
+
) # (b, 65, 6400), (b, 65, 1600), (b, 65, 400)
|
|
613
|
+
]
|
|
614
|
+
return torch.cat(
|
|
615
|
+
[
|
|
616
|
+
torch.cat((yi_tmp, yi_kpt), dim=1)
|
|
617
|
+
for yi_tmp, yi_kpt in zip(y_tmp, y_kpt)
|
|
618
|
+
],
|
|
619
|
+
dim=-1,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def process_box_cls(self, box_cls: torch.Tensor) -> torch.Tensor:
|
|
623
|
+
"""Processes pose estimation results for a single image.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
box_cls: Raw detections for one image.
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
Decoded boxes, scores, and keypoints.
|
|
630
|
+
"""
|
|
631
|
+
ic = (
|
|
632
|
+
torch.amax(box_cls[-self.nc - self.n_extra : -self.n_extra, :], dim=0)
|
|
633
|
+
> self.inv_conf_thres
|
|
634
|
+
)
|
|
635
|
+
box_cls = box_cls[:, ic] # (116, *)
|
|
636
|
+
if box_cls.numel() == 0:
|
|
637
|
+
return torch.zeros(
|
|
638
|
+
(4 + self.nc + self.n_extra, 0), dtype=torch.float32
|
|
639
|
+
) # (56, 0)
|
|
640
|
+
anchors = self.anchors_as_tensor()
|
|
641
|
+
stride = self.stride_as_tensor()
|
|
642
|
+
box, scores, keypoints = torch.split(
|
|
643
|
+
box_cls[None], [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
644
|
+
) # (1, 64, *), (1, 1, *), (1, 51, *)
|
|
645
|
+
dbox = (
|
|
646
|
+
dist2bbox(
|
|
647
|
+
self.dfl(box),
|
|
648
|
+
anchors[:, ic],
|
|
649
|
+
xywh=False,
|
|
650
|
+
dim=1,
|
|
651
|
+
)
|
|
652
|
+
* stride[:, ic]
|
|
653
|
+
)
|
|
654
|
+
keypoints = keypoints.view(1, 17, 3, -1)
|
|
655
|
+
key_coord, key_conf = torch.split(
|
|
656
|
+
keypoints, [2, 1], dim=2
|
|
657
|
+
) # (1, 17, 2, 8400), (1, 17, 1, 8400)
|
|
658
|
+
key_coord = (key_coord * 2 + anchors[:, ic] - 0.5) * stride[
|
|
659
|
+
:, ic
|
|
660
|
+
] # (1, 17, 2, *)
|
|
661
|
+
keypoints = torch.cat([key_coord, key_conf.sigmoid()], dim=2).view(
|
|
662
|
+
1, self.n_extra, -1
|
|
663
|
+
) # (1, 51, *)
|
|
664
|
+
return torch.cat([dbox, scores.sigmoid(), keypoints], dim=1).squeeze(
|
|
665
|
+
0
|
|
666
|
+
) # (56, *)
|
|
667
|
+
|
|
668
|
+
def decode_batch(self, x: torch.Tensor) -> torch.Tensor:
|
|
669
|
+
"""Decode every anchor without confidence filtering for export-style pose output."""
|
|
670
|
+
box, scores, keypoints = torch.split(
|
|
671
|
+
x, [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
672
|
+
)
|
|
673
|
+
anchors = self.anchors_as_tensor().unsqueeze(0)
|
|
674
|
+
stride = self.stride_as_tensor().unsqueeze(0)
|
|
675
|
+
dbox = dist2bbox(self.dfl(box), anchors, xywh=False, dim=1) * stride
|
|
676
|
+
keypoints = keypoints.view(x.shape[0], 17, 3, -1)
|
|
677
|
+
key_coord, key_conf = torch.split(keypoints, [2, 1], dim=2)
|
|
678
|
+
key_coord = (key_coord * 2 + anchors.unsqueeze(1) - 0.5) * stride.unsqueeze(1)
|
|
679
|
+
keypoints = torch.cat([key_coord, key_conf.sigmoid()], dim=2).view(
|
|
680
|
+
x.shape[0], self.n_extra, -1
|
|
681
|
+
)
|
|
682
|
+
return torch.cat([dbox, scores.sigmoid(), keypoints], dim=1)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class YOLOAnchorlessOBBPost(YOLOOBBPostMixin, YOLOAnchorlessDetectionPost):
|
|
686
|
+
"""Postprocessing for anchorless YOLO OBB models."""
|
|
687
|
+
|
|
688
|
+
def _angle_from_raw(self, angle: torch.Tensor) -> torch.Tensor:
|
|
689
|
+
"""Decode YOLOv8/YOLO11 raw angle logits to radians."""
|
|
690
|
+
return (angle.sigmoid() - 0.25) * torch.pi
|
|
691
|
+
|
|
692
|
+
def _pre_process(
|
|
693
|
+
self, x: list[torch.Tensor]
|
|
694
|
+
) -> tuple[_AnchorlessNMSInput, torch.Tensor | None]:
|
|
695
|
+
"""Preprocess OBB inputs into row-major detections.
|
|
696
|
+
|
|
697
|
+
Args:
|
|
698
|
+
x: Raw model outputs.
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
A tuple of detections and no prototype output.
|
|
702
|
+
"""
|
|
703
|
+
if len(x) in {1, 3, 5}:
|
|
704
|
+
detections = self.filter_conversion(self.conversion(x))
|
|
705
|
+
return _AnchorlessNMSInput(detections, "candidates_first"), None
|
|
706
|
+
return _AnchorlessNMSInput(
|
|
707
|
+
self.decode(self.rearrange(x)), "channels_first"
|
|
708
|
+
), None
|
|
709
|
+
|
|
710
|
+
def conversion(self, x: list[torch.Tensor]) -> torch.Tensor:
|
|
711
|
+
"""Convert exported OBB outputs to one canonical tensor.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
x: Converted OBB runtime outputs.
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
Detections in ``cx, cy, w, h, class scores..., angle`` format.
|
|
718
|
+
"""
|
|
719
|
+
if len(x) == 5:
|
|
720
|
+
return decode_split_converted_obb_outputs(
|
|
721
|
+
x,
|
|
722
|
+
self.nc,
|
|
723
|
+
self.n_extra,
|
|
724
|
+
self.anchors_as_tensor(),
|
|
725
|
+
self.stride_as_tensor(),
|
|
726
|
+
)
|
|
727
|
+
return concat_converted_obb_outputs(x, self.nc, self.n_extra)
|
|
728
|
+
|
|
729
|
+
def rearrange(self, x: list[torch.Tensor]) -> torch.Tensor:
|
|
730
|
+
"""Rearrange raw OBB heads into ``(batch, channels, anchors)`` format.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
x: Raw model output tensors.
|
|
734
|
+
|
|
735
|
+
Returns:
|
|
736
|
+
Concatenated OBB detection tensor.
|
|
737
|
+
"""
|
|
738
|
+
target_count = len(x) // 3
|
|
739
|
+
y_det: list[torch.Tensor] = []
|
|
740
|
+
y_cls: list[torch.Tensor] = []
|
|
741
|
+
y_angle: list[torch.Tensor] = []
|
|
742
|
+
ambiguous: list[tuple[torch.Tensor, list[int]]] = []
|
|
743
|
+
for xi in x:
|
|
744
|
+
if xi.ndim == 3:
|
|
745
|
+
xi = xi.unsqueeze(0)
|
|
746
|
+
elif xi.ndim > 4:
|
|
747
|
+
while xi.ndim > 4 and 1 in xi.shape:
|
|
748
|
+
xi = xi.squeeze(
|
|
749
|
+
next(idx for idx, size in enumerate(xi.shape) if size == 1)
|
|
750
|
+
)
|
|
751
|
+
if xi.ndim == 3:
|
|
752
|
+
xi = xi.unsqueeze(0)
|
|
753
|
+
if xi.ndim != 4:
|
|
754
|
+
raise ValueError(
|
|
755
|
+
f"Expected 3D or 4D OBB head, got shape {tuple(xi.shape)}."
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
candidates: list[tuple[int, torch.Tensor]] = []
|
|
759
|
+
if xi.shape[1] in {self.reg_max * 4, self.nc, self.n_extra}:
|
|
760
|
+
candidates.append((int(xi.shape[1]), xi))
|
|
761
|
+
if xi.shape[-1] in {self.reg_max * 4, self.nc, self.n_extra}:
|
|
762
|
+
candidates.append((int(xi.shape[-1]), xi.permute(0, 3, 1, 2)))
|
|
763
|
+
|
|
764
|
+
deduped: list[tuple[int, torch.Tensor]] = []
|
|
765
|
+
seen_channels: set[int] = set()
|
|
766
|
+
for channel_count, candidate in candidates:
|
|
767
|
+
if channel_count not in seen_channels:
|
|
768
|
+
seen_channels.add(channel_count)
|
|
769
|
+
deduped.append((channel_count, candidate))
|
|
770
|
+
|
|
771
|
+
if len(candidates) == 2 and len(deduped) == 1:
|
|
772
|
+
channel_count, _ = deduped[0]
|
|
773
|
+
normalized = xi.permute(0, 3, 1, 2)
|
|
774
|
+
if channel_count == self.reg_max * 4:
|
|
775
|
+
y_det.append(normalized)
|
|
776
|
+
elif channel_count == self.nc:
|
|
777
|
+
y_cls.append(normalized)
|
|
778
|
+
elif channel_count == self.n_extra:
|
|
779
|
+
y_angle.append(normalized)
|
|
780
|
+
else:
|
|
781
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
782
|
+
elif len(deduped) == 1:
|
|
783
|
+
channel_count, normalized = deduped[0]
|
|
784
|
+
if channel_count == self.reg_max * 4:
|
|
785
|
+
y_det.append(normalized)
|
|
786
|
+
elif channel_count == self.nc:
|
|
787
|
+
y_cls.append(normalized)
|
|
788
|
+
elif channel_count == self.n_extra:
|
|
789
|
+
y_angle.append(normalized)
|
|
790
|
+
else:
|
|
791
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
792
|
+
elif len(deduped) > 1:
|
|
793
|
+
ambiguous.append((xi, [channel_count for channel_count, _ in deduped]))
|
|
794
|
+
else:
|
|
795
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
796
|
+
|
|
797
|
+
for xi, channel_options in ambiguous:
|
|
798
|
+
if self.reg_max * 4 in channel_options and len(y_det) < target_count:
|
|
799
|
+
y_det.append(
|
|
800
|
+
xi if xi.shape[1] == self.reg_max * 4 else xi.permute(0, 3, 1, 2)
|
|
801
|
+
)
|
|
802
|
+
continue
|
|
803
|
+
if self.nc in channel_options and len(y_cls) < target_count:
|
|
804
|
+
y_cls.append(xi if xi.shape[1] == self.nc else xi.permute(0, 3, 1, 2))
|
|
805
|
+
continue
|
|
806
|
+
if self.n_extra in channel_options and len(y_angle) < target_count:
|
|
807
|
+
y_angle.append(
|
|
808
|
+
xi if xi.shape[1] == self.n_extra else xi.permute(0, 3, 1, 2)
|
|
809
|
+
)
|
|
810
|
+
continue
|
|
811
|
+
raise ValueError(f"Wrong shape of input: {xi.shape}")
|
|
812
|
+
|
|
813
|
+
y_det = sorted(y_det, key=lambda x: x.numel(), reverse=True)
|
|
814
|
+
y_cls = sorted(y_cls, key=lambda x: x.numel(), reverse=True)
|
|
815
|
+
y_angle = sorted(y_angle, key=lambda x: x.numel(), reverse=True)
|
|
816
|
+
self.validate_split_head_counts(
|
|
817
|
+
detection=y_det, classification=y_cls, angle=y_angle
|
|
818
|
+
)
|
|
819
|
+
return torch.cat(
|
|
820
|
+
[
|
|
821
|
+
torch.cat((yi_det, yi_cls, yi_angle), dim=1).flatten(2)
|
|
822
|
+
for yi_det, yi_cls, yi_angle in zip(y_det, y_cls, y_angle)
|
|
823
|
+
],
|
|
824
|
+
dim=-1,
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
def decode_batch(self, x: torch.Tensor) -> torch.Tensor:
|
|
828
|
+
"""Decode every OBB anchor without confidence filtering."""
|
|
829
|
+
box, scores, angle = torch.split(
|
|
830
|
+
x, [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
831
|
+
)
|
|
832
|
+
anchors = self.anchors_as_tensor().unsqueeze(0)
|
|
833
|
+
stride = self.stride_as_tensor().unsqueeze(0)
|
|
834
|
+
angle = self._angle_from_raw(angle)
|
|
835
|
+
rbox = dist2rbox(self.dfl(box), angle, anchors, dim=1) * stride
|
|
836
|
+
return torch.cat([rbox, scores.sigmoid(), angle], dim=1)
|
|
837
|
+
|
|
838
|
+
def process_box_cls(self, box_cls: torch.Tensor) -> torch.Tensor:
|
|
839
|
+
"""Processes OBB results for a single image.
|
|
840
|
+
|
|
841
|
+
Args:
|
|
842
|
+
box_cls: Raw detections for one image.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
Decoded rotated boxes, scores, and angle.
|
|
846
|
+
"""
|
|
847
|
+
ic = (
|
|
848
|
+
torch.amax(box_cls[-self.nc - self.n_extra : -self.n_extra, :], dim=0)
|
|
849
|
+
> self.inv_conf_thres
|
|
850
|
+
)
|
|
851
|
+
box_cls = box_cls[:, ic]
|
|
852
|
+
if box_cls.numel() == 0:
|
|
853
|
+
return torch.zeros(
|
|
854
|
+
(4 + self.nc + self.n_extra, 0), dtype=torch.float32, device=self.device
|
|
855
|
+
)
|
|
856
|
+
anchors = self.anchors_as_tensor()
|
|
857
|
+
stride = self.stride_as_tensor()
|
|
858
|
+
box, scores, angle = torch.split(
|
|
859
|
+
box_cls[None], [self.reg_max * 4, self.nc, self.n_extra], dim=1
|
|
860
|
+
)
|
|
861
|
+
angle = self._angle_from_raw(angle)
|
|
862
|
+
rbox = dist2rbox(self.dfl(box), angle, anchors[:, ic], dim=1) * stride[:, ic]
|
|
863
|
+
return torch.cat([rbox, scores.sigmoid(), angle], dim=1).squeeze(0)
|
|
864
|
+
|
|
865
|
+
def filter_conversion(self, x: torch.Tensor) -> list[torch.Tensor]:
|
|
866
|
+
"""Filters converted OBB outputs from a single ONNX tensor.
|
|
867
|
+
|
|
868
|
+
Args:
|
|
869
|
+
x: Converted output tensor.
|
|
870
|
+
|
|
871
|
+
Returns:
|
|
872
|
+
Per-image row-major tensors in ``cx, cy, w, h, scores..., angle`` format.
|
|
873
|
+
"""
|
|
874
|
+
while x.ndim == 4 and 1 in (x.shape[0], x.shape[1]):
|
|
875
|
+
if x.shape[0] == 1:
|
|
876
|
+
x = x.squeeze(0)
|
|
877
|
+
elif x.shape[1] == 1:
|
|
878
|
+
x = x.squeeze(1)
|
|
879
|
+
if x.ndim != 3:
|
|
880
|
+
raise ValueError(
|
|
881
|
+
f"Expected 3D converted tensor, got shape {tuple(x.shape)}."
|
|
882
|
+
)
|
|
883
|
+
expected_dim = 4 + self.nc + self.n_extra
|
|
884
|
+
if x.shape[-1] == expected_dim:
|
|
885
|
+
normalized = x
|
|
886
|
+
elif x.shape[1] == expected_dim:
|
|
887
|
+
normalized = x.transpose(1, 2)
|
|
888
|
+
else:
|
|
889
|
+
raise ValueError(f"Unsupported converted tensor shape {tuple(x.shape)}.")
|
|
890
|
+
|
|
891
|
+
outputs = []
|
|
892
|
+
for xi in torch.split(normalized, 1, dim=0):
|
|
893
|
+
xi = xi.squeeze(0)
|
|
894
|
+
ic = torch.amax(xi[:, 4 : 4 + self.nc], dim=1) > self.conf_thres
|
|
895
|
+
if torch.any(ic):
|
|
896
|
+
outputs.append(xi[ic])
|
|
897
|
+
else:
|
|
898
|
+
outputs.append(
|
|
899
|
+
torch.zeros((0, expected_dim), dtype=xi.dtype, device=xi.device)
|
|
900
|
+
)
|
|
901
|
+
return outputs
|
|
902
|
+
|
|
903
|
+
def _nms_single(
|
|
904
|
+
self,
|
|
905
|
+
xi: torch.Tensor,
|
|
906
|
+
max_det: int,
|
|
907
|
+
max_nms: int,
|
|
908
|
+
max_wh: int,
|
|
909
|
+
multi_label: bool,
|
|
910
|
+
) -> torch.Tensor:
|
|
911
|
+
"""Apply rotated NMS to a single decoded OBB image tensor."""
|
|
912
|
+
if xi.numel() == 0:
|
|
913
|
+
return torch.zeros((0, 7), dtype=torch.float32, device=self.device)
|
|
914
|
+
xi_t = xi.transpose(0, 1)
|
|
915
|
+
return self._nms_single_legacy_rows(
|
|
916
|
+
xi_t,
|
|
917
|
+
max_det=max_det,
|
|
918
|
+
max_nms=max_nms,
|
|
919
|
+
max_wh=max_wh,
|
|
920
|
+
multi_label=multi_label,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
def _nms_single_legacy_rows(
|
|
924
|
+
self,
|
|
925
|
+
xi: torch.Tensor,
|
|
926
|
+
max_det: int,
|
|
927
|
+
max_nms: int,
|
|
928
|
+
max_wh: int,
|
|
929
|
+
multi_label: bool,
|
|
930
|
+
) -> torch.Tensor:
|
|
931
|
+
"""Apply rotated NMS to row-major OBB detections."""
|
|
932
|
+
del multi_label
|
|
933
|
+
if xi.numel() == 0:
|
|
934
|
+
return torch.zeros((0, 7), dtype=torch.float32, device=self.device)
|
|
935
|
+
xi_out = yolo_multilabel_candidates(xi, self.nc, self.n_extra, self.conf_thres)
|
|
936
|
+
if xi_out.numel() == 0:
|
|
937
|
+
return torch.zeros((0, 7), dtype=torch.float32, device=self.device)
|
|
938
|
+
xi_out = xi_out[torch.argsort(xi_out[:, 4], descending=True)[:max_nms]]
|
|
939
|
+
c = xi_out[:, 5:6] * max_wh
|
|
940
|
+
boxes = torch.cat([xi_out[:, :2] + c, xi_out[:, 2:4], xi_out[:, 6:7]], dim=-1)
|
|
941
|
+
keep = rotated_nms(boxes, xi_out[:, 4], self.iou_thres)[:max_det]
|
|
942
|
+
return xi_out[keep]
|
|
943
|
+
|
|
944
|
+
def nms_multilabel(
|
|
945
|
+
self,
|
|
946
|
+
x: _AnchorlessNMSInput | torch.Tensor | list[torch.Tensor],
|
|
947
|
+
) -> list[torch.Tensor]:
|
|
948
|
+
"""Preserve existing OBB NMS behavior outside the COCO validation scope."""
|
|
949
|
+
return self.nms(x)
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
YOLOAnchorlessPost = YOLOAnchorlessDetectionPost
|