fastplateocr-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastplateocr/__init__.py +5 -0
- fastplateocr/det/__init__.py +1 -0
- fastplateocr/det/c3k2/__init__.py +0 -0
- fastplateocr/det/c3k2/bottleneck/__init__.py +0 -0
- fastplateocr/det/c3k2/bottleneck/rep_mixer.py +81 -0
- fastplateocr/license_plate_dict.txt +37 -0
- fastplateocr/pipeline.py +185 -0
- fastplateocr/rec/losses/__init__.py +71 -0
- fastplateocr/rec/losses/ctc_loss.py +33 -0
- fastplateocr/rec/metrics/__init__.py +33 -0
- fastplateocr/rec/metrics/rec_metric.py +286 -0
- fastplateocr/rec/modeling/__init__.py +11 -0
- fastplateocr/rec/modeling/base_recognizer.py +58 -0
- fastplateocr/rec/modeling/common.py +238 -0
- fastplateocr/rec/modeling/decoders/__init__.py +21 -0
- fastplateocr/rec/modeling/decoders/efficient_rctc_decoder.py +60 -0
- fastplateocr/rec/modeling/encoders/__init__.py +22 -0
- fastplateocr/rec/modeling/encoders/svtrv2.py +470 -0
- fastplateocr/rec/modeling/encoders/svtrv2_lnconv.py +503 -0
- fastplateocr/rec/modeling/encoders/svtrv2_lnconv_two33.py +636 -0
- fastplateocr/rec/optimizer/__init__.py +74 -0
- fastplateocr/rec/optimizer/lr.py +276 -0
- fastplateocr/rec/postprocess/__init__.py +26 -0
- fastplateocr/rec/postprocess/ctc_postprocess.py +118 -0
- fastplateocr/rec/preprocess/__init__.py +78 -0
- fastplateocr/rec/preprocess/auto_augment.py +494 -0
- fastplateocr/rec/preprocess/ctc_label_encode.py +124 -0
- fastplateocr/rec/preprocess/parseq_aug.py +150 -0
- fastplateocr/rec/preprocess/rec_aug.py +13 -0
- fastplateocr_py-0.1.0.dist-info/LICENSE +201 -0
- fastplateocr_py-0.1.0.dist-info/METADATA +197 -0
- fastplateocr_py-0.1.0.dist-info/RECORD +34 -0
- fastplateocr_py-0.1.0.dist-info/WHEEL +5 -0
- fastplateocr_py-0.1.0.dist-info/top_level.txt +1 -0
fastplateocr/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# det package
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RepMixerBlock(nn.Module):
|
|
6
|
+
"""
|
|
7
|
+
Macro-architectural Reparameterization (Large Kernel 5x5).
|
|
8
|
+
Replace two consecutive 3x3 layers with a single 5x5 layer.
|
|
9
|
+
During training, use 4 branches (5x5, 3x3, 1x1, Identity)
|
|
10
|
+
to maximize the network's feature extraction capability.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, in_channels, out_channels, deploy=False):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.deploy = deploy
|
|
15
|
+
self.in_channels = in_channels
|
|
16
|
+
self.out_channels = out_channels
|
|
17
|
+
self.act = nn.SiLU(inplace=True)
|
|
18
|
+
|
|
19
|
+
if deploy:
|
|
20
|
+
# At deploy time: Fuse into a single 5x5 matrix
|
|
21
|
+
self.rbr_reparam = nn.Conv2d(in_channels, out_channels, kernel_size=5, padding=2, bias=True)
|
|
22
|
+
else:
|
|
23
|
+
# Branch 1: 5x5 (Ultra-wide receptive field)
|
|
24
|
+
self.rbr_5x5 = nn.Sequential(
|
|
25
|
+
nn.Conv2d(in_channels, out_channels, kernel_size=5, padding=2, bias=False),
|
|
26
|
+
nn.BatchNorm2d(out_channels)
|
|
27
|
+
)
|
|
28
|
+
# Branch 2: 3x3 (Learn local details)
|
|
29
|
+
self.rbr_3x3 = nn.Sequential(
|
|
30
|
+
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, bias=False),
|
|
31
|
+
nn.BatchNorm2d(out_channels)
|
|
32
|
+
)
|
|
33
|
+
# Branch 3: 1x1 (Learn channel cross-talk)
|
|
34
|
+
self.rbr_1x1 = nn.Sequential(
|
|
35
|
+
nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0, bias=False),
|
|
36
|
+
nn.BatchNorm2d(out_channels)
|
|
37
|
+
)
|
|
38
|
+
# Branch 4: Identity (Skip computation)
|
|
39
|
+
if in_channels == out_channels:
|
|
40
|
+
self.rbr_identity = nn.BatchNorm2d(in_channels)
|
|
41
|
+
else:
|
|
42
|
+
self.rbr_identity = None
|
|
43
|
+
|
|
44
|
+
def forward(self, x):
|
|
45
|
+
if self.deploy:
|
|
46
|
+
return self.act(self.rbr_reparam(x))
|
|
47
|
+
|
|
48
|
+
# Thanks to standard padding (2, 1, 0), all 3 conv branches output Tensors with the same H, W.
|
|
49
|
+
out = self.rbr_5x5(x) + self.rbr_3x3(x) + self.rbr_1x1(x)
|
|
50
|
+
if self.rbr_identity is not None:
|
|
51
|
+
out += self.rbr_identity(x)
|
|
52
|
+
return self.act(out)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def setup_monkey_patch_injection():
|
|
56
|
+
"""
|
|
57
|
+
Remove the entire old Bottleneck structure (including cv1 3x3 and cv2 3x3).
|
|
58
|
+
Replace it with a 5x5 RepMixerBlock going straight from input channel (c1) to output channel (c2).
|
|
59
|
+
"""
|
|
60
|
+
import ultralytics.nn.modules.block as block
|
|
61
|
+
|
|
62
|
+
def new_init(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
|
|
63
|
+
# Initialize the base nn.Module class (Discard all old Bottleneck code)
|
|
64
|
+
nn.Module.__init__(self)
|
|
65
|
+
self.c1 = c1
|
|
66
|
+
self.c2 = c2
|
|
67
|
+
# Shortcut only activated if c1 == c2 (keeping YOLO's rule)
|
|
68
|
+
self.add = shortcut and c1 == c2
|
|
69
|
+
|
|
70
|
+
# ARCHITECTURAL BREAKTHROUGH: Remove the channel compression layer (c_)
|
|
71
|
+
# Use exactly 1 multi-branch Large Kernel block.
|
|
72
|
+
self.rep_block = RepMixerBlock(c1, c2, deploy=False)
|
|
73
|
+
|
|
74
|
+
def new_forward(self, x):
|
|
75
|
+
# Straight signal transmission through RepMixerBlock
|
|
76
|
+
return x + self.rep_block(x) if self.add else self.rep_block(x)
|
|
77
|
+
|
|
78
|
+
# Overwrite YOLO architecture
|
|
79
|
+
block.Bottleneck.__init__ = new_init
|
|
80
|
+
block.Bottleneck.forward = new_forward
|
|
81
|
+
print("[INFO] Monkey-Patch successful: Overhauled Bottleneck architecture with multi-branch 5x5 Large Kernel!")
|
fastplateocr/pipeline.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import torch
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
import warnings
|
|
7
|
+
warnings.filterwarnings("ignore")
|
|
8
|
+
|
|
9
|
+
# This ensures that we can import det and rec if running from this file
|
|
10
|
+
__dir__ = os.path.dirname(os.path.abspath(__file__))
|
|
11
|
+
root_dir = os.path.abspath(os.path.join(__dir__, '..'))
|
|
12
|
+
if root_dir not in sys.path:
|
|
13
|
+
sys.path.insert(0, root_dir)
|
|
14
|
+
|
|
15
|
+
from ultralytics import YOLO
|
|
16
|
+
from fastplateocr.det.c3k2.bottleneck.rep_mixer import setup_monkey_patch_injection, RepMixerBlock
|
|
17
|
+
from fastplateocr.rec.modeling import build_model
|
|
18
|
+
from fastplateocr.rec.postprocess import build_post_process
|
|
19
|
+
|
|
20
|
+
# Trick for PyTorch Unpickler: Inject RepMixerBlock into __main__
|
|
21
|
+
import __main__
|
|
22
|
+
if not hasattr(__main__, 'RepMixerBlock'):
|
|
23
|
+
setattr(__main__, 'RepMixerBlock', RepMixerBlock)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def switch_deploy_flag(model):
|
|
27
|
+
"""Recursively convert RepMixer layers in SVTR to deploy mode."""
|
|
28
|
+
for m in model.modules():
|
|
29
|
+
if hasattr(m, 'switch_to_deploy'):
|
|
30
|
+
m.switch_to_deploy()
|
|
31
|
+
return model
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def download_from_hf(filename):
|
|
35
|
+
try:
|
|
36
|
+
from huggingface_hub import hf_hub_download
|
|
37
|
+
print(f"[FastPlateOCR] Downloading/Verifying {filename} from HuggingFace...")
|
|
38
|
+
return hf_hub_download(repo_id="anhone3/FastPlateOCR", filename=filename)
|
|
39
|
+
except ImportError:
|
|
40
|
+
raise ImportError("Please install huggingface_hub to auto-download models: pip install huggingface_hub")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FastPlateOCR:
|
|
44
|
+
def __init__(self,
|
|
45
|
+
use_det=True,
|
|
46
|
+
use_rec=True,
|
|
47
|
+
det_model_path=None,
|
|
48
|
+
rec_model_path=None,
|
|
49
|
+
device=None):
|
|
50
|
+
|
|
51
|
+
self.device = torch.device(device if device else ('cuda:0' if torch.cuda.is_available() else 'cpu'))
|
|
52
|
+
print(f"[FastPlateOCR] Initializing on {self.device}...")
|
|
53
|
+
|
|
54
|
+
self.det_model = None
|
|
55
|
+
self.rec_model = None
|
|
56
|
+
self.post_process_class = None
|
|
57
|
+
|
|
58
|
+
# 1. Initialize DET (YOLO)
|
|
59
|
+
if use_det:
|
|
60
|
+
if det_model_path is None:
|
|
61
|
+
det_model_path = download_from_hf("yolo26n_rep_mixer/best.pt")
|
|
62
|
+
|
|
63
|
+
print(f"[FastPlateOCR] Loading Detection Model: {det_model_path}")
|
|
64
|
+
setup_monkey_patch_injection()
|
|
65
|
+
self.det_model = YOLO(det_model_path)
|
|
66
|
+
self.det_model.to(self.device)
|
|
67
|
+
self.det_model.fuse()
|
|
68
|
+
|
|
69
|
+
# Fuse RepMixerBlock inside YOLO if they exist
|
|
70
|
+
if hasattr(self.det_model, 'model'):
|
|
71
|
+
for m in self.det_model.model.modules():
|
|
72
|
+
if hasattr(m, 'switch_to_deploy'):
|
|
73
|
+
m.switch_to_deploy()
|
|
74
|
+
|
|
75
|
+
# 2. Initialize REC (SVTRv2)
|
|
76
|
+
if use_rec:
|
|
77
|
+
if rec_model_path is None:
|
|
78
|
+
rec_model_path = download_from_hf("svtrv2_tiny_efficient_rctc/best.pth")
|
|
79
|
+
|
|
80
|
+
print(f"[FastPlateOCR] Loading Recognition Model: {rec_model_path}")
|
|
81
|
+
checkpoint = torch.load(rec_model_path, map_location='cpu')
|
|
82
|
+
cfg = checkpoint['config']
|
|
83
|
+
|
|
84
|
+
# Monkey patch the dictionary path
|
|
85
|
+
dict_path = os.path.join(os.path.dirname(__file__), 'license_plate_dict.txt')
|
|
86
|
+
cfg['Global']['character_dict_path'] = dict_path
|
|
87
|
+
|
|
88
|
+
self.post_process_class = build_post_process(cfg['PostProcess'], cfg['Global'])
|
|
89
|
+
cfg['Architecture']['Decoder']['out_channels'] = self.post_process_class.get_character_num()
|
|
90
|
+
|
|
91
|
+
self.rec_model = build_model(cfg['Architecture'])
|
|
92
|
+
self.rec_model.load_state_dict(checkpoint['state_dict'], strict=True)
|
|
93
|
+
self.rec_model.to(self.device)
|
|
94
|
+
self.rec_model.eval()
|
|
95
|
+
self.rec_model = switch_deploy_flag(self.rec_model)
|
|
96
|
+
|
|
97
|
+
if self.det_model or self.rec_model:
|
|
98
|
+
print("[FastPlateOCR] Models loaded successfully!")
|
|
99
|
+
else:
|
|
100
|
+
print("[FastPlateOCR] WARNING: No models loaded. Please provide det_model_path or rec_model_path.")
|
|
101
|
+
|
|
102
|
+
def _preprocess_crop(self, img_crop, target_h=32, target_w=128):
|
|
103
|
+
"""Preprocesses cropped image for SVTR."""
|
|
104
|
+
img = cv2.resize(img_crop, (target_w, target_h))
|
|
105
|
+
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
106
|
+
img = img.astype(np.float32) / 255.0
|
|
107
|
+
img -= 0.5
|
|
108
|
+
img /= 0.5
|
|
109
|
+
img = img.transpose((2, 0, 1))
|
|
110
|
+
img = np.expand_dims(img, axis=0)
|
|
111
|
+
return torch.from_numpy(img).to(self.device)
|
|
112
|
+
|
|
113
|
+
def detect(self, img, conf_thresh=0.25):
|
|
114
|
+
"""
|
|
115
|
+
Detect license plates in an image.
|
|
116
|
+
Returns: list of bounding boxes [[x1, y1, x2, y2], ...]
|
|
117
|
+
"""
|
|
118
|
+
if self.det_model is None:
|
|
119
|
+
raise ValueError("Detection model not loaded! Initialize with det_model_path.")
|
|
120
|
+
|
|
121
|
+
if isinstance(img, str):
|
|
122
|
+
img = cv2.imread(img)
|
|
123
|
+
|
|
124
|
+
det_results = self.det_model(img, verbose=False, conf=conf_thresh)[0]
|
|
125
|
+
boxes = det_results.boxes.data.cpu().numpy() # [x1, y1, x2, y2, conf, cls]
|
|
126
|
+
|
|
127
|
+
results = []
|
|
128
|
+
for box in boxes:
|
|
129
|
+
x1, y1, x2, y2, conf, cls = box
|
|
130
|
+
results.append([int(x1), int(y1), int(x2), int(y2)])
|
|
131
|
+
return results
|
|
132
|
+
|
|
133
|
+
def recognize(self, crop_img):
|
|
134
|
+
"""
|
|
135
|
+
Recognize text in a cropped license plate image.
|
|
136
|
+
Returns: tuple (text, confidence_score)
|
|
137
|
+
"""
|
|
138
|
+
if self.rec_model is None:
|
|
139
|
+
raise ValueError("Recognition model not loaded! Initialize with rec_model_path.")
|
|
140
|
+
|
|
141
|
+
if crop_img.size == 0:
|
|
142
|
+
return "", 0.0
|
|
143
|
+
|
|
144
|
+
tensor = self._preprocess_crop(crop_img)
|
|
145
|
+
with torch.no_grad():
|
|
146
|
+
preds = self.rec_model(tensor)
|
|
147
|
+
|
|
148
|
+
post_result = self.post_process_class(preds)
|
|
149
|
+
text, score = post_result[0]
|
|
150
|
+
return text, float(score)
|
|
151
|
+
|
|
152
|
+
def read(self, image_path, conf_thresh=0.25):
|
|
153
|
+
"""
|
|
154
|
+
End-to-End inference.
|
|
155
|
+
Returns: list of dicts [{'box': [x1,y1,x2,y2], 'text': '51F1234', 'score': 0.99}]
|
|
156
|
+
"""
|
|
157
|
+
if self.det_model is None or self.rec_model is None:
|
|
158
|
+
raise ValueError("End-to-End read() requires BOTH det_model_path and rec_model_path to be loaded.")
|
|
159
|
+
|
|
160
|
+
img = cv2.imread(image_path)
|
|
161
|
+
if img is None:
|
|
162
|
+
raise ValueError(f"Could not read image: {image_path}")
|
|
163
|
+
|
|
164
|
+
boxes = self.detect(img, conf_thresh)
|
|
165
|
+
final_results = []
|
|
166
|
+
|
|
167
|
+
h, w, _ = img.shape
|
|
168
|
+
for box in boxes:
|
|
169
|
+
x1, y1, x2, y2 = box
|
|
170
|
+
|
|
171
|
+
# Ensure bounds
|
|
172
|
+
x1_c, y1_c = max(0, x1), max(0, y1)
|
|
173
|
+
x2_c, y2_c = min(w, x2), min(h, y2)
|
|
174
|
+
|
|
175
|
+
crop_img = img[y1_c:y2_c, x1_c:x2_c]
|
|
176
|
+
|
|
177
|
+
text, score = self.recognize(crop_img)
|
|
178
|
+
|
|
179
|
+
final_results.append({
|
|
180
|
+
'box': box,
|
|
181
|
+
'text': text,
|
|
182
|
+
'score': score
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
return final_results
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
from torch import nn
|
|
4
|
+
|
|
5
|
+
name_to_module = {
|
|
6
|
+
'ABINetLoss': '.abinet_loss',
|
|
7
|
+
'ARLoss': '.ar_loss',
|
|
8
|
+
'CDistNetLoss': '.cdistnet_loss',
|
|
9
|
+
'CELoss': '.ce_loss',
|
|
10
|
+
'CPPDLoss': '.cppd_loss',
|
|
11
|
+
'CTCLoss': '.ctc_loss',
|
|
12
|
+
'IGTRLoss': '.igtr_loss',
|
|
13
|
+
'LISTERLoss': '.lister_loss',
|
|
14
|
+
'LPVLoss': '.lpv_loss',
|
|
15
|
+
'MGPLoss': '.mgp_loss',
|
|
16
|
+
'PARSeqLoss': '.parseq_loss',
|
|
17
|
+
'RobustScannerLoss': '.robustscanner_loss',
|
|
18
|
+
'SEEDLoss': '.seed_loss',
|
|
19
|
+
'SMTRLoss': '.smtr_loss',
|
|
20
|
+
'SRNLoss': '.srn_loss',
|
|
21
|
+
'VisionLANLoss': '.visionlan_loss',
|
|
22
|
+
'CAMLoss': '.cam_loss',
|
|
23
|
+
'MDiffLoss': '.mdiff_loss',
|
|
24
|
+
'UniRecLoss': '.unirec_loss',
|
|
25
|
+
'CMERLoss': '.cmer_loss',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_loss(config):
|
|
30
|
+
config = copy.deepcopy(config)
|
|
31
|
+
module_name = config.pop('name')
|
|
32
|
+
|
|
33
|
+
if module_name in globals():
|
|
34
|
+
module_class = globals()[module_name]
|
|
35
|
+
else:
|
|
36
|
+
assert module_name in name_to_module, Exception(
|
|
37
|
+
'{} is not supported. The losses in {} are supportes'.format(
|
|
38
|
+
module_name, list(name_to_module.keys())))
|
|
39
|
+
module_path = name_to_module[module_name]
|
|
40
|
+
module = import_module(module_path, package=__package__)
|
|
41
|
+
module_class = getattr(module, module_name)
|
|
42
|
+
|
|
43
|
+
return module_class(**config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GTCLoss(nn.Module):
|
|
47
|
+
|
|
48
|
+
def __init__(self,
|
|
49
|
+
gtc_loss,
|
|
50
|
+
gtc_weight=1.0,
|
|
51
|
+
ctc_weight=1.0,
|
|
52
|
+
zero_infinity=True,
|
|
53
|
+
**kwargs):
|
|
54
|
+
super(GTCLoss, self).__init__()
|
|
55
|
+
# Dynamically build CTCLoss
|
|
56
|
+
ctc_config = {'name': 'CTCLoss', 'zero_infinity': zero_infinity}
|
|
57
|
+
self.ctc_loss = build_loss(ctc_config)
|
|
58
|
+
# Build GTC loss
|
|
59
|
+
self.gtc_loss = build_loss(gtc_loss)
|
|
60
|
+
self.gtc_weight = gtc_weight
|
|
61
|
+
self.ctc_weight = ctc_weight
|
|
62
|
+
|
|
63
|
+
def forward(self, predicts, batch):
|
|
64
|
+
ctc_loss = self.ctc_loss(predicts['ctc_pred'],
|
|
65
|
+
[None] + batch[-2:])['loss']
|
|
66
|
+
gtc_loss = self.gtc_loss(predicts['gtc_pred'], batch[:-2])['loss']
|
|
67
|
+
return {
|
|
68
|
+
'loss': self.ctc_weight * ctc_loss + self.gtc_weight * gtc_loss,
|
|
69
|
+
'ctc_loss': ctc_loss,
|
|
70
|
+
'gtc_loss': gtc_loss
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch import nn
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CTCLoss(nn.Module):
|
|
6
|
+
|
|
7
|
+
def __init__(self, use_focal_loss=False, zero_infinity=False, **kwargs):
|
|
8
|
+
super(CTCLoss, self).__init__()
|
|
9
|
+
self.loss_func = nn.CTCLoss(blank=0,
|
|
10
|
+
reduction='none',
|
|
11
|
+
zero_infinity=zero_infinity)
|
|
12
|
+
self.use_focal_loss = use_focal_loss
|
|
13
|
+
|
|
14
|
+
def forward(self, predicts, batch):
|
|
15
|
+
# predicts = predicts['res']
|
|
16
|
+
|
|
17
|
+
batch_size = predicts.size(0)
|
|
18
|
+
label, label_length = batch[1], batch[2]
|
|
19
|
+
predicts = predicts.log_softmax(2)
|
|
20
|
+
predicts = predicts.permute(1, 0, 2)
|
|
21
|
+
preds_lengths = torch.tensor([predicts.size(0)] * batch_size,
|
|
22
|
+
dtype=torch.long)
|
|
23
|
+
loss = self.loss_func(predicts, label, preds_lengths, label_length)
|
|
24
|
+
|
|
25
|
+
if self.use_focal_loss:
|
|
26
|
+
# Use torch.clamp to limit the range of loss, avoiding overflow in exponential calculation
|
|
27
|
+
clamped_loss = torch.clamp(loss, min=-20, max=20)
|
|
28
|
+
weight = 1 - torch.exp(-clamped_loss)
|
|
29
|
+
weight = torch.square(weight)
|
|
30
|
+
# Use torch.where to avoid multiplying by zero weight
|
|
31
|
+
loss = torch.where(weight > 0, loss * weight, loss)
|
|
32
|
+
loss = loss.mean()
|
|
33
|
+
return {'loss': loss}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
__all__ = ['build_metric']
|
|
4
|
+
|
|
5
|
+
support_dict = [
|
|
6
|
+
'RecMetric', 'RecMetricLong', 'RecGTCMetric', 'RecMPGMetric', 'CMERMetric'
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_metric(config):
|
|
11
|
+
config = copy.deepcopy(config)
|
|
12
|
+
module_name = config.pop('name')
|
|
13
|
+
assert module_name in support_dict, Exception(
|
|
14
|
+
'metric only support {}'.format(support_dict))
|
|
15
|
+
|
|
16
|
+
# Lazy import
|
|
17
|
+
if module_name == 'RecMetric':
|
|
18
|
+
from .rec_metric import RecMetric
|
|
19
|
+
module_class = RecMetric(**config)
|
|
20
|
+
elif module_name == 'RecGTCMetric':
|
|
21
|
+
from .rec_metric_gtc import RecGTCMetric
|
|
22
|
+
module_class = RecGTCMetric(**config)
|
|
23
|
+
elif module_name == 'RecMetricLong':
|
|
24
|
+
from .rec_metric_long import RecMetricLong
|
|
25
|
+
module_class = RecMetricLong(**config)
|
|
26
|
+
elif module_name == 'RecMPGMetric':
|
|
27
|
+
from .rec_metric_mgp import RecMPGMetric
|
|
28
|
+
module_class = RecMPGMetric(**config)
|
|
29
|
+
elif module_name == 'CMERMetric':
|
|
30
|
+
from .rec_metric_cmer import CMERMetric
|
|
31
|
+
module_class = CMERMetric(**config)
|
|
32
|
+
|
|
33
|
+
return module_class
|