示例#1
0
def distribute_package(root, version=None, repository=None, *, upload=True):
    """ 发布包的工具函数

    :param root: 项目的根目录,例如 'D:/slns/pyxllib'
        根目录下有对应的 setup.py 等文件
    :param repository: 比如我配置了 [xlpr],就可以传入 'xlpr'
    """
    from pyxllib.file.specialist import File, Dir

    # 1 切换工作目录
    os.chdir(str(root))

    # 2 改版本号
    if version:
        f = File('setup.py', root)
        s = re.sub(r"(version\s*=\s*)(['\"])(.+?)(\2)", fr'\1\g<2>{version}\4', f.read())
        f.write(s)

    # 3 打包
    subprocess.run('python setup.py sdist')

    # 4 上传
    if upload:
        # 上传
        cmd = 'twine upload dist/*'
        if repository:
            cmd += f' -r {repository}'
        subprocess.run(cmd)
        # 删除打包生成的中间文件
        Dir('dist').delete()
        Dir('build').delete()
示例#2
0
    def to_images(self,
                  dst_dir=None,
                  file_fmt='{filestem}_{number}.png',
                  num_width=None,
                  *,
                  scale=1,
                  start=1,
                  fmt_onepage=False):
        """ 将pdf转为若干页图片

        :param dst_dir: 目标目录
            默认情况下,只有一页pdf则存储到对应的pdf目录,多页则存储到同名子目录下
            如果不想这样被智能控制,只要指定明确的dst即可
        :param file_fmt: 后缀格式,包括修改导出的图片类型,注意要用 {} 占位符表示页码编号
        :param num_width: 生成的每一页文件编号,使用的数字前导0域宽
            默认根据pdf总页数来设置对应所用域宽
            0表示不设域宽
        :param scale: 对每页图片进行缩放,一般推荐都要设成2,导出的图片才清晰
        :param start: 起始页码,一般建议从1开始比较符合常识直觉
        :param fmt_onepage: 当pdf就只有一页的时候,是否还对导出的图片编号
            默认只有一页的时候,进行优化,不增设后缀格式
        :return: 返回转换完的图片名称清单

        注:如果要导出单张图,可以用 FitzPdfPage.get_cv_image
        """
        # 1 基本参数计算
        srcfile, doc = self.src_file, self.doc
        filestem, n_page = srcfile.stem, doc.page_count

        # 自动推导目标目录
        if dst_dir is None:
            dst_dir = Dir(srcfile.stem, srcfile.parent) if n_page > 1 else Dir(
                srcfile.parent)
        Dir(dst_dir).ensure_dir()

        # 域宽
        num_width = num_width or get_number_width(n_page)  # 根据总页数计算需要的对齐域宽

        # 2 导出图片
        if fmt_onepage or n_page != 1:  # 多页的处理规则
            res = []
            for i in range(n_page):
                im = self.load_page(i).get_cv_image(scale)
                number = ('{:0' + str(num_width) + 'd}').format(
                    i + start)  # 前面的括号不要删,这样才是完整的一个字符串来使用format
                f = xlcv.write(
                    im,
                    File(file_fmt.format(filestem=filestem, number=number),
                         dst_dir))
                res.append(f)
            return res
        else:
            im = self.load_page(0).get_cv_image(scale)
            return [
                xlcv.write(
                    im,
                    File(srcfile.stem + os.path.splitext(file_fmt)[1],
                         dst_dir))
            ]
示例#3
0
    def to_labelme_gt(self,
                      imdir,
                      dst_dir=None,
                      *,
                      segmentation=False,
                      max_workers=4):
        """ 在图片目录里生成图片的可视化json配置文件

        :param segmentation: 是否显示分割效果
        """
        def func(g):
            # 1 获得图片id和文件
            image_id, df = g
            imfile = File(df.iloc[0]['file_name'], imdir)
            if not imfile:
                return  # 如果没有图片不处理

            # 2 生成这张图片对应的json标注
            if dst_dir:
                imfile = imfile.copy(dst_dir, if_exists='skip')
            lm = Coco2Labelme(imfile)
            height, width = lm.img.size(
            )  # 也可以用image['height'], image['width']获取
            # 注意df取出来的image_id默认是int64类型,要转成int,否则json会保存不了int64类型
            lm.add_shape('', [0, 0, 10, 0],
                         shape_type='line',
                         shape_color=[0, 0, 0],
                         n_gt_box=len(df),
                         image_id=int(image_id),
                         size=f'{height}x{width}')
            lm.anns_gt(df, segmentation=segmentation)
            lm.write()  # 保存json文件到img对应目录下

        if dst_dir:
            dst_dir = Dir(dst_dir)
            dst_dir.ensure_dir()
        gt_anns = self.gt_anns.copy()
        # 为了方便labelme操作,需要扩展几列内容
        gt_anns['file_name'] = [
            self.images.loc[x, 'file_name'] for x in gt_anns['image_id']
        ]
        gt_anns['gt_category_name'] = [
            self.categories.loc[x, 'name'] for x in gt_anns['gt_category_id']
        ]
        gt_anns['gt_supercategory'] = [
            self.categories.loc[x, 'supercategory']
            for x in gt_anns['gt_category_id']
        ]
        mtqdm(func,
              list(gt_anns.groupby('image_id').__iter__()),
              'create labelme gt jsons',
              max_workers=max_workers)
示例#4
0
    def main_normal(cls, imdir, labeldir=None, label_file_suffix='.txt'):
        """ 封装更高层的接口,输入目录,直接标注目录下所有图片

        :param imdir: 图片路径
        :param labeldir: 标注数据路径,默认跟imdir同目录
        :return:
        """
        ims = Dir(imdir).select_files(['*.jpg', '*.png'])
        if not labeldir: labeldir = imdir
        txts = [File(f.stem, labeldir, suffix=label_file_suffix) for f in ims]
        cls.main_pair(ims, txts)
示例#5
0
    def __init__(self, root):
        """
        data:dict
            key: 相对路径/图片stem (这一节称为loc)
                label名称:对应属性dict1 (跟loc拼在一起,称为loclabel)
                label2:dict2
        """
        self.root = Dir(root)
        self.data = defaultdict(dict)
        self.imfiles = {}

        for file in self.root.select_files('**/*.json'):
            lmdict = file.read()
            imfile = file.with_name(lmdict['imagePath'])
            img = xlcv.read(imfile)
            loc = file.relpath(self.root).replace('\\', '/')[:-5]
            self.imfiles[loc] = imfile

            for shape in lmdict['shapes']:
                attrs = self.parse_shape(shape, img)
                self.data[loc][attrs['label']] = attrs
示例#6
0
def browser_jsons_kv(fd,
                     files='**/*.json',
                     encoding=None,
                     max_items=10,
                     max_value_length=100):
    """ demo_keyvaluescounter,查看目录下json数据的键值对信息

    :param fd: 目录
    :param files: 匹配的文件格式
    :param encoding: 文件编码
    :param max_items: 项目显示上限,有些数据项目太多了,要精简下
            设为假值则不设上限
    :param max_value_length: 添加的值,进行截断,防止有些值太长
    :return:
    """
    kvc = KeyValuesCounter()
    d = Dir(fd)
    for p in d.select_files(files):
        # print(p)
        data = p.read(encoding=encoding, mode='.json')
        kvc.add(data, max_value_length=max_value_length)
    p = File(r'demo_keyvaluescounter.html', Dir.TEMP)
    p.write(kvc.to_html_table(max_items=max_items), if_exists='replace')
    browser(p.to_str())
示例#7
0
    def gen_images(cls, imdir, start_idx=1):
        """ 自动生成标准的images字段

        :param imdir: 图片目录
        :param start_idx: 图片起始下标
        :return: list[dict(id, file_name, width, height)]
        """
        files = Dir(imdir).select_files(['*.jpg', '*.png'])
        images = []
        for i, f in enumerate(files, start=start_idx):
            w, h = Image.open(str(f)).size
            images.append({
                'id': i,
                'file_name': f.name,
                'width': w,
                'height': h
            })
        return images
示例#8
0
    def find_pattern(self, pattern, files=None) -> pd.DataFrame:
        """
        :param pattern: re.compile 对象
        :param files: 要搜索的文件清单

        191108周五10:40,目前跑 c:/pycode 要2分钟
        >> browser(Git('C:/pycode/').find_pattern(re.compile(r'ssb等改明文')))

        """
        # 1 主对象
        df = self.list_commits()
        all_shas = list(reversed(list(df['sha'])))

        # 2 files没有设置则匹配所有文件
        # TODO 当前已经不存在的文件这样是找不到的,有办法把历史文件也挖出来?
        if not files:
            with Dir(self.g.working_dir):
                files = filesmatch('**/*.py')

        # 3 遍历文件
        for file in files:
            d = {}
            for sha in self.commits_sha(file=file):
                try:
                    cnt = len(pattern.findall(self.show(file, sha)))
                    if cnt: d[sha] = cnt
                except git.exc.GitCommandError:
                    pass
            if d:
                li = []
                v = 0
                for sha in all_shas:
                    if sha in d:
                        v = d[sha]
                    li.append(v)
                df[file] = list(reversed(li))

        return df
示例#9
0
    def __init__(self,
                 root,
                 relpath2data=None,
                 *,
                 reads=True,
                 prt=False,
                 fltr=None,
                 slt=None,
                 extdata=None):
        """
        :param root: 数据所在根目录
        :param dict[str, readed_data] relpath2data: {relpath: data1, 'a/1.txt': data2, ...}
            如果未传入data具体值,则根据目录里的情况自动初始化获得data的值

            relpath是对应的File标注文件的相对路径字符串
            data1, data2 是读取的标注数据,根据不同情况,会存成不同格式
                如果是json则直接保存json内存对象结构
                如果是txt可能会进行一定的结构化解析存储
        :param extdata: 可以存储一些扩展信息内容
        :param fltr: filter的缩写,PathGroups 的过滤规则。一般用来进行图片匹配。
            None,没有过滤规则,就算不存在slt格式的情况下,也会保留分组
            'json'等字符串规则, 使用 select_group_which_hassuffix,必须含有特定后缀的分组
            judge(k, v),自定义函数规则
        :param slt: select的缩写,要选中的标注文件后缀格式
            如果传入slt参数,该 Basic 基础类只会预设好 file 参数,数据部分会置 None,需要后续主动读取

        >> BasicLabelData('textGroup/aabb', {'a.json': ..., 'a/1.json': ...})
        >> BasicLabelData('textGroup/aabb', slt='json')
        >> BasicLabelData('textGroup/aabb', fltr='jpg', slt='json')  # 只获取有对应jpg图片的json文件
        >> BasicLabelData('textGroup/aabb', fltr='jpg|png', slt='json')
        """

        # 1 基础操作
        root = Dir(root)
        self.root, self.rp2data, self.extdata = root, relpath2data or {}, extdata or {}
        self.pathgs = None

        if relpath2data is not None or slt is None:
            return

        # 2 如果没有默认data数据,以及传入slt参数,则需要使用默认文件关联方式读取标注
        relpath2data = {}
        gs = PathGroups.groupby(Dir(root).select_files('**/*'))
        if isinstance(fltr, str):
            gs = gs.select_group_which_hassuffix(fltr)
        elif callable(fltr):
            gs = gs.select_group(fltr)
        self.pathgs = gs

        # 3 读取数据
        for stem, suffixs in tqdm(gs.data.items(),
                                  f'{self.__class__.__name__}读取数据',
                                  disable=not prt):
            f = File(stem, suffix=slt)
            if reads and f:
                # dprint(f)  # 空json会报错:json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
                relpath2data[f.relpath(self.root)] = f.read()
            else:
                relpath2data[f.relpath(self.root)] = None

        self.rp2data = relpath2data
示例#10
0
    def codefile(self):
        """ 多用途文件处理器

        核心理念是不设定具体功能,而是让用户在 subinput 自己写需要执行的py代码功能
        而在subinput,可以用一些特殊的标识符来传参

        以file为例,有4类参数:
            file,处理当前目录 文件
            rfile,递归处理所有目录下 文件
            files,当前目录 所有文件
            rfiles,递归获取所有目录下 所有文件

        类型上,file可以改为
            dir,只分析目录
            path,所有文件和目录

        扩展了一些特殊的类型:
            imfile,图片文件
        """
        from functools import reduce
        try:
            from xlproject.kzconfig import KzDataSync
            from pyxllib.data.labelme import reduce_labelme_jsonfile
        except ModuleNotFoundError:
            pass

        tt = TicToc()

        # 1 获得所有标识符
        # 每个单词前后都有空格,方便定界
        keywords = filter(
            lambda x: re.match(r'[a-zA-Z_]+$', x),
            set(re.findall(r'\.?[a-zA-Z_]+\(?', self.cmds['subinput'])))
        keywords = ' ' + ' '.join(keywords) + ' '
        # print('keywords:', keywords)

        # 2 生成一些备用的智能参数
        # 一级目录下选中的文件
        paths = self.paths
        # 用正则判断一些智能参数是否要计算,注意不能只判断files,如果出现file,也是要引用files的
        files = [File(p) for p in paths
                 if p.is_file()] if re.search(r'files? ', keywords) else []
        imfiles = [f for f in files if self.is_image(f)] if re.search(
            r' imfiles? ', keywords) else []
        # 出现r系列的递归操作,都是要计算出dirs的
        dirs = [Dir(p) for p in paths if p.is_dir()] if re.search(
            r' (dirs?|r[a-zA-Z_]+) ', keywords) else []

        # 递归所有的文件
        rpaths = reduce(lambda x, y: x + y.select('**/*').subpaths(), [paths] + dirs) \
            if re.search(r' (r[a-zA-Z_]+) ', keywords) else []
        rfiles = [File(p) for p in rpaths if p.is_file()] if re.search(
            r' (r(im)?files?) ', keywords) else []
        rimfiles = [f for f in rfiles if self.is_image(f)] if re.search(
            r' rimfiles? ', keywords) else []
        rdirs = [Dir(p) for p in rpaths
                 if p.is_dir()] if re.search(r' (rdirs?) ', keywords) else []

        # 3 判断是否要智能开循环处理
        m = re.search(r' (r?(path|(im)?file|dir)) ', keywords)
        if m:
            name = m.group(1)
            objs = eval(name + 's')
            print('len(' + name + 's)=', len(objs))
            for x in objs:
                locals()[name] = x
                eval(self.cmds['subinput'])
        else:  # 没有的话就直接处理所有文件
            eval(self.cmds['subinput'])

        # 4 运行结束标志
        print(f'finished in {format_timespan(tt.tocvalue())}.')
示例#11
0
class AutoGuiLabelData:
    """ AutoGuiLabelData """
    def __init__(self, root):
        """
        data:dict
            key: 相对路径/图片stem (这一节称为loc)
                label名称:对应属性dict1 (跟loc拼在一起,称为loclabel)
                label2:dict2
        """
        self.root = Dir(root)
        self.data = defaultdict(dict)
        self.imfiles = {}

        for file in self.root.select_files('**/*.json'):
            lmdict = file.read()
            imfile = file.with_name(lmdict['imagePath'])
            img = xlcv.read(imfile)
            loc = file.relpath(self.root).replace('\\', '/')[:-5]
            self.imfiles[loc] = imfile

            for shape in lmdict['shapes']:
                attrs = self.parse_shape(shape, img)
                self.data[loc][attrs['label']] = attrs

    @classmethod
    def parse_shape(cls, shape, image=None):
        """ 解析一个shape的数据为dict字典 """
        # 1 解析原label为字典
        attrs = DictTool.json_loads(shape['label'], 'label')
        attrs.update(DictTool.sub(shape, ['label']))

        # 2 中心点 center
        shape_type = shape['shape_type']
        pts = shape['points']
        if shape_type in ('rectangle', 'polygon', 'line'):
            attrs['center'] = np.array(np.array(pts).mean(axis=0),
                                       dtype=int).tolist()
        elif shape_type == 'circle':
            attrs['center'] = pts[0]

        # 3 外接矩形 rect
        if shape_type in ('rectangle', 'polygon'):
            attrs['ltrb'] = np.array(pts, dtype=int).reshape(-1).tolist()
        elif shape_type == 'circle':
            x, y = pts[0]
            r = ((x - pts[1][0])**2 + (y - pts[1][1])**2)**0.5
            attrs['ltrb'] = [
                round_int(v) for v in [x - r, y - r, x + r, y + r]
            ]

        # 4 图片数据 img, etag
        if image is not None and attrs['ltrb']:
            attrs['img'] = xlcv.get_sub(image, attrs['ltrb'])
            # attrs['etag'] = get_etag(attrs['img'])
            # TODO 这里目的其实就是让相同图片对比尽量相似,所以可以用dhash而不是etag

        # 5 中心点像素值 pixel
        p = attrs['center']
        if image is not None and p:
            attrs['pixel'] = tuple(image[p[1], p[0]].tolist()[::-1])

        # if 'rect' in attrs:
        #     del attrs['rect']  # 旧版的格式数据,删除

        return attrs

    def update_loclabel_img(self, loclabel, img, *, if_exists='update'):
        """ 添加一张图片数据

        :param if_exists:
            update,更新
            skip,跳过,不更新
        """
        loc, label = os.path.split(loclabel)
        h, w = img.shape[:2]

        # 1 如果loc图片不存在,则新建一个jpg图片
        update = True
        if loc not in self.data or not self.data[loc]:
            imfile = xlcv.write(img, File(loc, self.root, suffix='.jpg'))
            self.imfiles[loc] = imfile
            shape = LabelmeDict.gen_shape(label, [[0, 0], [w, h]])
            self.data[loc][label] = self.parse_shape(shape)
        # 2 不存在的标签,则在最后一行新建一张图
        elif label not in self.data[loc]:
            image = xlcv.read(self.imfiles[loc])
            height, width = image.shape[:2]
            assert width == w  # 先按行拼接,以后有时间可以扩展更灵活的拼接操作
            # 拼接,并重新存储为图片
            image = np.concatenate([image, img])
            xlcv.write(image, self.imfiles[loc])
            shape = LabelmeDict.gen_shape(label,
                                          [[0, height], [width, height + h]])
            self.data[loc][label] = self.parse_shape(shape)
        # 3 已有的图,则进行替换
        elif if_exists == 'update':
            image = xlcv.read(self.imfiles[loc])
            [x1, y1, x2, y2] = self.data[loc][label]['ltrb']
            image[y1:y2, x1:x2] = img
            xlcv.write(image, self.imfiles[loc])
        else:
            update = False

        if update:  # 需要实时保存到文件中
            self.write(loc)

    def write(self, loc):
        f = File(loc, self.root, suffix='.json')
        imfile = self.imfiles[loc]
        lmdict = LabelmeDict.gen_data(imfile)
        for label, ann in self.data[loc].items():
            a = ann.copy()
            DictTool.isub(a, ['img'])
            shape = LabelmeDict.gen_shape(json.dumps(a, ensure_ascii=False),
                                          a['points'],
                                          a['shape_type'],
                                          group_id=a['group_id'],
                                          flags=a['flags'])
            lmdict['shapes'].append(shape)
        f.write(lmdict, indent=2)

    def writes(self):
        for loc in self.data.keys():
            self.write(loc)

    def __getitem__(self, loclabel):
        loc, label = os.path.split(loclabel)
        try:
            return self.data[loc][label]
        except KeyError:
            return None

    def __setitem__(self, loclabel, value):
        loc, label = os.path.split(loclabel)
        self.data[loc][label] = value
示例#12
0
def check_repeat_filenames(dir, key='stem', link=True):
    """ 检查目录下文件结构情况的功能函数

    https://www.yuque.com/xlpr/pyxllib/check_repeat_filenames

    :param dir: 目录Dir类型,也可以输入路径,如果没有files成员,则默认会获取所有子文件
    :param key: 以什么作为行分组的key名称,基本上都是用'stem',偶尔可能用'name'
        遇到要忽略 -eps-to-pdf.pdf 这种后缀的,也可以自定义处理规则
        例如 key=lambda p: re.sub(r'-eps-to-pdf', '', p.stem).lower()
    :param link: 默认True会生成文件超链接
    :return: 一个df表格,行按照key的规则分组,列默认按suffix扩展名分组
    """
    # 1 智能解析dir参数
    if not isinstance(dir, Dir):
        dir = Dir(dir)
    if not dir.subs:
        dir = dir.select('**/*', type_='file')

    # 2 辅助函数,智能解析key参数
    if isinstance(key, str):

        def extract_key(p):
            return getattr(p, key).lower()
    elif callable(key):
        extract_key = key
    else:
        raise TypeError

    # 3 制作df表格数据
    columns = ['key', 'suffix', 'filename']
    li = []
    for f in dir.subs:
        p = File(f)
        li.append([extract_key(p), p.suffix.lower(), f])
    df = pd.DataFrame.from_records(li, columns=columns)

    # 4 分组
    def joinfile(files):
        if len(files):
            if link:
                return ', '.join([
                    f'<a href="{dir / f}" target="_blank">{f}</a>'
                    for f in files
                ])
            else:
                return ', '.join(files)
        else:
            return ''

    groups = df.groupby(['key', 'suffix']).agg({'filename': joinfile})
    groups.reset_index(inplace=True)
    view_table = groups.pivot(index='key', columns='suffix', values='filename')
    view_table.fillna('', inplace=True)

    # 5 判断每个key的文件总数
    count_df = df.groupby('key').agg({'filename': 'count'})
    view_table = pd.concat([view_table, count_df], axis=1)
    view_table.rename({'filename': 'count'}, axis=1, inplace=True)

    browser(view_table, to_html_args={'escape': not link})
    return df
示例#13
0
    def to_labelme_cls(self, root, *, bbox=True, seg=False, info=False):
        """
        :param root: 图片根目录
        :return:
            extdata,存储了一些匹配异常信息
        """
        root, data = Dir(root), {}
        catid2name = {x['id']: x['name'] for x in self.gt_dict['categories']}

        # 1 准备工作,构建文件名索引字典
        gs = PathGroups.groupby(root.select_files('**/*'))

        # 2 遍历生成labelme数据
        not_finds = set()  # coco里有的图片,root里没有找到
        multimatch = dict()  # coco里的某张图片,在root找到多个匹配文件
        for img, anns in tqdm(self.group_gt(reserve_empty=True),
                              disable=not info):
            # 2.1 文件匹配
            imfiles = gs.find_files(img['file_name'])
            if not imfiles:  # 没有匹配图片的,不处理
                not_finds.add(img['file_name'])
                continue
            elif len(imfiles) > 1:
                multimatch[img['file_name']] = imfiles
                imfile = imfiles[0]
            else:
                imfile = imfiles[0]

            # 2.2 数据内容转换
            lmdict = LabelmeDict.gen_data(imfile)
            img = DictTool.or_(img, {'xltype': 'image'})
            lmdict['shapes'].append(
                LabelmeDict.gen_shape(json.dumps(img, ensure_ascii=False),
                                      [[-10, 0], [-5, 0]]))
            for ann in anns:
                if bbox:
                    ann = DictTool.or_(
                        ann, {'category_name': catid2name[ann['category_id']]})
                    label = json.dumps(ann, ensure_ascii=False)
                    shape = LabelmeDict.gen_shape(label,
                                                  xywh2ltrb(ann['bbox']))
                    lmdict['shapes'].append(shape)

                if seg:
                    # 把分割也显示出来(用灰色)
                    for x in ann['segmentation']:
                        an = {
                            'box_id': ann['id'],
                            'xltype': 'seg',
                            'shape_color': [191, 191, 191]
                        }
                        label = json.dumps(an, ensure_ascii=False)
                        lmdict['shapes'].append(LabelmeDict.gen_shape(
                            label, x))

            f = imfile.with_suffix('.json')

            data[f.relpath(root)] = lmdict

        return LabelmeDataset(root,
                              data,
                              extdata={
                                  'categories': self.gt_dict['categories'],
                                  'not_finds': not_finds,
                                  'multimatch': Groups(multimatch)
                              })
示例#14
0
    def _to_labelme_match(self,
                          match_func_name,
                          imdir,
                          dst_dir=None,
                          *,
                          segmentation=False,
                          hide_match_dt=False,
                          **kwargs):
        """ 可视化目标检测效果

        :param imdir: 默认会把结果存储到imdir
        :param dst_dir: 但如果写了dst_dir参数,则会有选择地从imdir筛选出图片到dst_dir
        """
        def func(g):
            # 1 获得图片id和文件
            image_id, df = g
            imfile = File(df.iloc[0]['file_name'], imdir)
            if not imfile:
                return  # 如果没有图片不处理
            image = self.images.loc[image_id]
            image = image.drop(['file_name', 'height', 'width'])

            # 2 生成这张图片对应的json标注
            if dst_dir:
                imfile = imfile.copy(dst_dir, if_exists='skip')
            lm = Coco2Labelme(imfile)

            height, width = lm.data['imageHeight'], lm.data['imageWidth']
            # 注意df取出来的image_id默认是int64类型,要转成int,否则json会保存不了int64类型
            lm.add_shape('', [0, 0, 10, 0],
                         shape_type='line',
                         shape_color=[0, 0, 0],
                         size=f'{height}x{width}',
                         **(image.to_dict()))
            getattr(lm, match_func_name)(df,
                                         segmentation=segmentation,
                                         hide_match_dt=hide_match_dt,
                                         **kwargs)
            lm.write()  # 保存json文件到img对应目录下

        if dst_dir is not None:
            dst_dir = Dir(dst_dir)
            dst_dir.ensure_dir()
        match_anns = self.match_anns.copy()
        # 为了方便labelme操作,需要扩展几列内容
        match_anns['file_name'] = [
            self.images.loc[x, 'file_name'] for x in match_anns['image_id']
        ]
        match_anns['gt_category_name'] = [
            self.categories.loc[x, 'name']
            for x in match_anns['gt_category_id']
        ]
        match_anns['dt_category_name'] = [
            self.categories.loc[x, 'name']
            for x in match_anns['dt_category_id']
        ]
        match_anns['gt_supercategory'] = [
            self.categories.loc[x, 'supercategory']
            for x in match_anns['gt_category_id']
        ]
        mtqdm(func,
              list(iter(match_anns.groupby('image_id'))),
              max_workers=8,
              desc='make labelme json:')