lhhj commited on
Commit
463b952
·
1 Parent(s): b7ca4bf

initial ppush

Browse files
Dockerfile ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvcr.io/nvidia/pytorch:23.03-py3
2
+ # FROM nvcr.io/nvidia/pytorch:24.02-py3
3
+
4
+ ARG HOME_PATH="/home"
5
+ WORKDIR ${HOME_PATH}
6
+
7
+ RUN pip3 install --upgrade pip wheel
8
+ RUN pip3 install azure-storage-blob azure-identity
9
+
10
+ # supervision
11
+ RUN git clone https://github.com/roboflow/supervision.git && \
12
+ cd supervision && \
13
+ grep -v "^opencv-python-headless" pyproject.toml > tmp.toml && \
14
+ mv tmp.toml pyproject.toml && \
15
+ pip3 install --no-cache -e .
16
+
17
+ # ultralytics
18
+ ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/
19
+ RUN git clone https://github.com/ultralytics/ultralytics && \
20
+ cd ultralytics && \
21
+ grep -v "opencv-python\|openvino-dev" pyproject.toml > tmp.toml && mv tmp.toml pyproject.toml && \
22
+ pip3 install "opencv-python-headless<4.7" "opencv-contrib-python<4.7" "opencv-contrib-python-headless<4.7" "albumentations<1.4.0" && \
23
+ pip3 install .
24
+
25
+ # download dataset
26
+ ARG CVAT_URL
27
+ ARG CVAT_ORG
28
+ ARG CVAT_TASKS_YAML
29
+ ARG TRAIN_HP_YAML
30
+ ARG PYPREPROCESS
31
+
32
+ COPY . .
33
+ # COPY AIEM/trainer /home/trainer
34
+ # COPY ${CVAT_TASKS_YAML} ${CVAT_TASKS_YAML}
35
+ # COPY ${TRAIN_HP_YAML} ${TRAIN_HP_YAML}
36
+
37
+ ENV APP_PYPREPROCESS=${PYPREPROCESS}
38
+ ENV APP_CVAT_TASKS_YAML=${CVAT_TASKS_YAML}
39
+ ENV APP_HOME=${HOME_PATH}
40
+ ENV APP_TRAIN_HP_YAML=${TRAIN_HP_YAML}
41
+
42
+ RUN cd AIEM/trainer && \
43
+ python3 utils/download_cvatdata.py \
44
+ "$CVAT_URL" \
45
+ "$CVAT_ORG"
46
+ RUN cd /data && \
47
+ rm -rf *.zip
48
+
49
+ ENTRYPOINT ["python3", "AIEM/trainer/train_yolov8.py"]
Dockerfile.x86.yolov8_trainer ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvcr.io/nvidia/pytorch:23.03-py3
2
+ # FROM nvcr.io/nvidia/pytorch:24.02-py3
3
+
4
+ ARG HOME_PATH="/home"
5
+ WORKDIR ${HOME_PATH}
6
+
7
+ RUN pip3 install --upgrade pip wheel
8
+ RUN pip3 install azure-storage-blob azure-identity
9
+
10
+ # supervision
11
+ RUN git clone https://github.com/roboflow/supervision.git && \
12
+ cd supervision && \
13
+ grep -v "^opencv-python-headless" pyproject.toml > tmp.toml && \
14
+ mv tmp.toml pyproject.toml && \
15
+ pip3 install --no-cache -e .
16
+
17
+ # ultralytics
18
+ ADD https://ultralytics.com/assets/Arial.ttf https://ultralytics.com/assets/Arial.Unicode.ttf /root/.config/Ultralytics/
19
+ RUN git clone https://github.com/ultralytics/ultralytics && \
20
+ cd ultralytics && \
21
+ grep -v "opencv-python\|openvino-dev" pyproject.toml > tmp.toml && mv tmp.toml pyproject.toml && \
22
+ pip3 install "opencv-python-headless<4.7" "opencv-contrib-python<4.7" "opencv-contrib-python-headless<4.7" "albumentations<1.4.0" && \
23
+ pip3 install .
24
+
25
+ # download dataset
26
+ ARG CVAT_URL
27
+ ARG CVAT_ORG
28
+ ARG CVAT_TASKS_YAML
29
+ ARG TRAIN_HP_YAML
30
+ ARG PYPREPROCESS
31
+
32
+ COPY . .
33
+ # COPY AIEM/trainer /home/trainer
34
+ # COPY ${CVAT_TASKS_YAML} ${CVAT_TASKS_YAML}
35
+ # COPY ${TRAIN_HP_YAML} ${TRAIN_HP_YAML}
36
+
37
+ ENV APP_PYPREPROCESS=${PYPREPROCESS}
38
+ ENV APP_CVAT_TASKS_YAML=${CVAT_TASKS_YAML}
39
+ ENV APP_HOME=${HOME_PATH}
40
+ ENV APP_TRAIN_HP_YAML=${TRAIN_HP_YAML}
41
+
42
+ RUN cd AIEM/trainer && \
43
+ python3 utils/download_cvatdata.py \
44
+ "$CVAT_URL" \
45
+ "$CVAT_ORG"
46
+ RUN cd /data && \
47
+ rm -rf *.zip
48
+
49
+ ENTRYPOINT ["python3", "AIEM/trainer/train_yolov8.py"]
README.md CHANGED
@@ -1,10 +1,34 @@
1
- ---
2
- title: AIEM
3
- emoji: 🐠
4
- colorFrom: blue
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AIEM
2
+
3
+ AI Edge Management
4
+
5
+ **TODO**: introduce segmentation env variable
6
+
7
+ AIEM repo can be seen as the core shared across all the projects that require an AI model to be trained or to run an inference server. It talks to the rest of the project-specific repos by means of, e.g., a GitHub Actions workflow. It contains Dockerfiles for different architectures and for different purposes. For example: training a YoloV8 model in an x86 architecture (*Dockerfile.x86.yolov8_trainer*).
8
+
9
+ ## Structure
10
+
11
+ The structure of the project:
12
+
13
+ ```bash
14
+ .
15
+ ├── docker
16
+ │ ├── Dockerfile.x86.yolov8_trainer
17
+ │ └── scripts
18
+ │ └── docker_build.sh
19
+ ├── README.md
20
+ ├── runner
21
+ │ └── README.md
22
+ └── trainer
23
+ ├── README.md
24
+ ├── train_yolov8.py
25
+ └── utils
26
+ ├── cvat_dataset.py
27
+ ├── download_cvatdata.py
28
+ ├── merge_cocos.py
29
+ ├── path_utils.py
30
+ ├── unzip_datasets.py
31
+ └── yolo_labels.py
32
+ ```
33
+
34
+ - **Download data** (*trainer/utils/download_cvatdata.py*). Main script to download the dataset into the docker container. It reads from project-specific YAML file with the tasks to download from CVAT, preprocess the data and get the workspace ready for the model be able to be trained.
docker/scripts/docker_build.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ CONTAINER=$1
4
+ DOCKERFILE=$2
5
+
6
+ shift
7
+ shift
8
+
9
+ echo "Building $CONTAINER container..."
10
+
11
+ docker build --network=host -t $CONTAINER -f $DOCKERFILE "$@" .
runner/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ To be designed. It has to do with project-wise running models.
trainer/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ This folder reads from a config file specific to a project. This repo must be sym-linked inside the project folder.
trainer/train_yolov8.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import yaml
4
+ import shutil
5
+ import datetime
6
+ import numpy as np
7
+ import pandas as pd
8
+ import yaml
9
+ from azure.storage.blob import BlobServiceClient
10
+ from pathlib import Path
11
+ from sklearn.model_selection import KFold
12
+ from collections import Counter
13
+ from ultralytics import YOLO
14
+ from utils.path_utils import *
15
+
16
+ STORAGE_ACCOUNT_KEY = "mhqTCNmdIgsnvyFnfv0r2JKfs8iG//5YVnphCq336XNxhyI72brMy6lP88I9XKVya/G9ZlAAMoNd+AStsXFe0Q=="
17
+ STORAGE_ACCOUNT_NAME = "camtagstoreaiem"
18
+ CONNECTION_STRING = "DefaultEndpointsProtocol=https;AccountName=camtagstoreaiem;AccountKey=mhqTCNmdIgsnvyFnfv0r2JKfs8iG//5YVnphCq336XNxhyI72brMy6lP88I9XKVya/G9ZlAAMoNd+AStsXFe0Q==;EndpointSuffix=core.windows.net"
19
+ CONTAINER_NAME = "upload"
20
+
21
+ # Get YAML file containing the training hyperparameters
22
+ HOME = os.getenv("APP_HOME")
23
+ APP_TRAIN_HP_YAML = os.path.join(HOME, os.getenv("APP_TRAIN_HP_YAML"))
24
+
25
+ def azure_upload(local_fname, blob_fname, overwrite=True):
26
+ blob_service_client = BlobServiceClient.from_connection_string(CONNECTION_STRING)
27
+ blob_client = blob_service_client.get_blob_client(
28
+ container = CONTAINER_NAME,
29
+ blob = blob_fname
30
+ )
31
+ with open(local_fname, "rb") as data:
32
+ blob_client.upload_blob(data, overwrite=overwrite)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ with open(APP_TRAIN_HP_YAML, "r") as f:
37
+ y = yaml.safe_load(f)
38
+ KSPLIT = y['ksplit']
39
+ EPOCHS = y['epochs']
40
+ MODEL = y['model']
41
+ DATA_PATH = y['data_path']
42
+ BATCH_SIZE = y['batch_size']
43
+
44
+ # coco
45
+ coco_dataset_path = Path(DATA_PATH)
46
+ coco_dict = read_coco_json(coco_dataset_path / "merged.json")
47
+
48
+ classes = {cat['id']-1: cat['name'] for cat in coco_dict['categories']}
49
+ cls_idx = sorted(classes.keys())
50
+
51
+ labels = sorted((coco_dataset_path / "labels").rglob("*.txt"))
52
+ indx = [l.stem for l in labels]
53
+ labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
54
+
55
+ for label in labels:
56
+ label_counter = Counter()
57
+ with open(label, 'r') as lf:
58
+ lines = lf.readlines()
59
+
60
+ for l in lines:
61
+ label_counter[int(l.split(' ')[0])] += 1
62
+ labels_df.loc[label.stem] = label_counter
63
+
64
+ labels_df = labels_df.fillna(0.0)
65
+
66
+ # KFOLD
67
+ kf = KFold(
68
+ n_splits = KSPLIT,
69
+ shuffle = True,
70
+ random_state = 42
71
+ )
72
+ kfolds = list(kf.split(labels_df))
73
+
74
+ folds = [f'split_{n}' for n in range(1, KSPLIT + 1)]
75
+ folds_df = pd.DataFrame(index=indx, columns=folds)
76
+ for idx, (train, val) in enumerate(kfolds, start=1):
77
+ folds_df[f'split_{idx}'].loc[labels_df.iloc[train].index] = 'train'
78
+ folds_df[f'split_{idx}'].loc[labels_df.iloc[val].index] = 'val'
79
+
80
+ # check distributions. balanced?
81
+ fold_lbl_distrb = pd.DataFrame(index=folds, columns=cls_idx)
82
+ for n, (train_indices, val_indices) in enumerate(kfolds, start=1):
83
+ train_totals = labels_df.iloc[train_indices].sum()
84
+ val_totals = labels_df.iloc[val_indices].sum()
85
+
86
+ ratio = val_totals / (train_totals + 1E-7)
87
+ fold_lbl_distrb.loc[f'split_{n}'] = ratio
88
+
89
+ # datasets for each fold
90
+ save_path = Path(coco_dataset_path / f'{datetime.date.today().isoformat()}_{KSPLIT}-Fold_Cross-val')
91
+ save_path.mkdir(parents=True, exist_ok=True)
92
+
93
+ suffix = sorted((coco_dataset_path / 'images').rglob("*.*"))[0].suffix
94
+ images = [coco_dataset_path / "images" / l.with_suffix(suffix).name for l in labels]
95
+ ds_yamls = []
96
+
97
+ for split in folds_df.columns:
98
+ # create directories
99
+ split_dir = save_path / split
100
+ split_dir.mkdir(parents=True, exist_ok=True)
101
+ (split_dir / 'train' / 'images').mkdir(parents=True, exist_ok=True)
102
+ (split_dir / 'train' / 'labels').mkdir(parents=True, exist_ok=True)
103
+ (split_dir / 'val' / 'images').mkdir(parents=True, exist_ok=True)
104
+ (split_dir / 'val' / 'labels').mkdir(parents=True, exist_ok=True)
105
+
106
+ # create yaml files
107
+ dataset_yaml = split_dir / f'{split}_dataset.yaml'
108
+ ds_yamls.append(dataset_yaml)
109
+
110
+ with open(dataset_yaml, 'w') as ds_y:
111
+ yaml.safe_dump({
112
+ 'path' : split_dir.resolve().as_posix(),
113
+ 'train': 'train',
114
+ 'val' : 'val',
115
+ 'names': classes
116
+ }, ds_y)
117
+
118
+ for image, label in zip(images, labels):
119
+ for split, k_split in folds_df.loc[image.stem].items():
120
+ # destination directory
121
+ img_to_path = save_path / split / k_split / 'images'
122
+ lbl_to_path = save_path / split / k_split / 'labels'
123
+
124
+ # copy image and label file to new directory
125
+ shutil.copy(image, img_to_path / image.name)
126
+ shutil.copy(label, lbl_to_path / label.name)
127
+
128
+ folds_df.to_csv(save_path / "kfold_datasplit.csv")
129
+ fold_lbl_distrb.to_csv(save_path / "kfold_label_distributions.csv")
130
+
131
+ model = YOLO(MODEL)
132
+
133
+ for k in range(KSPLIT):
134
+ dataset_yaml = ds_yamls[k]
135
+ model.train(
136
+ data = dataset_yaml,
137
+ epochs = EPOCHS,
138
+ batch = BATCH_SIZE,
139
+ plots = False
140
+ )
141
+
142
+ # azure upload
143
+ flag = '2' * (KSPLIT - 1)
144
+ local_fname = f'runs/detect/train{flag}/weights/best.pt'
145
+ blob_fname = f"kohberg/host_train_{MODEL}"
146
+ azure_upload(local_fname, blob_fname, overwrite=True)
trainer/utils/cvat_dataset.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import requests
4
+ import shutil
5
+ import time
6
+ from pathlib import Path
7
+ from tqdm.auto import tqdm
8
+
9
+ class CVATDataset:
10
+ def __init__(self, cvat_url, org, task_ids, headers=None, params=None, names=None, dest_folder=None):
11
+ """
12
+ Connects to serverless CVAT to download datasets.
13
+
14
+ Args:
15
+ cvat_url (str) : CVAT base URL where the server is loaded.
16
+ org (str) : organization we are working with, e.g.: 'bulow'
17
+ task_ids (list): list with the task IDs inside CVAT.
18
+ params (dict): query parameters.
19
+ names (dict): dict where the keys are the task id and values
20
+ the names of the local files.
21
+ dest_folder (str) : destination folder of the zip files.
22
+
23
+ Returns:
24
+ Content ZIP file containing JSON coco annotations and the images.
25
+ """
26
+ self.cvat_url = cvat_url
27
+ self.org = org
28
+ self.task_ids = task_ids
29
+ self.dest_folder = dest_folder
30
+ self.names_dict = names
31
+ if self.names_dict is not None:
32
+ assert all([id_ in self.names_dict.keys() for id_ in self.task_ids]), \
33
+ "The keys in names do not match the task IDs."
34
+
35
+ self.headers = headers
36
+ if self.headers is None:
37
+ # FIXME: avoid hardcoded authorization.
38
+ self.headers = {"Authorization": "Basic ZGphbmdvOlMwbHNraW4xMjM0IQ=="}
39
+
40
+ self.params = params
41
+ if self.params is None:
42
+ self.params = {
43
+ "format" : "COCO 1.0",
44
+ "action" : "download",
45
+ "location": "local",
46
+ "org" : self.org
47
+ }
48
+
49
+ @staticmethod
50
+ def countdown_clock(waiting_time):
51
+ t0 = time.monotonic()
52
+ while time.monotonic() - t0 < waiting_time:
53
+ remaining_time = waiting_time - (time.monotonic() - t0)
54
+ mins, secs = divmod(int(remaining_time), 60)
55
+ sys.stdout.write("\r")
56
+ sys.stdout.write(f"{mins:02d}:{secs:02d}")
57
+ sys.stdout.flush()
58
+ time.sleep(1)
59
+ sys.stdout.write("\n")
60
+
61
+ def _get_dataset(self, endpoint):
62
+ response = requests.get(
63
+ endpoint,
64
+ headers = self.headers,
65
+ params = self.params,
66
+ stream = True
67
+ )
68
+ return response
69
+
70
+ def _download_task(self, task_id: int, fname: str):
71
+ """ Downloads dataset linked to a task. """
72
+ endpoint = f"{self.cvat_url}/api/tasks/{task_id}/dataset"
73
+ r = self._get_dataset(endpoint)
74
+ while r.status_code != 200:
75
+ if r.status_code == 202:
76
+ print(f" Status code {r.status_code}: server processing request")
77
+ self.countdown_clock(10)
78
+ else:
79
+ print(f" Status code {r.status_code}: connection error")
80
+ self.countdown_clock(30)
81
+ r = self._get_dataset(endpoint)
82
+
83
+ print(f" Status code {r.status_code}: request is ready")
84
+ total_length = int(r.headers.get("Content-Length"))
85
+ with tqdm.wrapattr(r.raw, "read", total=total_length, desc="") as raw:
86
+ with open(fname, "wb") as file:
87
+ shutil.copyfileobj(raw, file)
88
+
89
+ def download_tasks(self):
90
+ """ Download all the tasks passed as input. """
91
+ for task_id in self.task_ids:
92
+ name_label = task_id
93
+ if self.names_dict is not None:
94
+ name_label = self.names_dict[task_id]
95
+ fname = f"dataset_{name_label}.zip"
96
+ if self.dest_folder is not None:
97
+ self.dest_folder = Path(self.dest_folder)
98
+ self.dest_folder.mkdir(exist_ok=True, parents=True)
99
+ fname = (self.dest_folder / fname).resolve().as_posix()
100
+
101
+ if os.path.exists(fname):
102
+ print(f"File {fname} already exists.")
103
+ continue
104
+
105
+ print(f"\nDownloading task {task_id}, with fname {fname}")
106
+ self._download_task(task_id, fname)
107
+
108
+ # TODO: implement unzip function for the tasks
trainer/utils/download_cvatdata.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This script reads from a YAML file and downloads data from CVAT.
3
+ """
4
+
5
+ import os
6
+ import argparse
7
+ import subprocess
8
+ import shutil
9
+ import yaml
10
+ from pathlib import Path
11
+ from cvat_dataset import CVATDataset
12
+ from merge_cocos import merge
13
+ from yolo_labels import get_yolo_labels
14
+
15
+ HOME = os.getenv("APP_HOME")
16
+ CVAT_TASKS = os.path.join(HOME, os.getenv("APP_CVAT_TASKS_YAML"))
17
+ PYPREPROCESS = os.getenv("APP_PYPREPROCESS")
18
+ import sys
19
+ sys.path.append(HOME)
20
+
21
+ if __name__ == "__main__":
22
+ parser = argparse.ArgumentParser()
23
+ parser.add_argument(
24
+ 'cvat_url',
25
+ type = str,
26
+ help = 'cvat url'
27
+ )
28
+ parser.add_argument(
29
+ 'cvat_org',
30
+ type = str,
31
+ help = 'cvat organization'
32
+ )
33
+ parser.add_argument(
34
+ '-odir', '--output_dir',
35
+ type = str,
36
+ help = "path to download directory",
37
+ default = "/data"
38
+ )
39
+ args = parser.parse_args()
40
+
41
+ with open(CVAT_TASKS, "r") as f:
42
+ y = yaml.safe_load(f)
43
+ TASK_IDS = y["task_ids"]
44
+ NAMES = None
45
+ if "names" in y:
46
+ NAMES = y["names"]
47
+
48
+ data_folder = Path(args.output_dir)
49
+ data_folder.mkdir(parents=True, exist_ok=True)
50
+
51
+ CVAT = CVATDataset(
52
+ args.cvat_url,
53
+ args.cvat_org,
54
+ TASK_IDS,
55
+ names = NAMES,
56
+ dest_folder = data_folder
57
+ )
58
+ CVAT.download_tasks()
59
+
60
+ paths2imgs = []
61
+ paths2json = []
62
+ paths2dirs = []
63
+ for dataset in data_folder.rglob("*.zip"):
64
+ dir_name = dataset.parent / dataset.stem
65
+ paths2dirs.append(dir_name)
66
+ paths2imgs.append(dir_name / "images")
67
+ paths2json.append(dir_name / "annotations" / "instances_default.json")
68
+ if dir_name.exists():
69
+ continue
70
+ subprocess.call(['unzip', '-o', dataset, '-d', dir_name])
71
+
72
+ if PYPREPROCESS == 'true':
73
+ # looks for the py script called: trainer_files/preprocess.py
74
+ # this script is characteristic to the project
75
+ from trainer_files.preprocess import preprocess_cvat
76
+ paths2json, paths2imgs = preprocess_cvat(paths2dirs)
77
+
78
+ # TODO: add debugging / assert script to make sure preprocess is done correctly
79
+
80
+ # merge everything into a single json file
81
+ if len(paths2json) > 1:
82
+ merge(
83
+ paths2json, paths2imgs, data_folder / 'merged_cocos', 'merged', verbose=True
84
+ )
85
+ else:
86
+ json_file = Path(paths2json[0])
87
+ shutil.copy(
88
+ json_file.as_posix(),
89
+ (json_file.parents[1] / 'merged.json').as_posix()
90
+ )
91
+ shutil.move(
92
+ json_file.parents[1].as_posix(),
93
+ (data_folder / 'merged_cocos').as_posix()
94
+ )
95
+
96
+ # yolo format - labels
97
+ path2json = data_folder / 'merged_cocos' / 'merged.json'
98
+ get_yolo_labels(path2json, use_segment=False)
trainer/utils/merge_cocos.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import glob
3
+ from pathlib import Path
4
+ from datetime import date
5
+ from collections import defaultdict
6
+ from warnings import warn
7
+
8
+ from path_utils import *
9
+
10
+ def merge_cats_get_id(cats, this_cat):
11
+ cat_nms = [c['name'] for c in cats]
12
+ if this_cat['name'] not in cat_nms:
13
+ this_cat['id'] = len(cats) + 1
14
+ cats.append(this_cat)
15
+ return this_cat["id"]
16
+ else:
17
+ return this_cat["id"]
18
+
19
+
20
+ def filter_images(images, annotations):
21
+ img_ids_from_anns = [ann['image_id'] for ann in annotations]
22
+ images_ = [
23
+ img_info for img_info in images if img_info['id'] in img_ids_from_anns
24
+ ]
25
+ return images_
26
+
27
+
28
+ def merge(jsons, img_roots, output_dir, output_nm="merged", verbose=True):
29
+ assert len(jsons) == len(img_roots)
30
+
31
+ out_dir_path = Path(output_dir)
32
+ out_imgs_dir_path = out_dir_path / "images"
33
+
34
+ merged_img_id_state = 1
35
+ merged_ann_id_state = 1
36
+ merged_names = []
37
+ merged_dict = {
38
+ "info" : {"description": "", "data_created": f"{date.today():%Y/%m/%d}"},
39
+ "annotations": [],
40
+ "categories" : [],
41
+ "images" : []
42
+ }
43
+ for i, (json_path, imgs_dir_path) in enumerate(zip(jsons, img_roots)):
44
+ coco_dict = read_coco_json(json_path)
45
+ dataset_name = get_setname(json_path)
46
+ merged_names.append(dataset_name)
47
+
48
+ # categories
49
+ cat_id_old2new = {}
50
+ for cat in coco_dict['categories']:
51
+ old_cat_id = cat['id']
52
+ new_cat_id = merge_cats_get_id(merged_dict['categories'], cat)
53
+ cat_id_old2new[old_cat_id] = new_cat_id
54
+
55
+ # images
56
+ coco_dict['images'] = filter_images(
57
+ coco_dict['images'], coco_dict['annotations']
58
+ )
59
+ img_id_old2new = {}
60
+ for img in coco_dict['images']:
61
+ img_id_old2new[img["id"]] = merged_img_id_state
62
+ img["id"] = merged_img_id_state
63
+
64
+ old_img_path = Path(imgs_dir_path) / img['file_name']
65
+ img['file_name'] = dataset_name + "_" + img['file_name']
66
+ new_img_path = out_imgs_dir_path / img['file_name']
67
+ assure_copy(old_img_path, new_img_path)
68
+
69
+ merged_img_id_state += 1
70
+ merged_dict['images'].append(img)
71
+
72
+ # annotations
73
+ for ann in coco_dict['annotations']:
74
+ ann['id'] = merged_ann_id_state
75
+ ann['image_id'] = img_id_old2new[ann['image_id']]
76
+ ann['category_id'] = cat_id_old2new[ann['category_id']]
77
+
78
+ merged_ann_id_state += 1
79
+ merged_dict['annotations'].append(ann)
80
+
81
+ merged_dict["info"]["description"] = "+".join(merged_names)
82
+
83
+ out_json = out_dir_path / f"{output_nm}.json"
84
+ write_json(out_json, merged_dict)
85
+
86
+ if verbose:
87
+ print(f"Number of images: {len(merged_dict['images'])}")
88
+ print(f"Number of annotations: {len(merged_dict['annotations'])}")
89
+
90
+
91
+ if __name__ == '__main__':
92
+ paths2images = []
93
+ paths2json = []
94
+ for dataset in glob.glob("dataset_*"):
95
+ paths2images.append(os.path.join(dataset, "images"))
96
+ paths2json.append(os.path.join(dataset, "annotations/instances_default.json"))
97
+
98
+ merge(paths2json, paths2images, './merged_cocos', 'merged', verbose=True)
trainer/utils/path_utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import filecmp
3
+ from pathlib import Path
4
+ from shutil import copy
5
+
6
+
7
+ def read_json(json_path):
8
+ with open(json_path, "r") as f:
9
+ d = json.load(f)
10
+ return d
11
+
12
+
13
+ def write_json(json_path, dic):
14
+ with open(json_path, "w") as f:
15
+ json.dump(dic, f)
16
+ print(f"Wrote json to {json_path}")
17
+
18
+
19
+ def get_setname(json_path):
20
+ json_path_ = Path(json_path)
21
+ dataset_nm = json_path_.parent.parts[-2]
22
+ print(f"Processing {dataset_nm} (name derived from json path)")
23
+ return dataset_nm
24
+
25
+
26
+ def read_coco_json(coco_json):
27
+ coco_dict = read_json(coco_json)
28
+ return coco_dict
29
+
30
+
31
+ def assure_copy(src, dst):
32
+ assert Path(src).is_file()
33
+ if Path(dst).is_file() and filecmp.cmp(src, dst, shallow=True):
34
+ return
35
+ Path(dst).parent.mkdir(exist_ok=True, parents=True)
36
+ copy(src, dst)
37
+
38
+
39
+ def path(str_path, is_dir=False, mkdir=False):
40
+ path_ = Path(str_path)
41
+ if is_dir:
42
+ if mkdir:
43
+ path_.mkdir(parents=True, exist_ok=True)
44
+ assert path_.is_dir(), path_
45
+ else:
46
+ assert path_.is_file(), path_
47
+ return path_
48
+
trainer/utils/unzip_datasets.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import glob
3
+
4
+ if __name__ == "__main__":
5
+ for dataset in glob.glob("*.zip"):
6
+ dir_name = dataset.split(".")[0]
7
+ subprocess.call(['unzip', '-o', dataset, '-d', dir_name])
trainer/utils/yolo_labels.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import json
3
+ from pathlib import Path, PosixPath
4
+ from pycocotools.coco import COCO
5
+
6
+ def min_index(arr1, arr2):
7
+ """
8
+ Find a pair of indexes with the shortest distance.
9
+
10
+ Args:
11
+ arr1: (N, 2).
12
+ arr2: (M, 2).
13
+ Return:
14
+ a pair of indexes (tuple)
15
+ """
16
+ dis = ((arr1[:, None, :] - arr2[None, :, :]) ** 2).sum(-1)
17
+ return np.unravel_index(np.argmin(dis, axis=None), dis.shape)
18
+
19
+
20
+ def merge_multi_segment(segments):
21
+ """
22
+ Merge multi segments to one list.
23
+ Find coordinates with min distance between each segment,
24
+ then connect these coordinates with one thin line to merge all
25
+ segments into one.
26
+
27
+ Args:
28
+ segments (List(List)): original segmentations in coco's json file
29
+ like [segmentation1, segmentation2, ...], where
30
+ each segmentation is a list of coordinates
31
+ """
32
+ s = []
33
+ segments = [np.array(i).reshape(-1,2) for i in segments]
34
+ idx_list = [[] for _ in range(len(segments))]
35
+
36
+ # record the indexes with the min distance between each segment
37
+ for i in range(1, len(segments)):
38
+ idx1, idx2 = min_index(segments[i - 1, segments[i]])
39
+ idx_list[i - 1].append(idx1)
40
+ idx_list[i].append(idx2)
41
+
42
+ # use two round to connect all the segments
43
+ for k in range(2):
44
+ # forward connection
45
+ if k == 0:
46
+ for i, idx in enumerate(idx_list):
47
+ # middle segments have two indexes
48
+ # reverse the index of middle segments
49
+ if len(idx) == 2 and idx[0] > idx[1]:
50
+ idx = idx[::-1]
51
+ segments[i] = segments[i][::-1, :]
52
+
53
+ segments[i] = np.roll(segments[i], -idx[0], axis=0)
54
+ segments[i] = np.concatenate([segments[i], segments[i][:1]])
55
+
56
+ # deal with the first segment and the last one
57
+ if i in [0, len(idx_list) - 1]:
58
+ s.append(segments[i])
59
+ else:
60
+ idx = [0, idx[1] - idx[0]]
61
+ s.append(segments[i][idx[0]:idx[1] + 1])
62
+
63
+ else:
64
+ for i in range(len(idx_list) - 1, -1, -1):
65
+ if i not in [0, len(idx_list) - 1]:
66
+ idx = idx_list[i]
67
+ nidx = abs(idx[1] - idx[0])
68
+ s.append(segments[i][nidx:])
69
+ return s
70
+
71
+
72
+ def get_yolo_labels(path2json, use_segment=False):
73
+ if not isinstance(path2json, PosixPath):
74
+ path2json = Path(path2json)
75
+ path2labels = path2json.parents[0] / "labels"
76
+ path2labels.mkdir(parents=True, exist_ok=True)
77
+
78
+ coco = COCO(path2json)
79
+
80
+ img2anns = {}
81
+ for ann in coco.dataset['annotations']:
82
+ img_id = ann['image_id']
83
+ if img_id not in img2anns:
84
+ img2anns[img_id] = [ann]
85
+ else:
86
+ img2anns[img_id].append(ann)
87
+
88
+ id2img = {img["id"]: img for img in coco.dataset["images"]}
89
+
90
+ for img_id, anns in img2anns.items():
91
+ img = id2img[img_id]
92
+ h, w, f = img['height'], img['width'], img['file_name']
93
+
94
+ bboxes = []
95
+ segments = []
96
+ for ann in anns:
97
+ if ann['iscrowd']:
98
+ continue
99
+ # coco box format: [top left x, top left y, width, height]
100
+ box = np.array(ann['bbox'], dtype=np.float64)
101
+ box[:2] += box[2:] / 2 # center coordinates
102
+ box[[0, 2]] /= w # normalize x
103
+ box[[1, 3]] /= h # normalize y
104
+ if box[2] <= 0 or box[3] <= 0:
105
+ continue
106
+
107
+ cls = ann['category_id'] - 1
108
+ box = [cls] + box.tolist()
109
+ if box not in bboxes:
110
+ bboxes.append(box)
111
+
112
+ # segmentation?
113
+ if use_segment:
114
+ if len(ann['segmentation']) > 1:
115
+ s = merge_multi_segment(ann['segmentation'])
116
+ s = (np.concatenate(s, axis=0) / np.array([w, h])).reshape(-1).tolist()
117
+ else:
118
+ s = [j for i in ann['segmentation'] for j in i] # all segments concatenated
119
+ s = (np.array(s).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist()
120
+ s = [cls] + s
121
+ if s not in segments:
122
+ segments.append(s)
123
+
124
+ # write
125
+ with open((path2labels / f).with_suffix('.txt'), 'a') as file:
126
+ for i in range(len(bboxes)):
127
+ line = *(segments[i] if use_segment else bboxes[i]),
128
+ file.write(('%g ' * len(line)).rstrip() % line + '\n')