From 9fdfde34a6e2df75d427ffa4b90895d7c4b2f224 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:22:59 +0800 Subject: [PATCH] refactor: optimize preprocess (#227) * build(deps-dev): bump vitepress from 1.0.0-rc.40 to 1.0.0-rc.44 Bumps [vitepress](https://github.com/vuejs/vitepress) from 1.0.0-rc.40 to 1.0.0-rc.44. - [Release notes](https://github.com/vuejs/vitepress/releases) - [Changelog](https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/vitepress/compare/v1.0.0-rc.40...v1.0.0-rc.44) --- updated-dependencies: - dependency-name: vitepress dependency-type: direct:development update-type: version-update:semver-patch ... * feat: add support for sensor data classification * build(deps-dev): bump vitepress from 1.0.0-rc.44 to 1.0.1 Bumps [vitepress](https://github.com/vuejs/vitepress) from 1.0.0-rc.44 to 1.0.1. - [Release notes](https://github.com/vuejs/vitepress/releases) - [Changelog](https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/vitepress/compare/v1.0.0-rc.44...v1.0.1) --- updated-dependencies: - dependency-name: vitepress dependency-type: direct:development update-type: version-update:semver-patch ... * build(deps-dev): bump vitepress from 1.0.1 to 1.1.4 Bumps [vitepress](https://github.com/vuejs/vitepress) from 1.0.1 to 1.1.4. - [Release notes](https://github.com/vuejs/vitepress/releases) - [Changelog](https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/vitepress/compare/v1.0.1...v1.1.4) --- updated-dependencies: - dependency-name: vitepress dependency-type: direct:development update-type: version-update:semver-minor ... * refactor: async copy between device and cpu * refactor: async copy in data preprocess * refactor: cached mosaic * refactor: replace transform modules from mmyolo * chore: register modified modules * chore: update configs * fix: epochs --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: LynnL4 Co-authored-by: nullptr Co-authored-by: Hongtai Liu <51182126+LynnL4@users.noreply.github.com> --- .../3axes_accelerometer_62.5Hz_1s_classify.py | 1 + configs/accelerometer/base.py | 1 + configs/swift_yolo/base_arch.py | 76 +- .../swift_yolo_large_1xb16_300e_coco.py | 54 +- .../swift_yolo_mb2_1xb16_300e_coco.py | 54 +- .../swift_yolo_medium_1xb16_300e_coco.py | 54 +- .../swift_yolo_nano_1xb16_300e_coco.py | 54 +- .../swift_yolo_shuff_1xb16_300e_coco.py | 62 +- .../swift_yolo_small_1xb16_300e_coco.py | 62 +- .../swift_yolo_tiny_1xb16_300e_coco.py | 60 +- package-lock.json | 845 +++++++++--------- package.json | 2 +- sscma/datasets/data_preprocessors/__init__.py | 3 +- .../data_preprocessors/det_data_processor.py | 60 ++ sscma/datasets/transforms/__init__.py | 19 +- sscma/datasets/transforms/affine.py | 449 ++++++++++ sscma/datasets/transforms/base.py | 110 ++- sscma/datasets/transforms/color.py | 68 ++ .../datasets/transforms/keypoint_structure.py | 250 ++++++ sscma/datasets/transforms/loading.py | 267 +++++- sscma/datasets/transforms/mosaic.py | 289 ++++++ sscma/datasets/transforms/resize.py | 295 ++++++ sscma/engine/hooks/visualization_hook.py | 5 +- sscma/utils/inference.py | 8 +- 24 files changed, 2587 insertions(+), 561 deletions(-) create mode 100644 sscma/datasets/data_preprocessors/det_data_processor.py create mode 100644 sscma/datasets/transforms/affine.py create mode 100644 sscma/datasets/transforms/color.py create mode 100644 sscma/datasets/transforms/keypoint_structure.py create mode 100644 sscma/datasets/transforms/mosaic.py create mode 100644 sscma/datasets/transforms/resize.py diff --git a/configs/accelerometer/3axes_accelerometer_62.5Hz_1s_classify.py b/configs/accelerometer/3axes_accelerometer_62.5Hz_1s_classify.py index 0a06f70d..f4bfd19b 100644 --- a/configs/accelerometer/3axes_accelerometer_62.5Hz_1s_classify.py +++ b/configs/accelerometer/3axes_accelerometer_62.5Hz_1s_classify.py @@ -1,4 +1,5 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. + _base_ = './base.py' default_scope = 'sscma' diff --git a/configs/accelerometer/base.py b/configs/accelerometer/base.py index d1f909cc..4a64cc72 100644 --- a/configs/accelerometer/base.py +++ b/configs/accelerometer/base.py @@ -1,4 +1,5 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. + _base_ = '../_base_/default_runtime_cls.py' # defaults input type image diff --git a/configs/swift_yolo/base_arch.py b/configs/swift_yolo/base_arch.py index 135d147f..8b79f095 100644 --- a/configs/swift_yolo/base_arch.py +++ b/configs/swift_yolo/base_arch.py @@ -40,7 +40,11 @@ # Number of input data per iteration in the model training phase batch = 16 # Number of threads used to load data during training, this value should be adjusted accordingly to the training batch -workers = 4 +workers = 16 +# Whether to use cached data when performing data augmentation +use_cached = True +# The maximum number of cached images +max_cached_images = 4096 # Optimizer weight decay value weight_decay = 0.0005 # SGD momentum/Adam beta1 @@ -49,6 +53,8 @@ lr_factor = 0.01 # persistent_workers must be False if num_workers is 0 persistent_workers = True +# Disable mosaic augmentation for final 10 epochs (stage 2) +close_mosaic_epochs = 15 # VAL # Batch size of a single GPU during validation @@ -116,7 +122,7 @@ model = dict( type='sscma.YOLODetector', data_preprocessor=dict( - type='mmdet.DetDataPreprocessor', mean=[0.0, 0.0, 0.0], std=[255.0, 255.0, 255.0], bgr_to_rgb=True + type='sscma.DetDataPreprocessor', mean=[0.0, 0.0, 0.0], std=[255.0, 255.0, 255.0], bgr_to_rgb=True ), backbone=dict( type='YOLOv5CSPDarknet', @@ -183,14 +189,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -198,17 +224,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( @@ -227,11 +259,12 @@ ), ) + test_pipeline = [ - dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='YOLOv5KeepRatioResize', scale=imgsz), - dict(type='LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), - dict(type='LoadAnnotations', with_bbox=True, _scope_='mmdet'), + dict(type='sscma.LoadImageFromFile', file_client_args=dict(backend='disk')), + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), + dict(type='sscma.LoadAnnotations', with_bbox=True, _scope_='mmdet'), dict( type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'scale_factor', 'pad_param'), @@ -277,7 +310,12 @@ custom_hooks = [ dict( type='EMAHook', ema_type='ExpMomentumEMA', momentum=0.0001, update_buffers=True, strict_load=False, priority=49 - ) + ), + dict( + type='mmdet.PipelineSwitchHook', + switch_epoch=epochs - close_mosaic_epochs, + switch_pipeline=train_pipeline_stage2, + ), ] val_evaluator = dict(type='mmdet.CocoMetric', proposal_nums=(100, 1, 10), ann_file=data_root + val_ann, metric='bbox') diff --git a/configs/swift_yolo/swift_yolo_large_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_large_1xb16_300e_coco.py index 1ae075d7..65a95bae 100644 --- a/configs/swift_yolo/swift_yolo_large_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_large_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 640 width = 640 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -83,14 +85,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -98,17 +120,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( diff --git a/configs/swift_yolo/swift_yolo_mb2_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_mb2_1xb16_300e_coco.py index 034a2dcf..bbbe8e6e 100644 --- a/configs/swift_yolo/swift_yolo_mb2_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_mb2_1xb16_300e_coco.py @@ -35,7 +35,9 @@ # batch batch = 8 # workers -workers = 2 +workers = 8 +use_cached = True +max_cached_images = 4096 # Batch size of a single GPU during validation val_batch = 1 # Worker to pre-fetch data for each single GPU during validation @@ -181,14 +183,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -196,17 +218,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( diff --git a/configs/swift_yolo/swift_yolo_medium_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_medium_1xb16_300e_coco.py index 82063d0f..f47cade0 100644 --- a/configs/swift_yolo/swift_yolo_medium_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_medium_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 640 width = 640 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -83,14 +85,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -98,17 +120,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( diff --git a/configs/swift_yolo/swift_yolo_nano_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_nano_1xb16_300e_coco.py index fdc03442..576770e9 100644 --- a/configs/swift_yolo/swift_yolo_nano_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_nano_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 192 width = 192 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -83,14 +85,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -98,17 +120,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( diff --git a/configs/swift_yolo/swift_yolo_shuff_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_shuff_1xb16_300e_coco.py index 375defd5..f4edf4ff 100644 --- a/configs/swift_yolo/swift_yolo_shuff_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_shuff_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 320 width = 320 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -93,14 +95,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -108,17 +130,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( @@ -138,10 +166,10 @@ ) test_pipeline = [ - dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='YOLOv5KeepRatioResize', scale=imgsz), - dict(type='LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), - dict(type='LoadAnnotations', with_bbox=True, _scope_='mmdet'), + dict(type='sscma.LoadImageFromFile', file_client_args=dict(backend='disk')), + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), + dict(type='sscma.LoadAnnotations', with_bbox=True, _scope_='mmdet'), dict( type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'scale_factor', 'pad_param'), diff --git a/configs/swift_yolo/swift_yolo_small_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_small_1xb16_300e_coco.py index fe0e0d05..8df2b23d 100644 --- a/configs/swift_yolo/swift_yolo_small_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_small_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 640 width = 640 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -83,14 +85,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -98,17 +120,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( @@ -128,10 +156,10 @@ ) test_pipeline = [ - dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='YOLOv5KeepRatioResize', scale=imgsz), - dict(type='LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), - dict(type='LoadAnnotations', with_bbox=True, _scope_='mmdet'), + dict(type='sscma.LoadImageFromFile', file_client_args=dict(backend='disk')), + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), + dict(type='sscma.LoadAnnotations', with_bbox=True, _scope_='mmdet'), dict( type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'scale_factor', 'pad_param'), diff --git a/configs/swift_yolo/swift_yolo_tiny_1xb16_300e_coco.py b/configs/swift_yolo/swift_yolo_tiny_1xb16_300e_coco.py index 9b31e533..66c68cb0 100644 --- a/configs/swift_yolo/swift_yolo_tiny_1xb16_300e_coco.py +++ b/configs/swift_yolo/swift_yolo_tiny_1xb16_300e_coco.py @@ -19,7 +19,9 @@ height = 640 width = 640 batch = 16 -workers = 2 +workers = 16 +use_cached = True +max_cached_images = 4096 val_batch = batch val_workers = workers imgsz = (width, height) @@ -83,14 +85,34 @@ pre_transform = [ dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.YOLOLoadAnnotations', with_bbox=True), +] + +last_transform = [ + dict( + type='mmdet.Albu', + transforms=albu_train_transforms, + bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), + keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, + ), + dict(type='sscma.YOLOv5HSVRandomAug'), + dict(type='mmdet.RandomFlip', prob=0.5), + dict( + type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + ), ] train_pipeline = [ *pre_transform, - dict(type='Mosaic', img_scale=imgsz, pad_val=114.0, pre_transform=pre_transform, _scope_='sscma'), dict( - type='YOLOv5RandomAffine', + type='sscma.Mosaic', + img_scale=imgsz, + pad_val=114.0, + use_cached=use_cached, + max_cached_images=max_cached_images, + ), + dict( + type='sscma.YOLOv5RandomAffine', max_rotate_degree=0.0, max_shear_degree=0.0, scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), @@ -98,17 +120,23 @@ border=(-imgsz[0] // 2, -imgsz[1] // 2), border_val=(114, 114, 114), ), + *last_transform, +] + +train_pipeline_stage2 = [ + *pre_transform, + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=True, pad_val=dict(img=114.0)), dict( - type='mmdet.Albu', - transforms=albu_train_transforms, - bbox_params=dict(type='BboxParams', format='pascal_voc', label_fields=['gt_bboxes_labels', 'gt_ignore_flags']), - keymap={'img': 'image', 'gt_bboxes': 'bboxes'}, - ), - dict(type='YOLOv5HSVRandomAug'), - dict(type='mmdet.RandomFlip', prob=0.5), - dict( - type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'flip', 'flip_direction') + type='sscma.YOLOv5RandomAffine', + max_rotate_degree=0.0, + max_shear_degree=0.0, + scaling_ratio_range=(1 - affine_scale, 1 + affine_scale), + # imgsz is (width, height) + border=(-imgsz[0] // 2, -imgsz[1] // 2), + border_val=(114, 114, 114), ), + *last_transform, ] train_dataloader = dict( @@ -128,10 +156,10 @@ ) test_pipeline = [ - dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), - dict(type='YOLOv5KeepRatioResize', scale=imgsz), + dict(type='sscma.LoadImageFromFile', file_client_args=dict(backend='disk')), + dict(type='sscma.YOLOv5KeepRatioResize', scale=imgsz), dict(type='sscma.LetterResize', scale=imgsz, allow_scale_up=False, pad_val=dict(img=114)), - dict(type='LoadAnnotations', with_bbox=True), + dict(type='sscma.LoadAnnotations', with_bbox=True, _scope_='mmdet'), dict( type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'scale_factor', 'pad_param'), diff --git a/package-lock.json b/package-lock.json index 9816e850..013f52a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "vitepress": "^1.0.0-rc.44" + "vitepress": "^1.1.4" } }, "node_modules/@algolia/autocomplete-core": { @@ -54,138 +54,157 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.19.1.tgz", - "integrity": "sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz", + "integrity": "sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1" + "@algolia/cache-common": "4.23.2" } }, "node_modules/@algolia/cache-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.19.1.tgz", - "integrity": "sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.2.tgz", + "integrity": "sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==", "dev": true }, "node_modules/@algolia/cache-in-memory": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.19.1.tgz", - "integrity": "sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz", + "integrity": "sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1" + "@algolia/cache-common": "4.23.2" } }, "node_modules/@algolia/client-account": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.19.1.tgz", - "integrity": "sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.2.tgz", + "integrity": "sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-analytics": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.19.1.tgz", - "integrity": "sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.2.tgz", + "integrity": "sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.19.1.tgz", - "integrity": "sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.2.tgz", + "integrity": "sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-personalization": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.19.1.tgz", - "integrity": "sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.2.tgz", + "integrity": "sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/client-search": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.19.1.tgz", - "integrity": "sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.2.tgz", + "integrity": "sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/client-common": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/logger-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.19.1.tgz", - "integrity": "sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.2.tgz", + "integrity": "sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==", "dev": true }, "node_modules/@algolia/logger-console": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.19.1.tgz", - "integrity": "sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.2.tgz", + "integrity": "sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.19.1" + "@algolia/logger-common": "4.23.2" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.2.tgz", + "integrity": "sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.2", + "@algolia/cache-common": "4.23.2", + "@algolia/cache-in-memory": "4.23.2", + "@algolia/client-common": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/logger-console": "4.23.2", + "@algolia/requester-browser-xhr": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/requester-node-http": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.19.1.tgz", - "integrity": "sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz", + "integrity": "sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1" + "@algolia/requester-common": "4.23.2" } }, "node_modules/@algolia/requester-common": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.19.1.tgz", - "integrity": "sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.2.tgz", + "integrity": "sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==", "dev": true }, "node_modules/@algolia/requester-node-http": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.19.1.tgz", - "integrity": "sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz", + "integrity": "sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.19.1" + "@algolia/requester-common": "4.23.2" } }, "node_modules/@algolia/transporter": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.19.1.tgz", - "integrity": "sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.2.tgz", + "integrity": "sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.19.1", - "@algolia/logger-common": "4.19.1", - "@algolia/requester-common": "4.19.1" + "@algolia/cache-common": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/requester-common": "4.23.2" } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -195,30 +214,30 @@ } }, "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", "dev": true }, "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", "dev": true, "dependencies": { - "@docsearch/react": "3.5.2", + "@docsearch/react": "3.6.0", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", "dev": true, "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", + "@docsearch/css": "3.6.0", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -243,9 +262,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -259,9 +278,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -275,9 +294,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -291,9 +310,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -307,9 +326,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -323,9 +342,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -339,9 +358,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -355,9 +374,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -371,9 +390,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -387,9 +406,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -403,9 +422,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -419,9 +438,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -435,9 +454,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -451,9 +470,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -467,9 +486,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -483,9 +502,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -499,9 +518,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -515,9 +534,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -531,9 +550,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -547,9 +566,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -563,9 +582,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -579,9 +598,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -595,9 +614,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -617,9 +636,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", + "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", "cpu": [ "arm" ], @@ -630,9 +649,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", + "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", "cpu": [ "arm64" ], @@ -643,9 +662,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", + "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", "cpu": [ "arm64" ], @@ -656,9 +675,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", + "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", "cpu": [ "x64" ], @@ -669,9 +688,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", + "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", "cpu": [ "arm" ], @@ -682,9 +701,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", "cpu": [ "arm64" ], @@ -695,9 +714,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", + "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", "cpu": [ "arm64" ], @@ -707,10 +726,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", + "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", + "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", "cpu": [ "riscv64" ], @@ -720,10 +752,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", + "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", "cpu": [ "x64" ], @@ -734,9 +779,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", + "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", "cpu": [ "x64" ], @@ -747,9 +792,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", + "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", "cpu": [ "arm64" ], @@ -760,9 +805,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", + "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", "cpu": [ "ia32" ], @@ -773,9 +818,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", + "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", "cpu": [ "x64" ], @@ -786,18 +831,18 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.7.tgz", - "integrity": "sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.4.0.tgz", + "integrity": "sha512-CxpKLntAi64h3j+TwWqVIQObPTED0FyXLHTTh3MKXtqiQNn2JGcMQQ362LftDbc9kYbDtrksNMNoVmVXzKFYUQ==", "dev": true }, "node_modules/@shikijs/transformers": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.1.7.tgz", - "integrity": "sha512-lXz011ao4+rvweps/9h3CchBfzb1U5OtP5D51Tqc9lQYdLblWMIxQxH6Ybe1GeGINcEVM4goMyPrI0JvlIp4UQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.4.0.tgz", + "integrity": "sha512-kzvlWmWYYSeaLKRce/kgmFFORUtBtFahfXRKndor0b60ocYiXufBQM6d6w1PlMuUkdk55aor9xLvy9wy7hTEJg==", "dev": true, "dependencies": { - "shiki": "1.1.7" + "shiki": "1.4.0" } }, "node_modules/@types/estree": { @@ -813,9 +858,9 @@ "dev": true }, "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.0.1.tgz", + "integrity": "sha512-6WfOG3jXR78DW8L5cTYCVVGAsIFZskRHCDo5tbqa+qtKVt4oDRVH7hyIWu1SpDQJlmIoEivNQZ5h+AGAOrgOtQ==", "dev": true, "dependencies": { "@types/linkify-it": "*", @@ -848,71 +893,71 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", - "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.26.tgz", + "integrity": "sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.26", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", - "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.26.tgz", + "integrity": "sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-core": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", - "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.26.tgz", + "integrity": "sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.21", - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.26", + "@vue/compiler-dom": "3.4.26", + "@vue/compiler-ssr": "3.4.26", + "@vue/shared": "3.4.26", "estree-walker": "^2.0.2", - "magic-string": "^0.30.7", - "postcss": "^8.4.35", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", - "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.26.tgz", + "integrity": "sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/devtools-api": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.15.tgz", - "integrity": "sha512-kgEYWosDyWpS1vFSuJNNWUnHkP+VkL3Y+9mw+rf7ex41SwbYL/WdC3KXqAtjiSrEs7r/FrHmUTh0BkINJPFkbA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.1.3.tgz", + "integrity": "sha512-W8IwFJ/o5iUk78jpqhvScbgCsPiOp2uileDVC0NDtW38gCWhsnu9SeBTjcdu3lbwLdsjc+H1c5Msd/x9ApbcFA==", "dev": true, "dependencies": { - "@vue/devtools-kit": "^7.0.15" + "@vue/devtools-kit": "^7.1.3" } }, "node_modules/@vue/devtools-kit": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.15.tgz", - "integrity": "sha512-dT7OeCe1LUCIhHIb/yRR6Hn+XHh73r1o78onqCrxEKHdoZwBItiIeVnmJZPEUDFstIxfs+tJL231mySk3laTow==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.1.3.tgz", + "integrity": "sha512-NFskFSJMVCBXTkByuk2llzI3KD3Blcm7WqiRorWjD6nClHPgkH5BobDH08rfulqq5ocRt5xV+3qOT1Q9FXJrwQ==", "dev": true, "dependencies": { - "@vue/devtools-shared": "^7.0.15", + "@vue/devtools-shared": "^7.1.3", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", @@ -923,82 +968,82 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.15.tgz", - "integrity": "sha512-fpfvMVvS7aDgO7x2JPFiTQ1MHcCc63/bE7yTgs278gMBybuO9b3hdiZ/k0Pw1rN+RefaU9yQiFA+5CCFc1D+6w==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.1.3.tgz", + "integrity": "sha512-KJ3AfgjTn3tJz/XKF+BlVShNPecim3G21oHRue+YQOsooW+0s+qXvm09U09aO7yBza5SivL1QgxSrzAbiKWjhQ==", "dev": true, "dependencies": { "rfdc": "^1.3.1" } }, "node_modules/@vue/reactivity": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", - "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.26.tgz", + "integrity": "sha512-E/ynEAu/pw0yotJeLdvZEsp5Olmxt+9/WqzvKff0gE67tw73gmbx6tRkiagE/eH0UCubzSlGRebCbidB1CpqZQ==", "dev": true, "dependencies": { - "@vue/shared": "3.4.21" + "@vue/shared": "3.4.26" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", - "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.26.tgz", + "integrity": "sha512-AFJDLpZvhT4ujUgZSIL9pdNcO23qVFh7zWCsNdGQBw8ecLNxOOnPcK9wTTIYCmBJnuPHpukOwo62a2PPivihqw==", "dev": true, "dependencies": { - "@vue/reactivity": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/reactivity": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", - "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.26.tgz", + "integrity": "sha512-UftYA2hUXR2UOZD/Fc3IndZuCOOJgFxJsWOxDkhfVcwLbsfh2CdXE2tG4jWxBZuDAs9J9PzRTUFt1PgydEtItw==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.4.21", - "@vue/shared": "3.4.21", + "@vue/runtime-core": "3.4.26", + "@vue/shared": "3.4.26", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", - "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.26.tgz", + "integrity": "sha512-xoGAqSjYDPGAeRWxeoYwqJFD/gw7mpgzOvSxEmjWaFO2rE6qpbD1PC172YRpvKhrihkyHJkNDADFXTfCyVGhKw==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-ssr": "3.4.26", + "@vue/shared": "3.4.26" }, "peerDependencies": { - "vue": "3.4.21" + "vue": "3.4.26" } }, "node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.26.tgz", + "integrity": "sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==", "dev": true }, "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", + "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", "dev": true, "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" + "@vueuse/metadata": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -1022,14 +1067,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", - "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz", + "integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==", "dev": true, "dependencies": { - "@vueuse/core": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" + "@vueuse/core": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1088,9 +1133,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -1114,30 +1159,30 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", + "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", + "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", "dev": true, "dependencies": { - "vue-demi": ">=0.14.6" + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", "dev": true, "hasInstallScript": true, "bin": { @@ -1161,25 +1206,26 @@ } }, "node_modules/algoliasearch": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.19.1.tgz", - "integrity": "sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.2.tgz", + "integrity": "sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ==", "dev": true, "dependencies": { - "@algolia/cache-browser-local-storage": "4.19.1", - "@algolia/cache-common": "4.19.1", - "@algolia/cache-in-memory": "4.19.1", - "@algolia/client-account": "4.19.1", - "@algolia/client-analytics": "4.19.1", - "@algolia/client-common": "4.19.1", - "@algolia/client-personalization": "4.19.1", - "@algolia/client-search": "4.19.1", - "@algolia/logger-common": "4.19.1", - "@algolia/logger-console": "4.19.1", - "@algolia/requester-browser-xhr": "4.19.1", - "@algolia/requester-common": "4.19.1", - "@algolia/requester-node-http": "4.19.1", - "@algolia/transporter": "4.19.1" + "@algolia/cache-browser-local-storage": "4.23.2", + "@algolia/cache-common": "4.23.2", + "@algolia/cache-in-memory": "4.23.2", + "@algolia/client-account": "4.23.2", + "@algolia/client-analytics": "4.23.2", + "@algolia/client-common": "4.23.2", + "@algolia/client-personalization": "4.23.2", + "@algolia/client-search": "4.23.2", + "@algolia/logger-common": "4.23.2", + "@algolia/logger-console": "4.23.2", + "@algolia/recommend": "4.23.2", + "@algolia/requester-browser-xhr": "4.23.2", + "@algolia/requester-common": "4.23.2", + "@algolia/requester-node-http": "4.23.2", + "@algolia/transporter": "4.23.2" } }, "node_modules/csstype": { @@ -1201,9 +1247,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -1213,29 +1259,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/estree-walker": { @@ -1274,15 +1320,12 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/mark.js": { @@ -1334,9 +1377,9 @@ "dev": true }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -1355,16 +1398,16 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.17.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.17.1.tgz", - "integrity": "sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==", + "version": "10.20.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.20.1.tgz", + "integrity": "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw==", "dev": true, "funding": { "type": "opencollective", @@ -1378,9 +1421,9 @@ "dev": true }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", + "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -1393,42 +1436,44 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.13.2", + "@rollup/rollup-android-arm64": "4.13.2", + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-darwin-x64": "4.13.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-arm64-musl": "4.13.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", + "@rollup/rollup-linux-riscv64-gnu": "4.13.2", + "@rollup/rollup-linux-s390x-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-musl": "4.13.2", + "@rollup/rollup-win32-arm64-msvc": "4.13.2", + "@rollup/rollup-win32-ia32-msvc": "4.13.2", + "@rollup/rollup-win32-x64-msvc": "4.13.2", "fsevents": "~2.3.2" } }, "node_modules/search-insights": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.8.2.tgz", - "integrity": "sha512-PxA9M5Q2bpBelVvJ3oDZR8nuY00Z6qwOxL53wNpgzV28M/D6u9WUbImDckjLSILBF8F1hn/mgyuUaOPtjow4Qw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", + "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", "dev": true, "peer": true }, "node_modules/shiki": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.7.tgz", - "integrity": "sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.4.0.tgz", + "integrity": "sha512-5WIn0OL8PWm7JhnTwRWXniy6eEDY234mRrERVlFa646V2ErQqwIFd2UML7e0Pq9eqSKLoMa3Ke+xbsF+DAuy+Q==", "dev": true, "dependencies": { - "@shikijs/core": "1.1.7" + "@shikijs/core": "1.4.0" } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1450,14 +1495,14 @@ "dev": true }, "node_modules/vite": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", - "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", + "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -1505,33 +1550,33 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-rc.44", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.44.tgz", - "integrity": "sha512-tO5taxGI7fSpBK1D8zrZTyJJERlyU9nnt0jHSt3fywfq3VKn977Hg0wUuTkEmwXlFYwuW26+6+3xorf4nD3XvA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.1.4.tgz", + "integrity": "sha512-bWIzFZXpPB6NIDBuWnS20aMADH+FcFKDfQNYFvbOWij03PR29eImTceQHIzCKordjXYBhM/TjE5VKFTUJ3EheA==", "dev": true, "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@shikijs/core": "^1.1.5", - "@shikijs/transformers": "^1.1.5", - "@types/markdown-it": "^13.0.7", + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.3.0", + "@shikijs/transformers": "^1.3.0", + "@types/markdown-it": "^14.0.1", "@vitejs/plugin-vue": "^5.0.4", - "@vue/devtools-api": "^7.0.14", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", + "@vue/devtools-api": "^7.0.27", + "@vueuse/core": "^10.9.0", + "@vueuse/integrations": "^10.9.0", "focus-trap": "^7.5.4", "mark.js": "8.11.1", "minisearch": "^6.3.0", - "shiki": "^1.1.5", - "vite": "^5.1.3", - "vue": "^3.4.19" + "shiki": "^1.3.0", + "vite": "^5.2.10", + "vue": "^3.4.25" }, "bin": { "vitepress": "bin/vitepress.js" }, "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.35" + "markdown-it-mathjax3": "^4", + "postcss": "^8" }, "peerDependenciesMeta": { "markdown-it-mathjax3": { @@ -1543,16 +1588,16 @@ } }, "node_modules/vue": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", - "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.26.tgz", + "integrity": "sha512-bUIq/p+VB+0xrJubaemrfhk1/FiW9iX+pDV+62I/XJ6EkspAO9/DXEjbDFoe8pIfOZBqfk45i9BMc41ptP/uRg==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-sfc": "3.4.21", - "@vue/runtime-dom": "3.4.21", - "@vue/server-renderer": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.26", + "@vue/compiler-sfc": "3.4.26", + "@vue/runtime-dom": "3.4.26", + "@vue/server-renderer": "3.4.26", + "@vue/shared": "3.4.26" }, "peerDependencies": { "typescript": "*" @@ -1564,4 +1609,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 4019e7f8..8659435e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "devDependencies": { - "vitepress": "^1.0.0-rc.44" + "vitepress": "^1.1.4" }, "scripts": { "docs:dev": "vitepress dev docs", diff --git a/sscma/datasets/data_preprocessors/__init__.py b/sscma/datasets/data_preprocessors/__init__.py index afc66c2a..0f878f50 100644 --- a/sscma/datasets/data_preprocessors/__init__.py +++ b/sscma/datasets/data_preprocessors/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. from .pointpreprocessor import ETADataPreprocessor from .SensorDataPreprocessor import SensorDataPreprocessor +from .det_data_processor import DetDataPreprocessor -__all__ = ['ETADataPreprocessor', 'SensorDataPreprocessor'] +__all__ = ['ETADataPreprocessor', 'SensorDataPreprocessor', 'DetDataPreprocessor'] diff --git a/sscma/datasets/data_preprocessors/det_data_processor.py b/sscma/datasets/data_preprocessors/det_data_processor.py new file mode 100644 index 00000000..8e2ab9d2 --- /dev/null +++ b/sscma/datasets/data_preprocessors/det_data_processor.py @@ -0,0 +1,60 @@ +from typing import Union + +import torch + +from mmengine.structures import BaseDataElement +from mmdet.models.data_preprocessors import DetDataPreprocessor as MMDetDataPreprocessor + +from sscma.registry import MODELS + +CastData = Union[tuple, dict, BaseDataElement, torch.Tensor, list, bytes, str, None] + + +@MODELS.register_module() +class DetDataPreprocessor(MMDetDataPreprocessor): + """ + Optimized data casting function for the detector model, only works for a specified input format. + Actually the most time wasting part on data casting is the data transfer between CPU and GPU. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # overide the 'cast_data' function if it exists in base class + if hasattr(super(), 'cast_data'): + if self.device.type == 'cuda': + self.cast_data = self._cast_data_cuda + elif self.device.type == 'mps': + self.cast_data = self._cast_data_mps + else: + self.cast_data = self._cast_data_default + + def _cast_data_cuda(self, data: CastData): + data_dict = { + 'inputs': [tsr.to(self.device, non_blocking=True) for tsr in data['inputs']], + 'data_samples': [smp.to(self.device, non_blocking=True) for smp in data['data_samples']], + } + + if not self._non_blocking: + torch.cuda.synchronize(self.device) + + return data_dict + + def _cast_data_mps(self, data: CastData): + data_dict = { + 'inputs': [tsr.to(self.device, non_blocking=True) for tsr in data['inputs']], + 'data_samples': [smp.to(self.device, non_blocking=True) for smp in data['data_samples']], + } + + if not self._non_blocking: + torch.mps.synchronize() + + return data_dict + + def _cast_data_default(self, data: CastData): + data_dict = { + 'inputs': [tsr.to(self.device, non_blocking=self._non_blocking) for tsr in data['inputs']], + 'data_samples': [smp.to(self.device, non_blocking=self._non_blocking) for smp in data['data_samples']], + } + + return data_dict diff --git a/sscma/datasets/transforms/__init__.py b/sscma/datasets/transforms/__init__.py index 16e6433d..0eb57c9d 100644 --- a/sscma/datasets/transforms/__init__.py +++ b/sscma/datasets/transforms/__init__.py @@ -1,7 +1,13 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. from .auto_augment import RandomRotate from .formatting import PackClsInputs, PackSensorInputs -from .loading import LoadSensorFromFile +from .loading import LoadSensorFromFile, YOLOLoadAnnotations +from .utils import BatchShapePolicy, yolov5_collate +from .wrappers import MutiBranchPipe +from .mosaic import Mosaic +from .color import YOLOv5HSVRandomAug +from .affine import YOLOv5RandomAffine +from .resize import YOLOv5KeepRatioResize, LetterResize from .processing import ( AlbumentationsCls, ColorJitterCls, @@ -12,16 +18,7 @@ RandomResizedCropCls, ResizeEdge, ) -from .transforms import ( - LetterResize, - LoadAnnotations, - Mosaic, - YOLOv5HSVRandomAug, - YOLOv5KeepRatioResize, - YOLOv5RandomAffine, -) -from .utils import BatchShapePolicy, yolov5_collate -from .wrappers import MutiBranchPipe + __all__ = [ 'PackSensorInputs', diff --git a/sscma/datasets/transforms/affine.py b/sscma/datasets/transforms/affine.py new file mode 100644 index 00000000..6116f4a6 --- /dev/null +++ b/sscma/datasets/transforms/affine.py @@ -0,0 +1,449 @@ +from typing import Tuple +import math + +import torch +import numpy as np +from numpy import random + +import cv2 +from mmcv.transforms import BaseTransform + + +from mmcv.transforms.utils import cache_randomness +from mmdet.structures.bbox import HorizontalBoxes, autocast_box_type +from mmdet.structures.mask import PolygonMasks + +from sscma.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class YOLOv5RandomAffine(BaseTransform): + """Random affine transform data augmentation in YOLOv5 and YOLOv8. It is + different from the implementation in YOLOX. + + Copyright (c) OpenMMLab. + + This operation randomly generates affine transform matrix which including + rotation, translation, shear and scaling transforms. + If you set use_mask_refine == True, the code will use the masks + annotation to refine the bbox. + Our implementation is slightly different from the official. In COCO + dataset, a gt may have multiple mask tags. The official YOLOv5 + annotation file already combines the masks that an object has, + but our code takes into account the fact that an object has multiple masks. + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - gt_masks (PolygonMasks) (optional) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + - gt_masks (PolygonMasks) (optional) + + Args: + max_rotate_degree (float): Maximum degrees of rotation transform. + Defaults to 10. + max_translate_ratio (float): Maximum ratio of translation. + Defaults to 0.1. + scaling_ratio_range (tuple[float]): Min and max ratio of + scaling transform. Defaults to (0.5, 1.5). + max_shear_degree (float): Maximum degrees of shear + transform. Defaults to 2. + border (tuple[int]): Distance from width and height sides of input + image to adjust output shape. Only used in mosaic dataset. + Defaults to (0, 0). + border_val (tuple[int]): Border padding values of 3 channels. + Defaults to (114, 114, 114). + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + min_bbox_size (float): Width and height threshold to filter bboxes. + If the height or width of a box is smaller than this value, it + will be removed. Defaults to 2. + min_area_ratio (float): Threshold of area ratio between + original bboxes and wrapped bboxes. If smaller than this value, + the box will be removed. Defaults to 0.1. + use_mask_refine (bool): Whether to refine bbox by mask. Deprecated. + max_aspect_ratio (float): Aspect ratio of width and height + threshold to filter bboxes. If max(h/w, w/h) larger than this + value, the box will be removed. Defaults to 20. + resample_num (int): Number of poly to resample to. + """ + + def __init__( + self, + max_rotate_degree: float = 10.0, + max_translate_ratio: float = 0.1, + scaling_ratio_range: Tuple[float, float] = (0.5, 1.5), + max_shear_degree: float = 2.0, + border: Tuple[int, int] = (0, 0), + border_val: Tuple[int, int, int] = (114, 114, 114), + bbox_clip_border: bool = True, + min_bbox_size: int = 2, + min_area_ratio: float = 0.1, + use_mask_refine: bool = False, + max_aspect_ratio: float = 20.0, + resample_num: int = 1000, + ): + assert 0 <= max_translate_ratio <= 1 + assert scaling_ratio_range[0] <= scaling_ratio_range[1] + assert scaling_ratio_range[0] > 0 + self.max_rotate_degree = max_rotate_degree + self.max_translate_ratio = max_translate_ratio + self.scaling_ratio_range = scaling_ratio_range + self.max_shear_degree = max_shear_degree + self.border = border + self.border_val = border_val + self.bbox_clip_border = bbox_clip_border + self.min_bbox_size = min_bbox_size + self.min_area_ratio = min_area_ratio + # The use_mask_refine parameter has been deprecated. + self.use_mask_refine = use_mask_refine + self.max_aspect_ratio = max_aspect_ratio + self.resample_num = resample_num + + @autocast_box_type() + def transform(self, results: dict) -> dict: + """The YOLOv5 random affine transform function. + + Args: + results (dict): The result dict. + + Returns: + dict: The result dict. + """ + img = results['img'] + # self.border is wh format + height = img.shape[0] + self.border[1] * 2 + width = img.shape[1] + self.border[0] * 2 + + # Note: Different from YOLOX + center_matrix = np.eye(3, dtype=np.float32) + center_matrix[0, 2] = -img.shape[1] / 2 + center_matrix[1, 2] = -img.shape[0] / 2 + + warp_matrix, scaling_ratio = self._get_random_homography_matrix(height, width) + warp_matrix = warp_matrix @ center_matrix + + img = cv2.warpPerspective(img, warp_matrix, dsize=(width, height), borderValue=self.border_val) + results['img'] = img + results['img_shape'] = img.shape + img_h, img_w = img.shape[:2] + + bboxes = results['gt_bboxes'] + num_bboxes = len(bboxes) + if num_bboxes: + orig_bboxes = bboxes.clone() + orig_bboxes.rescale_([scaling_ratio, scaling_ratio]) + if 'gt_masks' in results: + # If the dataset has annotations of mask, + # the mask will be used to refine bbox. + gt_masks = results['gt_masks'] + + gt_masks_resample = self.resample_masks(gt_masks) + gt_masks = self.warp_mask(gt_masks_resample, warp_matrix, img_h, img_w) + + # refine bboxes by masks + bboxes = self.segment2box(gt_masks, height, width) + # filter bboxes outside image + valid_index = self.filter_gt_bboxes(orig_bboxes, bboxes).numpy() + if self.bbox_clip_border: + bboxes.clip_([height - 1e-3, width - 1e-3]) + gt_masks = self.clip_polygons(gt_masks, height, width) + results['gt_masks'] = gt_masks[valid_index] + else: + bboxes.project_(warp_matrix) + if self.bbox_clip_border: + bboxes.clip_([height, width]) + + # filter bboxes + # Be careful: valid_index must convert to numpy, + # otherwise it will raise out of bounds when len(valid_index)=1 + valid_index = self.filter_gt_bboxes(orig_bboxes, bboxes).numpy() + + results['gt_bboxes'] = bboxes[valid_index] + results['gt_bboxes_labels'] = results['gt_bboxes_labels'][valid_index] + results['gt_ignore_flags'] = results['gt_ignore_flags'][valid_index] + else: + if 'gt_masks' in results: + results['gt_masks'] = PolygonMasks([], img_h, img_w) + + return results + + def segment2box(self, gt_masks: PolygonMasks, height: int, width: int) -> HorizontalBoxes: + """ + Convert 1 segment label to 1 box label, applying inside-image + constraint i.e. (xy1, xy2, ...) to (xyxy) + Args: + gt_masks (torch.Tensor): the segment label + width (int): the width of the image. Defaults to 640 + height (int): The height of the image. Defaults to 640 + Returns: + HorizontalBoxes: the clip bboxes from gt_masks. + """ + bboxes = [] + for _, poly_per_obj in enumerate(gt_masks): + # simply use a number that is big enough for comparison with + # coordinates + xy_min = np.array([width * 2, height * 2], dtype=np.float32) + xy_max = np.zeros(2, dtype=np.float32) - 1 + + for p in poly_per_obj: + xy = np.array(p).reshape(-1, 2).astype(np.float32) + x, y = xy.T + inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height) + x, y = x[inside], y[inside] + if not any(x): + continue + xy = np.stack([x, y], axis=0).T + + xy_min = np.minimum(xy_min, np.min(xy, axis=0)) + xy_max = np.maximum(xy_max, np.max(xy, axis=0)) + if xy_max[0] == -1: + bbox = np.zeros(4, dtype=np.float32) + else: + bbox = np.concatenate([xy_min, xy_max], axis=0) + bboxes.append(bbox) + + return HorizontalBoxes(np.stack(bboxes, axis=0)) + + # TODO: Move to mmdet + def clip_polygons(self, gt_masks: PolygonMasks, height: int, width: int) -> PolygonMasks: + """Function to clip points of polygons with height and width. + + Args: + gt_masks (PolygonMasks): Annotations of instance segmentation. + height (int): height of clip border. + width (int): width of clip border. + Return: + clipped_masks (PolygonMasks): + Clip annotations of instance segmentation. + """ + if len(gt_masks) == 0: + clipped_masks = PolygonMasks([], height, width) + else: + clipped_masks = [] + for poly_per_obj in gt_masks: + clipped_poly_per_obj = [] + for p in poly_per_obj: + p = p.copy() + p[0::2] = p[0::2].clip(0, width) + p[1::2] = p[1::2].clip(0, height) + clipped_poly_per_obj.append(p) + clipped_masks.append(clipped_poly_per_obj) + clipped_masks = PolygonMasks(clipped_masks, height, width) + return clipped_masks + + @staticmethod + def warp_poly(poly: np.ndarray, warp_matrix: np.ndarray, img_w: int, img_h: int) -> np.ndarray: + """Function to warp one mask and filter points outside image. + + Args: + poly (np.ndarray): Segmentation annotation with shape (n, ) and + with format (x1, y1, x2, y2, ...). + warp_matrix (np.ndarray): Affine transformation matrix. + Shape: (3, 3). + img_w (int): Width of output image. + img_h (int): Height of output image. + """ + # TODO: Current logic may cause retained masks unusable for + # semantic segmentation training, which is same as official + # implementation. + poly = poly.reshape((-1, 2)) + poly = np.concatenate((poly, np.ones((len(poly), 1), dtype=poly.dtype)), axis=-1) + # transform poly + poly = poly @ warp_matrix.T + poly = poly[:, :2] / poly[:, 2:3] + + return poly.reshape(-1) + + def warp_mask(self, gt_masks: PolygonMasks, warp_matrix: np.ndarray, img_w: int, img_h: int) -> PolygonMasks: + """Warp masks by warp_matrix and retain masks inside image after + warping. + + Args: + gt_masks (PolygonMasks): Annotations of semantic segmentation. + warp_matrix (np.ndarray): Affine transformation matrix. + Shape: (3, 3). + img_w (int): Width of output image. + img_h (int): Height of output image. + + Returns: + PolygonMasks: Masks after warping. + """ + masks = gt_masks.masks + + new_masks = [] + for poly_per_obj in masks: + warpped_poly_per_obj = [] + # One gt may have multiple masks. + for poly in poly_per_obj: + valid_poly = self.warp_poly(poly, warp_matrix, img_w, img_h) + if len(valid_poly): + warpped_poly_per_obj.append(valid_poly.reshape(-1)) + # If all the masks are invalid, + # add [0, 0, 0, 0, 0, 0,] here. + if not warpped_poly_per_obj: + # This will be filtered in function `filter_gt_bboxes`. + warpped_poly_per_obj = [np.zeros(6, dtype=poly_per_obj[0].dtype)] + new_masks.append(warpped_poly_per_obj) + + gt_masks = PolygonMasks(new_masks, img_h, img_w) + return gt_masks + + def resample_masks(self, gt_masks: PolygonMasks) -> PolygonMasks: + """Function to resample each mask annotation with shape (2 * n, ) to + shape (resample_num * 2, ). + + Args: + gt_masks (PolygonMasks): Annotations of semantic segmentation. + """ + masks = gt_masks.masks + new_masks = [] + for poly_per_obj in masks: + resample_poly_per_obj = [] + for poly in poly_per_obj: + poly = poly.reshape((-1, 2)) # xy + poly = np.concatenate((poly, poly[0:1, :]), axis=0) + x = np.linspace(0, len(poly) - 1, self.resample_num) + xp = np.arange(len(poly)) + poly = np.concatenate([np.interp(x, xp, poly[:, i]) for i in range(2)]).reshape(2, -1).T.reshape(-1) + resample_poly_per_obj.append(poly) + new_masks.append(resample_poly_per_obj) + return PolygonMasks(new_masks, gt_masks.height, gt_masks.width) + + def filter_gt_bboxes(self, origin_bboxes: HorizontalBoxes, wrapped_bboxes: HorizontalBoxes) -> torch.Tensor: + """Filter gt bboxes. + + Args: + origin_bboxes (HorizontalBoxes): Origin bboxes. + wrapped_bboxes (HorizontalBoxes): Wrapped bboxes + + Returns: + dict: The result dict. + """ + origin_w = origin_bboxes.widths + origin_h = origin_bboxes.heights + wrapped_w = wrapped_bboxes.widths + wrapped_h = wrapped_bboxes.heights + aspect_ratio = np.maximum(wrapped_w / (wrapped_h + 1e-16), wrapped_h / (wrapped_w + 1e-16)) + + wh_valid_idx = (wrapped_w > self.min_bbox_size) & (wrapped_h > self.min_bbox_size) + area_valid_idx = wrapped_w * wrapped_h / (origin_w * origin_h + 1e-16) > self.min_area_ratio + aspect_ratio_valid_idx = aspect_ratio < self.max_aspect_ratio + return wh_valid_idx & area_valid_idx & aspect_ratio_valid_idx + + @cache_randomness + def _get_random_homography_matrix(self, height: int, width: int) -> Tuple[np.ndarray, float]: + """Get random homography matrix. + + Args: + height (int): Image height. + width (int): Image width. + + Returns: + Tuple[np.ndarray, float]: The result of warp_matrix and + scaling_ratio. + """ + # Rotation + rotation_degree = random.uniform(-self.max_rotate_degree, self.max_rotate_degree) + rotation_matrix = self._get_rotation_matrix(rotation_degree) + + # Scaling + scaling_ratio = random.uniform(self.scaling_ratio_range[0], self.scaling_ratio_range[1]) + scaling_matrix = self._get_scaling_matrix(scaling_ratio) + + # Shear + x_degree = random.uniform(-self.max_shear_degree, self.max_shear_degree) + y_degree = random.uniform(-self.max_shear_degree, self.max_shear_degree) + shear_matrix = self._get_shear_matrix(x_degree, y_degree) + + # Translation + trans_x = random.uniform(0.5 - self.max_translate_ratio, 0.5 + self.max_translate_ratio) * width + trans_y = random.uniform(0.5 - self.max_translate_ratio, 0.5 + self.max_translate_ratio) * height + translate_matrix = self._get_translation_matrix(trans_x, trans_y) + warp_matrix = translate_matrix @ shear_matrix @ rotation_matrix @ scaling_matrix + return warp_matrix, scaling_ratio + + @staticmethod + def _get_rotation_matrix(rotate_degrees: float) -> np.ndarray: + """Get rotation matrix. + + Args: + rotate_degrees (float): Rotate degrees. + + Returns: + np.ndarray: The rotation matrix. + """ + radian = math.radians(rotate_degrees) + rotation_matrix = np.array( + [[np.cos(radian), -np.sin(radian), 0.0], [np.sin(radian), np.cos(radian), 0.0], [0.0, 0.0, 1.0]], + dtype=np.float32, + ) + return rotation_matrix + + @staticmethod + def _get_scaling_matrix(scale_ratio: float) -> np.ndarray: + """Get scaling matrix. + + Args: + scale_ratio (float): Scale ratio. + + Returns: + np.ndarray: The scaling matrix. + """ + scaling_matrix = np.array([[scale_ratio, 0.0, 0.0], [0.0, scale_ratio, 0.0], [0.0, 0.0, 1.0]], dtype=np.float32) + return scaling_matrix + + @staticmethod + def _get_shear_matrix(x_shear_degrees: float, y_shear_degrees: float) -> np.ndarray: + """Get shear matrix. + + Args: + x_shear_degrees (float): X shear degrees. + y_shear_degrees (float): Y shear degrees. + + Returns: + np.ndarray: The shear matrix. + """ + x_radian = math.radians(x_shear_degrees) + y_radian = math.radians(y_shear_degrees) + shear_matrix = np.array( + [[1, np.tan(x_radian), 0.0], [np.tan(y_radian), 1, 0.0], [0.0, 0.0, 1.0]], dtype=np.float32 + ) + return shear_matrix + + @staticmethod + def _get_translation_matrix(x: float, y: float) -> np.ndarray: + """Get translation matrix. + + Args: + x (float): X translation. + y (float): Y translation. + + Returns: + np.ndarray: The translation matrix. + """ + translation_matrix = np.array([[1, 0.0, x], [0.0, 1, y], [0.0, 0.0, 1.0]], dtype=np.float32) + return translation_matrix + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(max_rotate_degree={self.max_rotate_degree}, ' + repr_str += f'max_translate_ratio={self.max_translate_ratio}, ' + repr_str += f'scaling_ratio_range={self.scaling_ratio_range}, ' + repr_str += f'max_shear_degree={self.max_shear_degree}, ' + repr_str += f'border={self.border}, ' + repr_str += f'border_val={self.border_val}, ' + repr_str += f'bbox_clip_border={self.bbox_clip_border})' + return repr_str diff --git a/sscma/datasets/transforms/base.py b/sscma/datasets/transforms/base.py index 0024359d..117f1150 100644 --- a/sscma/datasets/transforms/base.py +++ b/sscma/datasets/transforms/base.py @@ -1,27 +1,101 @@ -# Copyright (c) OpenMMLab. All rights reserved. +import copy from abc import ABCMeta, abstractmethod -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Sequence, Union +from mmcv.transforms import BaseTransform +from mmdet.structures.bbox import autocast_box_type +from mmengine.dataset import BaseDataset +from mmengine.dataset.base_dataset import Compose +from numpy import random -class BaseTransform(metaclass=ABCMeta): - """Base class for all transformations.""" - def __call__(self, results: Dict) -> Optional[Union[Dict, Tuple[List, List]]]: - return self.transform(results) +class BaseMixImageTransform(BaseTransform, metaclass=ABCMeta): + + def __init__( + self, + pre_transform: Optional[Sequence[str]] = None, + prob: float = 1.0, + use_cached: bool = False, + max_cached_images: int = 40, + random_pop: bool = True, + max_refetch: int = 15, + ): + + self.max_refetch = max_refetch + self.prob = prob + + self.use_cached = use_cached + self.max_cached_images = max_cached_images + self.random_pop = random_pop + self.results_cache = [] + + if pre_transform is None: + self.pre_transform = None + else: + self.pre_transform = Compose(pre_transform) @abstractmethod - def transform(self, results: Dict) -> Optional[Union[Dict, Tuple[List, List]]]: - """The transform function. All subclass of BaseTransform should - override this method. + def get_indexes(self, dataset: Union[BaseDataset, list]) -> Union[list, int]: + pass + + @abstractmethod + def mix_img_transform(self, results: dict) -> dict: + pass + + @autocast_box_type() + def transform(self, results: dict) -> dict: + + if random.uniform(0, 1) > self.prob: + return results + + assert 'dataset' in results + dataset = results.pop('dataset', None) + + if self.use_cached: + self.results_cache.append(copy.deepcopy(results)) + if len(self.results_cache) > self.max_cached_images: + if self.random_pop: + index = random.randint(0, len(self.results_cache) - 1) + else: + index = 0 + self.results_cache.pop(index) + + for _ in range(self.max_refetch): + # get index of one or three other images + if self.use_cached: + indexes = self.get_indexes(self.results_cache) + mix_results = [copy.deepcopy(self.results_cache[i]) for i in indexes] + + else: + indexes = self.get_indexes(dataset) + mix_results = [copy.deepcopy(dataset.get_data_info(i)) for i in indexes] + + if self.pre_transform is not None: + for i, data in enumerate(mix_results): + # pre_transform may also require dataset + data.update({'dataset': dataset}) + # before Mosaic or MixUp need to go through + # the necessary pre_transform + _results = self.pre_transform(data) + _results.pop('dataset') + mix_results[i] = _results + + if None not in mix_results: + results['mix_results'] = mix_results + break + + else: + raise RuntimeError( + 'The loading pipeline of the original dataset' + ' always return None. Please check the correctness ' + 'of the dataset and its pipeline.' + ) - This function takes the result dict as the input, and can add new - items to the dict or modify existing items in the dict. And the result - dict will be returned in the end, which allows to concate multiple - transforms into a pipeline. + # Mosaic or MixUp + results = self.mix_img_transform(results) - Args: - results (dict): The result dict. + if 'mix_results' in results: + results.pop('mix_results') + results['dataset'] = dataset - Returns: - dict: The result dict. - """ + return results diff --git a/sscma/datasets/transforms/color.py b/sscma/datasets/transforms/color.py new file mode 100644 index 00000000..3ed6b158 --- /dev/null +++ b/sscma/datasets/transforms/color.py @@ -0,0 +1,68 @@ +from typing import Union + +import numpy as np +from numpy import random +import cv2 + +from mmcv.transforms import BaseTransform + +from sscma.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class YOLOv5HSVRandomAug(BaseTransform): + """Apply HSV augmentation to image sequentially. + + Copyright (c) OpenMMLab. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + hue_delta ([int, float]): delta of hue. Defaults to 0.015. + saturation_delta ([int, float]): delta of saturation. Defaults to 0.7. + value_delta ([int, float]): delta of value. Defaults to 0.4. + """ + + def __init__( + self, + hue_delta: Union[int, float] = 0.015, + saturation_delta: Union[int, float] = 0.7, + value_delta: Union[int, float] = 0.4, + ): + self.hue_delta = hue_delta + self.saturation_delta = saturation_delta + self.value_delta = value_delta + + def transform(self, results: dict) -> dict: + """The HSV augmentation transform function. + + Args: + results (dict): The result dict. + + Returns: + dict: The result dict. + """ + hsv_gains = random.uniform(-1, 1, 3) * [self.hue_delta, self.saturation_delta, self.value_delta] + 1 + hue, sat, val = cv2.split(cv2.cvtColor(results['img'], cv2.COLOR_BGR2HSV)) + + table_list = np.arange(0, 256, dtype=hsv_gains.dtype) + lut_hue = ((table_list * hsv_gains[0]) % 180).astype(np.uint8) + lut_sat = np.clip(table_list * hsv_gains[1], 0, 255).astype(np.uint8) + lut_val = np.clip(table_list * hsv_gains[2], 0, 255).astype(np.uint8) + + im_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))) + results['img'] = cv2.cvtColor(im_hsv, cv2.COLOR_HSV2BGR) + return results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(hue_delta={self.hue_delta}, ' + repr_str += f'saturation_delta={self.saturation_delta}, ' + repr_str += f'value_delta={self.value_delta})' + return repr_str diff --git a/sscma/datasets/transforms/keypoint_structure.py b/sscma/datasets/transforms/keypoint_structure.py new file mode 100644 index 00000000..fd194767 --- /dev/null +++ b/sscma/datasets/transforms/keypoint_structure.py @@ -0,0 +1,250 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta +from copy import deepcopy +from typing import List, Optional, Sequence, Tuple, Type, TypeVar, Union + +import numpy as np +import torch +from torch import Tensor + +DeviceType = Union[str, torch.device] +T = TypeVar('T') +IndexType = Union[slice, int, list, torch.LongTensor, torch.cuda.LongTensor, + torch.BoolTensor, torch.cuda.BoolTensor, np.ndarray] + + +class Keypoints(metaclass=ABCMeta): + """The Keypoints class is for keypoints representation. + + Copyright (c) OpenMMLab. + + Args: + keypoints (Tensor or np.ndarray): The keypoint data with shape of + (N, K, 2). + keypoints_visible (Tensor or np.ndarray): The visibility of keypoints + with shape of (N, K). + device (str or torch.device, Optional): device of keypoints. + Default to None. + clone (bool): Whether clone ``keypoints`` or not. Defaults to True. + flip_indices (list, Optional): The indices of keypoints when the + images is flipped. Defaults to None. + + Notes: + N: the number of instances. + K: the number of keypoints. + """ + + def __init__(self, + keypoints: Union[Tensor, np.ndarray], + keypoints_visible: Union[Tensor, np.ndarray], + device: Optional[DeviceType] = None, + clone: bool = True, + flip_indices: Optional[List] = None) -> None: + + assert len(keypoints_visible) == len(keypoints) + assert keypoints.ndim == 3 + assert keypoints_visible.ndim == 2 + + keypoints = torch.as_tensor(keypoints) + keypoints_visible = torch.as_tensor(keypoints_visible) + + if device is not None: + keypoints = keypoints.to(device=device) + keypoints_visible = keypoints_visible.to(device=device) + + if clone: + keypoints = keypoints.clone() + keypoints_visible = keypoints_visible.clone() + + self.keypoints = keypoints + self.keypoints_visible = keypoints_visible + self.flip_indices = flip_indices + + def flip_(self, + img_shape: Tuple[int, int], + direction: str = 'horizontal') -> None: + """Flip boxes & kpts horizontally in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + direction (str): Flip direction, options are "horizontal", + "vertical" and "diagonal". Defaults to "horizontal" + """ + assert direction == 'horizontal' + self.keypoints[..., 0] = img_shape[1] - self.keypoints[..., 0] + self.keypoints = self.keypoints[:, self.flip_indices] + self.keypoints_visible = self.keypoints_visible[:, self.flip_indices] + + def translate_(self, distances: Tuple[float, float]) -> None: + """Translate boxes and keypoints in-place. + + Args: + distances (Tuple[float, float]): translate distances. The first + is horizontal distance and the second is vertical distance. + """ + assert len(distances) == 2 + distances = self.keypoints.new_tensor(distances).reshape(1, 1, 2) + self.keypoints = self.keypoints + distances + + def rescale_(self, scale_factor: Tuple[float, float]) -> None: + """Rescale boxes & keypoints w.r.t. rescale_factor in-place. + + Note: + Both ``rescale_`` and ``resize_`` will enlarge or shrink boxes + w.r.t ``scale_facotr``. The difference is that ``resize_`` only + changes the width and the height of boxes, but ``rescale_`` also + rescales the box centers simultaneously. + + Args: + scale_factor (Tuple[float, float]): factors for scaling boxes. + The length should be 2. + """ + assert len(scale_factor) == 2 + + scale_factor = self.keypoints.new_tensor(scale_factor).reshape(1, 1, 2) + self.keypoints = self.keypoints * scale_factor + + def clip_(self, img_shape: Tuple[int, int]) -> None: + """Clip bounding boxes and set invisible keypoints outside the image + boundary in-place. + + Args: + img_shape (Tuple[int, int]): A tuple of image height and width. + """ + + kpt_outside = torch.logical_or( + torch.logical_or(self.keypoints[..., 0] < 0, + self.keypoints[..., 1] < 0), + torch.logical_or(self.keypoints[..., 0] > img_shape[1], + self.keypoints[..., 1] > img_shape[0])) + self.keypoints_visible[kpt_outside] *= 0 + + def project_(self, homography_matrix: Union[Tensor, np.ndarray]) -> None: + """Geometrically transform bounding boxes and keypoints in-place using + a homography matrix. + + Args: + homography_matrix (Tensor or np.ndarray): A 3x3 tensor or ndarray + representing the homography matrix for the transformation. + """ + keypoints = self.keypoints + if isinstance(homography_matrix, np.ndarray): + homography_matrix = keypoints.new_tensor(homography_matrix) + + # Convert keypoints to homogeneous coordinates + keypoints = torch.cat([ + self.keypoints, + self.keypoints.new_ones(*self.keypoints.shape[:-1], 1) + ], + dim=-1) + + # Transpose keypoints for matrix multiplication + keypoints_T = torch.transpose(keypoints, -1, 0).contiguous().flatten(1) + + # Apply homography matrix to corners and keypoints + keypoints_T = torch.matmul(homography_matrix, keypoints_T) + + # Transpose back to original shape + keypoints_T = keypoints_T.reshape(3, self.keypoints.shape[1], -1) + keypoints = torch.transpose(keypoints_T, -1, 0).contiguous() + + # Convert corners and keypoints back to non-homogeneous coordinates + keypoints = keypoints[..., :2] / keypoints[..., 2:3] + + # Convert corners back to bounding boxes and update object attributes + self.keypoints = keypoints + + @classmethod + def cat(cls: Type[T], kps_list: Sequence[T], dim: int = 0) -> T: + """Cancatenates an instance list into one single instance. Similar to + ``torch.cat``. + + Args: + box_list (Sequence[T]): A sequence of instances. + dim (int): The dimension over which the box and keypoint are + concatenated. Defaults to 0. + + Returns: + T: Concatenated instance. + """ + assert isinstance(kps_list, Sequence) + if len(kps_list) == 0: + raise ValueError('kps_list should not be a empty list.') + + assert dim == 0 + assert all(isinstance(keypoints, cls) for keypoints in kps_list) + + th_kpt_list = torch.cat( + [keypoints.keypoints for keypoints in kps_list], dim=dim) + th_kpt_vis_list = torch.cat( + [keypoints.keypoints_visible for keypoints in kps_list], dim=dim) + flip_indices = kps_list[0].flip_indices + return cls( + th_kpt_list, + th_kpt_vis_list, + clone=False, + flip_indices=flip_indices) + + def __getitem__(self: T, index: IndexType) -> T: + """Rewrite getitem to protect the last dimension shape.""" + if isinstance(index, np.ndarray): + index = torch.as_tensor(index, device=self.device) + if isinstance(index, Tensor) and index.dtype == torch.bool: + assert index.dim() < self.keypoints.dim() - 1 + elif isinstance(index, tuple): + assert len(index) < self.keypoints.dim() - 1 + # `Ellipsis`(...) is commonly used in index like [None, ...]. + # When `Ellipsis` is in index, it must be the last item. + if Ellipsis in index: + assert index[-1] is Ellipsis + + keypoints = self.keypoints[index] + keypoints_visible = self.keypoints_visible[index] + if self.keypoints.dim() == 2: + keypoints = keypoints.reshape(1, -1, 2) + keypoints_visible = keypoints_visible.reshape(1, -1) + return type(self)( + keypoints, + keypoints_visible, + flip_indices=self.flip_indices, + clone=False) + + def __repr__(self) -> str: + """Return a strings that describes the object.""" + return self.__class__.__name__ + '(\n' + str(self.keypoints) + ')' + + @property + def num_keypoints(self) -> Tensor: + """Compute the number of visible keypoints for each object.""" + return self.keypoints_visible.sum(dim=1).int() + + def __deepcopy__(self, memo): + """Only clone the tensors when applying deepcopy.""" + cls = self.__class__ + other = cls.__new__(cls) + memo[id(self)] = other + other.keypoints = self.keypoints.clone() + other.keypoints_visible = self.keypoints_visible.clone() + other.flip_indices = deepcopy(self.flip_indices) + return other + + def clone(self: T) -> T: + """Reload ``clone`` for tensors.""" + return type(self)( + self.keypoints, + self.keypoints_visible, + flip_indices=self.flip_indices, + clone=True) + + def to(self: T, *args, **kwargs) -> T: + """Reload ``to`` for tensors.""" + return type(self)( + self.keypoints.to(*args, **kwargs), + self.keypoints_visible.to(*args, **kwargs), + flip_indices=self.flip_indices, + clone=False) + + @property + def device(self) -> torch.device: + """Reload ``device`` from self.tensor.""" + return self.keypoints.device diff --git a/sscma/datasets/transforms/loading.py b/sscma/datasets/transforms/loading.py index ae66bb4e..a7cb3f36 100644 --- a/sscma/datasets/transforms/loading.py +++ b/sscma/datasets/transforms/loading.py @@ -1,13 +1,19 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. import json import warnings -from typing import Optional +from typing import Optional, List, Tuple -import mmengine.fileio as fileio +import torch import numpy as np + +import mmengine.fileio as fileio from mmcv.transforms.base import BaseTransform +from mmdet.datasets.transforms import LoadAnnotations as MMDET_LoadAnnotations +from mmdet.structures.bbox import get_box_type +from mmdet.structures.mask import PolygonMasks from sscma.registry import TRANSFORMS +from .keypoint_structure import Keypoints @TRANSFORMS.register_module() @@ -71,3 +77,260 @@ def transform(self, results: dict) -> Optional[dict]: results['sensors'] = sensors return results + + +@TRANSFORMS.register_module() +class YOLOLoadAnnotations(MMDET_LoadAnnotations): + """Because the yolo series does not need to consider ignore bboxes for the + time being, in order to speed up the pipeline, it can be excluded in + advance. + + Args: + mask2bbox (bool): Whether to use mask annotation to get bbox. + Defaults to False. + poly2mask (bool): Whether to transform the polygons to bitmaps. + Defaults to False. + merge_polygons (bool): Whether to merge polygons into one polygon. + If merged, the storage structure is simpler and training is more + effcient, especially if the mask inside a bbox is divided into + multiple polygons. Defaults to True. + """ + + def __init__(self, mask2bbox: bool = False, poly2mask: bool = False, merge_polygons: bool = True, **kwargs): + self.mask2bbox = mask2bbox + self.merge_polygons = merge_polygons + assert not poly2mask, 'Does not support BitmapMasks considering ' 'that bitmap consumes more memory.' + super().__init__(poly2mask=poly2mask, **kwargs) + if self.mask2bbox: + assert self.with_mask, 'Using mask2bbox requires ' 'with_mask is True.' + self._mask_ignore_flag = None + + def transform(self, results: dict) -> dict: + """Function to load multiple types annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + + Returns: + dict: The dict contains loaded bounding box, label and + semantic segmentation. + """ + if self.mask2bbox: + self._load_masks(results) + if self.with_label: + self._load_labels(results) + self._update_mask_ignore_data(results) + gt_bboxes = results['gt_masks'].get_bboxes(dst_type='hbox') + results['gt_bboxes'] = gt_bboxes + elif self.with_keypoints: + self._load_kps(results) + _, box_type_cls = get_box_type(self.box_type) + results['gt_bboxes'] = box_type_cls(results.get('bbox', []), dtype=torch.float32) + else: + results = super().transform(results) + self._update_mask_ignore_data(results) + return results + + def _update_mask_ignore_data(self, results: dict) -> None: + if 'gt_masks' not in results: + return + + if 'gt_bboxes_labels' in results and len(results['gt_bboxes_labels']) != len(results['gt_masks']): + assert len(results['gt_bboxes_labels']) == len(self._mask_ignore_flag) + results['gt_bboxes_labels'] = results['gt_bboxes_labels'][self._mask_ignore_flag] + + if 'gt_bboxes' in results and len(results['gt_bboxes']) != len(results['gt_masks']): + assert len(results['gt_bboxes']) == len(self._mask_ignore_flag) + results['gt_bboxes'] = results['gt_bboxes'][self._mask_ignore_flag] + + def _load_bboxes(self, results: dict): + """Private function to load bounding box annotations. + Note: BBoxes with ignore_flag of 1 is not considered. + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + + Returns: + dict: The dict contains loaded bounding box annotations. + """ + gt_bboxes = [] + gt_ignore_flags = [] + for instance in results.get('instances', []): + if instance['ignore_flag'] == 0: + gt_bboxes.append(instance['bbox']) + gt_ignore_flags.append(instance['ignore_flag']) + results['gt_ignore_flags'] = np.array(gt_ignore_flags, dtype=bool) + + if self.box_type is None: + results['gt_bboxes'] = np.array(gt_bboxes, dtype=np.float32).reshape((-1, 4)) + else: + _, box_type_cls = get_box_type(self.box_type) + results['gt_bboxes'] = box_type_cls(gt_bboxes, dtype=torch.float32) + + def _load_labels(self, results: dict): + """Private function to load label annotations. + + Note: BBoxes with ignore_flag of 1 is not considered. + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + Returns: + dict: The dict contains loaded label annotations. + """ + gt_bboxes_labels = [] + for instance in results.get('instances', []): + if instance['ignore_flag'] == 0: + gt_bboxes_labels.append(instance['bbox_label']) + results['gt_bboxes_labels'] = np.array(gt_bboxes_labels, dtype=np.int64) + + def _load_masks(self, results: dict) -> None: + """Private function to load mask annotations. + + Args: + results (dict): Result dict from :obj:``mmengine.BaseDataset``. + """ + gt_masks = [] + gt_ignore_flags = [] + self._mask_ignore_flag = [] + for instance in results.get('instances', []): + if instance['ignore_flag'] == 0: + if 'mask' in instance: + gt_mask = instance['mask'] + if isinstance(gt_mask, list): + gt_mask = [ + np.array(polygon) for polygon in gt_mask if len(polygon) % 2 == 0 and len(polygon) >= 6 + ] + if len(gt_mask) == 0: + # ignore + self._mask_ignore_flag.append(0) + else: + if len(gt_mask) > 1 and self.merge_polygons: + gt_mask = self.merge_multi_segment(gt_mask) + gt_masks.append(gt_mask) + gt_ignore_flags.append(instance['ignore_flag']) + self._mask_ignore_flag.append(1) + else: + raise NotImplementedError('Only supports mask annotations in polygon ' 'format currently') + else: + # TODO: Actually, gt with bbox and without mask needs + # to be retained + self._mask_ignore_flag.append(0) + self._mask_ignore_flag = np.array(self._mask_ignore_flag, dtype=bool) + results['gt_ignore_flags'] = np.array(gt_ignore_flags, dtype=bool) + + h, w = results['ori_shape'] + gt_masks = PolygonMasks([mask for mask in gt_masks], h, w) + results['gt_masks'] = gt_masks + + def merge_multi_segment(self, gt_masks: List[np.ndarray]) -> List[np.ndarray]: + """Merge multi segments to one list. + + Find the coordinates with min distance between each segment, + then connect these coordinates with one thin line to merge all + segments into one. + Args: + gt_masks(List(np.array)): + original segmentations in coco's json file. + like [segmentation1, segmentation2,...], + each segmentation is a list of coordinates. + Return: + gt_masks(List(np.array)): merged gt_masks + """ + s = [] + segments = [np.array(i).reshape(-1, 2) for i in gt_masks] + idx_list = [[] for _ in range(len(gt_masks))] + + # record the indexes with min distance between each segment + for i in range(1, len(segments)): + idx1, idx2 = self.min_index(segments[i - 1], segments[i]) + idx_list[i - 1].append(idx1) + idx_list[i].append(idx2) + + # use two round to connect all the segments + # first round: first to end, i.e. A->B(partial)->C + # second round: end to first, i.e. C->B(remaining)-A + for k in range(2): + # forward first round + if k == 0: + for i, idx in enumerate(idx_list): + # middle segments have two indexes + # reverse the index of middle segments + if len(idx) == 2 and idx[0] > idx[1]: + idx = idx[::-1] + segments[i] = segments[i][::-1, :] + # add the idx[0] point for connect next segment + segments[i] = np.roll(segments[i], -idx[0], axis=0) + segments[i] = np.concatenate([segments[i], segments[i][:1]]) + # deal with the first segment and the last one + if i in [0, len(idx_list) - 1]: + s.append(segments[i]) + # deal with the middle segment + # Note that in the first round, only partial segment + # are appended. + else: + idx = [0, idx[1] - idx[0]] + s.append(segments[i][idx[0] : idx[1] + 1]) + # forward second round + else: + for i in range(len(idx_list) - 1, -1, -1): + # deal with the middle segment + # append the remaining points + if i not in [0, len(idx_list) - 1]: + idx = idx_list[i] + nidx = abs(idx[1] - idx[0]) + s.append(segments[i][nidx:]) + return [ + np.concatenate(s).reshape( + -1, + ) + ] + + def min_index(self, arr1: np.ndarray, arr2: np.ndarray) -> Tuple[int, int]: + """Find a pair of indexes with the shortest distance. + + Args: + arr1: (N, 2). + arr2: (M, 2). + Return: + tuple: a pair of indexes. + """ + dis = ((arr1[:, None, :] - arr2[None, :, :]) ** 2).sum(-1) + return np.unravel_index(np.argmin(dis, axis=None), dis.shape) + + def _load_kps(self, results: dict) -> None: + """Private function to load keypoints annotations. + + Args: + results (dict): Result dict from + :class:`mmengine.dataset.BaseDataset`. + + Returns: + dict: The dict contains loaded keypoints annotations. + """ + results['height'] = results['img_shape'][0] + results['width'] = results['img_shape'][1] + num_instances = len(results.get('bbox', [])) + + if num_instances == 0: + results['keypoints'] = np.empty((0, len(results['flip_indices']), 2), dtype=np.float32) + results['keypoints_visible'] = np.empty((0, len(results['flip_indices'])), dtype=np.int32) + results['category_id'] = [] + + results['gt_keypoints'] = Keypoints( + keypoints=results['keypoints'], + keypoints_visible=results['keypoints_visible'], + flip_indices=results['flip_indices'], + ) + + results['gt_ignore_flags'] = np.array([False] * num_instances) + results['gt_bboxes_labels'] = np.array(results['category_id']) - 1 + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(with_bbox={self.with_bbox}, ' + repr_str += f'with_label={self.with_label}, ' + repr_str += f'with_mask={self.with_mask}, ' + repr_str += f'with_seg={self.with_seg}, ' + repr_str += f'mask2bbox={self.mask2bbox}, ' + repr_str += f'poly2mask={self.poly2mask}, ' + repr_str += f"imdecode_backend='{self.imdecode_backend}', " + repr_str += f'backend_args={self.backend_args})' + return repr_str diff --git a/sscma/datasets/transforms/mosaic.py b/sscma/datasets/transforms/mosaic.py new file mode 100644 index 00000000..3ac13004 --- /dev/null +++ b/sscma/datasets/transforms/mosaic.py @@ -0,0 +1,289 @@ +from typing import Sequence, Tuple, Union + +import numpy as np +from numpy import random + +import mmcv + +from mmengine.dataset import BaseDataset + +from sscma.registry import TRANSFORMS +from sscma.datasets.transforms.base import BaseMixImageTransform + + +@TRANSFORMS.register_module() +class Mosaic(BaseMixImageTransform): + """Mosaic augmentation. + + Copyright (c) OpenMMLab. + + Given 4 images, mosaic transform combines them into + one output image. The output image is composed of the parts from each sub- + image. + + .. code:: text + + mosaic transform + center_x + +------------------------------+ + | pad | | + | +-----------+ pad | + | | | | + | | image1 +-----------+ + | | | | + | | | image2 | + center_y |----+-+-----------+-----------+ + | | cropped | | + |pad | image3 | image4 | + | | | | + +----|-------------+-----------+ + | | + +-------------+ + + The mosaic transform steps are as follows: + + 1. Choose the mosaic center as the intersections of 4 images + 2. Get the left top image according to the index, and randomly + sample another 3 images from the custom dataset. + 3. Sub image will be cropped if image is larger than mosaic patch + + Required Keys: + + - img + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + - gt_bboxes_labels (np.int64) (optional) + - gt_ignore_flags (bool) (optional) + - mix_results (List[dict]) + + Modified Keys: + + - img + - img_shape + - gt_bboxes (optional) + - gt_bboxes_labels (optional) + - gt_ignore_flags (optional) + + Args: + img_scale (Sequence[int]): Image size after mosaic pipeline of single + image. The shape order should be (width, height). + Defaults to (640, 640). + center_ratio_range (Sequence[float]): Center ratio range of mosaic + output. Defaults to (0.5, 1.5). + bbox_clip_border (bool, optional): Whether to clip the objects outside + the border of the image. In some dataset like MOT17, the gt bboxes + are allowed to cross the border of images. Therefore, we don't + need to clip the gt bboxes in these cases. Defaults to True. + pad_val (int): Pad value. Defaults to 114. + use_cached (bool): Whether to use cache. Defaults to False. + max_cached_images (int): The maximum length of the cache. The larger + the cache, the stronger the randomness of this transform. As a + rule of thumb, providing 10 caches for each image suffices for + randomness. Defaults to 40. + random_pop (bool): Whether to randomly pop a result from the cache + when the cache is full. If set to False, use FIFO popping method. + Defaults to True. + max_refetch (int): The maximum number of retry iterations for getting + valid results from the pipeline. If the number of iterations is + greater than `max_refetch`, but results is still None, then the + iteration is terminated and raise the error. Defaults to 15. + """ + + def __init__( + self, + img_scale: Tuple[int, int] = (640, 640), + center_ratio_range: Tuple[float, float] = (0.5, 1.5), + bbox_clip_border: bool = True, + pad_val: float = 114.0, + pre_transform: Sequence[dict] = None, + prob: float = 1.0, + use_cached: bool = False, + max_cached_images: int = 40, + random_pop: bool = True, + max_refetch: int = 15, + ): + assert isinstance(img_scale, tuple) + if use_cached: + assert max_cached_images >= 4, 'The length of cache must >= 4, ' f'but got {max_cached_images}.' + + super().__init__( + pre_transform=pre_transform, + prob=prob, + use_cached=use_cached, + max_cached_images=max_cached_images, + random_pop=random_pop, + max_refetch=max_refetch, + ) + + self.img_scale = img_scale + self.center_ratio_range = center_ratio_range + self.bbox_clip_border = bbox_clip_border + self.pad_val = pad_val + + def get_indexes(self, dataset: Union[BaseDataset, list]) -> list: + return random.randint(0, len(dataset), 3) # 3 other images + + def mix_img_transform(self, results: dict) -> dict: + assert 'mix_results' in results + mosaic_bboxes = [] + mosaic_bboxes_labels = [] + mosaic_ignore_flags = [] + mosaic_masks = [] + mosaic_kps = [] + with_mask = True if 'gt_masks' in results else False + with_kps = True if 'gt_keypoints' in results else False + # self.img_scale is wh format + img_scale_w, img_scale_h = self.img_scale + + if len(results['img'].shape) == 3: + mosaic_img = np.full( + (int(img_scale_h * 2), int(img_scale_w * 2), 3), self.pad_val, dtype=results['img'].dtype + ) + else: + mosaic_img = np.full((int(img_scale_h * 2), int(img_scale_w * 2)), self.pad_val, dtype=results['img'].dtype) + + # mosaic center x, y + center_x = int(random.uniform(*self.center_ratio_range) * img_scale_w) + center_y = int(random.uniform(*self.center_ratio_range) * img_scale_h) + center_position = (center_x, center_y) + + loc_strs = ('top_left', 'top_right', 'bottom_left', 'bottom_right') + for i, loc in enumerate(loc_strs): + if loc == 'top_left': + results_patch = results + else: + results_patch = results['mix_results'][i - 1] + + img_i = results_patch['img'] + h_i, w_i = img_i.shape[:2] + # keep_ratio resize + scale_ratio_i = min(img_scale_h / h_i, img_scale_w / w_i) + img_i = mmcv.imresize(img_i, (int(w_i * scale_ratio_i), int(h_i * scale_ratio_i))) + + # compute the combine parameters + paste_coord, crop_coord = self._mosaic_combine(loc, center_position, img_i.shape[:2][::-1]) + x1_p, y1_p, x2_p, y2_p = paste_coord + x1_c, y1_c, x2_c, y2_c = crop_coord + + # crop and paste image + mosaic_img[y1_p:y2_p, x1_p:x2_p] = img_i[y1_c:y2_c, x1_c:x2_c] + + # adjust coordinate + gt_bboxes_i = results_patch['gt_bboxes'] + gt_bboxes_labels_i = results_patch['gt_bboxes_labels'] + gt_ignore_flags_i = results_patch['gt_ignore_flags'] + + padw = x1_p - x1_c + padh = y1_p - y1_c + gt_bboxes_i.rescale_([scale_ratio_i, scale_ratio_i]) + gt_bboxes_i.translate_([padw, padh]) + mosaic_bboxes.append(gt_bboxes_i) + mosaic_bboxes_labels.append(gt_bboxes_labels_i) + mosaic_ignore_flags.append(gt_ignore_flags_i) + if with_mask and results_patch.get('gt_masks', None) is not None: + gt_masks_i = results_patch['gt_masks'] + gt_masks_i = gt_masks_i.resize(img_i.shape[:2]) + gt_masks_i = gt_masks_i.translate( + out_shape=(int(self.img_scale[0] * 2), int(self.img_scale[1] * 2)), + offset=padw, + direction='horizontal', + ) + gt_masks_i = gt_masks_i.translate( + out_shape=(int(self.img_scale[0] * 2), int(self.img_scale[1] * 2)), + offset=padh, + direction='vertical', + ) + mosaic_masks.append(gt_masks_i) + if with_kps and results_patch.get('gt_keypoints', None) is not None: + gt_kps_i = results_patch['gt_keypoints'] + gt_kps_i.rescale_([scale_ratio_i, scale_ratio_i]) + gt_kps_i.translate_([padw, padh]) + mosaic_kps.append(gt_kps_i) + + mosaic_bboxes = mosaic_bboxes[0].cat(mosaic_bboxes, 0) + mosaic_bboxes_labels = np.concatenate(mosaic_bboxes_labels, 0) + mosaic_ignore_flags = np.concatenate(mosaic_ignore_flags, 0) + + if self.bbox_clip_border: + mosaic_bboxes.clip_([2 * img_scale_h, 2 * img_scale_w]) + if with_mask: + mosaic_masks = mosaic_masks[0].cat(mosaic_masks) + results['gt_masks'] = mosaic_masks + if with_kps: + mosaic_kps = mosaic_kps[0].cat(mosaic_kps, 0) + mosaic_kps.clip_([2 * img_scale_h, 2 * img_scale_w]) + results['gt_keypoints'] = mosaic_kps + else: + # remove outside bboxes + inside_inds = mosaic_bboxes.is_inside([2 * img_scale_h, 2 * img_scale_w]).numpy() + mosaic_bboxes = mosaic_bboxes[inside_inds] + mosaic_bboxes_labels = mosaic_bboxes_labels[inside_inds] + mosaic_ignore_flags = mosaic_ignore_flags[inside_inds] + if with_mask: + mosaic_masks = mosaic_masks[0].cat(mosaic_masks)[inside_inds] + results['gt_masks'] = mosaic_masks + if with_kps: + mosaic_kps = mosaic_kps[0].cat(mosaic_kps, 0) + mosaic_kps = mosaic_kps[inside_inds] + results['gt_keypoints'] = mosaic_kps + + results['img'] = mosaic_img + results['img_shape'] = mosaic_img.shape + results['gt_bboxes'] = mosaic_bboxes + results['gt_bboxes_labels'] = mosaic_bboxes_labels + results['gt_ignore_flags'] = mosaic_ignore_flags + + return results + + def _mosaic_combine( + self, loc: str, center_position_xy: Sequence[float], img_shape_wh: Sequence[int] + ) -> Tuple[Tuple[int], Tuple[int]]: + assert loc in ('top_left', 'top_right', 'bottom_left', 'bottom_right') + if loc == 'top_left': + # index0 to top left part of image + x1, y1, x2, y2 = ( + max(center_position_xy[0] - img_shape_wh[0], 0), + max(center_position_xy[1] - img_shape_wh[1], 0), + center_position_xy[0], + center_position_xy[1], + ) + crop_coord = img_shape_wh[0] - (x2 - x1), img_shape_wh[1] - (y2 - y1), img_shape_wh[0], img_shape_wh[1] + + elif loc == 'top_right': + # index1 to top right part of image + x1, y1, x2, y2 = ( + center_position_xy[0], + max(center_position_xy[1] - img_shape_wh[1], 0), + min(center_position_xy[0] + img_shape_wh[0], self.img_scale[0] * 2), + center_position_xy[1], + ) + crop_coord = 0, img_shape_wh[1] - (y2 - y1), min(img_shape_wh[0], x2 - x1), img_shape_wh[1] + + elif loc == 'bottom_left': + # index2 to bottom left part of image + x1, y1, x2, y2 = ( + max(center_position_xy[0] - img_shape_wh[0], 0), + center_position_xy[1], + center_position_xy[0], + min(self.img_scale[1] * 2, center_position_xy[1] + img_shape_wh[1]), + ) + crop_coord = img_shape_wh[0] - (x2 - x1), 0, img_shape_wh[0], min(y2 - y1, img_shape_wh[1]) + + else: + # index3 to bottom right part of image + x1, y1, x2, y2 = ( + center_position_xy[0], + center_position_xy[1], + min(center_position_xy[0] + img_shape_wh[0], self.img_scale[0] * 2), + min(self.img_scale[1] * 2, center_position_xy[1] + img_shape_wh[1]), + ) + crop_coord = 0, 0, min(img_shape_wh[0], x2 - x1), min(y2 - y1, img_shape_wh[1]) + + paste_coord = x1, y1, x2, y2 + return paste_coord, crop_coord + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(img_scale={self.img_scale}, ' + repr_str += f'center_ratio_range={self.center_ratio_range}, ' + repr_str += f'pad_val={self.pad_val}, ' + return repr_str diff --git a/sscma/datasets/transforms/resize.py b/sscma/datasets/transforms/resize.py new file mode 100644 index 00000000..b5234f73 --- /dev/null +++ b/sscma/datasets/transforms/resize.py @@ -0,0 +1,295 @@ +from typing import Tuple, Union + +import numpy as np + +import mmcv + +from mmdet.datasets.transforms import Resize as MMDET_Resize +from mmdet.structures.mask import PolygonMasks + +from sscma.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class YOLOv5KeepRatioResize(MMDET_Resize): + """Resize images & bbox(if existed). + + Copyright (c) OpenMMLab. + + This transform resizes the input image according to ``scale``. + Bboxes (if existed) are then resized with the same scale factor. + + Required Keys: + + - img (np.uint8) + - gt_bboxes (BaseBoxes[torch.float32]) (optional) + + Modified Keys: + + - img (np.uint8) + - img_shape (tuple) + - gt_bboxes (optional) + - scale (float) + + Added Keys: + + - scale_factor (np.float32) + + Args: + scale (Union[int, Tuple[int, int]]): Images scales for resizing. + """ + + def __init__(self, scale: Union[int, Tuple[int, int]], keep_ratio: bool = True, **kwargs): + assert keep_ratio is True + super().__init__(scale=scale, keep_ratio=True, **kwargs) + + @staticmethod + def _get_rescale_ratio(old_size: Tuple[int, int], scale: Union[float, Tuple[int]]) -> float: + """Calculate the ratio for rescaling. + + Args: + old_size (tuple[int]): The old size (w, h) of image. + scale (float | tuple[int]): The scaling factor or maximum size. + If it is a float number, then the image will be rescaled by + this factor, else if it is a tuple of 2 integers, then + the image will be rescaled as large as possible within + the scale. + + Returns: + float: The resize ratio. + """ + w, h = old_size + if isinstance(scale, (float, int)): + if scale <= 0: + raise ValueError(f'Invalid scale {scale}, must be positive.') + scale_factor = scale + elif isinstance(scale, tuple): + max_long_edge = max(scale) + max_short_edge = min(scale) + scale_factor = min(max_long_edge / max(h, w), max_short_edge / min(h, w)) + else: + raise TypeError('Scale must be a number or tuple of int, ' f'but got {type(scale)}') + + return scale_factor + + def _resize_img(self, results: dict): + """Resize images with ``results['scale']``.""" + assert self.keep_ratio is True + + if results.get('img', None) is not None: + image = results['img'] + original_h, original_w = image.shape[:2] + ratio = self._get_rescale_ratio((original_h, original_w), self.scale) + + if ratio != 1: + # resize image according to the shape + # NOTE: We are currently testing on COCO that modifying + # this code will not affect the results. + # If you find that it has an effect on your results, + # please feel free to contact us. + image = mmcv.imresize( + img=image, + size=(int(original_w * ratio), int(original_h * ratio)), + interpolation='area' if ratio < 1 else 'bilinear', + backend=self.backend, + ) + + resized_h, resized_w = image.shape[:2] + scale_ratio_h = resized_h / original_h + scale_ratio_w = resized_w / original_w + scale_factor = (scale_ratio_w, scale_ratio_h) + + results['img'] = image + results['img_shape'] = image.shape[:2] + results['scale_factor'] = scale_factor + + +@TRANSFORMS.register_module() +class LetterResize(MMDET_Resize): + """Resize and pad image while meeting stride-multiple constraints. + + Copyright (c) OpenMMLab. + + Required Keys: + + - img (np.uint8) + - batch_shape (np.int64) (optional) + + Modified Keys: + + - img (np.uint8) + - img_shape (tuple) + - gt_bboxes (optional) + + Added Keys: + - pad_param (np.float32) + + Args: + scale (Union[int, Tuple[int, int]]): Images scales for resizing. + pad_val (dict): Padding value. Defaults to dict(img=0, seg=255). + use_mini_pad (bool): Whether using minimum rectangle padding. + Defaults to True + stretch_only (bool): Whether stretch to the specified size directly. + Defaults to False + allow_scale_up (bool): Allow scale up when ratio > 1. Defaults to True + half_pad_param (bool): If set to True, left and right pad_param will + be given by dividing padding_h by 2. If set to False, pad_param is + in int format. We recommend setting this to False for object + detection tasks, and True for instance segmentation tasks. + Default to False. + """ + + def __init__( + self, + scale: Union[int, Tuple[int, int]], + pad_val: dict = dict(img=0, mask=0, seg=255), + use_mini_pad: bool = False, + stretch_only: bool = False, + allow_scale_up: bool = True, + half_pad_param: bool = False, + **kwargs, + ): + super().__init__(scale=scale, keep_ratio=True, **kwargs) + + self.pad_val = pad_val + if isinstance(pad_val, (int, float)): + pad_val = dict(img=pad_val, seg=255) + assert isinstance(pad_val, dict), f'pad_val must be dict, but got {type(pad_val)}' + + self.use_mini_pad = use_mini_pad + self.stretch_only = stretch_only + self.allow_scale_up = allow_scale_up + self.half_pad_param = half_pad_param + + def _resize_img(self, results: dict): + """Resize images with ``results['scale']``.""" + image = results.get('img', None) + if image is None: + return + + # Use batch_shape if a batch_shape policy is configured + if 'batch_shape' in results: + scale = tuple(results['batch_shape']) # hw + else: + scale = self.scale[::-1] # wh -> hw + + image_shape = image.shape[:2] # height, width + + # Scale ratio (new / old) + ratio = min(scale[0] / image_shape[0], scale[1] / image_shape[1]) + + # only scale down, do not scale up (for better test mAP) + if not self.allow_scale_up: + ratio = min(ratio, 1.0) + + ratio = [ratio, ratio] # float -> (float, float) for (height, width) + + # compute the best size of the image + no_pad_shape = (int(round(image_shape[0] * ratio[0])), int(round(image_shape[1] * ratio[1]))) + + # padding height & width + padding_h, padding_w = [scale[0] - no_pad_shape[0], scale[1] - no_pad_shape[1]] + if self.use_mini_pad: + # minimum rectangle padding + padding_w, padding_h = np.mod(padding_w, 32), np.mod(padding_h, 32) + + elif self.stretch_only: + # stretch to the specified size directly + padding_h, padding_w = 0.0, 0.0 + no_pad_shape = (scale[0], scale[1]) + ratio = [scale[0] / image_shape[0], scale[1] / image_shape[1]] # height, width ratios + + if image_shape != no_pad_shape: + # compare with no resize and padding size + image = mmcv.imresize( + image, (no_pad_shape[1], no_pad_shape[0]), interpolation=self.interpolation, backend=self.backend + ) + + scale_factor = (no_pad_shape[1] / image_shape[1], no_pad_shape[0] / image_shape[0]) + + if 'scale_factor' in results: + results['scale_factor_origin'] = results['scale_factor'] + results['scale_factor'] = scale_factor + + # padding + top_padding, left_padding = int(round(padding_h // 2 - 0.1)), int(round(padding_w // 2 - 0.1)) + bottom_padding = padding_h - top_padding + right_padding = padding_w - left_padding + + padding_list = [top_padding, bottom_padding, left_padding, right_padding] + if top_padding != 0 or bottom_padding != 0 or left_padding != 0 or right_padding != 0: + + pad_val = self.pad_val.get('img', 0) + if isinstance(pad_val, int) and image.ndim == 3: + pad_val = tuple(pad_val for _ in range(image.shape[2])) + + image = mmcv.impad( + img=image, + padding=(padding_list[2], padding_list[0], padding_list[3], padding_list[1]), + pad_val=pad_val, + padding_mode='constant', + ) + + results['img'] = image + results['img_shape'] = image.shape + if 'pad_param' in results: + results['pad_param_origin'] = results['pad_param'] * np.repeat(ratio, 2) + + if self.half_pad_param: + results['pad_param'] = np.array( + [padding_h / 2, padding_h / 2, padding_w / 2, padding_w / 2], dtype=np.float32 + ) + else: + # We found in object detection, using padding list with + # int type can get higher mAP. + results['pad_param'] = np.array(padding_list, dtype=np.float32) + + def _resize_masks(self, results: dict): + """Resize masks with ``results['scale']``""" + if results.get('gt_masks', None) is None: + return + + gt_masks = results['gt_masks'] + assert isinstance(gt_masks, PolygonMasks), f'Only supports PolygonMasks, but got {type(gt_masks)}' + + # resize the gt_masks + gt_mask_h = results['gt_masks'].height * results['scale_factor'][1] + gt_mask_w = results['gt_masks'].width * results['scale_factor'][0] + gt_masks = results['gt_masks'].resize((int(round(gt_mask_h)), int(round(gt_mask_w)))) + + top_padding, _, left_padding, _ = results['pad_param'] + if int(left_padding) != 0: + gt_masks = gt_masks.translate( + out_shape=results['img_shape'][:2], offset=int(left_padding), direction='horizontal' + ) + if int(top_padding) != 0: + gt_masks = gt_masks.translate( + out_shape=results['img_shape'][:2], offset=int(top_padding), direction='vertical' + ) + results['gt_masks'] = gt_masks + + def _resize_bboxes(self, results: dict): + """Resize bounding boxes with ``results['scale_factor']``.""" + if results.get('gt_bboxes', None) is None: + return + results['gt_bboxes'].rescale_(results['scale_factor']) + + if len(results['pad_param']) != 4: + return + results['gt_bboxes'].translate_((results['pad_param'][2], results['pad_param'][0])) + + if self.clip_object_border: + results['gt_bboxes'].clip_(results['img_shape']) + + def transform(self, results: dict) -> dict: + results = super().transform(results) + if 'scale_factor_origin' in results: + scale_factor_origin = results.pop('scale_factor_origin') + results['scale_factor'] = ( + results['scale_factor'][0] * scale_factor_origin[0], + results['scale_factor'][1] * scale_factor_origin[1], + ) + if 'pad_param_origin' in results: + pad_param_origin = results.pop('pad_param_origin') + results['pad_param'] += pad_param_origin + return results diff --git a/sscma/engine/hooks/visualization_hook.py b/sscma/engine/hooks/visualization_hook.py index 0a44ed50..04e8a588 100644 --- a/sscma/engine/hooks/visualization_hook.py +++ b/sscma/engine/hooks/visualization_hook.py @@ -1,4 +1,5 @@ # Copyright (c) Seeed Technology Co.,Ltd. All rights reserved. + import math import os import os.path as osp @@ -8,6 +9,7 @@ import mmcv import mmengine import mmengine.fileio as fileio +from mmcls.structures import ClsDataSample from mmdet.engine.hooks import DetVisualizationHook from mmengine.fileio import join_path from mmengine.hooks import Hook @@ -163,7 +165,8 @@ class SensorVisualizationHook(Hook): out_dir (str, optional): directory where painted images will be saved in the testing process. If None, handle with the backends of the visualizer. Defaults to None. - **kwargs: other keyword arguments + **kwargs: other keyword arguments of + :meth:`mmcls.visualization.ClsVisualizer.add_data_setample`. """ def __init__(self, enable=False, interval: int = 5000, show: bool = False, out_dir: Optional[str] = None, **kwargs): diff --git a/sscma/utils/inference.py b/sscma/utils/inference.py index 0fccc969..d5bef4c0 100644 --- a/sscma/utils/inference.py +++ b/sscma/utils/inference.py @@ -166,6 +166,7 @@ def __call__( if input_int8: scale, zero_point = input_['quantization'] data = (data / scale + zero_point).astype(np.int8) + self.inter.set_tensor(input_['index'], data) self.inter.invoke() for output in outputs: @@ -343,12 +344,7 @@ def test(self) -> None: data = self.data_preprocess(data, False) inputs = data['inputs'][0] if self.cfg.input_type == 'image': - data_samples = data['data_samples'] - img_path = ( - data_samples.get('img_path', None) - if isinstance(data_samples, dict) - else data_samples[0].get('img_path', None) - ) + img_path = data['data_samples'][0].get('img_path', None) img = data['inputs'][0].permute(1, 2, 0).cpu().numpy() else: img = data