|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +# This tool is used to update model-index.yml which is required by MIM, and |
| 4 | +# will be automatically called as a pre-commit hook. The updating will be |
| 5 | +# triggered if any change of model information (.md files in configs/) has been |
| 6 | +# detected before a commit. |
| 7 | + |
| 8 | +import glob |
| 9 | +import os |
| 10 | +import os.path as osp |
| 11 | +import sys |
| 12 | + |
| 13 | +import mmcv |
| 14 | + |
| 15 | +MMSEG_ROOT = osp.dirname(osp.dirname((osp.dirname(__file__)))) |
| 16 | + |
| 17 | + |
| 18 | +def dump_yaml_and_check_difference(obj, filename): |
| 19 | + """Dump object to a yaml file, and check if the file content is different |
| 20 | + from the original. |
| 21 | +
|
| 22 | + Args: |
| 23 | + obj (any): The python object to be dumped. |
| 24 | + filename (str): YAML filename to dump the object to. |
| 25 | + Returns: |
| 26 | + Bool: If the target YAML file is different from the original. |
| 27 | + """ |
| 28 | + original = None |
| 29 | + if osp.isfile(filename): |
| 30 | + with open(filename, 'r', encoding='utf-8') as f: |
| 31 | + original = f.read() |
| 32 | + with open(filename, 'w', encoding='utf-8') as f: |
| 33 | + mmcv.dump(obj, f, file_format='yaml', sort_keys=False) |
| 34 | + is_different = True |
| 35 | + if original is not None: |
| 36 | + with open(filename, 'r') as f: |
| 37 | + new = f.read() |
| 38 | + is_different = (original != new) |
| 39 | + return is_different |
| 40 | + |
| 41 | + |
| 42 | +def parse_md(md_file): |
| 43 | + """Parse .md file and convert it to a .yml file which can be used for MIM. |
| 44 | +
|
| 45 | + Args: |
| 46 | + md_file (str): Path to .md file. |
| 47 | + Returns: |
| 48 | + Bool: If the target YAML file is different from the original. |
| 49 | + """ |
| 50 | + collection_name = osp.dirname(md_file).split('/')[-1] |
| 51 | + configs = os.listdir(osp.dirname(md_file)) |
| 52 | + |
| 53 | + collection = dict(Name=collection_name, Metadata={'Training Data': []}) |
| 54 | + models = [] |
| 55 | + datasets = [] |
| 56 | + |
| 57 | + with open(md_file, 'r') as md: |
| 58 | + lines = md.readlines() |
| 59 | + i = 0 |
| 60 | + current_dataset = '' |
| 61 | + while i < len(lines): |
| 62 | + line = lines[i].strip() |
| 63 | + if len(line) == 0: |
| 64 | + i += 1 |
| 65 | + continue |
| 66 | + if line[:3] == '###': |
| 67 | + datasets.append(line[4:]) |
| 68 | + current_dataset = line[4:] |
| 69 | + i += 2 |
| 70 | + elif line[0] == '|' and ( |
| 71 | + i + 1) < len(lines) and lines[i + 1][:3] == '| -': |
| 72 | + cols = [col.strip() for col in line.split('|')] |
| 73 | + backbone_id = cols.index('Backbone') |
| 74 | + crop_size_id = cols.index('Crop Size') |
| 75 | + lr_schd_id = cols.index('Lr schd') |
| 76 | + mem_id = cols.index('Mem (GB)') |
| 77 | + fps_id = cols.index('Inf time (fps)') |
| 78 | + try: |
| 79 | + ss_id = cols.index('mIoU') |
| 80 | + except ValueError: |
| 81 | + ss_id = cols.index('Dice') |
| 82 | + try: |
| 83 | + ms_id = cols.index('mIoU(ms+flip)') |
| 84 | + except ValueError: |
| 85 | + ms_id = False |
| 86 | + config_id = cols.index('config') |
| 87 | + download_id = cols.index('download') |
| 88 | + j = i + 2 |
| 89 | + while j < len(lines) and lines[j][0] == '|': |
| 90 | + els = [el.strip() for el in lines[j].split('|')] |
| 91 | + config = '' |
| 92 | + model_name = '' |
| 93 | + weight = '' |
| 94 | + for fn in configs: |
| 95 | + if fn in els[config_id]: |
| 96 | + left = els[download_id].index( |
| 97 | + 'https://download.openmmlab.com') |
| 98 | + right = els[download_id].index('.pth') + 4 |
| 99 | + weight = els[download_id][left:right] |
| 100 | + config = f'configs/{collection_name}/{fn}' |
| 101 | + model_name = fn[:-3] |
| 102 | + fps = els[fps_id] if els[fps_id] != '-' and els[ |
| 103 | + fps_id] != '' else -1 |
| 104 | + mem = els[mem_id] if els[mem_id] != '-' and els[ |
| 105 | + mem_id] != '' else -1 |
| 106 | + crop_size = els[crop_size_id].split('x') |
| 107 | + assert len(crop_size) == 2 |
| 108 | + model = { |
| 109 | + 'Name': model_name, |
| 110 | + 'In Collection': collection_name, |
| 111 | + 'Metadata': { |
| 112 | + 'backbone': els[backbone_id], |
| 113 | + 'crop size': f'({crop_size[0]},{crop_size[1]})', |
| 114 | + 'lr schd': int(els[lr_schd_id]), |
| 115 | + }, |
| 116 | + 'Results': { |
| 117 | + 'Task': 'Semantic Segmentation', |
| 118 | + 'Dataset': current_dataset, |
| 119 | + 'Metrics': { |
| 120 | + 'mIoU': float(els[ss_id]), |
| 121 | + }, |
| 122 | + }, |
| 123 | + 'Config': config, |
| 124 | + 'Weights': weight, |
| 125 | + } |
| 126 | + if fps != -1: |
| 127 | + try: |
| 128 | + fps = float(fps) |
| 129 | + except Exception: |
| 130 | + j += 1 |
| 131 | + continue |
| 132 | + model['Metadata']['inference time (ms/im)'] = [{ |
| 133 | + 'value': |
| 134 | + round(1000 / float(fps), 2), |
| 135 | + 'hardware': |
| 136 | + 'V100', |
| 137 | + 'backend': |
| 138 | + 'PyTorch', |
| 139 | + 'batch size': |
| 140 | + 1, |
| 141 | + 'mode': |
| 142 | + 'FP32', |
| 143 | + 'resolution': |
| 144 | + f'({crop_size[0]},{crop_size[1]})' |
| 145 | + }] |
| 146 | + if mem != -1: |
| 147 | + model['Metadata']['memory (GB)'] = float(mem) |
| 148 | + if ms_id and els[ms_id] != '-' and els[ms_id] != '': |
| 149 | + model['Results']['Metrics']['mIoU(ms+flip)'] = float( |
| 150 | + els[ms_id]) |
| 151 | + models.append(model) |
| 152 | + j += 1 |
| 153 | + i = j |
| 154 | + else: |
| 155 | + i += 1 |
| 156 | + collection['Metadata']['Training Data'] = datasets |
| 157 | + result = {'Collections': [collection], 'Models': models} |
| 158 | + yml_file = f'{md_file[:-9]}{collection_name}.yml' |
| 159 | + return dump_yaml_and_check_difference(result, yml_file) |
| 160 | + |
| 161 | + |
| 162 | +def update_model_index(): |
| 163 | + """Update model-index.yml according to model .md files. |
| 164 | +
|
| 165 | + Returns: |
| 166 | + Bool: If the updated model-index.yml is different from the original. |
| 167 | + """ |
| 168 | + configs_dir = osp.join(MMSEG_ROOT, 'configs') |
| 169 | + yml_files = glob.glob(osp.join(configs_dir, '**', '*.yml'), recursive=True) |
| 170 | + yml_files.sort() |
| 171 | + |
| 172 | + model_index = { |
| 173 | + 'Import': |
| 174 | + [osp.relpath(yml_file, MMSEG_ROOT) for yml_file in yml_files] |
| 175 | + } |
| 176 | + model_index_file = osp.join(MMSEG_ROOT, 'model-index.yml') |
| 177 | + is_different = dump_yaml_and_check_difference(model_index, |
| 178 | + model_index_file) |
| 179 | + |
| 180 | + return is_different |
| 181 | + |
| 182 | + |
| 183 | +if __name__ == '__main__': |
| 184 | + file_list = [fn for fn in sys.argv[1:] if osp.basename(fn) == 'README.md'] |
| 185 | + if not file_list: |
| 186 | + exit(0) |
| 187 | + file_modified = False |
| 188 | + for fn in file_list: |
| 189 | + file_modified |= parse_md(fn) |
| 190 | + |
| 191 | + file_modified |= update_model_index() |
| 192 | + |
| 193 | + exit(1 if file_modified else 0) |
0 commit comments