This commit is contained in:
yueliang
2024-09-03 20:36:30 +08:00
parent ad9b3e25c4
commit bc584dfd21
14 changed files with 927 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
from transformers import PretrainedConfig
class BiRefNetConfig(PretrainedConfig):
model_type = "SegformerForSemanticSegmentation"
def __init__(
self,
bb_pretrained=False,
**kwargs
):
self.bb_pretrained = bb_pretrained
super().__init__(**kwargs)
+59
View File
@@ -0,0 +1,59 @@
# ComfyUI-BiRefNet-Hugo
## 介绍 | Introduction
本仓库将BiRefNet最新模型封装为ComfyUI节点来使用,相较于旧模型,最新模型的抠图精度更高更好。<br>
This repository wraps the latest BiRefNet model as ComfyUI nodes. Compared to the previous model, the latest model offers higher and better matting accuracy.
## 安装 | Installation
1. 进入节点目录, `ComfyUI/custom_nodes/`
2. `git clone https://github.com/MoonHugo/ComfyUI-BiRefNet-Hugo.git`
3. `cd ComfyUI-BiRefNet-Hugo`
4. `pip install -r requirements.txt`
___
1. Go to comfyUI custom_nodes folder, `ComfyUI/custom_nodes/`
2. `git clone https://github.com/MoonHugo/ComfyUI-BiRefNet-Hugo.git`
3. `cd ComfyUI-BiRefNet-Hugo`
4. `pip install -r requirements.txt`
## 使用 | Usage
示例工作流放置在`ComfyUI-BiRefNet-Hugo/workflow`中<br/>
The demo workflow placed in `ComfyUI-BiRefNet-Hugo/workflow`
___
工作流workflow.json的使用<br/>
The use of workflow.json
![plot](./assets/d0a22b2a-ceb3-4205-9b4e-f6a68e4337c7.png)
工作流video_workflow.json的使用<br/>
The use of video_workflow.json
___
![plot](./assets/2de5b085-1125-46f9-8ef3-06706743f182.png)
## 效果演示 | Sample Result
![](./assets/demo1.gif)
![](./assets/demo2.gif)
![](./assets/demo3.gif)
## 社交账号 | Social Account Homepage
- Bilibili:[我的B站主页](https://space.bilibili.com/1303099255)
## 感谢 | Acknowledgments
感谢BiRefNet仓库的所有作者 [ZhengPeng7/BiRefNet](https://github.com/zhengpeng7/birefnet)
Thanks to BiRefNet repo owner [ZhengPeng7/BiRefNet](https://github.com/zhengpeng7/birefnet)
部分代码参考了 [ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO](https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO) 感谢!
Some of the code references [ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO](https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO) Thanks!
## 关注历史 | star history
[![Star History Chart](https://api.star-history.com/svg?repos=MoonHugo/ComfyUI-BiRefNet-Hugo&type=Date)](https://star-history.com/#MoonHugo/ComfyUI-BiRefNet-Hugo&Date)
+3
View File
@@ -0,0 +1,3 @@
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

+148
View File
@@ -0,0 +1,148 @@
### config.py
import os
import math
from folder_paths import models_dir
os.environ['HOME'] = os.path.expanduser("~")
class Config():
def __init__(self) -> None:
# PATH settings
self.sys_home_dir = os.environ['HOME'] # Make up your file system as: SYS_HOME_DIR/codes/dis/BiRefNet, SYS_HOME_DIR/datasets/dis/xx, SYS_HOME_DIR/weights/xx
# TASK settings
self.task = ['DIS5K', 'COD', 'HRSOD', 'DIS5K+HRSOD+HRS10K', 'P3M-10k'][0]
self.training_set = {
'DIS5K': ['DIS-TR', 'DIS-TR+DIS-TE1+DIS-TE2+DIS-TE3+DIS-TE4'][0],
'COD': 'TR-COD10K+TR-CAMO',
'HRSOD': ['TR-DUTS', 'TR-HRSOD', 'TR-UHRSD', 'TR-DUTS+TR-HRSOD', 'TR-DUTS+TR-UHRSD', 'TR-HRSOD+TR-UHRSD', 'TR-DUTS+TR-HRSOD+TR-UHRSD'][5],
'DIS5K+HRSOD+HRS10K': 'DIS-TE1+DIS-TE2+DIS-TE3+DIS-TE4+DIS-TR+TE-HRS10K+TE-HRSOD+TE-UHRSD+TR-HRS10K+TR-HRSOD+TR-UHRSD', # leave DIS-VD for evaluation.
'P3M-10k': 'TR-P3M-10k',
}[self.task]
self.prompt4loc = ['dense', 'sparse'][0]
# Faster-Training settings
self.load_all = True
self.compile = True # 1. Trigger CPU memory leak in some extend, which is an inherent problem of PyTorch.
# Machines with > 70GB CPU memory can run the whole training on DIS5K with default setting.
# 2. Higher PyTorch version may fix it: https://github.com/pytorch/pytorch/issues/119607.
# 3. But compile in Pytorch > 2.0.1 seems to bring no acceleration for training.
self.precisionHigh = True
# MODEL settings
self.ms_supervision = True
self.out_ref = self.ms_supervision and True
self.dec_ipt = True
self.dec_ipt_split = True
self.cxt_num = [0, 3][1] # multi-scale skip connections from encoder
self.mul_scl_ipt = ['', 'add', 'cat'][2]
self.dec_att = ['', 'ASPP', 'ASPPDeformable'][2]
self.squeeze_block = ['', 'BasicDecBlk_x1', 'ResBlk_x4', 'ASPP_x3', 'ASPPDeformable_x3'][1]
self.dec_blk = ['BasicDecBlk', 'ResBlk', 'HierarAttDecBlk'][0]
# TRAINING settings
self.batch_size = 4
self.IoU_finetune_last_epochs = [
0,
{
'DIS5K': -50,
'COD': -20,
'HRSOD': -20,
'DIS5K+HRSOD+HRS10K': -20,
'P3M-10k': -20,
}[self.task]
][1] # choose 0 to skip
self.lr = (1e-4 if 'DIS5K' in self.task else 1e-5) * math.sqrt(self.batch_size / 4) # DIS needs high lr to converge faster. Adapt the lr linearly
self.size = 1024
self.num_workers = max(4, self.batch_size) # will be decrease to min(it, batch_size) at the initialization of the data_loader
# Backbone settings
self.bb = [
'vgg16', 'vgg16bn', 'resnet50', # 0, 1, 2
'swin_v1_t', 'swin_v1_s', # 3, 4
'swin_v1_b', 'swin_v1_l', # 5-bs9, 6-bs4
'pvt_v2_b0', 'pvt_v2_b1', # 7, 8
'pvt_v2_b2', 'pvt_v2_b5', # 9-bs10, 10-bs5
][6]
self.lateral_channels_in_collection = {
'vgg16': [512, 256, 128, 64], 'vgg16bn': [512, 256, 128, 64], 'resnet50': [1024, 512, 256, 64],
'pvt_v2_b2': [512, 320, 128, 64], 'pvt_v2_b5': [512, 320, 128, 64],
'swin_v1_b': [1024, 512, 256, 128], 'swin_v1_l': [1536, 768, 384, 192],
'swin_v1_t': [768, 384, 192, 96], 'swin_v1_s': [768, 384, 192, 96],
'pvt_v2_b0': [256, 160, 64, 32], 'pvt_v2_b1': [512, 320, 128, 64],
}[self.bb]
if self.mul_scl_ipt == 'cat':
self.lateral_channels_in_collection = [channel * 2 for channel in self.lateral_channels_in_collection]
self.cxt = self.lateral_channels_in_collection[1:][::-1][-self.cxt_num:] if self.cxt_num else []
# MODEL settings - inactive
self.lat_blk = ['BasicLatBlk'][0]
self.dec_channels_inter = ['fixed', 'adap'][0]
self.refine = ['', 'itself', 'RefUNet', 'Refiner', 'RefinerPVTInChannels4'][0]
self.progressive_ref = self.refine and True
self.ender = self.progressive_ref and False
self.scale = self.progressive_ref and 2
self.auxiliary_classification = False # Only for DIS5K, where class labels are saved in `dataset.py`.
self.refine_iteration = 1
self.freeze_bb = False
self.model = [
'BiRefNet',
][0]
if self.dec_blk == 'HierarAttDecBlk':
self.batch_size = 2 ** [0, 1, 2, 3, 4][2]
# TRAINING settings - inactive
self.preproc_methods = ['flip', 'enhance', 'rotate', 'pepper', 'crop'][:4]
self.optimizer = ['Adam', 'AdamW'][1]
self.lr_decay_epochs = [1e5] # Set to negative N to decay the lr in the last N-th epoch.
self.lr_decay_rate = 0.5
# Loss
self.lambdas_pix_last = {
# not 0 means opening this loss
# original rate -- 1 : 30 : 1.5 : 0.2, bce x 30
'bce': 30 * 1, # high performance
'iou': 0.5 * 1, # 0 / 255
'iou_patch': 0.5 * 0, # 0 / 255, win_size = (64, 64)
'mse': 150 * 0, # can smooth the saliency map
'triplet': 3 * 0,
'reg': 100 * 0,
'ssim': 10 * 1, # help contours,
'cnt': 5 * 0, # help contours
'structure': 5 * 0, # structure loss from codes of MVANet. A little improvement on DIS-TE[1,2,3], a bit more decrease on DIS-TE4.
}
self.lambdas_cls = {
'ce': 5.0
}
# Adv
self.lambda_adv_g = 10. * 0 # turn to 0 to avoid adv training
self.lambda_adv_d = 3. * (self.lambda_adv_g > 0)
# PATH settings - inactive
self.data_root_dir = os.path.join(self.sys_home_dir, 'datasets/dis')
self.weights_root_dir = os.path.join(self.sys_home_dir, 'weights')
self.weights = {
'pvt_v2_b2': os.path.join(self.weights_root_dir, 'pvt_v2_b2.pth'),
'pvt_v2_b5': os.path.join(self.weights_root_dir, ['pvt_v2_b5.pth', 'pvt_v2_b5_22k.pth'][0]),
'swin_v1_b': os.path.join(self.weights_root_dir, ['swin_base_patch4_window12_384_22kto1k.pth', 'swin_base_patch4_window12_384_22k.pth'][0]),
'swin_v1_l': os.path.join(self.weights_root_dir, ['swin_large_patch4_window12_384_22kto1k.pth', 'swin_large_patch4_window12_384_22k.pth'][0]),
'swin_v1_t': os.path.join(self.weights_root_dir, ['swin_tiny_patch4_window7_224_22kto1k_finetune.pth'][0]),
'swin_v1_s': os.path.join(self.weights_root_dir, ['swin_small_patch4_window7_224_22kto1k_finetune.pth'][0]),
'pvt_v2_b0': os.path.join(self.weights_root_dir, ['pvt_v2_b0.pth'][0]),
'pvt_v2_b1': os.path.join(self.weights_root_dir, ['pvt_v2_b1.pth'][0]),
}
# Callbacks - inactive
self.verbose_eval = True
self.only_S_MAE = False
self.use_fp16 = False # Bugs. It may cause nan in training.
self.SDPA_enabled = False # Bugs. Slower and errors occur in multi-GPUs
# others
self.device = [0, 'cpu'][0] # .to(0) == .to('cuda:0')
self.batch_size_valid = 1
self.rand_seed = 7
# run_sh_file = [f for f in os.listdir('.') if 'train.sh' == f] + [os.path.join('..', f) for f in os.listdir('..') if 'train.sh' == f]
# with open(run_sh_file[0], 'r') as f:
# lines = f.readlines()
# self.save_last = int([l.strip() for l in lines if '"{}")'.format(self.task) in l and 'val_last=' in l][0].split('val_last=')[-1].split()[0])
# self.save_step = int([l.strip() for l in lines if '"{}")'.format(self.task) in l and 'step=' in l][0].split('step=')[-1].split()[0])
# self.val_step = [0, self.save_step][0]
+95
View File
@@ -0,0 +1,95 @@
from transformers import AutoModelForImageSegmentation
import torch
from torchvision import transforms
import numpy as np
from PIL import Image
import torch.nn.functional as F
from .config import Config
Config()
torch.set_float32_matmul_precision(["high", "highest"][0])
birefnet = AutoModelForImageSegmentation.from_pretrained(
"ZhengPeng7/BiRefNet", trust_remote_code=True
)
birefnet.to("cuda")
transform_image = transforms.Compose(
[
transforms.Resize((1024, 1024)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
)
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def resize_image(image):
image = image.convert('RGB')
model_input_size = (1024, 1024)
image = image.resize(model_input_size, Image.BILINEAR)
return image
class BiRefNet_Hugo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE", "MASK",)
RETURN_NAMES = ("image", "mask",)
FUNCTION = "background_remove"
CATEGORY = "🔥BiRefNet"
def background_remove(self, image):
processed_images = []
processed_masks = []
for image in image:
orig_image = tensor2pil(image)
w,h = orig_image.size
image = resize_image(orig_image)
im_tensor = transform_image(image).unsqueeze(0)
if torch.cuda.is_available():
im_tensor=im_tensor.cuda()
with torch.no_grad():
result = birefnet(im_tensor)[-1].sigmoid().cpu()
result = torch.squeeze(F.interpolate(result, size=(h,w)))
ma = torch.max(result)
mi = torch.min(result)
result = (result-mi)/(ma-mi)
im_array = (result*255).cpu().data.numpy().astype(np.uint8)
pil_im = Image.fromarray(np.squeeze(im_array))
new_im = Image.new("RGBA", pil_im.size, (0,0,0,0))
new_im.paste(orig_image, mask=pil_im)
new_im_tensor = pil2tensor(new_im)
pil_im_tensor = pil2tensor(pil_im)
processed_images.append(new_im_tensor)
processed_masks.append(pil_im_tensor)
new_ims = torch.cat(processed_images, dim=0)
new_masks = torch.cat(processed_masks, dim=0)
return new_ims, new_masks
NODE_CLASS_MAPPINGS = {
"BiRefNet_Hugo": BiRefNet_Hugo
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"BiRefNet_Hugo": "🔥BiRefNet"
}
+2
View File
@@ -0,0 +1,2 @@
numpy
timm
Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

+378
View File
@@ -0,0 +1,378 @@
{
"last_node_id": 15,
"last_link_id": 16,
"nodes": [
{
"id": 11,
"type": "VHS_LoadVideo",
"pos": [
840,
-90
],
"size": [
210,
480
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null,
"label": "批次管理"
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
12
],
"shape": 3,
"label": "图像",
"slot_index": 0
},
{
"name": "frame_count",
"type": "INT",
"links": null,
"shape": 3,
"label": "帧计数"
},
{
"name": "audio",
"type": "AUDIO",
"links": null,
"shape": 3,
"label": "音频"
},
{
"name": "video_info",
"type": "VHS_VIDEOINFO",
"links": null,
"shape": 3,
"label": "视频信息"
}
],
"properties": {
"Node name for S&R": "VHS_LoadVideo"
},
"widgets_values": {
"video": "6月28日(1).mp4",
"force_rate": 0,
"force_size": "Disabled",
"custom_width": 512,
"custom_height": 512,
"frame_load_cap": 0,
"skip_first_frames": 0,
"select_every_nth": 1,
"choose video to upload": "image",
"videopreview": {
"hidden": false,
"paused": false,
"params": {
"frame_load_cap": 0,
"skip_first_frames": 0,
"force_rate": 0,
"filename": "6月28日(1).mp4",
"type": "input",
"format": "video/mp4",
"select_every_nth": 1
},
"muted": false
}
}
},
{
"id": 1,
"type": "BiRefNet_Hugo",
"pos": [
1110,
-60
],
"size": {
"0": 210,
"1": 60
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 12,
"slot_index": 0,
"label": "image"
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
14
],
"slot_index": 0,
"shape": 3,
"label": "image"
},
{
"name": "mask",
"type": "MASK",
"links": [
15
],
"slot_index": 1,
"shape": 3,
"label": "mask"
}
],
"properties": {
"Node name for S&R": "BiRefNet_Hugo"
}
},
{
"id": 14,
"type": "MaskToImage",
"pos": [
1410,
30
],
"size": {
"0": 210,
"1": 30
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 15,
"label": "遮罩"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
16
],
"shape": 3,
"label": "图像",
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "MaskToImage"
}
},
{
"id": 15,
"type": "VHS_VideoCombine",
"pos": [
1410,
120
],
"size": [
360,
660
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 16,
"label": "图像"
},
{
"name": "audio",
"type": "AUDIO",
"link": null,
"label": "音频"
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null,
"label": "批次管理"
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": null,
"shape": 3,
"label": "文件名"
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 30,
"loop_count": 0,
"filename_prefix": "AnimateDiff",
"format": "video/h265-mp4",
"pix_fmt": "yuv420p10le",
"crf": 22,
"save_metadata": true,
"pingpong": false,
"save_output": true,
"videopreview": {
"hidden": false,
"paused": false,
"params": {
"filename": "AnimateDiff_00005.mp4",
"subfolder": "",
"type": "output",
"format": "video/h265-mp4",
"frame_rate": 30
},
"muted": false
}
}
},
{
"id": 13,
"type": "VHS_VideoCombine",
"pos": [
1830,
-180
],
"size": [
360,
660
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 14,
"label": "图像"
},
{
"name": "audio",
"type": "AUDIO",
"link": null,
"label": "音频"
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null,
"label": "批次管理"
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": null,
"shape": 3,
"label": "文件名"
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 30,
"loop_count": 0,
"filename_prefix": "AnimateDiff",
"format": "video/h265-mp4",
"pix_fmt": "yuv420p10le",
"crf": 22,
"save_metadata": true,
"pingpong": false,
"save_output": true,
"videopreview": {
"hidden": false,
"paused": false,
"params": {
"filename": "AnimateDiff_00004.mp4",
"subfolder": "",
"type": "output",
"format": "video/h265-mp4",
"frame_rate": 30
},
"muted": false
}
}
}
],
"links": [
[
12,
11,
0,
1,
0,
"IMAGE"
],
[
14,
1,
0,
13,
0,
"IMAGE"
],
[
15,
1,
1,
14,
0,
"MASK"
],
[
16,
14,
0,
15,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8390545288824037,
"offset": [
-471.7394319558889,
296.200180526101
]
}
},
"version": 0.4
}
+231
View File
@@ -0,0 +1,231 @@
{
"last_node_id": 5,
"last_link_id": 4,
"nodes": [
{
"id": 1,
"type": "BiRefNet_Hugo",
"pos": [
921,
256
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 1,
"label": "image",
"slot_index": 0
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
2
],
"shape": 3,
"label": "image",
"slot_index": 0
},
{
"name": "mask",
"type": "MASK",
"links": [
3
],
"shape": 3,
"label": "mask",
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "BiRefNet_Hugo"
}
},
{
"id": 4,
"type": "MaskToImage",
"pos": [
922,
352
],
"size": {
"0": 210,
"1": 26
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 3,
"label": "遮罩"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
4
],
"shape": 3,
"label": "图像",
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "MaskToImage"
}
},
{
"id": 5,
"type": "PreviewImage",
"pos": [
1299,
410
],
"size": [
210,
246
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4,
"label": "图像"
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 3,
"type": "PreviewImage",
"pos": [
1295,
74
],
"size": [
210,
246
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2,
"label": "图像"
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 2,
"type": "LoadImage",
"pos": [
561,
254
],
"size": [
315,
314
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1
],
"shape": 3,
"label": "图像"
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3,
"label": "遮罩"
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"image.webp",
"image"
]
}
],
"links": [
[
1,
2,
0,
1,
0,
"IMAGE"
],
[
2,
1,
0,
3,
0,
"IMAGE"
],
[
3,
1,
1,
4,
0,
"MASK"
],
[
4,
4,
0,
5,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.2100000000000002,
"offset": [
-299.9616678351899,
94.8424540865177
]
}
},
"version": 0.4
}