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,1016 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom dataloaders for vision datasets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable
|
|
11
|
+
|
|
12
|
+
import cv2
|
|
13
|
+
import numpy as np
|
|
14
|
+
import torch
|
|
15
|
+
from faster_coco_eval import COCO
|
|
16
|
+
from mblt_vision.utils.preprocess.letterbox import letterbox_semantic_mask
|
|
17
|
+
from PIL import Image
|
|
18
|
+
|
|
19
|
+
from .cityscapes import CITYSCAPES_SOURCE_TO_TRAIN_ID
|
|
20
|
+
from .readiness import IMAGE_SUFFIXES
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _unique_paths_by_stem(
|
|
24
|
+
directory: str, suffixes: tuple[str, ...], description: str
|
|
25
|
+
) -> dict[str, str]:
|
|
26
|
+
"""Return supported direct-child files keyed by unique case-preserving stems."""
|
|
27
|
+
|
|
28
|
+
names = [name for name in os.listdir(directory) if name.lower().endswith(suffixes)]
|
|
29
|
+
stems = [os.path.splitext(name)[0] for name in names]
|
|
30
|
+
if len(stems) != len(set(stems)):
|
|
31
|
+
raise ValueError(f"{description} contain duplicate filename stems.")
|
|
32
|
+
return {
|
|
33
|
+
stem: os.path.join(directory, name)
|
|
34
|
+
for stem, name in zip(stems, names, strict=True)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CustomCOCODataset(torch.utils.data.Dataset[tuple[np.ndarray, int, int, int]]):
|
|
39
|
+
"""Custom COCO dataset class for loading images and metadata.
|
|
40
|
+
|
|
41
|
+
This class provides a simple interface for accessing COCO formatted data
|
|
42
|
+
without requiring external library dependencies like torchvision.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
root (str): Root directory path containing the images.
|
|
46
|
+
coco (COCO): COCO helper object from faster_coco_eval.
|
|
47
|
+
ids (list[int]): Sorted list of image IDs in the dataset.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self, root: str, annFile: str, min_keypoints: int | None = None
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Initialize the custom COCO dataset.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
root (str): Path to the directory containing images.
|
|
57
|
+
annFile (str): Path to the COCO annotation JSON file.
|
|
58
|
+
min_keypoints: If set, keep only images with at least one
|
|
59
|
+
annotation whose ``num_keypoints`` is greater than this value.
|
|
60
|
+
"""
|
|
61
|
+
self.root = root
|
|
62
|
+
try:
|
|
63
|
+
raw_annotation = json.loads(Path(annFile).read_text(encoding="utf-8"))
|
|
64
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Unable to read COCO annotation file {annFile}: {exc}."
|
|
67
|
+
) from exc
|
|
68
|
+
if not isinstance(raw_annotation, dict):
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"COCO annotation file {annFile} must contain a JSON object."
|
|
71
|
+
)
|
|
72
|
+
self.raw_annotation = raw_annotation
|
|
73
|
+
self.coco = COCO(annFile)
|
|
74
|
+
if min_keypoints is None:
|
|
75
|
+
self.ids = list(sorted(self.coco.imgs.keys()))
|
|
76
|
+
else:
|
|
77
|
+
self.ids = list(
|
|
78
|
+
sorted(
|
|
79
|
+
{
|
|
80
|
+
ann["image_id"]
|
|
81
|
+
for ann in self.coco.anns.values()
|
|
82
|
+
if ann.get("num_keypoints", 0) > min_keypoints
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _load_image(self, image_id: int) -> np.ndarray:
|
|
88
|
+
"""Load image by ID"""
|
|
89
|
+
file_name = self.coco.loadImgs(image_id)[0]["file_name"]
|
|
90
|
+
if not isinstance(file_name, str) or not file_name:
|
|
91
|
+
raise ValueError(f"COCO image ID {image_id} has an invalid file_name.")
|
|
92
|
+
relative_path = Path(file_name)
|
|
93
|
+
if relative_path.is_absolute() or ".." in relative_path.parts:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"COCO image ID {image_id} has an unsafe file_name: {file_name!r}."
|
|
96
|
+
)
|
|
97
|
+
image_root = Path(self.root).resolve()
|
|
98
|
+
image_path = (image_root / relative_path).resolve()
|
|
99
|
+
try:
|
|
100
|
+
image_path.relative_to(image_root)
|
|
101
|
+
except ValueError as exc:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"COCO image ID {image_id} resolves outside the image root: {file_name!r}."
|
|
104
|
+
) from exc
|
|
105
|
+
image = cv2.imread(str(image_path)) # Load image (BGR format)
|
|
106
|
+
|
|
107
|
+
if image is None:
|
|
108
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
109
|
+
|
|
110
|
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Convert to RGB
|
|
111
|
+
|
|
112
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, int, int, int]:
|
|
113
|
+
"""Get the image and target by index"""
|
|
114
|
+
image_id = self.ids[index]
|
|
115
|
+
image = self._load_image(image_id)
|
|
116
|
+
height = self.coco.imgs[image_id]["height"]
|
|
117
|
+
width = self.coco.imgs[image_id]["width"]
|
|
118
|
+
if (height, width) != image.shape[:2]:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
"COCO annotation geometry does not match decoded image for "
|
|
121
|
+
f"image ID {image_id}: annotation {(height, width)}, "
|
|
122
|
+
f"image {image.shape[:2]}."
|
|
123
|
+
)
|
|
124
|
+
return image, index, height, width
|
|
125
|
+
|
|
126
|
+
def __len__(self) -> int:
|
|
127
|
+
"""Return the total number of images"""
|
|
128
|
+
return len(self.ids)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
CustomCocodata = CustomCOCODataset
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_coco_loader(
|
|
135
|
+
dataset: CustomCOCODataset,
|
|
136
|
+
batch_size: int,
|
|
137
|
+
preprocess_fn: Callable,
|
|
138
|
+
) -> torch.utils.data.DataLoader:
|
|
139
|
+
"""Creates a DataLoader for the COCO dataset.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
dataset (CustomCOCODataset): The dataset instance to load from.
|
|
143
|
+
batch_size (int): Number of samples per batch.
|
|
144
|
+
preprocess_fn (Callable): Function used to preprocess images.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
torch.utils.data.DataLoader: A configured DataLoader for the COCO dataset.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def loader(
|
|
151
|
+
batch: list[Any],
|
|
152
|
+
) -> tuple[np.ndarray, np.ndarray, list[Any], tuple[int, ...]]:
|
|
153
|
+
"""Collate function for COCO DataLoader."""
|
|
154
|
+
batch = list(filter(lambda x: x is not None, batch))
|
|
155
|
+
images, idx, height, width = zip(*batch)
|
|
156
|
+
|
|
157
|
+
processed_images = []
|
|
158
|
+
ratio_pads = []
|
|
159
|
+
for img in images:
|
|
160
|
+
processed = preprocess_fn(img)
|
|
161
|
+
if (
|
|
162
|
+
isinstance(processed, tuple)
|
|
163
|
+
and len(processed) == 2
|
|
164
|
+
and isinstance(processed[1], dict)
|
|
165
|
+
):
|
|
166
|
+
processed_img, metadata = processed
|
|
167
|
+
ratio_pads.append(metadata.get("ratio_pad"))
|
|
168
|
+
else:
|
|
169
|
+
processed_img = processed
|
|
170
|
+
ratio_pads.append(None)
|
|
171
|
+
processed_images.append(processed_img)
|
|
172
|
+
|
|
173
|
+
height_arr = np.array(height)
|
|
174
|
+
width_arr = np.array(width)
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
np.stack(processed_images, axis=0),
|
|
178
|
+
np.stack((height_arr, width_arr), axis=1),
|
|
179
|
+
ratio_pads,
|
|
180
|
+
idx,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return torch.utils.data.DataLoader(
|
|
184
|
+
dataset,
|
|
185
|
+
batch_size=batch_size,
|
|
186
|
+
shuffle=False,
|
|
187
|
+
num_workers=0,
|
|
188
|
+
collate_fn=loader,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class CustomNYUDepth(torch.utils.data.Dataset[tuple[np.ndarray, np.ndarray, str]]):
|
|
193
|
+
"""NYU Depth V2 validation dataset with paired RGB images and ``.npy`` depth maps."""
|
|
194
|
+
|
|
195
|
+
IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
|
|
196
|
+
|
|
197
|
+
def __init__(self, root: str) -> None:
|
|
198
|
+
"""Validate the organizer's ``images/`` and ``depth/`` validation-only layout."""
|
|
199
|
+
|
|
200
|
+
self.root = root
|
|
201
|
+
image_root, depth_root = (
|
|
202
|
+
os.path.join(root, "images"),
|
|
203
|
+
os.path.join(root, "depth"),
|
|
204
|
+
)
|
|
205
|
+
if not os.path.isdir(image_root) or not os.path.isdir(depth_root):
|
|
206
|
+
raise FileNotFoundError(
|
|
207
|
+
f"NYU Depth requires images/ and depth/ directories under: {root}"
|
|
208
|
+
)
|
|
209
|
+
images = _unique_paths_by_stem(
|
|
210
|
+
image_root, self.IMG_EXTENSIONS, "NYU Depth images"
|
|
211
|
+
)
|
|
212
|
+
depths = _unique_paths_by_stem(depth_root, (".npy",), "NYU Depth depth maps")
|
|
213
|
+
missing_depths, missing_images = (
|
|
214
|
+
sorted(set(images) - set(depths)),
|
|
215
|
+
sorted(set(depths) - set(images)),
|
|
216
|
+
)
|
|
217
|
+
if missing_depths or missing_images:
|
|
218
|
+
details = []
|
|
219
|
+
if missing_depths:
|
|
220
|
+
details.append(
|
|
221
|
+
f"images without depth maps: {', '.join(missing_depths[:5])}"
|
|
222
|
+
)
|
|
223
|
+
if missing_images:
|
|
224
|
+
details.append(
|
|
225
|
+
f"depth maps without images: {', '.join(missing_images[:5])}"
|
|
226
|
+
)
|
|
227
|
+
raise ValueError(f"NYU Depth image/depth mismatch ({'; '.join(details)}).")
|
|
228
|
+
if not images:
|
|
229
|
+
raise ValueError(f"NYU Depth contains no image/depth pairs: {root}")
|
|
230
|
+
self.samples = [(images[stem], depths[stem], stem) for stem in sorted(images)]
|
|
231
|
+
|
|
232
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, np.ndarray, str]:
|
|
233
|
+
"""Load an RGB image and finite-safe depth target."""
|
|
234
|
+
|
|
235
|
+
image_path, depth_path, stem = self.samples[index]
|
|
236
|
+
image = cv2.imread(image_path)
|
|
237
|
+
if image is None:
|
|
238
|
+
raise FileNotFoundError(f"NYU Depth image not found: {image_path}")
|
|
239
|
+
raw_depth = np.load(depth_path, allow_pickle=False)
|
|
240
|
+
if not np.issubdtype(raw_depth.dtype, np.number) or np.issubdtype(
|
|
241
|
+
raw_depth.dtype, np.complexfloating
|
|
242
|
+
):
|
|
243
|
+
raise ValueError(
|
|
244
|
+
"NYU Depth target must use a real numeric dtype, "
|
|
245
|
+
f"got {raw_depth.dtype}: {depth_path}"
|
|
246
|
+
)
|
|
247
|
+
depth = np.asarray(raw_depth, dtype=np.float32)
|
|
248
|
+
if depth.ndim != 2:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"NYU Depth target must be two-dimensional, got {depth.shape}: {depth_path}"
|
|
251
|
+
)
|
|
252
|
+
if depth.shape != image.shape[:2]:
|
|
253
|
+
raise ValueError(
|
|
254
|
+
"NYU Depth image and target shapes must match for "
|
|
255
|
+
f"{stem}: image {image.shape[:2]}, depth {depth.shape}."
|
|
256
|
+
)
|
|
257
|
+
if not bool(np.isfinite(depth).all()):
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"NYU Depth target must contain only finite values: {depth_path}"
|
|
260
|
+
)
|
|
261
|
+
if bool((depth < 0).any()):
|
|
262
|
+
raise ValueError(
|
|
263
|
+
f"NYU Depth target must not contain negative values: {depth_path}"
|
|
264
|
+
)
|
|
265
|
+
return (
|
|
266
|
+
cv2.cvtColor(image, cv2.COLOR_BGR2RGB),
|
|
267
|
+
depth,
|
|
268
|
+
stem,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def __len__(self) -> int:
|
|
272
|
+
"""Return the number of paired validation samples."""
|
|
273
|
+
|
|
274
|
+
return len(self.samples)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def get_nyu_depth_loader(
|
|
278
|
+
dataset: CustomNYUDepth,
|
|
279
|
+
batch_size: int,
|
|
280
|
+
preprocess_fn: Callable,
|
|
281
|
+
image_size: tuple[int, int] | None = None,
|
|
282
|
+
) -> torch.utils.data.DataLoader:
|
|
283
|
+
"""Create a NYU Depth loader with optional stretch-to-size validation preprocessing.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
dataset: Paired NYU Depth validation dataset.
|
|
287
|
+
batch_size: Number of samples per batch.
|
|
288
|
+
preprocess_fn: Preprocessing applied after an optional validation resize.
|
|
289
|
+
image_size: Optional ``(height, width)`` used to stretch RGB inputs with bilinear
|
|
290
|
+
interpolation and depth targets with nearest-neighbor interpolation. This
|
|
291
|
+
matches the Ultralytics depth validation pipeline.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Configured NYU Depth validation loader.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def loader(
|
|
298
|
+
batch: list[Any],
|
|
299
|
+
) -> tuple[
|
|
300
|
+
np.ndarray, list[np.ndarray], list[tuple[int, int]], list[Any], tuple[str, ...]
|
|
301
|
+
]:
|
|
302
|
+
images, targets, stems = zip(*batch)
|
|
303
|
+
processed_images, shapes, ratio_pads = [], [], []
|
|
304
|
+
processed_targets = []
|
|
305
|
+
for image, target in zip(images, targets):
|
|
306
|
+
if image_size is not None:
|
|
307
|
+
height, width = image_size
|
|
308
|
+
image = cv2.resize(
|
|
309
|
+
image, (width, height), interpolation=cv2.INTER_LINEAR
|
|
310
|
+
)
|
|
311
|
+
target = cv2.resize(
|
|
312
|
+
target, (width, height), interpolation=cv2.INTER_NEAREST
|
|
313
|
+
)
|
|
314
|
+
shapes.append(tuple(image.shape[:2]))
|
|
315
|
+
processed = preprocess_fn(image)
|
|
316
|
+
if (
|
|
317
|
+
isinstance(processed, tuple)
|
|
318
|
+
and len(processed) == 2
|
|
319
|
+
and isinstance(processed[1], dict)
|
|
320
|
+
):
|
|
321
|
+
processed_image, metadata = processed
|
|
322
|
+
ratio_pads.append(metadata.get("ratio_pad"))
|
|
323
|
+
else:
|
|
324
|
+
processed_image = processed
|
|
325
|
+
ratio_pads.append(None)
|
|
326
|
+
processed_images.append(processed_image)
|
|
327
|
+
processed_targets.append(target)
|
|
328
|
+
return np.stack(processed_images), processed_targets, shapes, ratio_pads, stems
|
|
329
|
+
|
|
330
|
+
return torch.utils.data.DataLoader(
|
|
331
|
+
dataset, batch_size=batch_size, shuffle=False, num_workers=0, collate_fn=loader
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class CustomADE20K(torch.utils.data.Dataset[tuple[np.ndarray, np.ndarray, str]]):
|
|
336
|
+
"""ADE20K validation dataset with paired RGB images and semantic PNG masks."""
|
|
337
|
+
|
|
338
|
+
IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
|
|
339
|
+
|
|
340
|
+
def __init__(self, root: str) -> None:
|
|
341
|
+
"""Validate the organizer's flat ``images/`` and ``annotations/`` layout."""
|
|
342
|
+
|
|
343
|
+
self.root = root
|
|
344
|
+
image_root = os.path.join(root, "images")
|
|
345
|
+
annotation_root = os.path.join(root, "annotations")
|
|
346
|
+
if not os.path.isdir(image_root) or not os.path.isdir(annotation_root):
|
|
347
|
+
raise FileNotFoundError(
|
|
348
|
+
f"ADE20K requires images/ and annotations/ directories under: {root}"
|
|
349
|
+
)
|
|
350
|
+
images = _unique_paths_by_stem(image_root, self.IMG_EXTENSIONS, "ADE20K images")
|
|
351
|
+
annotations = _unique_paths_by_stem(
|
|
352
|
+
annotation_root, (".png",), "ADE20K annotations"
|
|
353
|
+
)
|
|
354
|
+
missing_annotations = sorted(set(images) - set(annotations))
|
|
355
|
+
missing_images = sorted(set(annotations) - set(images))
|
|
356
|
+
if missing_annotations or missing_images:
|
|
357
|
+
details = []
|
|
358
|
+
if missing_annotations:
|
|
359
|
+
details.append(
|
|
360
|
+
f"images without annotations: {', '.join(missing_annotations[:5])}"
|
|
361
|
+
)
|
|
362
|
+
if missing_images:
|
|
363
|
+
details.append(
|
|
364
|
+
f"annotations without images: {', '.join(missing_images[:5])}"
|
|
365
|
+
)
|
|
366
|
+
raise ValueError(
|
|
367
|
+
f"ADE20K image/annotation mismatch ({'; '.join(details)})."
|
|
368
|
+
)
|
|
369
|
+
if not images:
|
|
370
|
+
raise ValueError(f"ADE20K contains no image/annotation pairs: {root}")
|
|
371
|
+
self.samples = [
|
|
372
|
+
(images[stem], annotations[stem], stem) for stem in sorted(images)
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, np.ndarray, str]:
|
|
376
|
+
"""Load one RGB image and map its source labels to model class IDs."""
|
|
377
|
+
|
|
378
|
+
image_path, annotation_path, stem = self.samples[index]
|
|
379
|
+
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
|
|
380
|
+
if image is None:
|
|
381
|
+
raise FileNotFoundError(f"ADE20K image not found: {image_path}")
|
|
382
|
+
try:
|
|
383
|
+
with Image.open(annotation_path) as annotation_image:
|
|
384
|
+
annotation = np.asarray(annotation_image)
|
|
385
|
+
except OSError as exc:
|
|
386
|
+
raise FileNotFoundError(
|
|
387
|
+
f"ADE20K annotation not found: {annotation_path}"
|
|
388
|
+
) from exc
|
|
389
|
+
if annotation.ndim != 2 or annotation.dtype != np.uint8:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
"ADE20K annotations must be single-channel 8-bit PNG masks: "
|
|
392
|
+
f"{annotation_path}"
|
|
393
|
+
)
|
|
394
|
+
if image.shape[:2] != annotation.shape:
|
|
395
|
+
raise ValueError(
|
|
396
|
+
f"ADE20K image and annotation shapes must match, got {image.shape[:2]} and {annotation.shape}: {stem}"
|
|
397
|
+
)
|
|
398
|
+
if annotation.size and int(annotation.max()) > 150:
|
|
399
|
+
raise ValueError(
|
|
400
|
+
f"ADE20K annotation values must be in [0, 150]: {annotation_path}"
|
|
401
|
+
)
|
|
402
|
+
target = np.full(annotation.shape, 255, dtype=np.uint8)
|
|
403
|
+
valid = annotation > 0
|
|
404
|
+
target[valid] = annotation[valid] - 1
|
|
405
|
+
if not valid.any():
|
|
406
|
+
raise ValueError(
|
|
407
|
+
f"ADE20K annotation contains no evaluable class IDs: {annotation_path}"
|
|
408
|
+
)
|
|
409
|
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB), target, stem
|
|
410
|
+
|
|
411
|
+
def __len__(self) -> int:
|
|
412
|
+
"""Return the number of paired validation samples."""
|
|
413
|
+
|
|
414
|
+
return len(self.samples)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def get_ade20k_loader(
|
|
418
|
+
dataset: CustomADE20K,
|
|
419
|
+
batch_size: int,
|
|
420
|
+
preprocess_fn: Callable,
|
|
421
|
+
image_size: tuple[int, int],
|
|
422
|
+
) -> torch.utils.data.DataLoader:
|
|
423
|
+
"""Create an ADE20K loader that applies matching letterbox geometry to masks.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
dataset: Paired ADE20K validation dataset.
|
|
427
|
+
batch_size: Number of samples per batch.
|
|
428
|
+
preprocess_fn: Image preprocessing function that returns letterbox metadata.
|
|
429
|
+
image_size: Configured model input size as ``(height, width)``.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Configured ADE20K validation loader.
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
def loader(
|
|
436
|
+
batch: list[Any],
|
|
437
|
+
) -> tuple[
|
|
438
|
+
np.ndarray, np.ndarray, list[tuple[int, int]], list[Any], tuple[str, ...]
|
|
439
|
+
]:
|
|
440
|
+
images, targets, stems = zip(*batch)
|
|
441
|
+
processed_images, processed_targets, shapes, ratio_pads = [], [], [], []
|
|
442
|
+
input_height, input_width = image_size
|
|
443
|
+
for image, target in zip(images, targets):
|
|
444
|
+
shapes.append(tuple(image.shape[:2]))
|
|
445
|
+
processed = preprocess_fn(image)
|
|
446
|
+
if not (
|
|
447
|
+
isinstance(processed, tuple)
|
|
448
|
+
and len(processed) == 2
|
|
449
|
+
and isinstance(processed[1], dict)
|
|
450
|
+
):
|
|
451
|
+
raise ValueError(
|
|
452
|
+
"ADE20K preprocessing must return image data and letterbox metadata."
|
|
453
|
+
)
|
|
454
|
+
processed_image, metadata = processed
|
|
455
|
+
ratio_pad = metadata.get("ratio_pad")
|
|
456
|
+
if ratio_pad is None:
|
|
457
|
+
raise ValueError(
|
|
458
|
+
"ADE20K preprocessing requires LetterBox ratio_pad metadata."
|
|
459
|
+
)
|
|
460
|
+
processed_target, target_ratio_pad = letterbox_semantic_mask(
|
|
461
|
+
target,
|
|
462
|
+
[input_height, input_width],
|
|
463
|
+
)
|
|
464
|
+
if target_ratio_pad != ratio_pad:
|
|
465
|
+
raise ValueError(
|
|
466
|
+
"ADE20K image and mask LetterBox geometry do not match."
|
|
467
|
+
)
|
|
468
|
+
processed_images.append(processed_image)
|
|
469
|
+
processed_targets.append(processed_target)
|
|
470
|
+
ratio_pads.append(ratio_pad)
|
|
471
|
+
return (
|
|
472
|
+
np.stack(processed_images),
|
|
473
|
+
np.stack(processed_targets),
|
|
474
|
+
shapes,
|
|
475
|
+
ratio_pads,
|
|
476
|
+
stems,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return torch.utils.data.DataLoader(
|
|
480
|
+
dataset, batch_size=batch_size, shuffle=False, num_workers=0, collate_fn=loader
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class CustomCityscapes(torch.utils.data.Dataset[tuple[np.ndarray, np.ndarray, str]]):
|
|
485
|
+
"""Cityscapes validation dataset with paired RGB images and source-ID masks."""
|
|
486
|
+
|
|
487
|
+
IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
|
|
488
|
+
|
|
489
|
+
def __init__(self, root: str) -> None:
|
|
490
|
+
"""Validate the organizer's flat ``images/`` and ``annotations/`` layout."""
|
|
491
|
+
|
|
492
|
+
self.root = root
|
|
493
|
+
image_root = os.path.join(root, "images")
|
|
494
|
+
annotation_root = os.path.join(root, "annotations")
|
|
495
|
+
if not os.path.isdir(image_root) or not os.path.isdir(annotation_root):
|
|
496
|
+
raise FileNotFoundError(
|
|
497
|
+
f"Cityscapes requires images/ and annotations/ directories under: {root}"
|
|
498
|
+
)
|
|
499
|
+
images = _unique_paths_by_stem(
|
|
500
|
+
image_root, self.IMG_EXTENSIONS, "Cityscapes images"
|
|
501
|
+
)
|
|
502
|
+
annotations = _unique_paths_by_stem(
|
|
503
|
+
annotation_root, (".png",), "Cityscapes annotations"
|
|
504
|
+
)
|
|
505
|
+
missing_annotations = sorted(set(images) - set(annotations))
|
|
506
|
+
missing_images = sorted(set(annotations) - set(images))
|
|
507
|
+
if missing_annotations or missing_images:
|
|
508
|
+
details = []
|
|
509
|
+
if missing_annotations:
|
|
510
|
+
details.append(
|
|
511
|
+
f"images without annotations: {', '.join(missing_annotations[:5])}"
|
|
512
|
+
)
|
|
513
|
+
if missing_images:
|
|
514
|
+
details.append(
|
|
515
|
+
f"annotations without images: {', '.join(missing_images[:5])}"
|
|
516
|
+
)
|
|
517
|
+
raise ValueError(
|
|
518
|
+
f"Cityscapes image/annotation mismatch ({'; '.join(details)})."
|
|
519
|
+
)
|
|
520
|
+
if not images:
|
|
521
|
+
raise ValueError(f"Cityscapes contains no image/annotation pairs: {root}")
|
|
522
|
+
self.samples = [
|
|
523
|
+
(images[stem], annotations[stem], stem) for stem in sorted(images)
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, np.ndarray, str]:
|
|
527
|
+
"""Load an image and map Cityscapes source IDs to contiguous train IDs."""
|
|
528
|
+
|
|
529
|
+
image_path, annotation_path, stem = self.samples[index]
|
|
530
|
+
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
|
|
531
|
+
if image is None:
|
|
532
|
+
raise FileNotFoundError(f"Cityscapes image not found: {image_path}")
|
|
533
|
+
with Image.open(annotation_path) as annotation_image:
|
|
534
|
+
annotation = np.asarray(annotation_image)
|
|
535
|
+
if annotation.ndim == 3:
|
|
536
|
+
if annotation.shape[2] not in {3, 4} or not np.array_equal(
|
|
537
|
+
annotation[..., 0], annotation[..., 1]
|
|
538
|
+
):
|
|
539
|
+
raise ValueError(
|
|
540
|
+
f"Cityscapes RGB annotation channels must contain identical source IDs: {annotation_path}"
|
|
541
|
+
)
|
|
542
|
+
if not np.array_equal(annotation[..., 0], annotation[..., 2]):
|
|
543
|
+
raise ValueError(
|
|
544
|
+
f"Cityscapes RGB annotation channels must contain identical source IDs: {annotation_path}"
|
|
545
|
+
)
|
|
546
|
+
annotation = annotation[..., 0]
|
|
547
|
+
if annotation.ndim != 2:
|
|
548
|
+
raise ValueError(
|
|
549
|
+
f"Cityscapes annotation must be grayscale or RGB-grayscale: {annotation_path}"
|
|
550
|
+
)
|
|
551
|
+
if image.shape[:2] != annotation.shape:
|
|
552
|
+
raise ValueError(
|
|
553
|
+
"Cityscapes image and annotation shapes must match, "
|
|
554
|
+
f"got {image.shape[:2]} and {annotation.shape}: {stem}"
|
|
555
|
+
)
|
|
556
|
+
if annotation.size and (
|
|
557
|
+
int(annotation.min()) < 0 or int(annotation.max()) > 255
|
|
558
|
+
):
|
|
559
|
+
raise ValueError(
|
|
560
|
+
f"Cityscapes annotation values must be in [0, 255]: {annotation_path}"
|
|
561
|
+
)
|
|
562
|
+
source_ids = np.unique(annotation.astype(np.uint8))
|
|
563
|
+
known_ids = np.array([*range(34), 255], dtype=np.uint8)
|
|
564
|
+
unknown_ids = source_ids[~np.isin(source_ids, known_ids)]
|
|
565
|
+
if unknown_ids.size:
|
|
566
|
+
raise ValueError(
|
|
567
|
+
"Cityscapes annotation contains unsupported source IDs "
|
|
568
|
+
f"{unknown_ids.tolist()}: {annotation_path}"
|
|
569
|
+
)
|
|
570
|
+
target = CITYSCAPES_SOURCE_TO_TRAIN_ID[annotation.astype(np.uint8)]
|
|
571
|
+
if not (target != 255).any():
|
|
572
|
+
raise ValueError(
|
|
573
|
+
f"Cityscapes annotation contains no evaluable class IDs: {annotation_path}"
|
|
574
|
+
)
|
|
575
|
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB), target, stem
|
|
576
|
+
|
|
577
|
+
def __len__(self) -> int:
|
|
578
|
+
"""Return the number of paired validation samples."""
|
|
579
|
+
|
|
580
|
+
return len(self.samples)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def get_cityscapes_loader(
|
|
584
|
+
dataset: CustomCityscapes,
|
|
585
|
+
batch_size: int,
|
|
586
|
+
preprocess_fn: Callable,
|
|
587
|
+
image_size: tuple[int, int],
|
|
588
|
+
) -> torch.utils.data.DataLoader:
|
|
589
|
+
"""Create a Cityscapes loader with image-matching letterbox geometry."""
|
|
590
|
+
|
|
591
|
+
def loader(
|
|
592
|
+
batch: list[Any],
|
|
593
|
+
) -> tuple[
|
|
594
|
+
np.ndarray, np.ndarray, list[tuple[int, int]], list[Any], tuple[str, ...]
|
|
595
|
+
]:
|
|
596
|
+
images, targets, stems = zip(*batch)
|
|
597
|
+
processed_images, processed_targets, shapes, ratio_pads = [], [], [], []
|
|
598
|
+
input_height, input_width = image_size
|
|
599
|
+
for image, target in zip(images, targets):
|
|
600
|
+
shapes.append(tuple(image.shape[:2]))
|
|
601
|
+
processed = preprocess_fn(image)
|
|
602
|
+
if not (
|
|
603
|
+
isinstance(processed, tuple)
|
|
604
|
+
and len(processed) == 2
|
|
605
|
+
and isinstance(processed[1], dict)
|
|
606
|
+
):
|
|
607
|
+
raise ValueError(
|
|
608
|
+
"Cityscapes preprocessing must return image data and letterbox metadata."
|
|
609
|
+
)
|
|
610
|
+
processed_image, metadata = processed
|
|
611
|
+
ratio_pad = metadata.get("ratio_pad")
|
|
612
|
+
if ratio_pad is None:
|
|
613
|
+
raise ValueError(
|
|
614
|
+
"Cityscapes preprocessing requires LetterBox ratio_pad metadata."
|
|
615
|
+
)
|
|
616
|
+
processed_target, target_ratio_pad = letterbox_semantic_mask(
|
|
617
|
+
target,
|
|
618
|
+
[input_height, input_width],
|
|
619
|
+
)
|
|
620
|
+
if target_ratio_pad != ratio_pad:
|
|
621
|
+
raise ValueError(
|
|
622
|
+
"Cityscapes image and mask LetterBox geometry do not match."
|
|
623
|
+
)
|
|
624
|
+
processed_images.append(processed_image)
|
|
625
|
+
processed_targets.append(processed_target)
|
|
626
|
+
ratio_pads.append(ratio_pad)
|
|
627
|
+
return (
|
|
628
|
+
np.stack(processed_images),
|
|
629
|
+
np.stack(processed_targets),
|
|
630
|
+
shapes,
|
|
631
|
+
ratio_pads,
|
|
632
|
+
stems,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
return torch.utils.data.DataLoader(
|
|
636
|
+
dataset, batch_size=batch_size, shuffle=False, num_workers=0, collate_fn=loader
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class CustomDOTAv1(torch.utils.data.Dataset[tuple[np.ndarray, str, int, int]]):
|
|
641
|
+
"""Custom DOTAv1 validation dataset for OBB evaluation.
|
|
642
|
+
|
|
643
|
+
Attributes:
|
|
644
|
+
root: DOTAv1 dataset root.
|
|
645
|
+
image_root: Directory containing validation images.
|
|
646
|
+
ids: Image IDs derived from file stems.
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")
|
|
650
|
+
|
|
651
|
+
def __init__(self, root: str) -> None:
|
|
652
|
+
"""Initializes the DOTAv1 validation dataset.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
root: DOTAv1 root containing flat ``images/`` or legacy
|
|
656
|
+
``images/val`` validation images.
|
|
657
|
+
|
|
658
|
+
Raises:
|
|
659
|
+
FileNotFoundError: If the validation image directory is missing.
|
|
660
|
+
ValueError: If neither supported layout contains validation images.
|
|
661
|
+
"""
|
|
662
|
+
self.root = root
|
|
663
|
+
self.image_root = os.path.join(root, "images")
|
|
664
|
+
if not os.path.isdir(self.image_root):
|
|
665
|
+
raise FileNotFoundError(
|
|
666
|
+
f"DOTAv1 image directory not found: {self.image_root}"
|
|
667
|
+
)
|
|
668
|
+
self.image_paths = self._find_image_paths(self.image_root)
|
|
669
|
+
legacy_image_root = os.path.join(self.image_root, "val")
|
|
670
|
+
if not self.image_paths and os.path.isdir(legacy_image_root):
|
|
671
|
+
self.image_root = legacy_image_root
|
|
672
|
+
self.image_paths = self._find_image_paths(self.image_root)
|
|
673
|
+
if not self.image_paths:
|
|
674
|
+
raise ValueError(
|
|
675
|
+
f"DOTAv1 validation images not found directly under {os.path.join(root, 'images')} "
|
|
676
|
+
"or its legacy `val` subdirectory."
|
|
677
|
+
)
|
|
678
|
+
self.ids = [
|
|
679
|
+
os.path.splitext(os.path.basename(path))[0] for path in self.image_paths
|
|
680
|
+
]
|
|
681
|
+
if len(self.ids) != len(set(self.ids)):
|
|
682
|
+
raise ValueError(
|
|
683
|
+
"DOTAv1 validation images contain duplicate filename stems."
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
def _find_image_paths(self, image_root: str) -> list[str]:
|
|
687
|
+
"""Return supported image files directly under a DOTAv1 image directory."""
|
|
688
|
+
|
|
689
|
+
return [
|
|
690
|
+
os.path.join(image_root, file_name)
|
|
691
|
+
for file_name in sorted(os.listdir(image_root))
|
|
692
|
+
if file_name.lower().endswith(self.IMG_EXTENSIONS)
|
|
693
|
+
]
|
|
694
|
+
|
|
695
|
+
def _load_image(self, image_path: str) -> np.ndarray:
|
|
696
|
+
"""Load an image as RGB."""
|
|
697
|
+
image = cv2.imread(image_path)
|
|
698
|
+
if image is None:
|
|
699
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
700
|
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
701
|
+
|
|
702
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, str, int, int]:
|
|
703
|
+
"""Get the image and metadata by index."""
|
|
704
|
+
image_path = self.image_paths[index]
|
|
705
|
+
image = self._load_image(image_path)
|
|
706
|
+
height, width = image.shape[:2]
|
|
707
|
+
return image, self.ids[index], height, width
|
|
708
|
+
|
|
709
|
+
def __len__(self) -> int:
|
|
710
|
+
"""Return the number of validation images."""
|
|
711
|
+
return len(self.image_paths)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def get_dota_loader(
|
|
715
|
+
dataset: CustomDOTAv1, batch_size: int, preprocess_fn: Callable
|
|
716
|
+
) -> torch.utils.data.DataLoader:
|
|
717
|
+
"""Creates a DataLoader for DOTAv1 validation.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
dataset: The DOTAv1 dataset instance.
|
|
721
|
+
batch_size: Number of samples per batch.
|
|
722
|
+
preprocess_fn: Function used to preprocess images.
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
Configured DataLoader for DOTAv1.
|
|
726
|
+
"""
|
|
727
|
+
|
|
728
|
+
def loader(
|
|
729
|
+
batch: list[Any],
|
|
730
|
+
) -> tuple[np.ndarray, np.ndarray, list[Any], tuple[str, ...]]:
|
|
731
|
+
"""Collate function for DOTAv1 DataLoader."""
|
|
732
|
+
batch = list(filter(lambda x: x is not None, batch))
|
|
733
|
+
images, image_ids, height, width = zip(*batch)
|
|
734
|
+
|
|
735
|
+
processed_images = []
|
|
736
|
+
ratio_pads = []
|
|
737
|
+
for img in images:
|
|
738
|
+
processed = preprocess_fn(img)
|
|
739
|
+
if (
|
|
740
|
+
isinstance(processed, tuple)
|
|
741
|
+
and len(processed) == 2
|
|
742
|
+
and isinstance(processed[1], dict)
|
|
743
|
+
):
|
|
744
|
+
processed_img, metadata = processed
|
|
745
|
+
ratio_pads.append(metadata.get("ratio_pad"))
|
|
746
|
+
else:
|
|
747
|
+
processed_img = processed
|
|
748
|
+
ratio_pads.append(None)
|
|
749
|
+
processed_images.append(processed_img)
|
|
750
|
+
|
|
751
|
+
return (
|
|
752
|
+
np.stack(processed_images, axis=0),
|
|
753
|
+
np.stack((np.array(height), np.array(width)), axis=1),
|
|
754
|
+
ratio_pads,
|
|
755
|
+
image_ids,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
return torch.utils.data.DataLoader(
|
|
759
|
+
dataset,
|
|
760
|
+
batch_size=batch_size,
|
|
761
|
+
shuffle=False,
|
|
762
|
+
num_workers=0,
|
|
763
|
+
collate_fn=loader,
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
class CustomImageFolder(torch.utils.data.Dataset[tuple[Image.Image, int]]):
|
|
768
|
+
"""Custom ImageFolder dataset for loading images from class-based directory structures.
|
|
769
|
+
|
|
770
|
+
Expects data to be organized in the format: root/class_name/image.jpg.
|
|
771
|
+
|
|
772
|
+
Attributes:
|
|
773
|
+
root (str): Root directory path.
|
|
774
|
+
classes (list[str]): List of class names found in the root directory.
|
|
775
|
+
class_to_idx (dict): Mapping from class name to class index.
|
|
776
|
+
samples (list[tuple]): List of (image_path, class_index) tuples.
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
def __init__(self, root: str) -> None:
|
|
780
|
+
"""Initializes the CustomImageFolder instance.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
root (str): Path to the root directory.
|
|
784
|
+
"""
|
|
785
|
+
self.root = root
|
|
786
|
+
self.classes, self.class_to_idx = self.find_classes(root)
|
|
787
|
+
self.samples: list[tuple[str, int]] = []
|
|
788
|
+
self.make_dataset()
|
|
789
|
+
|
|
790
|
+
def make_dataset(self) -> None:
|
|
791
|
+
"""Scans the root directory to create a list of samples."""
|
|
792
|
+
instances = []
|
|
793
|
+
for target_class in sorted(self.class_to_idx.keys()):
|
|
794
|
+
class_index = self.class_to_idx[target_class]
|
|
795
|
+
target_dir = os.path.join(self.root, target_class)
|
|
796
|
+
if not os.path.isdir(target_dir):
|
|
797
|
+
continue
|
|
798
|
+
for fname in sorted(os.listdir(target_dir)):
|
|
799
|
+
path = os.path.join(target_dir, fname)
|
|
800
|
+
if not os.path.isfile(path):
|
|
801
|
+
continue
|
|
802
|
+
if os.path.splitext(fname)[1].lower() not in IMAGE_SUFFIXES:
|
|
803
|
+
continue
|
|
804
|
+
instances.append((path, class_index))
|
|
805
|
+
|
|
806
|
+
self.samples = instances
|
|
807
|
+
|
|
808
|
+
def loader(self, path: str) -> Image.Image:
|
|
809
|
+
"""Load image from path using PIL."""
|
|
810
|
+
with open(path, "rb") as f:
|
|
811
|
+
img = Image.open(f)
|
|
812
|
+
return img.convert("RGB")
|
|
813
|
+
|
|
814
|
+
def find_classes(self, directory: str) -> tuple[list[str], dict[str, int]]:
|
|
815
|
+
"""Find classes in the specified directory."""
|
|
816
|
+
classes = sorted([d.name for d in os.scandir(directory) if d.is_dir()])
|
|
817
|
+
class_to_idx = {cls: i for i, cls in enumerate(classes)}
|
|
818
|
+
return classes, class_to_idx
|
|
819
|
+
|
|
820
|
+
def __getitem__(self, index: int) -> tuple[Image.Image, int]:
|
|
821
|
+
"""
|
|
822
|
+
Get sample and target at the specified index.
|
|
823
|
+
Args:
|
|
824
|
+
index (int): Index of the sample to retrieve.
|
|
825
|
+
Returns:
|
|
826
|
+
tuple: (sample, target) where sample is the loaded image and target is the class index.
|
|
827
|
+
"""
|
|
828
|
+
path, target = self.samples[index]
|
|
829
|
+
sample = self.loader(path)
|
|
830
|
+
return sample, target
|
|
831
|
+
|
|
832
|
+
def __len__(self) -> int:
|
|
833
|
+
"""
|
|
834
|
+
Return the total number of samples.
|
|
835
|
+
Returns:
|
|
836
|
+
int: Number of samples in the dataset.
|
|
837
|
+
"""
|
|
838
|
+
return len(self.samples)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def get_imagenet_loader(
|
|
842
|
+
dataset: CustomImageFolder, batch_size: int, preprocess_fn: Callable
|
|
843
|
+
) -> torch.utils.data.DataLoader:
|
|
844
|
+
"""Creates a DataLoader for the ImageNet dataset.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
dataset (CustomImageFolder): The dataset instance to load from.
|
|
848
|
+
batch_size (int): Number of samples per batch.
|
|
849
|
+
preprocess_fn (Callable): Function used to preprocess images.
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
torch.utils.data.DataLoader: A configured DataLoader for the ImageNet dataset.
|
|
853
|
+
"""
|
|
854
|
+
|
|
855
|
+
def loader(batch: list[Any]) -> tuple[np.ndarray, np.ndarray]:
|
|
856
|
+
"""Collate function for ImageNet DataLoader."""
|
|
857
|
+
batch = list(filter(lambda x: x is not None, batch)) # remove None
|
|
858
|
+
images, labels = zip(*batch)
|
|
859
|
+
processed_images = []
|
|
860
|
+
for img in images:
|
|
861
|
+
img = preprocess_fn(img)
|
|
862
|
+
processed_images.append(img)
|
|
863
|
+
|
|
864
|
+
return (
|
|
865
|
+
np.stack(processed_images, axis=0),
|
|
866
|
+
np.array(labels),
|
|
867
|
+
) # BHWC, labels
|
|
868
|
+
|
|
869
|
+
return torch.utils.data.DataLoader(
|
|
870
|
+
dataset,
|
|
871
|
+
batch_size=batch_size,
|
|
872
|
+
shuffle=False,
|
|
873
|
+
num_workers=0,
|
|
874
|
+
collate_fn=loader,
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
class CustomWiderFaceDataset(torch.utils.data.Dataset[tuple[np.ndarray, str, str]]):
|
|
879
|
+
"""Custom dataset class for the WiderFace dataset.
|
|
880
|
+
|
|
881
|
+
Attributes:
|
|
882
|
+
root (str): Path to the root directory containing WiderFace images.
|
|
883
|
+
classes (list[str]): List of class/event names found in the root.
|
|
884
|
+
samples (list[tuple]): List of (image_path, class_name, file_name) tuples.
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
def __init__(self, root: str) -> None:
|
|
888
|
+
"""Initialize the custom WiderFace dataset.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
root (str): Path to the directory containing WiderFace images.
|
|
892
|
+
"""
|
|
893
|
+
self.root = root
|
|
894
|
+
self.classes = self.find_classes(root)
|
|
895
|
+
self.samples: list[tuple[str, str, str]] = []
|
|
896
|
+
self.make_dataset()
|
|
897
|
+
|
|
898
|
+
def make_dataset(self) -> None:
|
|
899
|
+
"""Scans the root directory to create a list of samples."""
|
|
900
|
+
instances = []
|
|
901
|
+
for target_class in self.classes:
|
|
902
|
+
target_dir = os.path.join(self.root, target_class)
|
|
903
|
+
if not os.path.isdir(target_dir):
|
|
904
|
+
continue
|
|
905
|
+
for fname in sorted(os.listdir(target_dir)):
|
|
906
|
+
path = os.path.join(target_dir, fname)
|
|
907
|
+
if (
|
|
908
|
+
os.path.islink(path)
|
|
909
|
+
or not os.path.isfile(path)
|
|
910
|
+
or os.path.splitext(fname)[1].lower() not in IMAGE_SUFFIXES
|
|
911
|
+
):
|
|
912
|
+
continue
|
|
913
|
+
instances.append((path, target_class, fname))
|
|
914
|
+
|
|
915
|
+
self.samples = instances
|
|
916
|
+
|
|
917
|
+
def loader(self, image_path: str) -> np.ndarray:
|
|
918
|
+
"""Load image by image path"""
|
|
919
|
+
image = cv2.imread(image_path) # Load image (BGR format)
|
|
920
|
+
if image is None:
|
|
921
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
922
|
+
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Convert to RGB
|
|
923
|
+
|
|
924
|
+
def find_classes(self, directory: str) -> list[str]:
|
|
925
|
+
"""Find classes in the specified directory."""
|
|
926
|
+
unsorted_classes = [d.name for d in os.scandir(directory) if d.is_dir()]
|
|
927
|
+
class_to_idx = {}
|
|
928
|
+
for cls_name in unsorted_classes:
|
|
929
|
+
cls_idx = int(cls_name.split("--")[0])
|
|
930
|
+
class_to_idx[cls_name] = cls_idx
|
|
931
|
+
|
|
932
|
+
sorted_classes = sorted(
|
|
933
|
+
class_to_idx.keys(), key=lambda x: class_to_idx[x]
|
|
934
|
+
) # sort by dictionary value with ascending order
|
|
935
|
+
|
|
936
|
+
return sorted_classes
|
|
937
|
+
|
|
938
|
+
def __getitem__(self, index: int) -> tuple[np.ndarray, str, str]:
|
|
939
|
+
"""
|
|
940
|
+
Get the image and target by index.
|
|
941
|
+
Args:
|
|
942
|
+
index (int): Index of the sample to retrieve.
|
|
943
|
+
Returns:
|
|
944
|
+
tuple: (image, target_class, fname) where image is the loaded image in RGB format.
|
|
945
|
+
"""
|
|
946
|
+
image_path, target_class, fname = self.samples[index]
|
|
947
|
+
image = self.loader(image_path)
|
|
948
|
+
|
|
949
|
+
return image, target_class, fname
|
|
950
|
+
|
|
951
|
+
def __len__(self) -> int:
|
|
952
|
+
"""
|
|
953
|
+
Return the total number of images.
|
|
954
|
+
Returns:
|
|
955
|
+
int: Number of images in the dataset.
|
|
956
|
+
"""
|
|
957
|
+
return len(self.samples)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
CustomWiderface = CustomWiderFaceDataset
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def get_widerface_loader(
|
|
964
|
+
dataset: CustomWiderFaceDataset, batch_size: int, preprocess_fn: Callable
|
|
965
|
+
) -> torch.utils.data.DataLoader:
|
|
966
|
+
"""Creates a DataLoader for the WiderFace dataset.
|
|
967
|
+
|
|
968
|
+
Args:
|
|
969
|
+
dataset (CustomWiderFaceDataset): The dataset instance to load from.
|
|
970
|
+
batch_size (int): Number of samples per batch.
|
|
971
|
+
preprocess_fn (Callable): Function used to preprocess images.
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
torch.utils.data.DataLoader: A configured DataLoader for the WiderFace dataset.
|
|
975
|
+
"""
|
|
976
|
+
|
|
977
|
+
def loader(
|
|
978
|
+
batch: list[Any],
|
|
979
|
+
) -> tuple[
|
|
980
|
+
np.ndarray, np.ndarray, list[Any | None], tuple[str, ...], tuple[str, ...]
|
|
981
|
+
]:
|
|
982
|
+
"""Collate function for WiderFace DataLoader."""
|
|
983
|
+
batch = list(filter(lambda x: x is not None, batch))
|
|
984
|
+
images, target_classes, fnames = zip(*batch)
|
|
985
|
+
processed_images = []
|
|
986
|
+
heights = []
|
|
987
|
+
widths = []
|
|
988
|
+
ratio_pads = []
|
|
989
|
+
for img in images:
|
|
990
|
+
height, width = img.shape[:2]
|
|
991
|
+
processed = preprocess_fn(img)
|
|
992
|
+
if isinstance(processed, tuple):
|
|
993
|
+
processed_img, metadata = processed
|
|
994
|
+
ratio_pads.append(metadata.get("ratio_pad"))
|
|
995
|
+
else:
|
|
996
|
+
processed_img = processed
|
|
997
|
+
ratio_pads.append(None)
|
|
998
|
+
processed_images.append(processed_img)
|
|
999
|
+
heights.append(height)
|
|
1000
|
+
widths.append(width)
|
|
1001
|
+
|
|
1002
|
+
return (
|
|
1003
|
+
np.stack(processed_images, axis=0),
|
|
1004
|
+
np.stack((heights, widths), axis=1),
|
|
1005
|
+
ratio_pads,
|
|
1006
|
+
target_classes,
|
|
1007
|
+
fnames,
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
return torch.utils.data.DataLoader(
|
|
1011
|
+
dataset,
|
|
1012
|
+
batch_size=batch_size,
|
|
1013
|
+
shuffle=False,
|
|
1014
|
+
num_workers=0,
|
|
1015
|
+
collate_fn=loader,
|
|
1016
|
+
)
|