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