From aad3093a919f8cedc4fdc660c05d4a05b922bda1 Mon Sep 17 00:00:00 2001 From: WenmuZhou Date: Tue, 13 Oct 2020 17:13:33 +0800 Subject: [PATCH] dygraph first commit --- configs/det/det_db_icdar15_reader.yml | 22 - configs/det/det_east_icdar15_reader.yml | 23 - configs/det/det_mv3_db.yml | 147 +- configs/det/det_mv3_east.yml | 45 - configs/det/det_r50_vd_db.yml | 145 +- configs/det/det_r50_vd_east.yml | 44 - configs/det/det_r50_vd_sast_icdar15.yml | 50 - configs/det/det_r50_vd_sast_totaltext.yml | 50 - configs/det/det_sast_icdar15_reader.yml | 24 - configs/det/det_sast_totaltext_reader.yml | 24 - configs/rec/rec_benchmark_reader.yml | 12 - configs/rec/rec_chinese_common_train.yml | 45 - configs/rec/rec_chinese_lite_train.yml | 46 - configs/rec/rec_chinese_reader.yml | 13 - configs/rec/rec_icdar15_reader.yml | 13 - configs/rec/rec_icdar15_train.yml | 49 - configs/rec/rec_mv3_none_bilstm_ctc.yml | 135 +- configs/rec/rec_mv3_none_bilstm_ctc_lmdb.yml | 106 + configs/rec/rec_mv3_none_none_ctc.yml | 41 - configs/rec/rec_mv3_tps_bilstm_attn.yml | 54 - configs/rec/rec_mv3_tps_bilstm_ctc.yml | 51 - configs/rec/rec_r34_vd_none_bilstm_ctc.yml | 131 +- configs/rec/rec_r34_vd_none_none_ctc.yml | 40 - configs/rec/rec_r34_vd_tps_bilstm_attn.yml | 53 - configs/rec/rec_r34_vd_tps_bilstm_ctc.yml | 50 - configs/rec/rec_r50fpn_vd_none_srn.yml | 49 - ppocr/data/__init__.py | 111 + ppocr/data/dataset.py | 300 ++ ppocr/data/det/data_augment.py | 47 - ppocr/data/det/dataset_traversal.py | 167 - ppocr/data/det/db_process.py | 216 - ppocr/data/det/east_process.py | 537 -- ppocr/data/det/make_border_map.py | 147 - ppocr/data/det/make_shrink_map.py | 88 - ppocr/data/det/random_crop_data.py | 155 - ppocr/data/det/sast_process.py | 781 --- ppocr/data/imaug/__init__.py | 59 + ppocr/data/imaug/iaa_augment.py | 101 + ppocr/data/imaug/label_ops.py | 197 + ppocr/data/imaug/make_border_map.py | 157 + ppocr/data/imaug/make_shrink_map.py | 94 + ppocr/data/imaug/operators.py | 185 + ppocr/data/imaug/random_crop_data.py | 210 + .../img_tools.py => imaug/rec_img_aug.py} | 260 +- .../{rec => imaug/text_image_aug}/__init__.py | 8 +- ppocr/data/imaug/text_image_aug/augment.py | 116 + ppocr/data/imaug/text_image_aug/warp_mls.py | 164 + ppocr/data/reader_main.py | 77 - ppocr/data/rec/dataset_traversal.py | 335 -- ppocr/metrics/DetMetric.py | 72 + ppocr/metrics/RecMetric.py | 59 + ppocr/metrics/__init__.py | 36 + .../metrics}/eval_det_iou.py | 8 +- ppocr/modeling/__init__.py | 13 + ppocr/modeling/architectures/__init__.py | 3 + ppocr/modeling/architectures/det_model.py | 146 - ppocr/modeling/architectures/model.py | 129 + ppocr/modeling/architectures/rec_model.py | 228 - ppocr/modeling/backbones/__init__.py | 23 + ppocr/modeling/backbones/det_mobilenet_v3.py | 377 +- ppocr/modeling/backbones/det_resnet_vd.py | 451 +- .../modeling/backbones/det_resnet_vd_sast.py | 274 - ppocr/modeling/backbones/rec_mobilenet_v3.py | 283 +- ppocr/modeling/backbones/rec_resnet_fpn.py | 246 - ppocr/modeling/backbones/rec_resnet_vd.py | 447 +- ppocr/modeling/common_functions.py | 95 - ppocr/modeling/heads/__init__.py | 17 + ppocr/modeling/heads/det_db_head.py | 279 +- ppocr/modeling/heads/det_east_head.py | 117 - ppocr/modeling/heads/det_sast_head.py | 228 - ppocr/modeling/heads/rec_attention_head.py | 237 - ppocr/modeling/heads/rec_ctc_head.py | 77 +- ppocr/modeling/heads/rec_seq_encoder.py | 100 - ppocr/modeling/heads/rec_srn_all_head.py | 230 - .../modeling/heads/self_attention/__init__.py | 0 ppocr/modeling/heads/self_attention/model.py | 485 -- ppocr/modeling/losses/__init__.py | 19 + ppocr/modeling/losses/det_basic_loss.py | 296 +- ppocr/modeling/losses/det_db_loss.py | 89 +- ppocr/modeling/losses/det_east_loss.py | 61 - ppocr/modeling/losses/det_sast_loss.py | 115 - ppocr/modeling/losses/rec_attention_loss.py | 38 - ppocr/modeling/losses/rec_ctc_loss.py | 44 +- ppocr/modeling/losses/rec_srn_loss.py | 55 - .../modeling/necks}/__init__.py | 14 + ppocr/modeling/necks/fpn.py | 113 + ppocr/modeling/necks/rnn.py | 143 + ppocr/modeling/stns/tps.py | 261 - .../modeling/{stns => transform}/__init__.py | 12 + ppocr/optimizer.py | 155 - ppocr/optimizer/__init__.py | 56 + ppocr/optimizer/learning_rate.py | 183 + ppocr/optimizer/optimizer.py | 119 + ppocr/optimizer/regularizer.py | 54 + ppocr/postprocess/__init__.py | 38 + ppocr/postprocess/db_postprocess.py | 55 +- ppocr/postprocess/db_postprocess_torch.py | 133 + ppocr/postprocess/east_postprocess.py | 136 - ppocr/postprocess/lanms/.gitignore | 1 - ppocr/postprocess/lanms/.ycm_extra_conf.py | 140 - ppocr/postprocess/lanms/Makefile | 13 - ppocr/postprocess/lanms/__init__.py | 20 - ppocr/postprocess/lanms/__main__.py | 10 - ppocr/postprocess/lanms/adaptor.cpp | 61 - .../lanms/include/clipper/clipper.cpp | 4622 ----------------- .../lanms/include/clipper/clipper.hpp | 404 -- .../postprocess/lanms/include/pybind11/attr.h | 471 -- .../lanms/include/pybind11/buffer_info.h | 108 - .../postprocess/lanms/include/pybind11/cast.h | 2058 -------- .../lanms/include/pybind11/chrono.h | 162 - .../lanms/include/pybind11/class_support.h | 603 --- .../lanms/include/pybind11/common.h | 857 --- .../lanms/include/pybind11/complex.h | 61 - .../lanms/include/pybind11/descr.h | 185 - .../lanms/include/pybind11/eigen.h | 610 --- .../lanms/include/pybind11/embed.h | 194 - .../postprocess/lanms/include/pybind11/eval.h | 117 - .../lanms/include/pybind11/functional.h | 85 - .../lanms/include/pybind11/numpy.h | 1598 ------ .../lanms/include/pybind11/operators.h | 167 - .../lanms/include/pybind11/options.h | 65 - .../lanms/include/pybind11/pybind11.h | 1869 ------- .../lanms/include/pybind11/pytypes.h | 1318 ----- .../postprocess/lanms/include/pybind11/stl.h | 367 -- .../lanms/include/pybind11/stl_bind.h | 585 --- .../lanms/include/pybind11/typeid.h | 53 - ppocr/postprocess/lanms/lanms.h | 234 - ppocr/postprocess/locality_aware_nms.py | 199 - ppocr/postprocess/rec_postprocess.py | 174 + ppocr/postprocess/sast_postprocess.py | 289 -- ppocr/utils/check.py | 2 - ppocr/utils/logging.py | 67 + ppocr/utils/save_load.py | 197 +- ppocr/utils/stats.py | 8 +- ppocr/utils/utility.py | 67 +- requirments.txt => requirements.txt | 4 +- setup.py | 2 +- tools/eval.py | 94 +- tools/eval_utils/eval_det_utils.py | 149 - tools/eval_utils/eval_rec_utils.py | 150 - tools/export_model.py | 75 - tools/infer/predict_det.py | 112 +- tools/infer/utility.py | 62 +- tools/infer_det.py | 169 +- tools/infer_rec.py | 204 +- tools/program.py | 450 +- tools/train.py | 175 +- train.sh | 1 + 148 files changed, 5638 insertions(+), 26873 deletions(-) delete mode 100755 configs/det/det_db_icdar15_reader.yml delete mode 100755 configs/det/det_east_icdar15_reader.yml mode change 100755 => 100644 configs/det/det_mv3_db.yml delete mode 100755 configs/det/det_mv3_east.yml mode change 100755 => 100644 configs/det/det_r50_vd_db.yml delete mode 100755 configs/det/det_r50_vd_east.yml delete mode 100644 configs/det/det_r50_vd_sast_icdar15.yml delete mode 100644 configs/det/det_r50_vd_sast_totaltext.yml delete mode 100644 configs/det/det_sast_icdar15_reader.yml delete mode 100644 configs/det/det_sast_totaltext_reader.yml delete mode 100755 configs/rec/rec_benchmark_reader.yml delete mode 100644 configs/rec/rec_chinese_common_train.yml delete mode 100755 configs/rec/rec_chinese_lite_train.yml delete mode 100755 configs/rec/rec_chinese_reader.yml delete mode 100755 configs/rec/rec_icdar15_reader.yml delete mode 100755 configs/rec/rec_icdar15_train.yml mode change 100755 => 100644 configs/rec/rec_mv3_none_bilstm_ctc.yml create mode 100644 configs/rec/rec_mv3_none_bilstm_ctc_lmdb.yml delete mode 100755 configs/rec/rec_mv3_none_none_ctc.yml delete mode 100755 configs/rec/rec_mv3_tps_bilstm_attn.yml delete mode 100755 configs/rec/rec_mv3_tps_bilstm_ctc.yml mode change 100755 => 100644 configs/rec/rec_r34_vd_none_bilstm_ctc.yml delete mode 100755 configs/rec/rec_r34_vd_none_none_ctc.yml delete mode 100755 configs/rec/rec_r34_vd_tps_bilstm_attn.yml delete mode 100755 configs/rec/rec_r34_vd_tps_bilstm_ctc.yml delete mode 100755 configs/rec/rec_r50fpn_vd_none_srn.yml mode change 100755 => 100644 ppocr/data/__init__.py create mode 100644 ppocr/data/dataset.py delete mode 100644 ppocr/data/det/data_augment.py delete mode 100644 ppocr/data/det/dataset_traversal.py delete mode 100644 ppocr/data/det/db_process.py delete mode 100755 ppocr/data/det/east_process.py delete mode 100644 ppocr/data/det/make_border_map.py delete mode 100644 ppocr/data/det/make_shrink_map.py delete mode 100644 ppocr/data/det/random_crop_data.py delete mode 100644 ppocr/data/det/sast_process.py create mode 100644 ppocr/data/imaug/__init__.py create mode 100644 ppocr/data/imaug/iaa_augment.py create mode 100644 ppocr/data/imaug/label_ops.py create mode 100644 ppocr/data/imaug/make_border_map.py create mode 100644 ppocr/data/imaug/make_shrink_map.py create mode 100644 ppocr/data/imaug/operators.py create mode 100644 ppocr/data/imaug/random_crop_data.py rename ppocr/data/{rec/img_tools.py => imaug/rec_img_aug.py} (52%) mode change 100755 => 100644 rename ppocr/data/{rec => imaug/text_image_aug}/__init__.py (67%) mode change 100755 => 100644 create mode 100644 ppocr/data/imaug/text_image_aug/augment.py create mode 100644 ppocr/data/imaug/text_image_aug/warp_mls.py delete mode 100755 ppocr/data/reader_main.py delete mode 100755 ppocr/data/rec/dataset_traversal.py create mode 100644 ppocr/metrics/DetMetric.py create mode 100644 ppocr/metrics/RecMetric.py create mode 100644 ppocr/metrics/__init__.py rename {tools/eval_utils => ppocr/metrics}/eval_det_iou.py (97%) delete mode 100644 ppocr/modeling/architectures/det_model.py create mode 100644 ppocr/modeling/architectures/model.py delete mode 100755 ppocr/modeling/architectures/rec_model.py mode change 100755 => 100644 ppocr/modeling/backbones/det_resnet_vd.py delete mode 100644 ppocr/modeling/backbones/det_resnet_vd_sast.py mode change 100755 => 100644 ppocr/modeling/backbones/rec_mobilenet_v3.py delete mode 100755 ppocr/modeling/backbones/rec_resnet_fpn.py mode change 100755 => 100644 ppocr/modeling/backbones/rec_resnet_vd.py delete mode 100755 ppocr/modeling/common_functions.py delete mode 100755 ppocr/modeling/heads/det_east_head.py delete mode 100644 ppocr/modeling/heads/det_sast_head.py delete mode 100755 ppocr/modeling/heads/rec_attention_head.py delete mode 100755 ppocr/modeling/heads/rec_seq_encoder.py delete mode 100755 ppocr/modeling/heads/rec_srn_all_head.py delete mode 100644 ppocr/modeling/heads/self_attention/__init__.py delete mode 100644 ppocr/modeling/heads/self_attention/model.py delete mode 100755 ppocr/modeling/losses/det_east_loss.py delete mode 100644 ppocr/modeling/losses/det_sast_loss.py delete mode 100755 ppocr/modeling/losses/rec_attention_loss.py delete mode 100755 ppocr/modeling/losses/rec_srn_loss.py rename {tools/eval_utils => ppocr/modeling/necks}/__init__.py (62%) create mode 100644 ppocr/modeling/necks/fpn.py create mode 100644 ppocr/modeling/necks/rnn.py delete mode 100755 ppocr/modeling/stns/tps.py rename ppocr/modeling/{stns => transform}/__init__.py (66%) delete mode 100644 ppocr/optimizer.py create mode 100644 ppocr/optimizer/__init__.py create mode 100644 ppocr/optimizer/learning_rate.py create mode 100644 ppocr/optimizer/optimizer.py create mode 100644 ppocr/optimizer/regularizer.py create mode 100644 ppocr/postprocess/__init__.py create mode 100644 ppocr/postprocess/db_postprocess_torch.py delete mode 100755 ppocr/postprocess/east_postprocess.py delete mode 100644 ppocr/postprocess/lanms/.gitignore delete mode 100644 ppocr/postprocess/lanms/.ycm_extra_conf.py delete mode 100644 ppocr/postprocess/lanms/Makefile delete mode 100644 ppocr/postprocess/lanms/__init__.py delete mode 100644 ppocr/postprocess/lanms/__main__.py delete mode 100644 ppocr/postprocess/lanms/adaptor.cpp delete mode 100644 ppocr/postprocess/lanms/include/clipper/clipper.cpp delete mode 100644 ppocr/postprocess/lanms/include/clipper/clipper.hpp delete mode 100644 ppocr/postprocess/lanms/include/pybind11/attr.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/buffer_info.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/cast.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/chrono.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/class_support.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/common.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/complex.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/descr.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/eigen.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/embed.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/eval.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/functional.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/numpy.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/operators.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/options.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/pybind11.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/pytypes.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/stl.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/stl_bind.h delete mode 100644 ppocr/postprocess/lanms/include/pybind11/typeid.h delete mode 100644 ppocr/postprocess/lanms/lanms.h delete mode 100644 ppocr/postprocess/locality_aware_nms.py create mode 100644 ppocr/postprocess/rec_postprocess.py delete mode 100644 ppocr/postprocess/sast_postprocess.py create mode 100644 ppocr/utils/logging.py rename requirments.txt => requirements.txt (51%) delete mode 100644 tools/eval_utils/eval_det_utils.py delete mode 100644 tools/eval_utils/eval_rec_utils.py delete mode 100644 tools/export_model.py create mode 100644 train.sh diff --git a/configs/det/det_db_icdar15_reader.yml b/configs/det/det_db_icdar15_reader.yml deleted file mode 100755 index 0f99257b..00000000 --- a/configs/det/det_db_icdar15_reader.yml +++ /dev/null @@ -1,22 +0,0 @@ -TrainReader: - reader_function: ppocr.data.det.dataset_traversal,TrainReader - process_function: ppocr.data.det.db_process,DBProcessTrain - num_workers: 8 - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/train_icdar2015_label.txt - -EvalReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.db_process,DBProcessTest - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/test_icdar2015_label.txt - test_image_shape: [736, 1280] - -TestReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.db_process,DBProcessTest - infer_img: - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/test_icdar2015_label.txt - test_image_shape: [736, 1280] - do_eval: True diff --git a/configs/det/det_east_icdar15_reader.yml b/configs/det/det_east_icdar15_reader.yml deleted file mode 100755 index 060ed4dd..00000000 --- a/configs/det/det_east_icdar15_reader.yml +++ /dev/null @@ -1,23 +0,0 @@ -TrainReader: - reader_function: ppocr.data.det.dataset_traversal,TrainReader - process_function: ppocr.data.det.east_process,EASTProcessTrain - num_workers: 8 - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/train_icdar2015_label.txt - background_ratio: 0.125 - min_crop_side_ratio: 0.1 - min_text_size: 10 - -EvalReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.east_process,EASTProcessTest - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/test_icdar2015_label.txt - -TestReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.east_process,EASTProcessTest - infer_img: - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/test_icdar2015_label.txt - do_eval: True diff --git a/configs/det/det_mv3_db.yml b/configs/det/det_mv3_db.yml old mode 100755 new mode 100644 index caa7bd4f..8af845db --- a/configs/det/det_mv3_db.yml +++ b/configs/det/det_mv3_db.yml @@ -1,54 +1,133 @@ Global: - algorithm: DB use_gpu: true epoch_num: 1200 log_smooth_window: 20 print_batch_step: 2 - save_model_dir: ./output/det_db/ - save_epoch_step: 200 + save_model_dir: ./output/20201010/ + save_epoch_step: 1200 # evaluation is run every 5000 iterations after the 4000th iteration - eval_batch_step: [4000, 5000] - train_batch_size_per_card: 16 - test_batch_size_per_card: 16 - image_shape: [3, 640, 640] - reader_yml: ./configs/det/det_db_icdar15_reader.yml - pretrain_weights: ./pretrain_models/MobileNetV3_large_x0_5_pretrained/ - checkpoints: - save_res_path: ./output/det_db/predicts_db.txt + eval_batch_step: 8 + # if pretrained_model is saved in static mode, load_static_weights must set to True + load_static_weights: True + cal_metric_during_train: False + pretrained_model: /home/zhoujun20/pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: #./output/det_db_0.001_DiceLoss_256_pp_config_2.0b_4gpu/best_accuracy save_inference_dir: - -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel + use_visualdl: True + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt -Backbone: - function: ppocr.modeling.backbones.det_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + learning_rate: +# name: Cosine + lr: 0.001 +# warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 -Head: - function: ppocr.modeling.heads.det_db_head,DBHead - model_name: large - k: 50 - inner_channels: 96 - out_channels: 2 +Architecture: + type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: FPN + out_channels: 256 + Head: + name: DBHead + k: 50 Loss: - function: ppocr.modeling.losses.det_db_loss,DBLoss + name: DBLoss balance_loss: true main_loss_type: DiceLoss alpha: 5 beta: 10 ohem_ratio: 3 -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 - PostProcess: - function: ppocr.postprocess.db_postprocess,DBPostProcess + name: DBPostProcess thresh: 0.3 - box_thresh: 0.7 + box_thresh: 0.6 max_candidates: 1000 - unclip_ratio: 2.0 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +TRAIN: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/detection/ + file_list: + - /home/zhoujun20/detection/train_icdar2015_label.txt # dataset1 + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10,10 ] } } + - { 'type': Resize,'args': { 'size': [ 0.5,3 ] } } + - EastRandomCropData: + size: [ 640,640 ] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - keepKeys: + keep_keys: ['image','threshold_map','threshold_mask','shrink_map','shrink_mask'] # dataloader将按照此顺序返回list + loader: + shuffle: True + drop_last: False + batch_size: 16 + num_workers: 6 + +EVAL: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/detection/ + file_list: + - /home/zhoujun20/detection/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736,1280] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - keepKeys: + keep_keys: ['image','shape','polys','ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size: 1 # must be 1 + num_workers: 6 \ No newline at end of file diff --git a/configs/det/det_mv3_east.yml b/configs/det/det_mv3_east.yml deleted file mode 100755 index 67b82fff..00000000 --- a/configs/det/det_mv3_east.yml +++ /dev/null @@ -1,45 +0,0 @@ -Global: - algorithm: EAST - use_gpu: true - epoch_num: 100000 - log_smooth_window: 20 - print_batch_step: 5 - save_model_dir: ./output/det_east/ - save_epoch_step: 200 - eval_batch_step: [5000, 5000] - train_batch_size_per_card: 16 - test_batch_size_per_card: 16 - image_shape: [3, 512, 512] - reader_yml: ./configs/det/det_east_icdar15_reader.yml - pretrain_weights: ./pretrain_models/MobileNetV3_large_x0_5_pretrained/ - checkpoints: - save_res_path: ./output/det_east/predicts_east.txt - save_inference_dir: - -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel - -Backbone: - function: ppocr.modeling.backbones.det_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.det_east_head,EASTHead - model_name: small - -Loss: - function: ppocr.modeling.losses.det_east_loss,EASTLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 - -PostProcess: - function: ppocr.postprocess.east_postprocess,EASTPostPocess - score_thresh: 0.8 - cover_thresh: 0.1 - nms_thresh: 0.2 - diff --git a/configs/det/det_r50_vd_db.yml b/configs/det/det_r50_vd_db.yml old mode 100755 new mode 100644 index 9a3b77e7..13a25132 --- a/configs/det/det_r50_vd_db.yml +++ b/configs/det/det_r50_vd_db.yml @@ -1,53 +1,132 @@ Global: - algorithm: DB use_gpu: true epoch_num: 1200 log_smooth_window: 20 print_batch_step: 2 - save_model_dir: ./output/det_db/ - save_epoch_step: 200 - eval_batch_step: [5000, 5000] - train_batch_size_per_card: 8 - test_batch_size_per_card: 16 - image_shape: [3, 640, 640] - reader_yml: ./configs/det/det_db_icdar15_reader.yml - pretrain_weights: ./pretrain_models/ResNet50_vd_ssld_pretrained/ - save_res_path: ./output/det_db/predicts_db.txt - checkpoints: + save_model_dir: ./output/20201010/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: 8 + # if pretrained_model is saved in static mode, load_static_weights must set to True + load_static_weights: True + cal_metric_during_train: False + pretrained_model: /home/zhoujun20/pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: #./output/det_db_0.001_DiceLoss_256_pp_config_2.0b_4gpu/best_accuracy save_inference_dir: + use_visualdl: True + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel - -Backbone: - function: ppocr.modeling.backbones.det_resnet_vd,ResNet - layers: 50 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + learning_rate: +# name: Cosine + lr: 0.001 +# warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 -Head: - function: ppocr.modeling.heads.det_db_head,DBHead - model_name: large - k: 50 - inner_channels: 256 - out_channels: 2 +Architecture: + type: det + algorithm: DB + Transform: + Backbone: + name: ResNet + layers: 50 + Neck: + name: FPN + out_channels: 256 + Head: + name: DBHead + k: 50 Loss: - function: ppocr.modeling.losses.det_db_loss,DBLoss + name: DBLoss balance_loss: true main_loss_type: DiceLoss alpha: 5 beta: 10 ohem_ratio: 3 -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 - PostProcess: - function: ppocr.postprocess.db_postprocess,DBPostProcess + name: DBPostProcess thresh: 0.3 - box_thresh: 0.7 + box_thresh: 0.6 max_candidates: 1000 unclip_ratio: 1.5 - + +Metric: + name: DetMetric + main_indicator: hmean + +TRAIN: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/detection/ + file_list: + - /home/zhoujun20/detection/train_icdar2015_label.txt # dataset1 + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10,10 ] } } + - { 'type': Resize,'args': { 'size': [ 0.5,3 ] } } + - EastRandomCropData: + size: [ 640,640 ] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - keepKeys: + keep_keys: ['image','threshold_map','threshold_mask','shrink_map','shrink_mask'] # dataloader将按照此顺序返回list + loader: + shuffle: True + drop_last: False + batch_size: 16 + num_workers: 6 + +EVAL: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/detection/ + file_list: + - /home/zhoujun20/detection/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736,1280] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - keepKeys: + keep_keys: ['image','shape','polys','ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size: 1 # must be 1 + num_workers: 6 \ No newline at end of file diff --git a/configs/det/det_r50_vd_east.yml b/configs/det/det_r50_vd_east.yml deleted file mode 100755 index 8d868199..00000000 --- a/configs/det/det_r50_vd_east.yml +++ /dev/null @@ -1,44 +0,0 @@ -Global: - algorithm: EAST - use_gpu: true - epoch_num: 100000 - log_smooth_window: 20 - print_batch_step: 5 - save_model_dir: ./output/det_east/ - save_epoch_step: 200 - eval_batch_step: [5000, 5000] - train_batch_size_per_card: 8 - test_batch_size_per_card: 16 - image_shape: [3, 512, 512] - reader_yml: ./configs/det/det_east_icdar15_reader.yml - pretrain_weights: ./pretrain_models/ResNet50_vd_ssld_pretrained/ - save_res_path: ./output/det_east/predicts_east.txt - checkpoints: - save_inference_dir: - -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel - -Backbone: - function: ppocr.modeling.backbones.det_resnet_vd,ResNet - layers: 50 - -Head: - function: ppocr.modeling.heads.det_east_head,EASTHead - model_name: large - -Loss: - function: ppocr.modeling.losses.det_east_loss,EASTLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 - -PostProcess: - function: ppocr.postprocess.east_postprocess,EASTPostPocess - score_thresh: 0.8 - cover_thresh: 0.1 - nms_thresh: 0.2 - diff --git a/configs/det/det_r50_vd_sast_icdar15.yml b/configs/det/det_r50_vd_sast_icdar15.yml deleted file mode 100644 index f1ecd61d..00000000 --- a/configs/det/det_r50_vd_sast_icdar15.yml +++ /dev/null @@ -1,50 +0,0 @@ -Global: - algorithm: SAST - use_gpu: true - epoch_num: 2000 - log_smooth_window: 20 - print_batch_step: 2 - save_model_dir: ./output/det_sast/ - save_epoch_step: 20 - eval_batch_step: 5000 - train_batch_size_per_card: 8 - test_batch_size_per_card: 8 - image_shape: [3, 512, 512] - reader_yml: ./configs/det/det_sast_icdar15_reader.yml - pretrain_weights: ./pretrain_models/ResNet50_vd_ssld_pretrained/ - save_res_path: ./output/det_sast/predicts_sast.txt - checkpoints: - save_inference_dir: - -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel - -Backbone: - function: ppocr.modeling.backbones.det_resnet_vd_sast,ResNet - layers: 50 - -Head: - function: ppocr.modeling.heads.det_sast_head,SASTHead - model_name: large - only_fpn_up: False -# with_cab: False - with_cab: True - -Loss: - function: ppocr.modeling.losses.det_sast_loss,SASTLoss - -Optimizer: - function: ppocr.optimizer,RMSProp - base_lr: 0.001 - decay: - function: piecewise_decay - boundaries: [30000, 50000, 80000, 100000, 150000] - decay_rate: 0.3 - -PostProcess: - function: ppocr.postprocess.sast_postprocess,SASTPostProcess - score_thresh: 0.5 - sample_pts_num: 2 - nms_thresh: 0.2 - expand_scale: 1.0 - shrink_ratio_of_width: 0.3 \ No newline at end of file diff --git a/configs/det/det_r50_vd_sast_totaltext.yml b/configs/det/det_r50_vd_sast_totaltext.yml deleted file mode 100644 index ec42ce6d..00000000 --- a/configs/det/det_r50_vd_sast_totaltext.yml +++ /dev/null @@ -1,50 +0,0 @@ -Global: - algorithm: SAST - use_gpu: true - epoch_num: 2000 - log_smooth_window: 20 - print_batch_step: 2 - save_model_dir: ./output/det_sast/ - save_epoch_step: 20 - eval_batch_step: 5000 - train_batch_size_per_card: 8 - test_batch_size_per_card: 1 - image_shape: [3, 512, 512] - reader_yml: ./configs/det/det_sast_totaltext_reader.yml - pretrain_weights: ./pretrain_models/ResNet50_vd_ssld_pretrained/ - save_res_path: ./output/det_sast/predicts_sast.txt - checkpoints: - save_inference_dir: - -Architecture: - function: ppocr.modeling.architectures.det_model,DetModel - -Backbone: - function: ppocr.modeling.backbones.det_resnet_vd_sast,ResNet - layers: 50 - -Head: - function: ppocr.modeling.heads.det_sast_head,SASTHead - model_name: large - only_fpn_up: False - # with_cab: False - with_cab: True - -Loss: - function: ppocr.modeling.losses.det_sast_loss,SASTLoss - -Optimizer: - function: ppocr.optimizer,RMSProp - base_lr: 0.001 - decay: - function: piecewise_decay - boundaries: [30000, 50000, 80000, 100000, 150000] - decay_rate: 0.3 - -PostProcess: - function: ppocr.postprocess.sast_postprocess,SASTPostProcess - score_thresh: 0.5 - sample_pts_num: 6 - nms_thresh: 0.2 - expand_scale: 1.2 - shrink_ratio_of_width: 0.2 \ No newline at end of file diff --git a/configs/det/det_sast_icdar15_reader.yml b/configs/det/det_sast_icdar15_reader.yml deleted file mode 100644 index ee45a85d..00000000 --- a/configs/det/det_sast_icdar15_reader.yml +++ /dev/null @@ -1,24 +0,0 @@ -TrainReader: - reader_function: ppocr.data.det.dataset_traversal,TrainReader - process_function: ppocr.data.det.sast_process,SASTProcessTrain - num_workers: 8 - img_set_dir: ./train_data/ - label_file_path: [./train_data/icdar2013/train_label_json.txt, ./train_data/icdar2015/train_label_json.txt, ./train_data/icdar17_mlt_latin/train_label_json.txt, ./train_data/coco_text_icdar_4pts/train_label_json.txt] - data_ratio_list: [0.1, 0.45, 0.3, 0.15] - min_crop_side_ratio: 0.3 - min_crop_size: 24 - min_text_size: 4 - max_text_size: 512 - -EvalReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.sast_process,SASTProcessTest - img_set_dir: ./train_data/icdar2015/text_localization/ - label_file_path: ./train_data/icdar2015/text_localization/test_icdar2015_label.txt - max_side_len: 1536 - -TestReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.sast_process,SASTProcessTest - infer_img: ./train_data/icdar2015/text_localization/ch4_test_images/img_11.jpg - max_side_len: 1536 diff --git a/configs/det/det_sast_totaltext_reader.yml b/configs/det/det_sast_totaltext_reader.yml deleted file mode 100644 index 92503d9f..00000000 --- a/configs/det/det_sast_totaltext_reader.yml +++ /dev/null @@ -1,24 +0,0 @@ -TrainReader: - reader_function: ppocr.data.det.dataset_traversal,TrainReader - process_function: ppocr.data.det.sast_process,SASTProcessTrain - num_workers: 8 - img_set_dir: ./train_data/ - label_file_path: [./train_data/art_latin_icdar_14pt/train_no_tt_test/train_label_json.txt, ./train_data/total_text_icdar_14pt/train_label_json.txt] - data_ratio_list: [0.5, 0.5] - min_crop_side_ratio: 0.3 - min_crop_size: 24 - min_text_size: 4 - max_text_size: 512 - -EvalReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.sast_process,SASTProcessTest - img_set_dir: ./train_data/ - label_file_path: ./train_data/total_text_icdar_14pt/test_label_json.txt - max_side_len: 768 - -TestReader: - reader_function: ppocr.data.det.dataset_traversal,EvalTestReader - process_function: ppocr.data.det.sast_process,SASTProcessTest - infer_img: ./train_data/afs/total_text/Images/Test/img623.jpg - max_side_len: 768 diff --git a/configs/rec/rec_benchmark_reader.yml b/configs/rec/rec_benchmark_reader.yml deleted file mode 100755 index 524f2f68..00000000 --- a/configs/rec/rec_benchmark_reader.yml +++ /dev/null @@ -1,12 +0,0 @@ -TrainReader: - reader_function: ppocr.data.rec.dataset_traversal,LMDBReader - num_workers: 8 - lmdb_sets_dir: ./train_data/data_lmdb_release/training/ - -EvalReader: - reader_function: ppocr.data.rec.dataset_traversal,LMDBReader - lmdb_sets_dir: ./train_data/data_lmdb_release/validation/ - -TestReader: - reader_function: ppocr.data.rec.dataset_traversal,LMDBReader - lmdb_sets_dir: ./train_data/data_lmdb_release/evaluation/ diff --git a/configs/rec/rec_chinese_common_train.yml b/configs/rec/rec_chinese_common_train.yml deleted file mode 100644 index 0d897459..00000000 --- a/configs/rec/rec_chinese_common_train.yml +++ /dev/null @@ -1,45 +0,0 @@ -Global: - algorithm: CRNN - use_gpu: true - epoch_num: 3000 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: ./output/rec_CRNN - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 128 - test_batch_size_per_card: 128 - image_shape: [3, 32, 320] - max_text_length: 25 - character_type: ch - character_dict_path: ./ppocr/utils/ppocr_keys_v1.txt - loss_type: ctc - distort: false - use_space_char: false - reader_yml: ./configs/rec/rec_chinese_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_vd,ResNet - layers: 34 - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 256 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.0005 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_chinese_lite_train.yml b/configs/rec/rec_chinese_lite_train.yml deleted file mode 100755 index 95a39a3b..00000000 --- a/configs/rec/rec_chinese_lite_train.yml +++ /dev/null @@ -1,46 +0,0 @@ -Global: - algorithm: CRNN - use_gpu: true - epoch_num: 3000 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: ./output/rec_CRNN - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 320] - max_text_length: 25 - character_type: ch - character_dict_path: ./ppocr/utils/ppocr_keys_v1.txt - loss_type: ctc - distort: false - use_space_char: false - reader_yml: ./configs/rec/rec_chinese_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: small - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 48 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.0005 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_chinese_reader.yml b/configs/rec/rec_chinese_reader.yml deleted file mode 100755 index a44efd99..00000000 --- a/configs/rec/rec_chinese_reader.yml +++ /dev/null @@ -1,13 +0,0 @@ -TrainReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader - num_workers: 8 - img_set_dir: ./train_data - label_file_path: ./train_data/rec_gt_train.txt - -EvalReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader - img_set_dir: ./train_data - label_file_path: ./train_data/rec_gt_test.txt - -TestReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader diff --git a/configs/rec/rec_icdar15_reader.yml b/configs/rec/rec_icdar15_reader.yml deleted file mode 100755 index 322d5f25..00000000 --- a/configs/rec/rec_icdar15_reader.yml +++ /dev/null @@ -1,13 +0,0 @@ -TrainReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader - num_workers: 8 - img_set_dir: ./train_data/ic15_data - label_file_path: ./train_data/ic15_data/rec_gt_train.txt - -EvalReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader - img_set_dir: ./train_data/ic15_data - label_file_path: ./train_data/ic15_data/rec_gt_test.txt - -TestReader: - reader_function: ppocr.data.rec.dataset_traversal,SimpleReader diff --git a/configs/rec/rec_icdar15_train.yml b/configs/rec/rec_icdar15_train.yml deleted file mode 100755 index 98a38e74..00000000 --- a/configs/rec/rec_icdar15_train.yml +++ /dev/null @@ -1,49 +0,0 @@ -Global: - algorithm: CRNN - use_gpu: true - epoch_num: 1000 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: ./output/rec_CRNN - save_epoch_step: 300 - eval_batch_step: 500 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - distort: true - debug: false - reader_yml: ./configs/rec/rec_icdar15_reader.yml - pretrain_weights: ./pretrain_models/rec_mv3_none_bilstm_ctc/best_accuracy - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 96 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.0005 - beta1: 0.9 - beta2: 0.999 - decay: - function: cosine_decay - step_each_epoch: 20 - total_epoch: 1000 diff --git a/configs/rec/rec_mv3_none_bilstm_ctc.yml b/configs/rec/rec_mv3_none_bilstm_ctc.yml old mode 100755 new mode 100644 index d2e096fb..3541a9d7 --- a/configs/rec/rec_mv3_none_bilstm_ctc.yml +++ b/configs/rec/rec_mv3_none_bilstm_ctc.yml @@ -1,43 +1,108 @@ Global: - algorithm: CRNN - use_gpu: true - epoch_num: 72 + use_gpu: false + epoch_num: 500 log_smooth_window: 20 print_batch_step: 10 - save_model_dir: output/rec_CRNN - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: + save_model_dir: ./output/rec/test/ + save_epoch_step: 500 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: 127 + # if pretrained_model is saved in static mode, load_static_weights must set to True + load_static_weights: True + cal_metric_during_train: True + pretrained_model: + checkpoints: #output/rec/rec_crnn/best_accuracy save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 96 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + max_text_length: 80 + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + character_type: 'ch' + use_space_char: False + infer_mode: False + use_tps: False + Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 + name: Adam beta1: 0.9 beta2: 0.999 + learning_rate: + name: Cosine + lr: 0.001 + warmup_epoch: 4 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [ 1, 2, 2, 2 ] + Neck: + name: SequenceEncoder + encoder_type: fc + hidden_size: 96 + Head: + name: CTC + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +TRAIN: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/rec + file_list: + - /home/zhoujun20/rec/real_data.txt # dataset1 + ratio_list: [ 0.4,0.6 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecAug: + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + batch_size: 256 + shuffle: True + drop_last: True + num_workers: 6 + +EVAL: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/rec + file_list: + - /home/zhoujun20/rec/label_val_all.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + shuffle: False + drop_last: False + batch_size: 256 + num_workers: 6 diff --git a/configs/rec/rec_mv3_none_bilstm_ctc_lmdb.yml b/configs/rec/rec_mv3_none_bilstm_ctc_lmdb.yml new file mode 100644 index 00000000..c26752ba --- /dev/null +++ b/configs/rec/rec_mv3_none_bilstm_ctc_lmdb.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 1 + save_model_dir: ./output/rec/test/ + save_epoch_step: 500 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: 1016 + # if pretrained_model is saved in static mode, load_static_weights must set to True + load_static_weights: True + cal_metric_during_train: True + pretrained_model: + checkpoints: #output/rec/rec_crnn/best_accuracy + save_inference_dir: + use_visualdl: True + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + max_text_length: 80 + character_dict_path: /home/zhoujun20/rec/lmdb/dict.txt + character_type: 'ch' + use_space_char: True + infer_mode: False + use_tps: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + learning_rate: + name: Cosine + lr: 0.0005 + warmup_epoch: 1 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [ 1, 2, 2, 2 ] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTC + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +TRAIN: + dataset: + name: LMDBDateSet + file_list: + - /home/zhoujun20/rec/lmdb/train # dataset1 + ratio_list: [ 0.4,0.6 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecAug: + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + batch_size: 256 + shuffle: True + drop_last: True + num_workers: 6 + +EVAL: + dataset: + name: LMDBDateSet + file_list: + - /home/zhoujun20/rec/lmdb/val + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + shuffle: False + drop_last: False + batch_size: 256 + num_workers: 6 diff --git a/configs/rec/rec_mv3_none_none_ctc.yml b/configs/rec/rec_mv3_none_none_ctc.yml deleted file mode 100755 index ceec09ce..00000000 --- a/configs/rec/rec_mv3_none_none_ctc.yml +++ /dev/null @@ -1,41 +0,0 @@ -Global: - algorithm: Rosetta - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_Rosetta - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: reshape - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_mv3_tps_bilstm_attn.yml b/configs/rec/rec_mv3_tps_bilstm_attn.yml deleted file mode 100755 index 7fc4f679..00000000 --- a/configs/rec/rec_mv3_tps_bilstm_attn.yml +++ /dev/null @@ -1,54 +0,0 @@ -Global: - algorithm: RARE - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_RARE - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: attention - tps: true - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -TPS: - function: ppocr.modeling.stns.tps,TPS - num_fiducial: 20 - loc_lr: 0.1 - model_name: small - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.rec_attention_head,AttentionPredict - encoder_type: rnn - SeqRNN: - hidden_size: 96 - Attention: - decoder_size: 96 - word_vector_dim: 96 - -Loss: - function: ppocr.modeling.losses.rec_attention_loss,AttentionLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_mv3_tps_bilstm_ctc.yml b/configs/rec/rec_mv3_tps_bilstm_ctc.yml deleted file mode 100755 index 4b9660bc..00000000 --- a/configs/rec/rec_mv3_tps_bilstm_ctc.yml +++ /dev/null @@ -1,51 +0,0 @@ -Global: - algorithm: STARNet - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_STARNet - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - tps: true - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -TPS: - function: ppocr.modeling.stns.tps,TPS - num_fiducial: 20 - loc_lr: 0.1 - model_name: small - -Backbone: - function: ppocr.modeling.backbones.rec_mobilenet_v3,MobileNetV3 - scale: 0.5 - model_name: large - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 96 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_r34_vd_none_bilstm_ctc.yml b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml old mode 100755 new mode 100644 index b71e8fea..3066cfa8 --- a/configs/rec/rec_r34_vd_none_bilstm_ctc.yml +++ b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml @@ -1,43 +1,106 @@ Global: - algorithm: CRNN - use_gpu: true - epoch_num: 72 + use_gpu: false + epoch_num: 500 log_smooth_window: 20 print_batch_step: 10 - save_model_dir: output/rec_CRNN - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: + save_model_dir: ./output/rec/test/ + save_epoch_step: 500 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: 127 + # if pretrained_model is saved in static mode, load_static_weights must set to True + load_static_weights: True + cal_metric_during_train: True + pretrained_model: + checkpoints: #output/rec/rec_crnn/best_accuracy save_inference_dir: - infer_img: - + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + max_text_length: 80 + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + character_type: 'ch' + use_space_char: False + infer_mode: False + use_tps: False -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_vd,ResNet - layers: 34 - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 256 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 + name: Adam beta1: 0.9 beta2: 0.999 + learning_rate: + name: Cosine + lr: 0.001 + warmup_epoch: 4 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 200 + Neck: + name: SequenceEncoder + encoder_type: fc + hidden_size: 96 + Head: + name: CTC + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +TRAIN: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/rec + file_list: + - /home/zhoujun20/rec/real_data.txt # dataset1 + ratio_list: [ 0.4,0.6 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecAug: + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + batch_size: 256 + shuffle: True + drop_last: True + num_workers: 6 + +EVAL: + dataset: + name: SimpleDataSet + data_dir: /home/zhoujun20/rec + file_list: + - /home/zhoujun20/rec/label_val_all.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [ 3,32,320 ] + - keepKeys: + keep_keys: [ 'image','label','length' ] # dataloader将按照此顺序返回list + loader: + shuffle: False + drop_last: False + batch_size: 256 + num_workers: 6 diff --git a/configs/rec/rec_r34_vd_none_none_ctc.yml b/configs/rec/rec_r34_vd_none_none_ctc.yml deleted file mode 100755 index d9c9458d..00000000 --- a/configs/rec/rec_r34_vd_none_none_ctc.yml +++ /dev/null @@ -1,40 +0,0 @@ -Global: - algorithm: Rosetta - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_Rosetta - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_vd,ResNet - layers: 34 - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: reshape - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_attn.yml b/configs/rec/rec_r34_vd_tps_bilstm_attn.yml deleted file mode 100755 index dfcd97fa..00000000 --- a/configs/rec/rec_r34_vd_tps_bilstm_attn.yml +++ /dev/null @@ -1,53 +0,0 @@ -Global: - algorithm: RARE - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_RARE - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: attention - tps: true - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -TPS: - function: ppocr.modeling.stns.tps,TPS - num_fiducial: 20 - loc_lr: 0.1 - model_name: large - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_vd,ResNet - layers: 34 - -Head: - function: ppocr.modeling.heads.rec_attention_head,AttentionPredict - encoder_type: rnn - SeqRNN: - hidden_size: 256 - Attention: - decoder_size: 128 - word_vector_dim: 128 - -Loss: - function: ppocr.modeling.losses.rec_attention_loss,AttentionLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml b/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml deleted file mode 100755 index 574a088c..00000000 --- a/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml +++ /dev/null @@ -1,50 +0,0 @@ -Global: - algorithm: STARNet - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_STARNet - save_epoch_step: 3 - eval_batch_step: 2000 - train_batch_size_per_card: 256 - test_batch_size_per_card: 256 - image_shape: [3, 32, 100] - max_text_length: 25 - character_type: en - loss_type: ctc - tps: true - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -TPS: - function: ppocr.modeling.stns.tps,TPS - num_fiducial: 20 - loc_lr: 0.1 - model_name: large - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_vd,ResNet - layers: 34 - -Head: - function: ppocr.modeling.heads.rec_ctc_head,CTCPredict - encoder_type: rnn - SeqRNN: - hidden_size: 256 - -Loss: - function: ppocr.modeling.losses.rec_ctc_loss,CTCLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.001 - beta1: 0.9 - beta2: 0.999 diff --git a/configs/rec/rec_r50fpn_vd_none_srn.yml b/configs/rec/rec_r50fpn_vd_none_srn.yml deleted file mode 100755 index 30709e47..00000000 --- a/configs/rec/rec_r50fpn_vd_none_srn.yml +++ /dev/null @@ -1,49 +0,0 @@ -Global: - algorithm: SRN - use_gpu: true - epoch_num: 72 - log_smooth_window: 20 - print_batch_step: 10 - save_model_dir: output/rec_pvam_withrotate - save_epoch_step: 1 - eval_batch_step: 8000 - train_batch_size_per_card: 64 - test_batch_size_per_card: 1 - image_shape: [1, 64, 256] - max_text_length: 25 - character_type: en - loss_type: srn - num_heads: 8 - average_window: 0.15 - max_average_window: 15625 - min_average_window: 10000 - reader_yml: ./configs/rec/rec_benchmark_reader.yml - pretrain_weights: - checkpoints: - save_inference_dir: - infer_img: - -Architecture: - function: ppocr.modeling.architectures.rec_model,RecModel - -Backbone: - function: ppocr.modeling.backbones.rec_resnet_fpn,ResNet - layers: 50 - -Head: - function: ppocr.modeling.heads.rec_srn_all_head,SRNPredict - encoder_type: rnn - num_encoder_TUs: 2 - num_decoder_TUs: 4 - hidden_dims: 512 - SeqRNN: - hidden_size: 256 - -Loss: - function: ppocr.modeling.losses.rec_srn_loss,SRNLoss - -Optimizer: - function: ppocr.optimizer,AdamDecay - base_lr: 0.0001 - beta1: 0.9 - beta2: 0.999 diff --git a/ppocr/data/__init__.py b/ppocr/data/__init__.py old mode 100755 new mode 100644 index abf198b9..2a3e2771 --- a/ppocr/data/__init__.py +++ b/ppocr/data/__init__.py @@ -11,3 +11,114 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os +import sys +import numpy as np +import paddle + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.abspath(os.path.join(__dir__, '../..'))) + +import copy +from paddle.io import DataLoader, DistributedBatchSampler, BatchSampler +import paddle.distributed as dist + +from ppocr.data.imaug import transform, create_operators + +__all__ = ['build_dataloader', 'transform', 'create_operators'] + + +def build_dataset(config, global_config): + from ppocr.data.dataset import SimpleDataSet, LMDBDateSet + support_dict = ['SimpleDataSet', 'LMDBDateSet'] + + module_name = config.pop('name') + assert module_name in support_dict, Exception( + 'DataSet only support {}'.format(support_dict)) + + dataset = eval(module_name)(config, global_config) + return dataset + + +def build_dataloader(config, device, distributed=False, global_config=None): + from ppocr.data.dataset import BatchBalancedDataLoader + + config = copy.deepcopy(config) + dataset_config = config['dataset'] + + _dataset_list = [] + file_list = dataset_config.pop('file_list') + if len(file_list) == 1: + ratio_list = [1.0] + else: + ratio_list = dataset_config.pop('ratio_list') + for file in file_list: + dataset_config['file_list'] = file + _dataset = build_dataset(dataset_config, global_config) + _dataset_list.append(_dataset) + data_loader = BatchBalancedDataLoader(_dataset_list, ratio_list, + distributed, device, config['loader']) + return data_loader, _dataset.info_dict + + +def test_loader(): + import time + from tools.program import load_config, ArgsParser + + FLAGS = ArgsParser().parse_args() + config = load_config(FLAGS.config) + + place = paddle.CPUPlace() + paddle.disable_static(place) + import time + + data_loader, _ = build_dataloader( + config['TRAIN'], place, global_config=config['Global']) + start = time.time() + print(len(data_loader)) + for epoch in range(1): + print('epoch {} ****************'.format(epoch)) + for i, batch in enumerate(data_loader): + if i > len(data_loader): + break + t = time.time() - start + start = time.time() + print('{}, batch : {} ,time {}'.format(i, len(batch[0]), t)) + + continue + import matplotlib.pyplot as plt + + from matplotlib import pyplot as plt + import cv2 + fig = plt.figure() + # # cv2.imwrite('img.jpg',batch[0].numpy()[0].transpose((1,2,0))) + # # cv2.imwrite('bmap.jpg',batch[1].numpy()[0]) + # # cv2.imwrite('bmask.jpg',batch[2].numpy()[0]) + # # cv2.imwrite('smap.jpg',batch[3].numpy()[0]) + # # cv2.imwrite('smask.jpg',batch[4].numpy()[0]) + plt.title('img') + plt.imshow(batch[0].numpy()[0].transpose((1, 2, 0))) + # plt.figure() + # plt.title('bmap') + # plt.imshow(batch[1].numpy()[0],cmap='Greys') + # plt.figure() + # plt.title('bmask') + # plt.imshow(batch[2].numpy()[0],cmap='Greys') + # plt.figure() + # plt.title('smap') + # plt.imshow(batch[3].numpy()[0],cmap='Greys') + # plt.figure() + # plt.title('smask') + # plt.imshow(batch[4].numpy()[0],cmap='Greys') + # plt.show() + # break + + +if __name__ == '__main__': + test_loader() diff --git a/ppocr/data/dataset.py b/ppocr/data/dataset.py new file mode 100644 index 00000000..6183267d --- /dev/null +++ b/ppocr/data/dataset.py @@ -0,0 +1,300 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import copy +import numpy as np +import os +import lmdb +import random +import signal +import paddle +from paddle.io import Dataset, DataLoader, DistributedBatchSampler, BatchSampler + +from .imaug import transform, create_operators +from ppocr.utils.logging import get_logger + + +def term_mp(sig_num, frame): + """ kill all child processes + """ + pid = os.getpid() + pgid = os.getpgid(os.getpid()) + print("main proc {} exit, kill process group " "{}".format(pid, pgid)) + os.killpg(pgid, signal.SIGKILL) + + +signal.signal(signal.SIGINT, term_mp) +signal.signal(signal.SIGTERM, term_mp) + + +class ModeException(Exception): + """ + ModeException + """ + + def __init__(self, message='', mode=''): + message += "\nOnly the following 3 modes are supported: " \ + "train, valid, test. Given mode is {}".format(mode) + super(ModeException, self).__init__(message) + + +class SampleNumException(Exception): + """ + SampleNumException + """ + + def __init__(self, message='', sample_num=0, batch_size=1): + message += "\nError: The number of the whole data ({}) " \ + "is smaller than the batch_size ({}), and drop_last " \ + "is turnning on, so nothing will feed in program, " \ + "Terminated now. Please reset batch_size to a smaller " \ + "number or feed more data!".format(sample_num, batch_size) + super(SampleNumException, self).__init__(message) + + +def get_file_list(file_list, data_dir, delimiter='\t'): + """ + read label list from file and shuffle the list + + Args: + params(dict): + """ + if isinstance(file_list, str): + file_list = [file_list] + data_source_list = [] + for file in file_list: + with open(file) as f: + full_lines = [line.strip() for line in f] + for line in full_lines: + try: + img_path, label = line.split(delimiter) + except: + logger = get_logger() + logger.warning('label error in {}'.format(line)) + img_path = os.path.join(data_dir, img_path) + data = {'img_path': img_path, 'label': label} + data_source_list.append(data) + return data_source_list + + +class LMDBDateSet(Dataset): + def __init__(self, config, global_config): + super(LMDBDateSet, self).__init__() + self.data_list = self.load_lmdb_dataset( + config['file_list'], global_config['max_text_length']) + random.shuffle(self.data_list) + + self.ops = create_operators(config['transforms'], global_config) + + # for rec + character = '' + for op in self.ops: + if hasattr(op, 'character'): + character = getattr(op, 'character') + + self.info_dict = {'character': character} + + def load_lmdb_dataset(self, data_dir, max_text_length): + self.env = lmdb.open( + data_dir, + max_readers=32, + readonly=True, + lock=False, + readahead=False, + meminit=False) + if not self.env: + print('cannot create lmdb from %s' % (data_dir)) + exit(0) + + filtered_index_list = [] + with self.env.begin(write=False) as txn: + nSamples = int(txn.get('num-samples'.encode())) + self.nSamples = nSamples + for index in range(self.nSamples): + index += 1 # lmdb starts with 1 + label_key = 'label-%09d'.encode() % index + label = txn.get(label_key).decode('utf-8') + if len(label) > max_text_length: + # print(f'The length of the label is longer than max_length: length + # {len(label)}, {label} in dataset {self.root}') + continue + + # By default, images containing characters which are not in opt.character are filtered. + # You can add [UNK] token to `opt.character` in utils.py instead of this filtering. + filtered_index_list.append(index) + return filtered_index_list + + def print_lmdb_sets_info(self, lmdb_sets): + lmdb_info_strs = [] + for dataset_idx in range(len(lmdb_sets)): + tmp_str = " %s:%d," % (lmdb_sets[dataset_idx]['dirpath'], + lmdb_sets[dataset_idx]['num_samples']) + lmdb_info_strs.append(tmp_str) + lmdb_info_strs = ''.join(lmdb_info_strs) + logger = get_logger() + logger.info("DataSummary:" + lmdb_info_strs) + return + + def __getitem__(self, idx): + idx = self.data_list[idx] + with self.env.begin(write=False) as txn: + label_key = 'label-%09d'.encode() % idx + label = txn.get(label_key) + if label is not None: + label = label.decode('utf-8') + img_key = 'image-%09d'.encode() % idx + imgbuf = txn.get(img_key) + data = {'image': imgbuf, 'label': label} + outs = transform(data, self.ops) + else: + outs = None + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + def __len__(self): + return len(self.data_list) + + +class SimpleDataSet(Dataset): + def __init__(self, config, global_config): + super(SimpleDataSet, self).__init__() + delimiter = config.get('delimiter', '\t') + self.data_list = get_file_list(config['file_list'], config['data_dir'], + delimiter) + random.shuffle(self.data_list) + + self.ops = create_operators(config['transforms'], global_config) + + # for rec + character = '' + for op in self.ops: + if hasattr(op, 'character'): + character = getattr(op, 'character') + + self.info_dict = {'character': character} + + def __getitem__(self, idx): + data = copy.deepcopy(self.data_list[idx]) + with open(data['img_path'], 'rb') as f: + img = f.read() + data['image'] = img + outs = transform(data, self.ops) + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + def __len__(self): + return len(self.data_list) + + +class BatchBalancedDataLoader(object): + def __init__(self, + dataset_list: list, + ratio_list: list, + distributed, + device, + loader_args: dict): + """ + 对datasetlist里的dataset按照ratio_list里对应的比例组合,似的每个batch里的数据按按照比例采样的 + :param dataset_list: 数据集列表 + :param ratio_list: 比例列表 + :param loader_args: dataloader的配置 + """ + assert sum(ratio_list) == 1 and len(dataset_list) == len(ratio_list) + + self.dataset_len = 0 + self.data_loader_list = [] + self.dataloader_iter_list = [] + all_batch_size = loader_args.pop('batch_size') + batch_size_list = list( + map(int, [max(1.0, all_batch_size * x) for x in ratio_list])) + remain_num = all_batch_size - sum(batch_size_list) + batch_size_list[np.argmax(ratio_list)] += remain_num + + for _dataset, _batch_size in zip(dataset_list, batch_size_list): + if distributed: + batch_sampler_class = DistributedBatchSampler + else: + batch_sampler_class = BatchSampler + batch_sampler = batch_sampler_class( + dataset=_dataset, + batch_size=_batch_size, + shuffle=loader_args['shuffle'], + drop_last=loader_args['drop_last'], ) + _data_loader = DataLoader( + dataset=_dataset, + batch_sampler=batch_sampler, + places=device, + num_workers=loader_args['num_workers'], + return_list=True, ) + self.data_loader_list.append(_data_loader) + self.dataloader_iter_list.append(iter(_data_loader)) + self.dataset_len += len(_dataset) + + def __iter__(self): + return self + + def __len__(self): + return min([len(x) for x in self.data_loader_list]) + + def __next__(self): + batch = [] + for i, data_loader_iter in enumerate(self.dataloader_iter_list): + try: + _batch_i = next(data_loader_iter) + batch.append(_batch_i) + except StopIteration: + self.dataloader_iter_list[i] = iter(self.data_loader_list[i]) + _batch_i = next(self.dataloader_iter_list[i]) + batch.append(_batch_i) + except ValueError: + pass + if len(batch) > 0: + batch_list = [] + batch_item_size = len(batch[0]) + for i in range(batch_item_size): + cur_item_list = [batch_i[i] for batch_i in batch] + batch_list.append(paddle.concat(cur_item_list, axis=0)) + else: + batch_list = batch[0] + return batch_list + + +def fill_batch(batch): + """ + 2020.09.08: The current paddle version only supports returning data with the same length. + Therefore, fill in the batches with inconsistent lengths. + this method is currently only useful for text detection + """ + keys = list(range(len(batch[0]))) + v_max_len_dict = {} + for k in keys: + v_max_len_dict[k] = max([len(item[k]) for item in batch]) + for item in batch: + length = [] + for k in keys: + v = item[k] + length.append(len(v)) + assert isinstance(v, np.ndarray) + if len(v) == v_max_len_dict[k]: + continue + try: + tmp_shape = [v_max_len_dict[k] - len(v)] + list(v[0].shape) + except: + a = 1 + tmp_array = np.zeros(tmp_shape, dtype=v[0].dtype) + new_array = np.concatenate([v, tmp_array]) + item[k] = new_array + item.append(length) + return batch diff --git a/ppocr/data/det/data_augment.py b/ppocr/data/det/data_augment.py deleted file mode 100644 index f46c14ae..00000000 --- a/ppocr/data/det/data_augment.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding:utf-8 -*- - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -import random -import cv2 -import math - -import imgaug -import imgaug.augmenters as iaa - - -def AugmentData(data): - img = data['image'] - shape = img.shape - - aug = iaa.Sequential( - [iaa.Fliplr(0.5), iaa.Affine(rotate=(-10, 10)), iaa.Resize( - (0.5, 3))]).to_deterministic() - - def may_augment_annotation(aug, data, shape): - if aug is None: - return data - - line_polys = [] - for poly in data['polys']: - new_poly = may_augment_poly(aug, shape, poly) - line_polys.append(new_poly) - data['polys'] = np.array(line_polys) - return data - - def may_augment_poly(aug, img_shape, poly): - keypoints = [imgaug.Keypoint(p[0], p[1]) for p in poly] - keypoints = aug.augment_keypoints( - [imgaug.KeypointsOnImage( - keypoints, shape=img_shape)])[0].keypoints - poly = [(p.x, p.y) for p in keypoints] - return poly - - img_aug = aug.augment_image(img) - data['image'] = img_aug - data = may_augment_annotation(aug, data, shape) - return data diff --git a/ppocr/data/det/dataset_traversal.py b/ppocr/data/det/dataset_traversal.py deleted file mode 100644 index bd055c82..00000000 --- a/ppocr/data/det/dataset_traversal.py +++ /dev/null @@ -1,167 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import os -import sys -import math -import random -import functools -import numpy as np -import cv2 -import string -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from ppocr.utils.utility import create_module -from ppocr.utils.utility import get_image_file_list -import time - - -class TrainReader(object): - def __init__(self, params): - self.num_workers = params['num_workers'] - self.label_file_path = params['label_file_path'] - print(self.label_file_path) - self.use_mul_data = False - if isinstance(self.label_file_path, list): - self.use_mul_data = True - self.data_ratio_list = params['data_ratio_list'] - self.batch_size = params['train_batch_size_per_card'] - assert 'process_function' in params,\ - "absence process_function in Reader" - self.process = create_module(params['process_function'])(params) - - def __call__(self, process_id): - def sample_iter_reader(): - with open(self.label_file_path, "rb") as fin: - label_infor_list = fin.readlines() - img_num = len(label_infor_list) - img_id_list = list(range(img_num)) - random.shuffle(img_id_list) - if sys.platform == "win32" and self.num_workers != 1: - print("multiprocess is not fully compatible with Windows." - "num_workers will be 1.") - self.num_workers = 1 - for img_id in range(process_id, img_num, self.num_workers): - label_infor = label_infor_list[img_id_list[img_id]] - outs = self.process(label_infor) - if outs is None: - continue - yield outs - - def sample_iter_reader_mul(): - batch_size = 1000 - data_source_list = self.label_file_path - batch_size_list = list(map(int, [max(1.0, batch_size * x) for x in self.data_ratio_list])) - print(self.data_ratio_list, batch_size_list) - - data_filename_list, data_size_list, fetch_record_list = [], [], [] - for data_source in data_source_list: - image_files = open(data_source, "rb").readlines() - random.shuffle(image_files) - data_filename_list.append(image_files) - data_size_list.append(len(image_files)) - fetch_record_list.append(0) - - image_batch = [] - # get a batch of img_fns and poly_fns - for i in range(0, len(batch_size_list)): - bs = batch_size_list[i] - ds = data_size_list[i] - image_names = data_filename_list[i] - fetch_record = fetch_record_list[i] - data_path = data_source_list[i] - for j in range(fetch_record, fetch_record + bs): - index = j % ds - image_batch.append(image_names[index]) - - if (fetch_record + bs) > ds: - fetch_record_list[i] = 0 - random.shuffle(data_filename_list[i]) - else: - fetch_record_list[i] = fetch_record + bs - - if sys.platform == "win32": - print("multiprocess is not fully compatible with Windows." - "num_workers will be 1.") - self.num_workers = 1 - - for label_infor in image_batch: - outs = self.process(label_infor) - if outs is None: - continue - yield outs - - def batch_iter_reader(): - batch_outs = [] - if self.use_mul_data: - print("Sample date from multiple datasets!") - for outs in sample_iter_reader_mul(): - batch_outs.append(outs) - if len(batch_outs) == self.batch_size: - yield batch_outs - batch_outs = [] - else: - for outs in sample_iter_reader(): - batch_outs.append(outs) - if len(batch_outs) == self.batch_size: - yield batch_outs - batch_outs = [] - - return batch_iter_reader - - -class EvalTestReader(object): - def __init__(self, params): - self.params = params - assert 'process_function' in params,\ - "absence process_function in EvalTestReader" - - def __call__(self, mode): - process_function = create_module(self.params['process_function'])( - self.params) - batch_size = self.params['test_batch_size_per_card'] - - img_list = [] - if mode != "test": - img_set_dir = self.params['img_set_dir'] - img_name_list_path = self.params['label_file_path'] - with open(img_name_list_path, "rb") as fin: - lines = fin.readlines() - for line in lines: - img_name = line.decode().strip("\n").split("\t")[0] - img_path = os.path.join(img_set_dir, img_name) - img_list.append(img_path) - else: - img_path = self.params['infer_img'] - img_list = get_image_file_list(img_path) - - def batch_iter_reader(): - batch_outs = [] - for img_path in img_list: - img = cv2.imread(img_path) - if img is None: - logger.info("{} does not exist!".format(img_path)) - continue - elif len(list(img.shape)) == 2 or img.shape[2] == 1: - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - outs = process_function(img) - outs.append(img_path) - batch_outs.append(outs) - if len(batch_outs) == batch_size: - yield batch_outs - batch_outs = [] - if len(batch_outs) != 0: - yield batch_outs - - return batch_iter_reader diff --git a/ppocr/data/det/db_process.py b/ppocr/data/det/db_process.py deleted file mode 100644 index 9534c59e..00000000 --- a/ppocr/data/det/db_process.py +++ /dev/null @@ -1,216 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import math -import cv2 -import numpy as np -import json -import sys -from ppocr.utils.utility import initial_logger, check_and_read_gif -logger = initial_logger() - -from .data_augment import AugmentData -from .random_crop_data import RandomCropData -from .make_shrink_map import MakeShrinkMap -from .make_border_map import MakeBorderMap - - -class DBProcessTrain(object): - """ - DB pre-process for Train mode - """ - - def __init__(self, params): - self.img_set_dir = params['img_set_dir'] - self.image_shape = params['image_shape'] - - def order_points_clockwise(self, pts): - rect = np.zeros((4, 2), dtype="float32") - s = pts.sum(axis=1) - rect[0] = pts[np.argmin(s)] - rect[2] = pts[np.argmax(s)] - diff = np.diff(pts, axis=1) - rect[1] = pts[np.argmin(diff)] - rect[3] = pts[np.argmax(diff)] - return rect - - def make_data_dict(self, imgvalue, entry): - boxes = [] - texts = [] - ignores = [] - for rect in entry: - points = rect['points'] - transcription = rect['transcription'] - try: - box = self.order_points_clockwise( - np.array(points).reshape(-1, 2)) - if cv2.contourArea(box) > 0: - boxes.append(box) - texts.append(transcription) - ignores.append(transcription in ['*', '###']) - except: - print('load label failed!') - data = { - 'image': imgvalue, - 'shape': [imgvalue.shape[0], imgvalue.shape[1]], - 'polys': np.array(boxes), - 'texts': texts, - 'ignore_tags': ignores, - } - return data - - def NormalizeImage(self, data): - im = data['image'] - img_mean = [0.485, 0.456, 0.406] - img_std = [0.229, 0.224, 0.225] - im = im.astype(np.float32, copy=False) - im = im / 255 - im -= img_mean - im /= img_std - channel_swap = (2, 0, 1) - im = im.transpose(channel_swap) - data['image'] = im - return data - - def FilterKeys(self, data): - filter_keys = ['polys', 'texts', 'ignore_tags', 'shape'] - for key in filter_keys: - if key in data: - del data[key] - return data - - def convert_label_infor(self, label_infor): - label_infor = label_infor.decode() - label_infor = label_infor.encode('utf-8').decode('utf-8-sig') - substr = label_infor.strip("\n").split("\t") - img_path = self.img_set_dir + substr[0] - label = json.loads(substr[1]) - return img_path, label - - def __call__(self, label_infor): - img_path, gt_label = self.convert_label_infor(label_infor) - imgvalue, flag = check_and_read_gif(img_path) - if not flag: - imgvalue = cv2.imread(img_path) - if imgvalue is None: - logger.info("{} does not exist!".format(img_path)) - return None - if len(list(imgvalue.shape)) == 2 or imgvalue.shape[2] == 1: - imgvalue = cv2.cvtColor(imgvalue, cv2.COLOR_GRAY2BGR) - data = self.make_data_dict(imgvalue, gt_label) - data = AugmentData(data) - data = RandomCropData(data, self.image_shape[1:]) - data = MakeShrinkMap(data) - data = MakeBorderMap(data) - data = self.NormalizeImage(data) - data = self.FilterKeys(data) - return data['image'], data['shrink_map'], data['shrink_mask'], data[ - 'threshold_map'], data['threshold_mask'] - - -class DBProcessTest(object): - """ - DB pre-process for Test mode - """ - - def __init__(self, params): - super(DBProcessTest, self).__init__() - self.resize_type = 0 - if 'test_image_shape' in params: - self.image_shape = params['test_image_shape'] - # print(self.image_shape) - self.resize_type = 1 - if 'max_side_len' in params: - self.max_side_len = params['max_side_len'] - else: - self.max_side_len = 2400 - - def resize_image_type0(self, im): - """ - resize image to a size multiple of 32 which is required by the network - args: - img(array): array with shape [h, w, c] - return(tuple): - img, (ratio_h, ratio_w) - """ - max_side_len = self.max_side_len - h, w, _ = im.shape - - resize_w = w - resize_h = h - - # limit the max side - if max(resize_h, resize_w) > max_side_len: - if resize_h > resize_w: - ratio = float(max_side_len) / resize_h - else: - ratio = float(max_side_len) / resize_w - else: - ratio = 1. - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - if resize_h % 32 == 0: - resize_h = resize_h - elif resize_h // 32 <= 1: - resize_h = 32 - else: - resize_h = (resize_h // 32 - 1) * 32 - if resize_w % 32 == 0: - resize_w = resize_w - elif resize_w // 32 <= 1: - resize_w = 32 - else: - resize_w = (resize_w // 32 - 1) * 32 - try: - if int(resize_w) <= 0 or int(resize_h) <= 0: - return None, (None, None) - im = cv2.resize(im, (int(resize_w), int(resize_h))) - except: - print(im.shape, resize_w, resize_h) - sys.exit(0) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - return im, (ratio_h, ratio_w) - - def resize_image_type1(self, im): - resize_h, resize_w = self.image_shape - ori_h, ori_w = im.shape[:2] # (h, w, c) - im = cv2.resize(im, (int(resize_w), int(resize_h))) - ratio_h = float(resize_h) / ori_h - ratio_w = float(resize_w) / ori_w - return im, (ratio_h, ratio_w) - - def normalize(self, im): - img_mean = [0.485, 0.456, 0.406] - img_std = [0.229, 0.224, 0.225] - im = im.astype(np.float32, copy=False) - im = im / 255 - im[:, :, 0] -= img_mean[0] - im[:, :, 1] -= img_mean[1] - im[:, :, 2] -= img_mean[2] - im[:, :, 0] /= img_std[0] - im[:, :, 1] /= img_std[1] - im[:, :, 2] /= img_std[2] - channel_swap = (2, 0, 1) - im = im.transpose(channel_swap) - return im - - def __call__(self, im): - if self.resize_type == 0: - im, (ratio_h, ratio_w) = self.resize_image_type0(im) - else: - im, (ratio_h, ratio_w) = self.resize_image_type1(im) - im = self.normalize(im) - im = im[np.newaxis, :] - return [im, (ratio_h, ratio_w)] diff --git a/ppocr/data/det/east_process.py b/ppocr/data/det/east_process.py deleted file mode 100755 index e2581caa..00000000 --- a/ppocr/data/det/east_process.py +++ /dev/null @@ -1,537 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import math -import cv2 -import numpy as np -import json -import sys -import os - -class EASTProcessTrain(object): - def __init__(self, params): - self.img_set_dir = params['img_set_dir'] - self.random_scale = np.array([0.5, 1, 2.0, 3.0]) - self.background_ratio = params['background_ratio'] - self.min_crop_side_ratio = params['min_crop_side_ratio'] - image_shape = params['image_shape'] - self.input_size = image_shape[1] - self.min_text_size = params['min_text_size'] - - def preprocess(self, im): - input_size = self.input_size - im_shape = im.shape - im_size_min = np.min(im_shape[0:2]) - im_size_max = np.max(im_shape[0:2]) - im_scale = float(input_size) / float(im_size_max) - im = cv2.resize(im, None, None, fx=im_scale, fy=im_scale) - img_mean = [0.485, 0.456, 0.406] - img_std = [0.229, 0.224, 0.225] - im = im[:, :, ::-1].astype(np.float32) - im = im / 255 - im -= img_mean - im /= img_std - new_h, new_w, _ = im.shape - im_padded = np.zeros((input_size, input_size, 3), dtype=np.float32) - im_padded[:new_h, :new_w, :] = im - im_padded = im_padded.transpose((2, 0, 1)) - im_padded = im_padded[np.newaxis, :] - return im_padded, im_scale - - def convert_label_infor(self, label_infor): - label_infor = label_infor.decode() - label_infor = label_infor.encode('utf-8').decode('utf-8-sig') - substr = label_infor.strip("\n").split("\t") - img_path = os.path.join(self.img_set_dir, substr[0]) - label = json.loads(substr[1]) - nBox = len(label) - wordBBs, txts, txt_tags = [], [], [] - for bno in range(0, nBox): - wordBB = label[bno]['points'] - txt = label[bno]['transcription'] - wordBBs.append(wordBB) - txts.append(txt) - if txt == '###': - txt_tags.append(True) - else: - txt_tags.append(False) - wordBBs = np.array(wordBBs, dtype=np.float32) - txt_tags = np.array(txt_tags, dtype=np.bool) - return img_path, wordBBs, txt_tags, txts - - def rotate_im_poly(self, im, text_polys): - """ - rotate image with 90 / 180 / 270 degre - """ - im_w, im_h = im.shape[1], im.shape[0] - dst_im = im.copy() - dst_polys = [] - rand_degree_ratio = np.random.rand() - rand_degree_cnt = 1 - if 0.333 < rand_degree_ratio < 0.666: - rand_degree_cnt = 2 - elif rand_degree_ratio > 0.666: - rand_degree_cnt = 3 - for i in range(rand_degree_cnt): - dst_im = np.rot90(dst_im) - rot_degree = -90 * rand_degree_cnt - rot_angle = rot_degree * math.pi / 180.0 - n_poly = text_polys.shape[0] - cx, cy = 0.5 * im_w, 0.5 * im_h - ncx, ncy = 0.5 * dst_im.shape[1], 0.5 * dst_im.shape[0] - for i in range(n_poly): - wordBB = text_polys[i] - poly = [] - for j in range(4): - sx, sy = wordBB[j][0], wordBB[j][1] - dx = math.cos(rot_angle) * (sx - cx)\ - - math.sin(rot_angle) * (sy - cy) + ncx - dy = math.sin(rot_angle) * (sx - cx)\ - + math.cos(rot_angle) * (sy - cy) + ncy - poly.append([dx, dy]) - dst_polys.append(poly) - dst_polys = np.array(dst_polys, dtype=np.float32) - return dst_im, dst_polys - - def polygon_area(self, poly): - """ - compute area of a polygon - :param poly: - :return: - """ - edge = [(poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]), - (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]), - (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]), - (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1])] - return np.sum(edge) / 2. - - def check_and_validate_polys(self, polys, tags, img_height, img_width): - """ - check so that the text poly is in the same direction, - and also filter some invalid polygons - :param polys: - :param tags: - :return: - """ - h, w = img_height, img_width - if polys.shape[0] == 0: - return polys - polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1) - polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1) - - validated_polys = [] - validated_tags = [] - for poly, tag in zip(polys, tags): - p_area = self.polygon_area(poly) - #invalid poly - if abs(p_area) < 1: - continue - if p_area > 0: - #'poly in wrong direction' - if not tag: - tag = True #reversed cases should be ignore - poly = poly[(0, 3, 2, 1), :] - validated_polys.append(poly) - validated_tags.append(tag) - return np.array(validated_polys), np.array(validated_tags) - - def draw_img_polys(self, img, polys): - if len(img.shape) == 4: - img = np.squeeze(img, axis=0) - if img.shape[0] == 3: - img = img.transpose((1, 2, 0)) - img[:, :, 2] += 123.68 - img[:, :, 1] += 116.78 - img[:, :, 0] += 103.94 - cv2.imwrite("tmp.jpg", img) - img = cv2.imread("tmp.jpg") - for box in polys: - box = box.astype(np.int32).reshape((-1, 1, 2)) - cv2.polylines(img, [box], True, color=(255, 255, 0), thickness=2) - import random - ino = random.randint(0, 100) - cv2.imwrite("tmp_%d.jpg" % ino, img) - return - - def shrink_poly(self, poly, r): - """ - fit a poly inside the origin poly, maybe bugs here... - used for generate the score map - :param poly: the text poly - :param r: r in the paper - :return: the shrinked poly - """ - # shrink ratio - R = 0.3 - # find the longer pair - dist0 = np.linalg.norm(poly[0] - poly[1]) - dist1 = np.linalg.norm(poly[2] - poly[3]) - dist2 = np.linalg.norm(poly[0] - poly[3]) - dist3 = np.linalg.norm(poly[1] - poly[2]) - if dist0 + dist1 > dist2 + dist3: - # first move (p0, p1), (p2, p3), then (p0, p3), (p1, p2) - ## p0, p1 - theta = np.arctan2((poly[1][1] - poly[0][1]), - (poly[1][0] - poly[0][0])) - poly[0][0] += R * r[0] * np.cos(theta) - poly[0][1] += R * r[0] * np.sin(theta) - poly[1][0] -= R * r[1] * np.cos(theta) - poly[1][1] -= R * r[1] * np.sin(theta) - ## p2, p3 - theta = np.arctan2((poly[2][1] - poly[3][1]), - (poly[2][0] - poly[3][0])) - poly[3][0] += R * r[3] * np.cos(theta) - poly[3][1] += R * r[3] * np.sin(theta) - poly[2][0] -= R * r[2] * np.cos(theta) - poly[2][1] -= R * r[2] * np.sin(theta) - ## p0, p3 - theta = np.arctan2((poly[3][0] - poly[0][0]), - (poly[3][1] - poly[0][1])) - poly[0][0] += R * r[0] * np.sin(theta) - poly[0][1] += R * r[0] * np.cos(theta) - poly[3][0] -= R * r[3] * np.sin(theta) - poly[3][1] -= R * r[3] * np.cos(theta) - ## p1, p2 - theta = np.arctan2((poly[2][0] - poly[1][0]), - (poly[2][1] - poly[1][1])) - poly[1][0] += R * r[1] * np.sin(theta) - poly[1][1] += R * r[1] * np.cos(theta) - poly[2][0] -= R * r[2] * np.sin(theta) - poly[2][1] -= R * r[2] * np.cos(theta) - else: - ## p0, p3 - # print poly - theta = np.arctan2((poly[3][0] - poly[0][0]), - (poly[3][1] - poly[0][1])) - poly[0][0] += R * r[0] * np.sin(theta) - poly[0][1] += R * r[0] * np.cos(theta) - poly[3][0] -= R * r[3] * np.sin(theta) - poly[3][1] -= R * r[3] * np.cos(theta) - ## p1, p2 - theta = np.arctan2((poly[2][0] - poly[1][0]), - (poly[2][1] - poly[1][1])) - poly[1][0] += R * r[1] * np.sin(theta) - poly[1][1] += R * r[1] * np.cos(theta) - poly[2][0] -= R * r[2] * np.sin(theta) - poly[2][1] -= R * r[2] * np.cos(theta) - ## p0, p1 - theta = np.arctan2((poly[1][1] - poly[0][1]), - (poly[1][0] - poly[0][0])) - poly[0][0] += R * r[0] * np.cos(theta) - poly[0][1] += R * r[0] * np.sin(theta) - poly[1][0] -= R * r[1] * np.cos(theta) - poly[1][1] -= R * r[1] * np.sin(theta) - ## p2, p3 - theta = np.arctan2((poly[2][1] - poly[3][1]), - (poly[2][0] - poly[3][0])) - poly[3][0] += R * r[3] * np.cos(theta) - poly[3][1] += R * r[3] * np.sin(theta) - poly[2][0] -= R * r[2] * np.cos(theta) - poly[2][1] -= R * r[2] * np.sin(theta) - return poly - - def generate_quad(self, im_size, polys, tags): - """ - Generate quadrangle. - """ - h, w = im_size - poly_mask = np.zeros((h, w), dtype=np.uint8) - score_map = np.zeros((h, w), dtype=np.uint8) - # (x1, y1, ..., x4, y4, short_edge_norm) - geo_map = np.zeros((h, w, 9), dtype=np.float32) - # mask used during traning, to ignore some hard areas - training_mask = np.ones((h, w), dtype=np.uint8) - for poly_idx, poly_tag in enumerate(zip(polys, tags)): - poly = poly_tag[0] - tag = poly_tag[1] - - r = [None, None, None, None] - for i in range(4): - dist1 = np.linalg.norm(poly[i] - poly[(i + 1) % 4]) - dist2 = np.linalg.norm(poly[i] - poly[(i - 1) % 4]) - r[i] = min(dist1, dist2) - # score map - shrinked_poly = self.shrink_poly( - poly.copy(), r).astype(np.int32)[np.newaxis, :, :] - cv2.fillPoly(score_map, shrinked_poly, 1) - cv2.fillPoly(poly_mask, shrinked_poly, poly_idx + 1) - # if the poly is too small, then ignore it during training - poly_h = min( - np.linalg.norm(poly[0] - poly[3]), - np.linalg.norm(poly[1] - poly[2])) - poly_w = min( - np.linalg.norm(poly[0] - poly[1]), - np.linalg.norm(poly[2] - poly[3])) - if min(poly_h, poly_w) < self.min_text_size: - cv2.fillPoly(training_mask, - poly.astype(np.int32)[np.newaxis, :, :], 0) - - if tag: - cv2.fillPoly(training_mask, - poly.astype(np.int32)[np.newaxis, :, :], 0) - - xy_in_poly = np.argwhere(poly_mask == (poly_idx + 1)) - # geo map. - y_in_poly = xy_in_poly[:, 0] - x_in_poly = xy_in_poly[:, 1] - poly[:, 0] = np.minimum(np.maximum(poly[:, 0], 0), w) - poly[:, 1] = np.minimum(np.maximum(poly[:, 1], 0), h) - for pno in range(4): - geo_channel_beg = pno * 2 - geo_map[y_in_poly, x_in_poly, geo_channel_beg] =\ - x_in_poly - poly[pno, 0] - geo_map[y_in_poly, x_in_poly, geo_channel_beg+1] =\ - y_in_poly - poly[pno, 1] - geo_map[y_in_poly, x_in_poly, 8] = \ - 1.0 / max(min(poly_h, poly_w), 1.0) - return score_map, geo_map, training_mask - - def crop_area(self, - im, - polys, - tags, - txts, - crop_background=False, - max_tries=50): - """ - make random crop from the input image - :param im: - :param polys: - :param tags: - :param crop_background: - :param max_tries: - :return: - """ - h, w, _ = im.shape - pad_h = h // 10 - pad_w = w // 10 - h_array = np.zeros((h + pad_h * 2), dtype=np.int32) - w_array = np.zeros((w + pad_w * 2), dtype=np.int32) - for poly in polys: - poly = np.round(poly, decimals=0).astype(np.int32) - minx = np.min(poly[:, 0]) - maxx = np.max(poly[:, 0]) - w_array[minx + pad_w:maxx + pad_w] = 1 - miny = np.min(poly[:, 1]) - maxy = np.max(poly[:, 1]) - h_array[miny + pad_h:maxy + pad_h] = 1 - # ensure the cropped area not across a text - h_axis = np.where(h_array == 0)[0] - w_axis = np.where(w_array == 0)[0] - if len(h_axis) == 0 or len(w_axis) == 0: - return im, polys, tags, txts - - for i in range(max_tries): - xx = np.random.choice(w_axis, size=2) - xmin = np.min(xx) - pad_w - xmax = np.max(xx) - pad_w - xmin = np.clip(xmin, 0, w - 1) - xmax = np.clip(xmax, 0, w - 1) - yy = np.random.choice(h_axis, size=2) - ymin = np.min(yy) - pad_h - ymax = np.max(yy) - pad_h - ymin = np.clip(ymin, 0, h - 1) - ymax = np.clip(ymax, 0, h - 1) - if xmax - xmin < self.min_crop_side_ratio * w or \ - ymax - ymin < self.min_crop_side_ratio * h: - # area too small - continue - if polys.shape[0] != 0: - poly_axis_in_area = (polys[:, :, 0] >= xmin)\ - & (polys[:, :, 0] <= xmax)\ - & (polys[:, :, 1] >= ymin)\ - & (polys[:, :, 1] <= ymax) - selected_polys = np.where( - np.sum(poly_axis_in_area, axis=1) == 4)[0] - else: - selected_polys = [] - - if len(selected_polys) == 0: - # no text in this area - if crop_background: - im = im[ymin:ymax + 1, xmin:xmax + 1, :] - polys = [] - tags = [] - txts = [] - return im, polys, tags, txts - else: - continue - - im = im[ymin:ymax + 1, xmin:xmax + 1, :] - polys = polys[selected_polys] - tags = tags[selected_polys] - txts_tmp = [] - for selected_poly in selected_polys: - txts_tmp.append(txts[selected_poly]) - txts = txts_tmp - polys[:, :, 0] -= xmin - polys[:, :, 1] -= ymin - return im, polys, tags, txts - return im, polys, tags, txts - - def crop_background_infor(self, im, text_polys, text_tags, text_strs): - im, text_polys, text_tags, text_strs = self.crop_area( - im, text_polys, text_tags, text_strs, crop_background=True) - if len(text_polys) > 0: - return None - # pad and resize image - input_size = self.input_size - im, ratio = self.preprocess(im) - score_map = np.zeros((input_size, input_size), dtype=np.float32) - geo_map = np.zeros((input_size, input_size, 9), dtype=np.float32) - training_mask = np.ones((input_size, input_size), dtype=np.float32) - return im, score_map, geo_map, training_mask - - def crop_foreground_infor(self, im, text_polys, text_tags, text_strs): - im, text_polys, text_tags, text_strs = self.crop_area( - im, text_polys, text_tags, text_strs, crop_background=False) - if text_polys.shape[0] == 0: - return None - #continue for all ignore case - if np.sum((text_tags * 1.0)) >= text_tags.size: - return None - # pad and resize image - input_size = self.input_size - im, ratio = self.preprocess(im) - text_polys[:, :, 0] *= ratio - text_polys[:, :, 1] *= ratio - _, _, new_h, new_w = im.shape - # print(im.shape) - # self.draw_img_polys(im, text_polys) - score_map, geo_map, training_mask = self.generate_quad( - (new_h, new_w), text_polys, text_tags) - return im, score_map, geo_map, training_mask - - def __call__(self, label_infor): - infor = self.convert_label_infor(label_infor) - im_path, text_polys, text_tags, text_strs = infor - im = cv2.imread(im_path) - if im is None: - return None - if text_polys.shape[0] == 0: - return None - #add rotate cases - if np.random.rand() < 0.5: - im, text_polys = self.rotate_im_poly(im, text_polys) - h, w, _ = im.shape - text_polys, text_tags = self.check_and_validate_polys(text_polys, - text_tags, h, w) - if text_polys.shape[0] == 0: - return None - - # random scale this image - rd_scale = np.random.choice(self.random_scale) - im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) - text_polys *= rd_scale - if np.random.rand() < self.background_ratio: - outs = self.crop_background_infor(im, text_polys, text_tags, - text_strs) - else: - outs = self.crop_foreground_infor(im, text_polys, text_tags, - text_strs) - - if outs is None: - return None - im, score_map, geo_map, training_mask = outs - score_map = score_map[np.newaxis, ::4, ::4].astype(np.float32) - geo_map = np.swapaxes(geo_map, 1, 2) - geo_map = np.swapaxes(geo_map, 1, 0) - geo_map = geo_map[:, ::4, ::4].astype(np.float32) - training_mask = training_mask[np.newaxis, ::4, ::4] - training_mask = training_mask.astype(np.float32) - return im, score_map, geo_map, training_mask - - -class EASTProcessTest(object): - def __init__(self, params): - super(EASTProcessTest, self).__init__() - self.resize_type = 0 - if 'test_image_shape' in params: - self.image_shape = params['test_image_shape'] - # print(self.image_shape) - self.resize_type = 1 - if 'max_side_len' in params: - self.max_side_len = params['max_side_len'] - else: - self.max_side_len = 2400 - - def resize_image_type0(self, im): - """ - resize image to a size multiple of 32 which is required by the network - args: - img(array): array with shape [h, w, c] - return(tuple): - img, (ratio_h, ratio_w) - """ - max_side_len = self.max_side_len - h, w, _ = im.shape - - resize_w = w - resize_h = h - - # limit the max side - if max(resize_h, resize_w) > max_side_len: - if resize_h > resize_w: - ratio = float(max_side_len) / resize_h - else: - ratio = float(max_side_len) / resize_w - else: - ratio = 1. - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - if resize_h % 32 == 0: - resize_h = resize_h - elif resize_h // 32 <= 1: - resize_h = 32 - else: - resize_h = (resize_h // 32 - 1) * 32 - if resize_w % 32 == 0: - resize_w = resize_w - elif resize_w // 32 <= 1: - resize_w = 32 - else: - resize_w = (resize_w // 32 - 1) * 32 - try: - if int(resize_w) <= 0 or int(resize_h) <= 0: - return None, (None, None) - im = cv2.resize(im, (int(resize_w), int(resize_h))) - except: - print(im.shape, resize_w, resize_h) - sys.exit(0) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - return im, (ratio_h, ratio_w) - - def resize_image_type1(self, im): - resize_h, resize_w = self.image_shape - ori_h, ori_w = im.shape[:2] # (h, w, c) - im = cv2.resize(im, (int(resize_w), int(resize_h))) - ratio_h = float(resize_h) / ori_h - ratio_w = float(resize_w) / ori_w - return im, (ratio_h, ratio_w) - - def __call__(self, im): - if self.resize_type == 0: - im, (ratio_h, ratio_w) = self.resize_image_type0(im) - else: - im, (ratio_h, ratio_w) = self.resize_image_type1(im) - img_mean = [0.485, 0.456, 0.406] - img_std = [0.229, 0.224, 0.225] - im = im[:, :, ::-1].astype(np.float32) - im = im / 255 - im -= img_mean - im /= img_std - im = im.transpose((2, 0, 1)) - im = im[np.newaxis, :] - return [im, (ratio_h, ratio_w)] diff --git a/ppocr/data/det/make_border_map.py b/ppocr/data/det/make_border_map.py deleted file mode 100644 index 55941646..00000000 --- a/ppocr/data/det/make_border_map.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding:utf-8 -*- - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -import cv2 -np.seterr(divide='ignore', invalid='ignore') -import pyclipper -from shapely.geometry import Polygon -import sys -import warnings -warnings.simplefilter("ignore") - - -def draw_border_map(polygon, canvas, mask, shrink_ratio): - polygon = np.array(polygon) - assert polygon.ndim == 2 - assert polygon.shape[1] == 2 - - polygon_shape = Polygon(polygon) - if polygon_shape.area <= 0: - return - distance = polygon_shape.area * ( - 1 - np.power(shrink_ratio, 2)) / polygon_shape.length - subject = [tuple(l) for l in polygon] - padding = pyclipper.PyclipperOffset() - padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) - - padded_polygon = np.array(padding.Execute(distance)[0]) - cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0) - - xmin = padded_polygon[:, 0].min() - xmax = padded_polygon[:, 0].max() - ymin = padded_polygon[:, 1].min() - ymax = padded_polygon[:, 1].max() - width = xmax - xmin + 1 - height = ymax - ymin + 1 - - polygon[:, 0] = polygon[:, 0] - xmin - polygon[:, 1] = polygon[:, 1] - ymin - - xs = np.broadcast_to( - np.linspace( - 0, width - 1, num=width).reshape(1, width), (height, width)) - ys = np.broadcast_to( - np.linspace( - 0, height - 1, num=height).reshape(height, 1), (height, width)) - - distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32) - for i in range(polygon.shape[0]): - j = (i + 1) % polygon.shape[0] - absolute_distance = _distance(xs, ys, polygon[i], polygon[j]) - distance_map[i] = np.clip(absolute_distance / distance, 0, 1) - distance_map = distance_map.min(axis=0) - - xmin_valid = min(max(0, xmin), canvas.shape[1] - 1) - xmax_valid = min(max(0, xmax), canvas.shape[1] - 1) - ymin_valid = min(max(0, ymin), canvas.shape[0] - 1) - ymax_valid = min(max(0, ymax), canvas.shape[0] - 1) - canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax( - 1 - distance_map[ymin_valid - ymin:ymax_valid - ymax + height, - xmin_valid - xmin:xmax_valid - xmax + width], - canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]) - - -def _distance(xs, ys, point_1, point_2): - ''' - compute the distance from point to a line - ys: coordinates in the first axis - xs: coordinates in the second axis - point_1, point_2: (x, y), the end of the line - ''' - height, width = xs.shape[:2] - square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[1]) - square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[1]) - square_distance = np.square(point_1[0] - point_2[0]) + np.square(point_1[ - 1] - point_2[1]) - - cosin = (square_distance - square_distance_1 - square_distance_2) / ( - 2 * np.sqrt(square_distance_1 * square_distance_2)) - square_sin = 1 - np.square(cosin) - square_sin = np.nan_to_num(square_sin) - result = np.sqrt(square_distance_1 * square_distance_2 * square_sin / - square_distance) - - result[cosin < - 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[cosin < - 0] - # self.extend_line(point_1, point_2, result) - return result - - -def extend_line(point_1, point_2, result, shrink_ratio): - ex_point_1 = ( - int( - round(point_1[0] + (point_1[0] - point_2[0]) * (1 + shrink_ratio))), - int( - round(point_1[1] + (point_1[1] - point_2[1]) * (1 + shrink_ratio)))) - cv2.line( - result, - tuple(ex_point_1), - tuple(point_1), - 4096.0, - 1, - lineType=cv2.LINE_AA, - shift=0) - ex_point_2 = ( - int( - round(point_2[0] + (point_2[0] - point_1[0]) * (1 + shrink_ratio))), - int( - round(point_2[1] + (point_2[1] - point_1[1]) * (1 + shrink_ratio)))) - cv2.line( - result, - tuple(ex_point_2), - tuple(point_2), - 4096.0, - 1, - lineType=cv2.LINE_AA, - shift=0) - return ex_point_1, ex_point_2 - - -def MakeBorderMap(data): - shrink_ratio = 0.4 - thresh_min = 0.3 - thresh_max = 0.7 - - im = data['image'] - text_polys = data['polys'] - ignore_tags = data['ignore_tags'] - - canvas = np.zeros(im.shape[:2], dtype=np.float32) - mask = np.zeros(im.shape[:2], dtype=np.float32) - - for i in range(len(text_polys)): - if ignore_tags[i]: - continue - draw_border_map( - text_polys[i], canvas, mask=mask, shrink_ratio=shrink_ratio) - canvas = canvas * (thresh_max - thresh_min) + thresh_min - - data['threshold_map'] = canvas - data['threshold_mask'] = mask - return data diff --git a/ppocr/data/det/make_shrink_map.py b/ppocr/data/det/make_shrink_map.py deleted file mode 100644 index dec5c6f2..00000000 --- a/ppocr/data/det/make_shrink_map.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding:utf-8 -*- - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -import cv2 -from shapely.geometry import Polygon -import pyclipper - - -def validate_polygons(polygons, ignore_tags, h, w): - ''' - polygons (numpy.array, required): of shape (num_instances, num_points, 2) - ''' - if len(polygons) == 0: - return polygons, ignore_tags - assert len(polygons) == len(ignore_tags) - for polygon in polygons: - polygon[:, 0] = np.clip(polygon[:, 0], 0, w - 1) - polygon[:, 1] = np.clip(polygon[:, 1], 0, h - 1) - - for i in range(len(polygons)): - area = polygon_area(polygons[i]) - if abs(area) < 1: - ignore_tags[i] = True - if area > 0: - polygons[i] = polygons[i][::-1, :] - return polygons, ignore_tags - - -def polygon_area(polygon): - edge = 0 - for i in range(polygon.shape[0]): - next_index = (i + 1) % polygon.shape[0] - edge += (polygon[next_index, 0] - polygon[i, 0]) * ( - polygon[next_index, 1] - polygon[i, 1]) - - return edge / 2. - - -def MakeShrinkMap(data): - min_text_size = 8 - shrink_ratio = 0.4 - - image = data['image'] - text_polys = data['polys'] - ignore_tags = data['ignore_tags'] - - h, w = image.shape[:2] - text_polys, ignore_tags = validate_polygons(text_polys, ignore_tags, h, w) - gt = np.zeros((h, w), dtype=np.float32) - # gt = np.zeros((1, h, w), dtype=np.float32) - mask = np.ones((h, w), dtype=np.float32) - for i in range(len(text_polys)): - polygon = text_polys[i] - height = max(polygon[:, 1]) - min(polygon[:, 1]) - width = max(polygon[:, 0]) - min(polygon[:, 0]) - # height = min(np.linalg.norm(polygon[0] - polygon[3]), - # np.linalg.norm(polygon[1] - polygon[2])) - # width = min(np.linalg.norm(polygon[0] - polygon[1]), - # np.linalg.norm(polygon[2] - polygon[3])) - if ignore_tags[i] or min(height, width) < min_text_size: - cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) - ignore_tags[i] = True - else: - polygon_shape = Polygon(polygon) - distance = polygon_shape.area * ( - 1 - np.power(shrink_ratio, 2)) / polygon_shape.length - subject = [tuple(l) for l in text_polys[i]] - padding = pyclipper.PyclipperOffset() - padding.AddPath(subject, pyclipper.JT_ROUND, - pyclipper.ET_CLOSEDPOLYGON) - shrinked = padding.Execute(-distance) - if shrinked == []: - cv2.fillPoly(mask, - polygon.astype(np.int32)[np.newaxis, :, :], 0) - ignore_tags[i] = True - continue - shrinked = np.array(shrinked[0]).reshape(-1, 2) - cv2.fillPoly(gt, [shrinked.astype(np.int32)], 1) - # cv2.fillPoly(gt[0], [shrinked.astype(np.int32)], 1) - - data['shrink_map'] = gt - data['shrink_mask'] = mask - return data diff --git a/ppocr/data/det/random_crop_data.py b/ppocr/data/det/random_crop_data.py deleted file mode 100644 index d0c081e7..00000000 --- a/ppocr/data/det/random_crop_data.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding:utf-8 -*- - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -import cv2 -import random - - -def is_poly_in_rect(poly, x, y, w, h): - poly = np.array(poly) - if poly[:, 0].min() < x or poly[:, 0].max() > x + w: - return False - if poly[:, 1].min() < y or poly[:, 1].max() > y + h: - return False - return True - - -def is_poly_outside_rect(poly, x, y, w, h): - poly = np.array(poly) - if poly[:, 0].max() < x or poly[:, 0].min() > x + w: - return True - if poly[:, 1].max() < y or poly[:, 1].min() > y + h: - return True - return False - - -def split_regions(axis): - regions = [] - min_axis = 0 - for i in range(1, axis.shape[0]): - if axis[i] != axis[i - 1] + 1: - region = axis[min_axis:i] - min_axis = i - regions.append(region) - return regions - - -def random_select(axis, max_size): - xx = np.random.choice(axis, size=2) - xmin = np.min(xx) - xmax = np.max(xx) - xmin = np.clip(xmin, 0, max_size - 1) - xmax = np.clip(xmax, 0, max_size - 1) - return xmin, xmax - - -def region_wise_random_select(regions, max_size): - selected_index = list(np.random.choice(len(regions), 2)) - selected_values = [] - for index in selected_index: - axis = regions[index] - xx = int(np.random.choice(axis, size=1)) - selected_values.append(xx) - xmin = min(selected_values) - xmax = max(selected_values) - return xmin, xmax - - -def crop_area(im, text_polys, min_crop_side_ratio, max_tries): - h, w, _ = im.shape - h_array = np.zeros(h, dtype=np.int32) - w_array = np.zeros(w, dtype=np.int32) - for points in text_polys: - points = np.round(points, decimals=0).astype(np.int32) - minx = np.min(points[:, 0]) - maxx = np.max(points[:, 0]) - w_array[minx:maxx] = 1 - miny = np.min(points[:, 1]) - maxy = np.max(points[:, 1]) - h_array[miny:maxy] = 1 - # ensure the cropped area not across a text - h_axis = np.where(h_array == 0)[0] - w_axis = np.where(w_array == 0)[0] - - if len(h_axis) == 0 or len(w_axis) == 0: - return 0, 0, w, h - - h_regions = split_regions(h_axis) - w_regions = split_regions(w_axis) - - for i in range(max_tries): - if len(w_regions) > 1: - xmin, xmax = region_wise_random_select(w_regions, w) - else: - xmin, xmax = random_select(w_axis, w) - if len(h_regions) > 1: - ymin, ymax = region_wise_random_select(h_regions, h) - else: - ymin, ymax = random_select(h_axis, h) - - if xmax - xmin < min_crop_side_ratio * w or ymax - ymin < min_crop_side_ratio * h: - # area too small - continue - num_poly_in_rect = 0 - for poly in text_polys: - if not is_poly_outside_rect(poly, xmin, ymin, xmax - xmin, - ymax - ymin): - num_poly_in_rect += 1 - break - - if num_poly_in_rect > 0: - return xmin, ymin, xmax - xmin, ymax - ymin - - return 0, 0, w, h - - -def RandomCropData(data, size): - max_tries = 10 - min_crop_side_ratio = 0.1 - require_original_image = False - keep_ratio = True - - im = data['image'] - text_polys = data['polys'] - ignore_tags = data['ignore_tags'] - texts = data['texts'] - all_care_polys = [ - text_polys[i] for i, tag in enumerate(ignore_tags) if not tag - ] - # 计算crop区域 - crop_x, crop_y, crop_w, crop_h = crop_area(im, all_care_polys, - min_crop_side_ratio, max_tries) - # crop 图片 保持比例填充 - scale_w = size[0] / crop_w - scale_h = size[1] / crop_h - scale = min(scale_w, scale_h) - h = int(crop_h * scale) - w = int(crop_w * scale) - if keep_ratio: - padimg = np.zeros((size[1], size[0], im.shape[2]), im.dtype) - padimg[:h, :w] = cv2.resize( - im[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w], (w, h)) - img = padimg - else: - img = cv2.resize(im[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w], - tuple(size)) - # crop 文本框 - text_polys_crop = [] - ignore_tags_crop = [] - texts_crop = [] - for poly, text, tag in zip(text_polys, texts, ignore_tags): - poly = ((poly - (crop_x, crop_y)) * scale).tolist() - if not is_poly_outside_rect(poly, 0, 0, w, h): - text_polys_crop.append(poly) - ignore_tags_crop.append(tag) - texts_crop.append(text) - data['image'] = img - data['polys'] = np.array(text_polys_crop) - data['ignore_tags'] = ignore_tags_crop - data['texts'] = texts_crop - return data diff --git a/ppocr/data/det/sast_process.py b/ppocr/data/det/sast_process.py deleted file mode 100644 index 74a84846..00000000 --- a/ppocr/data/det/sast_process.py +++ /dev/null @@ -1,781 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import math -import cv2 -import numpy as np -import json - - -class SASTProcessTrain(object): - """ - SAST process function for training - """ - def __init__(self, params): - self.img_set_dir = params['img_set_dir'] - self.min_crop_side_ratio = params['min_crop_side_ratio'] - self.min_crop_size = params['min_crop_size'] - image_shape = params['image_shape'] - self.input_size = image_shape[1] - self.min_text_size = params['min_text_size'] - self.max_text_size = params['max_text_size'] - - def convert_label_infor(self, label_infor): - label_infor = label_infor.decode() - label_infor = label_infor.encode('utf-8').decode('utf-8-sig') - substr = label_infor.strip("\n").split("\t") - img_path = self.img_set_dir + substr[0] - label = json.loads(substr[1]) - nBox = len(label) - wordBBs, txts, txt_tags = [], [], [] - for bno in range(0, nBox): - wordBB = label[bno]['points'] - txt = label[bno]['transcription'] - wordBBs.append(wordBB) - txts.append(txt) - if txt == '###': - txt_tags.append(True) - else: - txt_tags.append(False) - wordBBs = np.array(wordBBs, dtype=np.float32) - txt_tags = np.array(txt_tags, dtype=np.bool) - return img_path, wordBBs, txt_tags, txts - - def quad_area(self, poly): - """ - compute area of a polygon - :param poly: - :return: - """ - edge = [ - (poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]), - (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]), - (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]), - (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1]) - ] - return np.sum(edge) / 2. - - def gen_quad_from_poly(self, poly): - """ - Generate min area quad from poly. - """ - point_num = poly.shape[0] - min_area_quad = np.zeros((4, 2), dtype=np.float32) - if True: - rect = cv2.minAreaRect(poly.astype(np.int32)) # (center (x,y), (width, height), angle of rotation) - center_point = rect[0] - box = np.array(cv2.boxPoints(rect)) - - first_point_idx = 0 - min_dist = 1e4 - for i in range(4): - dist = np.linalg.norm(box[(i + 0) % 4] - poly[0]) + \ - np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + \ - np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + \ - np.linalg.norm(box[(i + 3) % 4] - poly[-1]) - if dist < min_dist: - min_dist = dist - first_point_idx = i - for i in range(4): - min_area_quad[i] = box[(first_point_idx + i) % 4] - - return min_area_quad - - def check_and_validate_polys(self, polys, tags, xxx_todo_changeme): - """ - check so that the text poly is in the same direction, - and also filter some invalid polygons - :param polys: - :param tags: - :return: - """ - (h, w) = xxx_todo_changeme - if polys.shape[0] == 0: - return polys, np.array([]), np.array([]) - polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1) - polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1) - - validated_polys = [] - validated_tags = [] - hv_tags = [] - for poly, tag in zip(polys, tags): - quad = self.gen_quad_from_poly(poly) - p_area = self.quad_area(quad) - if abs(p_area) < 1: - print('invalid poly') - continue - if p_area > 0: - if tag == False: - print('poly in wrong direction') - tag = True # reversed cases should be ignore - poly = poly[(0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), :] - quad = quad[(0, 3, 2, 1), :] - - len_w = np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[3] - quad[2]) - len_h = np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2]) - hv_tag = 1 - - if len_w * 2.0 < len_h: - hv_tag = 0 - - validated_polys.append(poly) - validated_tags.append(tag) - hv_tags.append(hv_tag) - return np.array(validated_polys), np.array(validated_tags), np.array(hv_tags) - - def crop_area(self, im, polys, tags, hv_tags, txts, crop_background=False, max_tries=25): - """ - make random crop from the input image - :param im: - :param polys: - :param tags: - :param crop_background: - :param max_tries: 50 -> 25 - :return: - """ - h, w, _ = im.shape - pad_h = h // 10 - pad_w = w // 10 - h_array = np.zeros((h + pad_h * 2), dtype=np.int32) - w_array = np.zeros((w + pad_w * 2), dtype=np.int32) - for poly in polys: - poly = np.round(poly, decimals=0).astype(np.int32) - minx = np.min(poly[:, 0]) - maxx = np.max(poly[:, 0]) - w_array[minx + pad_w: maxx + pad_w] = 1 - miny = np.min(poly[:, 1]) - maxy = np.max(poly[:, 1]) - h_array[miny + pad_h: maxy + pad_h] = 1 - # ensure the cropped area not across a text - h_axis = np.where(h_array == 0)[0] - w_axis = np.where(w_array == 0)[0] - if len(h_axis) == 0 or len(w_axis) == 0: - return im, polys, tags, hv_tags, txts - for i in range(max_tries): - xx = np.random.choice(w_axis, size=2) - xmin = np.min(xx) - pad_w - xmax = np.max(xx) - pad_w - xmin = np.clip(xmin, 0, w - 1) - xmax = np.clip(xmax, 0, w - 1) - yy = np.random.choice(h_axis, size=2) - ymin = np.min(yy) - pad_h - ymax = np.max(yy) - pad_h - ymin = np.clip(ymin, 0, h - 1) - ymax = np.clip(ymax, 0, h - 1) - # if xmax - xmin < ARGS.min_crop_side_ratio * w or \ - # ymax - ymin < ARGS.min_crop_side_ratio * h: - if xmax - xmin < self.min_crop_size or \ - ymax - ymin < self.min_crop_size: - # area too small - continue - if polys.shape[0] != 0: - poly_axis_in_area = (polys[:, :, 0] >= xmin) & (polys[:, :, 0] <= xmax) \ - & (polys[:, :, 1] >= ymin) & (polys[:, :, 1] <= ymax) - selected_polys = np.where(np.sum(poly_axis_in_area, axis=1) == 4)[0] - else: - selected_polys = [] - if len(selected_polys) == 0: - # no text in this area - if crop_background: - txts_tmp = [] - for selected_poly in selected_polys: - txts_tmp.append(txts[selected_poly]) - txts = txts_tmp - return im[ymin : ymax + 1, xmin : xmax + 1, :], \ - polys[selected_polys], tags[selected_polys], hv_tags[selected_polys], txts - else: - continue - im = im[ymin: ymax + 1, xmin: xmax + 1, :] - polys = polys[selected_polys] - tags = tags[selected_polys] - hv_tags = hv_tags[selected_polys] - txts_tmp = [] - for selected_poly in selected_polys: - txts_tmp.append(txts[selected_poly]) - txts = txts_tmp - polys[:, :, 0] -= xmin - polys[:, :, 1] -= ymin - return im, polys, tags, hv_tags, txts - - return im, polys, tags, hv_tags, txts - - def generate_direction_map(self, poly_quads, direction_map): - """ - """ - width_list = [] - height_list = [] - for quad in poly_quads: - quad_w = (np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3])) / 2.0 - quad_h = (np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1])) / 2.0 - width_list.append(quad_w) - height_list.append(quad_h) - norm_width = max(sum(width_list) / (len(width_list) + 1e-6), 1.0) - average_height = max(sum(height_list) / (len(height_list) + 1e-6), 1.0) - - for quad in poly_quads: - direct_vector_full = ((quad[1] + quad[2]) - (quad[0] + quad[3])) / 2.0 - direct_vector = direct_vector_full / (np.linalg.norm(direct_vector_full) + 1e-6) * norm_width - direction_label = tuple(map(float, [direct_vector[0], direct_vector[1], 1.0 / (average_height + 1e-6)])) - cv2.fillPoly(direction_map, quad.round().astype(np.int32)[np.newaxis, :, :], direction_label) - return direction_map - - def calculate_average_height(self, poly_quads): - """ - """ - height_list = [] - for quad in poly_quads: - quad_h = (np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1])) / 2.0 - height_list.append(quad_h) - average_height = max(sum(height_list) / len(height_list), 1.0) - return average_height - - def generate_tcl_label(self, hw, polys, tags, ds_ratio, - tcl_ratio=0.3, shrink_ratio_of_width=0.15): - """ - Generate polygon. - """ - h, w = hw - h, w = int(h * ds_ratio), int(w * ds_ratio) - polys = polys * ds_ratio - - score_map = np.zeros((h, w,), dtype=np.float32) - tbo_map = np.zeros((h, w, 5), dtype=np.float32) - training_mask = np.ones((h, w,), dtype=np.float32) - direction_map = np.ones((h, w, 3)) * np.array([0, 0, 1]).reshape([1, 1, 3]).astype(np.float32) - - for poly_idx, poly_tag in enumerate(zip(polys, tags)): - poly = poly_tag[0] - tag = poly_tag[1] - - # generate min_area_quad - min_area_quad, center_point = self.gen_min_area_quad_from_poly(poly) - min_area_quad_h = 0.5 * (np.linalg.norm(min_area_quad[0] - min_area_quad[3]) + - np.linalg.norm(min_area_quad[1] - min_area_quad[2])) - min_area_quad_w = 0.5 * (np.linalg.norm(min_area_quad[0] - min_area_quad[1]) + - np.linalg.norm(min_area_quad[2] - min_area_quad[3])) - - if min(min_area_quad_h, min_area_quad_w) < self.min_text_size * ds_ratio \ - or min(min_area_quad_h, min_area_quad_w) > self.max_text_size * ds_ratio: - continue - - if tag: - # continue - cv2.fillPoly(training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0.15) - else: - tcl_poly = self.poly2tcl(poly, tcl_ratio) - tcl_quads = self.poly2quads(tcl_poly) - poly_quads = self.poly2quads(poly) - # stcl map - stcl_quads, quad_index = self.shrink_poly_along_width(tcl_quads, shrink_ratio_of_width=shrink_ratio_of_width, - expand_height_ratio=1.0 / tcl_ratio) - # generate tcl map - cv2.fillPoly(score_map, np.round(stcl_quads).astype(np.int32), 1.0) - - # generate tbo map - for idx, quad in enumerate(stcl_quads): - quad_mask = np.zeros((h, w), dtype=np.float32) - quad_mask = cv2.fillPoly(quad_mask, np.round(quad[np.newaxis, :, :]).astype(np.int32), 1.0) - tbo_map = self.gen_quad_tbo(poly_quads[quad_index[idx]], quad_mask, tbo_map) - return score_map, tbo_map, training_mask - - def generate_tvo_and_tco(self, hw, polys, tags, tcl_ratio=0.3, ds_ratio=0.25): - """ - Generate tcl map, tvo map and tbo map. - """ - h, w = hw - h, w = int(h * ds_ratio), int(w * ds_ratio) - polys = polys * ds_ratio - poly_mask = np.zeros((h, w), dtype=np.float32) - - tvo_map = np.ones((9, h, w), dtype=np.float32) - tvo_map[0:-1:2] = np.tile(np.arange(0, w), (h, 1)) - tvo_map[1:-1:2] = np.tile(np.arange(0, w), (h, 1)).T - poly_tv_xy_map = np.zeros((8, h, w), dtype=np.float32) - - # tco map - tco_map = np.ones((3, h, w), dtype=np.float32) - tco_map[0] = np.tile(np.arange(0, w), (h, 1)) - tco_map[1] = np.tile(np.arange(0, w), (h, 1)).T - poly_tc_xy_map = np.zeros((2, h, w), dtype=np.float32) - - poly_short_edge_map = np.ones((h, w), dtype=np.float32) - - for poly, poly_tag in zip(polys, tags): - - if poly_tag == True: - continue - - # adjust point order for vertical poly - poly = self.adjust_point(poly) - - # generate min_area_quad - min_area_quad, center_point = self.gen_min_area_quad_from_poly(poly) - min_area_quad_h = 0.5 * (np.linalg.norm(min_area_quad[0] - min_area_quad[3]) + - np.linalg.norm(min_area_quad[1] - min_area_quad[2])) - min_area_quad_w = 0.5 * (np.linalg.norm(min_area_quad[0] - min_area_quad[1]) + - np.linalg.norm(min_area_quad[2] - min_area_quad[3])) - - # generate tcl map and text, 128 * 128 - tcl_poly = self.poly2tcl(poly, tcl_ratio) - - # generate poly_tv_xy_map - for idx in range(4): - cv2.fillPoly(poly_tv_xy_map[2 * idx], - np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), - float(min(max(min_area_quad[idx, 0], 0), w))) - cv2.fillPoly(poly_tv_xy_map[2 * idx + 1], - np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), - float(min(max(min_area_quad[idx, 1], 0), h))) - - # generate poly_tc_xy_map - for idx in range(2): - cv2.fillPoly(poly_tc_xy_map[idx], - np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), float(center_point[idx])) - - # generate poly_short_edge_map - cv2.fillPoly(poly_short_edge_map, - np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), - float(max(min(min_area_quad_h, min_area_quad_w), 1.0))) - - # generate poly_mask and training_mask - cv2.fillPoly(poly_mask, np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), 1) - - tvo_map *= poly_mask - tvo_map[:8] -= poly_tv_xy_map - tvo_map[-1] /= poly_short_edge_map - tvo_map = tvo_map.transpose((1, 2, 0)) - - tco_map *= poly_mask - tco_map[:2] -= poly_tc_xy_map - tco_map[-1] /= poly_short_edge_map - tco_map = tco_map.transpose((1, 2, 0)) - - return tvo_map, tco_map - - def adjust_point(self, poly): - """ - adjust point order. - """ - point_num = poly.shape[0] - if point_num == 4: - len_1 = np.linalg.norm(poly[0] - poly[1]) - len_2 = np.linalg.norm(poly[1] - poly[2]) - len_3 = np.linalg.norm(poly[2] - poly[3]) - len_4 = np.linalg.norm(poly[3] - poly[0]) - - if (len_1 + len_3) * 1.5 < (len_2 + len_4): - poly = poly[[1, 2, 3, 0], :] - - elif point_num > 4: - vector_1 = poly[0] - poly[1] - vector_2 = poly[1] - poly[2] - cos_theta = np.dot(vector_1, vector_2) / (np.linalg.norm(vector_1) * np.linalg.norm(vector_2) + 1e-6) - theta = np.arccos(np.round(cos_theta, decimals=4)) - - if abs(theta) > (70 / 180 * math.pi): - index = list(range(1, point_num)) + [0] - poly = poly[np.array(index), :] - return poly - - def gen_min_area_quad_from_poly(self, poly): - """ - Generate min area quad from poly. - """ - point_num = poly.shape[0] - min_area_quad = np.zeros((4, 2), dtype=np.float32) - if point_num == 4: - min_area_quad = poly - center_point = np.sum(poly, axis=0) / 4 - else: - rect = cv2.minAreaRect(poly.astype(np.int32)) # (center (x,y), (width, height), angle of rotation) - center_point = rect[0] - box = np.array(cv2.boxPoints(rect)) - - first_point_idx = 0 - min_dist = 1e4 - for i in range(4): - dist = np.linalg.norm(box[(i + 0) % 4] - poly[0]) + \ - np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + \ - np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + \ - np.linalg.norm(box[(i + 3) % 4] - poly[-1]) - if dist < min_dist: - min_dist = dist - first_point_idx = i - - for i in range(4): - min_area_quad[i] = box[(first_point_idx + i) % 4] - - return min_area_quad, center_point - - def shrink_quad_along_width(self, quad, begin_width_ratio=0., end_width_ratio=1.): - """ - Generate shrink_quad_along_width. - """ - ratio_pair = np.array([[begin_width_ratio], [end_width_ratio]], dtype=np.float32) - p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair - p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair - return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) - - def shrink_poly_along_width(self, quads, shrink_ratio_of_width, expand_height_ratio=1.0): - """ - shrink poly with given length. - """ - upper_edge_list = [] - - def get_cut_info(edge_len_list, cut_len): - for idx, edge_len in enumerate(edge_len_list): - cut_len -= edge_len - if cut_len <= 0.000001: - ratio = (cut_len + edge_len_list[idx]) / edge_len_list[idx] - return idx, ratio - - for quad in quads: - upper_edge_len = np.linalg.norm(quad[0] - quad[1]) - upper_edge_list.append(upper_edge_len) - - # length of left edge and right edge. - left_length = np.linalg.norm(quads[0][0] - quads[0][3]) * expand_height_ratio - right_length = np.linalg.norm(quads[-1][1] - quads[-1][2]) * expand_height_ratio - - shrink_length = min(left_length, right_length, sum(upper_edge_list)) * shrink_ratio_of_width - # shrinking length - upper_len_left = shrink_length - upper_len_right = sum(upper_edge_list) - shrink_length - - left_idx, left_ratio = get_cut_info(upper_edge_list, upper_len_left) - left_quad = self.shrink_quad_along_width(quads[left_idx], begin_width_ratio=left_ratio, end_width_ratio=1) - right_idx, right_ratio = get_cut_info(upper_edge_list, upper_len_right) - right_quad = self.shrink_quad_along_width(quads[right_idx], begin_width_ratio=0, end_width_ratio=right_ratio) - - out_quad_list = [] - if left_idx == right_idx: - out_quad_list.append([left_quad[0], right_quad[1], right_quad[2], left_quad[3]]) - else: - out_quad_list.append(left_quad) - for idx in range(left_idx + 1, right_idx): - out_quad_list.append(quads[idx]) - out_quad_list.append(right_quad) - - return np.array(out_quad_list), list(range(left_idx, right_idx + 1)) - - def vector_angle(self, A, B): - """ - Calculate the angle between vector AB and x-axis positive direction. - """ - AB = np.array([B[1] - A[1], B[0] - A[0]]) - return np.arctan2(*AB) - - def theta_line_cross_point(self, theta, point): - """ - Calculate the line through given point and angle in ax + by + c =0 form. - """ - x, y = point - cos = np.cos(theta) - sin = np.sin(theta) - return [sin, -cos, cos * y - sin * x] - - def line_cross_two_point(self, A, B): - """ - Calculate the line through given point A and B in ax + by + c =0 form. - """ - angle = self.vector_angle(A, B) - return self.theta_line_cross_point(angle, A) - - def average_angle(self, poly): - """ - Calculate the average angle between left and right edge in given poly. - """ - p0, p1, p2, p3 = poly - angle30 = self.vector_angle(p3, p0) - angle21 = self.vector_angle(p2, p1) - return (angle30 + angle21) / 2 - - def line_cross_point(self, line1, line2): - """ - line1 and line2 in 0=ax+by+c form, compute the cross point of line1 and line2 - """ - a1, b1, c1 = line1 - a2, b2, c2 = line2 - d = a1 * b2 - a2 * b1 - - if d == 0: - #print("line1", line1) - #print("line2", line2) - print('Cross point does not exist') - return np.array([0, 0], dtype=np.float32) - else: - x = (b1 * c2 - b2 * c1) / d - y = (a2 * c1 - a1 * c2) / d - - return np.array([x, y], dtype=np.float32) - - def quad2tcl(self, poly, ratio): - """ - Generate center line by poly clock-wise point. (4, 2) - """ - ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) - p0_3 = poly[0] + (poly[3] - poly[0]) * ratio_pair - p1_2 = poly[1] + (poly[2] - poly[1]) * ratio_pair - return np.array([p0_3[0], p1_2[0], p1_2[1], p0_3[1]]) - - def poly2tcl(self, poly, ratio): - """ - Generate center line by poly clock-wise point. - """ - ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) - tcl_poly = np.zeros_like(poly) - point_num = poly.shape[0] - - for idx in range(point_num // 2): - point_pair = poly[idx] + (poly[point_num - 1 - idx] - poly[idx]) * ratio_pair - tcl_poly[idx] = point_pair[0] - tcl_poly[point_num - 1 - idx] = point_pair[1] - return tcl_poly - - def gen_quad_tbo(self, quad, tcl_mask, tbo_map): - """ - Generate tbo_map for give quad. - """ - # upper and lower line function: ax + by + c = 0; - up_line = self.line_cross_two_point(quad[0], quad[1]) - lower_line = self.line_cross_two_point(quad[3], quad[2]) - - quad_h = 0.5 * (np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2])) - quad_w = 0.5 * (np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3])) - - # average angle of left and right line. - angle = self.average_angle(quad) - - xy_in_poly = np.argwhere(tcl_mask == 1) - for y, x in xy_in_poly: - point = (x, y) - line = self.theta_line_cross_point(angle, point) - cross_point_upper = self.line_cross_point(up_line, line) - cross_point_lower = self.line_cross_point(lower_line, line) - ##FIX, offset reverse - upper_offset_x, upper_offset_y = cross_point_upper - point - lower_offset_x, lower_offset_y = cross_point_lower - point - tbo_map[y, x, 0] = upper_offset_y - tbo_map[y, x, 1] = upper_offset_x - tbo_map[y, x, 2] = lower_offset_y - tbo_map[y, x, 3] = lower_offset_x - tbo_map[y, x, 4] = 1.0 / max(min(quad_h, quad_w), 1.0) * 2 - return tbo_map - - def poly2quads(self, poly): - """ - Split poly into quads. - """ - quad_list = [] - point_num = poly.shape[0] - - # point pair - point_pair_list = [] - for idx in range(point_num // 2): - point_pair = [poly[idx], poly[point_num - 1 - idx]] - point_pair_list.append(point_pair) - - quad_num = point_num // 2 - 1 - for idx in range(quad_num): - # reshape and adjust to clock-wise - quad_list.append((np.array(point_pair_list)[[idx, idx + 1]]).reshape(4, 2)[[0, 2, 3, 1]]) - - return np.array(quad_list) - - def extract_polys(self, poly_txt_path): - """ - Read text_polys, txt_tags, txts from give txt file. - """ - text_polys, txt_tags, txts = [], [], [] - - with open(poly_txt_path) as f: - for line in f.readlines(): - poly_str, txt = line.strip().split('\t') - poly = map(float, poly_str.split(',')) - text_polys.append(np.array(poly, dtype=np.float32).reshape(-1, 2)) - txts.append(txt) - if txt == '###': - txt_tags.append(True) - else: - txt_tags.append(False) - - return np.array(map(np.array, text_polys)), \ - np.array(txt_tags, dtype=np.bool), txts - - def __call__(self, label_infor): - infor = self.convert_label_infor(label_infor) - im_path, text_polys, text_tags, text_strs = infor - im = cv2.imread(im_path) - if im is None: - return None - if text_polys.shape[0] == 0: - return None - - h, w, _ = im.shape - text_polys, text_tags, hv_tags = self.check_and_validate_polys(text_polys, text_tags, (h, w)) - - if text_polys.shape[0] == 0: - return None - - #set aspect ratio and keep area fix - asp_scales = np.arange(1.0, 1.55, 0.1) - asp_scale = np.random.choice(asp_scales) - - if np.random.rand() < 0.5: - asp_scale = 1.0 / asp_scale - asp_scale = math.sqrt(asp_scale) - - asp_wx = asp_scale - asp_hy = 1.0 / asp_scale - im = cv2.resize(im, dsize=None, fx=asp_wx, fy=asp_hy) - text_polys[:, :, 0] *= asp_wx - text_polys[:, :, 1] *= asp_hy - - h, w, _ = im.shape - if max(h, w) > 2048: - rd_scale = 2048.0 / max(h, w) - im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) - text_polys *= rd_scale - h, w, _ = im.shape - if min(h, w) < 16: - return None - - #no background - im, text_polys, text_tags, hv_tags, text_strs = self.crop_area(im, \ - text_polys, text_tags, hv_tags, text_strs, crop_background=False) - if text_polys.shape[0] == 0: - return None - #continue for all ignore case - if np.sum((text_tags * 1.0)) >= text_tags.size: - return None - new_h, new_w, _ = im.shape - if (new_h is None) or (new_w is None): - return None - #resize image - std_ratio = float(self.input_size) / max(new_w, new_h) - rand_scales = np.array([0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.0, 1.0, 1.0, 1.0]) - rz_scale = std_ratio * np.random.choice(rand_scales) - im = cv2.resize(im, dsize=None, fx=rz_scale, fy=rz_scale) - text_polys[:, :, 0] *= rz_scale - text_polys[:, :, 1] *= rz_scale - - #add gaussian blur - if np.random.rand() < 0.1 * 0.5: - ks = np.random.permutation(5)[0] + 1 - ks = int(ks/2)*2 + 1 - im = cv2.GaussianBlur(im, ksize=(ks, ks), sigmaX=0, sigmaY=0) - #add brighter - if np.random.rand() < 0.1 * 0.5: - im = im * (1.0 + np.random.rand() * 0.5) - im = np.clip(im, 0.0, 255.0) - #add darker - if np.random.rand() < 0.1 * 0.5: - im = im * (1.0 - np.random.rand() * 0.5) - im = np.clip(im, 0.0, 255.0) - - # Padding the im to [input_size, input_size] - new_h, new_w, _ = im.shape - if min(new_w, new_h) < self.input_size * 0.5: - return None - - im_padded = np.ones((self.input_size, self.input_size, 3), dtype=np.float32) - im_padded[:, :, 2] = 0.485 * 255 - im_padded[:, :, 1] = 0.456 * 255 - im_padded[:, :, 0] = 0.406 * 255 - - # Random the start position - del_h = self.input_size - new_h - del_w = self.input_size - new_w - sh, sw = 0, 0 - if del_h > 1: - sh = int(np.random.rand() * del_h) - if del_w > 1: - sw = int(np.random.rand() * del_w) - - # Padding - im_padded[sh: sh + new_h, sw: sw + new_w, :] = im.copy() - text_polys[:, :, 0] += sw - text_polys[:, :, 1] += sh - - score_map, border_map, training_mask = self.generate_tcl_label((self.input_size, self.input_size), - text_polys, text_tags, 0.25) - - # SAST head - tvo_map, tco_map = self.generate_tvo_and_tco((self.input_size, self.input_size), text_polys, text_tags, tcl_ratio=0.3, ds_ratio=0.25) - # print("test--------tvo_map shape:", tvo_map.shape) - - im_padded[:, :, 2] -= 0.485 * 255 - im_padded[:, :, 1] -= 0.456 * 255 - im_padded[:, :, 0] -= 0.406 * 255 - im_padded[:, :, 2] /= (255.0 * 0.229) - im_padded[:, :, 1] /= (255.0 * 0.224) - im_padded[:, :, 0] /= (255.0 * 0.225) - im_padded = im_padded.transpose((2, 0, 1)) - - return im_padded[::-1, :, :], score_map[np.newaxis, :, :], border_map.transpose((2, 0, 1)), training_mask[np.newaxis, :, :], tvo_map.transpose((2, 0, 1)), tco_map.transpose((2, 0, 1)) - - -class SASTProcessTest(object): - """ - SAST process function for test - """ - def __init__(self, params): - super(SASTProcessTest, self).__init__() - if 'max_side_len' in params: - self.max_side_len = params['max_side_len'] - else: - self.max_side_len = 2400 - - def resize_image(self, im): - """ - resize image to a size multiple of max_stride which is required by the network - :param im: the resized image - :param max_side_len: limit of max image size to avoid out of memory in gpu - :return: the resized image and the resize ratio - """ - h, w, _ = im.shape - - resize_w = w - resize_h = h - - # Fix the longer side - if resize_h > resize_w: - ratio = float(self.max_side_len) / resize_h - else: - ratio = float(self.max_side_len) / resize_w - - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - - max_stride = 128 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - im = cv2.resize(im, (int(resize_w), int(resize_h))) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - - return im, (ratio_h, ratio_w) - - def __call__(self, im): - src_h, src_w, _ = im.shape - im, (ratio_h, ratio_w) = self.resize_image(im) - img_mean = [0.485, 0.456, 0.406] - img_std = [0.229, 0.224, 0.225] - im = im[:, :, ::-1].astype(np.float32) - im = im / 255 - im -= img_mean - im /= img_std - im = im.transpose((2, 0, 1)) - im = im[np.newaxis, :] - return [im, (ratio_h, ratio_w, src_h, src_w)] diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py new file mode 100644 index 00000000..047664bf --- /dev/null +++ b/ppocr/data/imaug/__init__.py @@ -0,0 +1,59 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from .iaa_augment import IaaAugment +from .make_border_map import MakeBorderMap +from .make_shrink_map import MakeShrinkMap +from .random_crop_data import EastRandomCropData, PSERandomCrop + +from .rec_img_aug import RecAug, RecResizeImg + +from .operators import * +from .label_ops import * + + +def transform(data, ops=None): + """ transform """ + if ops is None: + ops = [] + for op in ops: + data = op(data) + if data is None: + return None + return data + + +def create_operators(op_param_list, global_config=None): + """ + create operators based on the config + + Args: + params(list): a dict list, used to create some operators + """ + assert isinstance(op_param_list, list), ('operator config should be a list') + ops = [] + for operator in op_param_list: + assert isinstance(operator, + dict) and len(operator) == 1, "yaml format error" + op_name = list(operator)[0] + param = {} if operator[op_name] is None else operator[op_name] + if global_config is not None: + param.update(global_config) + op = eval(op_name)(**param) + ops.append(op) + return ops diff --git a/ppocr/data/imaug/iaa_augment.py b/ppocr/data/imaug/iaa_augment.py new file mode 100644 index 00000000..9ce6bd42 --- /dev/null +++ b/ppocr/data/imaug/iaa_augment.py @@ -0,0 +1,101 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import imgaug +import imgaug.augmenters as iaa + + +class AugmenterBuilder(object): + def __init__(self): + pass + + def build(self, args, root=True): + if args is None or len(args) == 0: + return None + elif isinstance(args, list): + if root: + sequence = [self.build(value, root=False) for value in args] + return iaa.Sequential(sequence) + else: + return getattr(iaa, args[0])( + *[self.to_tuple_if_list(a) for a in args[1:]]) + elif isinstance(args, dict): + cls = getattr(iaa, args['type']) + return cls(**{ + k: self.to_tuple_if_list(v) + for k, v in args['args'].items() + }) + else: + raise RuntimeError('unknown augmenter arg: ' + str(args)) + + def to_tuple_if_list(self, obj): + if isinstance(obj, list): + return tuple(obj) + return obj + + +class IaaAugment(): + def __init__(self, augmenter_args=None, **kwargs): + if augmenter_args is None: + augmenter_args = [{ + 'type': 'Fliplr', + 'args': { + 'p': 0.5 + } + }, { + 'type': 'Affine', + 'args': { + 'rotate': [-10, 10] + } + }, { + 'type': 'Resize', + 'args': { + 'size': [0.5, 3] + } + }] + self.augmenter = AugmenterBuilder().build(augmenter_args) + + def __call__(self, data): + image = data['image'] + shape = image.shape + + if self.augmenter: + aug = self.augmenter.to_deterministic() + data['image'] = aug.augment_image(image) + data = self.may_augment_annotation(aug, data, shape) + return data + + def may_augment_annotation(self, aug, data, shape): + if aug is None: + return data + + line_polys = [] + for poly in data['polys']: + new_poly = self.may_augment_poly(aug, shape, poly) + line_polys.append(new_poly) + data['polys'] = np.array(line_polys) + return data + + def may_augment_poly(self, aug, img_shape, poly): + keypoints = [imgaug.Keypoint(p[0], p[1]) for p in poly] + keypoints = aug.augment_keypoints( + [imgaug.KeypointsOnImage( + keypoints, shape=img_shape)])[0].keypoints + poly = [(p.x, p.y) for p in keypoints] + return poly diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py new file mode 100644 index 00000000..72038c8f --- /dev/null +++ b/ppocr/data/imaug/label_ops.py @@ -0,0 +1,197 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +from ppocr.utils.logging import get_logger + + +class DetLabelEncode(object): + def __init__(self, **kwargs): + pass + + def __call__(self, data): + import json + label = data['label'] + label = json.loads(label) + nBox = len(label) + boxes, txts, txt_tags = [], [], [] + for bno in range(0, nBox): + box = label[bno]['points'] + txt = label[bno]['transcription'] + boxes.append(box) + txts.append(txt) + if txt in ['*', '###']: + txt_tags.append(True) + else: + txt_tags.append(False) + boxes = np.array(boxes, dtype=np.float32) + txt_tags = np.array(txt_tags, dtype=np.bool) + + data['polys'] = boxes + data['texts'] = txts + data['ignore_tags'] = txt_tags + return data + + def order_points_clockwise(self, pts): + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + diff = np.diff(pts, axis=1) + rect[1] = pts[np.argmin(diff)] + rect[3] = pts[np.argmax(diff)] + return rect + + +class BaseRecLabelEncode(object): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + character_type='ch', + use_space_char=False): + support_character_type = ['ch', 'en', 'en_sensitive'] + assert character_type in support_character_type, "Only {} are supported now but get {}".format( + support_character_type, self.character_str) + + self.max_text_len = max_text_length + if character_type == "en": + self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz" + dict_character = list(self.character_str) + elif character_type == "ch": + self.character_str = "" + assert character_dict_path is not None, "character_dict_path should not be None when character_type is ch" + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode('utf-8').strip("\n").strip("\r\n") + self.character_str += line + if use_space_char: + self.character_str += " " + dict_character = list(self.character_str) + elif character_type == "en_sensitive": + # same with ASTER setting (use 94 char). + import string + self.character_str = string.printable[:-6] + dict_character = list(self.character_str) + self.character_type = character_type + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.character = dict_character + + def add_special_char(self, dict_character): + return dict_character + + def encode(self, text): + """convert text-label into text-index. + input: + text: text labels of each image. [batch_size] + + output: + text: concatenated text index for CTCLoss. + [sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)] + length: length of each text. [batch_size] + """ + if len(text) > self.max_text_len: + return None + if self.character_type == "en": + text = text.lower() + text_list = [] + for char in text: + if char not in self.dict: + # logger = get_logger() + # logger.warning('{} is not in dict'.format(char)) + continue + text_list.append(self.dict[char]) + if len(text_list) == 0: + return None + return text_list + + def get_ignored_tokens(self): + return [0] # for ctc blank + + +class CTCLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + character_type='ch', + use_space_char=False, + **kwargs): + super(CTCLabelEncode, + self).__init__(max_text_length, character_dict_path, + character_type, use_space_char) + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + data['length'] = np.array(len(text)) + text = text + [0] * (self.max_text_len - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['blank'] + dict_character + return dict_character + + +class AttnLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + character_type='ch', + use_space_char=False, + **kwargs): + super(AttnLabelEncode, + self).__init__(max_text_length, character_dict_path, + character_type, use_space_char) + self.beg_str = "sos" + self.end_str = "eos" + + def add_special_char(self, dict_character): + dict_character = [self.beg_str, self.end_str] + dict_character + return dict_character + + def __call__(self, text): + text = self.encode(text) + return text + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "Unsupport type %s in get_beg_end_flag_idx" \ + % beg_or_end + return idx diff --git a/ppocr/data/imaug/make_border_map.py b/ppocr/data/imaug/make_border_map.py new file mode 100644 index 00000000..df53e04d --- /dev/null +++ b/ppocr/data/imaug/make_border_map.py @@ -0,0 +1,157 @@ +# -*- coding:utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 + +np.seterr(divide='ignore', invalid='ignore') +import pyclipper +from shapely.geometry import Polygon +import sys +import warnings + +warnings.simplefilter("ignore") + +__all__ = ['MakeBorderMap'] + + +class MakeBorderMap(object): + def __init__(self, + shrink_ratio=0.4, + thresh_min=0.3, + thresh_max=0.7, + **kwargs): + self.shrink_ratio = shrink_ratio + self.thresh_min = thresh_min + self.thresh_max = thresh_max + + def __call__(self, data: dict) -> dict: + + img = data['image'] + text_polys = data['polys'] + ignore_tags = data['ignore_tags'] + + canvas = np.zeros(img.shape[:2], dtype=np.float32) + mask = np.zeros(img.shape[:2], dtype=np.float32) + + for i in range(len(text_polys)): + if ignore_tags[i]: + continue + self.draw_border_map(text_polys[i], canvas, mask=mask) + canvas = canvas * (self.thresh_max - self.thresh_min) + self.thresh_min + + data['threshold_map'] = canvas + data['threshold_mask'] = mask + return data + + def draw_border_map(self, polygon, canvas, mask): + polygon = np.array(polygon) + assert polygon.ndim == 2 + assert polygon.shape[1] == 2 + + polygon_shape = Polygon(polygon) + if polygon_shape.area <= 0: + return + distance = polygon_shape.area * ( + 1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + + padded_polygon = np.array(padding.Execute(distance)[0]) + cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0) + + xmin = padded_polygon[:, 0].min() + xmax = padded_polygon[:, 0].max() + ymin = padded_polygon[:, 1].min() + ymax = padded_polygon[:, 1].max() + width = xmax - xmin + 1 + height = ymax - ymin + 1 + + polygon[:, 0] = polygon[:, 0] - xmin + polygon[:, 1] = polygon[:, 1] - ymin + + xs = np.broadcast_to( + np.linspace( + 0, width - 1, num=width).reshape(1, width), (height, width)) + ys = np.broadcast_to( + np.linspace( + 0, height - 1, num=height).reshape(height, 1), (height, width)) + + distance_map = np.zeros( + (polygon.shape[0], height, width), dtype=np.float32) + for i in range(polygon.shape[0]): + j = (i + 1) % polygon.shape[0] + absolute_distance = self._distance(xs, ys, polygon[i], polygon[j]) + distance_map[i] = np.clip(absolute_distance / distance, 0, 1) + distance_map = distance_map.min(axis=0) + + xmin_valid = min(max(0, xmin), canvas.shape[1] - 1) + xmax_valid = min(max(0, xmax), canvas.shape[1] - 1) + ymin_valid = min(max(0, ymin), canvas.shape[0] - 1) + ymax_valid = min(max(0, ymax), canvas.shape[0] - 1) + canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax( + 1 - distance_map[ymin_valid - ymin:ymax_valid - ymax + height, + xmin_valid - xmin:xmax_valid - xmax + width], + canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]) + + def _distance(self, xs, ys, point_1, point_2): + ''' + compute the distance from point to a line + ys: coordinates in the first axis + xs: coordinates in the second axis + point_1, point_2: (x, y), the end of the line + ''' + height, width = xs.shape[:2] + square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[ + 1]) + square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[ + 1]) + square_distance = np.square(point_1[0] - point_2[0]) + np.square( + point_1[1] - point_2[1]) + + cosin = (square_distance - square_distance_1 - square_distance_2) / ( + 2 * np.sqrt(square_distance_1 * square_distance_2)) + square_sin = 1 - np.square(cosin) + square_sin = np.nan_to_num(square_sin) + result = np.sqrt(square_distance_1 * square_distance_2 * square_sin / + square_distance) + + result[cosin < + 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[cosin + < 0] + # self.extend_line(point_1, point_2, result) + return result + + def extend_line(self, point_1, point_2, result, shrink_ratio): + ex_point_1 = (int( + round(point_1[0] + (point_1[0] - point_2[0]) * (1 + shrink_ratio))), + int( + round(point_1[1] + (point_1[1] - point_2[1]) * ( + 1 + shrink_ratio)))) + cv2.line( + result, + tuple(ex_point_1), + tuple(point_1), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0) + ex_point_2 = (int( + round(point_2[0] + (point_2[0] - point_1[0]) * (1 + shrink_ratio))), + int( + round(point_2[1] + (point_2[1] - point_1[1]) * ( + 1 + shrink_ratio)))) + cv2.line( + result, + tuple(ex_point_2), + tuple(point_2), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0) + return ex_point_1, ex_point_2 diff --git a/ppocr/data/imaug/make_shrink_map.py b/ppocr/data/imaug/make_shrink_map.py new file mode 100644 index 00000000..a66706f2 --- /dev/null +++ b/ppocr/data/imaug/make_shrink_map.py @@ -0,0 +1,94 @@ +# -*- coding:utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 +from shapely.geometry import Polygon +import pyclipper + +__all__ = ['MakeShrinkMap'] + + +class MakeShrinkMap(object): + r''' + Making binary mask from detection data with ICDAR format. + Typically following the process of class `MakeICDARData`. + ''' + + def __init__(self, min_text_size=8, shrink_ratio=0.4, **kwargs): + self.min_text_size = min_text_size + self.shrink_ratio = shrink_ratio + + def __call__(self, data): + image = data['image'] + text_polys = data['polys'] + ignore_tags = data['ignore_tags'] + + h, w = image.shape[:2] + text_polys, ignore_tags = self.validate_polygons(text_polys, + ignore_tags, h, w) + gt = np.zeros((h, w), dtype=np.float32) + # gt = np.zeros((1, h, w), dtype=np.float32) + mask = np.ones((h, w), dtype=np.float32) + for i in range(len(text_polys)): + polygon = text_polys[i] + height = max(polygon[:, 1]) - min(polygon[:, 1]) + width = max(polygon[:, 0]) - min(polygon[:, 0]) + if ignore_tags[i] or min(height, width) < self.min_text_size: + cv2.fillPoly(mask, + polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + else: + polygon_shape = Polygon(polygon) + distance = polygon_shape.area * ( + 1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length + subject = [tuple(l) for l in text_polys[i]] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, + pyclipper.ET_CLOSEDPOLYGON) + shrinked = padding.Execute(-distance) + if shrinked == []: + cv2.fillPoly(mask, + polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + continue + shrinked = np.array(shrinked[0]).reshape(-1, 2) + cv2.fillPoly(gt, [shrinked.astype(np.int32)], 1) + # cv2.fillPoly(gt[0], [shrinked.astype(np.int32)], 1) + + data['shrink_map'] = gt + data['shrink_mask'] = mask + return data + + def validate_polygons(self, polygons, ignore_tags, h, w): + ''' + polygons (numpy.array, required): of shape (num_instances, num_points, 2) + ''' + if len(polygons) == 0: + return polygons, ignore_tags + assert len(polygons) == len(ignore_tags) + for polygon in polygons: + polygon[:, 0] = np.clip(polygon[:, 0], 0, w - 1) + polygon[:, 1] = np.clip(polygon[:, 1], 0, h - 1) + + for i in range(len(polygons)): + area = self.polygon_area(polygons[i]) + if abs(area) < 1: + ignore_tags[i] = True + if area > 0: + polygons[i] = polygons[i][::-1, :] + return polygons, ignore_tags + + def polygon_area(self, polygon): + # return cv2.contourArea(polygon.astype(np.float32)) + edge = 0 + for i in range(polygon.shape[0]): + next_index = (i + 1) % polygon.shape[0] + edge += (polygon[next_index, 0] - polygon[i, 0]) * ( + polygon[next_index, 1] - polygon[i, 1]) + + return edge / 2. diff --git a/ppocr/data/imaug/operators.py b/ppocr/data/imaug/operators.py new file mode 100644 index 00000000..36b1335f --- /dev/null +++ b/ppocr/data/imaug/operators.py @@ -0,0 +1,185 @@ +""" +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys +import six +import cv2 +import numpy as np + + +class DecodeImage(object): + """ decode image """ + + def __init__(self, img_mode='RGB', channel_first=False, **kwargs): + self.img_mode = img_mode + self.channel_first = channel_first + + def __call__(self, data): + img = data['image'] + if six.PY2: + assert type(img) is str and len( + img) > 0, "invalid input 'img' in DecodeImage" + else: + assert type(img) is bytes and len( + img) > 0, "invalid input 'img' in DecodeImage" + img = np.frombuffer(img, dtype='uint8') + img = cv2.imdecode(img, 1) + if self.img_mode == 'GRAY': + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + elif self.img_mode == 'RGB': + assert img.shape[2] == 3, 'invalid shape of image[%s]' % (img.shape) + img = img[:, :, ::-1] + + if self.channel_first: + img = img.transpose((2, 0, 1)) + + data['image'] = img + return data + + +class NormalizeImage(object): + """ normalize image such as substract mean, divide std + """ + + def __init__(self, scale=None, mean=None, std=None, order='chw', **kwargs): + if isinstance(scale, str): + scale = eval(scale) + self.scale = np.float32(scale if scale is not None else 1.0 / 255.0) + mean = mean if mean is not None else [0.485, 0.456, 0.406] + std = std if std is not None else [0.229, 0.224, 0.225] + + shape = (3, 1, 1) if order == 'chw' else (1, 1, 3) + self.mean = np.array(mean).reshape(shape).astype('float32') + self.std = np.array(std).reshape(shape).astype('float32') + + def __call__(self, data): + img = data['image'] + from PIL import Image + if isinstance(img, Image.Image): + img = np.array(img) + + assert isinstance(img, + np.ndarray), "invalid input 'img' in NormalizeImage" + data['image'] = ( + img.astype('float32') * self.scale - self.mean) / self.std + return data + + +class ToCHWImage(object): + """ convert hwc image to chw image + """ + + def __init__(self, **kwargs): + pass + + def __call__(self, data): + img = data['image'] + from PIL import Image + if isinstance(img, Image.Image): + img = np.array(img) + data['image'] = img.transpose((2, 0, 1)) + return data + + +class keepKeys(object): + def __init__(self, keep_keys, **kwargs): + self.keep_keys = keep_keys + + def __call__(self, data): + data_list = [] + for key in self.keep_keys: + data_list.append(data[key]) + return data_list + + +class DetResizeForTest(object): + def __init__(self, **kwargs): + super(DetResizeForTest, self).__init__() + self.resize_type = 0 + if 'image_shape' in kwargs: + self.image_shape = kwargs['image_shape'] + self.resize_type = 1 + if 'limit_side_len' in kwargs: + self.limit_side_len = kwargs['limit_side_len'] + self.limit_type = kwargs.get('limit_type', 'min') + else: + self.limit_side_len = 736 + self.limit_type = 'min' + + def __call__(self, data): + img = data['image'] + + if self.resize_type == 0: + img, shape = self.resize_image_type0(img) + else: + img, shape = self.resize_image_type1(img) + data['image'] = img + data['shape'] = shape + return data + + def resize_image_type1(self, img): + resize_h, resize_w = self.image_shape + ori_h, ori_w = img.shape[:2] # (h, w, c) + img = cv2.resize(img, (int(resize_w), int(resize_h))) + return img, np.array([ori_h, ori_w]) + + def resize_image_type0(self, img): + """ + resize image to a size multiple of 32 which is required by the network + args: + img(array): array with shape [h, w, c] + return(tuple): + img, (ratio_h, ratio_w) + """ + limit_side_len = self.limit_side_len + h, w, _ = img.shape + + # limit the max side + if self.limit_type == 'max': + if max(h, w) > limit_side_len: + if h > w: + ratio = float(limit_side_len) / h + else: + ratio = float(limit_side_len) / w + else: + ratio = 1. + else: + if min(h, w) < limit_side_len: + if h < w: + ratio = float(limit_side_len) / h + else: + ratio = float(limit_side_len) / w + else: + ratio = 1. + resize_h = int(h * ratio) + resize_w = int(w * ratio) + + resize_h = int(round(resize_h / 32) * 32) + resize_w = int(round(resize_w / 32) * 32) + + try: + if int(resize_w) <= 0 or int(resize_h) <= 0: + return None, (None, None) + img = cv2.resize(img, (int(resize_w), int(resize_h))) + except: + print(img.shape, resize_w, resize_h) + sys.exit(0) + return img, np.array([h, w]) diff --git a/ppocr/data/imaug/random_crop_data.py b/ppocr/data/imaug/random_crop_data.py new file mode 100644 index 00000000..4d67cff6 --- /dev/null +++ b/ppocr/data/imaug/random_crop_data.py @@ -0,0 +1,210 @@ +# -*- coding:utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 +import random + + +def is_poly_in_rect(poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].min() < x or poly[:, 0].max() > x + w: + return False + if poly[:, 1].min() < y or poly[:, 1].max() > y + h: + return False + return True + + +def is_poly_outside_rect(poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].max() < x or poly[:, 0].min() > x + w: + return True + if poly[:, 1].max() < y or poly[:, 1].min() > y + h: + return True + return False + + +def split_regions(axis): + regions = [] + min_axis = 0 + for i in range(1, axis.shape[0]): + if axis[i] != axis[i - 1] + 1: + region = axis[min_axis:i] + min_axis = i + regions.append(region) + return regions + + +def random_select(axis, max_size): + xx = np.random.choice(axis, size=2) + xmin = np.min(xx) + xmax = np.max(xx) + xmin = np.clip(xmin, 0, max_size - 1) + xmax = np.clip(xmax, 0, max_size - 1) + return xmin, xmax + + +def region_wise_random_select(regions, max_size): + selected_index = list(np.random.choice(len(regions), 2)) + selected_values = [] + for index in selected_index: + axis = regions[index] + xx = int(np.random.choice(axis, size=1)) + selected_values.append(xx) + xmin = min(selected_values) + xmax = max(selected_values) + return xmin, xmax + + +def crop_area(im, text_polys, min_crop_side_ratio, max_tries): + h, w, _ = im.shape + h_array = np.zeros(h, dtype=np.int32) + w_array = np.zeros(w, dtype=np.int32) + for points in text_polys: + points = np.round(points, decimals=0).astype(np.int32) + minx = np.min(points[:, 0]) + maxx = np.max(points[:, 0]) + w_array[minx:maxx] = 1 + miny = np.min(points[:, 1]) + maxy = np.max(points[:, 1]) + h_array[miny:maxy] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + + if len(h_axis) == 0 or len(w_axis) == 0: + return 0, 0, w, h + + h_regions = split_regions(h_axis) + w_regions = split_regions(w_axis) + + for i in range(max_tries): + if len(w_regions) > 1: + xmin, xmax = region_wise_random_select(w_regions, w) + else: + xmin, xmax = random_select(w_axis, w) + if len(h_regions) > 1: + ymin, ymax = region_wise_random_select(h_regions, h) + else: + ymin, ymax = random_select(h_axis, h) + + if xmax - xmin < min_crop_side_ratio * w or ymax - ymin < min_crop_side_ratio * h: + # area too small + continue + num_poly_in_rect = 0 + for poly in text_polys: + if not is_poly_outside_rect(poly, xmin, ymin, xmax - xmin, + ymax - ymin): + num_poly_in_rect += 1 + break + + if num_poly_in_rect > 0: + return xmin, ymin, xmax - xmin, ymax - ymin + + return 0, 0, w, h + + +class EastRandomCropData(object): + def __init__(self, + size=(640, 640), + max_tries=10, + min_crop_side_ratio=0.1, + keep_ratio=True, + **kwargs): + self.size = size + self.max_tries = max_tries + self.min_crop_side_ratio = min_crop_side_ratio + self.keep_ratio = keep_ratio + + def __call__(self, data): + img = data['image'] + text_polys = data['polys'] + ignore_tags = data['ignore_tags'] + texts = data['texts'] + all_care_polys = [ + text_polys[i] for i, tag in enumerate(ignore_tags) if not tag + ] + # 计算crop区域 + crop_x, crop_y, crop_w, crop_h = crop_area( + img, all_care_polys, self.min_crop_side_ratio, self.max_tries) + # crop 图片 保持比例填充 + scale_w = self.size[0] / crop_w + scale_h = self.size[1] / crop_h + scale = min(scale_w, scale_h) + h = int(crop_h * scale) + w = int(crop_w * scale) + if self.keep_ratio: + padimg = np.zeros((self.size[1], self.size[0], img.shape[2]), + img.dtype) + padimg[:h, :w] = cv2.resize( + img[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w], (w, h)) + img = padimg + else: + img = cv2.resize( + img[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w], + tuple(self.size)) + # crop 文本框 + text_polys_crop = [] + ignore_tags_crop = [] + texts_crop = [] + for poly, text, tag in zip(text_polys, texts, ignore_tags): + poly = ((poly - (crop_x, crop_y)) * scale).tolist() + if not is_poly_outside_rect(poly, 0, 0, w, h): + text_polys_crop.append(poly) + ignore_tags_crop.append(tag) + texts_crop.append(text) + data['image'] = img + data['polys'] = np.array(text_polys_crop) + data['ignore_tags'] = ignore_tags_crop + data['texts'] = texts_crop + return data + + +class PSERandomCrop(object): + def __init__(self, size, **kwargs): + self.size = size + + def __call__(self, data): + imgs = data['imgs'] + + h, w = imgs[0].shape[0:2] + th, tw = self.size + if w == tw and h == th: + return imgs + + # label中存在文本实例,并且按照概率进行裁剪,使用threshold_label_map控制 + if np.max(imgs[2]) > 0 and random.random() > 3 / 8: + # 文本实例的左上角点 + tl = np.min(np.where(imgs[2] > 0), axis=1) - self.size + tl[tl < 0] = 0 + # 文本实例的右下角点 + br = np.max(np.where(imgs[2] > 0), axis=1) - self.size + br[br < 0] = 0 + # 保证选到右下角点时,有足够的距离进行crop + br[0] = min(br[0], h - th) + br[1] = min(br[1], w - tw) + + for _ in range(50000): + i = random.randint(tl[0], br[0]) + j = random.randint(tl[1], br[1]) + # 保证shrink_label_map有文本 + if imgs[1][i:i + th, j:j + tw].sum() <= 0: + continue + else: + break + else: + i = random.randint(0, h - th) + j = random.randint(0, w - tw) + + # return i, j, th, tw + for idx in range(len(imgs)): + if len(imgs[idx].shape) == 3: + imgs[idx] = imgs[idx][i:i + th, j:j + tw, :] + else: + imgs[idx] = imgs[idx][i:i + th, j:j + tw] + data['imgs'] = imgs + return data diff --git a/ppocr/data/rec/img_tools.py b/ppocr/data/imaug/rec_img_aug.py old mode 100755 new mode 100644 similarity index 52% rename from ppocr/data/rec/img_tools.py rename to ppocr/data/imaug/rec_img_aug.py index 8b497e6b..e3792553 --- a/ppocr/data/rec/img_tools.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -1,31 +1,70 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import math import cv2 import numpy as np import random -from ppocr.utils.utility import initial_logger -logger = initial_logger() +from .text_image_aug import tia_perspective, tia_stretch, tia_distort -def get_bounding_box_rect(pos): - left = min(pos[0]) - right = max(pos[0]) - top = min(pos[1]) - bottom = max(pos[1]) - return [left, top, right, bottom] + +class RecAug(object): + def __init__(self, **kwargsz): + pass + + def __call__(self, data): + img = data['image'] + img = warp(img, 10) + data['image'] = img + return data + + +class RecResizeImg(object): + def __init__(self, + image_shape, + infer_mode=False, + character_type='ch', + use_tps=False, + **kwargs): + self.image_shape = image_shape + self.infer_mode = infer_mode + self.character_type = character_type + self.use_tps = use_tps + + def __call__(self, data): + img = data['image'] + if self.infer_mode and self.character_type == "ch" and not self.use_tps: + norm_img = resize_norm_img_chinese(img, self.image_shape) + else: + norm_img = resize_norm_img(img, self.image_shape) + data['image'] = norm_img + return data def resize_norm_img(img, image_shape): @@ -77,19 +116,6 @@ def resize_norm_img_chinese(img, image_shape): return padding_im -def get_img_data(value): - """get_img_data""" - if not value: - return None - imgdata = np.frombuffer(value, dtype='uint8') - if imgdata is None: - return None - imgori = cv2.imdecode(imgdata, 1) - if imgori is None: - return None - return imgori - - def flag(): """ flag @@ -196,6 +222,9 @@ class Config: self.h = h self.perspective = True + self.stretch = True + self.distort = True + self.crop = True self.affine = False self.reverse = True @@ -299,168 +328,39 @@ def warp(img, ang): config.make(w, h, ang) new_img = img + prob = 0.4 + + if config.distort: + img_height, img_width = img.shape[0:2] + if random.random() <= prob and img_height >= 20 and img_width >= 20: + new_img = tia_distort(new_img, random.randint(3, 6)) + + if config.stretch: + img_height, img_width = img.shape[0:2] + if random.random() <= prob and img_height >= 20 and img_width >= 20: + new_img = tia_stretch(new_img, random.randint(3, 6)) + if config.perspective: - tp = random.randint(1, 100) - if tp >= 50: - warpR, (r1, c1), ratio, dst = get_warpR(config) - new_w = int(np.max(dst[:, 0])) - int(np.min(dst[:, 0])) - new_img = cv2.warpPerspective( - new_img, - warpR, (int(new_w * ratio), h), - borderMode=config.borderMode) + if random.random() <= prob: + new_img = tia_perspective(new_img) + if config.crop: img_height, img_width = img.shape[0:2] - tp = random.randint(1, 100) - if tp >= 50 and img_height >= 20 and img_width >= 20: + if random.random() <= prob and img_height >= 20 and img_width >= 20: new_img = get_crop(new_img) - if config.affine: - warpT = get_warpAffine(config) - new_img = cv2.warpAffine( - new_img, warpT, (w, h), borderMode=config.borderMode) + if config.blur: - tp = random.randint(1, 100) - if tp >= 50: + if random.random() <= prob: new_img = blur(new_img) if config.color: - tp = random.randint(1, 100) - if tp >= 50: + if random.random() <= prob: new_img = cvtColor(new_img) if config.jitter: new_img = jitter(new_img) if config.noise: - tp = random.randint(1, 100) - if tp >= 50: + if random.random() <= prob: new_img = add_gasuss_noise(new_img) if config.reverse: - tp = random.randint(1, 100) - if tp >= 50: + if random.random() <= prob: new_img = 255 - new_img return new_img - - -def process_image(img, - image_shape, - label=None, - char_ops=None, - loss_type=None, - max_text_length=None, - tps=None, - infer_mode=False, - distort=False): - if distort: - img = warp(img, 10) - if infer_mode and char_ops.character_type == "ch" and not tps: - norm_img = resize_norm_img_chinese(img, image_shape) - else: - norm_img = resize_norm_img(img, image_shape) - - norm_img = norm_img[np.newaxis, :] - if label is not None: - # char_num = char_ops.get_char_num() - text = char_ops.encode(label) - if len(text) == 0 or len(text) > max_text_length: - logger.info( - "Warning in ppocr/data/rec/img_tools.py: Wrong data type." - "Excepted string with length between 1 and {}, but " - "got '{}'. Label is '{}'".format(max_text_length, - len(text), label)) - return None - else: - if loss_type == "ctc": - text = text.reshape(-1, 1) - return (norm_img, text) - elif loss_type == "attention": - beg_flag_idx = char_ops.get_beg_end_flag_idx("beg") - end_flag_idx = char_ops.get_beg_end_flag_idx("end") - beg_text = np.append(beg_flag_idx, text) - end_text = np.append(text, end_flag_idx) - beg_text = beg_text.reshape(-1, 1) - end_text = end_text.reshape(-1, 1) - return (norm_img, beg_text, end_text) - else: - assert False, "Unsupport loss_type %s in process_image"\ - % loss_type - return (norm_img) - -def resize_norm_img_srn(img, image_shape): - imgC, imgH, imgW = image_shape - - img_black = np.zeros((imgH, imgW)) - im_hei = img.shape[0] - im_wid = img.shape[1] - - if im_wid <= im_hei * 1: - img_new = cv2.resize(img, (imgH * 1, imgH)) - elif im_wid <= im_hei * 2: - img_new = cv2.resize(img, (imgH * 2, imgH)) - elif im_wid <= im_hei * 3: - img_new = cv2.resize(img, (imgH * 3, imgH)) - else: - img_new = cv2.resize(img, (imgW, imgH)) - - img_np = np.asarray(img_new) - img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY) - img_black[:, 0:img_np.shape[1]] = img_np - img_black = img_black[:, :, np.newaxis] - - row, col, c = img_black.shape - c = 1 - - return np.reshape(img_black, (c, row, col)).astype(np.float32) - -def srn_other_inputs(image_shape, - num_heads, - max_text_length, - char_num): - - imgC, imgH, imgW = image_shape - feature_dim = int((imgH / 8) * (imgW / 8)) - - encoder_word_pos = np.array(range(0, feature_dim)).reshape((feature_dim, 1)).astype('int64') - gsrm_word_pos = np.array(range(0, max_text_length)).reshape((max_text_length, 1)).astype('int64') - - lbl_weight = np.array([int(char_num-1)] * max_text_length).reshape((-1,1)).astype('int64') - - gsrm_attn_bias_data = np.ones((1, max_text_length, max_text_length)) - gsrm_slf_attn_bias1 = np.triu(gsrm_attn_bias_data, 1).reshape([-1, 1, max_text_length, max_text_length]) - gsrm_slf_attn_bias1 = np.tile(gsrm_slf_attn_bias1, [1, num_heads, 1, 1]) * [-1e9] - - gsrm_slf_attn_bias2 = np.tril(gsrm_attn_bias_data, -1).reshape([-1, 1, max_text_length, max_text_length]) - gsrm_slf_attn_bias2 = np.tile(gsrm_slf_attn_bias2, [1, num_heads, 1, 1]) * [-1e9] - - encoder_word_pos = encoder_word_pos[np.newaxis, :] - gsrm_word_pos = gsrm_word_pos[np.newaxis, :] - - return [lbl_weight, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2] - -def process_image_srn(img, - image_shape, - num_heads, - max_text_length, - label=None, - char_ops=None, - loss_type=None): - norm_img = resize_norm_img_srn(img, image_shape) - norm_img = norm_img[np.newaxis, :] - char_num = char_ops.get_char_num() - - [lbl_weight, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2] = \ - srn_other_inputs(image_shape, num_heads, max_text_length,char_num) - - if label is not None: - text = char_ops.encode(label) - if len(text) == 0 or len(text) > max_text_length: - return None - else: - if loss_type == "srn": - text_padded = [int(char_num-1)] * max_text_length - for i in range(len(text)): - text_padded[i] = text[i] - lbl_weight[i] = [1.0] - text_padded = np.array(text_padded) - text = text_padded.reshape(-1, 1) - return (norm_img, text,encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2,lbl_weight) - else: - assert False, "Unsupport loss_type %s in process_image"\ - % loss_type - return (norm_img, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2) diff --git a/ppocr/data/rec/__init__.py b/ppocr/data/imaug/text_image_aug/__init__.py old mode 100755 new mode 100644 similarity index 67% rename from ppocr/data/rec/__init__.py rename to ppocr/data/imaug/text_image_aug/__init__.py index abf198b9..bca26263 --- a/ppocr/data/rec/__init__.py +++ b/ppocr/data/imaug/text_image_aug/__init__.py @@ -1,13 +1,17 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from .augment import tia_perspective, tia_distort, tia_stretch + +__all__ = ['tia_distort', 'tia_stretch', 'tia_perspective'] diff --git a/ppocr/data/imaug/text_image_aug/augment.py b/ppocr/data/imaug/text_image_aug/augment.py new file mode 100644 index 00000000..1aeff373 --- /dev/null +++ b/ppocr/data/imaug/text_image_aug/augment.py @@ -0,0 +1,116 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from .warp_mls import WarpMLS + + +def tia_distort(src, segment=4): + img_h, img_w = src.shape[:2] + + cut = img_w // segment + thresh = cut // 3 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([np.random.randint(thresh), np.random.randint(thresh)]) + dst_pts.append( + [img_w - np.random.randint(thresh), np.random.randint(thresh)]) + dst_pts.append( + [img_w - np.random.randint(thresh), img_h - np.random.randint(thresh)]) + dst_pts.append( + [np.random.randint(thresh), img_h - np.random.randint(thresh)]) + + half_thresh = thresh * 0.5 + + for cut_idx in np.arange(1, segment, 1): + src_pts.append([cut * cut_idx, 0]) + src_pts.append([cut * cut_idx, img_h]) + dst_pts.append([ + cut * cut_idx + np.random.randint(thresh) - half_thresh, + np.random.randint(thresh) - half_thresh + ]) + dst_pts.append([ + cut * cut_idx + np.random.randint(thresh) - half_thresh, + img_h + np.random.randint(thresh) - half_thresh + ]) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst + + +def tia_stretch(src, segment=4): + img_h, img_w = src.shape[:2] + + cut = img_w // segment + thresh = cut * 4 // 5 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([0, 0]) + dst_pts.append([img_w, 0]) + dst_pts.append([img_w, img_h]) + dst_pts.append([0, img_h]) + + half_thresh = thresh * 0.5 + + for cut_idx in np.arange(1, segment, 1): + move = np.random.randint(thresh) - half_thresh + src_pts.append([cut * cut_idx, 0]) + src_pts.append([cut * cut_idx, img_h]) + dst_pts.append([cut * cut_idx + move, 0]) + dst_pts.append([cut * cut_idx + move, img_h]) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst + + +def tia_perspective(src): + img_h, img_w = src.shape[:2] + + thresh = img_h // 2 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([0, np.random.randint(thresh)]) + dst_pts.append([img_w, np.random.randint(thresh)]) + dst_pts.append([img_w, img_h - np.random.randint(thresh)]) + dst_pts.append([0, img_h - np.random.randint(thresh)]) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst \ No newline at end of file diff --git a/ppocr/data/imaug/text_image_aug/warp_mls.py b/ppocr/data/imaug/text_image_aug/warp_mls.py new file mode 100644 index 00000000..d6cbe749 --- /dev/null +++ b/ppocr/data/imaug/text_image_aug/warp_mls.py @@ -0,0 +1,164 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + + +class WarpMLS: + def __init__(self, src, src_pts, dst_pts, dst_w, dst_h, trans_ratio=1.): + self.src = src + self.src_pts = src_pts + self.dst_pts = dst_pts + self.pt_count = len(self.dst_pts) + self.dst_w = dst_w + self.dst_h = dst_h + self.trans_ratio = trans_ratio + self.grid_size = 100 + self.rdx = np.zeros((self.dst_h, self.dst_w)) + self.rdy = np.zeros((self.dst_h, self.dst_w)) + + @staticmethod + def __bilinear_interp(x, y, v11, v12, v21, v22): + return (v11 * (1 - y) + v12 * y) * (1 - x) + (v21 * + (1 - y) + v22 * y) * x + + def generate(self): + self.calc_delta() + return self.gen_img() + + def calc_delta(self): + w = np.zeros(self.pt_count, dtype=np.float32) + + if self.pt_count < 2: + return + + i = 0 + while 1: + if self.dst_w <= i < self.dst_w + self.grid_size - 1: + i = self.dst_w - 1 + elif i >= self.dst_w: + break + + j = 0 + while 1: + if self.dst_h <= j < self.dst_h + self.grid_size - 1: + j = self.dst_h - 1 + elif j >= self.dst_h: + break + + sw = 0 + swp = np.zeros(2, dtype=np.float32) + swq = np.zeros(2, dtype=np.float32) + new_pt = np.zeros(2, dtype=np.float32) + cur_pt = np.array([i, j], dtype=np.float32) + + k = 0 + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + break + + w[k] = 1. / ( + (i - self.dst_pts[k][0]) * (i - self.dst_pts[k][0]) + + (j - self.dst_pts[k][1]) * (j - self.dst_pts[k][1])) + + sw += w[k] + swp = swp + w[k] * np.array(self.dst_pts[k]) + swq = swq + w[k] * np.array(self.src_pts[k]) + + if k == self.pt_count - 1: + pstar = 1 / sw * swp + qstar = 1 / sw * swq + + miu_s = 0 + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + continue + pt_i = self.dst_pts[k] - pstar + miu_s += w[k] * np.sum(pt_i * pt_i) + + cur_pt -= pstar + cur_pt_j = np.array([-cur_pt[1], cur_pt[0]]) + + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + continue + + pt_i = self.dst_pts[k] - pstar + pt_j = np.array([-pt_i[1], pt_i[0]]) + + tmp_pt = np.zeros(2, dtype=np.float32) + tmp_pt[0] = np.sum(pt_i * cur_pt) * self.src_pts[k][0] - \ + np.sum(pt_j * cur_pt) * self.src_pts[k][1] + tmp_pt[1] = -np.sum(pt_i * cur_pt_j) * self.src_pts[k][0] + \ + np.sum(pt_j * cur_pt_j) * self.src_pts[k][1] + tmp_pt *= (w[k] / miu_s) + new_pt += tmp_pt + + new_pt += qstar + else: + new_pt = self.src_pts[k] + + self.rdx[j, i] = new_pt[0] - i + self.rdy[j, i] = new_pt[1] - j + + j += self.grid_size + i += self.grid_size + + def gen_img(self): + src_h, src_w = self.src.shape[:2] + dst = np.zeros_like(self.src, dtype=np.float32) + + for i in np.arange(0, self.dst_h, self.grid_size): + for j in np.arange(0, self.dst_w, self.grid_size): + ni = i + self.grid_size + nj = j + self.grid_size + w = h = self.grid_size + if ni >= self.dst_h: + ni = self.dst_h - 1 + h = ni - i + 1 + if nj >= self.dst_w: + nj = self.dst_w - 1 + w = nj - j + 1 + + di = np.reshape(np.arange(h), (-1, 1)) + dj = np.reshape(np.arange(w), (1, -1)) + delta_x = self.__bilinear_interp( + di / h, dj / w, self.rdx[i, j], self.rdx[i, nj], + self.rdx[ni, j], self.rdx[ni, nj]) + delta_y = self.__bilinear_interp( + di / h, dj / w, self.rdy[i, j], self.rdy[i, nj], + self.rdy[ni, j], self.rdy[ni, nj]) + nx = j + dj + delta_x * self.trans_ratio + ny = i + di + delta_y * self.trans_ratio + nx = np.clip(nx, 0, src_w - 1) + ny = np.clip(ny, 0, src_h - 1) + nxi = np.array(np.floor(nx), dtype=np.int32) + nyi = np.array(np.floor(ny), dtype=np.int32) + nxi1 = np.array(np.ceil(nx), dtype=np.int32) + nyi1 = np.array(np.ceil(ny), dtype=np.int32) + + if len(self.src.shape) == 3: + x = np.tile(np.expand_dims(ny - nyi, axis=-1), (1, 1, 3)) + y = np.tile(np.expand_dims(nx - nxi, axis=-1), (1, 1, 3)) + else: + x = ny - nyi + y = nx - nxi + dst[i:i + h, j:j + w] = self.__bilinear_interp( + x, y, self.src[nyi, nxi], self.src[nyi, nxi1], + self.src[nyi1, nxi], self.src[nyi1, nxi1]) + + dst = np.clip(dst, 0, 255) + dst = np.array(dst, dtype=np.uint8) + + return dst \ No newline at end of file diff --git a/ppocr/data/reader_main.py b/ppocr/data/reader_main.py deleted file mode 100755 index b0df0d46..00000000 --- a/ppocr/data/reader_main.py +++ /dev/null @@ -1,77 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import os -import random -import numpy as np - -import paddle -from ppocr.utils.utility import create_module -from copy import deepcopy - -from .rec.img_tools import process_image -import cv2 - -import sys -import signal - - -# handle terminate reader process, do not print stack frame -def _reader_quit(signum, frame): - print("Reader process exit.") - sys.exit() - - -def _term_group(sig_num, frame): - print('pid {} terminated, terminate group ' - '{}...'.format(os.getpid(), os.getpgrp())) - os.killpg(os.getpgid(os.getpid()), signal.SIGKILL) - - -signal.signal(signal.SIGTERM, _reader_quit) -signal.signal(signal.SIGINT, _term_group) - - -def reader_main(config=None, mode=None): - """Create a reader for trainning - - Args: - settings: arguments - - Returns: - train reader - """ - assert mode in ["train", "eval", "test"],\ - "Nonsupport mode:{}".format(mode) - global_params = config['Global'] - if mode == "train": - params = deepcopy(config['TrainReader']) - elif mode == "eval": - params = deepcopy(config['EvalReader']) - else: - params = deepcopy(config['TestReader']) - params['mode'] = mode - params.update(global_params) - reader_function = params['reader_function'] - function = create_module(reader_function)(params) - if mode == "train": - if sys.platform == "win32": - return function(0) - readers = [] - num_workers = params['num_workers'] - for process_id in range(num_workers): - readers.append(function(process_id)) - return paddle.reader.multiprocess_reader(readers, False) - else: - return function(mode) diff --git a/ppocr/data/rec/dataset_traversal.py b/ppocr/data/rec/dataset_traversal.py deleted file mode 100755 index 84f325b9..00000000 --- a/ppocr/data/rec/dataset_traversal.py +++ /dev/null @@ -1,335 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -import os -import sys -import math -import random -import numpy as np -import cv2 - -import string -import lmdb - -from ppocr.utils.utility import initial_logger -from ppocr.utils.utility import get_image_file_list -logger = initial_logger() - -from .img_tools import process_image, process_image_srn, get_img_data - - -class LMDBReader(object): - def __init__(self, params): - if params['mode'] != 'train': - self.num_workers = 1 - else: - self.num_workers = params['num_workers'] - self.lmdb_sets_dir = params['lmdb_sets_dir'] - self.char_ops = params['char_ops'] - self.image_shape = params['image_shape'] - self.loss_type = params['loss_type'] - self.max_text_length = params['max_text_length'] - self.mode = params['mode'] - self.drop_last = False - self.use_tps = False - self.num_heads = None - if "num_heads" in params: - self.num_heads = params['num_heads'] - if "tps" in params: - self.ues_tps = True - self.use_distort = False - if "distort" in params: - self.use_distort = params['distort'] and params['use_gpu'] - if not params['use_gpu']: - logger.info( - "Distort operation can only support in GPU. Distort will be set to False." - ) - if params['mode'] == 'train': - self.batch_size = params['train_batch_size_per_card'] - self.drop_last = True - else: - self.batch_size = params['test_batch_size_per_card'] - self.drop_last = False - self.use_distort = False - self.infer_img = params['infer_img'] - - def load_hierarchical_lmdb_dataset(self): - lmdb_sets = {} - dataset_idx = 0 - for dirpath, dirnames, filenames in os.walk(self.lmdb_sets_dir + '/'): - if not dirnames: - env = lmdb.open( - dirpath, - max_readers=32, - readonly=True, - lock=False, - readahead=False, - meminit=False) - txn = env.begin(write=False) - num_samples = int(txn.get('num-samples'.encode())) - lmdb_sets[dataset_idx] = {"dirpath":dirpath, "env":env, \ - "txn":txn, "num_samples":num_samples} - dataset_idx += 1 - return lmdb_sets - - def print_lmdb_sets_info(self, lmdb_sets): - lmdb_info_strs = [] - for dataset_idx in range(len(lmdb_sets)): - tmp_str = " %s:%d," % (lmdb_sets[dataset_idx]['dirpath'], - lmdb_sets[dataset_idx]['num_samples']) - lmdb_info_strs.append(tmp_str) - lmdb_info_strs = ''.join(lmdb_info_strs) - logger.info("DataSummary:" + lmdb_info_strs) - return - - def close_lmdb_dataset(self, lmdb_sets): - for dataset_idx in lmdb_sets: - lmdb_sets[dataset_idx]['env'].close() - return - - def get_lmdb_sample_info(self, txn, index): - label_key = 'label-%09d'.encode() % index - label = txn.get(label_key) - if label is None: - return None - label = label.decode('utf-8') - img_key = 'image-%09d'.encode() % index - imgbuf = txn.get(img_key) - img = get_img_data(imgbuf) - if img is None: - return None - return img, label - - def __call__(self, process_id): - if self.mode != 'train': - process_id = 0 - - def sample_iter_reader(): - if self.mode != 'train' and self.infer_img is not None: - image_file_list = get_image_file_list(self.infer_img) - for single_img in image_file_list: - img = cv2.imread(single_img) - if img.shape[-1] == 1 or len(list(img.shape)) == 2: - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - if self.loss_type == 'srn': - norm_img = process_image_srn( - img=img, - image_shape=self.image_shape, - num_heads=self.num_heads, - max_text_length=self.max_text_length) - else: - norm_img = process_image( - img=img, - image_shape=self.image_shape, - char_ops=self.char_ops, - tps=self.use_tps, - infer_mode=True) - yield norm_img - else: - lmdb_sets = self.load_hierarchical_lmdb_dataset() - if process_id == 0: - self.print_lmdb_sets_info(lmdb_sets) - cur_index_sets = [1 + process_id] * len(lmdb_sets) - while True: - finish_read_num = 0 - for dataset_idx in range(len(lmdb_sets)): - cur_index = cur_index_sets[dataset_idx] - if cur_index > lmdb_sets[dataset_idx]['num_samples']: - finish_read_num += 1 - else: - sample_info = self.get_lmdb_sample_info( - lmdb_sets[dataset_idx]['txn'], cur_index) - cur_index_sets[dataset_idx] += self.num_workers - if sample_info is None: - continue - img, label = sample_info - outs = [] - if self.loss_type == "srn": - outs = process_image_srn( - img=img, - image_shape=self.image_shape, - num_heads=self.num_heads, - max_text_length=self.max_text_length, - label=label, - char_ops=self.char_ops, - loss_type=self.loss_type) - - else: - outs = process_image( - img=img, - image_shape=self.image_shape, - label=label, - char_ops=self.char_ops, - loss_type=self.loss_type, - max_text_length=self.max_text_length) - if outs is None: - continue - yield outs - - if finish_read_num == len(lmdb_sets): - break - self.close_lmdb_dataset(lmdb_sets) - - def batch_iter_reader(): - batch_outs = [] - for outs in sample_iter_reader(): - batch_outs.append(outs) - if len(batch_outs) == self.batch_size: - yield batch_outs - batch_outs = [] - if not self.drop_last: - if len(batch_outs) != 0: - yield batch_outs - - if self.infer_img is None: - return batch_iter_reader - return sample_iter_reader - - -class SimpleReader(object): - def __init__(self, params): - if params['mode'] != 'train': - self.num_workers = 1 - else: - self.num_workers = params['num_workers'] - if params['mode'] != 'test': - self.img_set_dir = params['img_set_dir'] - self.label_file_path = params['label_file_path'] - self.use_gpu = params['use_gpu'] - self.char_ops = params['char_ops'] - self.image_shape = params['image_shape'] - self.loss_type = params['loss_type'] - self.max_text_length = params['max_text_length'] - self.mode = params['mode'] - self.infer_img = params['infer_img'] - self.use_tps = False - if "num_heads" in params: - self.num_heads = params['num_heads'] - if "tps" in params: - self.use_tps = True - self.use_distort = False - if "distort" in params: - self.use_distort = params['distort'] and params['use_gpu'] - if not params['use_gpu']: - logger.info( - "Distort operation can only support in GPU.Distort will be set to False." - ) - if params['mode'] == 'train': - self.batch_size = params['train_batch_size_per_card'] - self.drop_last = True - else: - self.batch_size = params['test_batch_size_per_card'] - self.drop_last = False - self.use_distort = False - - def __call__(self, process_id): - if self.mode != 'train': - process_id = 0 - - def get_device_num(): - if self.use_gpu: - gpus = os.environ.get("CUDA_VISIBLE_DEVICES", '1') - gpu_num = len(gpus.split(',')) - return gpu_num - else: - cpu_num = os.environ.get("CPU_NUM", 1) - return int(cpu_num) - - def sample_iter_reader(): - if self.mode != 'train' and self.infer_img is not None: - image_file_list = get_image_file_list(self.infer_img) - for single_img in image_file_list: - img = cv2.imread(single_img) - if img.shape[-1] == 1 or len(list(img.shape)) == 2: - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - if self.loss_type == 'srn': - norm_img = process_image_srn( - img=img, - image_shape=self.image_shape, - char_ops=self.char_ops, - num_heads=self.num_heads, - max_text_length=self.max_text_length) - else: - norm_img = process_image( - img=img, - image_shape=self.image_shape, - char_ops=self.char_ops, - tps=self.use_tps, - infer_mode=True) - yield norm_img - else: - with open(self.label_file_path, "rb") as fin: - label_infor_list = fin.readlines() - img_num = len(label_infor_list) - img_id_list = list(range(img_num)) - random.shuffle(img_id_list) - if sys.platform == "win32" and self.num_workers != 1: - print("multiprocess is not fully compatible with Windows." - "num_workers will be 1.") - self.num_workers = 1 - if self.batch_size * get_device_num( - ) * self.num_workers > img_num: - raise Exception( - "The number of the whole data ({}) is smaller than the batch_size * devices_num * num_workers ({})". - format(img_num, self.batch_size * get_device_num() * - self.num_workers)) - for img_id in range(process_id, img_num, self.num_workers): - label_infor = label_infor_list[img_id_list[img_id]] - substr = label_infor.decode('utf-8').strip("\n").split("\t") - img_path = self.img_set_dir + "/" + substr[0] - img = cv2.imread(img_path) - if img is None: - logger.info("{} does not exist!".format(img_path)) - continue - if img.shape[-1] == 1 or len(list(img.shape)) == 2: - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - - label = substr[1] - if self.loss_type == "srn": - outs = process_image_srn( - img=img, - image_shape=self.image_shape, - num_heads=self.num_heads, - max_text_length=self.max_text_length, - label=label, - char_ops=self.char_ops, - loss_type=self.loss_type) - - else: - outs = process_image( - img=img, - image_shape=self.image_shape, - label=label, - char_ops=self.char_ops, - loss_type=self.loss_type, - max_text_length=self.max_text_length, - distort=self.use_distort) - if outs is None: - continue - yield outs - - def batch_iter_reader(): - batch_outs = [] - for outs in sample_iter_reader(): - batch_outs.append(outs) - if len(batch_outs) == self.batch_size: - yield batch_outs - batch_outs = [] - if not self.drop_last: - if len(batch_outs) != 0: - yield batch_outs - - if self.infer_img is None: - return batch_iter_reader - return sample_iter_reader diff --git a/ppocr/metrics/DetMetric.py b/ppocr/metrics/DetMetric.py new file mode 100644 index 00000000..889a8e15 --- /dev/null +++ b/ppocr/metrics/DetMetric.py @@ -0,0 +1,72 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__all__ = ['DetMetric'] + +from .eval_det_iou import DetectionIoUEvaluator + + +class DetMetric(object): + def __init__(self, main_indicator='hmean', **kwargs): + self.evaluator = DetectionIoUEvaluator() + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + ''' + batch: a list produced by dataloaders. + image: np.ndarray of shape (N, C, H, W). + ratio_list: np.ndarray of shape(N,2) + polygons: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: np.ndarray of shape (N, K), indicates whether a region is ignorable or not. + preds: a list of dict produced by post process + points: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + ''' + gt_polyons_batch = batch[2] + ignore_tags_batch = batch[3] + for pred, gt_polyons, ignore_tags in zip(preds, gt_polyons_batch, + ignore_tags_batch): + # prepare gt + gt_info_list = [{ + 'points': gt_polyon, + 'text': '', + 'ignore': ignore_tag + } for gt_polyon, ignore_tag in zip(gt_polyons, ignore_tags)] + # prepare det + det_info_list = [{ + 'points': det_polyon, + 'text': '' + } for det_polyon in pred['points']] + result = self.evaluator.evaluate_image(gt_info_list, det_info_list) + self.results.append(result) + + def get_metric(self): + """ + return metircs { + 'precision': 0, + 'recall': 0, + 'hmean': 0 + } + """ + + metircs = self.evaluator.combine_results(self.results) + self.reset() + return metircs + + def reset(self): + self.results = [] # clear results diff --git a/ppocr/metrics/RecMetric.py b/ppocr/metrics/RecMetric.py new file mode 100644 index 00000000..98817ad8 --- /dev/null +++ b/ppocr/metrics/RecMetric.py @@ -0,0 +1,59 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import Levenshtein + + +class RecMetric(object): + def __init__(self, main_indicator='acc', **kwargs): + self.main_indicator = main_indicator + self.reset() + + def __call__(self, pred_label, *args, **kwargs): + preds, labels = pred_label + correct_num = 0 + all_num = 0 + norm_edit_dis = 0.0 + for (pred, pred_conf), (target, _) in zip(preds, labels): + norm_edit_dis += Levenshtein.distance(pred, target) / max( + len(pred), len(target)) + if pred == target: + correct_num += 1 + all_num += 1 + # if all_num < 10 and kwargs.get('show_str', False): + # print('{} -> {}'.format(pred, target)) + self.correct_num += correct_num + self.all_num += all_num + self.norm_edit_dis += norm_edit_dis + return { + 'acc': correct_num / all_num, + 'norm_edit_dis': 1 - norm_edit_dis / all_num + } + + def get_metric(self): + """ + return metircs { + 'acc': 0, + 'norm_edit_dis': 0, + } + """ + acc = self.correct_num / self.all_num + norm_edit_dis = 1 - self.norm_edit_dis / self.all_num + self.reset() + return {'acc': acc, 'norm_edit_dis': norm_edit_dis} + + def reset(self): + self.correct_num = 0 + self.all_num = 0 + self.norm_edit_dis = 0 diff --git a/ppocr/metrics/__init__.py b/ppocr/metrics/__init__.py new file mode 100644 index 00000000..3fddac17 --- /dev/null +++ b/ppocr/metrics/__init__.py @@ -0,0 +1,36 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import copy + +__all__ = ['build_metric'] + + +def build_metric(config): + from .DetMetric import DetMetric + from .RecMetric import RecMetric + + support_dict = ['DetMetric', 'RecMetric'] + + config = copy.deepcopy(config) + module_name = config.pop('name') + assert module_name in support_dict, Exception( + 'metric only support {}'.format(support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/tools/eval_utils/eval_det_iou.py b/ppocr/metrics/eval_det_iou.py similarity index 97% rename from tools/eval_utils/eval_det_iou.py rename to ppocr/metrics/eval_det_iou.py index 64405984..a2a3f418 100644 --- a/tools/eval_utils/eval_det_iou.py +++ b/ppocr/metrics/eval_det_iou.py @@ -88,8 +88,8 @@ class DetectionIoUEvaluator(object): points = gt[n]['points'] # transcription = gt[n]['text'] dontCare = gt[n]['ignore'] -# points = Polygon(points) -# points = points.buffer(0) + # points = Polygon(points) + # points = points.buffer(0) if not Polygon(points).is_valid or not Polygon(points).is_simple: continue @@ -105,8 +105,8 @@ class DetectionIoUEvaluator(object): for n in range(len(pred)): points = pred[n]['points'] -# points = Polygon(points) -# points = points.buffer(0) + # points = Polygon(points) + # points = points.buffer(0) if not Polygon(points).is_valid or not Polygon(points).is_simple: continue diff --git a/ppocr/modeling/__init__.py b/ppocr/modeling/__init__.py index abf198b9..2d7f1b8e 100755 --- a/ppocr/modeling/__init__.py +++ b/ppocr/modeling/__init__.py @@ -11,3 +11,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import copy +from .losses import build_loss + +__all__ = ['build_model', 'build_loss'] + + +def build_model(config): + from .architectures import Model + + config = copy.deepcopy(config) + module_class = Model(config) + return module_class diff --git a/ppocr/modeling/architectures/__init__.py b/ppocr/modeling/architectures/__init__.py index abf198b9..e0f823e4 100755 --- a/ppocr/modeling/architectures/__init__.py +++ b/ppocr/modeling/architectures/__init__.py @@ -11,3 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from .model import Model +__all__ = ['Model'] \ No newline at end of file diff --git a/ppocr/modeling/architectures/det_model.py b/ppocr/modeling/architectures/det_model.py deleted file mode 100644 index 54d3a479..00000000 --- a/ppocr/modeling/architectures/det_model.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from paddle import fluid - -from ppocr.utils.utility import create_module -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from copy import deepcopy - - -class DetModel(object): - def __init__(self, params): - """ - Detection module for OCR text detection. - args: - params (dict): the super parameters for detection module. - """ - global_params = params['Global'] - self.algorithm = global_params['algorithm'] - - backbone_params = deepcopy(params["Backbone"]) - backbone_params.update(global_params) - self.backbone = create_module(backbone_params['function'])\ - (params=backbone_params) - - head_params = deepcopy(params["Head"]) - head_params.update(global_params) - self.head = create_module(head_params['function'])\ - (params=head_params) - - loss_params = deepcopy(params["Loss"]) - loss_params.update(global_params) - self.loss = create_module(loss_params['function'])\ - (params=loss_params) - - self.image_shape = global_params['image_shape'] - - def create_feed(self, mode): - """ - create Dataloader feeds - args: - mode (str): 'train' for training or else for evaluation - return: (image, corresponding label, dataloader) - """ - image_shape = deepcopy(self.image_shape) - if image_shape[1] % 4 != 0 or image_shape[2] % 4 != 0: - raise Exception("The size of the image must be divisible by 4, " - "received image shape is {}, please reset the " - "Global.image_shape in the yml file".format( - image_shape)) - - image = fluid.layers.data( - name='image', shape=image_shape, dtype='float32') - if mode == "train": - if self.algorithm == "EAST": - h, w = int(image_shape[1] // 4), int(image_shape[2] // 4) - score = fluid.layers.data( - name='score', shape=[1, h, w], dtype='float32') - geo = fluid.layers.data( - name='geo', shape=[9, h, w], dtype='float32') - mask = fluid.layers.data( - name='mask', shape=[1, h, w], dtype='float32') - feed_list = [image, score, geo, mask] - labels = {'score': score, 'geo': geo, 'mask': mask} - elif self.algorithm == "DB": - shrink_map = fluid.layers.data( - name='shrink_map', shape=image_shape[1:], dtype='float32') - shrink_mask = fluid.layers.data( - name='shrink_mask', shape=image_shape[1:], dtype='float32') - threshold_map = fluid.layers.data( - name='threshold_map', - shape=image_shape[1:], - dtype='float32') - threshold_mask = fluid.layers.data( - name='threshold_mask', - shape=image_shape[1:], - dtype='float32') - feed_list=[image, shrink_map, shrink_mask,\ - threshold_map, threshold_mask] - labels = {'shrink_map':shrink_map,\ - 'shrink_mask':shrink_mask,\ - 'threshold_map':threshold_map,\ - 'threshold_mask':threshold_mask} - elif self.algorithm == "SAST": - input_score = fluid.layers.data( - name='score', shape=[1, 128, 128], dtype='float32') - input_border = fluid.layers.data( - name='border', shape=[5, 128, 128], dtype='float32') - input_mask = fluid.layers.data( - name='mask', shape=[1, 128, 128], dtype='float32') - input_tvo = fluid.layers.data( - name='tvo', shape=[9, 128, 128], dtype='float32') - input_tco = fluid.layers.data( - name='tco', shape=[3, 128, 128], dtype='float32') - feed_list = [image, input_score, input_border, input_mask, input_tvo, input_tco] - labels = {'input_score': input_score,\ - 'input_border': input_border,\ - 'input_mask': input_mask,\ - 'input_tvo': input_tvo,\ - 'input_tco': input_tco} - loader = fluid.io.DataLoader.from_generator( - feed_list=feed_list, - capacity=64, - use_double_buffer=True, - iterable=False) - else: - labels = None - loader = None - return image, labels, loader - - def __call__(self, mode): - """ - run forward of defined module - args: - mode (str): 'train' for training; 'export' for inference, - others for evaluation] - """ - image, labels, loader = self.create_feed(mode) - conv_feas = self.backbone(image) - if self.algorithm == "DB": - predicts = self.head(conv_feas, mode) - else: - predicts = self.head(conv_feas) - if mode == "train": - losses = self.loss(predicts, labels) - return loader, losses - elif mode == "export": - return [image, predicts] - else: - return loader, predicts diff --git a/ppocr/modeling/architectures/model.py b/ppocr/modeling/architectures/model.py new file mode 100644 index 00000000..c8cb6f24 --- /dev/null +++ b/ppocr/modeling/architectures/model.py @@ -0,0 +1,129 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os, sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.append('/home/zhoujun20/PaddleOCR') + +import paddle +from paddle import nn +from ppocr.modeling.transform import build_transform +from ppocr.modeling.backbones import build_backbone +from ppocr.modeling.necks import build_neck +from ppocr.modeling.heads import build_head + +__all__ = ['Model'] + + +class Model(nn.Layer): + def __init__(self, config): + """ + Detection module for OCR. + args: + config (dict): the super parameters for module. + """ + super(Model, self).__init__() + algorithm = config['algorithm'] + self.type = config['type'] + self.model_name = '{}_{}'.format(self.type, algorithm) + + in_channels = config.get('in_channels', 3) + # build transfrom, + # for rec, transfrom can be TPS,None + # for det and cls, transfrom shoule to be None, + # if you make model differently, you can use transfrom in det and cls + if 'Transform' not in config or config['Transform'] is None: + self.use_transform = False + else: + self.use_transform = True + config['Transform']['in_channels'] = in_channels + self.transform = build_transform(config['Transform']) + in_channels = self.transform.out_channels + + # build backbone, backbone is need for del, rec and cls + config["Backbone"]['in_channels'] = in_channels + self.backbone = build_backbone(config["Backbone"], self.type) + in_channels = self.backbone.out_channels + + # build neck + # for rec, neck can be cnn,rnn or reshape(None) + # for det, neck can be FPN, BIFPN and so on. + # for cls, neck should be none + if 'Neck' not in config or config['Neck'] is None: + self.use_neck = False + else: + self.use_neck = True + config['Neck']['in_channels'] = in_channels + self.neck = build_neck(config['Neck']) + in_channels = self.neck.out_channels + + # # build head, head is need for del, rec and cls + config["Head"]['in_channels'] = in_channels + self.head = build_head(config["Head"]) + + # @paddle.jit.to_static + def forward(self, x): + if self.use_transform: + x = self.transform(x) + x = self.backbone(x) + if self.use_neck: + x = self.neck(x) + x = self.head(x) + return x + + +def check_static(): + import numpy as np + from ppocr.utils.save_load import load_dygraph_pretrain + from ppocr.utils.logging import get_logger + from tools import program + + config = program.load_config('configs/det/det_r50_vd_db.yml') + + # import cv2 + # data = cv2.imread('doc/imgs/1.jpg') + # data = normalize(data) + logger = get_logger() + data = np.zeros((1, 3, 640, 640), dtype=np.float32) + paddle.disable_static() + + config['Architecture']['in_channels'] = 3 + config['Architecture']["Head"]['out_channels'] = 6624 + model = Model(config['Architecture']) + model.eval() + load_dygraph_pretrain( + model, + logger, + '/Users/zhoujun20/Desktop/code/PaddleOCR/db/db', + load_static_weights=True) + x = paddle.to_variable(data) + y = model(x) + for y1 in y: + print(y1.shape) + # + # # from matplotlib import pyplot as plt + # # plt.imshow(y.numpy()) + # # plt.show() + static_out = np.load('/Users/zhoujun20/Desktop/code/PaddleOCR/db/db.npy') + diff = y.numpy() - static_out + print(y.shape, static_out.shape, diff.mean()) + + +if __name__ == '__main__': + check_static() diff --git a/ppocr/modeling/architectures/rec_model.py b/ppocr/modeling/architectures/rec_model.py deleted file mode 100755 index fe2d4c16..00000000 --- a/ppocr/modeling/architectures/rec_model.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from paddle import fluid - -from ppocr.utils.utility import create_module -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from copy import deepcopy - - -class RecModel(object): - def __init__(self, params): - super(RecModel, self).__init__() - global_params = params['Global'] - char_num = global_params['char_ops'].get_char_num() - global_params['char_num'] = char_num - self.char_type = global_params['character_type'] - self.infer_img = global_params['infer_img'] - if "TPS" in params: - tps_params = deepcopy(params["TPS"]) - tps_params.update(global_params) - self.tps = create_module(tps_params['function'])\ - (params=tps_params) - else: - self.tps = None - - backbone_params = deepcopy(params["Backbone"]) - backbone_params.update(global_params) - self.backbone = create_module(backbone_params['function'])\ - (params=backbone_params) - - head_params = deepcopy(params["Head"]) - head_params.update(global_params) - self.head = create_module(head_params['function'])\ - (params=head_params) - - loss_params = deepcopy(params["Loss"]) - loss_params.update(global_params) - self.loss = create_module(loss_params['function'])\ - (params=loss_params) - - self.loss_type = global_params['loss_type'] - self.image_shape = global_params['image_shape'] - self.max_text_length = global_params['max_text_length'] - if "num_heads" in global_params: - self.num_heads = global_params["num_heads"] - else: - self.num_heads = None - - def create_feed(self, mode): - image_shape = deepcopy(self.image_shape) - image_shape.insert(0, -1) - if mode == "train": - image = fluid.data(name='image', shape=image_shape, dtype='float32') - if self.loss_type == "attention": - label_in = fluid.data( - name='label_in', - shape=[None, 1], - dtype='int32', - lod_level=1) - label_out = fluid.data( - name='label_out', - shape=[None, 1], - dtype='int32', - lod_level=1) - feed_list = [image, label_in, label_out] - labels = {'label_in': label_in, 'label_out': label_out} - elif self.loss_type == "srn": - encoder_word_pos = fluid.data( - name="encoder_word_pos", - shape=[ - -1, int((image_shape[-2] / 8) * (image_shape[-1] / 8)), - 1 - ], - dtype="int64") - gsrm_word_pos = fluid.data( - name="gsrm_word_pos", - shape=[-1, self.max_text_length, 1], - dtype="int64") - gsrm_slf_attn_bias1 = fluid.data( - name="gsrm_slf_attn_bias1", - shape=[ - -1, self.num_heads, self.max_text_length, - self.max_text_length - ], - dtype="float32") - gsrm_slf_attn_bias2 = fluid.data( - name="gsrm_slf_attn_bias2", - shape=[ - -1, self.num_heads, self.max_text_length, - self.max_text_length - ], - dtype="float32") - lbl_weight = fluid.layers.data( - name="lbl_weight", shape=[-1, 1], dtype='int64') - label = fluid.data( - name='label', shape=[-1, 1], dtype='int32', lod_level=1) - feed_list = [ - image, label, encoder_word_pos, gsrm_word_pos, - gsrm_slf_attn_bias1, gsrm_slf_attn_bias2, lbl_weight - ] - labels = { - 'label': label, - 'encoder_word_pos': encoder_word_pos, - 'gsrm_word_pos': gsrm_word_pos, - 'gsrm_slf_attn_bias1': gsrm_slf_attn_bias1, - 'gsrm_slf_attn_bias2': gsrm_slf_attn_bias2, - 'lbl_weight': lbl_weight - } - else: - label = fluid.data( - name='label', shape=[None, 1], dtype='int32', lod_level=1) - feed_list = [image, label] - labels = {'label': label} - loader = fluid.io.DataLoader.from_generator( - feed_list=feed_list, - capacity=64, - use_double_buffer=True, - iterable=False) - else: - labels = None - loader = None - if self.char_type == "ch" and self.infer_img: - image_shape[-1] = -1 - if self.tps != None: - logger.info( - "WARNRNG!!!\n" - "TPS does not support variable shape in chinese!" - "We set img_shape to be the same , it may affect the inference effect" - ) - image_shape = deepcopy(self.image_shape) - image = fluid.data(name='image', shape=image_shape, dtype='float32') - if self.loss_type == "srn": - encoder_word_pos = fluid.data( - name="encoder_word_pos", - shape=[ - -1, int((image_shape[-2] / 8) * (image_shape[-1] / 8)), - 1 - ], - dtype="int64") - gsrm_word_pos = fluid.data( - name="gsrm_word_pos", - shape=[-1, self.max_text_length, 1], - dtype="int64") - gsrm_slf_attn_bias1 = fluid.data( - name="gsrm_slf_attn_bias1", - shape=[ - -1, self.num_heads, self.max_text_length, - self.max_text_length - ], - dtype="float32") - gsrm_slf_attn_bias2 = fluid.data( - name="gsrm_slf_attn_bias2", - shape=[ - -1, self.num_heads, self.max_text_length, - self.max_text_length - ], - dtype="float32") - feed_list = [ - image, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, - gsrm_slf_attn_bias2 - ] - labels = { - 'encoder_word_pos': encoder_word_pos, - 'gsrm_word_pos': gsrm_word_pos, - 'gsrm_slf_attn_bias1': gsrm_slf_attn_bias1, - 'gsrm_slf_attn_bias2': gsrm_slf_attn_bias2 - } - return image, labels, loader - - def __call__(self, mode): - image, labels, loader = self.create_feed(mode) - if self.tps is None: - inputs = image - else: - inputs = self.tps(image) - conv_feas = self.backbone(inputs) - predicts = self.head(conv_feas, labels, mode) - decoded_out = predicts['decoded_out'] - if mode == "train": - loss = self.loss(predicts, labels) - if self.loss_type == "attention": - label = labels['label_out'] - else: - label = labels['label'] - if self.loss_type == 'srn': - total_loss, img_loss, word_loss = self.loss(predicts, labels) - outputs = { - 'total_loss': total_loss, - 'img_loss': img_loss, - 'word_loss': word_loss, - 'decoded_out': decoded_out, - 'label': label - } - else: - outputs = {'total_loss':loss, 'decoded_out':\ - decoded_out, 'label':label} - return loader, outputs - - elif mode == "export": - predict = predicts['predict'] - if self.loss_type == "ctc": - predict = fluid.layers.softmax(predict) - if self.loss_type == "srn": - raise Exception( - "Warning! SRN does not support export model currently") - return [image, {'decoded_out': decoded_out, 'predicts': predict}] - else: - predict = predicts['predict'] - if self.loss_type == "ctc": - predict = fluid.layers.softmax(predict) - return loader, {'decoded_out': decoded_out, 'predicts': predict} diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py index abf198b9..9b873728 100755 --- a/ppocr/modeling/backbones/__init__.py +++ b/ppocr/modeling/backbones/__init__.py @@ -11,3 +11,26 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +__all__ = ['build_backbone'] + + +def build_backbone(config, model_type): + if model_type == 'det': + from .det_mobilenet_v3 import MobileNetV3 + from .det_resnet_vd import ResNet + + support_dict = ['MobileNetV3', 'ResNet', 'ResNet_SAST'] + elif model_type == 'rec': + from .rec_mobilenet_v3 import MobileNetV3 + from .rec_resnet_vd import ResNet + support_dict = ['MobileNetV3', 'ResNet', 'ResNet_FPN'] + else: + raise NotImplementedError + + module_name = config.pop('name') + assert module_name in support_dict, Exception( + 'when model typs is {}, backbone only support {}'.format(model_type, + support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/backbones/det_mobilenet_v3.py b/ppocr/modeling/backbones/det_mobilenet_v3.py index 87f5dd72..52dd34da 100755 --- a/ppocr/modeling/backbones/det_mobilenet_v3.py +++ b/ppocr/modeling/backbones/det_mobilenet_v3.py @@ -1,40 +1,48 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import paddle.fluid as fluid -from paddle.fluid.initializer import MSRA -from paddle.fluid.param_attr import ParamAttr +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr __all__ = ['MobileNetV3'] -class MobileNetV3(): - def __init__(self, params): +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class MobileNetV3(nn.Layer): + def __init__(self, in_channels=3, model_name='large', scale=0.5, **kwargs): """ the MobilenetV3 backbone network for detection module. Args: params(dict): the super parameters for build network """ - self.scale = params['scale'] - model_name = params['model_name'] - self.inplanes = 16 + super(MobileNetV3, self).__init__() if model_name == "large": - self.cfg = [ + cfg = [ # k, exp, c, se, nl, s, [3, 16, 16, False, 'relu', 1], [3, 64, 24, False, 'relu', 2], @@ -52,10 +60,9 @@ class MobileNetV3(): [5, 960, 160, True, 'hard_swish', 1], [5, 960, 160, True, 'hard_swish', 1], ] - self.cls_ch_squeeze = 960 - self.cls_ch_expand = 1280 + cls_ch_squeeze = 960 elif model_name == "small": - self.cfg = [ + cfg = [ # k, exp, c, se, nl, s, [3, 16, 16, True, 'relu', 2], [3, 72, 24, False, 'relu', 2], @@ -69,183 +76,203 @@ class MobileNetV3(): [5, 576, 96, True, 'hard_swish', 1], [5, 576, 96, True, 'hard_swish', 1], ] - self.cls_ch_squeeze = 576 - self.cls_ch_expand = 1280 + cls_ch_squeeze = 576 else: raise NotImplementedError("mode[" + model_name + "_model] is not implemented!") supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25] - assert self.scale in supported_scale, \ - "supported scale are {} but input scale is {}".format(supported_scale, self.scale) - - def __call__(self, input): - scale = self.scale - inplanes = self.inplanes - cfg = self.cfg - cls_ch_squeeze = self.cls_ch_squeeze - cls_ch_expand = self.cls_ch_expand - #conv1 - conv = self.conv_bn_layer( - input, - filter_size=3, - num_filters=self.make_divisible(inplanes * scale), + assert scale in supported_scale, \ + "supported scale are {} but input scale is {}".format(supported_scale, scale) + inplanes = 16 + # conv1 + self.conv = ConvBNLayer( + in_channels=in_channels, + out_channels=make_divisible(inplanes * scale), + kernel_size=3, stride=2, padding=1, - num_groups=1, + groups=1, if_act=True, act='hard_swish', name='conv1') + + self.stages = [] + self.out_channels = [] + block_list = [] i = 0 - inplanes = self.make_divisible(inplanes * scale) - outs = [] - for layer_cfg in cfg: - if layer_cfg[5] == 2 and i > 2: - outs.append(conv) - conv = self.residual_unit( - input=conv, - num_in_filter=inplanes, - num_mid_filter=self.make_divisible(scale * layer_cfg[1]), - num_out_filter=self.make_divisible(scale * layer_cfg[2]), - act=layer_cfg[4], - stride=layer_cfg[5], - filter_size=layer_cfg[0], - use_se=layer_cfg[3], - name='conv' + str(i + 2)) - inplanes = self.make_divisible(scale * layer_cfg[2]) + inplanes = make_divisible(inplanes * scale) + for (k, exp, c, se, nl, s) in cfg: + if s == 2 and i > 2: + self.out_channels.append(inplanes) + self.stages.append(nn.Sequential(*block_list)) + block_list = [] + block_list.append( + ResidualUnit( + in_channels=inplanes, + mid_channels=make_divisible(scale * exp), + out_channels=make_divisible(scale * c), + kernel_size=k, + stride=s, + use_se=se, + act=nl, + name="conv" + str(i + 2))) + inplanes = make_divisible(scale * c) i += 1 + block_list.append( + ConvBNLayer( + in_channels=inplanes, + out_channels=make_divisible(scale * cls_ch_squeeze), + kernel_size=1, + stride=1, + padding=0, + groups=1, + if_act=True, + act='hard_swish', + name='conv_last')) - conv = self.conv_bn_layer( - input=conv, - filter_size=1, - num_filters=self.make_divisible(scale * cls_ch_squeeze), - stride=1, - padding=0, - num_groups=1, - if_act=True, - act='hard_swish', - name='conv_last') - outs.append(conv) - return outs - - def conv_bn_layer(self, - input, - filter_size, - num_filters, - stride, - padding, - num_groups=1, - if_act=True, - act=None, - name=None, - use_cudnn=True, - res_last_bn_init=False): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, + self.stages.append(nn.Sequential(*block_list)) + self.out_channels.append(make_divisible(scale * cls_ch_squeeze)) + for i, stage in enumerate(self.stages): + self.add_sublayer(sublayer=stage, name="stage{}".format(i)) + + def forward(self, x): + x = self.conv(x) + out_list = [] + for stage in self.stages: + x = stage(x) + out_list.append(x) + return out_list + + +class ConvBNLayer(nn.Layer): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + name=None): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, stride=stride, padding=padding, - groups=num_groups, - act=None, - use_cudnn=use_cudnn, - param_attr=ParamAttr(name=name + '_weights'), + groups=groups, + weight_attr=ParamAttr(name=name + '_weights'), bias_attr=False) - bn_name = name + '_bn' - bn = fluid.layers.batch_norm( - input=conv, - param_attr=ParamAttr( - name=bn_name + "_scale", - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=0.0)), - bias_attr=ParamAttr( - name=bn_name + "_offset", - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=0.0)), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - if if_act: - if act == 'relu': - bn = fluid.layers.relu(bn) - elif act == 'hard_swish': - bn = fluid.layers.hard_swish(bn) - return bn - - def make_divisible(self, v, divisor=8, min_value=None): - if min_value is None: - min_value = divisor - new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) - if new_v < 0.9 * v: - new_v += divisor - return new_v - - def se_block(self, input, num_out_filter, ratio=4, name=None): - num_mid_filter = num_out_filter // ratio - pool = fluid.layers.pool2d( - input=input, pool_type='avg', global_pooling=True, use_cudnn=False) - conv1 = fluid.layers.conv2d( - input=pool, - filter_size=1, - num_filters=num_mid_filter, - act='relu', - param_attr=ParamAttr(name=name + '_1_weights'), - bias_attr=ParamAttr(name=name + '_1_offset')) - conv2 = fluid.layers.conv2d( - input=conv1, - filter_size=1, - num_filters=num_out_filter, - act='hard_sigmoid', - param_attr=ParamAttr(name=name + '_2_weights'), - bias_attr=ParamAttr(name=name + '_2_offset')) - scale = fluid.layers.elementwise_mul(x=input, y=conv2, axis=0) - return scale - - def residual_unit(self, - input, - num_in_filter, - num_mid_filter, - num_out_filter, - stride, - filter_size, - act=None, - use_se=False, - name=None): - - conv0 = self.conv_bn_layer( - input=input, - filter_size=1, - num_filters=num_mid_filter, + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=None, + param_attr=ParamAttr(name=name + "_bn_scale"), + bias_attr=ParamAttr(name=name + "_bn_offset"), + moving_mean_name=name + "_bn_mean", + moving_variance_name=name + "_bn_variance") + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.if_act: + if self.act == "relu": + x = F.relu(x) + elif self.act == "hard_swish": + x = F.hard_swish(x) + else: + print("The activation function is selected incorrectly.") + exit() + return x + + +class ResidualUnit(nn.Layer): + def __init__(self, + in_channels, + mid_channels, + out_channels, + kernel_size, + stride, + use_se, + act=None, + name=''): + super(ResidualUnit, self).__init__() + self.if_shortcut = stride == 1 and in_channels == out_channels + self.if_se = use_se + + self.expand_conv = ConvBNLayer( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=1, stride=1, padding=0, if_act=True, act=act, - name=name + '_expand') - - conv1 = self.conv_bn_layer( - input=conv0, - filter_size=filter_size, - num_filters=num_mid_filter, + name=name + "_expand") + self.bottleneck_conv = ConvBNLayer( + in_channels=mid_channels, + out_channels=mid_channels, + kernel_size=kernel_size, stride=stride, - padding=int((filter_size - 1) // 2), + padding=int((kernel_size - 1) // 2), + groups=mid_channels, if_act=True, act=act, - num_groups=num_mid_filter, - use_cudnn=False, - name=name + '_depthwise') - if use_se: - conv1 = self.se_block( - input=conv1, num_out_filter=num_mid_filter, name=name + '_se') - - conv2 = self.conv_bn_layer( - input=conv1, - filter_size=1, - num_filters=num_out_filter, + name=name + "_depthwise") + if self.if_se: + self.mid_se = SEModule(mid_channels, name=name + "_se") + self.linear_conv = ConvBNLayer( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, stride=1, padding=0, if_act=False, - name=name + '_linear', - res_last_bn_init=True) - if num_in_filter != num_out_filter or stride != 1: - return conv2 - else: - return fluid.layers.elementwise_add(x=input, y=conv2, act=None) + act=None, + name=name + "_linear") + + def forward(self, inputs): + x = self.expand_conv(inputs) + x = self.bottleneck_conv(x) + if self.if_se: + x = self.mid_se(x) + x = self.linear_conv(x) + if self.if_shortcut: + x = paddle.elementwise_add(inputs, x) + return x + + +class SEModule(nn.Layer): + def __init__(self, in_channels, reduction=4, name=""): + super(SEModule, self).__init__() + self.avg_pool = nn.Pool2D( + pool_type="avg", global_pooling=True, use_cudnn=False) + self.conv1 = nn.Conv2d( + in_channels=in_channels, + out_channels=in_channels // reduction, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(name=name + "_1_weights"), + bias_attr=ParamAttr(name=name + "_1_offset")) + self.conv2 = nn.Conv2d( + in_channels=in_channels // reduction, + out_channels=in_channels, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(name + "_2_weights"), + bias_attr=ParamAttr(name=name + "_2_offset")) + + def forward(self, inputs): + outputs = self.avg_pool(inputs) + outputs = self.conv1(outputs) + outputs = F.relu(outputs) + outputs = self.conv2(outputs) + outputs = F.hard_sigmoid(outputs) + return inputs * outputs diff --git a/ppocr/modeling/backbones/det_resnet_vd.py b/ppocr/modeling/backbones/det_resnet_vd.py old mode 100755 new mode 100644 index 52a441f3..b501bec8 --- a/ppocr/modeling/backbones/det_resnet_vd.py +++ b/ppocr/modeling/backbones/det_resnet_vd.py @@ -1,252 +1,329 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr +from paddle import nn +from paddle.nn import functional as F +from paddle import ParamAttr __all__ = ["ResNet"] -class ResNet(object): - def __init__(self, params): +class ResNet(nn.Layer): + def __init__(self, in_channels=3, layers=50, **kwargs): """ the Resnet backbone network for detection module. Args: params(dict): the super parameters for network build """ - self.layers = params['layers'] - supported_layers = [18, 34, 50, 101, 152] - assert self.layers in supported_layers, \ - "supported layers are {} but input layer is {}".format(supported_layers, self.layers) - self.is_3x3 = True - - def __call__(self, input): - layers = self.layers - is_3x3 = self.is_3x3 - if layers == 18: - depth = [2, 2, 2, 2] - elif layers == 34 or layers == 50: - depth = [3, 4, 6, 3] - elif layers == 101: - depth = [3, 4, 23, 3] - elif layers == 152: - depth = [3, 8, 36, 3] - elif layers == 200: - depth = [3, 12, 48, 3] + super(ResNet, self).__init__() + supported_layers = { + 18: { + 'depth': [2, 2, 2, 2], + 'block_class': BasicBlock + }, + 34: { + 'depth': [3, 4, 6, 3], + 'block_class': BasicBlock + }, + 50: { + 'depth': [3, 4, 6, 3], + 'block_class': BottleneckBlock + }, + 101: { + 'depth': [3, 4, 23, 3], + 'block_class': BottleneckBlock + }, + 152: { + 'depth': [3, 8, 36, 3], + 'block_class': BottleneckBlock + }, + 200: { + 'depth': [3, 12, 48, 3], + 'block_class': BottleneckBlock + } + } + assert layers in supported_layers, \ + "supported layers are {} but input layer is {}".format(supported_layers.keys(), layers) + is_3x3 = True + + depth = supported_layers[layers]['depth'] + block_class = supported_layers[layers]['block_class'] + num_filters = [64, 128, 256, 512] - outs = [] + conv = [] if is_3x3 == False: - conv = self.conv_bn_layer( - input=input, - num_filters=64, - filter_size=7, - stride=2, - act='relu') + conv.append( + ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=7, + stride=2, + act='relu')) else: - conv = self.conv_bn_layer( - input=input, - num_filters=32, - filter_size=3, - stride=2, - act='relu', - name='conv1_1') - conv = self.conv_bn_layer( - input=conv, - num_filters=32, - filter_size=3, - stride=1, - act='relu', - name='conv1_2') - conv = self.conv_bn_layer( - input=conv, - num_filters=64, - filter_size=3, - stride=1, - act='relu', - name='conv1_3') - - conv = fluid.layers.pool2d( - input=conv, - pool_size=3, - pool_stride=2, - pool_padding=1, - pool_type='max') - - if layers >= 50: - for block in range(len(depth)): - for i in range(depth[block]): - if layers in [101, 152, 200] and block == 2: + conv.append( + ConvBNLayer( + in_channels=3, + out_channels=32, + kernel_size=3, + stride=2, + act='relu', + name='conv1_1')) + conv.append( + ConvBNLayer( + in_channels=32, + out_channels=32, + kernel_size=3, + stride=1, + act='relu', + name='conv1_2')) + conv.append( + ConvBNLayer( + in_channels=32, + out_channels=64, + kernel_size=3, + stride=1, + act='relu', + name='conv1_3')) + self.conv1 = nn.Sequential(*conv) + self.pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.stages = [] + self.out_channels = [] + in_ch = 64 + for block_index in range(len(depth)): + block_list = [] + for i in range(depth[block_index]): + if layers >= 50: + if layers in [101, 152, 200] and block_index == 2: if i == 0: - conv_name = "res" + str(block + 2) + "a" + conv_name = "res" + str(block_index + 2) + "a" else: - conv_name = "res" + str(block + 2) + "b" + str(i) + conv_name = "res" + str(block_index + + 2) + "b" + str(i) else: - conv_name = "res" + str(block + 2) + chr(97 + i) - conv = self.bottleneck_block( - input=conv, - num_filters=num_filters[block], - stride=2 if i == 0 and block != 0 else 1, - if_first=block == i == 0, - name=conv_name) - outs.append(conv) - else: - for block in range(len(depth)): - for i in range(depth[block]): - conv_name = "res" + str(block + 2) + chr(97 + i) - conv = self.basic_block( - input=conv, - num_filters=num_filters[block], - stride=2 if i == 0 and block != 0 else 1, - if_first=block == i == 0, - name=conv_name) - outs.append(conv) - return outs - - def conv_bn_layer(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, + conv_name = "res" + str(block_index + 2) + chr(97 + i) + else: + conv_name = "res" + str(block_index + 2) + chr(97 + i) + block_list.append( + block_class( + in_channels=in_ch, + out_channels=num_filters[block_index], + stride=2 if i == 0 and block_index != 0 else 1, + if_first=block_index == i == 0, + name=conv_name)) + in_ch = block_list[-1].out_channels + self.out_channels.append(in_ch) + self.stages.append(nn.Sequential(*block_list)) + for i, stage in enumerate(self.stages): + self.add_sublayer(sublayer=stage, name="stage{}".format(i)) + + def forward(self, x): + x = self.conv1(x) + x = self.pool(x) + out_list = [] + for stage in self.stages: + x = stage(x) + out_list.append(x) + return out_list + + +class ConvBNLayer(nn.Layer): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None): + super(ConvBNLayer, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, stride=stride, - padding=(filter_size - 1) // 2, + padding=(kernel_size - 1) // 2, groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), + weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False) if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, + self.bn = nn.BatchNorm( + num_channels=out_channels, act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - def conv_bn_layer_new(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - pool = fluid.layers.pool2d( - input=input, - pool_size=2, - pool_stride=2, - pool_padding=0, - pool_type='avg', - ceil_mode=True) - - conv = fluid.layers.conv2d( - input=pool, - num_filters=num_filters, - filter_size=filter_size, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance") + + def __call__(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class ConvBNLayerNew(nn.Layer): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None): + super(ConvBNLayerNew, self).__init__() + self.pool = nn.AvgPool2d( + kernel_size=2, stride=2, padding=0, ceil_mode=True) + + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, stride=1, - padding=(filter_size - 1) // 2, + padding=(kernel_size - 1) // 2, groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), + weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False) if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, + self.bn = nn.BatchNorm( + num_channels=out_channels, act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - def shortcut(self, input, ch_out, stride, name, if_first=False): - ch_in = input.shape[1] - if ch_in != ch_out or stride != 1: + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance") + + def __call__(self, x): + x = self.pool(x) + x = self.conv(x) + x = self.bn(x) + return x + + +class ShortCut(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first=False): + super(ShortCut, self).__init__() + self.use_conv = True + if in_channels != out_channels or stride != 1: if if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayer( + in_channels, out_channels, 1, stride, name=name) else: - return self.conv_bn_layer_new( - input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayerNew( + in_channels, out_channels, 1, stride, name=name) elif if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayer( + in_channels, out_channels, 1, stride, name=name) else: - return input + self.use_conv = False + + def forward(self, x): + if self.use_conv: + x = self.conv(x) + return x - def bottleneck_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=1, + +class BottleneckBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first): + super(BottleneckBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, act='relu', name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, stride=stride, act='relu', name=name + "_branch2b") - conv2 = self.conv_bn_layer( - input=conv1, - num_filters=num_filters * 4, - filter_size=1, + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, act=None, name=name + "_branch2c") - short = self.shortcut( - input, - num_filters * 4, - stride, + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels * 4, + stride=stride, if_first=if_first, name=name + "_branch1") + self.out_channels = out_channels * 4 + + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = self.conv2(y) + y = y + self.short(x) + y = F.relu(y) + return y - return fluid.layers.elementwise_add(x=short, y=conv2, act='relu') - def basic_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=3, +class BasicBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first): + super(BasicBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, act='relu', stride=stride, name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, act=None, name=name + "_branch2b") - short = self.shortcut( - input, - num_filters, - stride, + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels, + stride=stride, if_first=if_first, name=name + "_branch1") - return fluid.layers.elementwise_add(x=short, y=conv1, act='relu') + self.out_channels = out_channels + + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = y + self.short(x) + return F.relu(y) + + +if __name__ == '__main__': + import paddle + + paddle.disable_static() + x = paddle.zeros([1, 3, 640, 640]) + x = paddle.to_variable(x) + print(x.shape) + net = ResNet(layers=18) + y = net(x) + + for stage in y: + print(stage.shape) + # paddle.save(net.state_dict(),'1.pth') diff --git a/ppocr/modeling/backbones/det_resnet_vd_sast.py b/ppocr/modeling/backbones/det_resnet_vd_sast.py deleted file mode 100644 index 14fe7138..00000000 --- a/ppocr/modeling/backbones/det_resnet_vd_sast.py +++ /dev/null @@ -1,274 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr - -__all__ = ["ResNet"] - - -class ResNet(object): - def __init__(self, params): - """ - the Resnet backbone network for detection module. - Args: - params(dict): the super parameters for network build - """ - self.layers = params['layers'] - supported_layers = [18, 34, 50, 101, 152] - assert self.layers in supported_layers, \ - "supported layers are {} but input layer is {}".format(supported_layers, self.layers) - self.is_3x3 = True - - def __call__(self, input): - layers = self.layers - is_3x3 = self.is_3x3 - # if layers == 18: - # depth = [2, 2, 2, 2] - # elif layers == 34 or layers == 50: - # depth = [3, 4, 6, 3] - # elif layers == 101: - # depth = [3, 4, 23, 3] - # elif layers == 152: - # depth = [3, 8, 36, 3] - # elif layers == 200: - # depth = [3, 12, 48, 3] - # num_filters = [64, 128, 256, 512] - # outs = [] - - if layers == 18: - depth = [2, 2, 2, 2]#, 3, 3] - elif layers == 34 or layers == 50: - #depth = [3, 4, 6, 3]#, 3, 3] - depth = [3, 4, 6, 3, 3]#, 3] - elif layers == 101: - depth = [3, 4, 23, 3]#, 3, 3] - elif layers == 152: - depth = [3, 8, 36, 3]#, 3, 3] - num_filters = [64, 128, 256, 512, 512]#, 512] - blocks = {} - - idx = 'block_0' - blocks[idx] = input - - if is_3x3 == False: - conv = self.conv_bn_layer( - input=input, - num_filters=64, - filter_size=7, - stride=2, - act='relu') - else: - conv = self.conv_bn_layer( - input=input, - num_filters=32, - filter_size=3, - stride=2, - act='relu', - name='conv1_1') - conv = self.conv_bn_layer( - input=conv, - num_filters=32, - filter_size=3, - stride=1, - act='relu', - name='conv1_2') - conv = self.conv_bn_layer( - input=conv, - num_filters=64, - filter_size=3, - stride=1, - act='relu', - name='conv1_3') - idx = 'block_1' - blocks[idx] = conv - - conv = fluid.layers.pool2d( - input=conv, - pool_size=3, - pool_stride=2, - pool_padding=1, - pool_type='max') - - if layers >= 50: - for block in range(len(depth)): - for i in range(depth[block]): - if layers in [101, 152, 200] and block == 2: - if i == 0: - conv_name = "res" + str(block + 2) + "a" - else: - conv_name = "res" + str(block + 2) + "b" + str(i) - else: - conv_name = "res" + str(block + 2) + chr(97 + i) - conv = self.bottleneck_block( - input=conv, - num_filters=num_filters[block], - stride=2 if i == 0 and block != 0 else 1, - if_first=block == i == 0, - name=conv_name) - # outs.append(conv) - idx = 'block_' + str(block + 2) - blocks[idx] = conv - else: - for block in range(len(depth)): - for i in range(depth[block]): - conv_name = "res" + str(block + 2) + chr(97 + i) - conv = self.basic_block( - input=conv, - num_filters=num_filters[block], - stride=2 if i == 0 and block != 0 else 1, - if_first=block == i == 0, - name=conv_name) - # outs.append(conv) - idx = 'block_' + str(block + 2) - blocks[idx] = conv - # return outs - return blocks - - def conv_bn_layer(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=(filter_size - 1) // 2, - groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), - bias_attr=False) - if name == "conv1": - bn_name = "bn_" + name - else: - bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, - act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - def conv_bn_layer_new(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - pool = fluid.layers.pool2d( - input=input, - pool_size=2, - pool_stride=2, - pool_padding=0, - pool_type='avg', - ceil_mode=True) - - conv = fluid.layers.conv2d( - input=pool, - num_filters=num_filters, - filter_size=filter_size, - stride=1, - padding=(filter_size - 1) // 2, - groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), - bias_attr=False) - if name == "conv1": - bn_name = "bn_" + name - else: - bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, - act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - def shortcut(self, input, ch_out, stride, name, if_first=False): - ch_in = input.shape[1] - if ch_in != ch_out or stride != 1: - if if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) - else: - return self.conv_bn_layer_new( - input, ch_out, 1, stride, name=name) - elif if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) - else: - return input - - def bottleneck_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=1, - act='relu', - name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, - stride=stride, - act='relu', - name=name + "_branch2b") - conv2 = self.conv_bn_layer( - input=conv1, - num_filters=num_filters * 4, - filter_size=1, - act=None, - name=name + "_branch2c") - - short = self.shortcut( - input, - num_filters * 4, - stride, - if_first=if_first, - name=name + "_branch1") - - return fluid.layers.elementwise_add(x=short, y=conv2, act='relu') - - def basic_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=3, - act='relu', - stride=stride, - name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, - act=None, - name=name + "_branch2b") - short = self.shortcut( - input, - num_filters, - stride, - if_first=if_first, - name=name + "_branch1") - return fluid.layers.elementwise_add(x=short, y=conv1, act='relu') diff --git a/ppocr/modeling/backbones/rec_mobilenet_v3.py b/ppocr/modeling/backbones/rec_mobilenet_v3.py old mode 100755 new mode 100644 index ff39a812..bcba8600 --- a/ppocr/modeling/backbones/rec_mobilenet_v3.py +++ b/ppocr/modeling/backbones/rec_mobilenet_v3.py @@ -1,53 +1,49 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +from paddle import nn -import paddle.fluid as fluid -from paddle.fluid.initializer import MSRA -from paddle.fluid.param_attr import ParamAttr +from ppocr.modeling.backbones.det_mobilenet_v3 import ResidualUnit, ConvBNLayer, make_divisible -__all__ = [ - 'MobileNetV3', 'MobileNetV3_small_x0_35', 'MobileNetV3_small_x0_5', - 'MobileNetV3_small_x0_75', 'MobileNetV3_small_x1_0', - 'MobileNetV3_small_x1_25', 'MobileNetV3_large_x0_35', - 'MobileNetV3_large_x0_5', 'MobileNetV3_large_x0_75', - 'MobileNetV3_large_x1_0', 'MobileNetV3_large_x1_25' -] +__all__ = ['MobileNetV3'] -class MobileNetV3(): - def __init__(self, params): - self.scale = params.get("scale", 0.5) - model_name = params.get("model_name", "small") - large_stride = params.get("large_stride", [1, 2, 2, 2]) - small_stride = params.get("small_stride", [2, 2, 2, 2]) +class MobileNetV3(nn.Layer): + def __init__(self, + in_channels=3, + model_name='small', + scale=0.5, + large_stride=None, + small_stride=None, + **kwargs): + super(MobileNetV3, self).__init__() + if small_stride is None: + small_stride = [2, 2, 2, 2] + if large_stride is None: + large_stride = [1, 2, 2, 2] assert isinstance(large_stride, list), "large_stride type must " \ - "be list but got {}".format(type(large_stride)) + "be list but got {}".format(type(large_stride)) assert isinstance(small_stride, list), "small_stride type must " \ - "be list but got {}".format(type(small_stride)) + "be list but got {}".format(type(small_stride)) assert len(large_stride) == 4, "large_stride length must be " \ - "4 but got {}".format(len(large_stride)) + "4 but got {}".format(len(large_stride)) assert len(small_stride) == 4, "small_stride length must be " \ - "4 but got {}".format(len(small_stride)) + "4 but got {}".format(len(small_stride)) - self.inplanes = 16 if model_name == "large": - self.cfg = [ + cfg = [ # k, exp, c, se, nl, s, [3, 16, 16, False, 'relu', large_stride[0]], [3, 64, 24, False, 'relu', (large_stride[1], 1)], @@ -65,10 +61,9 @@ class MobileNetV3(): [5, 960, 160, True, 'hard_swish', 1], [5, 960, 160, True, 'hard_swish', 1], ] - self.cls_ch_squeeze = 960 - self.cls_ch_expand = 1280 + cls_ch_squeeze = 960 elif model_name == "small": - self.cfg = [ + cfg = [ # k, exp, c, se, nl, s, [3, 16, 16, True, 'relu', (small_stride[0], 1)], [3, 72, 24, False, 'relu', (small_stride[1], 1)], @@ -82,186 +77,72 @@ class MobileNetV3(): [5, 576, 96, True, 'hard_swish', 1], [5, 576, 96, True, 'hard_swish', 1], ] - self.cls_ch_squeeze = 576 - self.cls_ch_expand = 1280 + cls_ch_squeeze = 576 else: raise NotImplementedError("mode[" + model_name + "_model] is not implemented!") supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25] - assert self.scale in supported_scale, \ - "supported scales are {} but input scale is {}".format(supported_scale, self.scale) - - def __call__(self, input): - scale = self.scale - inplanes = self.inplanes - cfg = self.cfg - cls_ch_squeeze = self.cls_ch_squeeze - cls_ch_expand = self.cls_ch_expand - #conv1 - conv = self.conv_bn_layer( - input, - filter_size=3, - num_filters=self.make_divisible(inplanes * scale), + assert scale in supported_scale, \ + "supported scales are {} but input scale is {}".format(supported_scale, scale) + + inplanes = 16 + # conv1 + self.conv1 = ConvBNLayer( + in_channels=in_channels, + out_channels=make_divisible(inplanes * scale), + kernel_size=3, stride=2, padding=1, - num_groups=1, + groups=1, if_act=True, act='hard_swish', name='conv1') i = 0 - inplanes = self.make_divisible(inplanes * scale) - for layer_cfg in cfg: - conv = self.residual_unit( - input=conv, - num_in_filter=inplanes, - num_mid_filter=self.make_divisible(scale * layer_cfg[1]), - num_out_filter=self.make_divisible(scale * layer_cfg[2]), - act=layer_cfg[4], - stride=layer_cfg[5], - filter_size=layer_cfg[0], - use_se=layer_cfg[3], - name='conv' + str(i + 2)) - inplanes = self.make_divisible(scale * layer_cfg[2]) + block_list = [] + inplanes = make_divisible(inplanes * scale) + for (k, exp, c, se, nl, s) in cfg: + block_list.append( + ResidualUnit( + in_channels=inplanes, + mid_channels=make_divisible(scale * exp), + out_channels=make_divisible(scale * c), + kernel_size=k, + stride=s, + use_se=se, + act=nl, + name='conv' + str(i + 2))) + inplanes = make_divisible(scale * c) i += 1 + self.blocks = nn.Sequential(*block_list) - conv = self.conv_bn_layer( - input=conv, - filter_size=1, - num_filters=self.make_divisible(scale * cls_ch_squeeze), + self.conv2 = ConvBNLayer( + in_channels=inplanes, + out_channels=make_divisible(scale * cls_ch_squeeze), + kernel_size=1, stride=1, padding=0, - num_groups=1, + groups=1, if_act=True, act='hard_swish', name='conv_last') - conv = fluid.layers.pool2d( - input=conv, - pool_size=2, - pool_stride=2, - pool_padding=0, - pool_type='max') - return conv - - def conv_bn_layer(self, - input, - filter_size, - num_filters, - stride, - padding, - num_groups=1, - if_act=True, - act=None, - name=None, - use_cudnn=True, - res_last_bn_init=False): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=padding, - groups=num_groups, - act=None, - use_cudnn=use_cudnn, - param_attr=ParamAttr(name=name + '_weights'), - bias_attr=False) - bn_name = name + '_bn' - bn = fluid.layers.batch_norm( - input=conv, - param_attr=ParamAttr( - name=bn_name + "_scale", - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=0.0)), - bias_attr=ParamAttr( - name=bn_name + "_offset", - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=0.0)), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - if if_act: - if act == 'relu': - bn = fluid.layers.relu(bn) - elif act == 'hard_swish': - bn = fluid.layers.hard_swish(bn) - return bn - - def make_divisible(self, v, divisor=8, min_value=None): - if min_value is None: - min_value = divisor - new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) - if new_v < 0.9 * v: - new_v += divisor - return new_v - - def se_block(self, input, num_out_filter, ratio=4, name=None): - num_mid_filter = num_out_filter // ratio - pool = fluid.layers.pool2d( - input=input, pool_type='avg', global_pooling=True, use_cudnn=False) - conv1 = fluid.layers.conv2d( - input=pool, - filter_size=1, - num_filters=num_mid_filter, - act='relu', - param_attr=ParamAttr(name=name + '_1_weights'), - bias_attr=ParamAttr(name=name + '_1_offset')) - conv2 = fluid.layers.conv2d( - input=conv1, - filter_size=1, - num_filters=num_out_filter, - act='hard_sigmoid', - param_attr=ParamAttr(name=name + '_2_weights'), - bias_attr=ParamAttr(name=name + '_2_offset')) - scale = fluid.layers.elementwise_mul(x=input, y=conv2, axis=0) - return scale - - def residual_unit(self, - input, - num_in_filter, - num_mid_filter, - num_out_filter, - stride, - filter_size, - act=None, - use_se=False, - name=None): - - conv0 = self.conv_bn_layer( - input=input, - filter_size=1, - num_filters=num_mid_filter, - stride=1, - padding=0, - if_act=True, - act=act, - name=name + '_expand') - - conv1 = self.conv_bn_layer( - input=conv0, - filter_size=filter_size, - num_filters=num_mid_filter, - stride=stride, - padding=int((filter_size - 1) // 2), - if_act=True, - act=act, - num_groups=num_mid_filter, - use_cudnn=False, - name=name + '_depthwise') - if use_se: - conv1 = self.se_block( - input=conv1, num_out_filter=num_mid_filter, name=name + '_se') - - conv2 = self.conv_bn_layer( - input=conv1, - filter_size=1, - num_filters=num_out_filter, - stride=1, - padding=0, - if_act=False, - name=name + '_linear', - res_last_bn_init=True) - if num_in_filter != num_out_filter or stride != 1: - return conv2 - else: - return fluid.layers.elementwise_add(x=input, y=conv2, act=None) + self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) + self.out_channels = make_divisible(scale * cls_ch_squeeze) + + def forward(self, x): + x = self.conv1(x) + x = self.blocks(x) + x = self.conv2(x) + x = self.pool(x) + return x + + +if __name__ == '__main__': + import paddle + paddle.disable_static() + x = paddle.zeros((1, 3, 32, 320)) + x = paddle.to_variable(x) + net = MobileNetV3(model_name='small', small_stride=[1, 2, 2, 2]) + y = net(x) + print(y.shape) diff --git a/ppocr/modeling/backbones/rec_resnet_fpn.py b/ppocr/modeling/backbones/rec_resnet_fpn.py deleted file mode 100755 index 0a05b5de..00000000 --- a/ppocr/modeling/backbones/rec_resnet_fpn.py +++ /dev/null @@ -1,246 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr - -__all__ = [ - "ResNet", "ResNet18", "ResNet34", "ResNet50", "ResNet101", "ResNet152" -] - -Trainable = True -w_nolr = fluid.ParamAttr(trainable=Trainable) -train_parameters = { - "input_size": [3, 224, 224], - "input_mean": [0.485, 0.456, 0.406], - "input_std": [0.229, 0.224, 0.225], - "learning_strategy": { - "name": "piecewise_decay", - "batch_size": 256, - "epochs": [30, 60, 90], - "steps": [0.1, 0.01, 0.001, 0.0001] - } -} - - -class ResNet(): - def __init__(self, params): - self.layers = params['layers'] - self.params = train_parameters - - def __call__(self, input): - layers = self.layers - supported_layers = [18, 34, 50, 101, 152] - assert layers in supported_layers, \ - "supported layers are {} but input layer is {}".format(supported_layers, layers) - - if layers == 18: - depth = [2, 2, 2, 2] - elif layers == 34 or layers == 50: - depth = [3, 4, 6, 3] - elif layers == 101: - depth = [3, 4, 23, 3] - elif layers == 152: - depth = [3, 8, 36, 3] - stride_list = [(2, 2), (2, 2), (1, 1), (1, 1)] - num_filters = [64, 128, 256, 512] - - conv = self.conv_bn_layer( - input=input, - num_filters=64, - filter_size=7, - stride=2, - act='relu', - name="conv1") - F = [] - if layers >= 50: - for block in range(len(depth)): - for i in range(depth[block]): - if layers in [101, 152] and block == 2: - if i == 0: - conv_name = "res" + str(block + 2) + "a" - else: - conv_name = "res" + str(block + 2) + "b" + str(i) - else: - conv_name = "res" + str(block + 2) + chr(97 + i) - conv = self.bottleneck_block( - input=conv, - num_filters=num_filters[block], - stride=stride_list[block] if i == 0 else 1, - name=conv_name) - F.append(conv) - else: - for block in range(len(depth)): - for i in range(depth[block]): - conv_name = "res" + str(block + 2) + chr(97 + i) - - if i == 0 and block != 0: - stride = (2, 1) - else: - stride = (1, 1) - - conv = self.basic_block( - input=conv, - num_filters=num_filters[block], - stride=stride, - if_first=block == i == 0, - name=conv_name) - F.append(conv) - - base = F[-1] - for i in [-2, -3]: - b, c, w, h = F[i].shape - if (w, h) == base.shape[2:]: - base = base - else: - base = fluid.layers.conv2d_transpose( - input=base, - num_filters=c, - filter_size=4, - stride=2, - padding=1, - act=None, - param_attr=w_nolr, - bias_attr=w_nolr) - base = fluid.layers.batch_norm( - base, act="relu", param_attr=w_nolr, bias_attr=w_nolr) - base = fluid.layers.concat([base, F[i]], axis=1) - base = fluid.layers.conv2d( - base, - num_filters=c, - filter_size=1, - param_attr=w_nolr, - bias_attr=w_nolr) - base = fluid.layers.conv2d( - base, - num_filters=c, - filter_size=3, - padding=1, - param_attr=w_nolr, - bias_attr=w_nolr) - base = fluid.layers.batch_norm( - base, act="relu", param_attr=w_nolr, bias_attr=w_nolr) - - base = fluid.layers.conv2d( - base, - num_filters=512, - filter_size=1, - bias_attr=w_nolr, - param_attr=w_nolr) - - return base - - def conv_bn_layer(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=2 if stride == (1, 1) else filter_size, - dilation=2 if stride == (1, 1) else 1, - stride=stride, - padding=(filter_size - 1) // 2, - groups=groups, - act=None, - param_attr=ParamAttr( - name=name + "_weights", trainable=Trainable), - bias_attr=False, - name=name + '.conv2d.output.1') - - if name == "conv1": - bn_name = "bn_" + name - else: - bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, - act=act, - name=bn_name + '.output.1', - param_attr=ParamAttr( - name=bn_name + '_scale', trainable=Trainable), - bias_attr=ParamAttr( - bn_name + '_offset', trainable=Trainable), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance', ) - - def shortcut(self, input, ch_out, stride, is_first, name): - ch_in = input.shape[1] - if ch_in != ch_out or stride != 1 or is_first == True: - if stride == (1, 1): - return self.conv_bn_layer(input, ch_out, 1, 1, name=name) - else: #stride == (2,2) - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) - - else: - return input - - def bottleneck_block(self, input, num_filters, stride, name): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=1, - act='relu', - name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, - stride=stride, - act='relu', - name=name + "_branch2b") - conv2 = self.conv_bn_layer( - input=conv1, - num_filters=num_filters * 4, - filter_size=1, - act=None, - name=name + "_branch2c") - - short = self.shortcut( - input, - num_filters * 4, - stride, - is_first=False, - name=name + "_branch1") - - return fluid.layers.elementwise_add( - x=short, y=conv2, act='relu', name=name + ".add.output.5") - - def basic_block(self, input, num_filters, stride, is_first, name): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=3, - act='relu', - stride=stride, - name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, - act=None, - name=name + "_branch2b") - short = self.shortcut( - input, num_filters, stride, is_first, name=name + "_branch1") - return fluid.layers.elementwise_add(x=short, y=conv1, act='relu') diff --git a/ppocr/modeling/backbones/rec_resnet_vd.py b/ppocr/modeling/backbones/rec_resnet_vd.py old mode 100755 new mode 100644 index bc58c8ac..d8602498 --- a/ppocr/modeling/backbones/rec_resnet_vd.py +++ b/ppocr/modeling/backbones/rec_resnet_vd.py @@ -1,271 +1,312 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import math +from paddle import nn, ParamAttr +from paddle.nn import functional as F -import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr +__all__ = ["ResNet"] -__all__ = [ - "ResNet", "ResNet18_vd", "ResNet34_vd", "ResNet50_vd", "ResNet101_vd", - "ResNet152_vd", "ResNet200_vd" -] +class ResNet(nn.Layer): + def __init__(self, in_channels=3, layers=34): + super(ResNet, self).__init__() + supported_layers = { + 18: { + 'depth': [2, 2, 2, 2], + 'block_class': BasicBlock + }, + 34: { + 'depth': [3, 4, 6, 3], + 'block_class': BasicBlock + }, + 50: { + 'depth': [3, 4, 6, 3], + 'block_class': BottleneckBlock + }, + 101: { + 'depth': [3, 4, 23, 3], + 'block_class': BottleneckBlock + }, + 152: { + 'depth': [3, 8, 36, 3], + 'block_class': BottleneckBlock + }, + 200: { + 'depth': [3, 12, 48, 3], + 'block_class': BottleneckBlock + } + } + assert layers in supported_layers, \ + "supported layers are {} but input layer is {}".format(supported_layers.keys(), layers) + is_3x3 = True -class ResNet(): - def __init__(self, params): - self.layers = params['layers'] - self.is_3x3 = True - supported_layers = [18, 34, 50, 101, 152, 200] - assert self.layers in supported_layers, \ - "supported layers are {} but input layer is {}".format(supported_layers, self.layers) - - def __call__(self, input): - is_3x3 = self.is_3x3 - layers = self.layers - - if layers == 18: - depth = [2, 2, 2, 2] - elif layers == 34 or layers == 50: - depth = [3, 4, 6, 3] - elif layers == 101: - depth = [3, 4, 23, 3] - elif layers == 152: - depth = [3, 8, 36, 3] - elif layers == 200: - depth = [3, 12, 48, 3] num_filters = [64, 128, 256, 512] + depth = supported_layers[layers]['depth'] + block_class = supported_layers[layers]['block_class'] + conv = [] if is_3x3 == False: - conv = self.conv_bn_layer( - input=input, - num_filters=64, - filter_size=7, - stride=1, - act='relu') + conv.append( + ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=7, + stride=1, + act='relu')) else: - conv = self.conv_bn_layer( - input=input, - num_filters=32, - filter_size=3, - stride=1, - act='relu', - name='conv1_1') - conv = self.conv_bn_layer( - input=conv, - num_filters=32, - filter_size=3, - stride=1, - act='relu', - name='conv1_2') - conv = self.conv_bn_layer( - input=conv, - num_filters=64, - filter_size=3, - stride=1, - act='relu', - name='conv1_3') + conv.append( + ConvBNLayer( + in_channels=in_channels, + out_channels=32, + kernel_size=3, + stride=1, + act='relu', + name='conv1_1')) + conv.append( + ConvBNLayer( + in_channels=32, + out_channels=32, + kernel_size=3, + stride=1, + act='relu', + name='conv1_2')) + conv.append( + ConvBNLayer( + in_channels=32, + out_channels=64, + kernel_size=3, + stride=1, + act='relu', + name='conv1_3')) + self.conv1 = nn.Sequential(*conv) - conv = fluid.layers.pool2d( - input=conv, - pool_size=3, - pool_stride=2, - pool_padding=1, - pool_type='max') + self.pool = nn.MaxPool2d( + kernel_size=3, + stride=2, + padding=1, ) - if layers >= 50: - for block in range(len(depth)): - for i in range(depth[block]): - if layers in [101, 152, 200] and block == 2: + block_list = [] + in_ch = 64 + for block_index in range(len(depth)): + for i in range(depth[block_index]): + if layers >= 50: + if layers in [101, 152, 200] and block_index == 2: if i == 0: - conv_name = "res" + str(block + 2) + "a" + conv_name = "res" + str(block_index + 2) + "a" else: - conv_name = "res" + str(block + 2) + "b" + str(i) - else: - conv_name = "res" + str(block + 2) + chr(97 + i) - - if i == 0 and block != 0: - stride = (2, 1) - else: - stride = (1, 1) - - conv = self.bottleneck_block( - input=conv, - num_filters=num_filters[block], - stride=stride, - if_first=block == i == 0, - name=conv_name) - else: - for block in range(len(depth)): - for i in range(depth[block]): - conv_name = "res" + str(block + 2) + chr(97 + i) - - if i == 0 and block != 0: - stride = (2, 1) + conv_name = "res" + str(block_index + + 2) + "b" + str(i) else: - stride = (1, 1) - - conv = self.basic_block( - input=conv, - num_filters=num_filters[block], + conv_name = "res" + str(block_index + 2) + chr(97 + i) + else: + conv_name = "res" + str(block_index + 2) + chr(97 + i) + if i == 0 and block_index != 0: + stride = (2, 1) + else: + stride = (1, 1) + block_list.append( + block_class( + in_channels=in_ch, + out_channels=num_filters[block_index], stride=stride, - if_first=block == i == 0, - name=conv_name) + if_first=block_index == i == 0, + name=conv_name)) + in_ch = block_list[-1].out_channels + self.block_list = nn.Sequential(*block_list) + self.add_sublayer(sublayer=self.block_list, name="block_list") + self.pool_out = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) + self.out_channels = in_ch - conv = fluid.layers.pool2d( - input=conv, - pool_size=2, - pool_stride=2, - pool_padding=0, - pool_type='max') + def forward(self, x): + x = self.conv1(x) + x = self.pool(x) + x = self.block_list(x) + x = self.pool_out(x) + return x - return conv - def conv_bn_layer(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, +class ConvBNLayer(nn.Layer): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None): + super(ConvBNLayer, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, stride=stride, - padding=(filter_size - 1) // 2, + padding=(kernel_size - 1) // 2, groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), + weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False) if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, + self.bn = nn.BatchNorm( + num_channels=out_channels, act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance") + + def __call__(self, x): + x = self.conv(x) + x = self.bn(x) + return x - def conv_bn_layer_new(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - pool = fluid.layers.pool2d( - input=input, - pool_size=stride, - pool_stride=stride, - pool_padding=0, - pool_type='avg', - ceil_mode=True) - conv = fluid.layers.conv2d( - input=pool, - num_filters=num_filters, - filter_size=filter_size, +class ConvBNLayerNew(nn.Layer): + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None): + super(ConvBNLayerNew, self).__init__() + self.pool = nn.AvgPool2d( + kernel_size=stride, stride=stride, padding=0, ceil_mode=True) + + self.conv = nn.Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, stride=1, - padding=(filter_size - 1) // 2, + padding=(kernel_size - 1) // 2, groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), + weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False) - if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] - return fluid.layers.batch_norm( - input=conv, + self.bn = nn.BatchNorm( + num_channels=out_channels, act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance") + + def __call__(self, x): + x = self.pool(x) + x = self.conv(x) + x = self.bn(x) + return x + + +class ShortCut(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first=False): + super(ShortCut, self).__init__() + self.use_conv = True - def shortcut(self, input, ch_out, stride, name, if_first=False): - ch_in = input.shape[1] - if ch_in != ch_out or stride[0] != 1: + if in_channels != out_channels or stride[0] != 1: if if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayer( + in_channels, out_channels, 1, stride, name=name) else: - return self.conv_bn_layer_new( - input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayerNew( + in_channels, out_channels, 1, stride, name=name) elif if_first: - return self.conv_bn_layer(input, ch_out, 1, stride, name=name) + self.conv = ConvBNLayer( + in_channels, out_channels, 1, stride, name=name) else: - return input + self.use_conv = False - def bottleneck_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=1, + def forward(self, x): + if self.use_conv: + x = self.conv(x) + return x + + +class BottleneckBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first): + super(BottleneckBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, act='relu', name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, stride=stride, act='relu', name=name + "_branch2b") - conv2 = self.conv_bn_layer( - input=conv1, - num_filters=num_filters * 4, - filter_size=1, + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, act=None, name=name + "_branch2c") - short = self.shortcut( - input, - num_filters * 4, - stride, + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels * 4, + stride=stride, if_first=if_first, name=name + "_branch1") + self.out_channels = out_channels * 4 - return fluid.layers.elementwise_add(x=short, y=conv2, act='relu') + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = self.conv2(y) + y = y + self.short(x) + y = F.relu(y) + return y - def basic_block(self, input, num_filters, stride, name, if_first): - conv0 = self.conv_bn_layer( - input=input, - num_filters=num_filters, - filter_size=3, + +class BasicBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, if_first): + super(BasicBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, act='relu', stride=stride, name=name + "_branch2a") - conv1 = self.conv_bn_layer( - input=conv0, - num_filters=num_filters, - filter_size=3, + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, act=None, name=name + "_branch2b") - short = self.shortcut( - input, - num_filters, - stride, + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels, + stride=stride, if_first=if_first, name=name + "_branch1") - return fluid.layers.elementwise_add(x=short, y=conv1, act='relu') + self.out_channels = out_channels + + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = y + self.short(x) + return F.relu(y) diff --git a/ppocr/modeling/common_functions.py b/ppocr/modeling/common_functions.py deleted file mode 100755 index 2ebcb042..00000000 --- a/ppocr/modeling/common_functions.py +++ /dev/null @@ -1,95 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr -import math - - -def get_para_bias_attr(l2_decay, k, name): - regularizer = fluid.regularizer.L2Decay(l2_decay) - stdv = 1.0 / math.sqrt(k * 1.0) - initializer = fluid.initializer.Uniform(-stdv, stdv) - para_attr = fluid.ParamAttr( - regularizer=regularizer, initializer=initializer, name=name + "_w_attr") - bias_attr = fluid.ParamAttr( - regularizer=regularizer, initializer=initializer, name=name + "_b_attr") - return [para_attr, bias_attr] - - -def conv_bn_layer(input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = fluid.layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=(filter_size - 1) // 2, - groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), - bias_attr=False, - name=name + '.conv2d') - - bn_name = "bn_" + name - return fluid.layers.batch_norm( - input=conv, - act=act, - name=bn_name + '.output', - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - -def deconv_bn_layer(input, - num_filters, - filter_size=4, - stride=2, - act='relu', - name=None): - deconv = fluid.layers.conv2d_transpose( - input=input, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=1, - act=None, - param_attr=ParamAttr(name=name + "_weights"), - bias_attr=False, - name=name + '.deconv2d') - bn_name = "bn_" + name - return fluid.layers.batch_norm( - input=deconv, - act=act, - name=bn_name + '.output', - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - -def create_tmp_var(program, name, dtype, shape, lod_level=0): - return program.current_block().create_var( - name=name, dtype=dtype, shape=shape, lod_level=lod_level) diff --git a/ppocr/modeling/heads/__init__.py b/ppocr/modeling/heads/__init__.py index abf198b9..bed7068d 100755 --- a/ppocr/modeling/heads/__init__.py +++ b/ppocr/modeling/heads/__init__.py @@ -11,3 +11,20 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +__all__ = ['build_head'] + + +def build_head(config): + # det head + from .det_db_head import DBHead + + # rec head + from .rec_ctc_head import CTC + support_dict = ['DBHead', 'CTC'] + + module_name = config.pop('name') + assert module_name in support_dict, Exception('head only support {}'.format( + support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/heads/det_db_head.py b/ppocr/modeling/heads/det_db_head.py index 56998044..85149abd 100644 --- a/ppocr/modeling/heads/det_db_head.py +++ b/ppocr/modeling/heads/det_db_head.py @@ -1,205 +1,128 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr -import paddle.fluid as fluid +def get_bias_attr(k, name): + stdv = 1.0 / math.sqrt(k * 1.0) + initializer = paddle.nn.initializer.Uniform(-stdv, stdv) + bias_attr = ParamAttr(initializer=initializer, name=name + "_b_attr") + return bias_attr -class DBHead(object): - """ - Differentiable Binarization (DB) for text detection: - see https://arxiv.org/abs/1911.08947 - args: - params(dict): super parameters for build DB network - """ - - def __init__(self, params): - self.k = params['k'] - self.inner_channels = params['inner_channels'] - self.C, self.H, self.W = params['image_shape'] - print(self.C, self.H, self.W) - def binarize(self, x): - conv1 = fluid.layers.conv2d( - input=x, - num_filters=self.inner_channels // 4, - filter_size=3, +class Head(nn.Layer): + def __init__(self, in_channels, name_list): + super(Head, self).__init__() + self.conv1 = nn.Conv2d( + in_channels=in_channels, + out_channels=in_channels // 4, + kernel_size=3, padding=1, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), + weight_attr=ParamAttr(name=name_list[0] + '.w_0'), bias_attr=False) - conv_bn1 = fluid.layers.batch_norm( - input=conv1, - param_attr=fluid.initializer.ConstantInitializer(value=1.0), - bias_attr=fluid.initializer.ConstantInitializer(value=1e-4), - act="relu") - conv2 = fluid.layers.conv2d_transpose( - input=conv_bn1, - num_filters=self.inner_channels // 4, - filter_size=2, + self.conv_bn1 = nn.BatchNorm( + num_channels=in_channels // 4, + param_attr=ParamAttr( + name=name_list[1] + '.w_0', + initializer=paddle.nn.initializer.Constant(value=1.0)), + bias_attr=ParamAttr( + name=name_list[1] + '.b_0', + initializer=paddle.nn.initializer.Constant(value=1e-4)), + moving_mean_name=name_list[1] + '.w_1', + moving_variance_name=name_list[1] + '.w_2', + act='relu') + self.conv2 = nn.ConvTranspose2d( + in_channels=in_channels // 4, + out_channels=in_channels // 4, + kernel_size=2, stride=2, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), - bias_attr=self._get_bias_attr(0.0004, conv_bn1.shape[1], "conv2"), - act=None) - conv_bn2 = fluid.layers.batch_norm( - input=conv2, - param_attr=fluid.initializer.ConstantInitializer(value=1.0), - bias_attr=fluid.initializer.ConstantInitializer(value=1e-4), + weight_attr=ParamAttr( + name=name_list[2] + '.w_0', + initializer=paddle.nn.initializer.MSRA(uniform=False)), + bias_attr=get_bias_attr(in_channels // 4, name_list[-1] + "conv2")) + self.conv_bn2 = nn.BatchNorm( + num_channels=in_channels // 4, + param_attr=ParamAttr( + name=name_list[3] + '.w_0', + initializer=paddle.nn.initializer.Constant(value=1.0)), + bias_attr=ParamAttr( + name=name_list[3] + '.b_0', + initializer=paddle.nn.initializer.Constant(value=1e-4)), + moving_mean_name=name_list[3] + '.w_1', + moving_variance_name=name_list[3] + '.w_2', act="relu") - conv3 = fluid.layers.conv2d_transpose( - input=conv_bn2, - num_filters=1, - filter_size=2, + self.conv3 = nn.ConvTranspose2d( + in_channels=in_channels // 4, + out_channels=1, + kernel_size=2, stride=2, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), - bias_attr=self._get_bias_attr(0.0004, conv_bn2.shape[1], "conv3"), - act=None) - out = fluid.layers.sigmoid(conv3) - return out + weight_attr=ParamAttr( + name=name_list[4] + '.w_0', + initializer=paddle.nn.initializer.MSRA(uniform=False)), + bias_attr=get_bias_attr(in_channels // 4, name_list[-1] + "conv3"), + ) - def thresh(self, x): - conv1 = fluid.layers.conv2d( - input=x, - num_filters=self.inner_channels // 4, - filter_size=3, - padding=1, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), - bias_attr=False) - conv_bn1 = fluid.layers.batch_norm( - input=conv1, - param_attr=fluid.initializer.ConstantInitializer(value=1.0), - bias_attr=fluid.initializer.ConstantInitializer(value=1e-4), - act="relu") - conv2 = fluid.layers.conv2d_transpose( - input=conv_bn1, - num_filters=self.inner_channels // 4, - filter_size=2, - stride=2, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), - bias_attr=self._get_bias_attr(0.0004, conv_bn1.shape[1], "conv2"), - act=None) - conv_bn2 = fluid.layers.batch_norm( - input=conv2, - param_attr=fluid.initializer.ConstantInitializer(value=1.0), - bias_attr=fluid.initializer.ConstantInitializer(value=1e-4), - act="relu") - conv3 = fluid.layers.conv2d_transpose( - input=conv_bn2, - num_filters=1, - filter_size=2, - stride=2, - param_attr=fluid.initializer.MSRAInitializer(uniform=False), - bias_attr=self._get_bias_attr(0.0004, conv_bn2.shape[1], "conv3"), - act=None) - out = fluid.layers.sigmoid(conv3) - return out + def forward(self, x): + x = self.conv1(x) + x = self.conv_bn1(x) + x = self.conv2(x) + x = self.conv_bn2(x) + x = self.conv3(x) + x = F.sigmoid(x) + return x - def _get_bias_attr(self, l2_decay, k, name, gradient_clip=None): - regularizer = fluid.regularizer.L2Decay(l2_decay) - stdv = 1.0 / math.sqrt(k * 1.0) - initializer = fluid.initializer.Uniform(-stdv, stdv) - bias_attr = fluid.ParamAttr( - regularizer=regularizer, - initializer=initializer, - name=name + "_b_attr") - return bias_attr - def step_function(self, x, y): - return fluid.layers.reciprocal(1 + fluid.layers.exp(-self.k * (x - y))) +class DBHead(nn.Layer): + """ + Differentiable Binarization (DB) for text detection: + see https://arxiv.org/abs/1911.08947 + args: + params(dict): super parameters for build DB network + """ - def __call__(self, conv_features, mode="train"): - c2, c3, c4, c5 = conv_features - param_attr = fluid.initializer.MSRAInitializer(uniform=False) - in5 = fluid.layers.conv2d( - input=c5, - num_filters=self.inner_channels, - filter_size=1, - param_attr=param_attr, - bias_attr=False) - in4 = fluid.layers.conv2d( - input=c4, - num_filters=self.inner_channels, - filter_size=1, - param_attr=param_attr, - bias_attr=False) - in3 = fluid.layers.conv2d( - input=c3, - num_filters=self.inner_channels, - filter_size=1, - param_attr=param_attr, - bias_attr=False) - in2 = fluid.layers.conv2d( - input=c2, - num_filters=self.inner_channels, - filter_size=1, - param_attr=param_attr, - bias_attr=False) + def __init__(self, in_channels, k=50, **kwargs): + super(DBHead, self).__init__() + self.k = k + binarize_name_list = [ + 'conv2d_56', 'batch_norm_47', 'conv2d_transpose_0', 'batch_norm_48', + 'conv2d_transpose_1', 'binarize' + ] + thresh_name_list = [ + 'conv2d_57', 'batch_norm_49', 'conv2d_transpose_2', 'batch_norm_50', + 'conv2d_transpose_3', 'thresh' + ] + self.binarize = Head(in_channels, binarize_name_list) + self.thresh = Head(in_channels, thresh_name_list) - out4 = fluid.layers.elementwise_add( - x=fluid.layers.resize_nearest( - input=in5, scale=2), y=in4) # 1/16 - out3 = fluid.layers.elementwise_add( - x=fluid.layers.resize_nearest( - input=out4, scale=2), y=in3) # 1/8 - out2 = fluid.layers.elementwise_add( - x=fluid.layers.resize_nearest( - input=out3, scale=2), y=in2) # 1/4 + def step_function(self, x, y): + return paddle.reciprocal(1 + paddle.exp(-self.k * (x - y))) - p5 = fluid.layers.conv2d( - input=in5, - num_filters=self.inner_channels // 4, - filter_size=3, - padding=1, - param_attr=param_attr, - bias_attr=False) - p5 = fluid.layers.resize_nearest(input=p5, scale=8) - p4 = fluid.layers.conv2d( - input=out4, - num_filters=self.inner_channels // 4, - filter_size=3, - padding=1, - param_attr=param_attr, - bias_attr=False) - p4 = fluid.layers.resize_nearest(input=p4, scale=4) - p3 = fluid.layers.conv2d( - input=out3, - num_filters=self.inner_channels // 4, - filter_size=3, - padding=1, - param_attr=param_attr, - bias_attr=False) - p3 = fluid.layers.resize_nearest(input=p3, scale=2) - p2 = fluid.layers.conv2d( - input=out2, - num_filters=self.inner_channels // 4, - filter_size=3, - padding=1, - param_attr=param_attr, - bias_attr=False) + def forward(self, x): + shrink_maps = self.binarize(x) + if not self.training: + return shrink_maps - fuse = fluid.layers.concat(input=[p5, p4, p3, p2], axis=1) - shrink_maps = self.binarize(fuse) - if mode != "train": - return {"maps": shrink_maps} - threshold_maps = self.thresh(fuse) + threshold_maps = self.thresh(x) binary_maps = self.step_function(shrink_maps, threshold_maps) - y = fluid.layers.concat( - input=[shrink_maps, threshold_maps, binary_maps], axis=1) - predicts = {} - predicts['maps'] = y - return predicts + y = paddle.concat([shrink_maps, threshold_maps, binary_maps], axis=1) + return y diff --git a/ppocr/modeling/heads/det_east_head.py b/ppocr/modeling/heads/det_east_head.py deleted file mode 100755 index de6ed51d..00000000 --- a/ppocr/modeling/heads/det_east_head.py +++ /dev/null @@ -1,117 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle.fluid as fluid -from ..common_functions import conv_bn_layer, deconv_bn_layer -from collections import OrderedDict - - -class EASTHead(object): - """ - EAST: An Efficient and Accurate Scene Text Detector - see arxiv: https://arxiv.org/abs/1704.03155 - args: - params(dict): the super parameters for network build - """ - - def __init__(self, params): - - self.model_name = params['model_name'] - - def unet_fusion(self, inputs): - f = inputs[::-1] - if self.model_name == "large": - num_outputs = [128, 128, 128, 128] - else: - num_outputs = [64, 64, 64, 64] - g = [None, None, None, None] - h = [None, None, None, None] - for i in range(4): - if i == 0: - h[i] = f[i] - else: - h[i] = fluid.layers.concat([g[i - 1], f[i]], axis=1) - h[i] = conv_bn_layer( - input=h[i], - num_filters=num_outputs[i], - filter_size=3, - stride=1, - act='relu', - name="unet_h_%d" % (i)) - if i <= 2: - #can be replaced with unpool - g[i] = deconv_bn_layer( - input=h[i], - num_filters=num_outputs[i], - name="unet_g_%d" % (i)) - else: - g[i] = conv_bn_layer( - input=h[i], - num_filters=num_outputs[i], - filter_size=3, - stride=1, - act='relu', - name="unet_g_%d" % (i)) - return g[3] - - def detector_header(self, f_common): - if self.model_name == "large": - num_outputs = [128, 64, 1, 8] - else: - num_outputs = [64, 32, 1, 8] - f_det = conv_bn_layer( - input=f_common, - num_filters=num_outputs[0], - filter_size=3, - stride=1, - act='relu', - name="det_head1") - f_det = conv_bn_layer( - input=f_det, - num_filters=num_outputs[1], - filter_size=3, - stride=1, - act='relu', - name="det_head2") - #f_score - f_score = conv_bn_layer( - input=f_det, - num_filters=num_outputs[2], - filter_size=1, - stride=1, - act=None, - name="f_score") - f_score = fluid.layers.sigmoid(f_score) - #f_geo - f_geo = conv_bn_layer( - input=f_det, - num_filters=num_outputs[3], - filter_size=1, - stride=1, - act=None, - name="f_geo") - f_geo = (fluid.layers.sigmoid(f_geo) - 0.5) * 2 * 800 - return f_score, f_geo - - def __call__(self, inputs): - f_common = self.unet_fusion(inputs) - f_score, f_geo = self.detector_header(f_common) - predicts = OrderedDict() - predicts['f_score'] = f_score - predicts['f_geo'] = f_geo - return predicts diff --git a/ppocr/modeling/heads/det_sast_head.py b/ppocr/modeling/heads/det_sast_head.py deleted file mode 100644 index 0097913d..00000000 --- a/ppocr/modeling/heads/det_sast_head.py +++ /dev/null @@ -1,228 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle.fluid as fluid -from ..common_functions import conv_bn_layer, deconv_bn_layer -from collections import OrderedDict - - -class SASTHead(object): - """ - SAST: - see arxiv: https://arxiv.org/abs/1908.05498 - args: - params(dict): the super parameters for network build - """ - - def __init__(self, params): - self.model_name = params['model_name'] - self.with_cab = params['with_cab'] - - def FPN_Up_Fusion(self, blocks): - """ - blocks{}: contain block_2, block_3, block_4, block_5, block_6, block_7 with - 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 resolution. - """ - f = [blocks['block_6'], blocks['block_5'], blocks['block_4'], blocks['block_3'], blocks['block_2']] - num_outputs = [256, 256, 192, 192, 128] - g = [None, None, None, None, None] - h = [None, None, None, None, None] - for i in range(5): - h[i] = conv_bn_layer(input=f[i], num_filters=num_outputs[i], - filter_size=1, stride=1, act=None, name='fpn_up_h'+str(i)) - - for i in range(4): - if i == 0: - g[i] = deconv_bn_layer(input=h[i], num_filters=num_outputs[i + 1], act=None, name='fpn_up_g0') - #print("g[{}] shape: {}".format(i, g[i].shape)) - else: - g[i] = fluid.layers.elementwise_add(x=g[i - 1], y=h[i]) - g[i] = fluid.layers.relu(g[i]) - #g[i] = conv_bn_layer(input=g[i], num_filters=num_outputs[i], - # filter_size=1, stride=1, act='relu') - g[i] = conv_bn_layer(input=g[i], num_filters=num_outputs[i], - filter_size=3, stride=1, act='relu', name='fpn_up_g%d_1'%i) - g[i] = deconv_bn_layer(input=g[i], num_filters=num_outputs[i + 1], act=None, name='fpn_up_g%d_2'%i) - #print("g[{}] shape: {}".format(i, g[i].shape)) - - g[4] = fluid.layers.elementwise_add(x=g[3], y=h[4]) - g[4] = fluid.layers.relu(g[4]) - g[4] = conv_bn_layer(input=g[4], num_filters=num_outputs[4], - filter_size=3, stride=1, act='relu', name='fpn_up_fusion_1') - g[4] = conv_bn_layer(input=g[4], num_filters=num_outputs[4], - filter_size=1, stride=1, act=None, name='fpn_up_fusion_2') - - return g[4] - - def FPN_Down_Fusion(self, blocks): - """ - blocks{}: contain block_2, block_3, block_4, block_5, block_6, block_7 with - 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 resolution. - """ - f = [blocks['block_0'], blocks['block_1'], blocks['block_2']] - num_outputs = [32, 64, 128] - g = [None, None, None] - h = [None, None, None] - for i in range(3): - h[i] = conv_bn_layer(input=f[i], num_filters=num_outputs[i], - filter_size=3, stride=1, act=None, name='fpn_down_h'+str(i)) - for i in range(2): - if i == 0: - g[i] = conv_bn_layer(input=h[i], num_filters=num_outputs[i+1], filter_size=3, stride=2, act=None, name='fpn_down_g0') - else: - g[i] = fluid.layers.elementwise_add(x=g[i - 1], y=h[i]) - g[i] = fluid.layers.relu(g[i]) - g[i] = conv_bn_layer(input=g[i], num_filters=num_outputs[i], filter_size=3, stride=1, act='relu', name='fpn_down_g%d_1'%i) - g[i] = conv_bn_layer(input=g[i], num_filters=num_outputs[i+1], filter_size=3, stride=2, act=None, name='fpn_down_g%d_2'%i) - # print("g[{}] shape: {}".format(i, g[i].shape)) - g[2] = fluid.layers.elementwise_add(x=g[1], y=h[2]) - g[2] = fluid.layers.relu(g[2]) - g[2] = conv_bn_layer(input=g[2], num_filters=num_outputs[2], - filter_size=3, stride=1, act='relu', name='fpn_down_fusion_1') - g[2] = conv_bn_layer(input=g[2], num_filters=num_outputs[2], - filter_size=1, stride=1, act=None, name='fpn_down_fusion_2') - return g[2] - - def SAST_Header1(self, f_common): - """Detector header.""" - #f_score - f_score = conv_bn_layer(input=f_common, num_filters=64, filter_size=1, stride=1, act='relu', name='f_score1') - f_score = conv_bn_layer(input=f_score, num_filters=64, filter_size=3, stride=1, act='relu', name='f_score2') - f_score = conv_bn_layer(input=f_score, num_filters=128, filter_size=1, stride=1, act='relu', name='f_score3') - f_score = conv_bn_layer(input=f_score, num_filters=1, filter_size=3, stride=1, name='f_score4') - f_score = fluid.layers.sigmoid(f_score) - # print("f_score shape: {}".format(f_score.shape)) - - #f_boder - f_border = conv_bn_layer(input=f_common, num_filters=64, filter_size=1, stride=1, act='relu', name='f_border1') - f_border = conv_bn_layer(input=f_border, num_filters=64, filter_size=3, stride=1, act='relu', name='f_border2') - f_border = conv_bn_layer(input=f_border, num_filters=128, filter_size=1, stride=1, act='relu', name='f_border3') - f_border = conv_bn_layer(input=f_border, num_filters=4, filter_size=3, stride=1, name='f_border4') - # print("f_border shape: {}".format(f_border.shape)) - - return f_score, f_border - - def SAST_Header2(self, f_common): - """Detector header.""" - #f_tvo - f_tvo = conv_bn_layer(input=f_common, num_filters=64, filter_size=1, stride=1, act='relu', name='f_tvo1') - f_tvo = conv_bn_layer(input=f_tvo, num_filters=64, filter_size=3, stride=1, act='relu', name='f_tvo2') - f_tvo = conv_bn_layer(input=f_tvo, num_filters=128, filter_size=1, stride=1, act='relu', name='f_tvo3') - f_tvo = conv_bn_layer(input=f_tvo, num_filters=8, filter_size=3, stride=1, name='f_tvo4') - # print("f_tvo shape: {}".format(f_tvo.shape)) - - #f_tco - f_tco = conv_bn_layer(input=f_common, num_filters=64, filter_size=1, stride=1, act='relu', name='f_tco1') - f_tco = conv_bn_layer(input=f_tco, num_filters=64, filter_size=3, stride=1, act='relu', name='f_tco2') - f_tco = conv_bn_layer(input=f_tco, num_filters=128, filter_size=1, stride=1, act='relu', name='f_tco3') - f_tco = conv_bn_layer(input=f_tco, num_filters=2, filter_size=3, stride=1, name='f_tco4') - # print("f_tco shape: {}".format(f_tco.shape)) - - return f_tvo, f_tco - - def cross_attention(self, f_common): - """ - """ - f_shape = fluid.layers.shape(f_common) - f_theta = conv_bn_layer(input=f_common, num_filters=128, filter_size=1, stride=1, act='relu', name='f_theta') - f_phi = conv_bn_layer(input=f_common, num_filters=128, filter_size=1, stride=1, act='relu', name='f_phi') - f_g = conv_bn_layer(input=f_common, num_filters=128, filter_size=1, stride=1, act='relu', name='f_g') - ### horizon - fh_theta = f_theta - fh_phi = f_phi - fh_g = f_g - #flatten - fh_theta = fluid.layers.transpose(fh_theta, [0, 2, 3, 1]) - fh_theta = fluid.layers.reshape(fh_theta, [f_shape[0] * f_shape[2], f_shape[3], 128]) - fh_phi = fluid.layers.transpose(fh_phi, [0, 2, 3, 1]) - fh_phi = fluid.layers.reshape(fh_phi, [f_shape[0] * f_shape[2], f_shape[3], 128]) - fh_g = fluid.layers.transpose(fh_g, [0, 2, 3, 1]) - fh_g = fluid.layers.reshape(fh_g, [f_shape[0] * f_shape[2], f_shape[3], 128]) - #correlation - fh_attn = fluid.layers.matmul(fh_theta, fluid.layers.transpose(fh_phi, [0, 2, 1])) - #scale - fh_attn = fh_attn / (128 ** 0.5) - fh_attn = fluid.layers.softmax(fh_attn) - #weighted sum - fh_weight = fluid.layers.matmul(fh_attn, fh_g) - fh_weight = fluid.layers.reshape(fh_weight, [f_shape[0], f_shape[2], f_shape[3], 128]) - # print("fh_weight: {}".format(fh_weight.shape)) - fh_weight = fluid.layers.transpose(fh_weight, [0, 3, 1, 2]) - fh_weight = conv_bn_layer(input=fh_weight, num_filters=128, filter_size=1, stride=1, name='fh_weight') - #short cut - fh_sc = conv_bn_layer(input=f_common, num_filters=128, filter_size=1, stride=1, name='fh_sc') - f_h = fluid.layers.relu(fh_weight + fh_sc) - ###### - #vertical - fv_theta = fluid.layers.transpose(f_theta, [0, 1, 3, 2]) - fv_phi = fluid.layers.transpose(f_phi, [0, 1, 3, 2]) - fv_g = fluid.layers.transpose(f_g, [0, 1, 3, 2]) - #flatten - fv_theta = fluid.layers.transpose(fv_theta, [0, 2, 3, 1]) - fv_theta = fluid.layers.reshape(fv_theta, [f_shape[0] * f_shape[3], f_shape[2], 128]) - fv_phi = fluid.layers.transpose(fv_phi, [0, 2, 3, 1]) - fv_phi = fluid.layers.reshape(fv_phi, [f_shape[0] * f_shape[3], f_shape[2], 128]) - fv_g = fluid.layers.transpose(fv_g, [0, 2, 3, 1]) - fv_g = fluid.layers.reshape(fv_g, [f_shape[0] * f_shape[3], f_shape[2], 128]) - #correlation - fv_attn = fluid.layers.matmul(fv_theta, fluid.layers.transpose(fv_phi, [0, 2, 1])) - #scale - fv_attn = fv_attn / (128 ** 0.5) - fv_attn = fluid.layers.softmax(fv_attn) - #weighted sum - fv_weight = fluid.layers.matmul(fv_attn, fv_g) - fv_weight = fluid.layers.reshape(fv_weight, [f_shape[0], f_shape[3], f_shape[2], 128]) - # print("fv_weight: {}".format(fv_weight.shape)) - fv_weight = fluid.layers.transpose(fv_weight, [0, 3, 2, 1]) - fv_weight = conv_bn_layer(input=fv_weight, num_filters=128, filter_size=1, stride=1, name='fv_weight') - #short cut - fv_sc = conv_bn_layer(input=f_common, num_filters=128, filter_size=1, stride=1, name='fv_sc') - f_v = fluid.layers.relu(fv_weight + fv_sc) - ###### - f_attn = fluid.layers.concat([f_h, f_v], axis=1) - f_attn = conv_bn_layer(input=f_attn, num_filters=128, filter_size=1, stride=1, act='relu', name='f_attn') - return f_attn - - def __call__(self, blocks, with_cab=False): - # for k, v in blocks.items(): - # print(k, v.shape) - - #down fpn - f_down = self.FPN_Down_Fusion(blocks) - # print("f_down shape: {}".format(f_down.shape)) - #up fpn - f_up = self.FPN_Up_Fusion(blocks) - # print("f_up shape: {}".format(f_up.shape)) - #fusion - f_common = fluid.layers.elementwise_add(x=f_down, y=f_up) - f_common = fluid.layers.relu(f_common) - # print("f_common: {}".format(f_common.shape)) - - if self.with_cab: - # print('enhence f_common with CAB.') - f_common = self.cross_attention(f_common) - - f_score, f_border= self.SAST_Header1(f_common) - f_tvo, f_tco = self.SAST_Header2(f_common) - - predicts = OrderedDict() - predicts['f_score'] = f_score - predicts['f_border'] = f_border - predicts['f_tvo'] = f_tvo - predicts['f_tco'] = f_tco - return predicts \ No newline at end of file diff --git a/ppocr/modeling/heads/rec_attention_head.py b/ppocr/modeling/heads/rec_attention_head.py deleted file mode 100755 index 66c8f300..00000000 --- a/ppocr/modeling/heads/rec_attention_head.py +++ /dev/null @@ -1,237 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle -import paddle.fluid as fluid -import paddle.fluid.layers as layers -from .rec_seq_encoder import SequenceEncoder -import numpy as np - - -class AttentionPredict(object): - def __init__(self, params): - super(AttentionPredict, self).__init__() - self.char_num = params['char_num'] - self.encoder = SequenceEncoder(params) - self.decoder_size = params['Attention']['decoder_size'] - self.word_vector_dim = params['Attention']['word_vector_dim'] - self.encoder_type = params['encoder_type'] - self.max_length = params['max_text_length'] - - def simple_attention(self, encoder_vec, encoder_proj, decoder_state, - decoder_size): - decoder_state_proj = layers.fc(input=decoder_state, - size=decoder_size, - bias_attr=False, - name="decoder_state_proj_fc") - decoder_state_expand = layers.sequence_expand( - x=decoder_state_proj, y=encoder_proj) - concated = layers.elementwise_add(encoder_proj, decoder_state_expand) - concated = layers.tanh(x=concated) - attention_weights = layers.fc(input=concated, - size=1, - act=None, - bias_attr=False, - name="attention_weights_fc") - attention_weights = layers.sequence_softmax(input=attention_weights) - weigths_reshape = layers.reshape(x=attention_weights, shape=[-1]) - scaled = layers.elementwise_mul( - x=encoder_vec, y=weigths_reshape, axis=0) - context = layers.sequence_pool(input=scaled, pool_type='sum') - return context - - def gru_decoder_with_attention(self, target_embedding, encoder_vec, - encoder_proj, decoder_boot, decoder_size, - char_num): - rnn = layers.DynamicRNN() - with rnn.block(): - current_word = rnn.step_input(target_embedding) - encoder_vec = rnn.static_input(encoder_vec) - encoder_proj = rnn.static_input(encoder_proj) - hidden_mem = rnn.memory(init=decoder_boot, need_reorder=True) - context = self.simple_attention(encoder_vec, encoder_proj, - hidden_mem, decoder_size) - fc_1 = layers.fc(input=context, - size=decoder_size * 3, - bias_attr=False, - name="rnn_fc1") - fc_2 = layers.fc(input=current_word, - size=decoder_size * 3, - bias_attr=False, - name="rnn_fc2") - decoder_inputs = fc_1 + fc_2 - h, _, _ = layers.gru_unit( - input=decoder_inputs, hidden=hidden_mem, size=decoder_size * 3) - rnn.update_memory(hidden_mem, h) - out = layers.fc(input=h, - size=char_num, - bias_attr=True, - act='softmax', - name="rnn_out_fc") - rnn.output(out) - return rnn() - - def gru_attention_infer(self, decoder_boot, max_length, char_num, - word_vector_dim, encoded_vector, encoded_proj, - decoder_size): - init_state = decoder_boot - beam_size = 1 - array_len = layers.fill_constant( - shape=[1], dtype='int64', value=max_length) - counter = layers.zeros(shape=[1], dtype='int64', force_cpu=True) - - # fill the first element with init_state - state_array = layers.create_array('float32') - layers.array_write(init_state, array=state_array, i=counter) - - # ids, scores as memory - ids_array = layers.create_array('int64') - scores_array = layers.create_array('float32') - rois_shape = layers.shape(init_state) - batch_size = layers.slice( - rois_shape, axes=[0], starts=[0], ends=[1]) + 1 - lod_level = layers.range( - start=0, end=batch_size, step=1, dtype=batch_size.dtype) - - init_ids = layers.fill_constant_batch_size_like( - input=init_state, shape=[-1, 1], value=0, dtype='int64') - init_ids = layers.lod_reset(init_ids, lod_level) - init_ids = layers.lod_append(init_ids, lod_level) - - init_scores = layers.fill_constant_batch_size_like( - input=init_state, shape=[-1, 1], value=1, dtype='float32') - init_scores = layers.lod_reset(init_scores, init_ids) - layers.array_write(init_ids, array=ids_array, i=counter) - layers.array_write(init_scores, array=scores_array, i=counter) - - full_ids = fluid.layers.fill_constant_batch_size_like( - input=init_state, shape=[-1, 1], dtype='int64', value=1) - full_scores = fluid.layers.fill_constant_batch_size_like( - input=init_state, shape=[-1, 1], dtype='float32', value=1) - - cond = layers.less_than(x=counter, y=array_len) - while_op = layers.While(cond=cond) - with while_op.block(): - pre_ids = layers.array_read(array=ids_array, i=counter) - pre_state = layers.array_read(array=state_array, i=counter) - pre_score = layers.array_read(array=scores_array, i=counter) - pre_ids_emb = layers.embedding( - input=pre_ids, - size=[char_num, word_vector_dim], - dtype='float32') - - context = self.simple_attention(encoded_vector, encoded_proj, - pre_state, decoder_size) - - # expand the recursive_sequence_lengths of pre_state - # to be the same with pre_score - pre_state_expanded = layers.sequence_expand(pre_state, pre_score) - context_expanded = layers.sequence_expand(context, pre_score) - - fc_1 = layers.fc(input=context_expanded, - size=decoder_size * 3, - bias_attr=False, - name="rnn_fc1") - - fc_2 = layers.fc(input=pre_ids_emb, - size=decoder_size * 3, - bias_attr=False, - name="rnn_fc2") - - decoder_inputs = fc_1 + fc_2 - current_state, _, _ = layers.gru_unit( - input=decoder_inputs, - hidden=pre_state_expanded, - size=decoder_size * 3) - current_state_with_lod = layers.lod_reset( - x=current_state, y=pre_score) - # use score to do beam search - current_score = layers.fc(input=current_state_with_lod, - size=char_num, - bias_attr=True, - act='softmax', - name="rnn_out_fc") - topk_scores, topk_indices = layers.topk(current_score, k=beam_size) - - new_ids = fluid.layers.concat([full_ids, topk_indices], axis=1) - fluid.layers.assign(new_ids, full_ids) - - new_scores = fluid.layers.concat([full_scores, topk_scores], axis=1) - fluid.layers.assign(new_scores, full_scores) - - layers.increment(x=counter, value=1, in_place=True) - - # update the memories - layers.array_write(current_state, array=state_array, i=counter) - layers.array_write(topk_indices, array=ids_array, i=counter) - layers.array_write(topk_scores, array=scores_array, i=counter) - - # update the break condition: - # up to the max length or all candidates of - # source sentences have ended. - length_cond = layers.less_than(x=counter, y=array_len) - finish_cond = layers.logical_not(layers.is_empty(x=topk_indices)) - layers.logical_and(x=length_cond, y=finish_cond, out=cond) - return full_ids, full_scores - - def __call__(self, inputs, labels=None, mode=None): - encoder_features = self.encoder(inputs) - char_num = self.char_num - word_vector_dim = self.word_vector_dim - decoder_size = self.decoder_size - - if self.encoder_type == "reshape": - encoder_input = encoder_features - encoded_vector = encoder_features - else: - encoder_input = encoder_features[1] - encoded_vector = layers.concat(encoder_features, axis=1) - encoded_proj = layers.fc(input=encoded_vector, - size=decoder_size, - bias_attr=False, - name="encoded_proj_fc") - backward_first = layers.sequence_pool( - input=encoder_input, pool_type='first') - decoder_boot = layers.fc(input=backward_first, - size=decoder_size, - bias_attr=False, - act="relu", - name='decoder_boot') - - if mode == "train": - label_in = labels['label_in'] - label_out = labels['label_out'] - label_in = layers.cast(x=label_in, dtype='int64') - trg_embedding = layers.embedding( - input=label_in, - size=[char_num, word_vector_dim], - dtype='float32') - predict = self.gru_decoder_with_attention( - trg_embedding, encoded_vector, encoded_proj, decoder_boot, - decoder_size, char_num) - _, decoded_out = layers.topk(input=predict, k=1) - decoded_out = layers.lod_reset(decoded_out, y=label_out) - predicts = {'predict':predict, 'decoded_out':decoded_out} - else: - ids, predict = self.gru_attention_infer( - decoder_boot, self.max_length, char_num, word_vector_dim, - encoded_vector, encoded_proj, decoder_size) - predicts = {'predict':predict, 'decoded_out':ids} - return predicts diff --git a/ppocr/modeling/heads/rec_ctc_head.py b/ppocr/modeling/heads/rec_ctc_head.py index 6b8635e4..8c7b904f 100755 --- a/ppocr/modeling/heads/rec_ctc_head.py +++ b/ppocr/modeling/heads/rec_ctc_head.py @@ -1,16 +1,16 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division @@ -19,34 +19,33 @@ from __future__ import print_function import math import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr -from .rec_seq_encoder import SequenceEncoder -from ..common_functions import get_para_bias_attr -import numpy as np - - -class CTCPredict(object): - def __init__(self, params): - super(CTCPredict, self).__init__() - self.char_num = params['char_num'] - self.encoder = SequenceEncoder(params) - self.encoder_type = params['encoder_type'] - self.fc_decay = params.get("fc_decay", 0.0004) - - def __call__(self, inputs, labels=None, mode=None): - encoder_features = self.encoder(inputs) - if self.encoder_type != "reshape": - encoder_features = fluid.layers.concat(encoder_features, axis=1) - name = "ctc_fc" - para_attr, bias_attr = get_para_bias_attr( - l2_decay=self.fc_decay, k=encoder_features.shape[1], name=name) - predict = fluid.layers.fc(input=encoder_features, - size=self.char_num + 1, - param_attr=para_attr, - bias_attr=bias_attr, - name=name) - decoded_out = fluid.layers.ctc_greedy_decoder( - input=predict, blank=self.char_num) - predicts = {'predict': predict, 'decoded_out': decoded_out} +from paddle import ParamAttr, nn + + +def get_para_bias_attr(l2_decay, k, name): + regularizer = paddle.fluid.regularizer.L2Decay(l2_decay) + stdv = 1.0 / math.sqrt(k * 1.0) + initializer = nn.initializer.Uniform(-stdv, stdv) + weight_attr = ParamAttr( + regularizer=regularizer, initializer=initializer, name=name + "_w_attr") + bias_attr = ParamAttr( + regularizer=regularizer, initializer=initializer, name=name + "_b_attr") + return [weight_attr, bias_attr] + + +class CTC(nn.Layer): + def __init__(self, in_channels, out_channels, fc_decay=1e-5, **kwargs): + super(CTC, self).__init__() + weight_attr, bias_attr = get_para_bias_attr( + l2_decay=fc_decay, k=in_channels, name='ctc_fc') + self.fc = nn.Linear( + in_channels, + out_channels, + weight_attr=weight_attr, + bias_attr=bias_attr, + name='ctc_fc') + self.out_channels = out_channels + + def forward(self, x, labels=None): + predicts = self.fc(x) return predicts diff --git a/ppocr/modeling/heads/rec_seq_encoder.py b/ppocr/modeling/heads/rec_seq_encoder.py deleted file mode 100755 index 0c49667a..00000000 --- a/ppocr/modeling/heads/rec_seq_encoder.py +++ /dev/null @@ -1,100 +0,0 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math -import paddle.fluid as fluid -import paddle.fluid.layers as layers - - -class EncoderWithReshape(object): - def __init__(self, params): - super(EncoderWithReshape, self).__init__() - - def __call__(self, inputs): - sliced_feature = layers.im2sequence( - input=inputs, - stride=[1, 1], - filter_size=[inputs.shape[2], 1], - name="sliced_feature") - return sliced_feature - - -class EncoderWithRNN(object): - def __init__(self, params): - super(EncoderWithRNN, self).__init__() - self.rnn_hidden_size = params['SeqRNN']['hidden_size'] - - def __call__(self, inputs): - lstm_list = [] - name_prefix = "lstm" - rnn_hidden_size = self.rnn_hidden_size - for no in range(1, 3): - if no == 1: - is_reverse = False - else: - is_reverse = True - name = "%s_st1_fc%d" % (name_prefix, no) - fc = layers.fc(input=inputs, - size=rnn_hidden_size * 4, - param_attr=fluid.ParamAttr(name=name + "_w"), - bias_attr=fluid.ParamAttr(name=name + "_b"), - name=name) - name = "%s_st1_out%d" % (name_prefix, no) - lstm, _ = layers.dynamic_lstm( - input=fc, - size=rnn_hidden_size * 4, - is_reverse=is_reverse, - param_attr=fluid.ParamAttr(name=name + "_w"), - bias_attr=fluid.ParamAttr(name=name + "_b"), - use_peepholes=False) - name = "%s_st2_fc%d" % (name_prefix, no) - fc = layers.fc(input=lstm, - size=rnn_hidden_size * 4, - param_attr=fluid.ParamAttr(name=name + "_w"), - bias_attr=fluid.ParamAttr(name=name + "_b"), - name=name) - name = "%s_st2_out%d" % (name_prefix, no) - lstm, _ = layers.dynamic_lstm( - input=fc, - size=rnn_hidden_size * 4, - is_reverse=is_reverse, - param_attr=fluid.ParamAttr(name=name + "_w"), - bias_attr=fluid.ParamAttr(name=name + "_b"), - use_peepholes=False) - lstm_list.append(lstm) - return lstm_list - - -class SequenceEncoder(object): - def __init__(self, params): - super(SequenceEncoder, self).__init__() - self.encoder_type = params['encoder_type'] - self.encoder_reshape = EncoderWithReshape(params) - if self.encoder_type == "rnn": - self.encoder_rnn = EncoderWithRNN(params) - - def __call__(self, inputs): - if self.encoder_type == "reshape": - encoder_features = self.encoder_reshape(inputs) - elif self.encoder_type == "rnn": - inputs = self.encoder_reshape(inputs) - encoder_features = self.encoder_rnn(inputs) - else: - assert False, "Unsupport encoder_type:%s"\ - % self.encoder_type - return encoder_features diff --git a/ppocr/modeling/heads/rec_srn_all_head.py b/ppocr/modeling/heads/rec_srn_all_head.py deleted file mode 100755 index e1bb955d..00000000 --- a/ppocr/modeling/heads/rec_srn_all_head.py +++ /dev/null @@ -1,230 +0,0 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr -import numpy as np -from .self_attention.model import wrap_encoder -from .self_attention.model import wrap_encoder_forFeature -gradient_clip = 10 - - -class SRNPredict(object): - def __init__(self, params): - super(SRNPredict, self).__init__() - self.char_num = params['char_num'] - self.max_length = params['max_text_length'] - - self.num_heads = params['num_heads'] - self.num_encoder_TUs = params['num_encoder_TUs'] - self.num_decoder_TUs = params['num_decoder_TUs'] - self.hidden_dims = params['hidden_dims'] - - def pvam(self, inputs, others): - - b, c, h, w = inputs.shape - conv_features = fluid.layers.reshape(x=inputs, shape=[-1, c, h * w]) - conv_features = fluid.layers.transpose(x=conv_features, perm=[0, 2, 1]) - - #===== Transformer encoder ===== - b, t, c = conv_features.shape - encoder_word_pos = others["encoder_word_pos"] - gsrm_word_pos = others["gsrm_word_pos"] - - enc_inputs = [conv_features, encoder_word_pos, None] - word_features = wrap_encoder_forFeature( - src_vocab_size=-1, - max_length=t, - n_layer=self.num_encoder_TUs, - n_head=self.num_heads, - d_key=int(self.hidden_dims / self.num_heads), - d_value=int(self.hidden_dims / self.num_heads), - d_model=self.hidden_dims, - d_inner_hid=self.hidden_dims, - prepostprocess_dropout=0.1, - attention_dropout=0.1, - relu_dropout=0.1, - preprocess_cmd="n", - postprocess_cmd="da", - weight_sharing=True, - enc_inputs=enc_inputs, ) - fluid.clip.set_gradient_clip( - fluid.clip.GradientClipByValue(gradient_clip)) - - #===== Parallel Visual Attention Module ===== - b, t, c = word_features.shape - - word_features = fluid.layers.fc(word_features, c, num_flatten_dims=2) - word_features_ = fluid.layers.reshape(word_features, [-1, 1, t, c]) - word_features_ = fluid.layers.expand(word_features_, - [1, self.max_length, 1, 1]) - word_pos_feature = fluid.layers.embedding(gsrm_word_pos, - [self.max_length, c]) - word_pos_ = fluid.layers.reshape(word_pos_feature, - [-1, self.max_length, 1, c]) - word_pos_ = fluid.layers.expand(word_pos_, [1, 1, t, 1]) - temp = fluid.layers.elementwise_add( - word_features_, word_pos_, act='tanh') - - attention_weight = fluid.layers.fc(input=temp, - size=1, - num_flatten_dims=3, - bias_attr=False) - attention_weight = fluid.layers.reshape( - x=attention_weight, shape=[-1, self.max_length, t]) - attention_weight = fluid.layers.softmax(input=attention_weight, axis=-1) - - pvam_features = fluid.layers.matmul(attention_weight, - word_features) #[b, max_length, c] - - return pvam_features - - def gsrm(self, pvam_features, others): - - #===== GSRM Visual-to-semantic embedding block ===== - b, t, c = pvam_features.shape - word_out = fluid.layers.fc( - input=fluid.layers.reshape(pvam_features, [-1, c]), - size=self.char_num, - act="softmax") - #word_out.stop_gradient = True - word_ids = fluid.layers.argmax(word_out, axis=1) - word_ids.stop_gradient = True - word_ids = fluid.layers.reshape(x=word_ids, shape=[-1, t, 1]) - - #===== GSRM Semantic reasoning block ===== - """ - This module is achieved through bi-transformers, - ngram_feature1 is the froward one, ngram_fetaure2 is the backward one - """ - pad_idx = self.char_num - gsrm_word_pos = others["gsrm_word_pos"] - gsrm_slf_attn_bias1 = others["gsrm_slf_attn_bias1"] - gsrm_slf_attn_bias2 = others["gsrm_slf_attn_bias2"] - - def prepare_bi(word_ids): - """ - prepare bi for gsrm - word1 for forward; word2 for backward - """ - word1 = fluid.layers.cast(word_ids, "float32") - word1 = fluid.layers.pad(word1, [0, 0, 1, 0, 0, 0], - pad_value=1.0 * pad_idx) - word1 = fluid.layers.cast(word1, "int64") - word1 = word1[:, :-1, :] - word2 = word_ids - return word1, word2 - - word1, word2 = prepare_bi(word_ids) - word1.stop_gradient = True - word2.stop_gradient = True - enc_inputs_1 = [word1, gsrm_word_pos, gsrm_slf_attn_bias1] - enc_inputs_2 = [word2, gsrm_word_pos, gsrm_slf_attn_bias2] - - gsrm_feature1 = wrap_encoder( - src_vocab_size=self.char_num + 1, - max_length=self.max_length, - n_layer=self.num_decoder_TUs, - n_head=self.num_heads, - d_key=int(self.hidden_dims / self.num_heads), - d_value=int(self.hidden_dims / self.num_heads), - d_model=self.hidden_dims, - d_inner_hid=self.hidden_dims, - prepostprocess_dropout=0.1, - attention_dropout=0.1, - relu_dropout=0.1, - preprocess_cmd="n", - postprocess_cmd="da", - weight_sharing=True, - enc_inputs=enc_inputs_1, ) - gsrm_feature2 = wrap_encoder( - src_vocab_size=self.char_num + 1, - max_length=self.max_length, - n_layer=self.num_decoder_TUs, - n_head=self.num_heads, - d_key=int(self.hidden_dims / self.num_heads), - d_value=int(self.hidden_dims / self.num_heads), - d_model=self.hidden_dims, - d_inner_hid=self.hidden_dims, - prepostprocess_dropout=0.1, - attention_dropout=0.1, - relu_dropout=0.1, - preprocess_cmd="n", - postprocess_cmd="da", - weight_sharing=True, - enc_inputs=enc_inputs_2, ) - gsrm_feature2 = fluid.layers.pad(gsrm_feature2, [0, 0, 0, 1, 0, 0], - pad_value=0.) - gsrm_feature2 = gsrm_feature2[:, 1:, ] - gsrm_features = gsrm_feature1 + gsrm_feature2 - - b, t, c = gsrm_features.shape - - gsrm_out = fluid.layers.matmul( - x=gsrm_features, - y=fluid.default_main_program().global_block().var( - "src_word_emb_table"), - transpose_y=True) - b, t, c = gsrm_out.shape - gsrm_out = fluid.layers.softmax(input=fluid.layers.reshape(gsrm_out, - [-1, c])) - - return gsrm_features, word_out, gsrm_out - - def vsfd(self, pvam_features, gsrm_features): - - #===== Visual-Semantic Fusion Decoder Module ===== - b, t, c1 = pvam_features.shape - b, t, c2 = gsrm_features.shape - combine_features_ = fluid.layers.concat( - [pvam_features, gsrm_features], axis=2) - img_comb_features_ = fluid.layers.reshape( - x=combine_features_, shape=[-1, c1 + c2]) - img_comb_features_map = fluid.layers.fc(input=img_comb_features_, - size=c1, - act="sigmoid") - img_comb_features_map = fluid.layers.reshape( - x=img_comb_features_map, shape=[-1, t, c1]) - combine_features = img_comb_features_map * pvam_features + ( - 1.0 - img_comb_features_map) * gsrm_features - img_comb_features = fluid.layers.reshape( - x=combine_features, shape=[-1, c1]) - - fc_out = fluid.layers.fc(input=img_comb_features, - size=self.char_num, - act="softmax") - return fc_out - - def __call__(self, inputs, others, mode=None): - - pvam_features = self.pvam(inputs, others) - gsrm_features, word_out, gsrm_out = self.gsrm(pvam_features, others) - final_out = self.vsfd(pvam_features, gsrm_features) - - _, decoded_out = fluid.layers.topk(input=final_out, k=1) - predicts = { - 'predict': final_out, - 'decoded_out': decoded_out, - 'word_out': word_out, - 'gsrm_out': gsrm_out - } - - return predicts diff --git a/ppocr/modeling/heads/self_attention/__init__.py b/ppocr/modeling/heads/self_attention/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ppocr/modeling/heads/self_attention/model.py b/ppocr/modeling/heads/self_attention/model.py deleted file mode 100644 index 8bf34e4a..00000000 --- a/ppocr/modeling/heads/self_attention/model.py +++ /dev/null @@ -1,485 +0,0 @@ -from functools import partial -import numpy as np - -import paddle.fluid as fluid -import paddle.fluid.layers as layers - -encoder_data_input_fields = ( - "src_word", - "src_pos", - "src_slf_attn_bias", ) - - -def wrap_layer_with_block(layer, block_idx): - """ - Make layer define support indicating block, by which we can add layers - to other blocks within current block. This will make it easy to define - cache among while loop. - """ - - class BlockGuard(object): - """ - BlockGuard class. - - BlockGuard class is used to switch to the given block in a program by - using the Python `with` keyword. - """ - - def __init__(self, block_idx=None, main_program=None): - self.main_program = fluid.default_main_program( - ) if main_program is None else main_program - self.old_block_idx = self.main_program.current_block().idx - self.new_block_idx = block_idx - - def __enter__(self): - self.main_program.current_block_idx = self.new_block_idx - - def __exit__(self, exc_type, exc_val, exc_tb): - self.main_program.current_block_idx = self.old_block_idx - if exc_type is not None: - return False # re-raise exception - return True - - def layer_wrapper(*args, **kwargs): - with BlockGuard(block_idx): - return layer(*args, **kwargs) - - return layer_wrapper - - -def multi_head_attention(queries, - keys, - values, - attn_bias, - d_key, - d_value, - d_model, - n_head=1, - dropout_rate=0., - cache=None, - gather_idx=None, - static_kv=False): - """ - Multi-Head Attention. Note that attn_bias is added to the logit before - computing softmax activiation to mask certain selected positions so that - they will not considered in attention weights. - """ - keys = queries if keys is None else keys - values = keys if values is None else values - - if not (len(queries.shape) == len(keys.shape) == len(values.shape) == 3): - raise ValueError( - "Inputs: quries, keys and values should all be 3-D tensors.") - - def __compute_qkv(queries, keys, values, n_head, d_key, d_value): - """ - Add linear projection to queries, keys, and values. - """ - q = layers.fc(input=queries, - size=d_key * n_head, - bias_attr=False, - num_flatten_dims=2) - # For encoder-decoder attention in inference, insert the ops and vars - # into global block to use as cache among beam search. - fc_layer = wrap_layer_with_block( - layers.fc, fluid.default_main_program().current_block() - .parent_idx) if cache is not None and static_kv else layers.fc - k = fc_layer( - input=keys, - size=d_key * n_head, - bias_attr=False, - num_flatten_dims=2) - v = fc_layer( - input=values, - size=d_value * n_head, - bias_attr=False, - num_flatten_dims=2) - return q, k, v - - def __split_heads_qkv(queries, keys, values, n_head, d_key, d_value): - """ - Reshape input tensors at the last dimension to split multi-heads - and then transpose. Specifically, transform the input tensor with shape - [bs, max_sequence_length, n_head * hidden_dim] to the output tensor - with shape [bs, n_head, max_sequence_length, hidden_dim]. - """ - # The value 0 in shape attr means copying the corresponding dimension - # size of the input as the output dimension size. - reshaped_q = layers.reshape( - x=queries, shape=[0, 0, n_head, d_key], inplace=True) - # permuate the dimensions into: - # [batch_size, n_head, max_sequence_len, hidden_size_per_head] - q = layers.transpose(x=reshaped_q, perm=[0, 2, 1, 3]) - # For encoder-decoder attention in inference, insert the ops and vars - # into global block to use as cache among beam search. - reshape_layer = wrap_layer_with_block( - layers.reshape, - fluid.default_main_program().current_block() - .parent_idx) if cache is not None and static_kv else layers.reshape - transpose_layer = wrap_layer_with_block( - layers.transpose, - fluid.default_main_program().current_block(). - parent_idx) if cache is not None and static_kv else layers.transpose - reshaped_k = reshape_layer( - x=keys, shape=[0, 0, n_head, d_key], inplace=True) - k = transpose_layer(x=reshaped_k, perm=[0, 2, 1, 3]) - reshaped_v = reshape_layer( - x=values, shape=[0, 0, n_head, d_value], inplace=True) - v = transpose_layer(x=reshaped_v, perm=[0, 2, 1, 3]) - - if cache is not None: # only for faster inference - if static_kv: # For encoder-decoder attention in inference - cache_k, cache_v = cache["static_k"], cache["static_v"] - # To init the static_k and static_v in cache. - # Maybe we can use condition_op(if_else) to do these at the first - # step in while loop to replace these, however it might be less - # efficient. - static_cache_init = wrap_layer_with_block( - layers.assign, - fluid.default_main_program().current_block().parent_idx) - static_cache_init(k, cache_k) - static_cache_init(v, cache_v) - else: # For decoder self-attention in inference - cache_k, cache_v = cache["k"], cache["v"] - # gather cell states corresponding to selected parent - select_k = layers.gather(cache_k, index=gather_idx) - select_v = layers.gather(cache_v, index=gather_idx) - if not static_kv: - # For self attention in inference, use cache and concat time steps. - select_k = layers.concat([select_k, k], axis=2) - select_v = layers.concat([select_v, v], axis=2) - # update cell states(caches) cached in global block - layers.assign(select_k, cache_k) - layers.assign(select_v, cache_v) - return q, select_k, select_v - return q, k, v - - def __combine_heads(x): - """ - Transpose and then reshape the last two dimensions of inpunt tensor x - so that it becomes one dimension, which is reverse to __split_heads. - """ - if len(x.shape) != 4: - raise ValueError("Input(x) should be a 4-D Tensor.") - - trans_x = layers.transpose(x, perm=[0, 2, 1, 3]) - # The value 0 in shape attr means copying the corresponding dimension - # size of the input as the output dimension size. - return layers.reshape( - x=trans_x, - shape=[0, 0, trans_x.shape[2] * trans_x.shape[3]], - inplace=True) - - def scaled_dot_product_attention(q, k, v, attn_bias, d_key, dropout_rate): - """ - Scaled Dot-Product Attention - """ - # print(q) - # print(k) - - product = layers.matmul(x=q, y=k, transpose_y=True, alpha=d_key**-0.5) - if attn_bias: - product += attn_bias - weights = layers.softmax(product) - if dropout_rate: - weights = layers.dropout( - weights, dropout_prob=dropout_rate, seed=None, is_test=False) - out = layers.matmul(weights, v) - return out - - q, k, v = __compute_qkv(queries, keys, values, n_head, d_key, d_value) - q, k, v = __split_heads_qkv(q, k, v, n_head, d_key, d_value) - - ctx_multiheads = scaled_dot_product_attention(q, k, v, attn_bias, d_model, - dropout_rate) - - out = __combine_heads(ctx_multiheads) - - # Project back to the model size. - proj_out = layers.fc(input=out, - size=d_model, - bias_attr=False, - num_flatten_dims=2) - return proj_out - - -def positionwise_feed_forward(x, d_inner_hid, d_hid, dropout_rate): - """ - Position-wise Feed-Forward Networks. - This module consists of two linear transformations with a ReLU activation - in between, which is applied to each position separately and identically. - """ - hidden = layers.fc(input=x, - size=d_inner_hid, - num_flatten_dims=2, - act="relu") - if dropout_rate: - hidden = layers.dropout( - hidden, dropout_prob=dropout_rate, seed=None, is_test=False) - out = layers.fc(input=hidden, size=d_hid, num_flatten_dims=2) - return out - - -def pre_post_process_layer(prev_out, out, process_cmd, dropout_rate=0.): - """ - Add residual connection, layer normalization and droput to the out tensor - optionally according to the value of process_cmd. - This will be used before or after multi-head attention and position-wise - feed-forward networks. - """ - for cmd in process_cmd: - if cmd == "a": # add residual connection - out = out + prev_out if prev_out else out - elif cmd == "n": # add layer normalization - out = layers.layer_norm( - out, - begin_norm_axis=len(out.shape) - 1, - param_attr=fluid.initializer.Constant(1.), - bias_attr=fluid.initializer.Constant(0.)) - elif cmd == "d": # add dropout - if dropout_rate: - out = layers.dropout( - out, dropout_prob=dropout_rate, seed=None, is_test=False) - return out - - -pre_process_layer = partial(pre_post_process_layer, None) -post_process_layer = pre_post_process_layer - - -def prepare_encoder( - src_word, # [b,t,c] - src_pos, - src_vocab_size, - src_emb_dim, - src_max_len, - dropout_rate=0., - bos_idx=0, - word_emb_param_name=None, - pos_enc_param_name=None): - """Add word embeddings and position encodings. - The output tensor has a shape of: - [batch_size, max_src_length_in_batch, d_model]. - This module is used at the bottom of the encoder stacks. - """ - - src_word_emb = src_word - src_word_emb = layers.cast(src_word_emb, 'float32') - - src_word_emb = layers.scale(x=src_word_emb, scale=src_emb_dim**0.5) - src_pos_enc = layers.embedding( - src_pos, - size=[src_max_len, src_emb_dim], - param_attr=fluid.ParamAttr( - name=pos_enc_param_name, trainable=False)) - src_pos_enc.stop_gradient = True - enc_input = src_word_emb + src_pos_enc - return layers.dropout( - enc_input, dropout_prob=dropout_rate, seed=None, - is_test=False) if dropout_rate else enc_input - - -def prepare_decoder(src_word, - src_pos, - src_vocab_size, - src_emb_dim, - src_max_len, - dropout_rate=0., - bos_idx=0, - word_emb_param_name=None, - pos_enc_param_name=None): - """Add word embeddings and position encodings. - The output tensor has a shape of: - [batch_size, max_src_length_in_batch, d_model]. - This module is used at the bottom of the encoder stacks. - """ - src_word_emb = layers.embedding( - src_word, - size=[src_vocab_size, src_emb_dim], - padding_idx=bos_idx, # set embedding of bos to 0 - param_attr=fluid.ParamAttr( - name=word_emb_param_name, - initializer=fluid.initializer.Normal(0., src_emb_dim**-0.5))) - - src_word_emb = layers.scale(x=src_word_emb, scale=src_emb_dim**0.5) - src_pos_enc = layers.embedding( - src_pos, - size=[src_max_len, src_emb_dim], - param_attr=fluid.ParamAttr( - name=pos_enc_param_name, trainable=False)) - src_pos_enc.stop_gradient = True - enc_input = src_word_emb + src_pos_enc - return layers.dropout( - enc_input, dropout_prob=dropout_rate, seed=None, - is_test=False) if dropout_rate else enc_input - - -def encoder_layer(enc_input, - attn_bias, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd="n", - postprocess_cmd="da"): - """The encoder layers that can be stacked to form a deep encoder. - This module consits of a multi-head (self) attention followed by - position-wise feed-forward networks and both the two components companied - with the post_process_layer to add residual connection, layer normalization - and droput. - """ - attn_output = multi_head_attention( - pre_process_layer(enc_input, preprocess_cmd, - prepostprocess_dropout), None, None, attn_bias, d_key, - d_value, d_model, n_head, attention_dropout) - attn_output = post_process_layer(enc_input, attn_output, postprocess_cmd, - prepostprocess_dropout) - ffd_output = positionwise_feed_forward( - pre_process_layer(attn_output, preprocess_cmd, prepostprocess_dropout), - d_inner_hid, d_model, relu_dropout) - return post_process_layer(attn_output, ffd_output, postprocess_cmd, - prepostprocess_dropout) - - -def encoder(enc_input, - attn_bias, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd="n", - postprocess_cmd="da"): - """ - The encoder is composed of a stack of identical layers returned by calling - encoder_layer. - """ - for i in range(n_layer): - enc_output = encoder_layer( - enc_input, - attn_bias, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd, - postprocess_cmd, ) - enc_input = enc_output - enc_output = pre_process_layer(enc_output, preprocess_cmd, - prepostprocess_dropout) - return enc_output - - -def wrap_encoder_forFeature(src_vocab_size, - max_length, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd, - postprocess_cmd, - weight_sharing, - enc_inputs=None, - bos_idx=0): - """ - The wrapper assembles together all needed layers for the encoder. - img, src_pos, src_slf_attn_bias = enc_inputs - img - """ - - conv_features, src_pos, src_slf_attn_bias = enc_inputs # - b, t, c = conv_features.shape - - enc_input = prepare_encoder( - conv_features, - src_pos, - src_vocab_size, - d_model, - max_length, - prepostprocess_dropout, - bos_idx=bos_idx, - word_emb_param_name="src_word_emb_table") - - enc_output = encoder( - enc_input, - src_slf_attn_bias, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd, - postprocess_cmd, ) - return enc_output - - -def wrap_encoder(src_vocab_size, - max_length, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd, - postprocess_cmd, - weight_sharing, - enc_inputs=None, - bos_idx=0): - """ - The wrapper assembles together all needed layers for the encoder. - img, src_pos, src_slf_attn_bias = enc_inputs - img - """ - - src_word, src_pos, src_slf_attn_bias = enc_inputs # - - enc_input = prepare_decoder( - src_word, - src_pos, - src_vocab_size, - d_model, - max_length, - prepostprocess_dropout, - bos_idx=bos_idx, - word_emb_param_name="src_word_emb_table") - - enc_output = encoder( - enc_input, - src_slf_attn_bias, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd, - postprocess_cmd, ) - return enc_output diff --git a/ppocr/modeling/losses/__init__.py b/ppocr/modeling/losses/__init__.py index abf198b9..1c258bc1 100755 --- a/ppocr/modeling/losses/__init__.py +++ b/ppocr/modeling/losses/__init__.py @@ -11,3 +11,22 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import copy + + +def build_loss(config): + # det loss + from .det_db_loss import DBLoss + + # rec loss + from .rec_ctc_loss import CTCLoss + + support_dict = ['DBLoss', 'CTCLoss'] + + config = copy.deepcopy(config) + module_name = config.pop('name') + assert module_name in support_dict, Exception('loss only support {}'.format( + support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/losses/det_basic_loss.py b/ppocr/modeling/losses/det_basic_loss.py index 8fb81070..ef656e8c 100644 --- a/ppocr/modeling/losses/det_basic_loss.py +++ b/ppocr/modeling/losses/det_basic_loss.py @@ -1,16 +1,16 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division @@ -18,99 +18,189 @@ from __future__ import print_function import numpy as np -import paddle.fluid as fluid - - -def BalanceLoss(pred, - gt, - mask, - balance_loss=True, - main_loss_type="DiceLoss", - negative_ratio=3, - return_origin=False, - eps=1e-6): - """ - The BalanceLoss for Differentiable Binarization text detection - args: - pred (variable): predicted feature maps. - gt (variable): ground truth feature maps. - mask (variable): masked maps. - balance_loss (bool): whether balance loss or not, default is True - main_loss_type (str): can only be one of ['CrossEntropy','DiceLoss', - 'Euclidean','BCELoss', 'MaskL1Loss'], default is 'DiceLoss'. - negative_ratio (int|float): float, default is 3. - return_origin (bool): whether return unbalanced loss or not, default is False. - eps (float): default is 1e-6. - return: (variable) balanced loss - """ - positive = gt * mask - negative = (1 - gt) * mask - - positive_count = fluid.layers.reduce_sum(positive) - positive_count_int = fluid.layers.cast(positive_count, dtype=np.int32) - negative_count = min( - fluid.layers.reduce_sum(negative), positive_count * negative_ratio) - negative_count_int = fluid.layers.cast(negative_count, dtype=np.int32) - - if main_loss_type == "CrossEntropy": - loss = fluid.layers.cross_entropy(input=pred, label=gt, soft_label=True) - loss = fluid.layers.reduce_mean(loss) - elif main_loss_type == "Euclidean": - loss = fluid.layers.square(pred - gt) - loss = fluid.layers.reduce_mean(loss) - elif main_loss_type == "DiceLoss": - loss = DiceLoss(pred, gt, mask) - elif main_loss_type == "BCELoss": - loss = fluid.layers.sigmoid_cross_entropy_with_logits(pred, label=gt) - elif main_loss_type == "MaskL1Loss": - loss = MaskL1Loss(pred, gt, mask) - else: - loss_type = [ - 'CrossEntropy', 'DiceLoss', 'Euclidean', 'BCELoss', 'MaskL1Loss' - ] - raise Exception("main_loss_type in BalanceLoss() can only be one of {}". - format(loss_type)) - - if not balance_loss: +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class BalanceLoss(nn.Layer): + def __init__(self, + balance_loss=True, + main_loss_type='DiceLoss', + negative_ratio=3, + return_origin=False, + eps=1e-6, + **kwargs): + """ + The BalanceLoss for Differentiable Binarization text detection + args: + balance_loss (bool): whether balance loss or not, default is True + main_loss_type (str): can only be one of ['CrossEntropy','DiceLoss', + 'Euclidean','BCELoss', 'MaskL1Loss'], default is 'DiceLoss'. + negative_ratio (int|float): float, default is 3. + return_origin (bool): whether return unbalanced loss or not, default is False. + eps (float): default is 1e-6. + """ + super(BalanceLoss, self).__init__() + self.balance_loss = balance_loss + self.main_loss_type = main_loss_type + self.negative_ratio = negative_ratio + self.main_loss_type = main_loss_type + self.return_origin = return_origin + self.eps = eps + + if self.main_loss_type == "CrossEntropy": + self.loss = nn.CrossEntropyLoss() + elif self.main_loss_type == "Euclidean": + self.loss = nn.MSELoss() + elif self.main_loss_type == "DiceLoss": + self.loss = DiceLoss(self.eps) + elif self.main_loss_type == "BCELoss": + self.loss = BCELoss(reduction='none') + elif self.main_loss_type == "MaskL1Loss": + self.loss = MaskL1Loss(self.eps) + else: + loss_type = [ + 'CrossEntropy', 'DiceLoss', 'Euclidean', 'BCELoss', 'MaskL1Loss' + ] + raise Exception( + "main_loss_type in BalanceLoss() can only be one of {}".format( + loss_type)) + + def forward(self, pred, gt, mask=None): + """ + The BalanceLoss for Differentiable Binarization text detection + args: + pred (variable): predicted feature maps. + gt (variable): ground truth feature maps. + mask (variable): masked maps. + return: (variable) balanced loss + """ + # if self.main_loss_type in ['DiceLoss']: + # # For the loss that returns to scalar value, perform ohem on the mask + # mask = ohem_batch(pred, gt, mask, self.negative_ratio) + # loss = self.loss(pred, gt, mask) + # return loss + + positive = gt * mask + negative = (1 - gt) * mask + + positive_count = int(positive.sum()) + negative_count = int( + min(negative.sum(), positive_count * self.negative_ratio)) + loss = self.loss(pred, gt, mask=mask) + + if not self.balance_loss: + return loss + + positive_loss = positive * loss + negative_loss = negative * loss + negative_loss = paddle.reshape(negative_loss, shape=[-1]) + if negative_count > 0: + sort_loss = negative_loss.sort(descending=True) + negative_loss = sort_loss[:negative_count] + # negative_loss, _ = paddle.topk(negative_loss, k=negative_count_int) + balance_loss = (positive_loss.sum() + negative_loss.sum()) / ( + positive_count + negative_count + self.eps) + else: + balance_loss = positive_loss.sum() / (positive_count + self.eps) + if self.return_origin: + return balance_loss, loss + + return balance_loss + + +class DiceLoss(nn.Layer): + def __init__(self, eps=1e-6): + super(DiceLoss, self).__init__() + self.eps = eps + + def forward(self, pred, gt, mask, weights=None): + """ + DiceLoss function. + """ + + assert pred.shape == gt.shape + assert pred.shape == mask.shape + if weights is not None: + assert weights.shape == mask.shape + mask = weights * mask + intersection = paddle.sum(pred * gt * mask) + + union = paddle.sum(pred * mask) + paddle.sum(gt * mask) + self.eps + loss = 1 - 2.0 * intersection / union + assert loss <= 1 return loss - positive_loss = positive * loss - negative_loss = negative * loss - negative_loss = fluid.layers.reshape(negative_loss, shape=[-1]) - negative_loss, _ = fluid.layers.topk(negative_loss, k=negative_count_int) - balance_loss = (fluid.layers.reduce_sum(positive_loss) + - fluid.layers.reduce_sum(negative_loss)) / ( - positive_count + negative_count + eps) - - if return_origin: - return balance_loss, loss - return balance_loss - - -def DiceLoss(pred, gt, mask, weights=None, eps=1e-6): - """ - DiceLoss function. - """ - - assert pred.shape == gt.shape - assert pred.shape == mask.shape - if weights is not None: - assert weights.shape == mask.shape - mask = weights * mask - intersection = fluid.layers.reduce_sum(pred * gt * mask) - - union = fluid.layers.reduce_sum(pred * mask) + fluid.layers.reduce_sum( - gt * mask) + eps - loss = 1 - 2.0 * intersection / union - assert loss <= 1 - return loss - - -def MaskL1Loss(pred, gt, mask, eps=1e-6): - """ - Mask L1 Loss - """ - loss = fluid.layers.reduce_sum((fluid.layers.abs(pred - gt) * mask)) / ( - fluid.layers.reduce_sum(mask) + eps) - loss = fluid.layers.reduce_mean(loss) - return loss + +class MaskL1Loss(nn.Layer): + def __init__(self, eps=1e-6): + super(MaskL1Loss, self).__init__() + self.eps = eps + + def forward(self, pred, gt, mask): + """ + Mask L1 Loss + """ + loss = (paddle.abs(pred - gt) * mask).sum() / (mask.sum() + self.eps) + loss = paddle.mean(loss) + return loss + + +class BCELoss(nn.Layer): + def __init__(self, reduction='mean'): + super(BCELoss, self).__init__() + self.reduction = reduction + + def forward(self, input, label, mask=None, weight=None, name=None): + loss = F.binary_cross_entropy(input, label, reduction=self.reduction) + return loss + + +def ohem_single(score, gt_text, training_mask, ohem_ratio): + pos_num = (int)(np.sum(gt_text > 0.5)) - ( + int)(np.sum((gt_text > 0.5) & (training_mask <= 0.5))) + + if pos_num == 0: + # selected_mask = gt_text.copy() * 0 # may be not good + selected_mask = training_mask + selected_mask = selected_mask.reshape( + 1, selected_mask.shape[0], selected_mask.shape[1]).astype('float32') + return selected_mask + + neg_num = (int)(np.sum(gt_text <= 0.5)) + neg_num = (int)(min(pos_num * ohem_ratio, neg_num)) + + if neg_num == 0: + selected_mask = training_mask + selected_mask = selected_mask.reshape( + 1, selected_mask.shape[0], selected_mask.shape[1]).astype('float32') + return selected_mask + + neg_score = score[gt_text <= 0.5] + # 将负样本得分从高到低排序 + neg_score_sorted = np.sort(-neg_score) + threshold = -neg_score_sorted[neg_num - 1] + # 选出 得分高的 负样本 和正样本 的 mask + selected_mask = ((score >= threshold) | + (gt_text > 0.5)) & (training_mask > 0.5) + selected_mask = selected_mask.reshape( + 1, selected_mask.shape[0], selected_mask.shape[1]).astype('float32') + return selected_mask + + +def ohem_batch(scores, gt_texts, training_masks, ohem_ratio): + scores = scores.numpy() + gt_texts = gt_texts.numpy() + training_masks = training_masks.numpy() + + selected_masks = [] + for i in range(scores.shape[0]): + selected_masks.append( + ohem_single(scores[i, :, :], gt_texts[i, :, :], training_masks[ + i, :, :], ohem_ratio)) + + selected_masks = np.concatenate(selected_masks, 0) + selected_masks = paddle.to_variable(selected_masks) + + return selected_masks diff --git a/ppocr/modeling/losses/det_db_loss.py b/ppocr/modeling/losses/det_db_loss.py index c35e33ae..f170f673 100755 --- a/ppocr/modeling/losses/det_db_loss.py +++ b/ppocr/modeling/losses/det_db_loss.py @@ -1,68 +1,71 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function +from paddle import nn + from .det_basic_loss import BalanceLoss, MaskL1Loss, DiceLoss -class DBLoss(object): +class DBLoss(nn.Layer): """ Differentiable Binarization (DB) Loss Function args: param (dict): the super paramter for DB Loss """ - def __init__(self, params): + def __init__(self, + balance_loss=True, + main_loss_type='DiceLoss', + alpha=5, + beta=10, + ohem_ratio=3, + eps=1e-6, + **kwargs): super(DBLoss, self).__init__() - self.balance_loss = params['balance_loss'] - self.main_loss_type = params['main_loss_type'] - - self.alpha = params['alpha'] - self.beta = params['beta'] - self.ohem_ratio = params['ohem_ratio'] + self.alpha = alpha + self.beta = beta + self.dice_loss = DiceLoss(eps=eps) + self.l1_loss = MaskL1Loss(eps=eps) + self.bce_loss = BalanceLoss( + balance_loss=balance_loss, + main_loss_type=main_loss_type, + negative_ratio=ohem_ratio) - def __call__(self, predicts, labels): - label_shrink_map = labels['shrink_map'] - label_shrink_mask = labels['shrink_mask'] - label_threshold_map = labels['threshold_map'] - label_threshold_mask = labels['threshold_mask'] - pred = predicts['maps'] - shrink_maps = pred[:, 0, :, :] - threshold_maps = pred[:, 1, :, :] - binary_maps = pred[:, 2, :, :] + def forward(self, predicts, labels): + label_threshold_map, label_threshold_mask, label_shrink_map, label_shrink_mask = labels[ + 1:] + shrink_maps = predicts[:, 0, :, :] + threshold_maps = predicts[:, 1, :, :] + binary_maps = predicts[:, 2, :, :] - loss_shrink_maps = BalanceLoss( - shrink_maps, - label_shrink_map, - label_shrink_mask, - balance_loss=self.balance_loss, - main_loss_type=self.main_loss_type, - negative_ratio=self.ohem_ratio) - loss_threshold_maps = MaskL1Loss(threshold_maps, label_threshold_map, - label_threshold_mask) - loss_binary_maps = DiceLoss(binary_maps, label_shrink_map, - label_shrink_mask) + loss_shrink_maps = self.bce_loss(shrink_maps, label_shrink_map, + label_shrink_mask) + loss_threshold_maps = self.l1_loss(threshold_maps, label_threshold_map, + label_threshold_mask) + loss_binary_maps = self.dice_loss(binary_maps, label_shrink_map, + label_shrink_mask) loss_shrink_maps = self.alpha * loss_shrink_maps loss_threshold_maps = self.beta * loss_threshold_maps - loss_all = loss_shrink_maps + loss_threshold_maps\ - + loss_binary_maps - losses = {'total_loss':loss_all,\ - "loss_shrink_maps":loss_shrink_maps,\ - "loss_threshold_maps":loss_threshold_maps,\ - "loss_binary_maps":loss_binary_maps} + loss_all = loss_shrink_maps + loss_threshold_maps \ + + loss_binary_maps + losses = {'loss': loss_all, \ + "loss_shrink_maps": loss_shrink_maps, \ + "loss_threshold_maps": loss_threshold_maps, \ + "loss_binary_maps": loss_binary_maps} return losses diff --git a/ppocr/modeling/losses/det_east_loss.py b/ppocr/modeling/losses/det_east_loss.py deleted file mode 100755 index 2019298a..00000000 --- a/ppocr/modeling/losses/det_east_loss.py +++ /dev/null @@ -1,61 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle.fluid as fluid - - -class EASTLoss(object): - """ - EAST Loss function - """ - - def __init__(self, params=None): - super(EASTLoss, self).__init__() - - def __call__(self, predicts, labels): - f_score = predicts['f_score'] - f_geo = predicts['f_geo'] - l_score = labels['score'] - l_geo = labels['geo'] - l_mask = labels['mask'] - ##dice_loss - intersection = fluid.layers.reduce_sum(f_score * l_score * l_mask) - union = fluid.layers.reduce_sum(f_score * l_mask)\ - + fluid.layers.reduce_sum(l_score * l_mask) - dice_loss = 1 - 2 * intersection / (union + 1e-5) - #smoooth_l1_loss - channels = 8 - l_geo_split = fluid.layers.split( - l_geo, num_or_sections=channels + 1, dim=1) - f_geo_split = fluid.layers.split(f_geo, num_or_sections=channels, dim=1) - smooth_l1 = 0 - for i in range(0, channels): - geo_diff = l_geo_split[i] - f_geo_split[i] - abs_geo_diff = fluid.layers.abs(geo_diff) - smooth_l1_sign = fluid.layers.less_than(abs_geo_diff, l_score) - smooth_l1_sign = fluid.layers.cast(smooth_l1_sign, dtype='float32') - in_loss = abs_geo_diff * abs_geo_diff * smooth_l1_sign + \ - (abs_geo_diff - 0.5) * (1.0 - smooth_l1_sign) - out_loss = l_geo_split[-1] / channels * in_loss * l_score - smooth_l1 += out_loss - smooth_l1_loss = fluid.layers.reduce_mean(smooth_l1 * l_score) - dice_loss = dice_loss * 0.01 - total_loss = dice_loss + smooth_l1_loss - losses = {'total_loss':total_loss, "dice_loss":dice_loss,\ - "smooth_l1_loss":smooth_l1_loss} - return losses diff --git a/ppocr/modeling/losses/det_sast_loss.py b/ppocr/modeling/losses/det_sast_loss.py deleted file mode 100644 index fb1a545a..00000000 --- a/ppocr/modeling/losses/det_sast_loss.py +++ /dev/null @@ -1,115 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle.fluid as fluid - - -class SASTLoss(object): - """ - SAST Loss function - """ - - def __init__(self, params=None): - super(SASTLoss, self).__init__() - - def __call__(self, predicts, labels): - """ - tcl_pos: N x 128 x 3 - tcl_mask: N x 128 x 1 - tcl_label: N x X list or LoDTensor - """ - - f_score = predicts['f_score'] - f_border = predicts['f_border'] - f_tvo = predicts['f_tvo'] - f_tco = predicts['f_tco'] - - l_score = labels['input_score'] - l_border = labels['input_border'] - l_mask = labels['input_mask'] - l_tvo = labels['input_tvo'] - l_tco = labels['input_tco'] - - #score_loss - intersection = fluid.layers.reduce_sum(f_score * l_score * l_mask) - union = fluid.layers.reduce_sum(f_score * l_mask) + fluid.layers.reduce_sum(l_score * l_mask) - score_loss = 1.0 - 2 * intersection / (union + 1e-5) - - #border loss - l_border_split, l_border_norm = fluid.layers.split(l_border, num_or_sections=[4, 1], dim=1) - f_border_split = f_border - l_border_norm_split = fluid.layers.expand(x=l_border_norm, expand_times=[1, 4, 1, 1]) - l_border_score = fluid.layers.expand(x=l_score, expand_times=[1, 4, 1, 1]) - l_border_mask = fluid.layers.expand(x=l_mask, expand_times=[1, 4, 1, 1]) - border_diff = l_border_split - f_border_split - abs_border_diff = fluid.layers.abs(border_diff) - border_sign = abs_border_diff < 1.0 - border_sign = fluid.layers.cast(border_sign, dtype='float32') - border_sign.stop_gradient = True - border_in_loss = 0.5 * abs_border_diff * abs_border_diff * border_sign + \ - (abs_border_diff - 0.5) * (1.0 - border_sign) - border_out_loss = l_border_norm_split * border_in_loss - border_loss = fluid.layers.reduce_sum(border_out_loss * l_border_score * l_border_mask) / \ - (fluid.layers.reduce_sum(l_border_score * l_border_mask) + 1e-5) - - #tvo_loss - l_tvo_split, l_tvo_norm = fluid.layers.split(l_tvo, num_or_sections=[8, 1], dim=1) - f_tvo_split = f_tvo - l_tvo_norm_split = fluid.layers.expand(x=l_tvo_norm, expand_times=[1, 8, 1, 1]) - l_tvo_score = fluid.layers.expand(x=l_score, expand_times=[1, 8, 1, 1]) - l_tvo_mask = fluid.layers.expand(x=l_mask, expand_times=[1, 8, 1, 1]) - # - tvo_geo_diff = l_tvo_split - f_tvo_split - abs_tvo_geo_diff = fluid.layers.abs(tvo_geo_diff) - tvo_sign = abs_tvo_geo_diff < 1.0 - tvo_sign = fluid.layers.cast(tvo_sign, dtype='float32') - tvo_sign.stop_gradient = True - tvo_in_loss = 0.5 * abs_tvo_geo_diff * abs_tvo_geo_diff * tvo_sign + \ - (abs_tvo_geo_diff - 0.5) * (1.0 - tvo_sign) - tvo_out_loss = l_tvo_norm_split * tvo_in_loss - tvo_loss = fluid.layers.reduce_sum(tvo_out_loss * l_tvo_score * l_tvo_mask) / \ - (fluid.layers.reduce_sum(l_tvo_score * l_tvo_mask) + 1e-5) - - #tco_loss - l_tco_split, l_tco_norm = fluid.layers.split(l_tco, num_or_sections=[2, 1], dim=1) - f_tco_split = f_tco - l_tco_norm_split = fluid.layers.expand(x=l_tco_norm, expand_times=[1, 2, 1, 1]) - l_tco_score = fluid.layers.expand(x=l_score, expand_times=[1, 2, 1, 1]) - l_tco_mask = fluid.layers.expand(x=l_mask, expand_times=[1, 2, 1, 1]) - # - tco_geo_diff = l_tco_split - f_tco_split - abs_tco_geo_diff = fluid.layers.abs(tco_geo_diff) - tco_sign = abs_tco_geo_diff < 1.0 - tco_sign = fluid.layers.cast(tco_sign, dtype='float32') - tco_sign.stop_gradient = True - tco_in_loss = 0.5 * abs_tco_geo_diff * abs_tco_geo_diff * tco_sign + \ - (abs_tco_geo_diff - 0.5) * (1.0 - tco_sign) - tco_out_loss = l_tco_norm_split * tco_in_loss - tco_loss = fluid.layers.reduce_sum(tco_out_loss * l_tco_score * l_tco_mask) / \ - (fluid.layers.reduce_sum(l_tco_score * l_tco_mask) + 1e-5) - - - # total loss - tvo_lw, tco_lw = 1.5, 1.5 - score_lw, border_lw = 1.0, 1.0 - total_loss = score_loss * score_lw + border_loss * border_lw + \ - tvo_loss * tvo_lw + tco_loss * tco_lw - - losses = {'total_loss':total_loss, "score_loss":score_loss,\ - "border_loss":border_loss, 'tvo_loss':tvo_loss, 'tco_loss':tco_loss} - return losses \ No newline at end of file diff --git a/ppocr/modeling/losses/rec_attention_loss.py b/ppocr/modeling/losses/rec_attention_loss.py deleted file mode 100755 index 8d8d7c13..00000000 --- a/ppocr/modeling/losses/rec_attention_loss.py +++ /dev/null @@ -1,38 +0,0 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr -import numpy as np - - -class AttentionLoss(object): - def __init__(self, params): - super(AttentionLoss, self).__init__() - self.char_num = params['char_num'] - - def __call__(self, predicts, labels): - predict = predicts['predict'] - label_out = labels['label_out'] - label_out = fluid.layers.cast(x=label_out, dtype='int64') - cost = fluid.layers.cross_entropy(input=predict, label=label_out) - sum_cost = fluid.layers.reduce_sum(cost) - return sum_cost diff --git a/ppocr/modeling/losses/rec_ctc_loss.py b/ppocr/modeling/losses/rec_ctc_loss.py index 3552d320..7894bea3 100755 --- a/ppocr/modeling/losses/rec_ctc_loss.py +++ b/ppocr/modeling/losses/rec_ctc_loss.py @@ -1,36 +1,36 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division from __future__ import print_function -import math - import paddle -import paddle.fluid as fluid +from paddle import nn -class CTCLoss(object): - def __init__(self, params): +class CTCLoss(nn.Layer): + def __init__(self, **kwargs): super(CTCLoss, self).__init__() - self.char_num = params['char_num'] + self.loss_func = nn.CTCLoss(blank=0, reduction='none') - def __call__(self, predicts, labels): - predict = predicts['predict'] - label = labels['label'] - cost = fluid.layers.warpctc( - input=predict, label=label, blank=self.char_num, norm_by_times=True) - sum_cost = fluid.layers.reduce_sum(cost) - return sum_cost + def __call__(self, predicts, batch): + predicts = predicts.transpose((1, 0, 2)) + N, B, _ = predicts.shape + preds_lengths = paddle.to_tensor([N] * B, dtype='int64') + labels = batch[1].astype("int32") + label_lengths = batch[2].astype('int64') + loss = self.loss_func(predicts, labels, preds_lengths, label_lengths) + loss = loss.mean() + return {'loss': loss} diff --git a/ppocr/modeling/losses/rec_srn_loss.py b/ppocr/modeling/losses/rec_srn_loss.py deleted file mode 100755 index b1ebd86f..00000000 --- a/ppocr/modeling/losses/rec_srn_loss.py +++ /dev/null @@ -1,55 +0,0 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle -import paddle.fluid as fluid - - -class SRNLoss(object): - def __init__(self, params): - super(SRNLoss, self).__init__() - self.char_num = params['char_num'] - - def __call__(self, predicts, others): - predict = predicts['predict'] - word_predict = predicts['word_out'] - gsrm_predict = predicts['gsrm_out'] - label = others['label'] - lbl_weight = others['lbl_weight'] - - casted_label = fluid.layers.cast(x=label, dtype='int64') - cost_word = fluid.layers.cross_entropy( - input=word_predict, label=casted_label) - cost_gsrm = fluid.layers.cross_entropy( - input=gsrm_predict, label=casted_label) - cost_vsfd = fluid.layers.cross_entropy( - input=predict, label=casted_label) - - cost_word = fluid.layers.reshape( - x=fluid.layers.reduce_sum(cost_word), shape=[1]) - cost_gsrm = fluid.layers.reshape( - x=fluid.layers.reduce_sum(cost_gsrm), shape=[1]) - cost_vsfd = fluid.layers.reshape( - x=fluid.layers.reduce_sum(cost_vsfd), shape=[1]) - - sum_cost = fluid.layers.sum( - [cost_word, cost_vsfd * 2.0, cost_gsrm * 0.15]) - - return [sum_cost, cost_vsfd, cost_word] diff --git a/tools/eval_utils/__init__.py b/ppocr/modeling/necks/__init__.py similarity index 62% rename from tools/eval_utils/__init__.py rename to ppocr/modeling/necks/__init__.py index abf198b9..bc7fdb79 100644 --- a/tools/eval_utils/__init__.py +++ b/ppocr/modeling/necks/__init__.py @@ -11,3 +11,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +__all__ = ['build_neck'] + + +def build_neck(config): + from .fpn import FPN + from .rnn import SequenceEncoder + support_dict = ['FPN', 'SequenceEncoder'] + + module_name = config.pop('name') + assert module_name in support_dict, Exception('neck only support {}'.format( + support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/necks/fpn.py b/ppocr/modeling/necks/fpn.py new file mode 100644 index 00000000..09f0bf9b --- /dev/null +++ b/ppocr/modeling/necks/fpn.py @@ -0,0 +1,113 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class FPN(nn.Layer): + def __init__(self, in_channels, out_channels, **kwargs): + super(FPN, self).__init__() + self.out_channels = out_channels + weight_attr = paddle.nn.initializer.MSRA(uniform=False) + + self.in2_conv = nn.Conv2d( + in_channels=in_channels[0], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr( + name='conv2d_51.w_0', initializer=weight_attr), + bias_attr=False) + self.in3_conv = nn.Conv2d( + in_channels=in_channels[1], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr( + name='conv2d_50.w_0', initializer=weight_attr), + bias_attr=False) + self.in4_conv = nn.Conv2d( + in_channels=in_channels[2], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr( + name='conv2d_49.w_0', initializer=weight_attr), + bias_attr=False) + self.in5_conv = nn.Conv2d( + in_channels=in_channels[3], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr( + name='conv2d_48.w_0', initializer=weight_attr), + bias_attr=False) + self.p5_conv = nn.Conv2d( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr( + name='conv2d_52.w_0', initializer=weight_attr), + bias_attr=False) + self.p4_conv = nn.Conv2d( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr( + name='conv2d_53.w_0', initializer=weight_attr), + bias_attr=False) + self.p3_conv = nn.Conv2d( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr( + name='conv2d_54.w_0', initializer=weight_attr), + bias_attr=False) + self.p2_conv = nn.Conv2d( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr( + name='conv2d_55.w_0', initializer=weight_attr), + bias_attr=False) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.in5_conv(c5) + in4 = self.in4_conv(c4) + in3 = self.in3_conv(c3) + in2 = self.in2_conv(c2) + + out4 = in4 + F.resize_nearest(in5, scale=2) # 1/16 + out3 = in3 + F.resize_nearest(out4, scale=2) # 1/8 + out2 = in2 + F.resize_nearest(out3, scale=2) # 1/4 + + p5 = self.p5_conv(in5) + p4 = self.p4_conv(out4) + p3 = self.p3_conv(out3) + p2 = self.p2_conv(out2) + p5 = F.resize_nearest(p5, scale=8) + p4 = F.resize_nearest(p4, scale=4) + p3 = F.resize_nearest(p3, scale=2) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + return fuse diff --git a/ppocr/modeling/necks/rnn.py b/ppocr/modeling/necks/rnn.py new file mode 100644 index 00000000..8a744e0d --- /dev/null +++ b/ppocr/modeling/necks/rnn.py @@ -0,0 +1,143 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn + +from ppocr.modeling.heads.rec_ctc_head import get_para_bias_attr + + +class EncoderWithReshape(nn.Layer): + def __init__(self, in_channels, **kwargs): + super().__init__() + self.out_channels = in_channels + + def forward(self, x): + B, C, H, W = x.shape + x = x.reshape((B, C, -1)) + x = x.transpose([0, 2, 1]) # (NTC)(batch, width, channels) + return x + + +class Im2Seq(nn.Layer): + def __init__(self, in_channels, **kwargs): + super().__init__() + self.out_channels = in_channels + + def forward(self, x): + B, C, H, W = x.shape + assert H == 1 + x = x.transpose((0, 2, 3, 1)) + x = x.reshape((-1, C)) + return x + + +class EncoderWithRNN(nn.Layer): + def __init__(self, in_channels, hidden_size): + super(EncoderWithRNN, self).__init__() + self.out_channels = hidden_size * 2 + # self.lstm1_fw = nn.LSTMCell( + # in_channels, + # hidden_size, + # weight_ih_attr=ParamAttr(name='lstm_st1_fc1_w'), + # bias_ih_attr=ParamAttr(name='lstm_st1_fc1_b'), + # weight_hh_attr=ParamAttr(name='lstm_st1_out1_w'), + # bias_hh_attr=ParamAttr(name='lstm_st1_out1_b'), + # ) + # self.lstm1_bw = nn.LSTMCell( + # in_channels, + # hidden_size, + # weight_ih_attr=ParamAttr(name='lstm_st1_fc2_w'), + # bias_ih_attr=ParamAttr(name='lstm_st1_fc2_b'), + # weight_hh_attr=ParamAttr(name='lstm_st1_out2_w'), + # bias_hh_attr=ParamAttr(name='lstm_st1_out2_b'), + # ) + # self.lstm2_fw = nn.LSTMCell( + # hidden_size, + # hidden_size, + # weight_ih_attr=ParamAttr(name='lstm_st2_fc1_w'), + # bias_ih_attr=ParamAttr(name='lstm_st2_fc1_b'), + # weight_hh_attr=ParamAttr(name='lstm_st2_out1_w'), + # bias_hh_attr=ParamAttr(name='lstm_st2_out1_b'), + # ) + # self.lstm2_bw = nn.LSTMCell( + # hidden_size, + # hidden_size, + # weight_ih_attr=ParamAttr(name='lstm_st2_fc2_w'), + # bias_ih_attr=ParamAttr(name='lstm_st2_fc2_b'), + # weight_hh_attr=ParamAttr(name='lstm_st2_out2_w'), + # bias_hh_attr=ParamAttr(name='lstm_st2_out2_b'), + # ) + self.lstm = nn.LSTM( + in_channels, hidden_size, direction='bidirectional', num_layers=2) + + def forward(self, x): + # fw_x, _ = self.lstm1_fw(x) + # fw_x, _ = self.lstm2_fw(fw_x) + # + # # bw + # bw_x, _ = self.lstm1_bw(x) + # bw_x, _ = self.lstm2_bw(bw_x) + # x = paddle.concat([fw_x, bw_x], axis=2) + x, _ = self.lstm(x) + return x + + +class EncoderWithFC(nn.Layer): + def __init__(self, in_channels, hidden_size): + super(EncoderWithFC, self).__init__() + self.out_channels = hidden_size + weight_attr, bias_attr = get_para_bias_attr( + l2_decay=0.00001, k=in_channels, name='reduce_encoder_fea') + self.fc = nn.Linear( + in_channels, + hidden_size, + weight_attr=weight_attr, + bias_attr=bias_attr, + name='reduce_encoder_fea') + + def forward(self, x): + x = self.fc(x) + return x + + +class SequenceEncoder(nn.Layer): + def __init__(self, in_channels, encoder_type, hidden_size, **kwargs): + super(SequenceEncoder, self).__init__() + self.encoder_reshape = EncoderWithReshape(in_channels) + self.out_channels = self.encoder_reshape.out_channels + if encoder_type == 'reshape': + self.only_reshape = True + else: + support_encoder_dict = { + 'reshape': EncoderWithReshape, + 'fc': EncoderWithFC, + 'rnn': EncoderWithRNN + } + assert encoder_type in support_encoder_dict, '{} must in {}'.format( + encoder_type, support_encoder_dict.keys()) + + self.encoder = support_encoder_dict[encoder_type]( + self.encoder_reshape.out_channels, hidden_size) + self.out_channels = self.encoder.out_channels + self.only_reshape = False + + def forward(self, x): + x = self.encoder_reshape(x) + if not self.only_reshape: + x = self.encoder(x) + return x diff --git a/ppocr/modeling/stns/tps.py b/ppocr/modeling/stns/tps.py deleted file mode 100755 index 24c6448d..00000000 --- a/ppocr/modeling/stns/tps.py +++ /dev/null @@ -1,261 +0,0 @@ -#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import math - -import paddle.fluid as fluid -import paddle.fluid.layers as layers -from paddle.fluid.param_attr import ParamAttr -import numpy as np - - -class LocalizationNetwork(object): - def __init__(self, params): - super(LocalizationNetwork, self).__init__() - self.F = params['num_fiducial'] - self.loc_lr = params['loc_lr'] - self.model_name = params['model_name'] - - def conv_bn_layer(self, - input, - num_filters, - filter_size, - stride=1, - groups=1, - act=None, - name=None): - conv = layers.conv2d( - input=input, - num_filters=num_filters, - filter_size=filter_size, - stride=stride, - padding=(filter_size - 1) // 2, - groups=groups, - act=None, - param_attr=ParamAttr(name=name + "_weights"), - bias_attr=False) - bn_name = "bn_" + name - return layers.batch_norm( - input=conv, - act=act, - param_attr=ParamAttr(name=bn_name + '_scale'), - bias_attr=ParamAttr(bn_name + '_offset'), - moving_mean_name=bn_name + '_mean', - moving_variance_name=bn_name + '_variance') - - def get_initial_fiducials(self): - """ see RARE paper Fig. 6 (a) """ - F = self.F - ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) - ctrl_pts_y_top = np.linspace(0.0, -1.0, num=int(F / 2)) - ctrl_pts_y_bottom = np.linspace(1.0, 0.0, num=int(F / 2)) - ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) - ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) - initial_bias = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) - return initial_bias - - def __call__(self, image): - F = self.F - loc_lr = self.loc_lr - if self.model_name == "large": - num_filters_list = [64, 128, 256, 512] - fc_dim = 256 - else: - num_filters_list = [16, 32, 64, 128] - fc_dim = 64 - for fno in range(len(num_filters_list)): - num_filters = num_filters_list[fno] - name = "loc_conv%d" % fno - if fno == 0: - conv = self.conv_bn_layer( - image, num_filters, 3, act='relu', name=name) - else: - conv = self.conv_bn_layer( - pool, num_filters, 3, act='relu', name=name) - - if fno == len(num_filters_list) - 1: - pool = layers.adaptive_pool2d( - input=conv, pool_size=[1, 1], pool_type='avg') - else: - pool = layers.pool2d( - input=conv, - pool_size=2, - pool_stride=2, - pool_padding=0, - pool_type='max') - name = "loc_fc1" - stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0) - fc1 = layers.fc(input=pool, - size=fc_dim, - param_attr=fluid.param_attr.ParamAttr( - learning_rate=loc_lr, - initializer=fluid.initializer.Uniform(-stdv, stdv), - name=name + "_w"), - act='relu', - name=name) - - initial_bias = self.get_initial_fiducials() - initial_bias = initial_bias.reshape(-1) - name = "loc_fc2" - param_attr = fluid.param_attr.ParamAttr( - learning_rate=loc_lr, - initializer=fluid.initializer.NumpyArrayInitializer( - np.zeros([fc_dim, F * 2])), - name=name + "_w") - bias_attr = fluid.param_attr.ParamAttr( - learning_rate=loc_lr, - initializer=fluid.initializer.NumpyArrayInitializer(initial_bias), - name=name + "_b") - fc2 = layers.fc(input=fc1, - size=F * 2, - param_attr=param_attr, - bias_attr=bias_attr, - name=name) - batch_C_prime = layers.reshape(x=fc2, shape=[-1, F, 2], inplace=False) - return batch_C_prime - - -class GridGenerator(object): - def __init__(self, params): - super(GridGenerator, self).__init__() - self.eps = 1e-6 - self.F = params['num_fiducial'] - - def build_C(self): - """ Return coordinates of fiducial points in I_r; C """ - F = self.F - ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) - ctrl_pts_y_top = -1 * np.ones(int(F / 2)) - ctrl_pts_y_bottom = np.ones(int(F / 2)) - ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) - ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) - C = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) - return C # F x 2 - - def build_P(self, I_r_size): - I_r_width, I_r_height = I_r_size - I_r_grid_x = (np.arange(-I_r_width, I_r_width, 2) + 1.0)\ - / I_r_width # self.I_r_width - I_r_grid_y = (np.arange(-I_r_height, I_r_height, 2) + 1.0)\ - / I_r_height # self.I_r_height - # P: self.I_r_width x self.I_r_height x 2 - P = np.stack(np.meshgrid(I_r_grid_x, I_r_grid_y), axis=2) - # n (= self.I_r_width x self.I_r_height) x 2 - return P.reshape([-1, 2]) - - def build_inv_delta_C(self, C): - """ Return inv_delta_C which is needed to calculate T """ - F = self.F - hat_C = np.zeros((F, F), dtype=float) # F x F - for i in range(0, F): - for j in range(i, F): - r = np.linalg.norm(C[i] - C[j]) - hat_C[i, j] = r - hat_C[j, i] = r - np.fill_diagonal(hat_C, 1) - hat_C = (hat_C**2) * np.log(hat_C) - # print(C.shape, hat_C.shape) - delta_C = np.concatenate( # F+3 x F+3 - [ - np.concatenate( - [np.ones((F, 1)), C, hat_C], axis=1), # F x F+3 - np.concatenate( - [np.zeros((2, 3)), np.transpose(C)], axis=1), # 2 x F+3 - np.concatenate( - [np.zeros((1, 3)), np.ones((1, F))], axis=1) # 1 x F+3 - ], - axis=0) - inv_delta_C = np.linalg.inv(delta_C) - return inv_delta_C # F+3 x F+3 - - def build_P_hat(self, C, P): - F = self.F - eps = self.eps - n = P.shape[0] # n (= self.I_r_width x self.I_r_height) - #P_tile: n x 2 -> n x 1 x 2 -> n x F x 2 - P_tile = np.tile(np.expand_dims(P, axis=1), (1, F, 1)) - C_tile = np.expand_dims(C, axis=0) # 1 x F x 2 - P_diff = P_tile - C_tile # n x F x 2 - #rbf_norm: n x F - rbf_norm = np.linalg.norm(P_diff, ord=2, axis=2, keepdims=False) - #rbf: n x F - rbf = np.multiply(np.square(rbf_norm), np.log(rbf_norm + eps)) - P_hat = np.concatenate([np.ones((n, 1)), P, rbf], axis=1) - return P_hat # n x F+3 - - def get_expand_tensor(self, batch_C_prime): - name = "ex_fc" - initializer = fluid.initializer.ConstantInitializer(value=0.0) - param_attr = fluid.param_attr.ParamAttr( - learning_rate=0.0, initializer=initializer, name=name + "_w") - bias_attr = fluid.param_attr.ParamAttr( - learning_rate=0.0, initializer=initializer, name=name + "_b") - batch_C_ex_part_tensor = fluid.layers.fc(input=batch_C_prime, - size=6, - param_attr=param_attr, - bias_attr=bias_attr, - name=name) - batch_C_ex_part_tensor = fluid.layers.reshape( - x=batch_C_ex_part_tensor, shape=[-1, 3, 2]) - return batch_C_ex_part_tensor - - def __call__(self, batch_C_prime, I_r_size): - C = self.build_C() - P = self.build_P(I_r_size) - inv_delta_C = self.build_inv_delta_C(C).astype('float32') - P_hat = self.build_P_hat(C, P).astype('float32') - - inv_delta_C_tensor = layers.create_tensor(dtype='float32') - layers.assign(inv_delta_C, inv_delta_C_tensor) - inv_delta_C_tensor.stop_gradient = True - P_hat_tensor = layers.create_tensor(dtype='float32') - layers.assign(P_hat, P_hat_tensor) - P_hat_tensor.stop_gradient = True - - batch_C_ex_part_tensor = self.get_expand_tensor(batch_C_prime) - # batch_C_ex_part_tensor = create_tmp_var( - # fluid.default_main_program(), - # name='batch_C_ex_part_tensor', - # dtype='float32', shape=[-1, 3, 2]) - # layers.py_func(func=get_batch_C_expand, - # x=[batch_C_prime], out=[batch_C_ex_part_tensor]) - - batch_C_ex_part_tensor.stop_gradient = True - - batch_C_prime_with_zeros = layers.concat( - [batch_C_prime, batch_C_ex_part_tensor], axis=1) - batch_T = layers.matmul(inv_delta_C_tensor, batch_C_prime_with_zeros) - batch_P_prime = layers.matmul(P_hat_tensor, batch_T) - return batch_P_prime - - -class TPS(object): - def __init__(self, params): - super(TPS, self).__init__() - self.loc_net = LocalizationNetwork(params) - self.grid_generator = GridGenerator(params) - - def __call__(self, image): - batch_C_prime = self.loc_net(image) - I_r_size = [image.shape[3], image.shape[2]] - batch_P_prime = self.grid_generator(batch_C_prime, I_r_size) - batch_P_prime = layers.reshape( - x=batch_P_prime, shape=[-1, image.shape[2], image.shape[3], 2]) - batch_I_r = layers.grid_sampler(x=image, grid=batch_P_prime) - image.stop_gradient = False - return batch_I_r diff --git a/ppocr/modeling/stns/__init__.py b/ppocr/modeling/transform/__init__.py similarity index 66% rename from ppocr/modeling/stns/__init__.py rename to ppocr/modeling/transform/__init__.py index abf198b9..af3b3f86 100755 --- a/ppocr/modeling/stns/__init__.py +++ b/ppocr/modeling/transform/__init__.py @@ -11,3 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +__all__ = ['build_transform'] + + +def build_transform(config): + support_dict = [''] + + module_name = config.pop('name') + assert module_name in support_dict, Exception( + 'transform only support {}'.format(support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/optimizer.py b/ppocr/optimizer.py deleted file mode 100644 index fd315cd1..00000000 --- a/ppocr/optimizer.py +++ /dev/null @@ -1,155 +0,0 @@ -#copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import math -import paddle.fluid as fluid -from paddle.fluid.regularizer import L2Decay -from paddle.fluid.layers.learning_rate_scheduler import _decay_step_counter -import paddle.fluid.layers.ops as ops - -from ppocr.utils.utility import initial_logger - -logger = initial_logger() - - -def cosine_decay_with_warmup(learning_rate, - step_each_epoch, - epochs=500, - warmup_minibatch=1000): - """Applies cosine decay to the learning rate. - lr = 0.05 * (math.cos(epoch * (math.pi / 120)) + 1) - decrease lr for every mini-batch and start with warmup. - """ - global_step = _decay_step_counter() - lr = fluid.layers.tensor.create_global_var( - shape=[1], - value=0.0, - dtype='float32', - persistable=True, - name="learning_rate") - - warmup_minibatch = fluid.layers.fill_constant( - shape=[1], - dtype='float32', - value=float(warmup_minibatch), - force_cpu=True) - - with fluid.layers.control_flow.Switch() as switch: - with switch.case(global_step < warmup_minibatch): - decayed_lr = learning_rate * (1.0 * global_step / warmup_minibatch) - fluid.layers.tensor.assign(input=decayed_lr, output=lr) - with switch.default(): - decayed_lr = learning_rate * \ - (ops.cos((global_step - warmup_minibatch) * (math.pi / (epochs * step_each_epoch))) + 1)/2 - fluid.layers.tensor.assign(input=decayed_lr, output=lr) - return lr - - -def AdamDecay(params, parameter_list=None): - """ - define optimizer function - args: - params(dict): the super parameters - parameter_list (list): list of Variable names to update to minimize loss - return: - """ - base_lr = params['base_lr'] - beta1 = params['beta1'] - beta2 = params['beta2'] - l2_decay = params.get("l2_decay", 0.0) - - if 'decay' in params: - supported_decay_mode = [ - "cosine_decay", "cosine_decay_warmup", "piecewise_decay" - ] - params = params['decay'] - decay_mode = params['function'] - assert decay_mode in supported_decay_mode, "Supported decay mode is {}, but got {}".format( - supported_decay_mode, decay_mode) - - if decay_mode == "cosine_decay": - step_each_epoch = params['step_each_epoch'] - total_epoch = params['total_epoch'] - base_lr = fluid.layers.cosine_decay( - learning_rate=base_lr, - step_each_epoch=step_each_epoch, - epochs=total_epoch) - elif decay_mode == "cosine_decay_warmup": - step_each_epoch = params['step_each_epoch'] - total_epoch = params['total_epoch'] - warmup_minibatch = params.get("warmup_minibatch", 1000) - base_lr = cosine_decay_with_warmup( - learning_rate=base_lr, - step_each_epoch=step_each_epoch, - epochs=total_epoch, - warmup_minibatch=warmup_minibatch) - elif decay_mode == "piecewise_decay": - boundaries = params["boundaries"] - decay_rate = params["decay_rate"] - values = [ - base_lr * decay_rate**idx - for idx in range(len(boundaries) + 1) - ] - base_lr = fluid.layers.piecewise_decay(boundaries, values) - - optimizer = fluid.optimizer.Adam( - learning_rate=base_lr, - beta1=beta1, - beta2=beta2, - regularization=L2Decay(regularization_coeff=l2_decay), - parameter_list=parameter_list) - return optimizer - - -def RMSProp(params, parameter_list=None): - """ - define optimizer function - args: - params(dict): the super parameters - parameter_list (list): list of Variable names to update to minimize loss - return: - """ - base_lr = params.get("base_lr", 0.001) - l2_decay = params.get("l2_decay", 0.00005) - - if 'decay' in params: - supported_decay_mode = ["cosine_decay", "piecewise_decay"] - params = params['decay'] - decay_mode = params['function'] - assert decay_mode in supported_decay_mode, "Supported decay mode is {}, but got {}".format( - supported_decay_mode, decay_mode) - - if decay_mode == "cosine_decay": - step_each_epoch = params['step_each_epoch'] - total_epoch = params['total_epoch'] - base_lr = fluid.layers.cosine_decay( - learning_rate=base_lr, - step_each_epoch=step_each_epoch, - epochs=total_epoch) - elif decay_mode == "piecewise_decay": - boundaries = params["boundaries"] - decay_rate = params["decay_rate"] - values = [ - base_lr * decay_rate**idx - for idx in range(len(boundaries) + 1) - ] - base_lr = fluid.layers.piecewise_decay(boundaries, values) - - optimizer = fluid.optimizer.RMSProp( - learning_rate=base_lr, - regularization=fluid.regularizer.L2Decay(regularization_coeff=l2_decay)) - - return optimizer diff --git a/ppocr/optimizer/__init__.py b/ppocr/optimizer/__init__.py new file mode 100644 index 00000000..a924f266 --- /dev/null +++ b/ppocr/optimizer/__init__.py @@ -0,0 +1,56 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import copy + +__all__ = ['build_optimizer'] + + +def build_lr_scheduler(lr_config, epochs, step_each_epoch): + from . import learning_rate + lr_config.update({'epochs': epochs, 'step_each_epoch': step_each_epoch}) + if 'name' in lr_config: + lr_name = lr_config.pop('name') + lr = getattr(learning_rate, lr_name)(**lr_config)() + else: + lr = lr_config['lr'] + return lr + + +def build_optimizer(config, epochs, step_each_epoch, parameters): + from . import regularizer, optimizer + config = copy.deepcopy(config) + # step1 build lr + lr = build_lr_scheduler( + config.pop('learning_rate'), epochs, step_each_epoch) + + # step2 build regularization + if 'regularizer' in config and config['regularizer'] is not None: + reg_config = config.pop('regularizer') + reg_name = reg_config.pop('name') + 'Decay' + reg = getattr(regularizer, reg_name)(**reg_config)() + else: + reg = None + + # step3 build optimizer + optim_name = config.pop('name') + optim = getattr(optimizer, optim_name)(learning_rate=lr, + regularization=reg, + **config) + return optim(parameters), lr diff --git a/ppocr/optimizer/learning_rate.py b/ppocr/optimizer/learning_rate.py new file mode 100644 index 00000000..5b86e846 --- /dev/null +++ b/ppocr/optimizer/learning_rate.py @@ -0,0 +1,183 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from paddle.optimizer import lr_scheduler + + +class Linear(object): + """ + Linear learning rate decay + Args: + lr (float): The initial learning rate. It is a python float number. + epochs(int): The decay step size. It determines the decay cycle. + end_lr(float, optional): The minimum final learning rate. Default: 0.0001. + power(float, optional): Power of polynomial. Default: 1.0. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__(self, + lr, + epochs, + step_each_epoch, + end_lr=0.0, + power=1.0, + warmup_epoch=0, + last_epoch=-1, + **kwargs): + super(Linear, self).__init__() + self.lr = lr + self.epochs = epochs * step_each_epoch + self.end_lr = end_lr + self.power = power + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch * step_each_epoch + + def __call__(self): + learning_rate = lr_scheduler.PolynomialLR( + learning_rate=self.lr, + decay_steps=self.epochs, + end_lr=self.end_lr, + power=self.power, + last_epoch=self.last_epoch) + if self.warmup_epoch > 0: + learning_rate = lr_scheduler.LinearLrWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.lr, + last_epoch=self.last_epoch) + return learning_rate + + +class Cosine(object): + """ + Cosine learning rate decay + lr = 0.05 * (math.cos(epoch * (math.pi / epochs)) + 1) + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__(self, + lr, + step_each_epoch, + epochs, + warmup_epoch=0, + last_epoch=-1, + **kwargs): + super(Cosine, self).__init__() + self.lr = lr + self.T_max = step_each_epoch * epochs + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch * step_each_epoch + + def __call__(self): + learning_rate = lr_scheduler.CosineAnnealingLR( + learning_rate=self.lr, T_max=self.T_max, last_epoch=self.last_epoch) + if self.warmup_epoch > 0: + learning_rate = lr_scheduler.LinearLrWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.lr, + last_epoch=self.last_epoch) + return learning_rate + + +class Step(object): + """ + Piecewise learning rate decay + Args: + step_each_epoch(int): steps each epoch + learning_rate (float): The initial learning rate. It is a python float number. + step_size (int): the interval to update. + gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . + It should be less than 1.0. Default: 0.1. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__(self, + lr, + step_size, + step_each_epoch, + gamma, + warmup_epoch=0, + last_epoch=-1, + **kwargs): + super(Step, self).__init__() + self.step_size = step_each_epoch * step_size + self.lr = lr + self.gamma = gamma + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch * step_each_epoch + + def __call__(self): + learning_rate = lr_scheduler.StepLR( + learning_rate=self.lr, + step_size=self.step_size, + gamma=self.gamma, + last_epoch=self.last_epoch) + if self.warmup_epoch > 0: + learning_rate = lr_scheduler.LinearLrWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.lr, + last_epoch=self.last_epoch) + return learning_rate + + +class Piecewise(object): + """ + Piecewise learning rate decay + Args: + boundaries(list): A list of steps numbers. The type of element in the list is python int. + values(list): A list of learning rate values that will be picked during different epoch boundaries. + The type of element in the list is python float. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__(self, + step_each_epoch, + decay_epochs, + values, + warmup_epoch=0, + last_epoch=-1, + **kwargs): + super(Piecewise, self).__init__() + self.boundaries = [step_each_epoch * e for e in decay_epochs] + self.values = values + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch * step_each_epoch + + def __call__(self): + learning_rate = lr_scheduler.PiecewiseLR( + boundaries=self.boundaries, + values=self.values, + last_epoch=self.last_epoch) + if self.warmup_epoch > 0: + learning_rate = lr_scheduler.LinearLrWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.values[0], + last_epoch=self.last_epoch) + return learning_rate diff --git a/ppocr/optimizer/optimizer.py b/ppocr/optimizer/optimizer.py new file mode 100644 index 00000000..b378a305 --- /dev/null +++ b/ppocr/optimizer/optimizer.py @@ -0,0 +1,119 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from paddle import optimizer as optim + + +class Momentum(object): + """ + Simple Momentum optimizer with velocity state. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__(self, learning_rate, momentum, weight_decay=None, **args): + super(Momentum, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.weight_decay = weight_decay + + def __call__(self, parameters): + opt = optim.Momentum( + learning_rate=self.learning_rate, + momentum=self.momentum, + parameters=self.weight_decay, + weight_decay=parameters) + return opt + + +class Adam(object): + def __init__(self, + learning_rate=0.001, + beta1=0.9, + beta2=0.999, + epsilon=1e-08, + parameter_list=None, + weight_decay=None, + grad_clip=None, + name=None, + lazy_mode=False, + **kwargs): + self.learning_rate = learning_rate + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.parameter_list = parameter_list + self.learning_rate = learning_rate + self.weight_decay = weight_decay + self.grad_clip = grad_clip + self.name = name + self.lazy_mode = lazy_mode + + def __call__(self, parameters): + opt = optim.Adam( + learning_rate=self.learning_rate, + beta1=self.beta1, + beta2=self.beta2, + epsilon=self.epsilon, + weight_decay=self.weight_decay, + grad_clip=self.grad_clip, + name=self.name, + lazy_mode=self.lazy_mode, + parameters=parameters) + return opt + + +class RMSProp(object): + """ + Root Mean Squared Propagation (RMSProp) is an unpublished, adaptive learning rate method. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + rho (float) - rho value in equation. + epsilon (float) - avoid division by zero, default is 1e-6. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__(self, + learning_rate, + momentum, + rho=0.95, + epsilon=1e-6, + weight_decay=None, + **args): + super(RMSProp, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.rho = rho + self.epsilon = epsilon + self.weight_decay = weight_decay + + def __call__(self, parameters): + opt = optim.RMSProp( + learning_rate=self.learning_rate, + momentum=self.momentum, + rho=self.rho, + epsilon=self.epsilon, + weight_decay=self.weight_decay, + parameters=parameters) + return opt diff --git a/ppocr/optimizer/regularizer.py b/ppocr/optimizer/regularizer.py new file mode 100644 index 00000000..8ac1b81f --- /dev/null +++ b/ppocr/optimizer/regularizer.py @@ -0,0 +1,54 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from paddle import fluid + + +class L1Decay(object): + """ + L1 Weight Decay Regularization, which encourages the weights to be sparse. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L1Decay, self).__init__() + self.regularization_coeff = factor + + def __call__(self): + reg = fluid.regularizer.L1Decay( + regularization_coeff=self.regularization_coeff) + return reg + + +class L2Decay(object): + """ + L2 Weight Decay Regularization, which encourages the weights to be sparse. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L2Decay, self).__init__() + self.regularization_coeff = factor + + def __call__(self): + reg = fluid.regularizer.L2Decay( + regularization_coeff=self.regularization_coeff) + return reg diff --git a/ppocr/postprocess/__init__.py b/ppocr/postprocess/__init__.py new file mode 100644 index 00000000..15fd7d3d --- /dev/null +++ b/ppocr/postprocess/__init__.py @@ -0,0 +1,38 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import copy + +__all__ = ['build_post_process'] + + +def build_post_process(config, global_config=None): + from .db_postprocess import DBPostProcess + + from .rec_postprocess import CTCLabelDecode, AttnLabelDecode + support_dict = ['DBPostProcess', 'CTCLabelDecode', 'AttnLabelDecode'] + + config = copy.deepcopy(config) + module_name = config.pop('name') + if global_config is not None: + config.update(global_config) + assert module_name in support_dict, Exception( + 'post process only support {}'.format(support_dict)) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/postprocess/db_postprocess.py b/ppocr/postprocess/db_postprocess.py index f115f12e..f09acb2a 100644 --- a/ppocr/postprocess/db_postprocess.py +++ b/ppocr/postprocess/db_postprocess.py @@ -16,11 +16,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import paddle -import paddle.fluid as fluid - import numpy as np -import string import cv2 from shapely.geometry import Polygon import pyclipper @@ -31,11 +27,16 @@ class DBPostProcess(object): The post process for Differentiable Binarization (DB). """ - def __init__(self, params): - self.thresh = params['thresh'] - self.box_thresh = params['box_thresh'] - self.max_candidates = params['max_candidates'] - self.unclip_ratio = params['unclip_ratio'] + def __init__(self, + thresh=0.3, + box_thresh=0.7, + max_candidates=1000, + unclip_ratio=2.0, + **kwargs): + self.thresh = thresh + self.box_thresh = box_thresh + self.max_candidates = max_candidates + self.unclip_ratio = unclip_ratio self.min_size = 3 def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): @@ -55,9 +56,9 @@ class DBPostProcess(object): contours, _ = outs[0], outs[1] num_contours = min(len(contours), self.max_candidates) - boxes = np.zeros((num_contours, 4, 2), dtype=np.int16) - scores = np.zeros((num_contours, ), dtype=np.float32) + boxes = [] + scores = [] for index in range(num_contours): contour = contours[index] points, sside = self.get_mini_boxes(contour) @@ -73,17 +74,14 @@ class DBPostProcess(object): if sside < self.min_size + 2: continue box = np.array(box) - if not isinstance(dest_width, int): - dest_width = dest_width.item() - dest_height = dest_height.item() box[:, 0] = np.clip( np.round(box[:, 0] / width * dest_width), 0, dest_width) box[:, 1] = np.clip( np.round(box[:, 1] / height * dest_height), 0, dest_height) - boxes[index, :, :] = box.astype(np.int16) - scores[index] = score - return boxes, scores + boxes.append(box.astype(np.int16)) + scores.append(score) + return np.array(boxes, dtype=np.int16), scores def unclip(self, box): unclip_ratio = self.unclip_ratio @@ -131,28 +129,15 @@ class DBPostProcess(object): cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1) return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] - def __call__(self, outs_dict, ratio_list): - pred = outs_dict['maps'] - - pred = pred[:, 0, :, :] + def __call__(self, pred, shape_list): + pred = pred.numpy()[:, 0, :, :] segmentation = pred > self.thresh boxes_batch = [] for batch_index in range(pred.shape[0]): - height, width = pred.shape[-2:] - tmp_boxes, tmp_scores = self.boxes_from_bitmap( + height, width = shape_list[batch_index] + boxes, scores = self.boxes_from_bitmap( pred[batch_index], segmentation[batch_index], width, height) - boxes = [] - for k in range(len(tmp_boxes)): - if tmp_scores[k] > self.box_thresh: - boxes.append(tmp_boxes[k]) - if len(boxes) > 0: - boxes = np.array(boxes) - - ratio_h, ratio_w = ratio_list[batch_index] - boxes[:, :, 0] = boxes[:, :, 0] / ratio_w - boxes[:, :, 1] = boxes[:, :, 1] / ratio_h - - boxes_batch.append(boxes) + boxes_batch.append({'points': boxes}) return boxes_batch diff --git a/ppocr/postprocess/db_postprocess_torch.py b/ppocr/postprocess/db_postprocess_torch.py new file mode 100644 index 00000000..83770df0 --- /dev/null +++ b/ppocr/postprocess/db_postprocess_torch.py @@ -0,0 +1,133 @@ +import cv2 +import numpy as np +import pyclipper +from shapely.geometry import Polygon + + +class DBPostProcess(): + def __init__(self, + thresh=0.3, + box_thresh=0.7, + max_candidates=1000, + unclip_ratio=1.5): + self.min_size = 3 + self.thresh = thresh + self.box_thresh = box_thresh + self.max_candidates = max_candidates + self.unclip_ratio = unclip_ratio + + def __call__(self, pred, shape_list, is_output_polygon=False): + ''' + batch: (image, polygons, ignore_tags + h_w_list: 包含[h,w]的数组 + pred: + binary: text region segmentation map, with shape (N, 1,H, W) + ''' + pred = pred.numpy()[:, 0, :, :] + segmentation = self.binarize(pred) + batch_out = [] + for batch_index in range(pred.shape[0]): + height, width = shape_list[batch_index] + boxes, scores = self.post_p( + pred[batch_index], + segmentation[batch_index], + width, + height, + is_output_polygon=is_output_polygon) + batch_out.append({"points": boxes}) + return batch_out + + def binarize(self, pred): + return pred > self.thresh + + def post_p(self, + pred, + bitmap, + dest_width, + dest_height, + is_output_polygon=True): + ''' + _bitmap: single map with shape (H, W), + whose values are binarized as {0, 1} + ''' + height, width = pred.shape + boxes = [] + new_scores = [] + contours, _ = cv2.findContours((bitmap * 255).astype(np.uint8), + cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) + for contour in contours[:self.max_candidates]: + epsilon = 0.005 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + points = approx.reshape((-1, 2)) + if points.shape[0] < 4: + continue + score = self.box_score_fast(pred, points.reshape(-1, 2)) + if self.box_thresh > score: + continue + + if points.shape[0] > 2: + box = self.unclip(points, unclip_ratio=self.unclip_ratio) + if len(box) > 1 or len(box) == 0: + continue + else: + continue + four_point_box, sside = self.get_mini_boxes(box.reshape((-1, 1, 2))) + if sside < self.min_size + 2: + continue + + if not is_output_polygon: + box = np.array(four_point_box) + else: + box = box.reshape(-1, 2) + box[:, 0] = np.clip( + np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height) + boxes.append(box) + new_scores.append(score) + return boxes, new_scores + + def unclip(self, box, unclip_ratio=1.5): + poly = Polygon(box) + distance = poly.area * unclip_ratio / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = np.array(offset.Execute(distance)) + return expanded + + def get_mini_boxes(self, contour): + bounding_box = cv2.minAreaRect(contour) + points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) + + index_1, index_2, index_3, index_4 = 0, 1, 2, 3 + if points[1][1] > points[0][1]: + index_1 = 0 + index_4 = 1 + else: + index_1 = 1 + index_4 = 0 + if points[3][1] > points[2][1]: + index_2 = 2 + index_3 = 3 + else: + index_2 = 3 + index_3 = 2 + + box = [ + points[index_1], points[index_2], points[index_3], points[index_4] + ] + return box, min(bounding_box[1]) + + def box_score_fast(self, bitmap, _box): + h, w = bitmap.shape[:2] + box = _box.copy() + xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int), 0, w - 1) + xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int), 0, w - 1) + ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int), 0, h - 1) + ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int), 0, h - 1) + + mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) + box[:, 0] = box[:, 0] - xmin + box[:, 1] = box[:, 1] - ymin + cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1) + return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] diff --git a/ppocr/postprocess/east_postprocess.py b/ppocr/postprocess/east_postprocess.py deleted file mode 100755 index 270cf669..00000000 --- a/ppocr/postprocess/east_postprocess.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from .locality_aware_nms import nms_locality -import cv2 - -import os -import sys -__dir__ = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(__dir__) -sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) - - -class EASTPostPocess(object): - """ - The post process for EAST. - """ - - def __init__(self, params): - self.score_thresh = params['score_thresh'] - self.cover_thresh = params['cover_thresh'] - self.nms_thresh = params['nms_thresh'] - - # c++ la-nms is faster, but only support python 3.5 - self.is_python35 = False - if sys.version_info.major == 3 and sys.version_info.minor == 5: - self.is_python35 = True - - def restore_rectangle_quad(self, origin, geometry): - """ - Restore rectangle from quadrangle. - """ - # quad - origin_concat = np.concatenate( - (origin, origin, origin, origin), axis=1) # (n, 8) - pred_quads = origin_concat - geometry - pred_quads = pred_quads.reshape((-1, 4, 2)) # (n, 4, 2) - return pred_quads - - def detect(self, - score_map, - geo_map, - score_thresh=0.8, - cover_thresh=0.1, - nms_thresh=0.2): - """ - restore text boxes from score map and geo map - """ - score_map = score_map[0] - geo_map = np.swapaxes(geo_map, 1, 0) - geo_map = np.swapaxes(geo_map, 1, 2) - # filter the score map - xy_text = np.argwhere(score_map > score_thresh) - if len(xy_text) == 0: - return [] - # sort the text boxes via the y axis - xy_text = xy_text[np.argsort(xy_text[:, 0])] - #restore quad proposals - text_box_restored = self.restore_rectangle_quad( - xy_text[:, ::-1] * 4, geo_map[xy_text[:, 0], xy_text[:, 1], :]) - boxes = np.zeros((text_box_restored.shape[0], 9), dtype=np.float32) - boxes[:, :8] = text_box_restored.reshape((-1, 8)) - boxes[:, 8] = score_map[xy_text[:, 0], xy_text[:, 1]] - if self.is_python35: - import lanms - boxes = lanms.merge_quadrangle_n9(boxes, nms_thresh) - else: - boxes = nms_locality(boxes.astype(np.float64), nms_thresh) - if boxes.shape[0] == 0: - return [] - # Here we filter some low score boxes by the average score map, - # this is different from the orginal paper. - for i, box in enumerate(boxes): - mask = np.zeros_like(score_map, dtype=np.uint8) - cv2.fillPoly(mask, box[:8].reshape( - (-1, 4, 2)).astype(np.int32) // 4, 1) - boxes[i, 8] = cv2.mean(score_map, mask)[0] - boxes = boxes[boxes[:, 8] > cover_thresh] - return boxes - - def sort_poly(self, p): - """ - Sort polygons. - """ - min_axis = np.argmin(np.sum(p, axis=1)) - p = p[[min_axis, (min_axis + 1) % 4,\ - (min_axis + 2) % 4, (min_axis + 3) % 4]] - if abs(p[0, 0] - p[1, 0]) > abs(p[0, 1] - p[1, 1]): - return p - else: - return p[[0, 3, 2, 1]] - - def __call__(self, outs_dict, ratio_list): - score_list = outs_dict['f_score'] - geo_list = outs_dict['f_geo'] - img_num = len(ratio_list) - dt_boxes_list = [] - for ino in range(img_num): - score = score_list[ino] - geo = geo_list[ino] - boxes = self.detect( - score_map=score, - geo_map=geo, - score_thresh=self.score_thresh, - cover_thresh=self.cover_thresh, - nms_thresh=self.nms_thresh) - boxes_norm = [] - if len(boxes) > 0: - ratio_h, ratio_w = ratio_list[ino] - boxes = boxes[:, :8].reshape((-1, 4, 2)) - boxes[:, :, 0] /= ratio_w - boxes[:, :, 1] /= ratio_h - for i_box, box in enumerate(boxes): - box = self.sort_poly(box.astype(np.int32)) - if np.linalg.norm(box[0] - box[1]) < 5 \ - or np.linalg.norm(box[3] - box[0]) < 5: - continue - boxes_norm.append(box) - dt_boxes_list.append(np.array(boxes_norm)) - return dt_boxes_list diff --git a/ppocr/postprocess/lanms/.gitignore b/ppocr/postprocess/lanms/.gitignore deleted file mode 100644 index 6a57227e..00000000 --- a/ppocr/postprocess/lanms/.gitignore +++ /dev/null @@ -1 +0,0 @@ -adaptor.so diff --git a/ppocr/postprocess/lanms/.ycm_extra_conf.py b/ppocr/postprocess/lanms/.ycm_extra_conf.py deleted file mode 100644 index cd1a74e9..00000000 --- a/ppocr/postprocess/lanms/.ycm_extra_conf.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2014 Google Inc. -# -# This file is part of YouCompleteMe. -# -# YouCompleteMe is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# YouCompleteMe is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with YouCompleteMe. If not, see . - -import os -import sys -import glob -import ycm_core - -# These are the compilation flags that will be used in case there's no -# compilation database set (by default, one is not set). -# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - - -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) - -from plumbum.cmd import python_config - - -flags = [ - '-Wall', - '-Wextra', - '-Wnon-virtual-dtor', - '-Winvalid-pch', - '-Wno-unused-local-typedefs', - '-std=c++11', - '-x', 'c++', - '-Iinclude', -] + python_config('--cflags').split() - - -# Set this to the absolute path to the folder (NOT the file!) containing the -# compile_commands.json file to use that instead of 'flags'. See here for -# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html -# -# Most projects will NOT need to set this to anything; you can just change the -# 'flags' list of compilation flags. -compilation_database_folder = '' - -if os.path.exists( compilation_database_folder ): - database = ycm_core.CompilationDatabase( compilation_database_folder ) -else: - database = None - -SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] - -def DirectoryOfThisScript(): - return os.path.dirname( os.path.abspath( __file__ ) ) - - -def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): - if not working_directory: - return list( flags ) - new_flags = [] - make_next_absolute = False - path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] - for flag in flags: - new_flag = flag - - if make_next_absolute: - make_next_absolute = False - if not flag.startswith( '/' ): - new_flag = os.path.join( working_directory, flag ) - - for path_flag in path_flags: - if flag == path_flag: - make_next_absolute = True - break - - if flag.startswith( path_flag ): - path = flag[ len( path_flag ): ] - new_flag = path_flag + os.path.join( working_directory, path ) - break - - if new_flag: - new_flags.append( new_flag ) - return new_flags - - -def IsHeaderFile( filename ): - extension = os.path.splitext( filename )[ 1 ] - return extension in [ '.h', '.hxx', '.hpp', '.hh' ] - - -def GetCompilationInfoForFile( filename ): - # The compilation_commands.json file generated by CMake does not have entries - # for header files. So we do our best by asking the db for flags for a - # corresponding source file, if any. If one exists, the flags for that file - # should be good enough. - if IsHeaderFile( filename ): - basename = os.path.splitext( filename )[ 0 ] - for extension in SOURCE_EXTENSIONS: - replacement_file = basename + extension - if os.path.exists( replacement_file ): - compilation_info = database.GetCompilationInfoForFile( - replacement_file ) - if compilation_info.compiler_flags_: - return compilation_info - return None - return database.GetCompilationInfoForFile( filename ) - - -# This is the entry point; this function is called by ycmd to produce flags for -# a file. -def FlagsForFile( filename, **kwargs ): - if database: - # Bear in mind that compilation_info.compiler_flags_ does NOT return a - # python list, but a "list-like" StringVec object - compilation_info = GetCompilationInfoForFile( filename ) - if not compilation_info: - return None - - final_flags = MakeRelativePathsInFlagsAbsolute( - compilation_info.compiler_flags_, - compilation_info.compiler_working_dir_ ) - else: - relative_to = DirectoryOfThisScript() - final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) - - return { - 'flags': final_flags, - 'do_cache': True - } - diff --git a/ppocr/postprocess/lanms/Makefile b/ppocr/postprocess/lanms/Makefile deleted file mode 100644 index 416871d1..00000000 --- a/ppocr/postprocess/lanms/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -CXXFLAGS = -I include -std=c++11 -O3 $(shell python3-config --cflags) -LDFLAGS = $(shell python3-config --ldflags) - -DEPS = lanms.h $(shell find include -xtype f) -CXX_SOURCES = adaptor.cpp include/clipper/clipper.cpp - -LIB_SO = adaptor.so - -$(LIB_SO): $(CXX_SOURCES) $(DEPS) - $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(CXX_SOURCES) --shared -fPIC - -clean: - rm -rf $(LIB_SO) diff --git a/ppocr/postprocess/lanms/__init__.py b/ppocr/postprocess/lanms/__init__.py deleted file mode 100644 index 649d6468..00000000 --- a/ppocr/postprocess/lanms/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -import subprocess -import os -import numpy as np - -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) - -if subprocess.call(['make', '-C', BASE_DIR]) != 0: # return value - raise RuntimeError('Cannot compile lanms: {}'.format(BASE_DIR)) - - -def merge_quadrangle_n9(polys, thres=0.3, precision=10000): - from .adaptor import merge_quadrangle_n9 as nms_impl - if len(polys) == 0: - return np.array([], dtype='float32') - p = polys.copy() - p[:,:8] *= precision - ret = np.array(nms_impl(p, thres), dtype='float32') - ret[:,:8] /= precision - return ret - diff --git a/ppocr/postprocess/lanms/__main__.py b/ppocr/postprocess/lanms/__main__.py deleted file mode 100644 index 72bba360..00000000 --- a/ppocr/postprocess/lanms/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -import numpy as np - - -from . import merge_quadrangle_n9 - -if __name__ == '__main__': - # unit square with confidence 1 - q = np.array([0, 0, 0, 1, 1, 1, 1, 0, 1], dtype='float32') - - print(merge_quadrangle_n9(np.array([q, q + 0.1, q + 2]))) diff --git a/ppocr/postprocess/lanms/adaptor.cpp b/ppocr/postprocess/lanms/adaptor.cpp deleted file mode 100644 index 7d38278f..00000000 --- a/ppocr/postprocess/lanms/adaptor.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "pybind11/pybind11.h" -#include "pybind11/numpy.h" -#include "pybind11/stl.h" -#include "pybind11/stl_bind.h" - -#include "lanms.h" - -namespace py = pybind11; - - -namespace lanms_adaptor { - - std::vector> polys2floats(const std::vector &polys) { - std::vector> ret; - for (size_t i = 0; i < polys.size(); i ++) { - auto &p = polys[i]; - auto &poly = p.poly; - ret.emplace_back(std::vector{ - float(poly[0].X), float(poly[0].Y), - float(poly[1].X), float(poly[1].Y), - float(poly[2].X), float(poly[2].Y), - float(poly[3].X), float(poly[3].Y), - float(p.score), - }); - } - - return ret; - } - - - /** - * - * \param quad_n9 an n-by-9 numpy array, where first 8 numbers denote the - * quadrangle, and the last one is the score - * \param iou_threshold two quadrangles with iou score above this threshold - * will be merged - * - * \return an n-by-9 numpy array, the merged quadrangles - */ - std::vector> merge_quadrangle_n9( - py::array_t quad_n9, - float iou_threshold) { - auto pbuf = quad_n9.request(); - if (pbuf.ndim != 2 || pbuf.shape[1] != 9) - throw std::runtime_error("quadrangles must have a shape of (n, 9)"); - auto n = pbuf.shape[0]; - auto ptr = static_cast(pbuf.ptr); - return polys2floats(lanms::merge_quadrangle_n9(ptr, n, iou_threshold)); - } - -} - -PYBIND11_PLUGIN(adaptor) { - py::module m("adaptor", "NMS"); - - m.def("merge_quadrangle_n9", &lanms_adaptor::merge_quadrangle_n9, - "merge quadrangels"); - - return m.ptr(); -} - diff --git a/ppocr/postprocess/lanms/include/clipper/clipper.cpp b/ppocr/postprocess/lanms/include/clipper/clipper.cpp deleted file mode 100644 index 09657560..00000000 --- a/ppocr/postprocess/lanms/include/clipper/clipper.cpp +++ /dev/null @@ -1,4622 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.0 * -* Date : 2 July 2015 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -/******************************************************************************* -* * -* This is a translation of the Delphi Clipper library and the naming style * -* used has retained a Delphi flavour. * -* * -*******************************************************************************/ - -#include "clipper.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -static double const pi = 3.141592653589793238; -static double const two_pi = pi *2; -static double const def_arc_tolerance = 0.25; - -enum Direction { dRightToLeft, dLeftToRight }; - -static int const Unassigned = -1; //edge not currently 'owning' a solution -static int const Skip = -2; //edge that would otherwise close a path - -#define HORIZONTAL (-1.0E+40) -#define TOLERANCE (1.0e-20) -#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) - -struct TEdge { - IntPoint Bot; - IntPoint Curr; //current (updated for every new scanbeam) - IntPoint Top; - double Dx; - PolyType PolyTyp; - EdgeSide Side; //side only refers to current side of solution poly - int WindDelta; //1 or -1 depending on winding direction - int WindCnt; - int WindCnt2; //winding count of the opposite polytype - int OutIdx; - TEdge *Next; - TEdge *Prev; - TEdge *NextInLML; - TEdge *NextInAEL; - TEdge *PrevInAEL; - TEdge *NextInSEL; - TEdge *PrevInSEL; -}; - -struct IntersectNode { - TEdge *Edge1; - TEdge *Edge2; - IntPoint Pt; -}; - -struct LocalMinimum { - cInt Y; - TEdge *LeftBound; - TEdge *RightBound; -}; - -struct OutPt; - -//OutRec: contains a path in the clipping solution. Edges in the AEL will -//carry a pointer to an OutRec when they are part of the clipping solution. -struct OutRec { - int Idx; - bool IsHole; - bool IsOpen; - OutRec *FirstLeft; //see comments in clipper.pas - PolyNode *PolyNd; - OutPt *Pts; - OutPt *BottomPt; -}; - -struct OutPt { - int Idx; - IntPoint Pt; - OutPt *Next; - OutPt *Prev; -}; - -struct Join { - OutPt *OutPt1; - OutPt *OutPt2; - IntPoint OffPt; -}; - -struct LocMinSorter -{ - inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2) - { - return locMin2.Y < locMin1.Y; - } -}; - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -inline cInt Round(double val) -{ - if ((val < 0)) return static_cast(val - 0.5); - else return static_cast(val + 0.5); -} -//------------------------------------------------------------------------------ - -inline cInt Abs(cInt val) -{ - return val < 0 ? -val : val; -} - -//------------------------------------------------------------------------------ -// PolyTree methods ... -//------------------------------------------------------------------------------ - -void PolyTree::Clear() -{ - for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) - delete AllNodes[i]; - AllNodes.resize(0); - Childs.resize(0); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyTree::GetFirst() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return 0; -} -//------------------------------------------------------------------------------ - -int PolyTree::Total() const -{ - int result = (int)AllNodes.size(); - //with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && Childs[0] != AllNodes[0]) result--; - return result; -} - -//------------------------------------------------------------------------------ -// PolyNode methods ... -//------------------------------------------------------------------------------ - -PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) -{ -} -//------------------------------------------------------------------------------ - -int PolyNode::ChildCount() const -{ - return (int)Childs.size(); -} -//------------------------------------------------------------------------------ - -void PolyNode::AddChild(PolyNode& child) -{ - unsigned cnt = (unsigned)Childs.size(); - Childs.push_back(&child); - child.Parent = this; - child.Index = cnt; -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNext() const -{ - if (!Childs.empty()) - return Childs[0]; - else - return GetNextSiblingUp(); -} -//------------------------------------------------------------------------------ - -PolyNode* PolyNode::GetNextSiblingUp() const -{ - if (!Parent) //protects against PolyTree.GetNextSiblingUp() - return 0; - else if (Index == Parent->Childs.size() - 1) - return Parent->GetNextSiblingUp(); - else - return Parent->Childs[Index + 1]; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsHole() const -{ - bool result = true; - PolyNode* node = Parent; - while (node) - { - result = !result; - node = node->Parent; - } - return result; -} -//------------------------------------------------------------------------------ - -bool PolyNode::IsOpen() const -{ - return m_IsOpen; -} -//------------------------------------------------------------------------------ - -#ifndef use_int32 - -//------------------------------------------------------------------------------ -// Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 -// Int128 val2((long64)9223372036854775807); -// Int128 val3 = val1 * val2; -// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) -//------------------------------------------------------------------------------ - -class Int128 -{ - public: - ulong64 lo; - long64 hi; - - Int128(long64 _lo = 0) - { - lo = (ulong64)_lo; - if (_lo < 0) hi = -1; else hi = 0; - } - - - Int128(const Int128 &val): lo(val.lo), hi(val.hi){} - - Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} - - Int128& operator = (const long64 &val) - { - lo = (ulong64)val; - if (val < 0) hi = -1; else hi = 0; - return *this; - } - - bool operator == (const Int128 &val) const - {return (hi == val.hi && lo == val.lo);} - - bool operator != (const Int128 &val) const - { return !(*this == val);} - - bool operator > (const Int128 &val) const - { - if (hi != val.hi) - return hi > val.hi; - else - return lo > val.lo; - } - - bool operator < (const Int128 &val) const - { - if (hi != val.hi) - return hi < val.hi; - else - return lo < val.lo; - } - - bool operator >= (const Int128 &val) const - { return !(*this < val);} - - bool operator <= (const Int128 &val) const - { return !(*this > val);} - - Int128& operator += (const Int128 &rhs) - { - hi += rhs.hi; - lo += rhs.lo; - if (lo < rhs.lo) hi++; - return *this; - } - - Int128 operator + (const Int128 &rhs) const - { - Int128 result(*this); - result+= rhs; - return result; - } - - Int128& operator -= (const Int128 &rhs) - { - *this += -rhs; - return *this; - } - - Int128 operator - (const Int128 &rhs) const - { - Int128 result(*this); - result -= rhs; - return result; - } - - Int128 operator-() const //unary negation - { - if (lo == 0) - return Int128(-hi, 0); - else - return Int128(~hi, ~lo + 1); - } - - operator double() const - { - const double shift64 = 18446744073709551616.0; //2^64 - if (hi < 0) - { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); - } - else - return (double)(lo + hi * shift64); - } - -}; -//------------------------------------------------------------------------------ - -Int128 Int128Mul (long64 lhs, long64 rhs) -{ - bool negate = (lhs < 0) != (rhs < 0); - - if (lhs < 0) lhs = -lhs; - ulong64 int1Hi = ulong64(lhs) >> 32; - ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); - - if (rhs < 0) rhs = -rhs; - ulong64 int2Hi = ulong64(rhs) >> 32; - ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); - - //nb: see comments in clipper.pas - ulong64 a = int1Hi * int2Hi; - ulong64 b = int1Lo * int2Lo; - ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; - - Int128 tmp; - tmp.hi = long64(a + (c >> 32)); - tmp.lo = long64(c << 32); - tmp.lo += long64(b); - if (tmp.lo < b) tmp.hi++; - if (negate) tmp = -tmp; - return tmp; -}; -#endif - -//------------------------------------------------------------------------------ -// Miscellaneous global functions -//------------------------------------------------------------------------------ - -bool Orientation(const Path &poly) -{ - return Area(poly) >= 0; -} -//------------------------------------------------------------------------------ - -double Area(const Path &poly) -{ - int size = (int)poly.size(); - if (size < 3) return 0; - - double a = 0; - for (int i = 0, j = size -1; i < size; ++i) - { - a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); - j = i; - } - return -a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutPt *op) -{ - const OutPt *startOp = op; - if (!op) return 0; - double a = 0; - do { - a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); - op = op->Next; - } while (op != startOp); - return a * 0.5; -} -//------------------------------------------------------------------------------ - -double Area(const OutRec &outRec) -{ - return Area(outRec.Pts); -} -//------------------------------------------------------------------------------ - -bool PointIsVertex(const IntPoint &Pt, OutPt *pp) -{ - OutPt *pp2 = pp; - do - { - if (pp2->Pt == Pt) return true; - pp2 = pp2->Next; - } - while (pp2 != pp); - return false; -} -//------------------------------------------------------------------------------ - -//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos -//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf -int PointInPolygon(const IntPoint &pt, const Path &path) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - size_t cnt = path.size(); - if (cnt < 3) return 0; - IntPoint ip = path[0]; - for(size_t i = 1; i <= cnt; ++i) - { - IntPoint ipNext = (i == cnt ? path[0] : path[i]); - if (ipNext.Y == pt.Y) - { - if ((ipNext.X == pt.X) || (ip.Y == pt.Y && - ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; - } - if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) - { - if (ip.X >= pt.X) - { - if (ipNext.X > pt.X) result = 1 - result; - else - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } else - { - if (ipNext.X > pt.X) - { - double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; - } - } - } - ip = ipNext; - } - return result; -} -//------------------------------------------------------------------------------ - -int PointInPolygon (const IntPoint &pt, OutPt *op) -{ - //returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - OutPt* startOp = op; - for(;;) - { - if (op->Next->Pt.Y == pt.Y) - { - if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && - ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; - } - if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) - { - if (op->Pt.X >= pt.X) - { - if (op->Next->Pt.X > pt.X) result = 1 - result; - else - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } else - { - if (op->Next->Pt.X > pt.X) - { - double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); - if (!d) return -1; - if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; - } - } - } - op = op->Next; - if (startOp == op) break; - } - return result; -} -//------------------------------------------------------------------------------ - -bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) -{ - OutPt* op = OutPt1; - do - { - //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon - int res = PointInPolygon(op->Pt, OutPt2); - if (res >= 0) return res > 0; - op = op->Next; - } - while (op != OutPt1); - return true; -} -//---------------------------------------------------------------------- - -bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == - Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); - else -#endif - return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == - (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); -} -//------------------------------------------------------------------------------ - -bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, - const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) -{ -#ifndef use_int32 - if (UseFullInt64Range) - return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); - else -#endif - return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); -} -//------------------------------------------------------------------------------ - -inline bool IsHorizontal(TEdge &e) -{ - return e.Dx == HORIZONTAL; -} -//------------------------------------------------------------------------------ - -inline double GetDx(const IntPoint pt1, const IntPoint pt2) -{ - return (pt1.Y == pt2.Y) ? - HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); -} -//--------------------------------------------------------------------------- - -inline void SetDx(TEdge &e) -{ - cInt dy = (e.Top.Y - e.Bot.Y); - if (dy == 0) e.Dx = HORIZONTAL; - else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; -} -//--------------------------------------------------------------------------- - -inline void SwapSides(TEdge &Edge1, TEdge &Edge2) -{ - EdgeSide Side = Edge1.Side; - Edge1.Side = Edge2.Side; - Edge2.Side = Side; -} -//------------------------------------------------------------------------------ - -inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) -{ - int OutIdx = Edge1.OutIdx; - Edge1.OutIdx = Edge2.OutIdx; - Edge2.OutIdx = OutIdx; -} -//------------------------------------------------------------------------------ - -inline cInt TopX(TEdge &edge, const cInt currentY) -{ - return ( currentY == edge.Top.Y ) ? - edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); -} -//------------------------------------------------------------------------------ - -void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) -{ -#ifdef use_xyz - ip.Z = 0; -#endif - - double b1, b2; - if (Edge1.Dx == Edge2.Dx) - { - ip.Y = Edge1.Curr.Y; - ip.X = TopX(Edge1, ip.Y); - return; - } - else if (Edge1.Dx == 0) - { - ip.X = Edge1.Bot.X; - if (IsHorizontal(Edge2)) - ip.Y = Edge2.Bot.Y; - else - { - b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); - ip.Y = Round(ip.X / Edge2.Dx + b2); - } - } - else if (Edge2.Dx == 0) - { - ip.X = Edge2.Bot.X; - if (IsHorizontal(Edge1)) - ip.Y = Edge1.Bot.Y; - else - { - b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); - ip.Y = Round(ip.X / Edge1.Dx + b1); - } - } - else - { - b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; - b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; - double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); - ip.Y = Round(q); - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = Round(Edge1.Dx * q + b1); - else - ip.X = Round(Edge2.Dx * q + b2); - } - - if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) - { - if (Edge1.Top.Y > Edge2.Top.Y) - ip.Y = Edge1.Top.Y; - else - ip.Y = Edge2.Top.Y; - if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) - ip.X = TopX(Edge1, ip.Y); - else - ip.X = TopX(Edge2, ip.Y); - } - //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > Edge1.Curr.Y) - { - ip.Y = Edge1.Curr.Y; - //use the more vertical edge to derive X ... - if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) - ip.X = TopX(Edge2, ip.Y); else - ip.X = TopX(Edge1, ip.Y); - } -} -//------------------------------------------------------------------------------ - -void ReversePolyPtLinks(OutPt *pp) -{ - if (!pp) return; - OutPt *pp1, *pp2; - pp1 = pp; - do { - pp2 = pp1->Next; - pp1->Next = pp1->Prev; - pp1->Prev = pp2; - pp1 = pp2; - } while( pp1 != pp ); -} -//------------------------------------------------------------------------------ - -void DisposeOutPts(OutPt*& pp) -{ - if (pp == 0) return; - pp->Prev->Next = 0; - while( pp ) - { - OutPt *tmpPp = pp; - pp = pp->Next; - delete tmpPp; - } -} -//------------------------------------------------------------------------------ - -inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) -{ - std::memset(e, 0, sizeof(TEdge)); - e->Next = eNext; - e->Prev = ePrev; - e->Curr = Pt; - e->OutIdx = Unassigned; -} -//------------------------------------------------------------------------------ - -void InitEdge2(TEdge& e, PolyType Pt) -{ - if (e.Curr.Y >= e.Next->Curr.Y) - { - e.Bot = e.Curr; - e.Top = e.Next->Curr; - } else - { - e.Top = e.Curr; - e.Bot = e.Next->Curr; - } - SetDx(e); - e.PolyTyp = Pt; -} -//------------------------------------------------------------------------------ - -TEdge* RemoveEdge(TEdge* e) -{ - //removes e from double_linked_list (but without removing from memory) - e->Prev->Next = e->Next; - e->Next->Prev = e->Prev; - TEdge* result = e->Next; - e->Prev = 0; //flag as removed (see ClipperBase.Clear) - return result; -} -//------------------------------------------------------------------------------ - -inline void ReverseHorizontal(TEdge &e) -{ - //swap horizontal edges' Top and Bottom x's so they follow the natural - //progression of the bounds - ie so their xbots will align with the - //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - std::swap(e.Top.X, e.Bot.X); -#ifdef use_xyz - std::swap(e.Top.Z, e.Bot.Z); -#endif -} -//------------------------------------------------------------------------------ - -void SwapPoints(IntPoint &pt1, IntPoint &pt2) -{ - IntPoint tmp = pt1; - pt1 = pt2; - pt2 = tmp; -} -//------------------------------------------------------------------------------ - -bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, - IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) -{ - //precondition: segments are Collinear. - if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) - { - if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); - if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); - if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; - return pt1.X < pt2.X; - } else - { - if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); - if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); - if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; - if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; - return pt1.Y > pt2.Y; - } -} -//------------------------------------------------------------------------------ - -bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) -{ - OutPt *p = btmPt1->Prev; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; - double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - p = btmPt1->Next; - while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; - double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); - - p = btmPt2->Prev; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; - double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - p = btmPt2->Next; - while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; - double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - - if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && - std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) - return Area(btmPt1) > 0; //if otherwise identical use orientation - else - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); -} -//------------------------------------------------------------------------------ - -OutPt* GetBottomPt(OutPt *pp) -{ - OutPt* dups = 0; - OutPt* p = pp->Next; - while (p != pp) - { - if (p->Pt.Y > pp->Pt.Y) - { - pp = p; - dups = 0; - } - else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) - { - if (p->Pt.X < pp->Pt.X) - { - dups = 0; - pp = p; - } else - { - if (p->Next != pp && p->Prev != pp) dups = p; - } - } - p = p->Next; - } - if (dups) - { - //there appears to be at least 2 vertices at BottomPt so ... - while (dups != p) - { - if (!FirstIsBottomPt(p, dups)) pp = dups; - dups = dups->Next; - while (dups->Pt != pp->Pt) dups = dups->Next; - } - } - return pp; -} -//------------------------------------------------------------------------------ - -bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, - const IntPoint pt2, const IntPoint pt3) -{ - if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) - return false; - else if (pt1.X != pt3.X) - return (pt2.X > pt1.X) == (pt2.X < pt3.X); - else - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); -} -//------------------------------------------------------------------------------ - -bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) -{ - if (seg1a > seg1b) std::swap(seg1a, seg1b); - if (seg2a > seg2b) std::swap(seg2a, seg2b); - return (seg1a < seg2b) && (seg2a < seg1b); -} - -//------------------------------------------------------------------------------ -// ClipperBase class methods ... -//------------------------------------------------------------------------------ - -ClipperBase::ClipperBase() //constructor -{ - m_CurrentLM = m_MinimaList.begin(); //begin() == end() here - m_UseFullRange = false; -} -//------------------------------------------------------------------------------ - -ClipperBase::~ClipperBase() //destructor -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void RangeTest(const IntPoint& Pt, bool& useFullRange) -{ - if (useFullRange) - { - if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) - throw clipperException("Coordinate outside allowed range"); - } - else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) - { - useFullRange = true; - RangeTest(Pt, useFullRange); - } -} -//------------------------------------------------------------------------------ - -TEdge* FindNextLocMin(TEdge* E) -{ - for (;;) - { - while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; - if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; - while (IsHorizontal(*E->Prev)) E = E->Prev; - TEdge* E2 = E; - while (IsHorizontal(*E)) E = E->Next; - if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. - if (E2->Prev->Bot.X < E->Bot.X) E = E2; - break; - } - return E; -} -//------------------------------------------------------------------------------ - -TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) -{ - TEdge *Result = E; - TEdge *Horz = 0; - - if (E->OutIdx == Skip) - { - //if edges still remain in the current bound beyond the skip edge then - //create another LocMin and call ProcessBound once more - if (NextIsForward) - { - while (E->Top.Y == E->Next->Bot.Y) E = E->Next; - //don't include top horizontals when parsing a bound a second time, - //they will be contained in the opposite bound ... - while (E != Result && IsHorizontal(*E)) E = E->Prev; - } - else - { - while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; - while (E != Result && IsHorizontal(*E)) E = E->Next; - } - - if (E == Result) - { - if (NextIsForward) Result = E->Next; - else Result = E->Prev; - } - else - { - //there are more edges in the bound beyond result starting with E - if (NextIsForward) - E = Result->Next; - else - E = Result->Prev; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - E->WindDelta = 0; - Result = ProcessBound(E, NextIsForward); - m_MinimaList.push_back(locMin); - } - return Result; - } - - TEdge *EStart; - - if (IsHorizontal(*E)) - { - //We need to be careful with open paths because this may not be a - //true local minima (ie E may be following a skip edge). - //Also, consecutive horz. edges may start heading left before going right. - if (NextIsForward) - EStart = E->Prev; - else - EStart = E->Next; - if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge - { - if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) - ReverseHorizontal(*E); - } - else if (EStart->Bot.X != E->Bot.X) - ReverseHorizontal(*E); - } - - EStart = E; - if (NextIsForward) - { - while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) - Result = Result->Next; - if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) - { - //nb: at the top of a bound, horizontals are added to the bound - //only when the preceding edge attaches to the horizontal's left vertex - //unless a Skip edge is encountered when that becomes the top divide - Horz = Result; - while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; - if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; - } - while (E != Result) - { - E->NextInLML = E->Next; - if (IsHorizontal(*E) && E != EStart && - E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - E = E->Next; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) - ReverseHorizontal(*E); - Result = Result->Next; //move to the edge just beyond current bound - } else - { - while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) - Result = Result->Prev; - if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) - { - Horz = Result; - while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; - if (Horz->Next->Top.X == Result->Prev->Top.X || - Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; - } - - while (E != Result) - { - E->NextInLML = E->Prev; - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - E = E->Prev; - } - if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) - ReverseHorizontal(*E); - Result = Result->Prev; //move to the edge just beyond current bound - } - - return Result; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) -{ -#ifdef use_lines - if (!Closed && PolyTyp == ptClip) - throw clipperException("AddPath: Open paths must be subject."); -#else - if (!Closed) - throw clipperException("AddPath: Open paths have been disabled."); -#endif - - int highI = (int)pg.size() -1; - if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; - while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; - if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; - - //create a new edge array ... - TEdge *edges = new TEdge [highI +1]; - - bool IsFlat = true; - //1. Basic (first) edge initialization ... - try - { - edges[1].Curr = pg[1]; - RangeTest(pg[0], m_UseFullRange); - RangeTest(pg[highI], m_UseFullRange); - InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); - InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); - for (int i = highI - 1; i >= 1; --i) - { - RangeTest(pg[i], m_UseFullRange); - InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); - } - } - catch(...) - { - delete [] edges; - throw; //range test fails - } - TEdge *eStart = &edges[0]; - - //2. Remove duplicate vertices, and (when closed) collinear edges ... - TEdge *E = eStart, *eLoopStop = eStart; - for (;;) - { - //nb: allows matching start and end points when not Closed ... - if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) - { - if (E == E->Next) break; - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - eLoopStop = E; - continue; - } - if (E->Prev == E->Next) - break; //only two vertices - else if (Closed && - SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && - (!m_PreserveCollinear || - !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) - { - //Collinear edges are allowed for open paths but in closed paths - //the default is to merge adjacent collinear edges into a single edge. - //However, if the PreserveCollinear property is enabled, only overlapping - //collinear edges (ie spikes) will be removed from closed paths. - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - E = E->Prev; - eLoopStop = E; - continue; - } - E = E->Next; - if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; - } - - if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) - { - delete [] edges; - return false; - } - - if (!Closed) - { - m_HasOpenPaths = true; - eStart->Prev->OutIdx = Skip; - } - - //3. Do second stage of edge initialization ... - E = eStart; - do - { - InitEdge2(*E, PolyTyp); - E = E->Next; - if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; - } - while (E != eStart); - - //4. Finally, add edge bounds to LocalMinima list ... - - //Totally flat paths must be handled differently when adding them - //to LocalMinima list to avoid endless loops etc ... - if (IsFlat) - { - if (Closed) - { - delete [] edges; - return false; - } - E->Prev->OutIdx = Skip; - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - locMin.LeftBound = 0; - locMin.RightBound = E; - locMin.RightBound->Side = esRight; - locMin.RightBound->WindDelta = 0; - for (;;) - { - if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); - if (E->Next->OutIdx == Skip) break; - E->NextInLML = E->Next; - E = E->Next; - } - m_MinimaList.push_back(locMin); - m_edges.push_back(edges); - return true; - } - - m_edges.push_back(edges); - bool leftBoundIsForward; - TEdge* EMin = 0; - - //workaround to avoid an endless loop in the while loop below when - //open paths have matching start and end points ... - if (E->Prev->Bot == E->Prev->Top) E = E->Next; - - for (;;) - { - E = FindNextLocMin(E); - if (E == EMin) break; - else if (!EMin) EMin = E; - - //E and E.Prev now share a local minima (left aligned if horizontal). - //Compare their slopes to find which starts which bound ... - MinimaList::value_type locMin; - locMin.Y = E->Bot.Y; - if (E->Dx < E->Prev->Dx) - { - locMin.LeftBound = E->Prev; - locMin.RightBound = E; - leftBoundIsForward = false; //Q.nextInLML = Q.prev - } else - { - locMin.LeftBound = E; - locMin.RightBound = E->Prev; - leftBoundIsForward = true; //Q.nextInLML = Q.next - } - - if (!Closed) locMin.LeftBound->WindDelta = 0; - else if (locMin.LeftBound->Next == locMin.RightBound) - locMin.LeftBound->WindDelta = -1; - else locMin.LeftBound->WindDelta = 1; - locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; - - E = ProcessBound(locMin.LeftBound, leftBoundIsForward); - if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); - - TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); - if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); - - if (locMin.LeftBound->OutIdx == Skip) - locMin.LeftBound = 0; - else if (locMin.RightBound->OutIdx == Skip) - locMin.RightBound = 0; - m_MinimaList.push_back(locMin); - if (!leftBoundIsForward) E = E2; - } - return true; -} -//------------------------------------------------------------------------------ - -bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) -{ - bool result = false; - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (AddPath(ppg[i], PolyTyp, Closed)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Clear() -{ - DisposeLocalMinimaList(); - for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) - { - TEdge* edges = m_edges[i]; - delete [] edges; - } - m_edges.clear(); - m_UseFullRange = false; - m_HasOpenPaths = false; -} -//------------------------------------------------------------------------------ - -void ClipperBase::Reset() -{ - m_CurrentLM = m_MinimaList.begin(); - if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process - std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); - - m_Scanbeam = ScanbeamList(); //clears/resets priority_queue - //reset all edges ... - for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) - { - InsertScanbeam(lm->Y); - TEdge* e = lm->LeftBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esLeft; - e->OutIdx = Unassigned; - } - - e = lm->RightBound; - if (e) - { - e->Curr = e->Bot; - e->Side = esRight; - e->OutIdx = Unassigned; - } - } - m_ActiveEdges = 0; - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeLocalMinimaList() -{ - m_MinimaList.clear(); - m_CurrentLM = m_MinimaList.begin(); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) -{ - if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; - locMin = &(*m_CurrentLM); - ++m_CurrentLM; - return true; -} -//------------------------------------------------------------------------------ - -IntRect ClipperBase::GetBounds() -{ - IntRect result; - MinimaList::iterator lm = m_MinimaList.begin(); - if (lm == m_MinimaList.end()) - { - result.left = result.top = result.right = result.bottom = 0; - return result; - } - result.left = lm->LeftBound->Bot.X; - result.top = lm->LeftBound->Bot.Y; - result.right = lm->LeftBound->Bot.X; - result.bottom = lm->LeftBound->Bot.Y; - while (lm != m_MinimaList.end()) - { - //todo - needs fixing for open paths - result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); - TEdge* e = lm->LeftBound; - for (;;) { - TEdge* bottomE = e; - while (e->NextInLML) - { - if (e->Bot.X < result.left) result.left = e->Bot.X; - if (e->Bot.X > result.right) result.right = e->Bot.X; - e = e->NextInLML; - } - result.left = std::min(result.left, e->Bot.X); - result.right = std::max(result.right, e->Bot.X); - result.left = std::min(result.left, e->Top.X); - result.right = std::max(result.right, e->Top.X); - result.top = std::min(result.top, e->Top.Y); - if (bottomE == lm->LeftBound) e = lm->RightBound; - else break; - } - ++lm; - } - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::InsertScanbeam(const cInt Y) -{ - m_Scanbeam.push(Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::PopScanbeam(cInt &Y) -{ - if (m_Scanbeam.empty()) return false; - Y = m_Scanbeam.top(); - m_Scanbeam.pop(); - while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. - return true; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeAllOutRecs(){ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - DisposeOutRec(i); - m_PolyOuts.clear(); -} -//------------------------------------------------------------------------------ - -void ClipperBase::DisposeOutRec(PolyOutList::size_type index) -{ - OutRec *outRec = m_PolyOuts[index]; - if (outRec->Pts) DisposeOutPts(outRec->Pts); - delete outRec; - m_PolyOuts[index] = 0; -} -//------------------------------------------------------------------------------ - -void ClipperBase::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted - if (AelPrev) AelPrev->NextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if (AelNext) AelNext->PrevInAEL = AelPrev; - e->NextInAEL = 0; - e->PrevInAEL = 0; -} -//------------------------------------------------------------------------------ - -OutRec* ClipperBase::CreateOutRec() -{ - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size() - 1; - return result; -} -//------------------------------------------------------------------------------ - -void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) -{ - //check that one or other edge hasn't already been removed from AEL ... - if (Edge1->NextInAEL == Edge1->PrevInAEL || - Edge2->NextInAEL == Edge2->PrevInAEL) return; - - if (Edge1->NextInAEL == Edge2) - { - TEdge* Next = Edge2->NextInAEL; - if (Next) Next->PrevInAEL = Edge1; - TEdge* Prev = Edge1->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge2; - Edge2->PrevInAEL = Prev; - Edge2->NextInAEL = Edge1; - Edge1->PrevInAEL = Edge2; - Edge1->NextInAEL = Next; - } - else if (Edge2->NextInAEL == Edge1) - { - TEdge* Next = Edge1->NextInAEL; - if (Next) Next->PrevInAEL = Edge2; - TEdge* Prev = Edge2->PrevInAEL; - if (Prev) Prev->NextInAEL = Edge1; - Edge1->PrevInAEL = Prev; - Edge1->NextInAEL = Edge2; - Edge2->PrevInAEL = Edge1; - Edge2->NextInAEL = Next; - } - else - { - TEdge* Next = Edge1->NextInAEL; - TEdge* Prev = Edge1->PrevInAEL; - Edge1->NextInAEL = Edge2->NextInAEL; - if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; - Edge1->PrevInAEL = Edge2->PrevInAEL; - if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; - Edge2->NextInAEL = Next; - if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; - Edge2->PrevInAEL = Prev; - if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; - } - - if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; - else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; -} -//------------------------------------------------------------------------------ - -void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) -{ - if (!e->NextInLML) - throw clipperException("UpdateEdgeIntoAEL: invalid call"); - - e->NextInLML->OutIdx = e->OutIdx; - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (AelPrev) AelPrev->NextInAEL = e->NextInLML; - else m_ActiveEdges = e->NextInLML; - if (AelNext) AelNext->PrevInAEL = e->NextInLML; - e->NextInLML->Side = e->Side; - e->NextInLML->WindDelta = e->WindDelta; - e->NextInLML->WindCnt = e->WindCnt; - e->NextInLML->WindCnt2 = e->WindCnt2; - e = e->NextInLML; - e->Curr = e->Bot; - e->PrevInAEL = AelPrev; - e->NextInAEL = AelNext; - if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); -} -//------------------------------------------------------------------------------ - -bool ClipperBase::LocalMinimaPending() -{ - return (m_CurrentLM != m_MinimaList.end()); -} - -//------------------------------------------------------------------------------ -// TClipper methods ... -//------------------------------------------------------------------------------ - -Clipper::Clipper(int initOptions) : ClipperBase() //constructor -{ - m_ExecuteLocked = false; - m_UseFullRange = false; - m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); - m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); - m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); - m_HasOpenPaths = false; -#ifdef use_xyz - m_ZFill = 0; -#endif -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::ZFillFunction(ZFillCallback zFillFunc) -{ - m_ZFill = zFillFunc; -} -//------------------------------------------------------------------------------ -#endif - -bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) -{ - return Execute(clipType, solution, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType) -{ - return Execute(clipType, polytree, fillType, fillType); -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, Paths &solution, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - if (m_HasOpenPaths) - throw clipperException("Error: PolyTree struct is needed for open path clipping."); - m_ExecuteLocked = true; - solution.resize(0); - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = false; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult(solution); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -bool Clipper::Execute(ClipType clipType, PolyTree& polytree, - PolyFillType subjFillType, PolyFillType clipFillType) -{ - if( m_ExecuteLocked ) return false; - m_ExecuteLocked = true; - m_SubjFillType = subjFillType; - m_ClipFillType = clipFillType; - m_ClipType = clipType; - m_UsingPolyTree = true; - bool succeeded = ExecuteInternal(); - if (succeeded) BuildResult2(polytree); - DisposeAllOutRecs(); - m_ExecuteLocked = false; - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::FixHoleLinkage(OutRec &outrec) -{ - //skip OutRecs that (a) contain outermost polygons or - //(b) already have the correct owner/child linkage ... - if (!outrec.FirstLeft || - (outrec.IsHole != outrec.FirstLeft->IsHole && - outrec.FirstLeft->Pts)) return; - - OutRec* orfl = outrec.FirstLeft; - while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) - orfl = orfl->FirstLeft; - outrec.FirstLeft = orfl; -} -//------------------------------------------------------------------------------ - -bool Clipper::ExecuteInternal() -{ - bool succeeded = true; - try { - Reset(); - m_Maxima = MaximaList(); - m_SortedEdges = 0; - - succeeded = true; - cInt botY, topY; - if (!PopScanbeam(botY)) return false; - InsertLocalMinimaIntoAEL(botY); - while (PopScanbeam(topY) || LocalMinimaPending()) - { - ProcessHorizontals(); - ClearGhostJoins(); - if (!ProcessIntersections(topY)) - { - succeeded = false; - break; - } - ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - InsertLocalMinimaIntoAEL(botY); - } - } - catch(...) - { - succeeded = false; - } - - if (succeeded) - { - //fix orientations ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts || outRec->IsOpen) continue; - if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) - ReversePolyPtLinks(outRec->Pts); - } - - if (!m_Joins.empty()) JoinCommonEdges(); - - //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec *outRec = m_PolyOuts[i]; - if (!outRec->Pts) continue; - if (outRec->IsOpen) - FixupOutPolyline(*outRec); - else - FixupOutPolygon(*outRec); - } - - if (m_StrictSimple) DoSimplePolygons(); - } - - ClearJoins(); - ClearGhostJoins(); - return succeeded; -} -//------------------------------------------------------------------------------ - -void Clipper::SetWindingCount(TEdge &edge) -{ - TEdge *e = edge.PrevInAEL; - //find the edge of the same polytype that immediately preceeds 'edge' in AEL - while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; - if (!e) - { - if (edge.WindDelta == 0) - { - PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); - edge.WindCnt = (pft == pftNegative ? -1 : 1); - } - else - edge.WindCnt = edge.WindDelta; - edge.WindCnt2 = 0; - e = m_ActiveEdges; //ie get ready to calc WindCnt2 - } - else if (edge.WindDelta == 0 && m_ClipType != ctUnion) - { - edge.WindCnt = 1; - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else if (IsEvenOddFillType(edge)) - { - //EvenOdd filling ... - if (edge.WindDelta == 0) - { - //are we inside a subj polygon ... - bool Inside = true; - TEdge *e2 = e->PrevInAEL; - while (e2) - { - if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) - Inside = !Inside; - e2 = e2->PrevInAEL; - } - edge.WindCnt = (Inside ? 0 : 1); - } - else - { - edge.WindCnt = edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - else - { - //nonZero, Positive or Negative filling ... - if (e->WindCnt * e->WindDelta < 0) - { - //prev edge is 'decreasing' WindCount (WC) toward zero - //so we're outside the previous polygon ... - if (Abs(e->WindCnt) > 1) - { - //outside prev poly but still inside another. - //when reversing direction of prev poly use the same WC - if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise continue to 'decrease' WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - else - //now outside all polys of same polytype so set own WC ... - edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); - } else - { - //prev edge is 'increasing' WindCount (WC) away from zero - //so we're inside the previous polygon ... - if (edge.WindDelta == 0) - edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); - //if wind direction is reversing prev then use same WC - else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; - //otherwise add to WC ... - else edge.WindCnt = e->WindCnt + edge.WindDelta; - } - edge.WindCnt2 = e->WindCnt2; - e = e->NextInAEL; //ie get ready to calc WindCnt2 - } - - //update WindCnt2 ... - if (IsEvenOddAltFillType(edge)) - { - //EvenOdd filling ... - while (e != &edge) - { - if (e->WindDelta != 0) - edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); - e = e->NextInAEL; - } - } else - { - //nonZero, Positive or Negative filling ... - while ( e != &edge ) - { - edge.WindCnt2 += e->WindDelta; - e = e->NextInAEL; - } - } -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_SubjFillType == pftEvenOdd; else - return m_ClipFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const -{ - if (edge.PolyTyp == ptSubject) - return m_ClipFillType == pftEvenOdd; else - return m_SubjFillType == pftEvenOdd; -} -//------------------------------------------------------------------------------ - -bool Clipper::IsContributing(const TEdge& edge) const -{ - PolyFillType pft, pft2; - if (edge.PolyTyp == ptSubject) - { - pft = m_SubjFillType; - pft2 = m_ClipFillType; - } else - { - pft = m_ClipFillType; - pft2 = m_SubjFillType; - } - - switch(pft) - { - case pftEvenOdd: - //return false if a subj line has been flagged as inside a subj polygon - if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; - break; - case pftNonZero: - if (Abs(edge.WindCnt) != 1) return false; - break; - case pftPositive: - if (edge.WindCnt != 1) return false; - break; - default: //pftNegative - if (edge.WindCnt != -1) return false; - } - - switch(m_ClipType) - { - case ctIntersection: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctUnion: - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - break; - case ctDifference: - if (edge.PolyTyp == ptSubject) - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 != 0); - case pftPositive: - return (edge.WindCnt2 > 0); - default: - return (edge.WindCnt2 < 0); - } - break; - case ctXor: - if (edge.WindDelta == 0) //XOr always contributing unless open - switch(pft2) - { - case pftEvenOdd: - case pftNonZero: - return (edge.WindCnt2 == 0); - case pftPositive: - return (edge.WindCnt2 <= 0); - default: - return (edge.WindCnt2 >= 0); - } - else - return true; - break; - default: - return true; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - OutPt* result; - TEdge *e, *prevE; - if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) - { - result = AddOutPt(e1, Pt); - e2->OutIdx = e1->OutIdx; - e1->Side = esLeft; - e2->Side = esRight; - e = e1; - if (e->PrevInAEL == e2) - prevE = e2->PrevInAEL; - else - prevE = e->PrevInAEL; - } else - { - result = AddOutPt(e2, Pt); - e1->OutIdx = e2->OutIdx; - e1->Side = esRight; - e2->Side = esLeft; - e = e2; - if (e->PrevInAEL == e1) - prevE = e1->PrevInAEL; - else - prevE = e->PrevInAEL; - } - - if (prevE && prevE->OutIdx >= 0) - { - cInt xPrev = TopX(*prevE, Pt.Y); - cInt xE = TopX(*e, Pt.Y); - if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && - SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) - { - OutPt* outPt = AddOutPt(prevE, Pt); - AddJoin(result, outPt, e->Top); - } - } - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - AddOutPt( e1, Pt ); - if (e2->WindDelta == 0) AddOutPt(e2, Pt); - if( e1->OutIdx == e2->OutIdx ) - { - e1->OutIdx = Unassigned; - e2->OutIdx = Unassigned; - } - else if (e1->OutIdx < e2->OutIdx) - AppendPolygon(e1, e2); - else - AppendPolygon(e2, e1); -} -//------------------------------------------------------------------------------ - -void Clipper::AddEdgeToSEL(TEdge *edge) -{ - //SEL pointers in PEdge are reused to build a list of horizontal edges. - //However, we don't need to worry about order with horizontal edge processing. - if( !m_SortedEdges ) - { - m_SortedEdges = edge; - edge->PrevInSEL = 0; - edge->NextInSEL = 0; - } - else - { - edge->NextInSEL = m_SortedEdges; - edge->PrevInSEL = 0; - m_SortedEdges->PrevInSEL = edge; - m_SortedEdges = edge; - } -} -//------------------------------------------------------------------------------ - -bool Clipper::PopEdgeFromSEL(TEdge *&edge) -{ - if (!m_SortedEdges) return false; - edge = m_SortedEdges; - DeleteFromSEL(m_SortedEdges); - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::CopyAELToSEL() -{ - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while ( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op1; - j->OutPt2 = op2; - j->OffPt = OffPt; - m_Joins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearJoins() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - delete m_Joins[i]; - m_Joins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::ClearGhostJoins() -{ - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) - delete m_GhostJoins[i]; - m_GhostJoins.resize(0); -} -//------------------------------------------------------------------------------ - -void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) -{ - Join* j = new Join; - j->OutPt1 = op; - j->OutPt2 = 0; - j->OffPt = OffPt; - m_GhostJoins.push_back(j); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) -{ - const LocalMinimum *lm; - while (PopLocalMinima(botY, lm)) - { - TEdge* lb = lm->LeftBound; - TEdge* rb = lm->RightBound; - - OutPt *Op1 = 0; - if (!lb) - { - //nb: don't insert LB into either AEL or SEL - InsertEdgeIntoAEL(rb, 0); - SetWindingCount(*rb); - if (IsContributing(*rb)) - Op1 = AddOutPt(rb, rb->Bot); - } - else if (!rb) - { - InsertEdgeIntoAEL(lb, 0); - SetWindingCount(*lb); - if (IsContributing(*lb)) - Op1 = AddOutPt(lb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - else - { - InsertEdgeIntoAEL(lb, 0); - InsertEdgeIntoAEL(rb, lb); - SetWindingCount( *lb ); - rb->WindCnt = lb->WindCnt; - rb->WindCnt2 = lb->WindCnt2; - if (IsContributing(*lb)) - Op1 = AddLocalMinPoly(lb, rb, lb->Bot); - InsertScanbeam(lb->Top.Y); - } - - if (rb) - { - if (IsHorizontal(*rb)) - { - AddEdgeToSEL(rb); - if (rb->NextInLML) - InsertScanbeam(rb->NextInLML->Top.Y); - } - else InsertScanbeam( rb->Top.Y ); - } - - if (!lb || !rb) continue; - - //if any output polygons share an edge, they'll need joining later ... - if (Op1 && IsHorizontal(*rb) && - m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) - { - for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) - { - Join* jr = m_GhostJoins[i]; - //if the horizontal Rb and a 'ghost' horizontal overlap, then convert - //the 'ghost' join to a real join ready for later ... - if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X)) - AddJoin(jr->OutPt1, Op1, jr->OffPt); - } - } - - if (lb->OutIdx >= 0 && lb->PrevInAEL && - lb->PrevInAEL->Curr.X == lb->Bot.X && - lb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && - (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); - AddJoin(Op1, Op2, lb->Top); - } - - if(lb->NextInAEL != rb) - { - - if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && - (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) - { - OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); - AddJoin(Op1, Op2, rb->Top); - } - - TEdge* e = lb->NextInAEL; - if (e) - { - while( e != rb ) - { - //nb: For calculating winding counts etc, IntersectEdges() assumes - //that param1 will be to the Right of param2 ABOVE the intersection ... - IntersectEdges(rb , e , lb->Curr); //order important here - e = e->NextInAEL; - } - } - } - - } -} -//------------------------------------------------------------------------------ - -void Clipper::DeleteFromSEL(TEdge *e) -{ - TEdge* SelPrev = e->PrevInSEL; - TEdge* SelNext = e->NextInSEL; - if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted - if( SelPrev ) SelPrev->NextInSEL = SelNext; - else m_SortedEdges = SelNext; - if( SelNext ) SelNext->PrevInSEL = SelPrev; - e->NextInSEL = 0; - e->PrevInSEL = 0; -} -//------------------------------------------------------------------------------ - -#ifdef use_xyz -void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) -{ - if (pt.Z != 0 || !m_ZFill) return; - else if (pt == e1.Bot) pt.Z = e1.Bot.Z; - else if (pt == e1.Top) pt.Z = e1.Top.Z; - else if (pt == e2.Bot) pt.Z = e2.Bot.Z; - else if (pt == e2.Top) pt.Z = e2.Top.Z; - else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); -} -//------------------------------------------------------------------------------ -#endif - -void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) -{ - bool e1Contributing = ( e1->OutIdx >= 0 ); - bool e2Contributing = ( e2->OutIdx >= 0 ); - -#ifdef use_xyz - SetZ(Pt, *e1, *e2); -#endif - -#ifdef use_lines - //if either edge is on an OPEN path ... - if (e1->WindDelta == 0 || e2->WindDelta == 0) - { - //ignore subject-subject open path intersections UNLESS they - //are both open paths, AND they are both 'contributing maximas' ... - if (e1->WindDelta == 0 && e2->WindDelta == 0) return; - - //if intersecting a subj line with a subj poly ... - else if (e1->PolyTyp == e2->PolyTyp && - e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) - { - if (e1->WindDelta == 0) - { - if (e2Contributing) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - } - else - { - if (e1Contributing) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - } - else if (e1->PolyTyp != e2->PolyTyp) - { - //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... - if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && - (m_ClipType != ctUnion || e2->WindCnt2 == 0)) - { - AddOutPt(e1, Pt); - if (e1Contributing) e1->OutIdx = Unassigned; - } - else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && - (m_ClipType != ctUnion || e1->WindCnt2 == 0)) - { - AddOutPt(e2, Pt); - if (e2Contributing) e2->OutIdx = Unassigned; - } - } - return; - } -#endif - - //update winding counts... - //assumes that e1 will be to the Right of e2 ABOVE the intersection - if ( e1->PolyTyp == e2->PolyTyp ) - { - if ( IsEvenOddFillType( *e1) ) - { - int oldE1WindCnt = e1->WindCnt; - e1->WindCnt = e2->WindCnt; - e2->WindCnt = oldE1WindCnt; - } else - { - if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; - else e1->WindCnt += e2->WindDelta; - if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; - else e2->WindCnt -= e1->WindDelta; - } - } else - { - if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; - else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; - if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; - else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; - } - - PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; - if (e1->PolyTyp == ptSubject) - { - e1FillType = m_SubjFillType; - e1FillType2 = m_ClipFillType; - } else - { - e1FillType = m_ClipFillType; - e1FillType2 = m_SubjFillType; - } - if (e2->PolyTyp == ptSubject) - { - e2FillType = m_SubjFillType; - e2FillType2 = m_ClipFillType; - } else - { - e2FillType = m_ClipFillType; - e2FillType2 = m_SubjFillType; - } - - cInt e1Wc, e2Wc; - switch (e1FillType) - { - case pftPositive: e1Wc = e1->WindCnt; break; - case pftNegative: e1Wc = -e1->WindCnt; break; - default: e1Wc = Abs(e1->WindCnt); - } - switch(e2FillType) - { - case pftPositive: e2Wc = e2->WindCnt; break; - case pftNegative: e2Wc = -e2->WindCnt; break; - default: e2Wc = Abs(e2->WindCnt); - } - - if ( e1Contributing && e2Contributing ) - { - if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) - { - AddLocalMaxPoly(e1, e2, Pt); - } - else - { - AddOutPt(e1, Pt); - AddOutPt(e2, Pt); - SwapSides( *e1 , *e2 ); - SwapPolyIndexes( *e1 , *e2 ); - } - } - else if ( e1Contributing ) - { - if (e2Wc == 0 || e2Wc == 1) - { - AddOutPt(e1, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( e2Contributing ) - { - if (e1Wc == 0 || e1Wc == 1) - { - AddOutPt(e2, Pt); - SwapSides(*e1, *e2); - SwapPolyIndexes(*e1, *e2); - } - } - else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) - { - //neither edge is currently contributing ... - - cInt e1Wc2, e2Wc2; - switch (e1FillType2) - { - case pftPositive: e1Wc2 = e1->WindCnt2; break; - case pftNegative : e1Wc2 = -e1->WindCnt2; break; - default: e1Wc2 = Abs(e1->WindCnt2); - } - switch (e2FillType2) - { - case pftPositive: e2Wc2 = e2->WindCnt2; break; - case pftNegative: e2Wc2 = -e2->WindCnt2; break; - default: e2Wc2 = Abs(e2->WindCnt2); - } - - if (e1->PolyTyp != e2->PolyTyp) - { - AddLocalMinPoly(e1, e2, Pt); - } - else if (e1Wc == 1 && e2Wc == 1) - switch( m_ClipType ) { - case ctIntersection: - if (e1Wc2 > 0 && e2Wc2 > 0) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctUnion: - if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctDifference: - if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - AddLocalMinPoly(e1, e2, Pt); - break; - case ctXor: - AddLocalMinPoly(e1, e2, Pt); - } - else - SwapSides( *e1, *e2 ); - } -} -//------------------------------------------------------------------------------ - -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) -{ - TEdge *e2 = e->PrevInAEL; - TEdge *eTmp = 0; - while (e2) - { - if (e2->OutIdx >= 0 && e2->WindDelta != 0) - { - if (!eTmp) eTmp = e2; - else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; - } - e2 = e2->PrevInAEL; - } - if (!eTmp) - { - outrec->FirstLeft = 0; - outrec->IsHole = false; - } - else - { - outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; - outrec->IsHole = !outrec->FirstLeft->IsHole; - } -} -//------------------------------------------------------------------------------ - -OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) -{ - //work out which polygon fragment has the correct hole state ... - if (!outRec1->BottomPt) - outRec1->BottomPt = GetBottomPt(outRec1->Pts); - if (!outRec2->BottomPt) - outRec2->BottomPt = GetBottomPt(outRec2->Pts); - OutPt *OutPt1 = outRec1->BottomPt; - OutPt *OutPt2 = outRec2->BottomPt; - if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; - else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; - else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; - else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; - else if (OutPt1->Next == OutPt1) return outRec2; - else if (OutPt2->Next == OutPt2) return outRec1; - else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; - else return outRec2; -} -//------------------------------------------------------------------------------ - -bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) -{ - do - { - outRec1 = outRec1->FirstLeft; - if (outRec1 == outRec2) return true; - } while (outRec1); - return false; -} -//------------------------------------------------------------------------------ - -OutRec* Clipper::GetOutRec(int Idx) -{ - OutRec* outrec = m_PolyOuts[Idx]; - while (outrec != m_PolyOuts[outrec->Idx]) - outrec = m_PolyOuts[outrec->Idx]; - return outrec; -} -//------------------------------------------------------------------------------ - -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) -{ - //get the start and ends of both output polygons ... - OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; - OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; - - OutRec *holeStateRec; - if (OutRec1RightOfOutRec2(outRec1, outRec2)) - holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) - holeStateRec = outRec1; - else - holeStateRec = GetLowermostRec(outRec1, outRec2); - - //get the start and ends of both output polygons and - //join e2 poly onto e1 poly and delete pointers to e2 ... - - OutPt* p1_lft = outRec1->Pts; - OutPt* p1_rt = p1_lft->Prev; - OutPt* p2_lft = outRec2->Pts; - OutPt* p2_rt = p2_lft->Prev; - - //join e2 poly onto e1 poly and delete pointers to e2 ... - if( e1->Side == esLeft ) - { - if( e2->Side == esLeft ) - { - //z y x a b c - ReversePolyPtLinks(p2_lft); - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - outRec1->Pts = p2_rt; - } else - { - //x y z a b c - p2_rt->Next = p1_lft; - p1_lft->Prev = p2_rt; - p2_lft->Prev = p1_rt; - p1_rt->Next = p2_lft; - outRec1->Pts = p2_lft; - } - } else - { - if( e2->Side == esRight ) - { - //a b c z y x - ReversePolyPtLinks(p2_lft); - p1_rt->Next = p2_rt; - p2_rt->Prev = p1_rt; - p2_lft->Next = p1_lft; - p1_lft->Prev = p2_lft; - } else - { - //a b c x y z - p1_rt->Next = p2_lft; - p2_lft->Prev = p1_rt; - p1_lft->Prev = p2_rt; - p2_rt->Next = p1_lft; - } - } - - outRec1->BottomPt = 0; - if (holeStateRec == outRec2) - { - if (outRec2->FirstLeft != outRec1) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec1->IsHole = outRec2->IsHole; - } - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->FirstLeft = outRec1; - - int OKIdx = e1->OutIdx; - int ObsoleteIdx = e2->OutIdx; - - e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly - e2->OutIdx = Unassigned; - - TEdge* e = m_ActiveEdges; - while( e ) - { - if( e->OutIdx == ObsoleteIdx ) - { - e->OutIdx = OKIdx; - e->Side = e1->Side; - break; - } - e = e->NextInAEL; - } - - outRec2->Idx = outRec1->Idx; -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) -{ - if( e->OutIdx < 0 ) - { - OutRec *outRec = CreateOutRec(); - outRec->IsOpen = (e->WindDelta == 0); - OutPt* newOp = new OutPt; - outRec->Pts = newOp; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = newOp; - newOp->Prev = newOp; - if (!outRec->IsOpen) - SetHoleState(e, outRec); - e->OutIdx = outRec->Idx; - return newOp; - } else - { - OutRec *outRec = m_PolyOuts[e->OutIdx]; - //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' - OutPt* op = outRec->Pts; - - bool ToFront = (e->Side == esLeft); - if (ToFront && (pt == op->Pt)) return op; - else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; - - OutPt* newOp = new OutPt; - newOp->Idx = outRec->Idx; - newOp->Pt = pt; - newOp->Next = op; - newOp->Prev = op->Prev; - newOp->Prev->Next = newOp; - op->Prev = newOp; - if (ToFront) outRec->Pts = newOp; - return newOp; - } -} -//------------------------------------------------------------------------------ - -OutPt* Clipper::GetLastOutPt(TEdge *e) -{ - OutRec *outRec = m_PolyOuts[e->OutIdx]; - if (e->Side == esLeft) - return outRec->Pts; - else - return outRec->Pts->Prev; -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessHorizontals() -{ - TEdge* horzEdge; - while (PopEdgeFromSEL(horzEdge)) - ProcessHorizontal(horzEdge); -} -//------------------------------------------------------------------------------ - -inline bool IsMinima(TEdge *e) -{ - return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); -} -//------------------------------------------------------------------------------ - -inline bool IsMaxima(TEdge *e, const cInt Y) -{ - return e && e->Top.Y == Y && !e->NextInLML; -} -//------------------------------------------------------------------------------ - -inline bool IsIntermediate(TEdge *e, const cInt Y) -{ - return e->Top.Y == Y && e->NextInLML; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPair(TEdge *e) -{ - if ((e->Next->Top == e->Top) && !e->Next->NextInLML) - return e->Next; - else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) - return e->Prev; - else return 0; -} -//------------------------------------------------------------------------------ - -TEdge *GetMaximaPairEx(TEdge *e) -{ - //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) - TEdge* result = GetMaximaPair(e); - if (result && (result->OutIdx == Skip || - (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) -{ - if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; - if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; - - if( Edge1->NextInSEL == Edge2 ) - { - TEdge* Next = Edge2->NextInSEL; - if( Next ) Next->PrevInSEL = Edge1; - TEdge* Prev = Edge1->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge2; - Edge2->PrevInSEL = Prev; - Edge2->NextInSEL = Edge1; - Edge1->PrevInSEL = Edge2; - Edge1->NextInSEL = Next; - } - else if( Edge2->NextInSEL == Edge1 ) - { - TEdge* Next = Edge1->NextInSEL; - if( Next ) Next->PrevInSEL = Edge2; - TEdge* Prev = Edge2->PrevInSEL; - if( Prev ) Prev->NextInSEL = Edge1; - Edge1->PrevInSEL = Prev; - Edge1->NextInSEL = Edge2; - Edge2->PrevInSEL = Edge1; - Edge2->NextInSEL = Next; - } - else - { - TEdge* Next = Edge1->NextInSEL; - TEdge* Prev = Edge1->PrevInSEL; - Edge1->NextInSEL = Edge2->NextInSEL; - if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; - Edge1->PrevInSEL = Edge2->PrevInSEL; - if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; - Edge2->NextInSEL = Next; - if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; - Edge2->PrevInSEL = Prev; - if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; - } - - if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; - else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; -} -//------------------------------------------------------------------------------ - -TEdge* GetNextInAEL(TEdge *e, Direction dir) -{ - return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; -} -//------------------------------------------------------------------------------ - -void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) -{ - if (HorzEdge.Bot.X < HorzEdge.Top.X) - { - Left = HorzEdge.Bot.X; - Right = HorzEdge.Top.X; - Dir = dLeftToRight; - } else - { - Left = HorzEdge.Top.X; - Right = HorzEdge.Bot.X; - Dir = dRightToLeft; - } -} -//------------------------------------------------------------------------ - -/******************************************************************************* -* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * -* Bottom of a scanbeam) are processed as if layered. The order in which HEs * -* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * -* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * -* and with other non-horizontal edges [*]. Once these intersections are * -* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * -* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * -*******************************************************************************/ - -void Clipper::ProcessHorizontal(TEdge *horzEdge) -{ - Direction dir; - cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->WindDelta == 0); - - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - TEdge* eLastHorz = horzEdge, *eMaxPair = 0; - while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) - eLastHorz = eLastHorz->NextInLML; - if (!eLastHorz->NextInLML) - eMaxPair = GetMaximaPair(eLastHorz); - - MaximaList::const_iterator maxIt; - MaximaList::const_reverse_iterator maxRit; - if (m_Maxima.size() > 0) - { - //get the first maxima in range (X) ... - if (dir == dLeftToRight) - { - maxIt = m_Maxima.begin(); - while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; - if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) - maxIt = m_Maxima.end(); - } - else - { - maxRit = m_Maxima.rbegin(); - while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; - if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) - maxRit = m_Maxima.rend(); - } - } - - OutPt* op1 = 0; - - for (;;) //loop through consec. horizontal edges - { - - bool IsLastHorz = (horzEdge == eLastHorz); - TEdge* e = GetNextInAEL(horzEdge, dir); - while(e) - { - - //this code block inserts extra coords into horizontal edges (in output - //polygons) whereever maxima touch these horizontal edges. This helps - //'simplifying' polygons (ie if the Simplify property is set). - if (m_Maxima.size() > 0) - { - if (dir == dLeftToRight) - { - while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); - maxIt++; - } - } - else - { - while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) - { - if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); - maxRit++; - } - } - }; - - if ((dir == dLeftToRight && e->Curr.X > horzRight) || - (dir == dRightToLeft && e->Curr.X < horzLeft)) break; - - //Also break if we've got to the end of an intermediate horizontal edge ... - //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && - e->Dx < horzEdge->NextInLML->Dx) break; - - if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times - { - op1 = AddOutPt(horzEdge, e->Curr); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Bot); - } - - //OK, so far we're still in range of the horizontal Edge but make sure - //we're at the last of consec. horizontals when matching with eMaxPair - if(e == eMaxPair && IsLastHorz) - { - if (horzEdge->OutIdx >= 0) - AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); - DeleteFromAEL(horzEdge); - DeleteFromAEL(eMaxPair); - return; - } - - if(dir == dLeftToRight) - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges(horzEdge, e, Pt); - } - else - { - IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); - IntersectEdges( e, horzEdge, Pt); - } - TEdge* eNext = GetNextInAEL(e, dir); - SwapPositionsInAEL( horzEdge, e ); - e = eNext; - } //end while(e) - - //Break out of loop if HorzEdge.NextInLML is not also horizontal ... - if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; - - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); - GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); - - } //end for (;;) - - if (horzEdge->OutIdx >= 0 && !op1) - { - op1 = GetLastOutPt(horzEdge); - TEdge* eNextHorz = m_SortedEdges; - while (eNextHorz) - { - if (eNextHorz->OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge->Bot.X, - horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) - { - OutPt* op2 = GetLastOutPt(eNextHorz); - AddJoin(op2, op1, eNextHorz->Top); - } - eNextHorz = eNextHorz->NextInSEL; - } - AddGhostJoin(op1, horzEdge->Top); - } - - if (horzEdge->NextInLML) - { - if(horzEdge->OutIdx >= 0) - { - op1 = AddOutPt( horzEdge, horzEdge->Top); - UpdateEdgeIntoAEL(horzEdge); - if (horzEdge->WindDelta == 0) return; - //nb: HorzEdge is no longer horizontal here - TEdge* ePrev = horzEdge->PrevInAEL; - TEdge* eNext = horzEdge->NextInAEL; - if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && - ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && - (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) - { - OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - else if (eNext && eNext->Curr.X == horzEdge->Bot.X && - eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) - { - OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); - AddJoin(op1, op2, horzEdge->Top); - } - } - else - UpdateEdgeIntoAEL(horzEdge); - } - else - { - if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); - DeleteFromAEL(horzEdge); - } -} -//------------------------------------------------------------------------------ - -bool Clipper::ProcessIntersections(const cInt topY) -{ - if( !m_ActiveEdges ) return true; - try { - BuildIntersectList(topY); - size_t IlSize = m_IntersectList.size(); - if (IlSize == 0) return true; - if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); - else return false; - } - catch(...) - { - m_SortedEdges = 0; - DisposeIntersectNodes(); - throw clipperException("ProcessIntersections error"); - } - m_SortedEdges = 0; - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeIntersectNodes() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i ) - delete m_IntersectList[i]; - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::BuildIntersectList(const cInt topY) -{ - if ( !m_ActiveEdges ) return; - - //prepare for sorting ... - TEdge* e = m_ActiveEdges; - m_SortedEdges = e; - while( e ) - { - e->PrevInSEL = e->PrevInAEL; - e->NextInSEL = e->NextInAEL; - e->Curr.X = TopX( *e, topY ); - e = e->NextInAEL; - } - - //bubblesort ... - bool isModified; - do - { - isModified = false; - e = m_SortedEdges; - while( e->NextInSEL ) - { - TEdge *eNext = e->NextInSEL; - IntPoint Pt; - if(e->Curr.X > eNext->Curr.X) - { - IntersectPoint(*e, *eNext, Pt); - if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); - IntersectNode * newNode = new IntersectNode; - newNode->Edge1 = e; - newNode->Edge2 = eNext; - newNode->Pt = Pt; - m_IntersectList.push_back(newNode); - - SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - e = eNext; - } - if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; - else break; - } - while ( isModified ); - m_SortedEdges = 0; //important -} -//------------------------------------------------------------------------------ - - -void Clipper::ProcessIntersectList() -{ - for (size_t i = 0; i < m_IntersectList.size(); ++i) - { - IntersectNode* iNode = m_IntersectList[i]; - { - IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt); - SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); - } - delete iNode; - } - m_IntersectList.clear(); -} -//------------------------------------------------------------------------------ - -bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) -{ - return node2->Pt.Y < node1->Pt.Y; -} -//------------------------------------------------------------------------------ - -inline bool EdgesAdjacent(const IntersectNode &inode) -{ - return (inode.Edge1->NextInSEL == inode.Edge2) || - (inode.Edge1->PrevInSEL == inode.Edge2); -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - //pre-condition: intersections are sorted Bottom-most first. - //Now it's crucial that intersections are made only between adjacent edges, - //so to ensure this the order of intersections may need adjusting ... - CopyAELToSEL(); - std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); - size_t cnt = m_IntersectList.size(); - for (size_t i = 0; i < cnt; ++i) - { - if (!EdgesAdjacent(*m_IntersectList[i])) - { - size_t j = i + 1; - while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; - if (j == cnt) return false; - std::swap(m_IntersectList[i], m_IntersectList[j]); - } - SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); - } - return true; -} -//------------------------------------------------------------------------------ - -void Clipper::DoMaxima(TEdge *e) -{ - TEdge* eMaxPair = GetMaximaPairEx(e); - if (!eMaxPair) - { - if (e->OutIdx >= 0) - AddOutPt(e, e->Top); - DeleteFromAEL(e); - return; - } - - TEdge* eNext = e->NextInAEL; - while(eNext && eNext != eMaxPair) - { - IntersectEdges(e, eNext, e->Top); - SwapPositionsInAEL(e, eNext); - eNext = e->NextInAEL; - } - - if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) - { - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } - else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) - { - if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); - DeleteFromAEL(e); - DeleteFromAEL(eMaxPair); - } -#ifdef use_lines - else if (e->WindDelta == 0) - { - if (e->OutIdx >= 0) - { - AddOutPt(e, e->Top); - e->OutIdx = Unassigned; - } - DeleteFromAEL(e); - - if (eMaxPair->OutIdx >= 0) - { - AddOutPt(eMaxPair, e->Top); - eMaxPair->OutIdx = Unassigned; - } - DeleteFromAEL(eMaxPair); - } -#endif - else throw clipperException("DoMaxima error"); -} -//------------------------------------------------------------------------------ - -void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) -{ - TEdge* e = m_ActiveEdges; - while( e ) - { - //1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - bool IsMaximaEdge = IsMaxima(e, topY); - - if(IsMaximaEdge) - { - TEdge* eMaxPair = GetMaximaPairEx(e); - IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); - } - - if(IsMaximaEdge) - { - if (m_StrictSimple) m_Maxima.push_back(e->Top.X); - TEdge* ePrev = e->PrevInAEL; - DoMaxima(e); - if( !ePrev ) e = m_ActiveEdges; - else e = ePrev->NextInAEL; - } - else - { - //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... - if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) - { - UpdateEdgeIntoAEL(e); - if (e->OutIdx >= 0) - AddOutPt(e, e->Bot); - AddEdgeToSEL(e); - } - else - { - e->Curr.X = TopX( *e, topY ); - e->Curr.Y = topY; - } - - //When StrictlySimple and 'e' is being touched by another edge, then - //make sure both edges have a vertex here ... - if (m_StrictSimple) - { - TEdge* ePrev = e->PrevInAEL; - if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && - (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) - { - IntPoint pt = e->Curr; -#ifdef use_xyz - SetZ(pt, *ePrev, *e); -#endif - OutPt* op = AddOutPt(ePrev, pt); - OutPt* op2 = AddOutPt(e, pt); - AddJoin(op, op2, pt); //StrictlySimple (type-3) join - } - } - - e = e->NextInAEL; - } - } - - //3. Process horizontals at the Top of the scanbeam ... - m_Maxima.sort(); - ProcessHorizontals(); - m_Maxima.clear(); - - //4. Promote intermediate vertices ... - e = m_ActiveEdges; - while(e) - { - if(IsIntermediate(e, topY)) - { - OutPt* op = 0; - if( e->OutIdx >= 0 ) - op = AddOutPt(e, e->Top); - UpdateEdgeIntoAEL(e); - - //if output polygons share an edge, they'll need joining later ... - TEdge* ePrev = e->PrevInAEL; - TEdge* eNext = e->NextInAEL; - if (ePrev && ePrev->Curr.X == e->Bot.X && - ePrev->Curr.Y == e->Bot.Y && op && - ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && - (e->WindDelta != 0) && (ePrev->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(ePrev, e->Bot); - AddJoin(op, op2, e->Top); - } - else if (eNext && eNext->Curr.X == e->Bot.X && - eNext->Curr.Y == e->Bot.Y && op && - eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && - (e->WindDelta != 0) && (eNext->WindDelta != 0)) - { - OutPt* op2 = AddOutPt(eNext, e->Bot); - AddJoin(op, op2, e->Top); - } - } - e = e->NextInAEL; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolyline(OutRec &outrec) -{ - OutPt *pp = outrec.Pts; - OutPt *lastPP = pp->Prev; - while (pp != lastPP) - { - pp = pp->Next; - if (pp->Pt == pp->Prev->Pt) - { - if (pp == lastPP) lastPP = pp->Prev; - OutPt *tmpPP = pp->Prev; - tmpPP->Next = pp->Next; - pp->Next->Prev = tmpPP; - delete pp; - pp = tmpPP; - } - } - - if (pp == pp->Prev) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } -} -//------------------------------------------------------------------------------ - -void Clipper::FixupOutPolygon(OutRec &outrec) -{ - //FixupOutPolygon() - removes duplicate points and simplifies consecutive - //parallel edges by removing the middle vertex. - OutPt *lastOK = 0; - outrec.BottomPt = 0; - OutPt *pp = outrec.Pts; - bool preserveCol = m_PreserveCollinear || m_StrictSimple; - - for (;;) - { - if (pp->Prev == pp || pp->Prev == pp->Next) - { - DisposeOutPts(pp); - outrec.Pts = 0; - return; - } - - //test for duplicate points and collinear edges ... - if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || - (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && - (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) - { - lastOK = 0; - OutPt *tmp = pp; - pp->Prev->Next = pp->Next; - pp->Next->Prev = pp->Prev; - pp = pp->Prev; - delete tmp; - } - else if (pp == lastOK) break; - else - { - if (!lastOK) lastOK = pp; - pp = pp->Next; - } - } - outrec.Pts = pp; -} -//------------------------------------------------------------------------------ - -int PointCount(OutPt *Pts) -{ - if (!Pts) return 0; - int result = 0; - OutPt* p = Pts; - do - { - result++; - p = p->Next; - } - while (p != Pts); - return result; -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult(Paths &polys) -{ - polys.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - if (!m_PolyOuts[i]->Pts) continue; - Path pg; - OutPt* p = m_PolyOuts[i]->Pts->Prev; - int cnt = PointCount(p); - if (cnt < 2) continue; - pg.reserve(cnt); - for (int i = 0; i < cnt; ++i) - { - pg.push_back(p->Pt); - p = p->Prev; - } - polys.push_back(pg); - } -} -//------------------------------------------------------------------------------ - -void Clipper::BuildResult2(PolyTree& polytree) -{ - polytree.Clear(); - polytree.AllNodes.reserve(m_PolyOuts.size()); - //add each output polygon/contour to polytree ... - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - int cnt = PointCount(outRec->Pts); - if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; - FixHoleLinkage(*outRec); - PolyNode* pn = new PolyNode(); - //nb: polytree takes ownership of all the PolyNodes - polytree.AllNodes.push_back(pn); - outRec->PolyNd = pn; - pn->Parent = 0; - pn->Index = 0; - pn->Contour.reserve(cnt); - OutPt *op = outRec->Pts->Prev; - for (int j = 0; j < cnt; j++) - { - pn->Contour.push_back(op->Pt); - op = op->Prev; - } - } - - //fixup PolyNode links etc ... - polytree.Childs.reserve(m_PolyOuts.size()); - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) - { - OutRec* outRec = m_PolyOuts[i]; - if (!outRec->PolyNd) continue; - if (outRec->IsOpen) - { - outRec->PolyNd->m_IsOpen = true; - polytree.AddChild(*outRec->PolyNd); - } - else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) - outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); - else - polytree.AddChild(*outRec->PolyNd); - } -} -//------------------------------------------------------------------------------ - -void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) -{ - //just swap the contents (because fIntersectNodes is a single-linked-list) - IntersectNode inode = int1; //gets a copy of Int1 - int1.Edge1 = int2.Edge1; - int1.Edge2 = int2.Edge2; - int1.Pt = int2.Pt; - int2.Edge1 = inode.Edge1; - int2.Edge2 = inode.Edge2; - int2.Pt = inode.Pt; -} -//------------------------------------------------------------------------------ - -inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) -{ - if (e2.Curr.X == e1.Curr.X) - { - if (e2.Top.Y > e1.Top.Y) - return e2.Top.X < TopX(e1, e2.Top.Y); - else return e1.Top.X > TopX(e2, e1.Top.Y); - } - else return e2.Curr.X < e1.Curr.X; -} -//------------------------------------------------------------------------------ - -bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, - cInt& Left, cInt& Right) -{ - if (a1 < a2) - { - if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} - else {Left = std::max(a1,b2); Right = std::min(a2,b1);} - } - else - { - if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} - else {Left = std::max(a2,b2); Right = std::min(a1,b1);} - } - return Left < Right; -} -//------------------------------------------------------------------------------ - -inline void UpdateOutPtIdxs(OutRec& outrec) -{ - OutPt* op = outrec.Pts; - do - { - op->Idx = outrec.Idx; - op = op->Prev; - } - while(op != outrec.Pts); -} -//------------------------------------------------------------------------------ - -void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) -{ - if(!m_ActiveEdges) - { - edge->PrevInAEL = 0; - edge->NextInAEL = 0; - m_ActiveEdges = edge; - } - else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) - { - edge->PrevInAEL = 0; - edge->NextInAEL = m_ActiveEdges; - m_ActiveEdges->PrevInAEL = edge; - m_ActiveEdges = edge; - } - else - { - if(!startEdge) startEdge = m_ActiveEdges; - while(startEdge->NextInAEL && - !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) - startEdge = startEdge->NextInAEL; - edge->NextInAEL = startEdge->NextInAEL; - if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; - edge->PrevInAEL = startEdge; - startEdge->NextInAEL = edge; - } -} -//---------------------------------------------------------------------- - -OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) -{ - OutPt* result = new OutPt; - result->Pt = outPt->Pt; - result->Idx = outPt->Idx; - if (InsertAfter) - { - result->Next = outPt->Next; - result->Prev = outPt; - outPt->Next->Prev = result; - outPt->Next = result; - } - else - { - result->Prev = outPt->Prev; - result->Next = outPt; - outPt->Prev->Next = result; - outPt->Prev = result; - } - return result; -} -//------------------------------------------------------------------------------ - -bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, - const IntPoint Pt, bool DiscardLeft) -{ - Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); - Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); - if (Dir1 == Dir2) return false; - - //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we - //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) - //So, to facilitate this while inserting Op1b and Op2b ... - //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, - //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) - if (Dir1 == dLeftToRight) - { - while (op1->Next->Pt.X <= Pt.X && - op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, !DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, !DiscardLeft); - } - } - else - { - while (op1->Next->Pt.X >= Pt.X && - op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) - op1 = op1->Next; - if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; - op1b = DupOutPt(op1, DiscardLeft); - if (op1b->Pt != Pt) - { - op1 = op1b; - op1->Pt = Pt; - op1b = DupOutPt(op1, DiscardLeft); - } - } - - if (Dir2 == dLeftToRight) - { - while (op2->Next->Pt.X <= Pt.X && - op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, !DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, !DiscardLeft); - }; - } else - { - while (op2->Next->Pt.X >= Pt.X && - op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) - op2 = op2->Next; - if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; - op2b = DupOutPt(op2, DiscardLeft); - if (op2b->Pt != Pt) - { - op2 = op2b; - op2->Pt = Pt; - op2b = DupOutPt(op2, DiscardLeft); - }; - }; - - if ((Dir1 == dLeftToRight) == DiscardLeft) - { - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - } - else - { - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - } - return true; -} -//------------------------------------------------------------------------------ - -bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) -{ - OutPt *op1 = j->OutPt1, *op1b; - OutPt *op2 = j->OutPt2, *op2b; - - //There are 3 kinds of joins for output polygons ... - //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere - //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). - //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same - //location at the Bottom of the overlapping segment (& Join.OffPt is above). - //3. StrictSimple joins where edges touch but are not collinear and where - //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); - - if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && - (j->OffPt == j->OutPt2->Pt)) - { - //Strictly Simple join ... - if (outRec1 != outRec2) return false; - op1b = j->OutPt1->Next; - while (op1b != op1 && (op1b->Pt == j->OffPt)) - op1b = op1b->Next; - bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); - op2b = j->OutPt2->Next; - while (op2b != op2 && (op2b->Pt == j->OffPt)) - op2b = op2b->Next; - bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); - if (reverse1 == reverse2) return false; - if (reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } - else if (isHorizontal) - { - //treat horizontal joins differently to non-horizontal joins since with - //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt - //may be anywhere along the horizontal edge. - op1b = op1; - while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) - op1 = op1->Prev; - while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) - op1b = op1b->Next; - if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' - - op2b = op2; - while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) - op2 = op2->Prev; - while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) - op2b = op2b->Next; - if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' - - cInt Left, Right; - //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges - if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) - return false; - - //DiscardLeftSide: when overlapping edges are joined, a spike will created - //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up - //on the discard Side as either may still be needed for other joins ... - IntPoint Pt; - bool DiscardLeftSide; - if (op1->Pt.X >= Left && op1->Pt.X <= Right) - { - Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); - } - else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) - { - Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); - } - else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) - { - Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; - } - else - { - Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); - } - j->OutPt1 = op1; j->OutPt2 = op2; - return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); - } else - { - //nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y - - //make sure the polygons are correctly oriented ... - op1b = op1->Next; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; - bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse1) - { - op1b = op1->Prev; - while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; - if ((op1b->Pt.Y > op1->Pt.Y) || - !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; - }; - op2b = op2->Next; - while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; - bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); - if (Reverse2) - { - op2b = op2->Prev; - while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; - if ((op2b->Pt.Y > op2->Pt.Y) || - !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; - } - - if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || - ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; - - if (Reverse1) - { - op1b = DupOutPt(op1, false); - op2b = DupOutPt(op2, true); - op1->Prev = op2; - op2->Next = op1; - op1b->Next = op2b; - op2b->Prev = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } else - { - op1b = DupOutPt(op1, true); - op2b = DupOutPt(op2, false); - op1->Next = op2; - op2->Prev = op1; - op1b->Prev = op2b; - op2b->Next = op1b; - j->OutPt1 = op1; - j->OutPt2 = op1b; - return true; - } - } -} -//---------------------------------------------------------------------- - -static OutRec* ParseFirstLeft(OutRec* FirstLeft) -{ - while (FirstLeft && !FirstLeft->Pts) - FirstLeft = FirstLeft->FirstLeft; - return FirstLeft; -} -//------------------------------------------------------------------------------ - -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //tests if NewOutRec contains the polygon before reassigning FirstLeft - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && firstLeft == OldOutRec) - { - if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) - outRec->FirstLeft = NewOutRec; - } - } -} -//---------------------------------------------------------------------- - -void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) -{ - //A polygon has split into two such that one is now the inner of the other. - //It's possible that these polygons now wrap around other polygons, so check - //every polygon that's also contained by OuterOutRec's FirstLeft container - //(including 0) to see if they've become inner to the new inner polygon ... - OutRec* orfl = OuterOutRec->FirstLeft; - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - - if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) - continue; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) - continue; - if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) - outRec->FirstLeft = InnerOutRec; - else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) - outRec->FirstLeft = OuterOutRec; - else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) - outRec->FirstLeft = orfl; - } -} -//---------------------------------------------------------------------- -void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) -{ - //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - { - OutRec* outRec = m_PolyOuts[i]; - OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (outRec->Pts && outRec->FirstLeft == OldOutRec) - outRec->FirstLeft = NewOutRec; - } -} -//---------------------------------------------------------------------- - -void Clipper::JoinCommonEdges() -{ - for (JoinList::size_type i = 0; i < m_Joins.size(); i++) - { - Join* join = m_Joins[i]; - - OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); - OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); - - if (!outRec1->Pts || !outRec2->Pts) continue; - if (outRec1->IsOpen || outRec2->IsOpen) continue; - - //get the polygon fragment with the correct hole state (FirstLeft) - //before calling JoinPoints() ... - OutRec *holeStateRec; - if (outRec1 == outRec2) holeStateRec = outRec1; - else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; - else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; - else holeStateRec = GetLowermostRec(outRec1, outRec2); - - if (!JoinPoints(join, outRec1, outRec2)) continue; - - if (outRec1 == outRec2) - { - //instead of joining two polygons, we've just created a new one by - //splitting one polygon into two. - outRec1->Pts = join->OutPt1; - outRec1->BottomPt = 0; - outRec2 = CreateOutRec(); - outRec2->Pts = join->OutPt2; - - //update all OutRec2.Pts Idx's ... - UpdateOutPtIdxs(*outRec2); - - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) - { - //outRec1 contains outRec2 ... - outRec2->IsHole = !outRec1->IsHole; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); - - if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) - ReversePolyPtLinks(outRec2->Pts); - - } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) - { - //outRec2 contains outRec1 ... - outRec2->IsHole = outRec1->IsHole; - outRec1->IsHole = !outRec2->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - outRec1->FirstLeft = outRec2; - - if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); - - if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) - ReversePolyPtLinks(outRec1->Pts); - } - else - { - //the 2 polygons are completely separate ... - outRec2->IsHole = outRec1->IsHole; - outRec2->FirstLeft = outRec1->FirstLeft; - - //fixup FirstLeft pointers that may need reassigning to OutRec2 - if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); - } - - } else - { - //joined 2 polygons together ... - - outRec2->Pts = 0; - outRec2->BottomPt = 0; - outRec2->Idx = outRec1->Idx; - - outRec1->IsHole = holeStateRec->IsHole; - if (holeStateRec == outRec2) - outRec1->FirstLeft = outRec2->FirstLeft; - outRec2->FirstLeft = outRec1; - - if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); - } - } -} - -//------------------------------------------------------------------------------ -// ClipperOffset support functions ... -//------------------------------------------------------------------------------ - -DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) -{ - if(pt2.X == pt1.X && pt2.Y == pt1.Y) - return DoublePoint(0, 0); - - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); - Dx *= f; - dy *= f; - return DoublePoint(dy, -Dx); -} - -//------------------------------------------------------------------------------ -// ClipperOffset class -//------------------------------------------------------------------------------ - -ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) -{ - this->MiterLimit = miterLimit; - this->ArcTolerance = arcTolerance; - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -ClipperOffset::~ClipperOffset() -{ - Clear(); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Clear() -{ - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - delete m_polyNodes.Childs[i]; - m_polyNodes.Childs.clear(); - m_lowest.X = -1; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) -{ - int highI = (int)path.size() - 1; - if (highI < 0) return; - PolyNode* newNode = new PolyNode(); - newNode->m_jointype = joinType; - newNode->m_endtype = endType; - - //strip duplicate points from path and also get index to the lowest point ... - if (endType == etClosedLine || endType == etClosedPolygon) - while (highI > 0 && path[0] == path[highI]) highI--; - newNode->Contour.reserve(highI + 1); - newNode->Contour.push_back(path[0]); - int j = 0, k = 0; - for (int i = 1; i <= highI; i++) - if (newNode->Contour[j] != path[i]) - { - j++; - newNode->Contour.push_back(path[i]); - if (path[i].Y > newNode->Contour[k].Y || - (path[i].Y == newNode->Contour[k].Y && - path[i].X < newNode->Contour[k].X)) k = j; - } - if (endType == etClosedPolygon && j < 2) - { - delete newNode; - return; - } - m_polyNodes.AddChild(*newNode); - - //if this path's lowest pt is lower than all the others then update m_lowest - if (endType != etClosedPolygon) return; - if (m_lowest.X < 0) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - else - { - IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; - if (newNode->Contour[k].Y > ip.Y || - (newNode->Contour[k].Y == ip.Y && - newNode->Contour[k].X < ip.X)) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) -{ - for (Paths::size_type i = 0; i < paths.size(); ++i) - AddPath(paths[i], joinType, endType); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::FixOrientations() -{ - //fixup orientations of all closed paths if the orientation of the - //closed path with the lowermost vertex is wrong ... - if (m_lowest.X >= 0 && - !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon || - (node.m_endtype == etClosedLine && Orientation(node.Contour))) - ReversePath(node.Contour); - } - } else - { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) - ReversePath(node.Contour); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(Paths& solution, double delta) -{ - solution.clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - if (solution.size() > 0) solution.erase(solution.begin()); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::Execute(PolyTree& solution, double delta) -{ - solution.Clear(); - FixOrientations(); - DoOffset(delta); - - //now clean up 'corners' ... - Clipper clpr; - clpr.AddPaths(m_destPolys, ptSubject, true); - if (delta > 0) - { - clpr.Execute(ctUnion, solution, pftPositive, pftPositive); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - clpr.Execute(ctUnion, solution, pftNegative, pftNegative); - //remove the outer PolyNode rectangle ... - if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) - { - PolyNode* outerNode = solution.Childs[0]; - solution.Childs.reserve(outerNode->ChildCount()); - solution.Childs[0] = outerNode->Childs[0]; - solution.Childs[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Childs[i]); - } - else - solution.Clear(); - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoOffset(double delta) -{ - m_destPolys.clear(); - m_delta = delta; - - //if Zero offset, just copy any CLOSED polygons to m_p and return ... - if (NEAR_ZERO(delta)) - { - m_destPolys.reserve(m_polyNodes.ChildCount()); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - if (node.m_endtype == etClosedPolygon) - m_destPolys.push_back(node.Contour); - } - return; - } - - //see offset_triginometry3.svg in the documentation folder ... - if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); - else m_miterLim = 0.5; - - double y; - if (ArcTolerance <= 0.0) y = def_arc_tolerance; - else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) - y = std::fabs(delta) * def_arc_tolerance; - else y = ArcTolerance; - //see offset_triginometry2.svg in the documentation folder ... - double steps = pi / std::acos(1 - y / std::fabs(delta)); - if (steps > std::fabs(delta) * pi) - steps = std::fabs(delta) * pi; //ie excessive precision check - m_sin = std::sin(two_pi / steps); - m_cos = std::cos(two_pi / steps); - m_StepsPerRad = steps / two_pi; - if (delta < 0.0) m_sin = -m_sin; - - m_destPolys.reserve(m_polyNodes.ChildCount() * 2); - for (int i = 0; i < m_polyNodes.ChildCount(); i++) - { - PolyNode& node = *m_polyNodes.Childs[i]; - m_srcPoly = node.Contour; - - int len = (int)m_srcPoly.size(); - if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) - continue; - - m_destPoly.clear(); - if (len == 1) - { - if (node.m_jointype == jtRound) - { - double X = 1.0, Y = 0.0; - for (cInt j = 1; j <= steps; j++) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - double X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - } - else - { - double X = -1.0, Y = -1.0; - for (int j = 0; j < 4; ++j) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[0].X + X * delta), - Round(m_srcPoly[0].Y + Y * delta))); - if (X < 0) X = 1; - else if (Y < 0) Y = 1; - else X = -1; - } - } - m_destPolys.push_back(m_destPoly); - continue; - } - //build m_normals ... - m_normals.clear(); - m_normals.reserve(len); - for (int j = 0; j < len - 1; ++j) - m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); - if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) - m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); - else - m_normals.push_back(DoublePoint(m_normals[len - 2])); - - if (node.m_endtype == etClosedPolygon) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else if (node.m_endtype == etClosedLine) - { - int k = len - 1; - for (int j = 0; j < len; ++j) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - m_destPoly.clear(); - //re-build m_normals ... - DoublePoint n = m_normals[len -1]; - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-n.X, -n.Y); - k = 0; - for (int j = len - 1; j >= 0; j--) - OffsetPoint(j, k, node.m_jointype); - m_destPolys.push_back(m_destPoly); - } - else - { - int k = 0; - for (int j = 1; j < len - 1; ++j) - OffsetPoint(j, k, node.m_jointype); - - IntPoint pt1; - if (node.m_endtype == etOpenButt) - { - int j = len - 1; - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * - delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - int j = len - 1; - k = len - 2; - m_sinA = 0; - m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); - if (node.m_endtype == etOpenSquare) - DoSquare(j, k); - else - DoRound(j, k); - } - - //re-build m_normals ... - for (int j = len - 1; j > 0; j--) - m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); - m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); - - k = len - 1; - for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); - - if (node.m_endtype == etOpenButt) - { - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), - (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); - m_destPoly.push_back(pt1); - } - else - { - k = 1; - m_sinA = 0; - if (node.m_endtype == etOpenSquare) - DoSquare(0, 1); - else - DoRound(0, 1); - } - m_destPolys.push_back(m_destPoly); - } - } -} -//------------------------------------------------------------------------------ - -void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) -{ - //cross product ... - m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); - if (std::fabs(m_sinA * m_delta) < 1.0) - { - //dot product ... - double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); - if (cosA > 0) // angle => 0 degrees - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - return; - } - //else angle => 180 degrees - } - else if (m_sinA > 1.0) m_sinA = 1.0; - else if (m_sinA < -1.0) m_sinA = -1.0; - - if (m_sinA * m_delta < 0) - { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); - m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); - } - else - switch (jointype) - { - case jtMiter: - { - double r = 1 + (m_normals[j].X * m_normals[k].X + - m_normals[j].Y * m_normals[k].Y); - if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); - break; - } - case jtSquare: DoSquare(j, k); break; - case jtRound: DoRound(j, k); break; - } - k = j; -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoSquare(int j, int k) -{ - double dx = std::tan(std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), - Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoMiter(int j, int k, double r) -{ - double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), - Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); -} -//------------------------------------------------------------------------------ - -void ClipperOffset::DoRound(int j, int k) -{ - double a = std::atan2(m_sinA, - m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); - int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); - - double X = m_normals[k].X, Y = m_normals[k].Y, X2; - for (int i = 0; i < steps; ++i) - { - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + X * m_delta), - Round(m_srcPoly[j].Y + Y * m_delta))); - X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - m_destPoly.push_back(IntPoint( - Round(m_srcPoly[j].X + m_normals[j].X * m_delta), - Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); -} - -//------------------------------------------------------------------------------ -// Miscellaneous public functions -//------------------------------------------------------------------------------ - -void Clipper::DoSimplePolygons() -{ - PolyOutList::size_type i = 0; - while (i < m_PolyOuts.size()) - { - OutRec* outrec = m_PolyOuts[i++]; - OutPt* op = outrec->Pts; - if (!op || outrec->IsOpen) continue; - do //for each Pt in Polygon until duplicate found do ... - { - OutPt* op2 = op->Next; - while (op2 != outrec->Pts) - { - if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) - { - //split the polygon into two ... - OutPt* op3 = op->Prev; - OutPt* op4 = op2->Prev; - op->Prev = op4; - op4->Next = op; - op2->Prev = op3; - op3->Next = op2; - - outrec->Pts = op; - OutRec* outrec2 = CreateOutRec(); - outrec2->Pts = op2; - UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) - { - //OutRec2 is contained by OutRec1 ... - outrec2->IsHole = !outrec->IsHole; - outrec2->FirstLeft = outrec; - if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); - } - else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) - { - //OutRec1 is contained by OutRec2 ... - outrec2->IsHole = outrec->IsHole; - outrec->IsHole = !outrec2->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - outrec->FirstLeft = outrec2; - if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); - } - else - { - //the 2 polygons are separate ... - outrec2->IsHole = outrec->IsHole; - outrec2->FirstLeft = outrec->FirstLeft; - if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); - } - op2 = op; //ie get ready for the Next iteration - } - op2 = op2->Next; - } - op = op->Next; - } - while (op != outrec->Pts); - } -} -//------------------------------------------------------------------------------ - -void ReversePath(Path& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePaths(Paths& p) -{ - for (Paths::size_type i = 0; i < p.size(); ++i) - ReversePath(p[i]); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPath(in_poly, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) -{ - Clipper c; - c.StrictlySimple(true); - c.AddPaths(in_polys, ptSubject, true); - c.Execute(ctUnion, out_polys, fillType, fillType); -} -//------------------------------------------------------------------------------ - -void SimplifyPolygons(Paths &polys, PolyFillType fillType) -{ - SimplifyPolygons(polys, polys, fillType); -} -//------------------------------------------------------------------------------ - -inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) -{ - double Dx = ((double)pt1.X - pt2.X); - double dy = ((double)pt1.Y - pt2.Y); - return (Dx*Dx + dy*dy); -} -//------------------------------------------------------------------------------ - -double DistanceFromLineSqrd( - const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) -{ - //The equation of a line in general form (Ax + By + C = 0) - //given 2 points (x,y) & (x,y) is ... - //(y - y)x + (x - x)y + (y - y)x - (x - x)y = 0 - //A = (y - y); B = (x - x); C = (y - y)x - (x - x)y - //perpendicular distance of point (x,y) = (Ax + By + C)/Sqrt(A + B) - //see http://en.wikipedia.org/wiki/Perpendicular_distance - double A = double(ln1.Y - ln2.Y); - double B = double(ln2.X - ln1.X); - double C = A * ln1.X + B * ln1.Y; - C = A * pt.X + B * pt.Y - C; - return (C * C) / (A * A + B * B); -} -//--------------------------------------------------------------------------- - -bool SlopesNearCollinear(const IntPoint& pt1, - const IntPoint& pt2, const IntPoint& pt3, double distSqrd) -{ - //this function is more accurate when the point that's geometrically - //between the other 2 points is the one that's tested for distance. - //ie makes it more likely to pick up 'spikes' ... - if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) - { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - else - { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - else - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } -} -//------------------------------------------------------------------------------ - -bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) -{ - double Dx = (double)pt1.X - pt2.X; - double dy = (double)pt1.Y - pt2.Y; - return ((Dx * Dx) + (dy * dy) <= distSqrd); -} -//------------------------------------------------------------------------------ - -OutPt* ExcludeOp(OutPt* op) -{ - OutPt* result = op->Prev; - result->Next = op->Next; - op->Next->Prev = result; - result->Idx = 0; - return result; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) -{ - //distance = proximity in units/pixels below which vertices - //will be stripped. Default ~= sqrt(2). - - size_t size = in_poly.size(); - - if (size == 0) - { - out_poly.clear(); - return; - } - - OutPt* outPts = new OutPt[size]; - for (size_t i = 0; i < size; ++i) - { - outPts[i].Pt = in_poly[i]; - outPts[i].Next = &outPts[(i + 1) % size]; - outPts[i].Next->Prev = &outPts[i]; - outPts[i].Idx = 0; - } - - double distSqrd = distance * distance; - OutPt* op = &outPts[0]; - while (op->Idx == 0 && op->Next != op->Prev) - { - if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) - { - ExcludeOp(op->Next); - op = ExcludeOp(op); - size -= 2; - } - else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) - { - op = ExcludeOp(op); - size--; - } - else - { - op->Idx = 1; - op = op->Next; - } - } - - if (size < 3) size = 0; - out_poly.resize(size); - for (size_t i = 0; i < size; ++i) - { - out_poly[i] = op->Pt; - op = op->Next; - } - delete [] outPts; -} -//------------------------------------------------------------------------------ - -void CleanPolygon(Path& poly, double distance) -{ - CleanPolygon(poly, poly, distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) -{ - out_polys.resize(in_polys.size()); - for (Paths::size_type i = 0; i < in_polys.size(); ++i) - CleanPolygon(in_polys[i], out_polys[i], distance); -} -//------------------------------------------------------------------------------ - -void CleanPolygons(Paths& polys, double distance) -{ - CleanPolygons(polys, polys, distance); -} -//------------------------------------------------------------------------------ - -void Minkowski(const Path& poly, const Path& path, - Paths& solution, bool isSum, bool isClosed) -{ - int delta = (isClosed ? 1 : 0); - size_t polyCnt = poly.size(); - size_t pathCnt = path.size(); - Paths pp; - pp.reserve(pathCnt); - if (isSum) - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); - pp.push_back(p); - } - else - for (size_t i = 0; i < pathCnt; ++i) - { - Path p; - p.reserve(polyCnt); - for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); - pp.push_back(p); - } - - solution.clear(); - solution.reserve((pathCnt + delta) * (polyCnt + 1)); - for (size_t i = 0; i < pathCnt - 1 + delta; ++i) - for (size_t j = 0; j < polyCnt; ++j) - { - Path quad; - quad.reserve(4); - quad.push_back(pp[i % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); - quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); - quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); - if (!Orientation(quad)) ReversePath(quad); - solution.push_back(quad); - } -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) -{ - Minkowski(pattern, path, solution, true, pathIsClosed); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void TranslatePath(const Path& input, Path& output, const IntPoint delta) -{ - //precondition: input != output - output.resize(input.size()); - for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); -} -//------------------------------------------------------------------------------ - -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) -{ - Clipper c; - for (size_t i = 0; i < paths.size(); ++i) - { - Paths tmp; - Minkowski(pattern, paths[i], tmp, true, pathIsClosed); - c.AddPaths(tmp, ptSubject, true); - if (pathIsClosed) - { - Path tmp2; - TranslatePath(paths[i], tmp2, pattern[0]); - c.AddPath(tmp2, ptClip, true); - } - } - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) -{ - Minkowski(poly1, poly2, solution, false, true); - Clipper c; - c.AddPaths(solution, ptSubject, true); - c.Execute(ctUnion, solution, pftNonZero, pftNonZero); -} -//------------------------------------------------------------------------------ - -enum NodeType {ntAny, ntOpen, ntClosed}; - -void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) -{ - bool match = true; - if (nodetype == ntClosed) match = !polynode.IsOpen(); - else if (nodetype == ntOpen) return; - - if (!polynode.Contour.empty() && match) - paths.push_back(polynode.Contour); - for (int i = 0; i < polynode.ChildCount(); ++i) - AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntAny, paths); -} -//------------------------------------------------------------------------------ - -void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - AddPolyNodeToPaths(polytree, ntClosed, paths); -} -//------------------------------------------------------------------------------ - -void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) -{ - paths.resize(0); - paths.reserve(polytree.Total()); - //Open paths are top level only, so ... - for (int i = 0; i < polytree.ChildCount(); ++i) - if (polytree.Childs[i]->IsOpen()) - paths.push_back(polytree.Childs[i]->Contour); -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const IntPoint &p) -{ - s << "(" << p.X << "," << p.Y << ")"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Path &p) -{ - if (p.empty()) return s; - Path::size_type last = p.size() -1; - for (Path::size_type i = 0; i < last; i++) - s << "(" << p[i].X << "," << p[i].Y << "), "; - s << "(" << p[last].X << "," << p[last].Y << ")\n"; - return s; -} -//------------------------------------------------------------------------------ - -std::ostream& operator <<(std::ostream &s, const Paths &p) -{ - for (Paths::size_type i = 0; i < p.size(); i++) - s << p[i]; - s << "\n"; - return s; -} -//------------------------------------------------------------------------------ - -} //ClipperLib namespace diff --git a/ppocr/postprocess/lanms/include/clipper/clipper.hpp b/ppocr/postprocess/lanms/include/clipper/clipper.hpp deleted file mode 100644 index c595ebc3..00000000 --- a/ppocr/postprocess/lanms/include/clipper/clipper.hpp +++ /dev/null @@ -1,404 +0,0 @@ -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.0 * -* Date : 2 July 2015 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -#ifndef clipper_hpp -#define clipper_hpp - -#define CLIPPER_VERSION "6.2.6" - -//use_int32: When enabled 32bit ints are used instead of 64bit ints. This -//improve performance but coordinate values are limited to the range +/- 46340 -//#define use_int32 - -//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. -//#define use_xyz - -//use_lines: Enables line clipping. Adds a very minor cost to performance. -#define use_lines - -//use_deprecated: Enables temporary support for the obsolete functions -//#define use_deprecated - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; -enum PolyType { ptSubject, ptClip }; -//By far the most widely used winding rules for polygon filling are -//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) -//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) -//see http://glprogramming.com/red/chapter11.html -enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; - -#ifdef use_int32 - typedef int cInt; - static cInt const loRange = 0x7FFF; - static cInt const hiRange = 0x7FFF; -#else - typedef signed long long cInt; - static cInt const loRange = 0x3FFFFFFF; - static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; - typedef signed long long long64; //used by Int128 class - typedef unsigned long long ulong64; - -#endif - -struct IntPoint { - cInt X; - cInt Y; -#ifdef use_xyz - cInt Z; - IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; -#else - IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; -#endif - - friend inline bool operator== (const IntPoint& a, const IntPoint& b) - { - return a.X == b.X && a.Y == b.Y; - } - friend inline bool operator!= (const IntPoint& a, const IntPoint& b) - { - return a.X != b.X || a.Y != b.Y; - } -}; -//------------------------------------------------------------------------------ - -typedef std::vector< IntPoint > Path; -typedef std::vector< Path > Paths; - -inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} -inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} - -std::ostream& operator <<(std::ostream &s, const IntPoint &p); -std::ostream& operator <<(std::ostream &s, const Path &p); -std::ostream& operator <<(std::ostream &s, const Paths &p); - -struct DoublePoint -{ - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} -}; -//------------------------------------------------------------------------------ - -#ifdef use_xyz -typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); -#endif - -enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; -enum JoinType {jtSquare, jtRound, jtMiter}; -enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; - -class PolyNode; -typedef std::vector< PolyNode* > PolyNodes; - -class PolyNode -{ -public: - PolyNode(); - virtual ~PolyNode(){}; - Path Contour; - PolyNodes Childs; - PolyNode* Parent; - PolyNode* GetNext() const; - bool IsHole() const; - bool IsOpen() const; - int ChildCount() const; -private: - unsigned Index; //node index in Parent.Childs - bool m_IsOpen; - JoinType m_jointype; - EndType m_endtype; - PolyNode* GetNextSiblingUp() const; - void AddChild(PolyNode& child); - friend class Clipper; //to access Index - friend class ClipperOffset; -}; - -class PolyTree: public PolyNode -{ -public: - ~PolyTree(){Clear();}; - PolyNode* GetFirst() const; - void Clear(); - int Total() const; -private: - PolyNodes AllNodes; - friend class Clipper; //to access AllNodes -}; - -bool Orientation(const Path &poly); -double Area(const Path &poly); -int PointInPolygon(const IntPoint &pt, const Path &path); - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); - -void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); -void CleanPolygon(Path& poly, double distance = 1.415); -void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); -void CleanPolygons(Paths& polys, double distance = 1.415); - -void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); -void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); -void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); - -void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); -void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); -void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); - -void ReversePath(Path& p); -void ReversePaths(Paths& p); - -struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; - -//enums that are used internally ... -enum EdgeSide { esLeft = 1, esRight = 2}; - -//forward declarations (for stuff used internally) ... -struct TEdge; -struct IntersectNode; -struct LocalMinimum; -struct OutPt; -struct OutRec; -struct Join; - -typedef std::vector < OutRec* > PolyOutList; -typedef std::vector < TEdge* > EdgeList; -typedef std::vector < Join* > JoinList; -typedef std::vector < IntersectNode* > IntersectList; - -//------------------------------------------------------------------------------ - -//ClipperBase is the ancestor to the Clipper class. It should not be -//instantiated directly. This class simply abstracts the conversion of sets of -//polygon coordinates into edge objects that are stored in a LocalMinima list. -class ClipperBase -{ -public: - ClipperBase(); - virtual ~ClipperBase(); - virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); - bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - virtual void Clear(); - IntRect GetBounds(); - bool PreserveCollinear() {return m_PreserveCollinear;}; - void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; -protected: - void DisposeLocalMinimaList(); - TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); - virtual void Reset(); - TEdge* ProcessBound(TEdge* E, bool IsClockwise); - void InsertScanbeam(const cInt Y); - bool PopScanbeam(cInt &Y); - bool LocalMinimaPending(); - bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); - OutRec* CreateOutRec(); - void DisposeAllOutRecs(); - void DisposeOutRec(PolyOutList::size_type index); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); - - typedef std::vector MinimaList; - MinimaList::iterator m_CurrentLM; - MinimaList m_MinimaList; - - bool m_UseFullRange; - EdgeList m_edges; - bool m_PreserveCollinear; - bool m_HasOpenPaths; - PolyOutList m_PolyOuts; - TEdge *m_ActiveEdges; - - typedef std::priority_queue ScanbeamList; - ScanbeamList m_Scanbeam; -}; -//------------------------------------------------------------------------------ - -class Clipper : public virtual ClipperBase -{ -public: - Clipper(int initOptions = 0); - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, - Paths &solution, - PolyFillType subjFillType, - PolyFillType clipFillType); - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, - PolyTree &polytree, - PolyFillType subjFillType, - PolyFillType clipFillType); - bool ReverseSolution() { return m_ReverseOutput; }; - void ReverseSolution(bool value) {m_ReverseOutput = value;}; - bool StrictlySimple() {return m_StrictSimple;}; - void StrictlySimple(bool value) {m_StrictSimple = value;}; - //set the callback function for z value filling on intersections (otherwise Z is 0) -#ifdef use_xyz - void ZFillFunction(ZFillCallback zFillFunc); -#endif -protected: - virtual bool ExecuteInternal(); -private: - JoinList m_Joins; - JoinList m_GhostJoins; - IntersectList m_IntersectList; - ClipType m_ClipType; - typedef std::list MaximaList; - MaximaList m_Maxima; - TEdge *m_SortedEdges; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - bool m_UsingPolyTree; - bool m_StrictSimple; -#ifdef use_xyz - ZFillCallback m_ZFill; //custom callback -#endif - void SetWindingCount(TEdge& edge); - bool IsEvenOddFillType(const TEdge& edge) const; - bool IsEvenOddAltFillType(const TEdge& edge) const; - void InsertLocalMinimaIntoAEL(const cInt botY); - void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); - void AddEdgeToSEL(TEdge *edge); - bool PopEdgeFromSEL(TEdge *&edge); - void CopyAELToSEL(); - void DeleteFromSEL(TEdge *e); - void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); - bool IsContributing(const TEdge& edge) const; - bool IsTopHorz(const cInt XPos); - void DoMaxima(TEdge *e); - void ProcessHorizontals(); - void ProcessHorizontal(TEdge *horzEdge); - void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutRec* GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2); - void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); - OutPt* AddOutPt(TEdge *e, const IntPoint &pt); - OutPt* GetLastOutPt(TEdge *e); - bool ProcessIntersections(const cInt topY); - void BuildIntersectList(const cInt topY); - void ProcessIntersectList(); - void ProcessEdgesAtTopOfScanbeam(const cInt topY); - void BuildResult(Paths& polys); - void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *outrec); - void DisposeIntersectNodes(); - bool FixupIntersectionOrder(); - void FixupOutPolygon(OutRec &outrec); - void FixupOutPolyline(OutRec &outrec); - bool IsHole(TEdge *e); - bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); - void FixHoleLinkage(OutRec &outrec); - void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); - void ClearJoins(); - void ClearGhostJoins(); - void AddGhostJoin(OutPt *op, const IntPoint offPt); - bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); - void JoinCommonEdges(); - void DoSimplePolygons(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); - void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec); - void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec); -#ifdef use_xyz - void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); -#endif -}; -//------------------------------------------------------------------------------ - -class ClipperOffset -{ -public: - ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); - ~ClipperOffset(); - void AddPath(const Path& path, JoinType joinType, EndType endType); - void AddPaths(const Paths& paths, JoinType joinType, EndType endType); - void Execute(Paths& solution, double delta); - void Execute(PolyTree& solution, double delta); - void Clear(); - double MiterLimit; - double ArcTolerance; -private: - Paths m_destPolys; - Path m_srcPoly; - Path m_destPoly; - std::vector m_normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_StepsPerRad; - IntPoint m_lowest; - PolyNode m_polyNodes; - - void FixOrientations(); - void DoOffset(double delta); - void OffsetPoint(int j, int& k, JoinType jointype); - void DoSquare(int j, int k); - void DoMiter(int j, int k, double r); - void DoRound(int j, int k); -}; -//------------------------------------------------------------------------------ - -class clipperException : public std::exception -{ - public: - clipperException(const char* description): m_descr(description) {} - virtual ~clipperException() throw() {} - virtual const char* what() const throw() {return m_descr.c_str();} - private: - std::string m_descr; -}; -//------------------------------------------------------------------------------ - -} //ClipperLib namespace - -#endif //clipper_hpp - - diff --git a/ppocr/postprocess/lanms/include/pybind11/attr.h b/ppocr/postprocess/lanms/include/pybind11/attr.h deleted file mode 100644 index b4137cb2..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/attr.h +++ /dev/null @@ -1,471 +0,0 @@ -/* - pybind11/attr.h: Infrastructure for processing custom - type and function attributes - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "cast.h" - -NAMESPACE_BEGIN(pybind11) - -/// \addtogroup annotations -/// @{ - -/// Annotation for methods -struct is_method { handle class_; is_method(const handle &c) : class_(c) { } }; - -/// Annotation for operators -struct is_operator { }; - -/// Annotation for parent scope -struct scope { handle value; scope(const handle &s) : value(s) { } }; - -/// Annotation for documentation -struct doc { const char *value; doc(const char *value) : value(value) { } }; - -/// Annotation for function names -struct name { const char *value; name(const char *value) : value(value) { } }; - -/// Annotation indicating that a function is an overload associated with a given "sibling" -struct sibling { handle value; sibling(const handle &value) : value(value.ptr()) { } }; - -/// Annotation indicating that a class derives from another given type -template struct base { - PYBIND11_DEPRECATED("base() was deprecated in favor of specifying 'T' as a template argument to class_") - base() { } -}; - -/// Keep patient alive while nurse lives -template struct keep_alive { }; - -/// Annotation indicating that a class is involved in a multiple inheritance relationship -struct multiple_inheritance { }; - -/// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class -struct dynamic_attr { }; - -/// Annotation which enables the buffer protocol for a type -struct buffer_protocol { }; - -/// Annotation which requests that a special metaclass is created for a type -struct metaclass { - handle value; - - PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.") - metaclass() {} - - /// Override pybind11's default metaclass - explicit metaclass(handle value) : value(value) { } -}; - -/// Annotation to mark enums as an arithmetic type -struct arithmetic { }; - -/** \rst - A call policy which places one or more guard variables (``Ts...``) around the function call. - - For example, this definition: - - .. code-block:: cpp - - m.def("foo", foo, py::call_guard()); - - is equivalent to the following pseudocode: - - .. code-block:: cpp - - m.def("foo", [](args...) { - T scope_guard; - return foo(args...); // forwarded arguments - }); - \endrst */ -template struct call_guard; - -template <> struct call_guard<> { using type = detail::void_type; }; - -template -struct call_guard { - static_assert(std::is_default_constructible::value, - "The guard type must be default constructible"); - - using type = T; -}; - -template -struct call_guard { - struct type { - T guard{}; // Compose multiple guard types with left-to-right default-constructor order - typename call_guard::type next{}; - }; -}; - -/// @} annotations - -NAMESPACE_BEGIN(detail) -/* Forward declarations */ -enum op_id : int; -enum op_type : int; -struct undefined_t; -template struct op_; -template struct init; -template struct init_alias; -inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret); - -/// Internal data structure which holds metadata about a keyword argument -struct argument_record { - const char *name; ///< Argument name - const char *descr; ///< Human-readable version of the argument value - handle value; ///< Associated Python object - bool convert : 1; ///< True if the argument is allowed to convert when loading - bool none : 1; ///< True if None is allowed when loading - - argument_record(const char *name, const char *descr, handle value, bool convert, bool none) - : name(name), descr(descr), value(value), convert(convert), none(none) { } -}; - -/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.) -struct function_record { - function_record() - : is_constructor(false), is_stateless(false), is_operator(false), - has_args(false), has_kwargs(false), is_method(false) { } - - /// Function name - char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ - - // User-specified documentation string - char *doc = nullptr; - - /// Human-readable version of the function signature - char *signature = nullptr; - - /// List of registered keyword arguments - std::vector args; - - /// Pointer to lambda function which converts arguments and performs the actual call - handle (*impl) (function_call &) = nullptr; - - /// Storage for the wrapped function pointer and captured data, if any - void *data[3] = { }; - - /// Pointer to custom destructor for 'data' (if needed) - void (*free_data) (function_record *ptr) = nullptr; - - /// Return value policy associated with this function - return_value_policy policy = return_value_policy::automatic; - - /// True if name == '__init__' - bool is_constructor : 1; - - /// True if this is a stateless function pointer - bool is_stateless : 1; - - /// True if this is an operator (__add__), etc. - bool is_operator : 1; - - /// True if the function has a '*args' argument - bool has_args : 1; - - /// True if the function has a '**kwargs' argument - bool has_kwargs : 1; - - /// True if this is a method - bool is_method : 1; - - /// Number of arguments (including py::args and/or py::kwargs, if present) - std::uint16_t nargs; - - /// Python method object - PyMethodDef *def = nullptr; - - /// Python handle to the parent scope (a class or a module) - handle scope; - - /// Python handle to the sibling function representing an overload chain - handle sibling; - - /// Pointer to next overload - function_record *next = nullptr; -}; - -/// Special data structure which (temporarily) holds metadata about a bound class -struct type_record { - PYBIND11_NOINLINE type_record() - : multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false) { } - - /// Handle to the parent scope - handle scope; - - /// Name of the class - const char *name = nullptr; - - // Pointer to RTTI type_info data structure - const std::type_info *type = nullptr; - - /// How large is the underlying C++ type? - size_t type_size = 0; - - /// How large is the type's holder? - size_t holder_size = 0; - - /// The global operator new can be overridden with a class-specific variant - void *(*operator_new)(size_t) = ::operator new; - - /// Function pointer to class_<..>::init_instance - void (*init_instance)(instance *, const void *) = nullptr; - - /// Function pointer to class_<..>::dealloc - void (*dealloc)(const detail::value_and_holder &) = nullptr; - - /// List of base classes of the newly created type - list bases; - - /// Optional docstring - const char *doc = nullptr; - - /// Custom metaclass (optional) - handle metaclass; - - /// Multiple inheritance marker - bool multiple_inheritance : 1; - - /// Does the class manage a __dict__? - bool dynamic_attr : 1; - - /// Does the class implement the buffer protocol? - bool buffer_protocol : 1; - - /// Is the default (unique_ptr) holder type used? - bool default_holder : 1; - - PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) { - auto base_info = detail::get_type_info(base, false); - if (!base_info) { - std::string tname(base.name()); - detail::clean_type_id(tname); - pybind11_fail("generic_type: type \"" + std::string(name) + - "\" referenced unknown base type \"" + tname + "\""); - } - - if (default_holder != base_info->default_holder) { - std::string tname(base.name()); - detail::clean_type_id(tname); - pybind11_fail("generic_type: type \"" + std::string(name) + "\" " + - (default_holder ? "does not have" : "has") + - " a non-default holder type while its base \"" + tname + "\" " + - (base_info->default_holder ? "does not" : "does")); - } - - bases.append((PyObject *) base_info->type); - - if (base_info->type->tp_dictoffset != 0) - dynamic_attr = true; - - if (caster) - base_info->implicit_casts.emplace_back(type, caster); - } -}; - -inline function_call::function_call(function_record &f, handle p) : - func(f), parent(p) { - args.reserve(f.nargs); - args_convert.reserve(f.nargs); -} - -/** - * Partial template specializations to process custom attributes provided to - * cpp_function_ and class_. These are either used to initialize the respective - * fields in the type_record and function_record data structures or executed at - * runtime to deal with custom call policies (e.g. keep_alive). - */ -template struct process_attribute; - -template struct process_attribute_default { - /// Default implementation: do nothing - static void init(const T &, function_record *) { } - static void init(const T &, type_record *) { } - static void precall(function_call &) { } - static void postcall(function_call &, handle) { } -}; - -/// Process an attribute specifying the function's name -template <> struct process_attribute : process_attribute_default { - static void init(const name &n, function_record *r) { r->name = const_cast(n.value); } -}; - -/// Process an attribute specifying the function's docstring -template <> struct process_attribute : process_attribute_default { - static void init(const doc &n, function_record *r) { r->doc = const_cast(n.value); } -}; - -/// Process an attribute specifying the function's docstring (provided as a C-style string) -template <> struct process_attribute : process_attribute_default { - static void init(const char *d, function_record *r) { r->doc = const_cast(d); } - static void init(const char *d, type_record *r) { r->doc = const_cast(d); } -}; -template <> struct process_attribute : process_attribute { }; - -/// Process an attribute indicating the function's return value policy -template <> struct process_attribute : process_attribute_default { - static void init(const return_value_policy &p, function_record *r) { r->policy = p; } -}; - -/// Process an attribute which indicates that this is an overloaded function associated with a given sibling -template <> struct process_attribute : process_attribute_default { - static void init(const sibling &s, function_record *r) { r->sibling = s.value; } -}; - -/// Process an attribute which indicates that this function is a method -template <> struct process_attribute : process_attribute_default { - static void init(const is_method &s, function_record *r) { r->is_method = true; r->scope = s.class_; } -}; - -/// Process an attribute which indicates the parent scope of a method -template <> struct process_attribute : process_attribute_default { - static void init(const scope &s, function_record *r) { r->scope = s.value; } -}; - -/// Process an attribute which indicates that this function is an operator -template <> struct process_attribute : process_attribute_default { - static void init(const is_operator &, function_record *r) { r->is_operator = true; } -}; - -/// Process a keyword argument attribute (*without* a default value) -template <> struct process_attribute : process_attribute_default { - static void init(const arg &a, function_record *r) { - if (r->is_method && r->args.empty()) - r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/); - r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); - } -}; - -/// Process a keyword argument attribute (*with* a default value) -template <> struct process_attribute : process_attribute_default { - static void init(const arg_v &a, function_record *r) { - if (r->is_method && r->args.empty()) - r->args.emplace_back("self", nullptr /*descr*/, handle() /*parent*/, true /*convert*/, false /*none not allowed*/); - - if (!a.value) { -#if !defined(NDEBUG) - std::string descr("'"); - if (a.name) descr += std::string(a.name) + ": "; - descr += a.type + "'"; - if (r->is_method) { - if (r->name) - descr += " in method '" + (std::string) str(r->scope) + "." + (std::string) r->name + "'"; - else - descr += " in method of '" + (std::string) str(r->scope) + "'"; - } else if (r->name) { - descr += " in function '" + (std::string) r->name + "'"; - } - pybind11_fail("arg(): could not convert default argument " - + descr + " into a Python object (type not registered yet?)"); -#else - pybind11_fail("arg(): could not convert default argument " - "into a Python object (type not registered yet?). " - "Compile in debug mode for more information."); -#endif - } - r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); - } -}; - -/// Process a parent class attribute. Single inheritance only (class_ itself already guarantees that) -template -struct process_attribute::value>> : process_attribute_default { - static void init(const handle &h, type_record *r) { r->bases.append(h); } -}; - -/// Process a parent class attribute (deprecated, does not support multiple inheritance) -template -struct process_attribute> : process_attribute_default> { - static void init(const base &, type_record *r) { r->add_base(typeid(T), nullptr); } -}; - -/// Process a multiple inheritance attribute -template <> -struct process_attribute : process_attribute_default { - static void init(const multiple_inheritance &, type_record *r) { r->multiple_inheritance = true; } -}; - -template <> -struct process_attribute : process_attribute_default { - static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } -}; - -template <> -struct process_attribute : process_attribute_default { - static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } -}; - -template <> -struct process_attribute : process_attribute_default { - static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; } -}; - - -/// Process an 'arithmetic' attribute for enums (does nothing here) -template <> -struct process_attribute : process_attribute_default {}; - -template -struct process_attribute> : process_attribute_default> { }; - -/** - * Process a keep_alive call policy -- invokes keep_alive_impl during the - * pre-call handler if both Nurse, Patient != 0 and use the post-call handler - * otherwise - */ -template struct process_attribute> : public process_attribute_default> { - template = 0> - static void precall(function_call &call) { keep_alive_impl(Nurse, Patient, call, handle()); } - template = 0> - static void postcall(function_call &, handle) { } - template = 0> - static void precall(function_call &) { } - template = 0> - static void postcall(function_call &call, handle ret) { keep_alive_impl(Nurse, Patient, call, ret); } -}; - -/// Recursively iterate over variadic template arguments -template struct process_attributes { - static void init(const Args&... args, function_record *r) { - int unused[] = { 0, (process_attribute::type>::init(args, r), 0) ... }; - ignore_unused(unused); - } - static void init(const Args&... args, type_record *r) { - int unused[] = { 0, (process_attribute::type>::init(args, r), 0) ... }; - ignore_unused(unused); - } - static void precall(function_call &call) { - int unused[] = { 0, (process_attribute::type>::precall(call), 0) ... }; - ignore_unused(unused); - } - static void postcall(function_call &call, handle fn_ret) { - int unused[] = { 0, (process_attribute::type>::postcall(call, fn_ret), 0) ... }; - ignore_unused(unused); - } -}; - -template -using is_call_guard = is_instantiation; - -/// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found) -template -using extract_guard_t = typename exactly_one_t, Extra...>::type; - -/// Check the number of named arguments at compile time -template ::value...), - size_t self = constexpr_sum(std::is_same::value...)> -constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) { - return named == 0 || (self + named + has_args + has_kwargs) == nargs; -} - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/buffer_info.h b/ppocr/postprocess/lanms/include/pybind11/buffer_info.h deleted file mode 100644 index 6d1167d2..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/buffer_info.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - pybind11/buffer_info.h: Python buffer object interface - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "common.h" - -NAMESPACE_BEGIN(pybind11) - -/// Information record describing a Python buffer object -struct buffer_info { - void *ptr = nullptr; // Pointer to the underlying storage - ssize_t itemsize = 0; // Size of individual items in bytes - ssize_t size = 0; // Total number of entries - std::string format; // For homogeneous buffers, this should be set to format_descriptor::format() - ssize_t ndim = 0; // Number of dimensions - std::vector shape; // Shape of the tensor (1 entry per dimension) - std::vector strides; // Number of entries between adjacent entries (for each per dimension) - - buffer_info() { } - - buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, - detail::any_container shape_in, detail::any_container strides_in) - : ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim), - shape(std::move(shape_in)), strides(std::move(strides_in)) { - if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) - pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length"); - for (size_t i = 0; i < (size_t) ndim; ++i) - size *= shape[i]; - } - - template - buffer_info(T *ptr, detail::any_container shape_in, detail::any_container strides_in) - : buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor::format(), static_cast(shape_in->size()), std::move(shape_in), std::move(strides_in)) { } - - buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size) - : buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}) { } - - template - buffer_info(T *ptr, ssize_t size) - : buffer_info(ptr, sizeof(T), format_descriptor::format(), size) { } - - explicit buffer_info(Py_buffer *view, bool ownview = true) - : buffer_info(view->buf, view->itemsize, view->format, view->ndim, - {view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}) { - this->view = view; - this->ownview = ownview; - } - - buffer_info(const buffer_info &) = delete; - buffer_info& operator=(const buffer_info &) = delete; - - buffer_info(buffer_info &&other) { - (*this) = std::move(other); - } - - buffer_info& operator=(buffer_info &&rhs) { - ptr = rhs.ptr; - itemsize = rhs.itemsize; - size = rhs.size; - format = std::move(rhs.format); - ndim = rhs.ndim; - shape = std::move(rhs.shape); - strides = std::move(rhs.strides); - std::swap(view, rhs.view); - std::swap(ownview, rhs.ownview); - return *this; - } - - ~buffer_info() { - if (view && ownview) { PyBuffer_Release(view); delete view; } - } - -private: - struct private_ctr_tag { }; - - buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim, - detail::any_container &&shape_in, detail::any_container &&strides_in) - : buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in)) { } - - Py_buffer *view = nullptr; - bool ownview = false; -}; - -NAMESPACE_BEGIN(detail) - -template struct compare_buffer_info { - static bool compare(const buffer_info& b) { - return b.format == format_descriptor::format() && b.itemsize == (ssize_t) sizeof(T); - } -}; - -template struct compare_buffer_info::value>> { - static bool compare(const buffer_info& b) { - return (size_t) b.itemsize == sizeof(T) && (b.format == format_descriptor::value || - ((sizeof(T) == sizeof(long)) && b.format == (std::is_unsigned::value ? "L" : "l")) || - ((sizeof(T) == sizeof(size_t)) && b.format == (std::is_unsigned::value ? "N" : "n"))); - } -}; - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/cast.h b/ppocr/postprocess/lanms/include/pybind11/cast.h deleted file mode 100644 index 5db03e2f..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/cast.h +++ /dev/null @@ -1,2058 +0,0 @@ -/* - pybind11/cast.h: Partial template specializations to cast between - C++ and Python types - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pytypes.h" -#include "typeid.h" -#include "descr.h" -#include -#include -#include - -#if defined(PYBIND11_CPP17) -# if defined(__has_include) -# if __has_include() -# define PYBIND11_HAS_STRING_VIEW -# endif -# elif defined(_MSC_VER) -# define PYBIND11_HAS_STRING_VIEW -# endif -#endif -#ifdef PYBIND11_HAS_STRING_VIEW -#include -#endif - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) -// Forward declarations: -inline PyTypeObject *make_static_property_type(); -inline PyTypeObject *make_default_metaclass(); -inline PyObject *make_object_base_type(PyTypeObject *metaclass); -struct value_and_holder; - -/// Additional type information which does not fit into the PyTypeObject -struct type_info { - PyTypeObject *type; - const std::type_info *cpptype; - size_t type_size, holder_size_in_ptrs; - void *(*operator_new)(size_t); - void (*init_instance)(instance *, const void *); - void (*dealloc)(const value_and_holder &v_h); - std::vector implicit_conversions; - std::vector> implicit_casts; - std::vector *direct_conversions; - buffer_info *(*get_buffer)(PyObject *, void *) = nullptr; - void *get_buffer_data = nullptr; - /* A simple type never occurs as a (direct or indirect) parent - * of a class that makes use of multiple inheritance */ - bool simple_type : 1; - /* True if there is no multiple inheritance in this type's inheritance tree */ - bool simple_ancestors : 1; - /* for base vs derived holder_type checks */ - bool default_holder : 1; -}; - -// Store the static internals pointer in a version-specific function so that we're guaranteed it -// will be distinct for modules compiled for different pybind11 versions. Without this, some -// compilers (i.e. gcc) can use the same static pointer storage location across different .so's, -// even though the `get_internals()` function itself is local to each shared object. -template -internals *&get_internals_ptr() { static internals *internals_ptr = nullptr; return internals_ptr; } - -PYBIND11_NOINLINE inline internals &get_internals() { - internals *&internals_ptr = get_internals_ptr(); - if (internals_ptr) - return *internals_ptr; - handle builtins(PyEval_GetBuiltins()); - const char *id = PYBIND11_INTERNALS_ID; - if (builtins.contains(id) && isinstance(builtins[id])) { - internals_ptr = *static_cast(capsule(builtins[id])); - } else { - internals_ptr = new internals(); - #if defined(WITH_THREAD) - PyEval_InitThreads(); - PyThreadState *tstate = PyThreadState_Get(); - internals_ptr->tstate = PyThread_create_key(); - PyThread_set_key_value(internals_ptr->tstate, tstate); - internals_ptr->istate = tstate->interp; - #endif - builtins[id] = capsule(&internals_ptr); - internals_ptr->registered_exception_translators.push_front( - [](std::exception_ptr p) -> void { - try { - if (p) std::rethrow_exception(p); - } catch (error_already_set &e) { e.restore(); return; - } catch (const builtin_exception &e) { e.set_error(); return; - } catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return; - } catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::invalid_argument &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return; - } catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; - } catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return; - } catch (...) { - PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!"); - return; - } - } - ); - internals_ptr->static_property_type = make_static_property_type(); - internals_ptr->default_metaclass = make_default_metaclass(); - internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass); - } - return *internals_ptr; -} - -/// A life support system for temporary objects created by `type_caster::load()`. -/// Adding a patient will keep it alive up until the enclosing function returns. -class loader_life_support { -public: - /// A new patient frame is created when a function is entered - loader_life_support() { - get_internals().loader_patient_stack.push_back(nullptr); - } - - /// ... and destroyed after it returns - ~loader_life_support() { - auto &stack = get_internals().loader_patient_stack; - if (stack.empty()) - pybind11_fail("loader_life_support: internal error"); - - auto ptr = stack.back(); - stack.pop_back(); - Py_CLEAR(ptr); - - // A heuristic to reduce the stack's capacity (e.g. after long recursive calls) - if (stack.capacity() > 16 && stack.size() != 0 && stack.capacity() / stack.size() > 2) - stack.shrink_to_fit(); - } - - /// This can only be used inside a pybind11-bound function, either by `argument_loader` - /// at argument preparation time or by `py::cast()` at execution time. - PYBIND11_NOINLINE static void add_patient(handle h) { - auto &stack = get_internals().loader_patient_stack; - if (stack.empty()) - throw cast_error("When called outside a bound function, py::cast() cannot " - "do Python -> C++ conversions which require the creation " - "of temporary values"); - - auto &list_ptr = stack.back(); - if (list_ptr == nullptr) { - list_ptr = PyList_New(1); - if (!list_ptr) - pybind11_fail("loader_life_support: error allocating list"); - PyList_SET_ITEM(list_ptr, 0, h.inc_ref().ptr()); - } else { - auto result = PyList_Append(list_ptr, h.ptr()); - if (result == -1) - pybind11_fail("loader_life_support: error adding patient"); - } - } -}; - -// Gets the cache entry for the given type, creating it if necessary. The return value is the pair -// returned by emplace, i.e. an iterator for the entry and a bool set to `true` if the entry was -// just created. -inline std::pair all_type_info_get_cache(PyTypeObject *type); - -// Populates a just-created cache entry. -PYBIND11_NOINLINE inline void all_type_info_populate(PyTypeObject *t, std::vector &bases) { - std::vector check; - for (handle parent : reinterpret_borrow(t->tp_bases)) - check.push_back((PyTypeObject *) parent.ptr()); - - auto const &type_dict = get_internals().registered_types_py; - for (size_t i = 0; i < check.size(); i++) { - auto type = check[i]; - // Ignore Python2 old-style class super type: - if (!PyType_Check((PyObject *) type)) continue; - - // Check `type` in the current set of registered python types: - auto it = type_dict.find(type); - if (it != type_dict.end()) { - // We found a cache entry for it, so it's either pybind-registered or has pre-computed - // pybind bases, but we have to make sure we haven't already seen the type(s) before: we - // want to follow Python/virtual C++ rules that there should only be one instance of a - // common base. - for (auto *tinfo : it->second) { - // NB: Could use a second set here, rather than doing a linear search, but since - // having a large number of immediate pybind11-registered types seems fairly - // unlikely, that probably isn't worthwhile. - bool found = false; - for (auto *known : bases) { - if (known == tinfo) { found = true; break; } - } - if (!found) bases.push_back(tinfo); - } - } - else if (type->tp_bases) { - // It's some python type, so keep follow its bases classes to look for one or more - // registered types - if (i + 1 == check.size()) { - // When we're at the end, we can pop off the current element to avoid growing - // `check` when adding just one base (which is typical--.e. when there is no - // multiple inheritance) - check.pop_back(); - i--; - } - for (handle parent : reinterpret_borrow(type->tp_bases)) - check.push_back((PyTypeObject *) parent.ptr()); - } - } -} - -/** - * Extracts vector of type_info pointers of pybind-registered roots of the given Python type. Will - * be just 1 pybind type for the Python type of a pybind-registered class, or for any Python-side - * derived class that uses single inheritance. Will contain as many types as required for a Python - * class that uses multiple inheritance to inherit (directly or indirectly) from multiple - * pybind-registered classes. Will be empty if neither the type nor any base classes are - * pybind-registered. - * - * The value is cached for the lifetime of the Python type. - */ -inline const std::vector &all_type_info(PyTypeObject *type) { - auto ins = all_type_info_get_cache(type); - if (ins.second) - // New cache entry: populate it - all_type_info_populate(type, ins.first->second); - - return ins.first->second; -} - -/** - * Gets a single pybind11 type info for a python type. Returns nullptr if neither the type nor any - * ancestors are pybind11-registered. Throws an exception if there are multiple bases--use - * `all_type_info` instead if you want to support multiple bases. - */ -PYBIND11_NOINLINE inline detail::type_info* get_type_info(PyTypeObject *type) { - auto &bases = all_type_info(type); - if (bases.size() == 0) - return nullptr; - if (bases.size() > 1) - pybind11_fail("pybind11::detail::get_type_info: type has multiple pybind11-registered bases"); - return bases.front(); -} - -PYBIND11_NOINLINE inline detail::type_info *get_type_info(const std::type_info &tp, - bool throw_if_missing = false) { - auto &types = get_internals().registered_types_cpp; - - auto it = types.find(std::type_index(tp)); - if (it != types.end()) - return (detail::type_info *) it->second; - if (throw_if_missing) { - std::string tname = tp.name(); - detail::clean_type_id(tname); - pybind11_fail("pybind11::detail::get_type_info: unable to find type info for \"" + tname + "\""); - } - return nullptr; -} - -PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp, bool throw_if_missing) { - detail::type_info *type_info = get_type_info(tp, throw_if_missing); - return handle(type_info ? ((PyObject *) type_info->type) : nullptr); -} - -struct value_and_holder { - instance *inst; - size_t index; - const detail::type_info *type; - void **vh; - - value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) : - inst{i}, index{index}, type{type}, - vh{inst->simple_layout ? inst->simple_value_holder : &inst->nonsimple.values_and_holders[vpos]} - {} - - // Used for past-the-end iterator - value_and_holder(size_t index) : index{index} {} - - template V *&value_ptr() const { - return reinterpret_cast(vh[0]); - } - // True if this `value_and_holder` has a non-null value pointer - explicit operator bool() const { return value_ptr(); } - - template H &holder() const { - return reinterpret_cast(vh[1]); - } - bool holder_constructed() const { - return inst->simple_layout - ? inst->simple_holder_constructed - : inst->nonsimple.status[index] & instance::status_holder_constructed; - } - void set_holder_constructed() { - if (inst->simple_layout) - inst->simple_holder_constructed = true; - else - inst->nonsimple.status[index] |= instance::status_holder_constructed; - } - bool instance_registered() const { - return inst->simple_layout - ? inst->simple_instance_registered - : inst->nonsimple.status[index] & instance::status_instance_registered; - } - void set_instance_registered() { - if (inst->simple_layout) - inst->simple_instance_registered = true; - else - inst->nonsimple.status[index] |= instance::status_instance_registered; - } -}; - -// Container for accessing and iterating over an instance's values/holders -struct values_and_holders { -private: - instance *inst; - using type_vec = std::vector; - const type_vec &tinfo; - -public: - values_and_holders(instance *inst) : inst{inst}, tinfo(all_type_info(Py_TYPE(inst))) {} - - struct iterator { - private: - instance *inst; - const type_vec *types; - value_and_holder curr; - friend struct values_and_holders; - iterator(instance *inst, const type_vec *tinfo) - : inst{inst}, types{tinfo}, - curr(inst /* instance */, - types->empty() ? nullptr : (*types)[0] /* type info */, - 0, /* vpos: (non-simple types only): the first vptr comes first */ - 0 /* index */) - {} - // Past-the-end iterator: - iterator(size_t end) : curr(end) {} - public: - bool operator==(const iterator &other) { return curr.index == other.curr.index; } - bool operator!=(const iterator &other) { return curr.index != other.curr.index; } - iterator &operator++() { - if (!inst->simple_layout) - curr.vh += 1 + (*types)[curr.index]->holder_size_in_ptrs; - ++curr.index; - curr.type = curr.index < types->size() ? (*types)[curr.index] : nullptr; - return *this; - } - value_and_holder &operator*() { return curr; } - value_and_holder *operator->() { return &curr; } - }; - - iterator begin() { return iterator(inst, &tinfo); } - iterator end() { return iterator(tinfo.size()); } - - iterator find(const type_info *find_type) { - auto it = begin(), endit = end(); - while (it != endit && it->type != find_type) ++it; - return it; - } - - size_t size() { return tinfo.size(); } -}; - -/** - * Extracts C++ value and holder pointer references from an instance (which may contain multiple - * values/holders for python-side multiple inheritance) that match the given type. Throws an error - * if the given type (or ValueType, if omitted) is not a pybind11 base of the given instance. If - * `find_type` is omitted (or explicitly specified as nullptr) the first value/holder are returned, - * regardless of type (and the resulting .type will be nullptr). - * - * The returned object should be short-lived: in particular, it must not outlive the called-upon - * instance. - */ -PYBIND11_NOINLINE inline value_and_holder instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/) { - // Optimize common case: - if (!find_type || Py_TYPE(this) == find_type->type) - return value_and_holder(this, find_type, 0, 0); - - detail::values_and_holders vhs(this); - auto it = vhs.find(find_type); - if (it != vhs.end()) - return *it; - -#if defined(NDEBUG) - pybind11_fail("pybind11::detail::instance::get_value_and_holder: " - "type is not a pybind11 base of the given instance " - "(compile in debug mode for type details)"); -#else - pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" + - std::string(find_type->type->tp_name) + "' is not a pybind11 base of the given `" + - std::string(Py_TYPE(this)->tp_name) + "' instance"); -#endif -} - -PYBIND11_NOINLINE inline void instance::allocate_layout() { - auto &tinfo = all_type_info(Py_TYPE(this)); - - const size_t n_types = tinfo.size(); - - if (n_types == 0) - pybind11_fail("instance allocation failed: new instance has no pybind11-registered base types"); - - simple_layout = - n_types == 1 && tinfo.front()->holder_size_in_ptrs <= instance_simple_holder_in_ptrs(); - - // Simple path: no python-side multiple inheritance, and a small-enough holder - if (simple_layout) { - simple_value_holder[0] = nullptr; - simple_holder_constructed = false; - simple_instance_registered = false; - } - else { // multiple base types or a too-large holder - // Allocate space to hold: [v1*][h1][v2*][h2]...[bb...] where [vN*] is a value pointer, - // [hN] is the (uninitialized) holder instance for value N, and [bb...] is a set of bool - // values that tracks whether each associated holder has been initialized. Each [block] is - // padded, if necessary, to an integer multiple of sizeof(void *). - size_t space = 0; - for (auto t : tinfo) { - space += 1; // value pointer - space += t->holder_size_in_ptrs; // holder instance - } - size_t flags_at = space; - space += size_in_ptrs(n_types); // status bytes (holder_constructed and instance_registered) - - // Allocate space for flags, values, and holders, and initialize it to 0 (flags and values, - // in particular, need to be 0). Use Python's memory allocation functions: in Python 3.6 - // they default to using pymalloc, which is designed to be efficient for small allocations - // like the one we're doing here; in earlier versions (and for larger allocations) they are - // just wrappers around malloc. -#if PY_VERSION_HEX >= 0x03050000 - nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *)); - if (!nonsimple.values_and_holders) throw std::bad_alloc(); -#else - nonsimple.values_and_holders = (void **) PyMem_New(void *, space); - if (!nonsimple.values_and_holders) throw std::bad_alloc(); - std::memset(nonsimple.values_and_holders, 0, space * sizeof(void *)); -#endif - nonsimple.status = reinterpret_cast(&nonsimple.values_and_holders[flags_at]); - } - owned = true; -} - -PYBIND11_NOINLINE inline void instance::deallocate_layout() { - if (!simple_layout) - PyMem_Free(nonsimple.values_and_holders); -} - -PYBIND11_NOINLINE inline bool isinstance_generic(handle obj, const std::type_info &tp) { - handle type = detail::get_type_handle(tp, false); - if (!type) - return false; - return isinstance(obj, type); -} - -PYBIND11_NOINLINE inline std::string error_string() { - if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred"); - return "Unknown internal error occurred"; - } - - error_scope scope; // Preserve error state - - std::string errorString; - if (scope.type) { - errorString += handle(scope.type).attr("__name__").cast(); - errorString += ": "; - } - if (scope.value) - errorString += (std::string) str(scope.value); - - PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace); - -#if PY_MAJOR_VERSION >= 3 - if (scope.trace != nullptr) - PyException_SetTraceback(scope.value, scope.trace); -#endif - -#if !defined(PYPY_VERSION) - if (scope.trace) { - PyTracebackObject *trace = (PyTracebackObject *) scope.trace; - - /* Get the deepest trace possible */ - while (trace->tb_next) - trace = trace->tb_next; - - PyFrameObject *frame = trace->tb_frame; - errorString += "\n\nAt:\n"; - while (frame) { - int lineno = PyFrame_GetLineNumber(frame); - errorString += - " " + handle(frame->f_code->co_filename).cast() + - "(" + std::to_string(lineno) + "): " + - handle(frame->f_code->co_name).cast() + "\n"; - frame = frame->f_back; - } - trace = trace->tb_next; - } -#endif - - return errorString; -} - -PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr, const detail::type_info *type ) { - auto &instances = get_internals().registered_instances; - auto range = instances.equal_range(ptr); - for (auto it = range.first; it != range.second; ++it) { - for (auto vh : values_and_holders(it->second)) { - if (vh.type == type) - return handle((PyObject *) it->second); - } - } - return handle(); -} - -inline PyThreadState *get_thread_state_unchecked() { -#if defined(PYPY_VERSION) - return PyThreadState_GET(); -#elif PY_VERSION_HEX < 0x03000000 - return _PyThreadState_Current; -#elif PY_VERSION_HEX < 0x03050000 - return (PyThreadState*) _Py_atomic_load_relaxed(&_PyThreadState_Current); -#elif PY_VERSION_HEX < 0x03050200 - return (PyThreadState*) _PyThreadState_Current.value; -#else - return _PyThreadState_UncheckedGet(); -#endif -} - -// Forward declarations -inline void keep_alive_impl(handle nurse, handle patient); -inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value = true); - -class type_caster_generic { -public: - PYBIND11_NOINLINE type_caster_generic(const std::type_info &type_info) - : typeinfo(get_type_info(type_info)) { } - - bool load(handle src, bool convert) { - return load_impl(src, convert); - } - - PYBIND11_NOINLINE static handle cast(const void *_src, return_value_policy policy, handle parent, - const detail::type_info *tinfo, - void *(*copy_constructor)(const void *), - void *(*move_constructor)(const void *), - const void *existing_holder = nullptr) { - if (!tinfo) // no type info: error will be set already - return handle(); - - void *src = const_cast(_src); - if (src == nullptr) - return none().release(); - - auto it_instances = get_internals().registered_instances.equal_range(src); - for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) { - for (auto instance_type : detail::all_type_info(Py_TYPE(it_i->second))) { - if (instance_type && instance_type == tinfo) - return handle((PyObject *) it_i->second).inc_ref(); - } - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type, false /* don't allocate value */)); - auto wrapper = reinterpret_cast(inst.ptr()); - wrapper->owned = false; - void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); - - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::take_ownership: - valueptr = src; - wrapper->owned = true; - break; - - case return_value_policy::automatic_reference: - case return_value_policy::reference: - valueptr = src; - wrapper->owned = false; - break; - - case return_value_policy::copy: - if (copy_constructor) - valueptr = copy_constructor(src); - else - throw cast_error("return_value_policy = copy, but the " - "object is non-copyable!"); - wrapper->owned = true; - break; - - case return_value_policy::move: - if (move_constructor) - valueptr = move_constructor(src); - else if (copy_constructor) - valueptr = copy_constructor(src); - else - throw cast_error("return_value_policy = move, but the " - "object is neither movable nor copyable!"); - wrapper->owned = true; - break; - - case return_value_policy::reference_internal: - valueptr = src; - wrapper->owned = false; - keep_alive_impl(inst, parent); - break; - - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - } - - tinfo->init_instance(wrapper, existing_holder); - - return inst.release(); - } - -protected: - - // Base methods for generic caster; there are overridden in copyable_holder_caster - void load_value(const value_and_holder &v_h) { - value = v_h.value_ptr(); - } - bool try_implicit_casts(handle src, bool convert) { - for (auto &cast : typeinfo->implicit_casts) { - type_caster_generic sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - value = cast.second(sub_caster.value); - return true; - } - } - return false; - } - bool try_direct_conversions(handle src) { - for (auto &converter : *typeinfo->direct_conversions) { - if (converter(src.ptr(), value)) - return true; - } - return false; - } - void check_holder_compat() {} - - // Implementation of `load`; this takes the type of `this` so that it can dispatch the relevant - // bits of code between here and copyable_holder_caster where the two classes need different - // logic (without having to resort to virtual inheritance). - template - PYBIND11_NOINLINE bool load_impl(handle src, bool convert) { - if (!src || !typeinfo) - return false; - if (src.is_none()) { - // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) return false; - value = nullptr; - return true; - } - - auto &this_ = static_cast(*this); - this_.check_holder_compat(); - - PyTypeObject *srctype = Py_TYPE(src.ptr()); - - // Case 1: If src is an exact type match for the target type then we can reinterpret_cast - // the instance's value pointer to the target type: - if (srctype == typeinfo->type) { - this_.load_value(reinterpret_cast(src.ptr())->get_value_and_holder()); - return true; - } - // Case 2: We have a derived class - else if (PyType_IsSubtype(srctype, typeinfo->type)) { - auto &bases = all_type_info(srctype); - bool no_cpp_mi = typeinfo->simple_type; - - // Case 2a: the python type is a Python-inherited derived class that inherits from just - // one simple (no MI) pybind11 class, or is an exact match, so the C++ instance is of - // the right type and we can use reinterpret_cast. - // (This is essentially the same as case 2b, but because not using multiple inheritance - // is extremely common, we handle it specially to avoid the loop iterator and type - // pointer lookup overhead) - if (bases.size() == 1 && (no_cpp_mi || bases.front()->type == typeinfo->type)) { - this_.load_value(reinterpret_cast(src.ptr())->get_value_and_holder()); - return true; - } - // Case 2b: the python type inherits from multiple C++ bases. Check the bases to see if - // we can find an exact match (or, for a simple C++ type, an inherited match); if so, we - // can safely reinterpret_cast to the relevant pointer. - else if (bases.size() > 1) { - for (auto base : bases) { - if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type) : base->type == typeinfo->type) { - this_.load_value(reinterpret_cast(src.ptr())->get_value_and_holder(base)); - return true; - } - } - } - - // Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type match - // in the registered bases, above, so try implicit casting (needed for proper C++ casting - // when MI is involved). - if (this_.try_implicit_casts(src, convert)) - return true; - } - - // Perform an implicit conversion - if (convert) { - for (auto &converter : typeinfo->implicit_conversions) { - auto temp = reinterpret_steal(converter(src.ptr(), typeinfo->type)); - if (load_impl(temp, false)) { - loader_life_support::add_patient(temp); - return true; - } - } - if (this_.try_direct_conversions(src)) - return true; - } - return false; - } - - - // Called to do type lookup and wrap the pointer and type in a pair when a dynamic_cast - // isn't needed or can't be used. If the type is unknown, sets the error and returns a pair - // with .second = nullptr. (p.first = nullptr is not an error: it becomes None). - PYBIND11_NOINLINE static std::pair src_and_type( - const void *src, const std::type_info &cast_type, const std::type_info *rtti_type = nullptr) { - auto &internals = get_internals(); - auto it = internals.registered_types_cpp.find(std::type_index(cast_type)); - if (it != internals.registered_types_cpp.end()) - return {src, (const type_info *) it->second}; - - // Not found, set error: - std::string tname = rtti_type ? rtti_type->name() : cast_type.name(); - detail::clean_type_id(tname); - std::string msg = "Unregistered type : " + tname; - PyErr_SetString(PyExc_TypeError, msg.c_str()); - return {nullptr, nullptr}; - } - - const type_info *typeinfo = nullptr; - void *value = nullptr; -}; - -/** - * Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster - * needs to provide `operator T*()` and `operator T&()` operators. - * - * If the type supports moving the value away via an `operator T&&() &&` method, it should use - * `movable_cast_op_type` instead. - */ -template -using cast_op_type = - conditional_t>::value, - typename std::add_pointer>::type, - typename std::add_lvalue_reference>::type>; - -/** - * Determine suitable casting operator for a type caster with a movable value. Such a type caster - * needs to provide `operator T*()`, `operator T&()`, and `operator T&&() &&`. The latter will be - * called in appropriate contexts where the value can be moved rather than copied. - * - * These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro. - */ -template -using movable_cast_op_type = - conditional_t::type>::value, - typename std::add_pointer>::type, - conditional_t::value, - typename std::add_rvalue_reference>::type, - typename std::add_lvalue_reference>::type>>; - -// std::is_copy_constructible isn't quite enough: it lets std::vector (and similar) through when -// T is non-copyable, but code containing such a copy constructor fails to actually compile. -template struct is_copy_constructible : std::is_copy_constructible {}; - -// Specialization for types that appear to be copy constructible but also look like stl containers -// (we specifically check for: has `value_type` and `reference` with `reference = value_type&`): if -// so, copy constructability depends on whether the value_type is copy constructible. -template struct is_copy_constructible, - std::is_same - >::value>> : is_copy_constructible {}; - -#if !defined(PYBIND11_CPP17) -// Likewise for std::pair before C++17 (which mandates that the copy constructor not exist when the -// two types aren't themselves copy constructible). -template struct is_copy_constructible> - : all_of, is_copy_constructible> {}; -#endif - -/// Generic type caster for objects stored on the heap -template class type_caster_base : public type_caster_generic { - using itype = intrinsic_t; -public: - static PYBIND11_DESCR name() { return type_descr(_()); } - - type_caster_base() : type_caster_base(typeid(type)) { } - explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) { } - - static handle cast(const itype &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) - policy = return_value_policy::copy; - return cast(&src, policy, parent); - } - - static handle cast(itype &&src, return_value_policy, handle parent) { - return cast(&src, return_value_policy::move, parent); - } - - // Returns a (pointer, type_info) pair taking care of necessary RTTI type lookup for a - // polymorphic type. If the instance isn't derived, returns the non-RTTI base version. - template ::value, int> = 0> - static std::pair src_and_type(const itype *src) { - const void *vsrc = src; - auto &internals = get_internals(); - auto &cast_type = typeid(itype); - const std::type_info *instance_type = nullptr; - if (vsrc) { - instance_type = &typeid(*src); - if (!same_type(cast_type, *instance_type)) { - // This is a base pointer to a derived type; if it is a pybind11-registered type, we - // can get the correct derived pointer (which may be != base pointer) by a - // dynamic_cast to most derived type: - auto it = internals.registered_types_cpp.find(std::type_index(*instance_type)); - if (it != internals.registered_types_cpp.end()) - return {dynamic_cast(src), (const type_info *) it->second}; - } - } - // Otherwise we have either a nullptr, an `itype` pointer, or an unknown derived pointer, so - // don't do a cast - return type_caster_generic::src_and_type(vsrc, cast_type, instance_type); - } - - // Non-polymorphic type, so no dynamic casting; just call the generic version directly - template ::value, int> = 0> - static std::pair src_and_type(const itype *src) { - return type_caster_generic::src_and_type(src, typeid(itype)); - } - - static handle cast(const itype *src, return_value_policy policy, handle parent) { - auto st = src_and_type(src); - return type_caster_generic::cast( - st.first, policy, parent, st.second, - make_copy_constructor(src), make_move_constructor(src)); - } - - static handle cast_holder(const itype *src, const void *holder) { - auto st = src_and_type(src); - return type_caster_generic::cast( - st.first, return_value_policy::take_ownership, {}, st.second, - nullptr, nullptr, holder); - } - - template using cast_op_type = cast_op_type; - - operator itype*() { return (type *) value; } - operator itype&() { if (!value) throw reference_cast_error(); return *((itype *) value); } - -protected: - using Constructor = void *(*)(const void *); - - /* Only enabled when the types are {copy,move}-constructible *and* when the type - does not have a private operator new implementation. */ - template ::value>> - static auto make_copy_constructor(const T *x) -> decltype(new T(*x), Constructor{}) { - return [](const void *arg) -> void * { - return new T(*reinterpret_cast(arg)); - }; - } - - template ::value>> - static auto make_move_constructor(const T *x) -> decltype(new T(std::move(*const_cast(x))), Constructor{}) { - return [](const void *arg) -> void * { - return new T(std::move(*const_cast(reinterpret_cast(arg)))); - }; - } - - static Constructor make_copy_constructor(...) { return nullptr; } - static Constructor make_move_constructor(...) { return nullptr; } -}; - -template class type_caster : public type_caster_base { }; -template using make_caster = type_caster>; - -// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T -template typename make_caster::template cast_op_type cast_op(make_caster &caster) { - return caster.operator typename make_caster::template cast_op_type(); -} -template typename make_caster::template cast_op_type::type> -cast_op(make_caster &&caster) { - return std::move(caster).operator - typename make_caster::template cast_op_type::type>(); -} - -template class type_caster> { -private: - using caster_t = make_caster; - caster_t subcaster; - using subcaster_cast_op_type = typename caster_t::template cast_op_type; - static_assert(std::is_same::type &, subcaster_cast_op_type>::value, - "std::reference_wrapper caster requires T to have a caster with an `T &` operator"); -public: - bool load(handle src, bool convert) { return subcaster.load(src, convert); } - static PYBIND11_DESCR name() { return caster_t::name(); } - static handle cast(const std::reference_wrapper &src, return_value_policy policy, handle parent) { - // It is definitely wrong to take ownership of this pointer, so mask that rvp - if (policy == return_value_policy::take_ownership || policy == return_value_policy::automatic) - policy = return_value_policy::automatic_reference; - return caster_t::cast(&src.get(), policy, parent); - } - template using cast_op_type = std::reference_wrapper; - operator std::reference_wrapper() { return subcaster.operator subcaster_cast_op_type&(); } -}; - -#define PYBIND11_TYPE_CASTER(type, py_name) \ - protected: \ - type value; \ - public: \ - static PYBIND11_DESCR name() { return type_descr(py_name); } \ - template >::value, int> = 0> \ - static handle cast(T_ *src, return_value_policy policy, handle parent) { \ - if (!src) return none().release(); \ - if (policy == return_value_policy::take_ownership) { \ - auto h = cast(std::move(*src), policy, parent); delete src; return h; \ - } else { \ - return cast(*src, policy, parent); \ - } \ - } \ - operator type*() { return &value; } \ - operator type&() { return value; } \ - operator type&&() && { return std::move(value); } \ - template using cast_op_type = pybind11::detail::movable_cast_op_type - - -template using is_std_char_type = any_of< - std::is_same, /* std::string */ - std::is_same, /* std::u16string */ - std::is_same, /* std::u32string */ - std::is_same /* std::wstring */ ->; - -template -struct type_caster::value && !is_std_char_type::value>> { - using _py_type_0 = conditional_t; - using _py_type_1 = conditional_t::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>; - using py_type = conditional_t::value, double, _py_type_1>; -public: - - bool load(handle src, bool convert) { - py_type py_value; - - if (!src) - return false; - - if (std::is_floating_point::value) { - if (convert || PyFloat_Check(src.ptr())) - py_value = (py_type) PyFloat_AsDouble(src.ptr()); - else - return false; - } else if (PyFloat_Check(src.ptr())) { - return false; - } else if (std::is_unsigned::value) { - py_value = as_unsigned(src.ptr()); - } else { // signed integer: - py_value = sizeof(T) <= sizeof(long) - ? (py_type) PyLong_AsLong(src.ptr()) - : (py_type) PYBIND11_LONG_AS_LONGLONG(src.ptr()); - } - - bool py_err = py_value == (py_type) -1 && PyErr_Occurred(); - if (py_err || (std::is_integral::value && sizeof(py_type) != sizeof(T) && - (py_value < (py_type) std::numeric_limits::min() || - py_value > (py_type) std::numeric_limits::max()))) { - bool type_error = py_err && PyErr_ExceptionMatches( -#if PY_VERSION_HEX < 0x03000000 && !defined(PYPY_VERSION) - PyExc_SystemError -#else - PyExc_TypeError -#endif - ); - PyErr_Clear(); - if (type_error && convert && PyNumber_Check(src.ptr())) { - auto tmp = reinterpret_borrow(std::is_floating_point::value - ? PyNumber_Float(src.ptr()) - : PyNumber_Long(src.ptr())); - PyErr_Clear(); - return load(tmp, false); - } - return false; - } - - value = (T) py_value; - return true; - } - - static handle cast(T src, return_value_policy /* policy */, handle /* parent */) { - if (std::is_floating_point::value) { - return PyFloat_FromDouble((double) src); - } else if (sizeof(T) <= sizeof(long)) { - if (std::is_signed::value) - return PyLong_FromLong((long) src); - else - return PyLong_FromUnsignedLong((unsigned long) src); - } else { - if (std::is_signed::value) - return PyLong_FromLongLong((long long) src); - else - return PyLong_FromUnsignedLongLong((unsigned long long) src); - } - } - - PYBIND11_TYPE_CASTER(T, _::value>("int", "float")); -}; - -template struct void_caster { -public: - bool load(handle src, bool) { - if (src && src.is_none()) - return true; - return false; - } - static handle cast(T, return_value_policy /* policy */, handle /* parent */) { - return none().inc_ref(); - } - PYBIND11_TYPE_CASTER(T, _("None")); -}; - -template <> class type_caster : public void_caster {}; - -template <> class type_caster : public type_caster { -public: - using type_caster::cast; - - bool load(handle h, bool) { - if (!h) { - return false; - } else if (h.is_none()) { - value = nullptr; - return true; - } - - /* Check if this is a capsule */ - if (isinstance(h)) { - value = reinterpret_borrow(h); - return true; - } - - /* Check if this is a C++ type */ - auto &bases = all_type_info((PyTypeObject *) h.get_type().ptr()); - if (bases.size() == 1) { // Only allowing loading from a single-value type - value = values_and_holders(reinterpret_cast(h.ptr())).begin()->value_ptr(); - return true; - } - - /* Fail */ - return false; - } - - static handle cast(const void *ptr, return_value_policy /* policy */, handle /* parent */) { - if (ptr) - return capsule(ptr).release(); - else - return none().inc_ref(); - } - - template using cast_op_type = void*&; - operator void *&() { return value; } - static PYBIND11_DESCR name() { return type_descr(_("capsule")); } -private: - void *value = nullptr; -}; - -template <> class type_caster : public void_caster { }; - -template <> class type_caster { -public: - bool load(handle src, bool convert) { - if (!src) return false; - else if (src.ptr() == Py_True) { value = true; return true; } - else if (src.ptr() == Py_False) { value = false; return true; } - else if (convert || !strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name)) { - // (allow non-implicit conversion for numpy booleans) - - Py_ssize_t res = -1; - if (src.is_none()) { - res = 0; // None is implicitly converted to False - } - #if defined(PYPY_VERSION) - // On PyPy, check that "__bool__" (or "__nonzero__" on Python 2.7) attr exists - else if (hasattr(src, PYBIND11_BOOL_ATTR)) { - res = PyObject_IsTrue(src.ptr()); - } - #else - // Alternate approach for CPython: this does the same as the above, but optimized - // using the CPython API so as to avoid an unneeded attribute lookup. - else if (auto tp_as_number = src.ptr()->ob_type->tp_as_number) { - if (PYBIND11_NB_BOOL(tp_as_number)) { - res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); - } - } - #endif - if (res == 0 || res == 1) { - value = (bool) res; - return true; - } - } - return false; - } - static handle cast(bool src, return_value_policy /* policy */, handle /* parent */) { - return handle(src ? Py_True : Py_False).inc_ref(); - } - PYBIND11_TYPE_CASTER(bool, _("bool")); -}; - -// Helper class for UTF-{8,16,32} C++ stl strings: -template struct string_caster { - using CharT = typename StringType::value_type; - - // Simplify life by being able to assume standard char sizes (the standard only guarantees - // minimums, but Python requires exact sizes) - static_assert(!std::is_same::value || sizeof(CharT) == 1, "Unsupported char size != 1"); - static_assert(!std::is_same::value || sizeof(CharT) == 2, "Unsupported char16_t size != 2"); - static_assert(!std::is_same::value || sizeof(CharT) == 4, "Unsupported char32_t size != 4"); - // wchar_t can be either 16 bits (Windows) or 32 (everywhere else) - static_assert(!std::is_same::value || sizeof(CharT) == 2 || sizeof(CharT) == 4, - "Unsupported wchar_t size != 2/4"); - static constexpr size_t UTF_N = 8 * sizeof(CharT); - - bool load(handle src, bool) { -#if PY_MAJOR_VERSION < 3 - object temp; -#endif - handle load_src = src; - if (!src) { - return false; - } else if (!PyUnicode_Check(load_src.ptr())) { -#if PY_MAJOR_VERSION >= 3 - return load_bytes(load_src); -#else - if (sizeof(CharT) == 1) { - return load_bytes(load_src); - } - - // The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false - if (!PYBIND11_BYTES_CHECK(load_src.ptr())) - return false; - - temp = reinterpret_steal(PyUnicode_FromObject(load_src.ptr())); - if (!temp) { PyErr_Clear(); return false; } - load_src = temp; -#endif - } - - object utfNbytes = reinterpret_steal(PyUnicode_AsEncodedString( - load_src.ptr(), UTF_N == 8 ? "utf-8" : UTF_N == 16 ? "utf-16" : "utf-32", nullptr)); - if (!utfNbytes) { PyErr_Clear(); return false; } - - const CharT *buffer = reinterpret_cast(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr())); - size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT); - if (UTF_N > 8) { buffer++; length--; } // Skip BOM for UTF-16/32 - value = StringType(buffer, length); - - // If we're loading a string_view we need to keep the encoded Python object alive: - if (IsView) - loader_life_support::add_patient(utfNbytes); - - return true; - } - - static handle cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) { - const char *buffer = reinterpret_cast(src.data()); - ssize_t nbytes = ssize_t(src.size() * sizeof(CharT)); - handle s = decode_utfN(buffer, nbytes); - if (!s) throw error_already_set(); - return s; - } - - PYBIND11_TYPE_CASTER(StringType, _(PYBIND11_STRING_NAME)); - -private: - static handle decode_utfN(const char *buffer, ssize_t nbytes) { -#if !defined(PYPY_VERSION) - return - UTF_N == 8 ? PyUnicode_DecodeUTF8(buffer, nbytes, nullptr) : - UTF_N == 16 ? PyUnicode_DecodeUTF16(buffer, nbytes, nullptr, nullptr) : - PyUnicode_DecodeUTF32(buffer, nbytes, nullptr, nullptr); -#else - // PyPy seems to have multiple problems related to PyUnicode_UTF*: the UTF8 version - // sometimes segfaults for unknown reasons, while the UTF16 and 32 versions require a - // non-const char * arguments, which is also a nuissance, so bypass the whole thing by just - // passing the encoding as a string value, which works properly: - return PyUnicode_Decode(buffer, nbytes, UTF_N == 8 ? "utf-8" : UTF_N == 16 ? "utf-16" : "utf-32", nullptr); -#endif - } - - // When loading into a std::string or char*, accept a bytes object as-is (i.e. - // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. - // which supports loading a unicode from a str, doesn't take this path. - template - bool load_bytes(enable_if_t src) { - if (PYBIND11_BYTES_CHECK(src.ptr())) { - // We were passed a Python 3 raw bytes; accept it into a std::string or char* - // without any encoding attempt. - const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr()); - if (bytes) { - value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr())); - return true; - } - } - - return false; - } - - template - bool load_bytes(enable_if_t) { return false; } -}; - -template -struct type_caster, enable_if_t::value>> - : string_caster> {}; - -#ifdef PYBIND11_HAS_STRING_VIEW -template -struct type_caster, enable_if_t::value>> - : string_caster, true> {}; -#endif - -// Type caster for C-style strings. We basically use a std::string type caster, but also add the -// ability to use None as a nullptr char* (which the string caster doesn't allow). -template struct type_caster::value>> { - using StringType = std::basic_string; - using StringCaster = type_caster; - StringCaster str_caster; - bool none = false; -public: - bool load(handle src, bool convert) { - if (!src) return false; - if (src.is_none()) { - // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) return false; - none = true; - return true; - } - return str_caster.load(src, convert); - } - - static handle cast(const CharT *src, return_value_policy policy, handle parent) { - if (src == nullptr) return pybind11::none().inc_ref(); - return StringCaster::cast(StringType(src), policy, parent); - } - - static handle cast(CharT src, return_value_policy policy, handle parent) { - if (std::is_same::value) { - handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr); - if (!s) throw error_already_set(); - return s; - } - return StringCaster::cast(StringType(1, src), policy, parent); - } - - operator CharT*() { return none ? nullptr : const_cast(static_cast(str_caster).c_str()); } - operator CharT() { - if (none) - throw value_error("Cannot convert None to a character"); - - auto &value = static_cast(str_caster); - size_t str_len = value.size(); - if (str_len == 0) - throw value_error("Cannot convert empty string to a character"); - - // If we're in UTF-8 mode, we have two possible failures: one for a unicode character that - // is too high, and one for multiple unicode characters (caught later), so we need to figure - // out how long the first encoded character is in bytes to distinguish between these two - // errors. We also allow want to allow unicode characters U+0080 through U+00FF, as those - // can fit into a single char value. - if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) { - unsigned char v0 = static_cast(value[0]); - size_t char0_bytes = !(v0 & 0x80) ? 1 : // low bits only: 0-127 - (v0 & 0xE0) == 0xC0 ? 2 : // 0b110xxxxx - start of 2-byte sequence - (v0 & 0xF0) == 0xE0 ? 3 : // 0b1110xxxx - start of 3-byte sequence - 4; // 0b11110xxx - start of 4-byte sequence - - if (char0_bytes == str_len) { - // If we have a 128-255 value, we can decode it into a single char: - if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx - return static_cast(((v0 & 3) << 6) + (static_cast(value[1]) & 0x3F)); - } - // Otherwise we have a single character, but it's > U+00FF - throw value_error("Character code point not in range(0x100)"); - } - } - - // UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a - // surrogate pair with total length 2 instantly indicates a range error (but not a "your - // string was too long" error). - else if (StringCaster::UTF_N == 16 && str_len == 2) { - char16_t v0 = static_cast(value[0]); - if (v0 >= 0xD800 && v0 < 0xE000) - throw value_error("Character code point not in range(0x10000)"); - } - - if (str_len != 1) - throw value_error("Expected a character, but multi-character string found"); - - return value[0]; - } - - static PYBIND11_DESCR name() { return type_descr(_(PYBIND11_STRING_NAME)); } - template using cast_op_type = remove_reference_t>; -}; - -// Base implementation for std::tuple and std::pair -template class Tuple, typename... Ts> class tuple_caster { - using type = Tuple; - static constexpr auto size = sizeof...(Ts); - using indices = make_index_sequence; -public: - - bool load(handle src, bool convert) { - if (!isinstance(src)) - return false; - const auto seq = reinterpret_borrow(src); - if (seq.size() != size) - return false; - return load_impl(seq, convert, indices{}); - } - - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - return cast_impl(std::forward(src), policy, parent, indices{}); - } - - static PYBIND11_DESCR name() { - return type_descr(_("Tuple[") + detail::concat(make_caster::name()...) + _("]")); - } - - template using cast_op_type = type; - - operator type() & { return implicit_cast(indices{}); } - operator type() && { return std::move(*this).implicit_cast(indices{}); } - -protected: - template - type implicit_cast(index_sequence) & { return type(cast_op(std::get(subcasters))...); } - template - type implicit_cast(index_sequence) && { return type(cast_op(std::move(std::get(subcasters)))...); } - - static constexpr bool load_impl(const sequence &, bool, index_sequence<>) { return true; } - - template - bool load_impl(const sequence &seq, bool convert, index_sequence) { - for (bool r : {std::get(subcasters).load(seq[Is], convert)...}) - if (!r) - return false; - return true; - } - - /* Implementation: Convert a C++ tuple into a Python tuple */ - template - static handle cast_impl(T &&src, return_value_policy policy, handle parent, index_sequence) { - std::array entries{{ - reinterpret_steal(make_caster::cast(std::get(std::forward(src)), policy, parent))... - }}; - for (const auto &entry: entries) - if (!entry) - return handle(); - tuple result(size); - int counter = 0; - for (auto & entry: entries) - PyTuple_SET_ITEM(result.ptr(), counter++, entry.release().ptr()); - return result.release(); - } - - Tuple...> subcasters; -}; - -template class type_caster> - : public tuple_caster {}; - -template class type_caster> - : public tuple_caster {}; - -/// Helper class which abstracts away certain actions. Users can provide specializations for -/// custom holders, but it's only necessary if the type has a non-standard interface. -template -struct holder_helper { - static auto get(const T &p) -> decltype(p.get()) { return p.get(); } -}; - -/// Type caster for holder types like std::shared_ptr, etc. -template -struct copyable_holder_caster : public type_caster_base { -public: - using base = type_caster_base; - static_assert(std::is_base_of>::value, - "Holder classes are only supported for custom types"); - using base::base; - using base::cast; - using base::typeinfo; - using base::value; - - bool load(handle src, bool convert) { - return base::template load_impl>(src, convert); - } - - explicit operator type*() { return this->value; } - explicit operator type&() { return *(this->value); } - explicit operator holder_type*() { return &holder; } - - // Workaround for Intel compiler bug - // see pybind11 issue 94 - #if defined(__ICC) || defined(__INTEL_COMPILER) - operator holder_type&() { return holder; } - #else - explicit operator holder_type&() { return holder; } - #endif - - static handle cast(const holder_type &src, return_value_policy, handle) { - const auto *ptr = holder_helper::get(src); - return type_caster_base::cast_holder(ptr, &src); - } - -protected: - friend class type_caster_generic; - void check_holder_compat() { - if (typeinfo->default_holder) - throw cast_error("Unable to load a custom holder type from a default-holder instance"); - } - - bool load_value(const value_and_holder &v_h) { - if (v_h.holder_constructed()) { - value = v_h.value_ptr(); - holder = v_h.holder(); - return true; - } else { - throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " -#if defined(NDEBUG) - "(compile in debug mode for type information)"); -#else - "of type '" + type_id() + "''"); -#endif - } - } - - template ::value, int> = 0> - bool try_implicit_casts(handle, bool) { return false; } - - template ::value, int> = 0> - bool try_implicit_casts(handle src, bool convert) { - for (auto &cast : typeinfo->implicit_casts) { - copyable_holder_caster sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - value = cast.second(sub_caster.value); - holder = holder_type(sub_caster.holder, (type *) value); - return true; - } - } - return false; - } - - static bool try_direct_conversions(handle) { return false; } - - - holder_type holder; -}; - -/// Specialize for the common std::shared_ptr, so users don't need to -template -class type_caster> : public copyable_holder_caster> { }; - -template -struct move_only_holder_caster { - static_assert(std::is_base_of, type_caster>::value, - "Holder classes are only supported for custom types"); - - static handle cast(holder_type &&src, return_value_policy, handle) { - auto *ptr = holder_helper::get(src); - return type_caster_base::cast_holder(ptr, &src); - } - static PYBIND11_DESCR name() { return type_caster_base::name(); } -}; - -template -class type_caster> - : public move_only_holder_caster> { }; - -template -using type_caster_holder = conditional_t::value, - copyable_holder_caster, - move_only_holder_caster>; - -template struct always_construct_holder { static constexpr bool value = Value; }; - -/// Create a specialization for custom holder types (silently ignores std::shared_ptr) -#define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \ - namespace pybind11 { namespace detail { \ - template \ - struct always_construct_holder : always_construct_holder { }; \ - template \ - class type_caster::value>> \ - : public type_caster_holder { }; \ - }} - -// PYBIND11_DECLARE_HOLDER_TYPE holder types: -template struct is_holder_type : - std::is_base_of, detail::type_caster> {}; -// Specialization for always-supported unique_ptr holders: -template struct is_holder_type> : - std::true_type {}; - -template struct handle_type_name { static PYBIND11_DESCR name() { return _(); } }; -template <> struct handle_type_name { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } }; -template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("*args"); } }; -template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("**kwargs"); } }; - -template -struct pyobject_caster { - template ::value, int> = 0> - bool load(handle src, bool /* convert */) { value = src; return static_cast(value); } - - template ::value, int> = 0> - bool load(handle src, bool /* convert */) { - if (!isinstance(src)) - return false; - value = reinterpret_borrow(src); - return true; - } - - static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) { - return src.inc_ref(); - } - PYBIND11_TYPE_CASTER(type, handle_type_name::name()); -}; - -template -class type_caster::value>> : public pyobject_caster { }; - -// Our conditions for enabling moving are quite restrictive: -// At compile time: -// - T needs to be a non-const, non-pointer, non-reference type -// - type_caster::operator T&() must exist -// - the type must be move constructible (obviously) -// At run-time: -// - if the type is non-copy-constructible, the object must be the sole owner of the type (i.e. it -// must have ref_count() == 1)h -// If any of the above are not satisfied, we fall back to copying. -template using move_is_plain_type = satisfies_none_of; -template struct move_always : std::false_type {}; -template struct move_always, - negation>, - std::is_move_constructible, - std::is_same>().operator T&()), T&> ->::value>> : std::true_type {}; -template struct move_if_unreferenced : std::false_type {}; -template struct move_if_unreferenced, - negation>, - std::is_move_constructible, - std::is_same>().operator T&()), T&> ->::value>> : std::true_type {}; -template using move_never = none_of, move_if_unreferenced>; - -// Detect whether returning a `type` from a cast on type's type_caster is going to result in a -// reference or pointer to a local variable of the type_caster. Basically, only -// non-reference/pointer `type`s and reference/pointers from a type_caster_generic are safe; -// everything else returns a reference/pointer to a local variable. -template using cast_is_temporary_value_reference = bool_constant< - (std::is_reference::value || std::is_pointer::value) && - !std::is_base_of>::value ->; - -// When a value returned from a C++ function is being cast back to Python, we almost always want to -// force `policy = move`, regardless of the return value policy the function/method was declared -// with. Some classes (most notably Eigen::Ref and related) need to avoid this, and so can do so by -// specializing this struct. -template struct return_value_policy_override { - static return_value_policy policy(return_value_policy p) { - return !std::is_lvalue_reference::value && !std::is_pointer::value - ? return_value_policy::move : p; - } -}; - -// Basic python -> C++ casting; throws if casting fails -template type_caster &load_type(type_caster &conv, const handle &handle) { - if (!conv.load(handle, true)) { -#if defined(NDEBUG) - throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); -#else - throw cast_error("Unable to cast Python instance of type " + - (std::string) str(handle.get_type()) + " to C++ type '" + type_id() + "''"); -#endif - } - return conv; -} -// Wrapper around the above that also constructs and returns a type_caster -template make_caster load_type(const handle &handle) { - make_caster conv; - load_type(conv, handle); - return conv; -} - -NAMESPACE_END(detail) - -// pytype -> C++ type -template ::value, int> = 0> -T cast(const handle &handle) { - using namespace detail; - static_assert(!cast_is_temporary_value_reference::value, - "Unable to cast type to reference: value is local to type caster"); - return cast_op(load_type(handle)); -} - -// pytype -> pytype (calls converting constructor) -template ::value, int> = 0> -T cast(const handle &handle) { return T(reinterpret_borrow(handle)); } - -// C++ type -> py::object -template ::value, int> = 0> -object cast(const T &value, return_value_policy policy = return_value_policy::automatic_reference, - handle parent = handle()) { - if (policy == return_value_policy::automatic) - policy = std::is_pointer::value ? return_value_policy::take_ownership : return_value_policy::copy; - else if (policy == return_value_policy::automatic_reference) - policy = std::is_pointer::value ? return_value_policy::reference : return_value_policy::copy; - return reinterpret_steal(detail::make_caster::cast(value, policy, parent)); -} - -template T handle::cast() const { return pybind11::cast(*this); } -template <> inline void handle::cast() const { return; } - -template -detail::enable_if_t::value, T> move(object &&obj) { - if (obj.ref_count() > 1) -#if defined(NDEBUG) - throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references" - " (compile in debug mode for details)"); -#else - throw cast_error("Unable to move from Python " + (std::string) str(obj.get_type()) + - " instance to C++ " + type_id() + " instance: instance has multiple references"); -#endif - - // Move into a temporary and return that, because the reference may be a local value of `conv` - T ret = std::move(detail::load_type(obj).operator T&()); - return ret; -} - -// Calling cast() on an rvalue calls pybind::cast with the object rvalue, which does: -// - If we have to move (because T has no copy constructor), do it. This will fail if the moved -// object has multiple references, but trying to copy will fail to compile. -// - If both movable and copyable, check ref count: if 1, move; otherwise copy -// - Otherwise (not movable), copy. -template detail::enable_if_t::value, T> cast(object &&object) { - return move(std::move(object)); -} -template detail::enable_if_t::value, T> cast(object &&object) { - if (object.ref_count() > 1) - return cast(object); - else - return move(std::move(object)); -} -template detail::enable_if_t::value, T> cast(object &&object) { - return cast(object); -} - -template T object::cast() const & { return pybind11::cast(*this); } -template T object::cast() && { return pybind11::cast(std::move(*this)); } -template <> inline void object::cast() const & { return; } -template <> inline void object::cast() && { return; } - -NAMESPACE_BEGIN(detail) - -// Declared in pytypes.h: -template ::value, int>> -object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } - -struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro -template using overload_caster_t = conditional_t< - cast_is_temporary_value_reference::value, make_caster, overload_unused>; - -// Trampoline use: for reference/pointer types to value-converted values, we do a value cast, then -// store the result in the given variable. For other types, this is a no-op. -template enable_if_t::value, T> cast_ref(object &&o, make_caster &caster) { - return cast_op(load_type(caster, o)); -} -template enable_if_t::value, T> cast_ref(object &&, overload_unused &) { - pybind11_fail("Internal error: cast_ref fallback invoked"); } - -// Trampoline use: Having a pybind11::cast with an invalid reference type is going to static_assert, even -// though if it's in dead code, so we provide a "trampoline" to pybind11::cast that only does anything in -// cases where pybind11::cast is valid. -template enable_if_t::value, T> cast_safe(object &&o) { - return pybind11::cast(std::move(o)); } -template enable_if_t::value, T> cast_safe(object &&) { - pybind11_fail("Internal error: cast_safe fallback invoked"); } -template <> inline void cast_safe(object &&) {} - -NAMESPACE_END(detail) - -template tuple make_tuple(Args&&... args_) { - constexpr size_t size = sizeof...(Args); - std::array args { - { reinterpret_steal(detail::make_caster::cast( - std::forward(args_), policy, nullptr))... } - }; - for (size_t i = 0; i < args.size(); i++) { - if (!args[i]) { -#if defined(NDEBUG) - throw cast_error("make_tuple(): unable to convert arguments to Python object (compile in debug mode for details)"); -#else - std::array argtypes { {type_id()...} }; - throw cast_error("make_tuple(): unable to convert argument of type '" + - argtypes[i] + "' to Python object"); -#endif - } - } - tuple result(size); - int counter = 0; - for (auto &arg_value : args) - PyTuple_SET_ITEM(result.ptr(), counter++, arg_value.release().ptr()); - return result; -} - -/// \ingroup annotations -/// Annotation for arguments -struct arg { - /// Constructs an argument with the name of the argument; if null or omitted, this is a positional argument. - constexpr explicit arg(const char *name = nullptr) : name(name), flag_noconvert(false), flag_none(true) { } - /// Assign a value to this argument - template arg_v operator=(T &&value) const; - /// Indicate that the type should not be converted in the type caster - arg &noconvert(bool flag = true) { flag_noconvert = flag; return *this; } - /// Indicates that the argument should/shouldn't allow None (e.g. for nullable pointer args) - arg &none(bool flag = true) { flag_none = flag; return *this; } - - const char *name; ///< If non-null, this is a named kwargs argument - bool flag_noconvert : 1; ///< If set, do not allow conversion (requires a supporting type caster!) - bool flag_none : 1; ///< If set (the default), allow None to be passed to this argument -}; - -/// \ingroup annotations -/// Annotation for arguments with values -struct arg_v : arg { -private: - template - arg_v(arg &&base, T &&x, const char *descr = nullptr) - : arg(base), - value(reinterpret_steal( - detail::make_caster::cast(x, return_value_policy::automatic, {}) - )), - descr(descr) -#if !defined(NDEBUG) - , type(type_id()) -#endif - { } - -public: - /// Direct construction with name, default, and description - template - arg_v(const char *name, T &&x, const char *descr = nullptr) - : arg_v(arg(name), std::forward(x), descr) { } - - /// Called internally when invoking `py::arg("a") = value` - template - arg_v(const arg &base, T &&x, const char *descr = nullptr) - : arg_v(arg(base), std::forward(x), descr) { } - - /// Same as `arg::noconvert()`, but returns *this as arg_v&, not arg& - arg_v &noconvert(bool flag = true) { arg::noconvert(flag); return *this; } - - /// Same as `arg::nonone()`, but returns *this as arg_v&, not arg& - arg_v &none(bool flag = true) { arg::none(flag); return *this; } - - /// The default value - object value; - /// The (optional) description of the default value - const char *descr; -#if !defined(NDEBUG) - /// The C++ type name of the default value (only available when compiled in debug mode) - std::string type; -#endif -}; - -template -arg_v arg::operator=(T &&value) const { return {std::move(*this), std::forward(value)}; } - -/// Alias for backward compatibility -- to be removed in version 2.0 -template using arg_t = arg_v; - -inline namespace literals { -/** \rst - String literal version of `arg` - \endrst */ -constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } -} - -NAMESPACE_BEGIN(detail) - -// forward declaration (definition in attr.h) -struct function_record; - -/// Internal data associated with a single function call -struct function_call { - function_call(function_record &f, handle p); // Implementation in attr.h - - /// The function data: - const function_record &func; - - /// Arguments passed to the function: - std::vector args; - - /// The `convert` value the arguments should be loaded with - std::vector args_convert; - - /// The parent, if any - handle parent; -}; - - -/// Helper class which loads arguments for C++ functions called from Python -template -class argument_loader { - using indices = make_index_sequence; - - template using argument_is_args = std::is_same, args>; - template using argument_is_kwargs = std::is_same, kwargs>; - // Get args/kwargs argument positions relative to the end of the argument list: - static constexpr auto args_pos = constexpr_first() - (int) sizeof...(Args), - kwargs_pos = constexpr_first() - (int) sizeof...(Args); - - static constexpr bool args_kwargs_are_last = kwargs_pos >= - 1 && args_pos >= kwargs_pos - 1; - - static_assert(args_kwargs_are_last, "py::args/py::kwargs are only permitted as the last argument(s) of a function"); - -public: - static constexpr bool has_kwargs = kwargs_pos < 0; - static constexpr bool has_args = args_pos < 0; - - static PYBIND11_DESCR arg_names() { return detail::concat(make_caster::name()...); } - - bool load_args(function_call &call) { - return load_impl_sequence(call, indices{}); - } - - template - enable_if_t::value, Return> call(Func &&f) && { - return std::move(*this).template call_impl(std::forward(f), indices{}, Guard{}); - } - - template - enable_if_t::value, void_type> call(Func &&f) && { - std::move(*this).template call_impl(std::forward(f), indices{}, Guard{}); - return void_type(); - } - -private: - - static bool load_impl_sequence(function_call &, index_sequence<>) { return true; } - - template - bool load_impl_sequence(function_call &call, index_sequence) { - for (bool r : {std::get(argcasters).load(call.args[Is], call.args_convert[Is])...}) - if (!r) - return false; - return true; - } - - template - Return call_impl(Func &&f, index_sequence, Guard &&) { - return std::forward(f)(cast_op(std::move(std::get(argcasters)))...); - } - - std::tuple...> argcasters; -}; - -/// Helper class which collects only positional arguments for a Python function call. -/// A fancier version below can collect any argument, but this one is optimal for simple calls. -template -class simple_collector { -public: - template - explicit simple_collector(Ts &&...values) - : m_args(pybind11::make_tuple(std::forward(values)...)) { } - - const tuple &args() const & { return m_args; } - dict kwargs() const { return {}; } - - tuple args() && { return std::move(m_args); } - - /// Call a Python function and pass the collected arguments - object call(PyObject *ptr) const { - PyObject *result = PyObject_CallObject(ptr, m_args.ptr()); - if (!result) - throw error_already_set(); - return reinterpret_steal(result); - } - -private: - tuple m_args; -}; - -/// Helper class which collects positional, keyword, * and ** arguments for a Python function call -template -class unpacking_collector { -public: - template - explicit unpacking_collector(Ts &&...values) { - // Tuples aren't (easily) resizable so a list is needed for collection, - // but the actual function call strictly requires a tuple. - auto args_list = list(); - int _[] = { 0, (process(args_list, std::forward(values)), 0)... }; - ignore_unused(_); - - m_args = std::move(args_list); - } - - const tuple &args() const & { return m_args; } - const dict &kwargs() const & { return m_kwargs; } - - tuple args() && { return std::move(m_args); } - dict kwargs() && { return std::move(m_kwargs); } - - /// Call a Python function and pass the collected arguments - object call(PyObject *ptr) const { - PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr()); - if (!result) - throw error_already_set(); - return reinterpret_steal(result); - } - -private: - template - void process(list &args_list, T &&x) { - auto o = reinterpret_steal(detail::make_caster::cast(std::forward(x), policy, {})); - if (!o) { -#if defined(NDEBUG) - argument_cast_error(); -#else - argument_cast_error(std::to_string(args_list.size()), type_id()); -#endif - } - args_list.append(o); - } - - void process(list &args_list, detail::args_proxy ap) { - for (const auto &a : ap) - args_list.append(a); - } - - void process(list &/*args_list*/, arg_v a) { - if (!a.name) -#if defined(NDEBUG) - nameless_argument_error(); -#else - nameless_argument_error(a.type); -#endif - - if (m_kwargs.contains(a.name)) { -#if defined(NDEBUG) - multiple_values_error(); -#else - multiple_values_error(a.name); -#endif - } - if (!a.value) { -#if defined(NDEBUG) - argument_cast_error(); -#else - argument_cast_error(a.name, a.type); -#endif - } - m_kwargs[a.name] = a.value; - } - - void process(list &/*args_list*/, detail::kwargs_proxy kp) { - if (!kp) - return; - for (const auto &k : reinterpret_borrow(kp)) { - if (m_kwargs.contains(k.first)) { -#if defined(NDEBUG) - multiple_values_error(); -#else - multiple_values_error(str(k.first)); -#endif - } - m_kwargs[k.first] = k.second; - } - } - - [[noreturn]] static void nameless_argument_error() { - throw type_error("Got kwargs without a name; only named arguments " - "may be passed via py::arg() to a python function call. " - "(compile in debug mode for details)"); - } - [[noreturn]] static void nameless_argument_error(std::string type) { - throw type_error("Got kwargs without a name of type '" + type + "'; only named " - "arguments may be passed via py::arg() to a python function call. "); - } - [[noreturn]] static void multiple_values_error() { - throw type_error("Got multiple values for keyword argument " - "(compile in debug mode for details)"); - } - - [[noreturn]] static void multiple_values_error(std::string name) { - throw type_error("Got multiple values for keyword argument '" + name + "'"); - } - - [[noreturn]] static void argument_cast_error() { - throw cast_error("Unable to convert call argument to Python object " - "(compile in debug mode for details)"); - } - - [[noreturn]] static void argument_cast_error(std::string name, std::string type) { - throw cast_error("Unable to convert call argument '" + name - + "' of type '" + type + "' to Python object"); - } - -private: - tuple m_args; - dict m_kwargs; -}; - -/// Collect only positional arguments for a Python function call -template ...>::value>> -simple_collector collect_arguments(Args &&...args) { - return simple_collector(std::forward(args)...); -} - -/// Collect all arguments, including keywords and unpacking (only instantiated when needed) -template ...>::value>> -unpacking_collector collect_arguments(Args &&...args) { - // Following argument order rules for generalized unpacking according to PEP 448 - static_assert( - constexpr_last() < constexpr_first() - && constexpr_last() < constexpr_first(), - "Invalid function call: positional args must precede keywords and ** unpacking; " - "* unpacking must precede ** unpacking" - ); - return unpacking_collector(std::forward(args)...); -} - -template -template -object object_api::operator()(Args &&...args) const { - return detail::collect_arguments(std::forward(args)...).call(derived().ptr()); -} - -template -template -object object_api::call(Args &&...args) const { - return operator()(std::forward(args)...); -} - -NAMESPACE_END(detail) - -#define PYBIND11_MAKE_OPAQUE(Type) \ - namespace pybind11 { namespace detail { \ - template<> class type_caster : public type_caster_base { }; \ - }} - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/chrono.h b/ppocr/postprocess/lanms/include/pybind11/chrono.h deleted file mode 100644 index 8a41d08b..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/chrono.h +++ /dev/null @@ -1,162 +0,0 @@ -/* - pybind11/chrono.h: Transparent conversion between std::chrono and python's datetime - - Copyright (c) 2016 Trent Houliston and - Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include -#include -#include -#include - -// Backport the PyDateTime_DELTA functions from Python3.3 if required -#ifndef PyDateTime_DELTA_GET_DAYS -#define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days) -#endif -#ifndef PyDateTime_DELTA_GET_SECONDS -#define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds) -#endif -#ifndef PyDateTime_DELTA_GET_MICROSECONDS -#define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds) -#endif - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -template class duration_caster { -public: - typedef typename type::rep rep; - typedef typename type::period period; - - typedef std::chrono::duration> days; - - bool load(handle src, bool) { - using namespace std::chrono; - - // Lazy initialise the PyDateTime import - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - if (!src) return false; - // If invoked with datetime.delta object - if (PyDelta_Check(src.ptr())) { - value = type(duration_cast>( - days(PyDateTime_DELTA_GET_DAYS(src.ptr())) - + seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr())) - + microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr())))); - return true; - } - // If invoked with a float we assume it is seconds and convert - else if (PyFloat_Check(src.ptr())) { - value = type(duration_cast>(duration(PyFloat_AsDouble(src.ptr())))); - return true; - } - else return false; - } - - // If this is a duration just return it back - static const std::chrono::duration& get_duration(const std::chrono::duration &src) { - return src; - } - - // If this is a time_point get the time_since_epoch - template static std::chrono::duration get_duration(const std::chrono::time_point> &src) { - return src.time_since_epoch(); - } - - static handle cast(const type &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - - // Use overloaded function to get our duration from our source - // Works out if it is a duration or time_point and get the duration - auto d = get_duration(src); - - // Lazy initialise the PyDateTime import - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - // Declare these special duration types so the conversions happen with the correct primitive types (int) - using dd_t = duration>; - using ss_t = duration>; - using us_t = duration; - - auto dd = duration_cast(d); - auto subd = d - dd; - auto ss = duration_cast(subd); - auto us = duration_cast(subd - ss); - return PyDelta_FromDSU(dd.count(), ss.count(), us.count()); - } - - PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); -}; - -// This is for casting times on the system clock into datetime.datetime instances -template class type_caster> { -public: - typedef std::chrono::time_point type; - bool load(handle src, bool) { - using namespace std::chrono; - - // Lazy initialise the PyDateTime import - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - if (!src) return false; - if (PyDateTime_Check(src.ptr())) { - std::tm cal; - cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr()); - cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr()); - cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr()); - cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); - cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; - cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900; - cal.tm_isdst = -1; - - value = system_clock::from_time_t(std::mktime(&cal)) + microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr())); - return true; - } - else return false; - } - - static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - - // Lazy initialise the PyDateTime import - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - std::time_t tt = system_clock::to_time_t(src); - // this function uses static memory so it's best to copy it out asap just in case - // otherwise other code that is using localtime may break this (not just python code) - std::tm localtime = *std::localtime(&tt); - - // Declare these special duration types so the conversions happen with the correct primitive types (int) - using us_t = duration; - - return PyDateTime_FromDateAndTime(localtime.tm_year + 1900, - localtime.tm_mon + 1, - localtime.tm_mday, - localtime.tm_hour, - localtime.tm_min, - localtime.tm_sec, - (duration_cast(src.time_since_epoch() % seconds(1))).count()); - } - PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); -}; - -// Other clocks that are not the system clock are not measured as datetime.datetime objects -// since they are not measured on calendar time. So instead we just make them timedeltas -// Or if they have passed us a time as a float we convert that -template class type_caster> -: public duration_caster> { -}; - -template class type_caster> -: public duration_caster> { -}; - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/class_support.h b/ppocr/postprocess/lanms/include/pybind11/class_support.h deleted file mode 100644 index 8e18c4c6..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/class_support.h +++ /dev/null @@ -1,603 +0,0 @@ -/* - pybind11/class_support.h: Python C API implementation details for py::class_ - - Copyright (c) 2017 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "attr.h" - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -inline PyTypeObject *type_incref(PyTypeObject *type) { - Py_INCREF(type); - return type; -} - -#if !defined(PYPY_VERSION) - -/// `pybind11_static_property.__get__()`: Always pass the class instead of the instance. -extern "C" inline PyObject *pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { - return PyProperty_Type.tp_descr_get(self, cls, cls); -} - -/// `pybind11_static_property.__set__()`: Just like the above `__get__()`. -extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value) { - PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj); - return PyProperty_Type.tp_descr_set(self, cls, value); -} - -/** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` - methods are modified to always use the object type instead of a concrete instance. - Return value: New reference. */ -inline PyTypeObject *make_static_property_type() { - constexpr auto *name = "pybind11_static_property"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0); - if (!heap_type) - pybind11_fail("make_static_property_type(): error allocating type!"); - - heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -#endif - - auto type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyProperty_Type); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - type->tp_descr_get = pybind11_static_get; - type->tp_descr_set = pybind11_static_set; - - if (PyType_Ready(type) < 0) - pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); - - setattr((PyObject *) type, "__module__", str("pybind11_builtins")); - - return type; -} - -#else // PYPY - -/** PyPy has some issues with the above C API, so we evaluate Python code instead. - This function will only be called once so performance isn't really a concern. - Return value: New reference. */ -inline PyTypeObject *make_static_property_type() { - auto d = dict(); - PyObject *result = PyRun_String(R"(\ - class pybind11_static_property(property): - def __get__(self, obj, cls): - return property.__get__(self, cls, cls) - - def __set__(self, obj, value): - cls = obj if isinstance(obj, type) else type(obj) - property.__set__(self, cls, value) - )", Py_file_input, d.ptr(), d.ptr() - ); - if (result == nullptr) - throw error_already_set(); - Py_DECREF(result); - return (PyTypeObject *) d["pybind11_static_property"].cast().release().ptr(); -} - -#endif // PYPY - -/** Types with static properties need to handle `Type.static_prop = x` in a specific way. - By default, Python replaces the `static_property` itself, but for wrapped C++ types - we need to call `static_property.__set__()` in order to propagate the new value to - the underlying C++ data structure. */ -extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyObject* value) { - // Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw - // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). - PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); - - // The following assignment combinations are possible: - // 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)` - // 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop` - // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment - const auto static_prop = (PyObject *) get_internals().static_property_type; - const auto call_descr_set = descr && PyObject_IsInstance(descr, static_prop) - && !PyObject_IsInstance(value, static_prop); - if (call_descr_set) { - // Call `static_property.__set__()` instead of replacing the `static_property`. -#if !defined(PYPY_VERSION) - return Py_TYPE(descr)->tp_descr_set(descr, obj, value); -#else - if (PyObject *result = PyObject_CallMethod(descr, "__set__", "OO", obj, value)) { - Py_DECREF(result); - return 0; - } else { - return -1; - } -#endif - } else { - // Replace existing attribute. - return PyType_Type.tp_setattro(obj, name, value); - } -} - -#if PY_MAJOR_VERSION >= 3 -/** - * Python 3's PyInstanceMethod_Type hides itself via its tp_descr_get, which prevents aliasing - * methods via cls.attr("m2") = cls.attr("m1"): instead the tp_descr_get returns a plain function, - * when called on a class, or a PyMethod, when called on an instance. Override that behaviour here - * to do a special case bypass for PyInstanceMethod_Types. - */ -extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name) { - PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); - if (descr && PyInstanceMethod_Check(descr)) { - Py_INCREF(descr); - return descr; - } - else { - return PyType_Type.tp_getattro(obj, name); - } -} -#endif - -/** This metaclass is assigned by default to all pybind11 types and is required in order - for static properties to function correctly. Users may override this using `py::metaclass`. - Return value: New reference. */ -inline PyTypeObject* make_default_metaclass() { - constexpr auto *name = "pybind11_type"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0); - if (!heap_type) - pybind11_fail("make_default_metaclass(): error allocating metaclass!"); - - heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -#endif - - auto type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyType_Type); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - - type->tp_setattro = pybind11_meta_setattro; -#if PY_MAJOR_VERSION >= 3 - type->tp_getattro = pybind11_meta_getattro; -#endif - - if (PyType_Ready(type) < 0) - pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!"); - - setattr((PyObject *) type, "__module__", str("pybind11_builtins")); - - return type; -} - -/// For multiple inheritance types we need to recursively register/deregister base pointers for any -/// base classes with pointers that are difference from the instance value pointer so that we can -/// correctly recognize an offset base class pointer. This calls a function with any offset base ptrs. -inline void traverse_offset_bases(void *valueptr, const detail::type_info *tinfo, instance *self, - bool (*f)(void * /*parentptr*/, instance * /*self*/)) { - for (handle h : reinterpret_borrow(tinfo->type->tp_bases)) { - if (auto parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) { - for (auto &c : parent_tinfo->implicit_casts) { - if (c.first == tinfo->cpptype) { - auto *parentptr = c.second(valueptr); - if (parentptr != valueptr) - f(parentptr, self); - traverse_offset_bases(parentptr, parent_tinfo, self, f); - break; - } - } - } - } -} - -inline bool register_instance_impl(void *ptr, instance *self) { - get_internals().registered_instances.emplace(ptr, self); - return true; // unused, but gives the same signature as the deregister func -} -inline bool deregister_instance_impl(void *ptr, instance *self) { - auto ®istered_instances = get_internals().registered_instances; - auto range = registered_instances.equal_range(ptr); - for (auto it = range.first; it != range.second; ++it) { - if (Py_TYPE(self) == Py_TYPE(it->second)) { - registered_instances.erase(it); - return true; - } - } - return false; -} - -inline void register_instance(instance *self, void *valptr, const type_info *tinfo) { - register_instance_impl(valptr, self); - if (!tinfo->simple_ancestors) - traverse_offset_bases(valptr, tinfo, self, register_instance_impl); -} - -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo) { - bool ret = deregister_instance_impl(valptr, self); - if (!tinfo->simple_ancestors) - traverse_offset_bases(valptr, tinfo, self, deregister_instance_impl); - return ret; -} - -/// Instance creation function for all pybind11 types. It only allocates space for the C++ object -/// (or multiple objects, for Python-side inheritance from multiple pybind11 types), but doesn't -/// call the constructor -- an `__init__` function must do that (followed by an `init_instance` -/// to set up the holder and register the instance). -inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= true (in cast.h)*/) { -#if defined(PYPY_VERSION) - // PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited - // object is a a plain Python type (i.e. not derived from an extension type). Fix it. - ssize_t instance_size = static_cast(sizeof(instance)); - if (type->tp_basicsize < instance_size) { - type->tp_basicsize = instance_size; - } -#endif - PyObject *self = type->tp_alloc(type, 0); - auto inst = reinterpret_cast(self); - // Allocate the value/holder internals: - inst->allocate_layout(); - - inst->owned = true; - // Allocate (if requested) the value pointers; otherwise leave them as nullptr - if (allocate_value) { - for (auto &v_h : values_and_holders(inst)) { - void *&vptr = v_h.value_ptr(); - vptr = v_h.type->operator_new(v_h.type->type_size); - } - } - - return self; -} - -/// Instance creation function for all pybind11 types. It only allocates space for the -/// C++ object, but doesn't call the constructor -- an `__init__` function must do that. -extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) { - return make_new_instance(type); -} - -/// An `__init__` function constructs the C++ object. Users should provide at least one -/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the -/// following default function will be used which simply throws an exception. -extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) { - PyTypeObject *type = Py_TYPE(self); - std::string msg; -#if defined(PYPY_VERSION) - msg += handle((PyObject *) type).attr("__module__").cast() + "."; -#endif - msg += type->tp_name; - msg += ": No constructor defined!"; - PyErr_SetString(PyExc_TypeError, msg.c_str()); - return -1; -} - -inline void add_patient(PyObject *nurse, PyObject *patient) { - auto &internals = get_internals(); - auto instance = reinterpret_cast(nurse); - instance->has_patients = true; - Py_INCREF(patient); - internals.patients[nurse].push_back(patient); -} - -inline void clear_patients(PyObject *self) { - auto instance = reinterpret_cast(self); - auto &internals = get_internals(); - auto pos = internals.patients.find(self); - assert(pos != internals.patients.end()); - // Clearing the patients can cause more Python code to run, which - // can invalidate the iterator. Extract the vector of patients - // from the unordered_map first. - auto patients = std::move(pos->second); - internals.patients.erase(pos); - instance->has_patients = false; - for (PyObject *&patient : patients) - Py_CLEAR(patient); -} - -/// Clears all internal data from the instance and removes it from registered instances in -/// preparation for deallocation. -inline void clear_instance(PyObject *self) { - auto instance = reinterpret_cast(self); - - // Deallocate any values/holders, if present: - for (auto &v_h : values_and_holders(instance)) { - if (v_h) { - - // We have to deregister before we call dealloc because, for virtual MI types, we still - // need to be able to get the parent pointers. - if (v_h.instance_registered() && !deregister_instance(instance, v_h.value_ptr(), v_h.type)) - pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!"); - - if (instance->owned || v_h.holder_constructed()) - v_h.type->dealloc(v_h); - } - } - // Deallocate the value/holder layout internals: - instance->deallocate_layout(); - - if (instance->weakrefs) - PyObject_ClearWeakRefs(self); - - PyObject **dict_ptr = _PyObject_GetDictPtr(self); - if (dict_ptr) - Py_CLEAR(*dict_ptr); - - if (instance->has_patients) - clear_patients(self); -} - -/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc` -/// to destroy the C++ object itself, while the rest is Python bookkeeping. -extern "C" inline void pybind11_object_dealloc(PyObject *self) { - clear_instance(self); - Py_TYPE(self)->tp_free(self); -} - -/** Create the type which can be used as a common base for all classes. This is - needed in order to satisfy Python's requirements for multiple inheritance. - Return value: New reference. */ -inline PyObject *make_object_base_type(PyTypeObject *metaclass) { - constexpr auto *name = "pybind11_object"; - auto name_obj = reinterpret_steal(PYBIND11_FROM_STRING(name)); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0); - if (!heap_type) - pybind11_fail("make_object_base_type(): error allocating type!"); - - heap_type->ht_name = name_obj.inc_ref().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - heap_type->ht_qualname = name_obj.inc_ref().ptr(); -#endif - - auto type = &heap_type->ht_type; - type->tp_name = name; - type->tp_base = type_incref(&PyBaseObject_Type); - type->tp_basicsize = static_cast(sizeof(instance)); - type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; - - type->tp_new = pybind11_object_new; - type->tp_init = pybind11_object_init; - type->tp_dealloc = pybind11_object_dealloc; - - /* Support weak references (needed for the keep_alive feature) */ - type->tp_weaklistoffset = offsetof(instance, weakrefs); - - if (PyType_Ready(type) < 0) - pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string()); - - setattr((PyObject *) type, "__module__", str("pybind11_builtins")); - - assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); - return (PyObject *) heap_type; -} - -/// dynamic_attr: Support for `d = instance.__dict__`. -extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) { - PyObject *&dict = *_PyObject_GetDictPtr(self); - if (!dict) - dict = PyDict_New(); - Py_XINCREF(dict); - return dict; -} - -/// dynamic_attr: Support for `instance.__dict__ = dict()`. -extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) { - if (!PyDict_Check(new_dict)) { - PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'", - Py_TYPE(new_dict)->tp_name); - return -1; - } - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_INCREF(new_dict); - Py_CLEAR(dict); - dict = new_dict; - return 0; -} - -/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`. -extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) { - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_VISIT(dict); - return 0; -} - -/// dynamic_attr: Allow the GC to clear the dictionary. -extern "C" inline int pybind11_clear(PyObject *self) { - PyObject *&dict = *_PyObject_GetDictPtr(self); - Py_CLEAR(dict); - return 0; -} - -/// Give instances of this type a `__dict__` and opt into garbage collection. -inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) { - auto type = &heap_type->ht_type; -#if defined(PYPY_VERSION) - pybind11_fail(std::string(type->tp_name) + ": dynamic attributes are " - "currently not supported in " - "conjunction with PyPy!"); -#endif - type->tp_flags |= Py_TPFLAGS_HAVE_GC; - type->tp_dictoffset = type->tp_basicsize; // place dict at the end - type->tp_basicsize += (ssize_t)sizeof(PyObject *); // and allocate enough space for it - type->tp_traverse = pybind11_traverse; - type->tp_clear = pybind11_clear; - - static PyGetSetDef getset[] = { - {const_cast("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr} - }; - type->tp_getset = getset; -} - -/// buffer_protocol: Fill in the view as specified by flags. -extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) { - // Look for a `get_buffer` implementation in this type's info or any bases (following MRO). - type_info *tinfo = nullptr; - for (auto type : reinterpret_borrow(Py_TYPE(obj)->tp_mro)) { - tinfo = get_type_info((PyTypeObject *) type.ptr()); - if (tinfo && tinfo->get_buffer) - break; - } - if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) { - if (view) - view->obj = nullptr; - PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error"); - return -1; - } - std::memset(view, 0, sizeof(Py_buffer)); - buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data); - view->obj = obj; - view->ndim = 1; - view->internal = info; - view->buf = info->ptr; - view->itemsize = info->itemsize; - view->len = view->itemsize; - for (auto s : info->shape) - view->len *= s; - if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) - view->format = const_cast(info->format.c_str()); - if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) { - view->ndim = (int) info->ndim; - view->strides = &info->strides[0]; - view->shape = &info->shape[0]; - } - Py_INCREF(view->obj); - return 0; -} - -/// buffer_protocol: Release the resources of the buffer. -extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) { - delete (buffer_info *) view->internal; -} - -/// Give this type a buffer interface. -inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) { - heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer; -#if PY_MAJOR_VERSION < 3 - heap_type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER; -#endif - - heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer; - heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer; -} - -/** Create a brand new Python type according to the `type_record` specification. - Return value: New reference. */ -inline PyObject* make_new_python_type(const type_record &rec) { - auto name = reinterpret_steal(PYBIND11_FROM_STRING(rec.name)); - -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - auto ht_qualname = name; - if (rec.scope && hasattr(rec.scope, "__qualname__")) { - ht_qualname = reinterpret_steal( - PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr())); - } -#endif - - object module; - if (rec.scope) { - if (hasattr(rec.scope, "__module__")) - module = rec.scope.attr("__module__"); - else if (hasattr(rec.scope, "__name__")) - module = rec.scope.attr("__name__"); - } - -#if !defined(PYPY_VERSION) - const auto full_name = module ? str(module).cast() + "." + rec.name - : std::string(rec.name); -#else - const auto full_name = std::string(rec.name); -#endif - - char *tp_doc = nullptr; - if (rec.doc && options::show_user_defined_docstrings()) { - /* Allocate memory for docstring (using PyObject_MALLOC, since - Python will free this later on) */ - size_t size = strlen(rec.doc) + 1; - tp_doc = (char *) PyObject_MALLOC(size); - memcpy((void *) tp_doc, rec.doc, size); - } - - auto &internals = get_internals(); - auto bases = tuple(rec.bases); - auto base = (bases.size() == 0) ? internals.instance_base - : bases[0].ptr(); - - /* Danger zone: from now (and until PyType_Ready), make sure to - issue no Python C API calls which could potentially invoke the - garbage collector (the GC will call type_traverse(), which will in - turn find the newly constructed type in an invalid state) */ - auto metaclass = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() - : internals.default_metaclass; - - auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0); - if (!heap_type) - pybind11_fail(std::string(rec.name) + ": Unable to create type object!"); - - heap_type->ht_name = name.release().ptr(); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - heap_type->ht_qualname = ht_qualname.release().ptr(); -#endif - - auto type = &heap_type->ht_type; - type->tp_name = strdup(full_name.c_str()); - type->tp_doc = tp_doc; - type->tp_base = type_incref((PyTypeObject *)base); - type->tp_basicsize = static_cast(sizeof(instance)); - if (bases.size() > 0) - type->tp_bases = bases.release().ptr(); - - /* Don't inherit base __init__ */ - type->tp_init = pybind11_object_init; - - /* Supported protocols */ - type->tp_as_number = &heap_type->as_number; - type->tp_as_sequence = &heap_type->as_sequence; - type->tp_as_mapping = &heap_type->as_mapping; - - /* Flags */ - type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; -#if PY_MAJOR_VERSION < 3 - type->tp_flags |= Py_TPFLAGS_CHECKTYPES; -#endif - - if (rec.dynamic_attr) - enable_dynamic_attributes(heap_type); - - if (rec.buffer_protocol) - enable_buffer_protocol(heap_type); - - if (PyType_Ready(type) < 0) - pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!"); - - assert(rec.dynamic_attr ? PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC) - : !PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)); - - /* Register type with the parent scope */ - if (rec.scope) - setattr(rec.scope, rec.name, (PyObject *) type); - - if (module) // Needed by pydoc - setattr((PyObject *) type, "__module__", module); - - return (PyObject *) type; -} - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/common.h b/ppocr/postprocess/lanms/include/pybind11/common.h deleted file mode 100644 index 240f6d8e..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/common.h +++ /dev/null @@ -1,857 +0,0 @@ -/* - pybind11/common.h -- Basic macros - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#if !defined(NAMESPACE_BEGIN) -# define NAMESPACE_BEGIN(name) namespace name { -#endif -#if !defined(NAMESPACE_END) -# define NAMESPACE_END(name) } -#endif - -#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# if __cplusplus >= 201402L -# define PYBIND11_CPP14 -# if __cplusplus > 201402L /* Temporary: should be updated to >= the final C++17 value once known */ -# define PYBIND11_CPP17 -# endif -# endif -#elif defined(_MSC_VER) -// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) -# if _MSVC_LANG >= 201402L -# define PYBIND11_CPP14 -# if _MSVC_LANG > 201402L && _MSC_VER >= 1910 -# define PYBIND11_CPP17 -# endif -# endif -#endif - -// Compiler version assertions -#if defined(__INTEL_COMPILER) -# if __INTEL_COMPILER < 1500 -# error pybind11 requires Intel C++ compiler v15 or newer -# endif -#elif defined(__clang__) && !defined(__apple_build_version__) -# if __clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 3) -# error pybind11 requires clang 3.3 or newer -# endif -#elif defined(__clang__) -// Apple changes clang version macros to its Xcode version; the first Xcode release based on -// (upstream) clang 3.3 was Xcode 5: -# if __clang_major__ < 5 -# error pybind11 requires Xcode/clang 5.0 or newer -# endif -#elif defined(__GNUG__) -# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) -# error pybind11 requires gcc 4.8 or newer -# endif -#elif defined(_MSC_VER) -// Pybind hits various compiler bugs in 2015u2 and earlier, and also makes use of some stl features -// (e.g. std::negation) added in 2015u3: -# if _MSC_FULL_VER < 190024210 -# error pybind11 requires MSVC 2015 update 3 or newer -# endif -#endif - -#if !defined(PYBIND11_EXPORT) -# if defined(WIN32) || defined(_WIN32) -# define PYBIND11_EXPORT __declspec(dllexport) -# else -# define PYBIND11_EXPORT __attribute__ ((visibility("default"))) -# endif -#endif - -#if defined(_MSC_VER) -# define PYBIND11_NOINLINE __declspec(noinline) -#else -# define PYBIND11_NOINLINE __attribute__ ((noinline)) -#endif - -#if defined(PYBIND11_CPP14) -# define PYBIND11_DEPRECATED(reason) [[deprecated(reason)]] -#else -# define PYBIND11_DEPRECATED(reason) __attribute__((deprecated(reason))) -#endif - -#define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 2 -#define PYBIND11_VERSION_PATCH dev0 - -/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode -#if defined(_MSC_VER) -# if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 4) -# define HAVE_ROUND 1 -# endif -# pragma warning(push) -# pragma warning(disable: 4510 4610 4512 4005) -# if defined(_DEBUG) -# define PYBIND11_DEBUG_MARKER -# undef _DEBUG -# endif -#endif - -#include -#include -#include - -#if defined(_WIN32) && (defined(min) || defined(max)) -# error Macro clash with min and max -- define NOMINMAX when compiling your program on Windows -#endif - -#if defined(isalnum) -# undef isalnum -# undef isalpha -# undef islower -# undef isspace -# undef isupper -# undef tolower -# undef toupper -#endif - -#if defined(_MSC_VER) -# if defined(PYBIND11_DEBUG_MARKER) -# define _DEBUG -# undef PYBIND11_DEBUG_MARKER -# endif -# pragma warning(pop) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if PY_MAJOR_VERSION >= 3 /// Compatibility macros for various Python versions -#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyInstanceMethod_New(ptr) -#define PYBIND11_INSTANCE_METHOD_CHECK PyInstanceMethod_Check -#define PYBIND11_INSTANCE_METHOD_GET_FUNCTION PyInstanceMethod_GET_FUNCTION -#define PYBIND11_BYTES_CHECK PyBytes_Check -#define PYBIND11_BYTES_FROM_STRING PyBytes_FromString -#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyBytes_FromStringAndSize -#define PYBIND11_BYTES_AS_STRING_AND_SIZE PyBytes_AsStringAndSize -#define PYBIND11_BYTES_AS_STRING PyBytes_AsString -#define PYBIND11_BYTES_SIZE PyBytes_Size -#define PYBIND11_LONG_CHECK(o) PyLong_Check(o) -#define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) -#define PYBIND11_BYTES_NAME "bytes" -#define PYBIND11_STRING_NAME "str" -#define PYBIND11_SLICE_OBJECT PyObject -#define PYBIND11_FROM_STRING PyUnicode_FromString -#define PYBIND11_STR_TYPE ::pybind11::str -#define PYBIND11_BOOL_ATTR "__bool__" -#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_bool) -#define PYBIND11_PLUGIN_IMPL(name) \ - extern "C" PYBIND11_EXPORT PyObject *PyInit_##name() - -#else -#define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyMethod_New(ptr, nullptr, class_) -#define PYBIND11_INSTANCE_METHOD_CHECK PyMethod_Check -#define PYBIND11_INSTANCE_METHOD_GET_FUNCTION PyMethod_GET_FUNCTION -#define PYBIND11_BYTES_CHECK PyString_Check -#define PYBIND11_BYTES_FROM_STRING PyString_FromString -#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize -#define PYBIND11_BYTES_AS_STRING_AND_SIZE PyString_AsStringAndSize -#define PYBIND11_BYTES_AS_STRING PyString_AsString -#define PYBIND11_BYTES_SIZE PyString_Size -#define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o)) -#define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o)) -#define PYBIND11_BYTES_NAME "str" -#define PYBIND11_STRING_NAME "unicode" -#define PYBIND11_SLICE_OBJECT PySliceObject -#define PYBIND11_FROM_STRING PyString_FromString -#define PYBIND11_STR_TYPE ::pybind11::bytes -#define PYBIND11_BOOL_ATTR "__nonzero__" -#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_nonzero) -#define PYBIND11_PLUGIN_IMPL(name) \ - static PyObject *pybind11_init_wrapper(); \ - extern "C" PYBIND11_EXPORT void init##name() { \ - (void)pybind11_init_wrapper(); \ - } \ - PyObject *pybind11_init_wrapper() -#endif - -#if PY_VERSION_HEX >= 0x03050000 && PY_VERSION_HEX < 0x03050200 -extern "C" { - struct _Py_atomic_address { void *value; }; - PyAPI_DATA(_Py_atomic_address) _PyThreadState_Current; -} -#endif - -#define PYBIND11_TRY_NEXT_OVERLOAD ((PyObject *) 1) // special failure return code -#define PYBIND11_STRINGIFY(x) #x -#define PYBIND11_TOSTRING(x) PYBIND11_STRINGIFY(x) -#define PYBIND11_INTERNALS_ID "__pybind11_" \ - PYBIND11_TOSTRING(PYBIND11_VERSION_MAJOR) "_" PYBIND11_TOSTRING(PYBIND11_VERSION_MINOR) "__" - -/** \rst - ***Deprecated in favor of PYBIND11_MODULE*** - - This macro creates the entry point that will be invoked when the Python interpreter - imports a plugin library. Please create a `module` in the function body and return - the pointer to its underlying Python object at the end. - - .. code-block:: cpp - - PYBIND11_PLUGIN(example) { - pybind11::module m("example", "pybind11 example plugin"); - /// Set up bindings here - return m.ptr(); - } -\endrst */ -#define PYBIND11_PLUGIN(name) \ - PYBIND11_DEPRECATED("PYBIND11_PLUGIN is deprecated, use PYBIND11_MODULE") \ - static PyObject *pybind11_init(); \ - PYBIND11_PLUGIN_IMPL(name) { \ - int major, minor; \ - if (sscanf(Py_GetVersion(), "%i.%i", &major, &minor) != 2) { \ - PyErr_SetString(PyExc_ImportError, "Can't parse Python version."); \ - return nullptr; \ - } else if (major != PY_MAJOR_VERSION || minor != PY_MINOR_VERSION) { \ - PyErr_Format(PyExc_ImportError, \ - "Python version mismatch: module was compiled for " \ - "version %i.%i, while the interpreter is running " \ - "version %i.%i.", PY_MAJOR_VERSION, PY_MINOR_VERSION, \ - major, minor); \ - return nullptr; \ - } \ - try { \ - return pybind11_init(); \ - } catch (pybind11::error_already_set &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } catch (const std::exception &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } \ - } \ - PyObject *pybind11_init() - -/** \rst - This macro creates the entry point that will be invoked when the Python interpreter - imports an extension module. The module name is given as the fist argument and it - should not be in quotes. The second macro argument defines a variable of type - `py::module` which can be used to initialize the module. - - .. code-block:: cpp - - PYBIND11_MODULE(example, m) { - m.doc() = "pybind11 example module"; - - // Add bindings here - m.def("foo", []() { - return "Hello, World!"; - }); - } -\endrst */ -#define PYBIND11_MODULE(name, variable) \ - static void pybind11_init_##name(pybind11::module &); \ - PYBIND11_PLUGIN_IMPL(name) { \ - int major, minor; \ - if (sscanf(Py_GetVersion(), "%i.%i", &major, &minor) != 2) { \ - PyErr_SetString(PyExc_ImportError, "Can't parse Python version."); \ - return nullptr; \ - } else if (major != PY_MAJOR_VERSION || minor != PY_MINOR_VERSION) { \ - PyErr_Format(PyExc_ImportError, \ - "Python version mismatch: module was compiled for " \ - "version %i.%i, while the interpreter is running " \ - "version %i.%i.", PY_MAJOR_VERSION, PY_MINOR_VERSION, \ - major, minor); \ - return nullptr; \ - } \ - auto m = pybind11::module(#name); \ - try { \ - pybind11_init_##name(m); \ - return m.ptr(); \ - } catch (pybind11::error_already_set &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } catch (const std::exception &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } \ - } \ - void pybind11_init_##name(pybind11::module &variable) - - -NAMESPACE_BEGIN(pybind11) - -using ssize_t = Py_ssize_t; -using size_t = std::size_t; - -/// Approach used to cast a previously unknown C++ instance into a Python object -enum class return_value_policy : uint8_t { - /** This is the default return value policy, which falls back to the policy - return_value_policy::take_ownership when the return value is a pointer. - Otherwise, it uses return_value::move or return_value::copy for rvalue - and lvalue references, respectively. See below for a description of what - all of these different policies do. */ - automatic = 0, - - /** As above, but use policy return_value_policy::reference when the return - value is a pointer. This is the default conversion policy for function - arguments when calling Python functions manually from C++ code (i.e. via - handle::operator()). You probably won't need to use this. */ - automatic_reference, - - /** Reference an existing object (i.e. do not create a new copy) and take - ownership. Python will call the destructor and delete operator when the - object’s reference count reaches zero. Undefined behavior ensues when - the C++ side does the same.. */ - take_ownership, - - /** Create a new copy of the returned object, which will be owned by - Python. This policy is comparably safe because the lifetimes of the two - instances are decoupled. */ - copy, - - /** Use std::move to move the return value contents into a new instance - that will be owned by Python. This policy is comparably safe because the - lifetimes of the two instances (move source and destination) are - decoupled. */ - move, - - /** Reference an existing object, but do not take ownership. The C++ side - is responsible for managing the object’s lifetime and deallocating it - when it is no longer used. Warning: undefined behavior will ensue when - the C++ side deletes an object that is still referenced and used by - Python. */ - reference, - - /** This policy only applies to methods and properties. It references the - object without taking ownership similar to the above - return_value_policy::reference policy. In contrast to that policy, the - function or property’s implicit this argument (called the parent) is - considered to be the the owner of the return value (the child). - pybind11 then couples the lifetime of the parent to the child via a - reference relationship that ensures that the parent cannot be garbage - collected while Python is still using the child. More advanced - variations of this scheme are also possible using combinations of - return_value_policy::reference and the keep_alive call policy */ - reference_internal -}; - -NAMESPACE_BEGIN(detail) - -inline static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); } - -// Returns the size as a multiple of sizeof(void *), rounded up. -inline static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); } - -/** - * The space to allocate for simple layout instance holders (see below) in multiple of the size of - * a pointer (e.g. 2 means 16 bytes on 64-bit architectures). The default is the minimum required - * to holder either a std::unique_ptr or std::shared_ptr (which is almost always - * sizeof(std::shared_ptr)). - */ -constexpr size_t instance_simple_holder_in_ptrs() { - static_assert(sizeof(std::shared_ptr) >= sizeof(std::unique_ptr), - "pybind assumes std::shared_ptrs are at least as big as std::unique_ptrs"); - return size_in_ptrs(sizeof(std::shared_ptr)); -} - -// Forward declarations -struct type_info; -struct value_and_holder; - -/// The 'instance' type which needs to be standard layout (need to be able to use 'offsetof') -struct instance { - PyObject_HEAD - /// Storage for pointers and holder; see simple_layout, below, for a description - union { - void *simple_value_holder[1 + instance_simple_holder_in_ptrs()]; - struct { - void **values_and_holders; - uint8_t *status; - } nonsimple; - }; - /// Weak references (needed for keep alive): - PyObject *weakrefs; - /// If true, the pointer is owned which means we're free to manage it with a holder. - bool owned : 1; - /** - * An instance has two possible value/holder layouts. - * - * Simple layout (when this flag is true), means the `simple_value_holder` is set with a pointer - * and the holder object governing that pointer, i.e. [val1*][holder]. This layout is applied - * whenever there is no python-side multiple inheritance of bound C++ types *and* the type's - * holder will fit in the default space (which is large enough to hold either a std::unique_ptr - * or std::shared_ptr). - * - * Non-simple layout applies when using custom holders that require more space than `shared_ptr` - * (which is typically the size of two pointers), or when multiple inheritance is used on the - * python side. Non-simple layout allocates the required amount of memory to have multiple - * bound C++ classes as parents. Under this layout, `nonsimple.values_and_holders` is set to a - * pointer to allocated space of the required space to hold a a sequence of value pointers and - * holders followed `status`, a set of bit flags (1 byte each), i.e. - * [val1*][holder1][val2*][holder2]...[bb...] where each [block] is rounded up to a multiple of - * `sizeof(void *)`. `nonsimple.holder_constructed` is, for convenience, a pointer to the - * beginning of the [bb...] block (but not independently allocated). - * - * Status bits indicate whether the associated holder is constructed (& - * status_holder_constructed) and whether the value pointer is registered (& - * status_instance_registered) in `registered_instances`. - */ - bool simple_layout : 1; - /// For simple layout, tracks whether the holder has been constructed - bool simple_holder_constructed : 1; - /// For simple layout, tracks whether the instance is registered in `registered_instances` - bool simple_instance_registered : 1; - /// If true, get_internals().patients has an entry for this object - bool has_patients : 1; - - /// Initializes all of the above type/values/holders data - void allocate_layout(); - - /// Destroys/deallocates all of the above - void deallocate_layout(); - - /// Returns the value_and_holder wrapper for the given type (or the first, if `find_type` - /// omitted) - value_and_holder get_value_and_holder(const type_info *find_type = nullptr); - - /// Bit values for the non-simple status flags - static constexpr uint8_t status_holder_constructed = 1; - static constexpr uint8_t status_instance_registered = 2; -}; - -static_assert(std::is_standard_layout::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); - -struct overload_hash { - inline size_t operator()(const std::pair& v) const { - size_t value = std::hash()(v.first); - value ^= std::hash()(v.second) + 0x9e3779b9 + (value<<6) + (value>>2); - return value; - } -}; - -// Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly -// other stls, this means `typeid(A)` from one module won't equal `typeid(A)` from another module -// even when `A` is the same, non-hidden-visibility type (e.g. from a common include). Under -// stdlibc++, this doesn't happen: equality and the type_index hash are based on the type name, -// which works. If not under a known-good stl, provide our own name-based hasher and equality -// functions that use the type name. -#if defined(__GLIBCXX__) -inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; } -using type_hash = std::hash; -using type_equal_to = std::equal_to; -#else -inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { - return lhs.name() == rhs.name() || - std::strcmp(lhs.name(), rhs.name()) == 0; -} -struct type_hash { - size_t operator()(const std::type_index &t) const { - size_t hash = 5381; - const char *ptr = t.name(); - while (auto c = static_cast(*ptr++)) - hash = (hash * 33) ^ c; - return hash; - } -}; -struct type_equal_to { - bool operator()(const std::type_index &lhs, const std::type_index &rhs) const { - return lhs.name() == rhs.name() || - std::strcmp(lhs.name(), rhs.name()) == 0; - } -}; -#endif - -template -using type_map = std::unordered_map; - -/// Internal data structure used to track registered instances and types -struct internals { - type_map registered_types_cpp; // std::type_index -> type_info - std::unordered_map> registered_types_py; // PyTypeObject* -> base type_info(s) - std::unordered_multimap registered_instances; // void * -> instance* - std::unordered_set, overload_hash> inactive_overload_cache; - type_map> direct_conversions; - std::unordered_map> patients; - std::forward_list registered_exception_translators; - std::unordered_map shared_data; // Custom data to be shared across extensions - std::vector loader_patient_stack; // Used by `loader_life_support` - PyTypeObject *static_property_type; - PyTypeObject *default_metaclass; - PyObject *instance_base; -#if defined(WITH_THREAD) - decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x - PyInterpreterState *istate = nullptr; -#endif -}; - -/// Return a reference to the current 'internals' information -inline internals &get_internals(); - -/// from __cpp_future__ import (convenient aliases from C++14/17) -#if defined(PYBIND11_CPP14) && (!defined(_MSC_VER) || _MSC_VER >= 1910) -using std::enable_if_t; -using std::conditional_t; -using std::remove_cv_t; -using std::remove_reference_t; -#else -template using enable_if_t = typename std::enable_if::type; -template using conditional_t = typename std::conditional::type; -template using remove_cv_t = typename std::remove_cv::type; -template using remove_reference_t = typename std::remove_reference::type; -#endif - -/// Index sequences -#if defined(PYBIND11_CPP14) -using std::index_sequence; -using std::make_index_sequence; -#else -template struct index_sequence { }; -template struct make_index_sequence_impl : make_index_sequence_impl { }; -template struct make_index_sequence_impl <0, S...> { typedef index_sequence type; }; -template using make_index_sequence = typename make_index_sequence_impl::type; -#endif - -/// Make an index sequence of the indices of true arguments -template struct select_indices_impl { using type = ISeq; }; -template struct select_indices_impl, I, B, Bs...> - : select_indices_impl, index_sequence>, I + 1, Bs...> {}; -template using select_indices = typename select_indices_impl, 0, Bs...>::type; - -/// Backports of std::bool_constant and std::negation to accomodate older compilers -template using bool_constant = std::integral_constant; -template struct negation : bool_constant { }; - -template struct void_t_impl { using type = void; }; -template using void_t = typename void_t_impl::type; - -/// Compile-time all/any/none of that check the boolean value of all template types -#ifdef __cpp_fold_expressions -template using all_of = bool_constant<(Ts::value && ...)>; -template using any_of = bool_constant<(Ts::value || ...)>; -#elif !defined(_MSC_VER) -template struct bools {}; -template using all_of = std::is_same< - bools, - bools>; -template using any_of = negation...>>; -#else -// MSVC has trouble with the above, but supports std::conjunction, which we can use instead (albeit -// at a slight loss of compilation efficiency). -template using all_of = std::conjunction; -template using any_of = std::disjunction; -#endif -template using none_of = negation>; - -template class... Predicates> using satisfies_all_of = all_of...>; -template class... Predicates> using satisfies_any_of = any_of...>; -template class... Predicates> using satisfies_none_of = none_of...>; - -/// Strip the class from a method type -template struct remove_class { }; -template struct remove_class { typedef R type(A...); }; -template struct remove_class { typedef R type(A...); }; - -/// Helper template to strip away type modifiers -template struct intrinsic_type { typedef T type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template struct intrinsic_type { typedef typename intrinsic_type::type type; }; -template using intrinsic_t = typename intrinsic_type::type; - -/// Helper type to replace 'void' in some expressions -struct void_type { }; - -/// Helper template which holds a list of types -template struct type_list { }; - -/// Compile-time integer sum -#ifdef __cpp_fold_expressions -template constexpr size_t constexpr_sum(Ts... ns) { return (0 + ... + size_t{ns}); } -#else -constexpr size_t constexpr_sum() { return 0; } -template -constexpr size_t constexpr_sum(T n, Ts... ns) { return size_t{n} + constexpr_sum(ns...); } -#endif - -NAMESPACE_BEGIN(constexpr_impl) -/// Implementation details for constexpr functions -constexpr int first(int i) { return i; } -template -constexpr int first(int i, T v, Ts... vs) { return v ? i : first(i + 1, vs...); } - -constexpr int last(int /*i*/, int result) { return result; } -template -constexpr int last(int i, int result, T v, Ts... vs) { return last(i + 1, v ? i : result, vs...); } -NAMESPACE_END(constexpr_impl) - -/// Return the index of the first type in Ts which satisfies Predicate. Returns sizeof...(Ts) if -/// none match. -template class Predicate, typename... Ts> -constexpr int constexpr_first() { return constexpr_impl::first(0, Predicate::value...); } - -/// Return the index of the last type in Ts which satisfies Predicate, or -1 if none match. -template class Predicate, typename... Ts> -constexpr int constexpr_last() { return constexpr_impl::last(0, -1, Predicate::value...); } - -/// Return the Nth element from the parameter pack -template -struct pack_element { using type = typename pack_element::type; }; -template -struct pack_element<0, T, Ts...> { using type = T; }; - -/// Return the one and only type which matches the predicate, or Default if none match. -/// If more than one type matches the predicate, fail at compile-time. -template class Predicate, typename Default, typename... Ts> -struct exactly_one { - static constexpr auto found = constexpr_sum(Predicate::value...); - static_assert(found <= 1, "Found more than one type matching the predicate"); - - static constexpr auto index = found ? constexpr_first() : 0; - using type = conditional_t::type, Default>; -}; -template class P, typename Default> -struct exactly_one { using type = Default; }; - -template class Predicate, typename Default, typename... Ts> -using exactly_one_t = typename exactly_one::type; - -/// Defer the evaluation of type T until types Us are instantiated -template struct deferred_type { using type = T; }; -template using deferred_t = typename deferred_type::type; - -/// Like is_base_of, but requires a strict base (i.e. `is_strict_base_of::value == false`, -/// unlike `std::is_base_of`) -template using is_strict_base_of = bool_constant< - std::is_base_of::value && !std::is_same::value>; - -template class Base> -struct is_template_base_of_impl { - template static std::true_type check(Base *); - static std::false_type check(...); -}; - -/// Check if a template is the base of a type. For example: -/// `is_template_base_of` is true if `struct T : Base {}` where U can be anything -template class Base, typename T> -#if !defined(_MSC_VER) -using is_template_base_of = decltype(is_template_base_of_impl::check((remove_cv_t*)nullptr)); -#else // MSVC2015 has trouble with decltype in template aliases -struct is_template_base_of : decltype(is_template_base_of_impl::check((remove_cv_t*)nullptr)) { }; -#endif - -/// Check if T is an instantiation of the template `Class`. For example: -/// `is_instantiation` is true if `T == shared_ptr` where U can be anything. -template class Class, typename T> -struct is_instantiation : std::false_type { }; -template class Class, typename... Us> -struct is_instantiation> : std::true_type { }; - -/// Check if T is std::shared_ptr where U can be anything -template using is_shared_ptr = is_instantiation; - -/// Check if T looks like an input iterator -template struct is_input_iterator : std::false_type {}; -template -struct is_input_iterator()), decltype(++std::declval())>> - : std::true_type {}; - -/// Ignore that a variable is unused in compiler warnings -inline void ignore_unused(const int *) { } - -/// Apply a function over each element of a parameter pack -#ifdef __cpp_fold_expressions -#define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) (((PATTERN), void()), ...) -#else -using expand_side_effects = bool[]; -#define PYBIND11_EXPAND_SIDE_EFFECTS(PATTERN) pybind11::detail::expand_side_effects{ ((PATTERN), void(), false)..., false } -#endif - -NAMESPACE_END(detail) - -/// Returns a named pointer that is shared among all extension modules (using the same -/// pybind11 version) running in the current interpreter. Names starting with underscores -/// are reserved for internal usage. Returns `nullptr` if no matching entry was found. -inline PYBIND11_NOINLINE void* get_shared_data(const std::string& name) { - auto& internals = detail::get_internals(); - auto it = internals.shared_data.find(name); - return it != internals.shared_data.end() ? it->second : nullptr; -} - -/// Set the shared data that can be later recovered by `get_shared_data()`. -inline PYBIND11_NOINLINE void *set_shared_data(const std::string& name, void *data) { - detail::get_internals().shared_data[name] = data; - return data; -} - -/// Returns a typed reference to a shared data entry (by using `get_shared_data()`) if -/// such entry exists. Otherwise, a new object of default-constructible type `T` is -/// added to the shared data under the given name and a reference to it is returned. -template T& get_or_create_shared_data(const std::string& name) { - auto& internals = detail::get_internals(); - auto it = internals.shared_data.find(name); - T* ptr = (T*) (it != internals.shared_data.end() ? it->second : nullptr); - if (!ptr) { - ptr = new T(); - internals.shared_data[name] = ptr; - } - return *ptr; -} - -/// C++ bindings of builtin Python exceptions -class builtin_exception : public std::runtime_error { -public: - using std::runtime_error::runtime_error; - /// Set the error using the Python C API - virtual void set_error() const = 0; -}; - -#define PYBIND11_RUNTIME_EXCEPTION(name, type) \ - class name : public builtin_exception { public: \ - using builtin_exception::builtin_exception; \ - name() : name("") { } \ - void set_error() const override { PyErr_SetString(type, what()); } \ - }; - -PYBIND11_RUNTIME_EXCEPTION(stop_iteration, PyExc_StopIteration) -PYBIND11_RUNTIME_EXCEPTION(index_error, PyExc_IndexError) -PYBIND11_RUNTIME_EXCEPTION(key_error, PyExc_KeyError) -PYBIND11_RUNTIME_EXCEPTION(value_error, PyExc_ValueError) -PYBIND11_RUNTIME_EXCEPTION(type_error, PyExc_TypeError) -PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybind11::cast or handle::call fail due to a type casting error -PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally - -[[noreturn]] PYBIND11_NOINLINE inline void pybind11_fail(const char *reason) { throw std::runtime_error(reason); } -[[noreturn]] PYBIND11_NOINLINE inline void pybind11_fail(const std::string &reason) { throw std::runtime_error(reason); } - -template struct format_descriptor { }; - -NAMESPACE_BEGIN(detail) -// Returns the index of the given type in the type char array below, and in the list in numpy.h -// The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double; -// complex float,double,long double. Note that the long double types only participate when long -// double is actually longer than double (it isn't under MSVC). -// NB: not only the string below but also complex.h and numpy.h rely on this order. -template struct is_fmt_numeric { static constexpr bool value = false; }; -template struct is_fmt_numeric::value>> { - static constexpr bool value = true; - static constexpr int index = std::is_same::value ? 0 : 1 + ( - std::is_integral::value ? detail::log2(sizeof(T))*2 + std::is_unsigned::value : 8 + ( - std::is_same::value ? 1 : std::is_same::value ? 2 : 0)); -}; -NAMESPACE_END(detail) - -template struct format_descriptor::value>> { - static constexpr const char c = "?bBhHiIqQfdg"[detail::is_fmt_numeric::index]; - static constexpr const char value[2] = { c, '\0' }; - static std::string format() { return std::string(1, c); } -}; - -template constexpr const char format_descriptor< - T, detail::enable_if_t::value>>::value[2]; - -/// RAII wrapper that temporarily clears any Python error state -struct error_scope { - PyObject *type, *value, *trace; - error_scope() { PyErr_Fetch(&type, &value, &trace); } - ~error_scope() { PyErr_Restore(type, value, trace); } -}; - -/// Dummy destructor wrapper that can be used to expose classes with a private destructor -struct nodelete { template void operator()(T*) { } }; - -// overload_cast requires variable templates: C++14 -#if defined(PYBIND11_CPP14) -#define PYBIND11_OVERLOAD_CAST 1 - -NAMESPACE_BEGIN(detail) -template -struct overload_cast_impl { - template - constexpr auto operator()(Return (*pf)(Args...)) const noexcept - -> decltype(pf) { return pf; } - - template - constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept - -> decltype(pmf) { return pmf; } - - template - constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept - -> decltype(pmf) { return pmf; } -}; -NAMESPACE_END(detail) - -/// Syntax sugar for resolving overloaded function pointers: -/// - regular: static_cast(&Class::func) -/// - sweet: overload_cast(&Class::func) -template -static constexpr detail::overload_cast_impl overload_cast = {}; -// MSVC 2015 only accepts this particular initialization syntax for this variable template. - -/// Const member function selector for overload_cast -/// - regular: static_cast(&Class::func) -/// - sweet: overload_cast(&Class::func, const_) -static constexpr auto const_ = std::true_type{}; - -#else // no overload_cast: providing something that static_assert-fails: -template struct overload_cast { - static_assert(detail::deferred_t::value, - "pybind11::overload_cast<...> requires compiling in C++14 mode"); -}; -#endif // overload_cast - -NAMESPACE_BEGIN(detail) - -// Adaptor for converting arbitrary container arguments into a vector; implicitly convertible from -// any standard container (or C-style array) supporting std::begin/std::end, any singleton -// arithmetic type (if T is arithmetic), or explicitly constructible from an iterator pair. -template -class any_container { - std::vector v; -public: - any_container() = default; - - // Can construct from a pair of iterators - template ::value>> - any_container(It first, It last) : v(first, last) { } - - // Implicit conversion constructor from any arbitrary container type with values convertible to T - template ())), T>::value>> - any_container(const Container &c) : any_container(std::begin(c), std::end(c)) { } - - // initializer_list's aren't deducible, so don't get matched by the above template; we need this - // to explicitly allow implicit conversion from one: - template ::value>> - any_container(const std::initializer_list &c) : any_container(c.begin(), c.end()) { } - - // Avoid copying if given an rvalue vector of the correct type. - any_container(std::vector &&v) : v(std::move(v)) { } - - // Moves the vector out of an rvalue any_container - operator std::vector &&() && { return std::move(v); } - - // Dereferencing obtains a reference to the underlying vector - std::vector &operator*() { return v; } - const std::vector &operator*() const { return v; } - - // -> lets you call methods on the underlying vector - std::vector *operator->() { return &v; } - const std::vector *operator->() const { return &v; } -}; - -NAMESPACE_END(detail) - - - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/complex.h b/ppocr/postprocess/lanms/include/pybind11/complex.h deleted file mode 100644 index 7d422e20..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/complex.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - pybind11/complex.h: Complex number support - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include - -/// glibc defines I as a macro which breaks things, e.g., boost template names -#ifdef I -# undef I -#endif - -NAMESPACE_BEGIN(pybind11) - -template struct format_descriptor, detail::enable_if_t::value>> { - static constexpr const char c = format_descriptor::c; - static constexpr const char value[3] = { 'Z', c, '\0' }; - static std::string format() { return std::string(value); } -}; - -template constexpr const char format_descriptor< - std::complex, detail::enable_if_t::value>>::value[3]; - -NAMESPACE_BEGIN(detail) - -template struct is_fmt_numeric, detail::enable_if_t::value>> { - static constexpr bool value = true; - static constexpr int index = is_fmt_numeric::index + 3; -}; - -template class type_caster> { -public: - bool load(handle src, bool convert) { - if (!src) - return false; - if (!convert && !PyComplex_Check(src.ptr())) - return false; - Py_complex result = PyComplex_AsCComplex(src.ptr()); - if (result.real == -1.0 && PyErr_Occurred()) { - PyErr_Clear(); - return false; - } - value = std::complex((T) result.real, (T) result.imag); - return true; - } - - static handle cast(const std::complex &src, return_value_policy /* policy */, handle /* parent */) { - return PyComplex_FromDoubles((double) src.real(), (double) src.imag()); - } - - PYBIND11_TYPE_CASTER(std::complex, _("complex")); -}; -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/descr.h b/ppocr/postprocess/lanms/include/pybind11/descr.h deleted file mode 100644 index 23a099cf..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/descr.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - pybind11/descr.h: Helper type for concatenating type signatures - either at runtime (C++11) or compile time (C++14) - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "common.h" - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -/* Concatenate type signatures at compile time using C++14 */ -#if defined(PYBIND11_CPP14) && !defined(_MSC_VER) -#define PYBIND11_CONSTEXPR_DESCR - -template class descr { - template friend class descr; -public: - constexpr descr(char const (&text) [Size1+1], const std::type_info * const (&types)[Size2+1]) - : descr(text, types, - make_index_sequence(), - make_index_sequence()) { } - - constexpr const char *text() const { return m_text; } - constexpr const std::type_info * const * types() const { return m_types; } - - template - constexpr descr operator+(const descr &other) const { - return concat(other, - make_index_sequence(), - make_index_sequence(), - make_index_sequence(), - make_index_sequence()); - } - -protected: - template - constexpr descr( - char const (&text) [Size1+1], - const std::type_info * const (&types) [Size2+1], - index_sequence, index_sequence) - : m_text{text[Indices1]..., '\0'}, - m_types{types[Indices2]..., nullptr } {} - - template - constexpr descr - concat(const descr &other, - index_sequence, index_sequence, - index_sequence, index_sequence) const { - return descr( - { m_text[Indices1]..., other.m_text[OtherIndices1]..., '\0' }, - { m_types[Indices2]..., other.m_types[OtherIndices2]..., nullptr } - ); - } - -protected: - char m_text[Size1 + 1]; - const std::type_info * m_types[Size2 + 1]; -}; - -template constexpr descr _(char const(&text)[Size]) { - return descr(text, { nullptr }); -} - -template struct int_to_str : int_to_str { }; -template struct int_to_str<0, Digits...> { - static constexpr auto digits = descr({ ('0' + Digits)..., '\0' }, { nullptr }); -}; - -// Ternary description (like std::conditional) -template -constexpr enable_if_t> _(char const(&text1)[Size1], char const(&)[Size2]) { - return _(text1); -} -template -constexpr enable_if_t> _(char const(&)[Size1], char const(&text2)[Size2]) { - return _(text2); -} -template -constexpr enable_if_t> _(descr d, descr) { return d; } -template -constexpr enable_if_t> _(descr, descr d) { return d; } - -template auto constexpr _() -> decltype(int_to_str::digits) { - return int_to_str::digits; -} - -template constexpr descr<1, 1> _() { - return descr<1, 1>({ '%', '\0' }, { &typeid(Type), nullptr }); -} - -inline constexpr descr<0, 0> concat() { return _(""); } -template auto constexpr concat(descr descr) { return descr; } -template auto constexpr concat(descr descr, Args&&... args) { return descr + _(", ") + concat(args...); } -template auto constexpr type_descr(descr descr) { return _("{") + descr + _("}"); } - -#define PYBIND11_DESCR constexpr auto - -#else /* Simpler C++11 implementation based on run-time memory allocation and copying */ - -class descr { -public: - PYBIND11_NOINLINE descr(const char *text, const std::type_info * const * types) { - size_t nChars = len(text), nTypes = len(types); - m_text = new char[nChars]; - m_types = new const std::type_info *[nTypes]; - memcpy(m_text, text, nChars * sizeof(char)); - memcpy(m_types, types, nTypes * sizeof(const std::type_info *)); - } - - PYBIND11_NOINLINE descr operator+(descr &&d2) && { - descr r; - - size_t nChars1 = len(m_text), nTypes1 = len(m_types); - size_t nChars2 = len(d2.m_text), nTypes2 = len(d2.m_types); - - r.m_text = new char[nChars1 + nChars2 - 1]; - r.m_types = new const std::type_info *[nTypes1 + nTypes2 - 1]; - memcpy(r.m_text, m_text, (nChars1-1) * sizeof(char)); - memcpy(r.m_text + nChars1 - 1, d2.m_text, nChars2 * sizeof(char)); - memcpy(r.m_types, m_types, (nTypes1-1) * sizeof(std::type_info *)); - memcpy(r.m_types + nTypes1 - 1, d2.m_types, nTypes2 * sizeof(std::type_info *)); - - delete[] m_text; delete[] m_types; - delete[] d2.m_text; delete[] d2.m_types; - - return r; - } - - char *text() { return m_text; } - const std::type_info * * types() { return m_types; } - -protected: - PYBIND11_NOINLINE descr() { } - - template static size_t len(const T *ptr) { // return length including null termination - const T *it = ptr; - while (*it++ != (T) 0) - ; - return static_cast(it - ptr); - } - - const std::type_info **m_types = nullptr; - char *m_text = nullptr; -}; - -/* The 'PYBIND11_NOINLINE inline' combinations below are intentional to get the desired linkage while producing as little object code as possible */ - -PYBIND11_NOINLINE inline descr _(const char *text) { - const std::type_info *types[1] = { nullptr }; - return descr(text, types); -} - -template PYBIND11_NOINLINE enable_if_t _(const char *text1, const char *) { return _(text1); } -template PYBIND11_NOINLINE enable_if_t _(char const *, const char *text2) { return _(text2); } -template PYBIND11_NOINLINE enable_if_t _(descr d, descr) { return d; } -template PYBIND11_NOINLINE enable_if_t _(descr, descr d) { return d; } - -template PYBIND11_NOINLINE descr _() { - const std::type_info *types[2] = { &typeid(Type), nullptr }; - return descr("%", types); -} - -template PYBIND11_NOINLINE descr _() { - const std::type_info *types[1] = { nullptr }; - return descr(std::to_string(Size).c_str(), types); -} - -PYBIND11_NOINLINE inline descr concat() { return _(""); } -PYBIND11_NOINLINE inline descr concat(descr &&d) { return d; } -template PYBIND11_NOINLINE descr concat(descr &&d, Args&&... args) { return std::move(d) + _(", ") + concat(std::forward(args)...); } -PYBIND11_NOINLINE inline descr type_descr(descr&& d) { return _("{") + std::move(d) + _("}"); } - -#define PYBIND11_DESCR ::pybind11::detail::descr -#endif - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/eigen.h b/ppocr/postprocess/lanms/include/pybind11/eigen.h deleted file mode 100644 index fc070516..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/eigen.h +++ /dev/null @@ -1,610 +0,0 @@ -/* - pybind11/eigen.h: Transparent conversion for dense and sparse Eigen matrices - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "numpy.h" - -#if defined(__INTEL_COMPILER) -# pragma warning(disable: 1682) // implicit conversion of a 64-bit integral type to a smaller integral type (potential portability problem) -#elif defined(__GNUG__) || defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wconversion" -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -# if __GNUC__ >= 7 -# pragma GCC diagnostic ignored "-Wint-in-bool-context" -# endif -#endif - -#include -#include - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant -#endif - -// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit -// move constructors that break things. We could detect this an explicitly copy, but an extra copy -// of matrices seems highly undesirable. -static_assert(EIGEN_VERSION_AT_LEAST(3,2,7), "Eigen support in pybind11 requires Eigen >= 3.2.7"); - -NAMESPACE_BEGIN(pybind11) - -// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides: -using EigenDStride = Eigen::Stride; -template using EigenDRef = Eigen::Ref; -template using EigenDMap = Eigen::Map; - -NAMESPACE_BEGIN(detail) - -#if EIGEN_VERSION_AT_LEAST(3,3,0) -using EigenIndex = Eigen::Index; -#else -using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE; -#endif - -// Matches Eigen::Map, Eigen::Ref, blocks, etc: -template using is_eigen_dense_map = all_of, std::is_base_of, T>>; -template using is_eigen_mutable_map = std::is_base_of, T>; -template using is_eigen_dense_plain = all_of>, is_template_base_of>; -template using is_eigen_sparse = is_template_base_of; -// Test for objects inheriting from EigenBase that aren't captured by the above. This -// basically covers anything that can be assigned to a dense matrix but that don't have a typical -// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and -// SelfAdjointView fall into this category. -template using is_eigen_other = all_of< - is_template_base_of, - negation, is_eigen_dense_plain, is_eigen_sparse>> ->; - -// Captures numpy/eigen conformability status (returned by EigenProps::conformable()): -template struct EigenConformable { - bool conformable = false; - EigenIndex rows = 0, cols = 0; - EigenDStride stride{0, 0}; // Only valid if negativestrides is false! - bool negativestrides = false; // If true, do not use stride! - - EigenConformable(bool fits = false) : conformable{fits} {} - // Matrix type: - EigenConformable(EigenIndex r, EigenIndex c, - EigenIndex rstride, EigenIndex cstride) : - conformable{true}, rows{r}, cols{c} { - // TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity. http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747 - if (rstride < 0 || cstride < 0) { - negativestrides = true; - } else { - stride = {EigenRowMajor ? rstride : cstride /* outer stride */, - EigenRowMajor ? cstride : rstride /* inner stride */ }; - } - } - // Vector type: - EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride) - : EigenConformable(r, c, r == 1 ? c*stride : stride, c == 1 ? r : r*stride) {} - - template bool stride_compatible() const { - // To have compatible strides, we need (on both dimensions) one of fully dynamic strides, - // matching strides, or a dimension size of 1 (in which case the stride value is irrelevant) - return - !negativestrides && - (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner() || - (EigenRowMajor ? cols : rows) == 1) && - (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer() || - (EigenRowMajor ? rows : cols) == 1); - } - operator bool() const { return conformable; } -}; - -template struct eigen_extract_stride { using type = Type; }; -template -struct eigen_extract_stride> { using type = StrideType; }; -template -struct eigen_extract_stride> { using type = StrideType; }; - -// Helper struct for extracting information from an Eigen type -template struct EigenProps { - using Type = Type_; - using Scalar = typename Type::Scalar; - using StrideType = typename eigen_extract_stride::type; - static constexpr EigenIndex - rows = Type::RowsAtCompileTime, - cols = Type::ColsAtCompileTime, - size = Type::SizeAtCompileTime; - static constexpr bool - row_major = Type::IsRowMajor, - vector = Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1 - fixed_rows = rows != Eigen::Dynamic, - fixed_cols = cols != Eigen::Dynamic, - fixed = size != Eigen::Dynamic, // Fully-fixed size - dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size - - template using if_zero = std::integral_constant; - static constexpr EigenIndex inner_stride = if_zero::value, - outer_stride = if_zero::value; - static constexpr bool dynamic_stride = inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic; - static constexpr bool requires_row_major = !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1; - static constexpr bool requires_col_major = !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1; - - // Takes an input array and determines whether we can make it fit into the Eigen type. If - // the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector - // (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type). - static EigenConformable conformable(const array &a) { - const auto dims = a.ndim(); - if (dims < 1 || dims > 2) - return false; - - if (dims == 2) { // Matrix type: require exact match (or dynamic) - - EigenIndex - np_rows = a.shape(0), - np_cols = a.shape(1), - np_rstride = a.strides(0) / static_cast(sizeof(Scalar)), - np_cstride = a.strides(1) / static_cast(sizeof(Scalar)); - if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) - return false; - - return {np_rows, np_cols, np_rstride, np_cstride}; - } - - // Otherwise we're storing an n-vector. Only one of the strides will be used, but whichever - // is used, we want the (single) numpy stride value. - const EigenIndex n = a.shape(0), - stride = a.strides(0) / static_cast(sizeof(Scalar)); - - if (vector) { // Eigen type is a compile-time vector - if (fixed && size != n) - return false; // Vector size mismatch - return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride}; - } - else if (fixed) { - // The type has a fixed size, but is not a vector: abort - return false; - } - else if (fixed_cols) { - // Since this isn't a vector, cols must be != 1. We allow this only if it exactly - // equals the number of elements (rows is Dynamic, and so 1 row is allowed). - if (cols != n) return false; - return {1, n, stride}; - } - else { - // Otherwise it's either fully dynamic, or column dynamic; both become a column vector - if (fixed_rows && rows != n) return false; - return {n, 1, stride}; - } - } - - static PYBIND11_DESCR descriptor() { - constexpr bool show_writeable = is_eigen_dense_map::value && is_eigen_mutable_map::value; - constexpr bool show_order = is_eigen_dense_map::value; - constexpr bool show_c_contiguous = show_order && requires_row_major; - constexpr bool show_f_contiguous = !show_c_contiguous && show_order && requires_col_major; - - return type_descr(_("numpy.ndarray[") + npy_format_descriptor::name() + - _("[") + _(_<(size_t) rows>(), _("m")) + - _(", ") + _(_<(size_t) cols>(), _("n")) + - _("]") + - // For a reference type (e.g. Ref) we have other constraints that might need to be - // satisfied: writeable=True (for a mutable reference), and, depending on the map's stride - // options, possibly f_contiguous or c_contiguous. We include them in the descriptor output - // to provide some hint as to why a TypeError is occurring (otherwise it can be confusing to - // see that a function accepts a 'numpy.ndarray[float64[3,2]]' and an error message that you - // *gave* a numpy.ndarray of the right type and dimensions. - _(", flags.writeable", "") + - _(", flags.c_contiguous", "") + - _(", flags.f_contiguous", "") + - _("]") - ); - } -}; - -// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data, -// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array. -template handle eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) { - constexpr ssize_t elem_size = sizeof(typename props::Scalar); - array a; - if (props::vector) - a = array({ src.size() }, { elem_size * src.innerStride() }, src.data(), base); - else - a = array({ src.rows(), src.cols() }, { elem_size * src.rowStride(), elem_size * src.colStride() }, - src.data(), base); - - if (!writeable) - array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_; - - return a.release(); -} - -// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that -// reference the Eigen object's data with `base` as the python-registered base class (if omitted, -// the base will be set to None, and lifetime management is up to the caller). The numpy array is -// non-writeable if the given type is const. -template -handle eigen_ref_array(Type &src, handle parent = none()) { - // none here is to get past array's should-we-copy detection, which currently always - // copies when there is no base. Setting the base to None should be harmless. - return eigen_array_cast(src, parent, !std::is_const::value); -} - -// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a numpy -// array that references the encapsulated data with a python-side reference to the capsule to tie -// its destruction to that of any dependent python objects. Const-ness is determined by whether or -// not the Type of the pointer given is const. -template ::value>> -handle eigen_encapsulate(Type *src) { - capsule base(src, [](void *o) { delete static_cast(o); }); - return eigen_ref_array(*src, base); -} - -// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense -// types. -template -struct type_caster::value>> { - using Scalar = typename Type::Scalar; - using props = EigenProps; - - bool load(handle src, bool convert) { - // If we're in no-convert mode, only load if given an array of the correct type - if (!convert && !isinstance>(src)) - return false; - - // Coerce into an array, but don't do type conversion yet; the copy below handles it. - auto buf = array::ensure(src); - - if (!buf) - return false; - - auto dims = buf.ndim(); - if (dims < 1 || dims > 2) - return false; - - auto fits = props::conformable(buf); - if (!fits) - return false; - - // Allocate the new type, then build a numpy reference into it - value = Type(fits.rows, fits.cols); - auto ref = reinterpret_steal(eigen_ref_array(value)); - if (dims == 1) ref = ref.squeeze(); - - int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr()); - - if (result < 0) { // Copy failed! - PyErr_Clear(); - return false; - } - - return true; - } - -private: - - // Cast implementation - template - static handle cast_impl(CType *src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::take_ownership: - case return_value_policy::automatic: - return eigen_encapsulate(src); - case return_value_policy::move: - return eigen_encapsulate(new CType(std::move(*src))); - case return_value_policy::copy: - return eigen_array_cast(*src); - case return_value_policy::reference: - case return_value_policy::automatic_reference: - return eigen_ref_array(*src); - case return_value_policy::reference_internal: - return eigen_ref_array(*src, parent); - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - }; - } - -public: - - // Normal returned non-reference, non-const value: - static handle cast(Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // If you return a non-reference const, we mark the numpy array readonly: - static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) { - return cast_impl(&src, return_value_policy::move, parent); - } - // lvalue reference return; default (automatic) becomes copy - static handle cast(Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) - policy = return_value_policy::copy; - return cast_impl(&src, policy, parent); - } - // const lvalue reference return; default (automatic) becomes copy - static handle cast(const Type &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference) - policy = return_value_policy::copy; - return cast(&src, policy, parent); - } - // non-const pointer return - static handle cast(Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - // const pointer return - static handle cast(const Type *src, return_value_policy policy, handle parent) { - return cast_impl(src, policy, parent); - } - - static PYBIND11_DESCR name() { return props::descriptor(); } - - operator Type*() { return &value; } - operator Type&() { return value; } - operator Type&&() && { return std::move(value); } - template using cast_op_type = movable_cast_op_type; - -private: - Type value; -}; - -// Eigen Ref/Map classes have slightly different policy requirements, meaning we don't want to force -// `move` when a Ref/Map rvalue is returned; we treat Ref<> sort of like a pointer (we care about -// the underlying data, not the outer shell). -template -struct return_value_policy_override::value>> { - static return_value_policy policy(return_value_policy p) { return p; } -}; - -// Base class for casting reference/map/block/etc. objects back to python. -template struct eigen_map_caster { -private: - using props = EigenProps; - -public: - - // Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has - // to stay around), but we'll allow it under the assumption that you know what you're doing (and - // have an appropriate keep_alive in place). We return a numpy array pointing directly at the - // ref's data (The numpy array ends up read-only if the ref was to a const matrix type.) Note - // that this means you need to ensure you don't destroy the object in some other way (e.g. with - // an appropriate keep_alive, or with a reference to a statically allocated matrix). - static handle cast(const MapType &src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::copy: - return eigen_array_cast(src); - case return_value_policy::reference_internal: - return eigen_array_cast(src, parent, is_eigen_mutable_map::value); - case return_value_policy::reference: - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - return eigen_array_cast(src, none(), is_eigen_mutable_map::value); - default: - // move, take_ownership don't make any sense for a ref/map: - pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type"); - } - } - - static PYBIND11_DESCR name() { return props::descriptor(); } - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator MapType() = delete; - template using cast_op_type = MapType; -}; - -// We can return any map-like object (but can only load Refs, specialized next): -template struct type_caster::value>> - : eigen_map_caster {}; - -// Loader for Ref<...> arguments. See the documentation for info on how to make this work without -// copying (it requires some extra effort in many cases). -template -struct type_caster< - Eigen::Ref, - enable_if_t>::value> -> : public eigen_map_caster> { -private: - using Type = Eigen::Ref; - using props = EigenProps; - using Scalar = typename props::Scalar; - using MapType = Eigen::Map; - using Array = array_t; - static constexpr bool need_writeable = is_eigen_mutable_map::value; - // Delay construction (these have no default constructor) - std::unique_ptr map; - std::unique_ptr ref; - // Our array. When possible, this is just a numpy array pointing to the source data, but - // sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an incompatible - // layout, or is an array of a type that needs to be converted). Using a numpy temporary - // (rather than an Eigen temporary) saves an extra copy when we need both type conversion and - // storage order conversion. (Note that we refuse to use this temporary copy when loading an - // argument for a Ref with M non-const, i.e. a read-write reference). - Array copy_or_ref; -public: - bool load(handle src, bool convert) { - // First check whether what we have is already an array of the right type. If not, we can't - // avoid a copy (because the copy is also going to do type conversion). - bool need_copy = !isinstance(src); - - EigenConformable fits; - if (!need_copy) { - // We don't need a converting copy, but we also need to check whether the strides are - // compatible with the Ref's stride requirements - Array aref = reinterpret_borrow(src); - - if (aref && (!need_writeable || aref.writeable())) { - fits = props::conformable(aref); - if (!fits) return false; // Incompatible dimensions - if (!fits.template stride_compatible()) - need_copy = true; - else - copy_or_ref = std::move(aref); - } - else { - need_copy = true; - } - } - - if (need_copy) { - // We need to copy: If we need a mutable reference, or we're not supposed to convert - // (either because we're in the no-convert overload pass, or because we're explicitly - // instructed not to copy (via `py::arg().noconvert()`) we have to fail loading. - if (!convert || need_writeable) return false; - - Array copy = Array::ensure(src); - if (!copy) return false; - fits = props::conformable(copy); - if (!fits || !fits.template stride_compatible()) - return false; - copy_or_ref = std::move(copy); - loader_life_support::add_patient(copy_or_ref); - } - - ref.reset(); - map.reset(new MapType(data(copy_or_ref), fits.rows, fits.cols, make_stride(fits.stride.outer(), fits.stride.inner()))); - ref.reset(new Type(*map)); - - return true; - } - - operator Type*() { return ref.get(); } - operator Type&() { return *ref; } - template using cast_op_type = pybind11::detail::cast_op_type<_T>; - -private: - template ::value, int> = 0> - Scalar *data(Array &a) { return a.mutable_data(); } - - template ::value, int> = 0> - const Scalar *data(Array &a) { return a.data(); } - - // Attempt to figure out a constructor of `Stride` that will work. - // If both strides are fixed, use a default constructor: - template using stride_ctor_default = bool_constant< - S::InnerStrideAtCompileTime != Eigen::Dynamic && S::OuterStrideAtCompileTime != Eigen::Dynamic && - std::is_default_constructible::value>; - // Otherwise, if there is a two-index constructor, assume it is (outer,inner) like - // Eigen::Stride, and use it: - template using stride_ctor_dual = bool_constant< - !stride_ctor_default::value && std::is_constructible::value>; - // Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use - // it (passing whichever stride is dynamic). - template using stride_ctor_outer = bool_constant< - !any_of, stride_ctor_dual>::value && - S::OuterStrideAtCompileTime == Eigen::Dynamic && S::InnerStrideAtCompileTime != Eigen::Dynamic && - std::is_constructible::value>; - template using stride_ctor_inner = bool_constant< - !any_of, stride_ctor_dual>::value && - S::InnerStrideAtCompileTime == Eigen::Dynamic && S::OuterStrideAtCompileTime != Eigen::Dynamic && - std::is_constructible::value>; - - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex) { return S(); } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex inner) { return S(outer, inner); } - template ::value, int> = 0> - static S make_stride(EigenIndex outer, EigenIndex) { return S(outer); } - template ::value, int> = 0> - static S make_stride(EigenIndex, EigenIndex inner) { return S(inner); } - -}; - -// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not -// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout). -// load() is not supported, but we can cast them into the python domain by first copying to a -// regular Eigen::Matrix, then casting that. -template -struct type_caster::value>> { -protected: - using Matrix = Eigen::Matrix; - using props = EigenProps; -public: - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - handle h = eigen_encapsulate(new Matrix(src)); - return h; - } - static handle cast(const Type *src, return_value_policy policy, handle parent) { return cast(*src, policy, parent); } - - static PYBIND11_DESCR name() { return props::descriptor(); } - - // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return - // types but not bound arguments). We still provide them (with an explicitly delete) so that - // you end up here if you try anyway. - bool load(handle, bool) = delete; - operator Type() = delete; - template using cast_op_type = Type; -}; - -template -struct type_caster::value>> { - typedef typename Type::Scalar Scalar; - typedef remove_reference_t().outerIndexPtr())> StorageIndex; - typedef typename Type::Index Index; - static constexpr bool rowMajor = Type::IsRowMajor; - - bool load(handle src, bool) { - if (!src) - return false; - - auto obj = reinterpret_borrow(src); - object sparse_module = module::import("scipy.sparse"); - object matrix_type = sparse_module.attr( - rowMajor ? "csr_matrix" : "csc_matrix"); - - if (!obj.get_type().is(matrix_type)) { - try { - obj = matrix_type(obj); - } catch (const error_already_set &) { - return false; - } - } - - auto values = array_t((object) obj.attr("data")); - auto innerIndices = array_t((object) obj.attr("indices")); - auto outerIndices = array_t((object) obj.attr("indptr")); - auto shape = pybind11::tuple((pybind11::object) obj.attr("shape")); - auto nnz = obj.attr("nnz").cast(); - - if (!values || !innerIndices || !outerIndices) - return false; - - value = Eigen::MappedSparseMatrix( - shape[0].cast(), shape[1].cast(), nnz, - outerIndices.mutable_data(), innerIndices.mutable_data(), values.mutable_data()); - - return true; - } - - static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) { - const_cast(src).makeCompressed(); - - object matrix_type = module::import("scipy.sparse").attr( - rowMajor ? "csr_matrix" : "csc_matrix"); - - array data(src.nonZeros(), src.valuePtr()); - array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr()); - array innerIndices(src.nonZeros(), src.innerIndexPtr()); - - return matrix_type( - std::make_tuple(data, innerIndices, outerIndices), - std::make_pair(src.rows(), src.cols()) - ).release(); - } - - PYBIND11_TYPE_CASTER(Type, _<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[", "scipy.sparse.csc_matrix[") - + npy_format_descriptor::name() + _("]")); -}; - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) - -#if defined(__GNUG__) || defined(__clang__) -# pragma GCC diagnostic pop -#elif defined(_MSC_VER) -# pragma warning(pop) -#endif diff --git a/ppocr/postprocess/lanms/include/pybind11/embed.h b/ppocr/postprocess/lanms/include/pybind11/embed.h deleted file mode 100644 index 0eb656b0..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/embed.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - pybind11/embed.h: Support for embedding the interpreter - - Copyright (c) 2017 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include "eval.h" - -#if defined(PYPY_VERSION) -# error Embedding the interpreter is not supported with PyPy -#endif - -#if PY_MAJOR_VERSION >= 3 -# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \ - extern "C" PyObject *pybind11_init_impl_##name() { \ - return pybind11_init_wrapper_##name(); \ - } -#else -# define PYBIND11_EMBEDDED_MODULE_IMPL(name) \ - extern "C" void pybind11_init_impl_##name() { \ - pybind11_init_wrapper_##name(); \ - } -#endif - -/** \rst - Add a new module to the table of builtins for the interpreter. Must be - defined in global scope. The first macro parameter is the name of the - module (without quotes). The second parameter is the variable which will - be used as the interface to add functions and classes to the module. - - .. code-block:: cpp - - PYBIND11_EMBEDDED_MODULE(example, m) { - // ... initialize functions and classes here - m.def("foo", []() { - return "Hello, World!"; - }); - } - \endrst */ -#define PYBIND11_EMBEDDED_MODULE(name, variable) \ - static void pybind11_init_##name(pybind11::module &); \ - static PyObject *pybind11_init_wrapper_##name() { \ - auto m = pybind11::module(#name); \ - try { \ - pybind11_init_##name(m); \ - return m.ptr(); \ - } catch (pybind11::error_already_set &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } catch (const std::exception &e) { \ - PyErr_SetString(PyExc_ImportError, e.what()); \ - return nullptr; \ - } \ - } \ - PYBIND11_EMBEDDED_MODULE_IMPL(name) \ - pybind11::detail::embedded_module name(#name, pybind11_init_impl_##name); \ - void pybind11_init_##name(pybind11::module &variable) - - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -/// Python 2.7/3.x compatible version of `PyImport_AppendInittab` and error checks. -struct embedded_module { -#if PY_MAJOR_VERSION >= 3 - using init_t = PyObject *(*)(); -#else - using init_t = void (*)(); -#endif - embedded_module(const char *name, init_t init) { - if (Py_IsInitialized()) - pybind11_fail("Can't add new modules after the interpreter has been initialized"); - - auto result = PyImport_AppendInittab(name, init); - if (result == -1) - pybind11_fail("Insufficient memory to add a new module"); - } -}; - -NAMESPACE_END(detail) - -/** \rst - Initialize the Python interpreter. No other pybind11 or CPython API functions can be - called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The - optional parameter can be used to skip the registration of signal handlers (see the - Python documentation for details). Calling this function again after the interpreter - has already been initialized is a fatal error. - \endrst */ -inline void initialize_interpreter(bool init_signal_handlers = true) { - if (Py_IsInitialized()) - pybind11_fail("The interpreter is already running"); - - Py_InitializeEx(init_signal_handlers ? 1 : 0); - - // Make .py files in the working directory available by default - auto sys_path = reinterpret_borrow(module::import("sys").attr("path")); - sys_path.append("."); -} - -/** \rst - Shut down the Python interpreter. No pybind11 or CPython API functions can be called - after this. In addition, pybind11 objects must not outlive the interpreter: - - .. code-block:: cpp - - { // BAD - py::initialize_interpreter(); - auto hello = py::str("Hello, World!"); - py::finalize_interpreter(); - } // <-- BOOM, hello's destructor is called after interpreter shutdown - - { // GOOD - py::initialize_interpreter(); - { // scoped - auto hello = py::str("Hello, World!"); - } // <-- OK, hello is cleaned up properly - py::finalize_interpreter(); - } - - { // BETTER - py::scoped_interpreter guard{}; - auto hello = py::str("Hello, World!"); - } - - .. warning:: - - The interpreter can be restarted by calling `initialize_interpreter` again. - Modules created using pybind11 can be safely re-initialized. However, Python - itself cannot completely unload binary extension modules and there are several - caveats with regard to interpreter restarting. All the details can be found - in the CPython documentation. In short, not all interpreter memory may be - freed, either due to reference cycles or user-created global data. - - \endrst */ -inline void finalize_interpreter() { - handle builtins(PyEval_GetBuiltins()); - const char *id = PYBIND11_INTERNALS_ID; - - // Get the internals pointer (without creating it if it doesn't exist). It's possible for the - // internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()` - // during destruction), so we get the pointer-pointer here and check it after Py_Finalize(). - detail::internals **internals_ptr_ptr = &detail::get_internals_ptr(); - // It could also be stashed in builtins, so look there too: - if (builtins.contains(id) && isinstance(builtins[id])) - internals_ptr_ptr = capsule(builtins[id]); - - Py_Finalize(); - - if (internals_ptr_ptr) { - delete *internals_ptr_ptr; - *internals_ptr_ptr = nullptr; - } -} - -/** \rst - Scope guard version of `initialize_interpreter` and `finalize_interpreter`. - This a move-only guard and only a single instance can exist. - - .. code-block:: cpp - - #include - - int main() { - py::scoped_interpreter guard{}; - py::print(Hello, World!); - } // <-- interpreter shutdown - \endrst */ -class scoped_interpreter { -public: - scoped_interpreter(bool init_signal_handlers = true) { - initialize_interpreter(init_signal_handlers); - } - - scoped_interpreter(const scoped_interpreter &) = delete; - scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; } - scoped_interpreter &operator=(const scoped_interpreter &) = delete; - scoped_interpreter &operator=(scoped_interpreter &&) = delete; - - ~scoped_interpreter() { - if (is_valid) - finalize_interpreter(); - } - -private: - bool is_valid = true; -}; - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/eval.h b/ppocr/postprocess/lanms/include/pybind11/eval.h deleted file mode 100644 index 165003b8..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/eval.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - pybind11/exec.h: Support for evaluating Python expressions and statements - from strings and files - - Copyright (c) 2016 Klemens Morgenstern and - Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" - -NAMESPACE_BEGIN(pybind11) - -enum eval_mode { - /// Evaluate a string containing an isolated expression - eval_expr, - - /// Evaluate a string containing a single statement. Returns \c none - eval_single_statement, - - /// Evaluate a string containing a sequence of statement. Returns \c none - eval_statements -}; - -template -object eval(str expr, object global = globals(), object local = object()) { - if (!local) - local = global; - - /* PyRun_String does not accept a PyObject / encoding specifier, - this seems to be the only alternative */ - std::string buffer = "# -*- coding: utf-8 -*-\n" + (std::string) expr; - - int start; - switch (mode) { - case eval_expr: start = Py_eval_input; break; - case eval_single_statement: start = Py_single_input; break; - case eval_statements: start = Py_file_input; break; - default: pybind11_fail("invalid evaluation mode"); - } - - PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr()); - if (!result) - throw error_already_set(); - return reinterpret_steal(result); -} - -template -object eval(const char (&s)[N], object global = globals(), object local = object()) { - /* Support raw string literals by removing common leading whitespace */ - auto expr = (s[0] == '\n') ? str(module::import("textwrap").attr("dedent")(s)) - : str(s); - return eval(expr, global, local); -} - -inline void exec(str expr, object global = globals(), object local = object()) { - eval(expr, global, local); -} - -template -void exec(const char (&s)[N], object global = globals(), object local = object()) { - eval(s, global, local); -} - -template -object eval_file(str fname, object global = globals(), object local = object()) { - if (!local) - local = global; - - int start; - switch (mode) { - case eval_expr: start = Py_eval_input; break; - case eval_single_statement: start = Py_single_input; break; - case eval_statements: start = Py_file_input; break; - default: pybind11_fail("invalid evaluation mode"); - } - - int closeFile = 1; - std::string fname_str = (std::string) fname; -#if PY_VERSION_HEX >= 0x03040000 - FILE *f = _Py_fopen_obj(fname.ptr(), "r"); -#elif PY_VERSION_HEX >= 0x03000000 - FILE *f = _Py_fopen(fname.ptr(), "r"); -#else - /* No unicode support in open() :( */ - auto fobj = reinterpret_steal(PyFile_FromString( - const_cast(fname_str.c_str()), - const_cast("r"))); - FILE *f = nullptr; - if (fobj) - f = PyFile_AsFile(fobj.ptr()); - closeFile = 0; -#endif - if (!f) { - PyErr_Clear(); - pybind11_fail("File \"" + fname_str + "\" could not be opened!"); - } - -#if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION) - PyObject *result = PyRun_File(f, fname_str.c_str(), start, global.ptr(), - local.ptr()); - (void) closeFile; -#else - PyObject *result = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), - local.ptr(), closeFile); -#endif - - if (!result) - throw error_already_set(); - return reinterpret_steal(result); -} - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/functional.h b/ppocr/postprocess/lanms/include/pybind11/functional.h deleted file mode 100644 index fdb6b330..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/functional.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - pybind11/functional.h: std::function<> support - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -template -struct type_caster> { - using type = std::function; - using retval_type = conditional_t::value, void_type, Return>; - using function_type = Return (*) (Args...); - -public: - bool load(handle src, bool convert) { - if (src.is_none()) { - // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) return false; - return true; - } - - if (!isinstance(src)) - return false; - - auto func = reinterpret_borrow(src); - - /* - When passing a C++ function as an argument to another C++ - function via Python, every function call would normally involve - a full C++ -> Python -> C++ roundtrip, which can be prohibitive. - Here, we try to at least detect the case where the function is - stateless (i.e. function pointer or lambda function without - captured variables), in which case the roundtrip can be avoided. - */ - if (auto cfunc = func.cpp_function()) { - auto c = reinterpret_borrow(PyCFunction_GET_SELF(cfunc.ptr())); - auto rec = (function_record *) c; - - if (rec && rec->is_stateless && - same_type(typeid(function_type), *reinterpret_cast(rec->data[1]))) { - struct capture { function_type f; }; - value = ((capture *) &rec->data)->f; - return true; - } - } - - value = [func](Args... args) -> Return { - gil_scoped_acquire acq; - object retval(func(std::forward(args)...)); - /* Visual studio 2015 parser issue: need parentheses around this expression */ - return (retval.template cast()); - }; - return true; - } - - template - static handle cast(Func &&f_, return_value_policy policy, handle /* parent */) { - if (!f_) - return none().inc_ref(); - - auto result = f_.template target(); - if (result) - return cpp_function(*result, policy).release(); - else - return cpp_function(std::forward(f_), policy).release(); - } - - PYBIND11_TYPE_CASTER(type, _("Callable[[") + - argument_loader::arg_names() + _("], ") + - make_caster::name() + - _("]")); -}; - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/numpy.h b/ppocr/postprocess/lanms/include/pybind11/numpy.h deleted file mode 100644 index 388e2122..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/numpy.h +++ /dev/null @@ -1,1598 +0,0 @@ -/* - pybind11/numpy.h: Basic NumPy support, vectorize() wrapper - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include "complex.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant -#endif - -/* This will be true on all flat address space platforms and allows us to reduce the - whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size - and dimension types (e.g. shape, strides, indexing), instead of inflicting this - upon the library user. */ -static_assert(sizeof(ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t"); - -NAMESPACE_BEGIN(pybind11) - -class array; // Forward declaration - -NAMESPACE_BEGIN(detail) -template struct npy_format_descriptor; - -struct PyArrayDescr_Proxy { - PyObject_HEAD - PyObject *typeobj; - char kind; - char type; - char byteorder; - char flags; - int type_num; - int elsize; - int alignment; - char *subarray; - PyObject *fields; - PyObject *names; -}; - -struct PyArray_Proxy { - PyObject_HEAD - char *data; - int nd; - ssize_t *dimensions; - ssize_t *strides; - PyObject *base; - PyObject *descr; - int flags; -}; - -struct PyVoidScalarObject_Proxy { - PyObject_VAR_HEAD - char *obval; - PyArrayDescr_Proxy *descr; - int flags; - PyObject *base; -}; - -struct numpy_type_info { - PyObject* dtype_ptr; - std::string format_str; -}; - -struct numpy_internals { - std::unordered_map registered_dtypes; - - numpy_type_info *get_type_info(const std::type_info& tinfo, bool throw_if_missing = true) { - auto it = registered_dtypes.find(std::type_index(tinfo)); - if (it != registered_dtypes.end()) - return &(it->second); - if (throw_if_missing) - pybind11_fail(std::string("NumPy type info missing for ") + tinfo.name()); - return nullptr; - } - - template numpy_type_info *get_type_info(bool throw_if_missing = true) { - return get_type_info(typeid(typename std::remove_cv::type), throw_if_missing); - } -}; - -inline PYBIND11_NOINLINE void load_numpy_internals(numpy_internals* &ptr) { - ptr = &get_or_create_shared_data("_numpy_internals"); -} - -inline numpy_internals& get_numpy_internals() { - static numpy_internals* ptr = nullptr; - if (!ptr) - load_numpy_internals(ptr); - return *ptr; -} - -struct npy_api { - enum constants { - NPY_ARRAY_C_CONTIGUOUS_ = 0x0001, - NPY_ARRAY_F_CONTIGUOUS_ = 0x0002, - NPY_ARRAY_OWNDATA_ = 0x0004, - NPY_ARRAY_FORCECAST_ = 0x0010, - NPY_ARRAY_ENSUREARRAY_ = 0x0040, - NPY_ARRAY_ALIGNED_ = 0x0100, - NPY_ARRAY_WRITEABLE_ = 0x0400, - NPY_BOOL_ = 0, - NPY_BYTE_, NPY_UBYTE_, - NPY_SHORT_, NPY_USHORT_, - NPY_INT_, NPY_UINT_, - NPY_LONG_, NPY_ULONG_, - NPY_LONGLONG_, NPY_ULONGLONG_, - NPY_FLOAT_, NPY_DOUBLE_, NPY_LONGDOUBLE_, - NPY_CFLOAT_, NPY_CDOUBLE_, NPY_CLONGDOUBLE_, - NPY_OBJECT_ = 17, - NPY_STRING_, NPY_UNICODE_, NPY_VOID_ - }; - - typedef struct { - Py_intptr_t *ptr; - int len; - } PyArray_Dims; - - static npy_api& get() { - static npy_api api = lookup(); - return api; - } - - bool PyArray_Check_(PyObject *obj) const { - return (bool) PyObject_TypeCheck(obj, PyArray_Type_); - } - bool PyArrayDescr_Check_(PyObject *obj) const { - return (bool) PyObject_TypeCheck(obj, PyArrayDescr_Type_); - } - - unsigned int (*PyArray_GetNDArrayCFeatureVersion_)(); - PyObject *(*PyArray_DescrFromType_)(int); - PyObject *(*PyArray_NewFromDescr_) - (PyTypeObject *, PyObject *, int, Py_intptr_t *, - Py_intptr_t *, void *, int, PyObject *); - PyObject *(*PyArray_DescrNewFromType_)(int); - int (*PyArray_CopyInto_)(PyObject *, PyObject *); - PyObject *(*PyArray_NewCopy_)(PyObject *, int); - PyTypeObject *PyArray_Type_; - PyTypeObject *PyVoidArrType_Type_; - PyTypeObject *PyArrayDescr_Type_; - PyObject *(*PyArray_DescrFromScalar_)(PyObject *); - PyObject *(*PyArray_FromAny_) (PyObject *, PyObject *, int, int, int, PyObject *); - int (*PyArray_DescrConverter_) (PyObject *, PyObject **); - bool (*PyArray_EquivTypes_) (PyObject *, PyObject *); - int (*PyArray_GetArrayParamsFromObject_)(PyObject *, PyObject *, char, PyObject **, int *, - Py_ssize_t *, PyObject **, PyObject *); - PyObject *(*PyArray_Squeeze_)(PyObject *); - int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); - PyObject* (*PyArray_Resize_)(PyObject*, PyArray_Dims*, int, int); -private: - enum functions { - API_PyArray_GetNDArrayCFeatureVersion = 211, - API_PyArray_Type = 2, - API_PyArrayDescr_Type = 3, - API_PyVoidArrType_Type = 39, - API_PyArray_DescrFromType = 45, - API_PyArray_DescrFromScalar = 57, - API_PyArray_FromAny = 69, - API_PyArray_Resize = 80, - API_PyArray_CopyInto = 82, - API_PyArray_NewCopy = 85, - API_PyArray_NewFromDescr = 94, - API_PyArray_DescrNewFromType = 9, - API_PyArray_DescrConverter = 174, - API_PyArray_EquivTypes = 182, - API_PyArray_GetArrayParamsFromObject = 278, - API_PyArray_Squeeze = 136, - API_PyArray_SetBaseObject = 282 - }; - - static npy_api lookup() { - module m = module::import("numpy.core.multiarray"); - auto c = m.attr("_ARRAY_API"); -#if PY_MAJOR_VERSION >= 3 - void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), NULL); -#else - void **api_ptr = (void **) PyCObject_AsVoidPtr(c.ptr()); -#endif - npy_api api; -#define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func]; - DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion); - if (api.PyArray_GetNDArrayCFeatureVersion_() < 0x7) - pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0"); - DECL_NPY_API(PyArray_Type); - DECL_NPY_API(PyVoidArrType_Type); - DECL_NPY_API(PyArrayDescr_Type); - DECL_NPY_API(PyArray_DescrFromType); - DECL_NPY_API(PyArray_DescrFromScalar); - DECL_NPY_API(PyArray_FromAny); - DECL_NPY_API(PyArray_Resize); - DECL_NPY_API(PyArray_CopyInto); - DECL_NPY_API(PyArray_NewCopy); - DECL_NPY_API(PyArray_NewFromDescr); - DECL_NPY_API(PyArray_DescrNewFromType); - DECL_NPY_API(PyArray_DescrConverter); - DECL_NPY_API(PyArray_EquivTypes); - DECL_NPY_API(PyArray_GetArrayParamsFromObject); - DECL_NPY_API(PyArray_Squeeze); - DECL_NPY_API(PyArray_SetBaseObject); -#undef DECL_NPY_API - return api; - } -}; - -inline PyArray_Proxy* array_proxy(void* ptr) { - return reinterpret_cast(ptr); -} - -inline const PyArray_Proxy* array_proxy(const void* ptr) { - return reinterpret_cast(ptr); -} - -inline PyArrayDescr_Proxy* array_descriptor_proxy(PyObject* ptr) { - return reinterpret_cast(ptr); -} - -inline const PyArrayDescr_Proxy* array_descriptor_proxy(const PyObject* ptr) { - return reinterpret_cast(ptr); -} - -inline bool check_flags(const void* ptr, int flag) { - return (flag == (array_proxy(ptr)->flags & flag)); -} - -template struct is_std_array : std::false_type { }; -template struct is_std_array> : std::true_type { }; -template struct is_complex : std::false_type { }; -template struct is_complex> : std::true_type { }; - -template struct array_info_scalar { - typedef T type; - static constexpr bool is_array = false; - static constexpr bool is_empty = false; - static PYBIND11_DESCR extents() { return _(""); } - static void append_extents(list& /* shape */) { } -}; -// Computes underlying type and a comma-separated list of extents for array -// types (any mix of std::array and built-in arrays). An array of char is -// treated as scalar because it gets special handling. -template struct array_info : array_info_scalar { }; -template struct array_info> { - using type = typename array_info::type; - static constexpr bool is_array = true; - static constexpr bool is_empty = (N == 0) || array_info::is_empty; - static constexpr size_t extent = N; - - // appends the extents to shape - static void append_extents(list& shape) { - shape.append(N); - array_info::append_extents(shape); - } - - template::is_array, int> = 0> - static PYBIND11_DESCR extents() { - return _(); - } - - template::is_array, int> = 0> - static PYBIND11_DESCR extents() { - return concat(_(), array_info::extents()); - } -}; -// For numpy we have special handling for arrays of characters, so we don't include -// the size in the array extents. -template struct array_info : array_info_scalar { }; -template struct array_info> : array_info_scalar> { }; -template struct array_info : array_info> { }; -template using remove_all_extents_t = typename array_info::type; - -template using is_pod_struct = all_of< - std::is_standard_layout, // since we're accessing directly in memory we need a standard layout type -#if !defined(__GNUG__) || defined(__clang__) || __GNUC__ >= 5 - std::is_trivially_copyable, -#else - // GCC 4 doesn't implement is_trivially_copyable, so approximate it - std::is_trivially_destructible, - satisfies_any_of, -#endif - satisfies_none_of ->; - -template ssize_t byte_offset_unsafe(const Strides &) { return 0; } -template -ssize_t byte_offset_unsafe(const Strides &strides, ssize_t i, Ix... index) { - return i * strides[Dim] + byte_offset_unsafe(strides, index...); -} - -/** - * Proxy class providing unsafe, unchecked const access to array data. This is constructed through - * the `unchecked()` method of `array` or the `unchecked()` method of `array_t`. `Dims` - * will be -1 for dimensions determined at runtime. - */ -template -class unchecked_reference { -protected: - static constexpr bool Dynamic = Dims < 0; - const unsigned char *data_; - // Storing the shape & strides in local variables (i.e. these arrays) allows the compiler to - // make large performance gains on big, nested loops, but requires compile-time dimensions - conditional_t> - shape_, strides_; - const ssize_t dims_; - - friend class pybind11::array; - // Constructor for compile-time dimensions: - template - unchecked_reference(const void *data, const ssize_t *shape, const ssize_t *strides, enable_if_t) - : data_{reinterpret_cast(data)}, dims_{Dims} { - for (size_t i = 0; i < (size_t) dims_; i++) { - shape_[i] = shape[i]; - strides_[i] = strides[i]; - } - } - // Constructor for runtime dimensions: - template - unchecked_reference(const void *data, const ssize_t *shape, const ssize_t *strides, enable_if_t dims) - : data_{reinterpret_cast(data)}, shape_{shape}, strides_{strides}, dims_{dims} {} - -public: - /** - * Unchecked const reference access to data at the given indices. For a compile-time known - * number of dimensions, this requires the correct number of arguments; for run-time - * dimensionality, this is not checked (and so is up to the caller to use safely). - */ - template const T &operator()(Ix... index) const { - static_assert(sizeof...(Ix) == Dims || Dynamic, - "Invalid number of indices for unchecked array reference"); - return *reinterpret_cast(data_ + byte_offset_unsafe(strides_, ssize_t(index)...)); - } - /** - * Unchecked const reference access to data; this operator only participates if the reference - * is to a 1-dimensional array. When present, this is exactly equivalent to `obj(index)`. - */ - template > - const T &operator[](ssize_t index) const { return operator()(index); } - - /// Pointer access to the data at the given indices. - template const T *data(Ix... ix) const { return &operator()(ssize_t(ix)...); } - - /// Returns the item size, i.e. sizeof(T) - constexpr static ssize_t itemsize() { return sizeof(T); } - - /// Returns the shape (i.e. size) of dimension `dim` - ssize_t shape(ssize_t dim) const { return shape_[(size_t) dim]; } - - /// Returns the number of dimensions of the array - ssize_t ndim() const { return dims_; } - - /// Returns the total number of elements in the referenced array, i.e. the product of the shapes - template - enable_if_t size() const { - return std::accumulate(shape_.begin(), shape_.end(), (ssize_t) 1, std::multiplies()); - } - template - enable_if_t size() const { - return std::accumulate(shape_, shape_ + ndim(), (ssize_t) 1, std::multiplies()); - } - - /// Returns the total number of bytes used by the referenced data. Note that the actual span in - /// memory may be larger if the referenced array has non-contiguous strides (e.g. for a slice). - ssize_t nbytes() const { - return size() * itemsize(); - } -}; - -template -class unchecked_mutable_reference : public unchecked_reference { - friend class pybind11::array; - using ConstBase = unchecked_reference; - using ConstBase::ConstBase; - using ConstBase::Dynamic; -public: - /// Mutable, unchecked access to data at the given indices. - template T& operator()(Ix... index) { - static_assert(sizeof...(Ix) == Dims || Dynamic, - "Invalid number of indices for unchecked array reference"); - return const_cast(ConstBase::operator()(index...)); - } - /** - * Mutable, unchecked access data at the given index; this operator only participates if the - * reference is to a 1-dimensional array (or has runtime dimensions). When present, this is - * exactly equivalent to `obj(index)`. - */ - template > - T &operator[](ssize_t index) { return operator()(index); } - - /// Mutable pointer access to the data at the given indices. - template T *mutable_data(Ix... ix) { return &operator()(ssize_t(ix)...); } -}; - -template -struct type_caster> { - static_assert(Dim == 0 && Dim > 0 /* always fail */, "unchecked array proxy object is not castable"); -}; -template -struct type_caster> : type_caster> {}; - -NAMESPACE_END(detail) - -class dtype : public object { -public: - PYBIND11_OBJECT_DEFAULT(dtype, object, detail::npy_api::get().PyArrayDescr_Check_); - - explicit dtype(const buffer_info &info) { - dtype descr(_dtype_from_pep3118()(PYBIND11_STR_TYPE(info.format))); - // If info.itemsize == 0, use the value calculated from the format string - m_ptr = descr.strip_padding(info.itemsize ? info.itemsize : descr.itemsize()).release().ptr(); - } - - explicit dtype(const std::string &format) { - m_ptr = from_args(pybind11::str(format)).release().ptr(); - } - - dtype(const char *format) : dtype(std::string(format)) { } - - dtype(list names, list formats, list offsets, ssize_t itemsize) { - dict args; - args["names"] = names; - args["formats"] = formats; - args["offsets"] = offsets; - args["itemsize"] = pybind11::int_(itemsize); - m_ptr = from_args(args).release().ptr(); - } - - /// This is essentially the same as calling numpy.dtype(args) in Python. - static dtype from_args(object args) { - PyObject *ptr = nullptr; - if (!detail::npy_api::get().PyArray_DescrConverter_(args.release().ptr(), &ptr) || !ptr) - throw error_already_set(); - return reinterpret_steal(ptr); - } - - /// Return dtype associated with a C++ type. - template static dtype of() { - return detail::npy_format_descriptor::type>::dtype(); - } - - /// Size of the data type in bytes. - ssize_t itemsize() const { - return detail::array_descriptor_proxy(m_ptr)->elsize; - } - - /// Returns true for structured data types. - bool has_fields() const { - return detail::array_descriptor_proxy(m_ptr)->names != nullptr; - } - - /// Single-character type code. - char kind() const { - return detail::array_descriptor_proxy(m_ptr)->kind; - } - -private: - static object _dtype_from_pep3118() { - static PyObject *obj = module::import("numpy.core._internal") - .attr("_dtype_from_pep3118").cast().release().ptr(); - return reinterpret_borrow(obj); - } - - dtype strip_padding(ssize_t itemsize) { - // Recursively strip all void fields with empty names that are generated for - // padding fields (as of NumPy v1.11). - if (!has_fields()) - return *this; - - struct field_descr { PYBIND11_STR_TYPE name; object format; pybind11::int_ offset; }; - std::vector field_descriptors; - - for (auto field : attr("fields").attr("items")()) { - auto spec = field.cast(); - auto name = spec[0].cast(); - auto format = spec[1].cast()[0].cast(); - auto offset = spec[1].cast()[1].cast(); - if (!len(name) && format.kind() == 'V') - continue; - field_descriptors.push_back({(PYBIND11_STR_TYPE) name, format.strip_padding(format.itemsize()), offset}); - } - - std::sort(field_descriptors.begin(), field_descriptors.end(), - [](const field_descr& a, const field_descr& b) { - return a.offset.cast() < b.offset.cast(); - }); - - list names, formats, offsets; - for (auto& descr : field_descriptors) { - names.append(descr.name); - formats.append(descr.format); - offsets.append(descr.offset); - } - return dtype(names, formats, offsets, itemsize); - } -}; - -class array : public buffer { -public: - PYBIND11_OBJECT_CVT(array, buffer, detail::npy_api::get().PyArray_Check_, raw_array) - - enum { - c_style = detail::npy_api::NPY_ARRAY_C_CONTIGUOUS_, - f_style = detail::npy_api::NPY_ARRAY_F_CONTIGUOUS_, - forcecast = detail::npy_api::NPY_ARRAY_FORCECAST_ - }; - - array() : array({{0}}, static_cast(nullptr)) {} - - using ShapeContainer = detail::any_container; - using StridesContainer = detail::any_container; - - // Constructs an array taking shape/strides from arbitrary container types - array(const pybind11::dtype &dt, ShapeContainer shape, StridesContainer strides, - const void *ptr = nullptr, handle base = handle()) { - - if (strides->empty()) - *strides = c_strides(*shape, dt.itemsize()); - - auto ndim = shape->size(); - if (ndim != strides->size()) - pybind11_fail("NumPy: shape ndim doesn't match strides ndim"); - auto descr = dt; - - int flags = 0; - if (base && ptr) { - if (isinstance(base)) - /* Copy flags from base (except ownership bit) */ - flags = reinterpret_borrow(base).flags() & ~detail::npy_api::NPY_ARRAY_OWNDATA_; - else - /* Writable by default, easy to downgrade later on if needed */ - flags = detail::npy_api::NPY_ARRAY_WRITEABLE_; - } - - auto &api = detail::npy_api::get(); - auto tmp = reinterpret_steal(api.PyArray_NewFromDescr_( - api.PyArray_Type_, descr.release().ptr(), (int) ndim, shape->data(), strides->data(), - const_cast(ptr), flags, nullptr)); - if (!tmp) - throw error_already_set(); - if (ptr) { - if (base) { - api.PyArray_SetBaseObject_(tmp.ptr(), base.inc_ref().ptr()); - } else { - tmp = reinterpret_steal(api.PyArray_NewCopy_(tmp.ptr(), -1 /* any order */)); - } - } - m_ptr = tmp.release().ptr(); - } - - array(const pybind11::dtype &dt, ShapeContainer shape, const void *ptr = nullptr, handle base = handle()) - : array(dt, std::move(shape), {}, ptr, base) { } - - template ::value && !std::is_same::value>> - array(const pybind11::dtype &dt, T count, const void *ptr = nullptr, handle base = handle()) - : array(dt, {{count}}, ptr, base) { } - - template - array(ShapeContainer shape, StridesContainer strides, const T *ptr, handle base = handle()) - : array(pybind11::dtype::of(), std::move(shape), std::move(strides), ptr, base) { } - - template - array(ShapeContainer shape, const T *ptr, handle base = handle()) - : array(std::move(shape), {}, ptr, base) { } - - template - explicit array(ssize_t count, const T *ptr, handle base = handle()) : array({count}, {}, ptr, base) { } - - explicit array(const buffer_info &info) - : array(pybind11::dtype(info), info.shape, info.strides, info.ptr) { } - - /// Array descriptor (dtype) - pybind11::dtype dtype() const { - return reinterpret_borrow(detail::array_proxy(m_ptr)->descr); - } - - /// Total number of elements - ssize_t size() const { - return std::accumulate(shape(), shape() + ndim(), (ssize_t) 1, std::multiplies()); - } - - /// Byte size of a single element - ssize_t itemsize() const { - return detail::array_descriptor_proxy(detail::array_proxy(m_ptr)->descr)->elsize; - } - - /// Total number of bytes - ssize_t nbytes() const { - return size() * itemsize(); - } - - /// Number of dimensions - ssize_t ndim() const { - return detail::array_proxy(m_ptr)->nd; - } - - /// Base object - object base() const { - return reinterpret_borrow(detail::array_proxy(m_ptr)->base); - } - - /// Dimensions of the array - const ssize_t* shape() const { - return detail::array_proxy(m_ptr)->dimensions; - } - - /// Dimension along a given axis - ssize_t shape(ssize_t dim) const { - if (dim >= ndim()) - fail_dim_check(dim, "invalid axis"); - return shape()[dim]; - } - - /// Strides of the array - const ssize_t* strides() const { - return detail::array_proxy(m_ptr)->strides; - } - - /// Stride along a given axis - ssize_t strides(ssize_t dim) const { - if (dim >= ndim()) - fail_dim_check(dim, "invalid axis"); - return strides()[dim]; - } - - /// Return the NumPy array flags - int flags() const { - return detail::array_proxy(m_ptr)->flags; - } - - /// If set, the array is writeable (otherwise the buffer is read-only) - bool writeable() const { - return detail::check_flags(m_ptr, detail::npy_api::NPY_ARRAY_WRITEABLE_); - } - - /// If set, the array owns the data (will be freed when the array is deleted) - bool owndata() const { - return detail::check_flags(m_ptr, detail::npy_api::NPY_ARRAY_OWNDATA_); - } - - /// Pointer to the contained data. If index is not provided, points to the - /// beginning of the buffer. May throw if the index would lead to out of bounds access. - template const void* data(Ix... index) const { - return static_cast(detail::array_proxy(m_ptr)->data + offset_at(index...)); - } - - /// Mutable pointer to the contained data. If index is not provided, points to the - /// beginning of the buffer. May throw if the index would lead to out of bounds access. - /// May throw if the array is not writeable. - template void* mutable_data(Ix... index) { - check_writeable(); - return static_cast(detail::array_proxy(m_ptr)->data + offset_at(index...)); - } - - /// Byte offset from beginning of the array to a given index (full or partial). - /// May throw if the index would lead to out of bounds access. - template ssize_t offset_at(Ix... index) const { - if ((ssize_t) sizeof...(index) > ndim()) - fail_dim_check(sizeof...(index), "too many indices for an array"); - return byte_offset(ssize_t(index)...); - } - - ssize_t offset_at() const { return 0; } - - /// Item count from beginning of the array to a given index (full or partial). - /// May throw if the index would lead to out of bounds access. - template ssize_t index_at(Ix... index) const { - return offset_at(index...) / itemsize(); - } - - /** - * Returns a proxy object that provides access to the array's data without bounds or - * dimensionality checking. Will throw if the array is missing the `writeable` flag. Use with - * care: the array must not be destroyed or reshaped for the duration of the returned object, - * and the caller must take care not to access invalid dimensions or dimension indices. - */ - template detail::unchecked_mutable_reference mutable_unchecked() { - if (Dims >= 0 && ndim() != Dims) - throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + - "; expected " + std::to_string(Dims)); - return detail::unchecked_mutable_reference(mutable_data(), shape(), strides(), ndim()); - } - - /** - * Returns a proxy object that provides const access to the array's data without bounds or - * dimensionality checking. Unlike `mutable_unchecked()`, this does not require that the - * underlying array have the `writable` flag. Use with care: the array must not be destroyed or - * reshaped for the duration of the returned object, and the caller must take care not to access - * invalid dimensions or dimension indices. - */ - template detail::unchecked_reference unchecked() const { - if (Dims >= 0 && ndim() != Dims) - throw std::domain_error("array has incorrect number of dimensions: " + std::to_string(ndim()) + - "; expected " + std::to_string(Dims)); - return detail::unchecked_reference(data(), shape(), strides(), ndim()); - } - - /// Return a new view with all of the dimensions of length 1 removed - array squeeze() { - auto& api = detail::npy_api::get(); - return reinterpret_steal(api.PyArray_Squeeze_(m_ptr)); - } - - /// Resize array to given shape - /// If refcheck is true and more that one reference exist to this array - /// then resize will succeed only if it makes a reshape, i.e. original size doesn't change - void resize(ShapeContainer new_shape, bool refcheck = true) { - detail::npy_api::PyArray_Dims d = { - new_shape->data(), int(new_shape->size()) - }; - // try to resize, set ordering param to -1 cause it's not used anyway - object new_array = reinterpret_steal( - detail::npy_api::get().PyArray_Resize_(m_ptr, &d, int(refcheck), -1) - ); - if (!new_array) throw error_already_set(); - if (isinstance(new_array)) { *this = std::move(new_array); } - } - - /// Ensure that the argument is a NumPy array - /// In case of an error, nullptr is returned and the Python error is cleared. - static array ensure(handle h, int ExtraFlags = 0) { - auto result = reinterpret_steal(raw_array(h.ptr(), ExtraFlags)); - if (!result) - PyErr_Clear(); - return result; - } - -protected: - template friend struct detail::npy_format_descriptor; - - void fail_dim_check(ssize_t dim, const std::string& msg) const { - throw index_error(msg + ": " + std::to_string(dim) + - " (ndim = " + std::to_string(ndim()) + ")"); - } - - template ssize_t byte_offset(Ix... index) const { - check_dimensions(index...); - return detail::byte_offset_unsafe(strides(), ssize_t(index)...); - } - - void check_writeable() const { - if (!writeable()) - throw std::domain_error("array is not writeable"); - } - - // Default, C-style strides - static std::vector c_strides(const std::vector &shape, ssize_t itemsize) { - auto ndim = shape.size(); - std::vector strides(ndim, itemsize); - for (size_t i = ndim - 1; i > 0; --i) - strides[i - 1] = strides[i] * shape[i]; - return strides; - } - - // F-style strides; default when constructing an array_t with `ExtraFlags & f_style` - static std::vector f_strides(const std::vector &shape, ssize_t itemsize) { - auto ndim = shape.size(); - std::vector strides(ndim, itemsize); - for (size_t i = 1; i < ndim; ++i) - strides[i] = strides[i - 1] * shape[i - 1]; - return strides; - } - - template void check_dimensions(Ix... index) const { - check_dimensions_impl(ssize_t(0), shape(), ssize_t(index)...); - } - - void check_dimensions_impl(ssize_t, const ssize_t*) const { } - - template void check_dimensions_impl(ssize_t axis, const ssize_t* shape, ssize_t i, Ix... index) const { - if (i >= *shape) { - throw index_error(std::string("index ") + std::to_string(i) + - " is out of bounds for axis " + std::to_string(axis) + - " with size " + std::to_string(*shape)); - } - check_dimensions_impl(axis + 1, shape + 1, index...); - } - - /// Create array from any object -- always returns a new reference - static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) { - if (ptr == nullptr) { - PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr"); - return nullptr; - } - return detail::npy_api::get().PyArray_FromAny_( - ptr, nullptr, 0, 0, detail::npy_api::NPY_ARRAY_ENSUREARRAY_ | ExtraFlags, nullptr); - } -}; - -template class array_t : public array { -private: - struct private_ctor {}; - // Delegating constructor needed when both moving and accessing in the same constructor - array_t(private_ctor, ShapeContainer &&shape, StridesContainer &&strides, const T *ptr, handle base) - : array(std::move(shape), std::move(strides), ptr, base) {} -public: - static_assert(!detail::array_info::is_array, "Array types cannot be used with array_t"); - - using value_type = T; - - array_t() : array(0, static_cast(nullptr)) {} - array_t(handle h, borrowed_t) : array(h, borrowed_t{}) { } - array_t(handle h, stolen_t) : array(h, stolen_t{}) { } - - PYBIND11_DEPRECATED("Use array_t::ensure() instead") - array_t(handle h, bool is_borrowed) : array(raw_array_t(h.ptr()), stolen_t{}) { - if (!m_ptr) PyErr_Clear(); - if (!is_borrowed) Py_XDECREF(h.ptr()); - } - - array_t(const object &o) : array(raw_array_t(o.ptr()), stolen_t{}) { - if (!m_ptr) throw error_already_set(); - } - - explicit array_t(const buffer_info& info) : array(info) { } - - array_t(ShapeContainer shape, StridesContainer strides, const T *ptr = nullptr, handle base = handle()) - : array(std::move(shape), std::move(strides), ptr, base) { } - - explicit array_t(ShapeContainer shape, const T *ptr = nullptr, handle base = handle()) - : array_t(private_ctor{}, std::move(shape), - ExtraFlags & f_style ? f_strides(*shape, itemsize()) : c_strides(*shape, itemsize()), - ptr, base) { } - - explicit array_t(size_t count, const T *ptr = nullptr, handle base = handle()) - : array({count}, {}, ptr, base) { } - - constexpr ssize_t itemsize() const { - return sizeof(T); - } - - template ssize_t index_at(Ix... index) const { - return offset_at(index...) / itemsize(); - } - - template const T* data(Ix... index) const { - return static_cast(array::data(index...)); - } - - template T* mutable_data(Ix... index) { - return static_cast(array::mutable_data(index...)); - } - - // Reference to element at a given index - template const T& at(Ix... index) const { - if (sizeof...(index) != ndim()) - fail_dim_check(sizeof...(index), "index dimension mismatch"); - return *(static_cast(array::data()) + byte_offset(ssize_t(index)...) / itemsize()); - } - - // Mutable reference to element at a given index - template T& mutable_at(Ix... index) { - if (sizeof...(index) != ndim()) - fail_dim_check(sizeof...(index), "index dimension mismatch"); - return *(static_cast(array::mutable_data()) + byte_offset(ssize_t(index)...) / itemsize()); - } - - /** - * Returns a proxy object that provides access to the array's data without bounds or - * dimensionality checking. Will throw if the array is missing the `writeable` flag. Use with - * care: the array must not be destroyed or reshaped for the duration of the returned object, - * and the caller must take care not to access invalid dimensions or dimension indices. - */ - template detail::unchecked_mutable_reference mutable_unchecked() { - return array::mutable_unchecked(); - } - - /** - * Returns a proxy object that provides const access to the array's data without bounds or - * dimensionality checking. Unlike `unchecked()`, this does not require that the underlying - * array have the `writable` flag. Use with care: the array must not be destroyed or reshaped - * for the duration of the returned object, and the caller must take care not to access invalid - * dimensions or dimension indices. - */ - template detail::unchecked_reference unchecked() const { - return array::unchecked(); - } - - /// Ensure that the argument is a NumPy array of the correct dtype (and if not, try to convert - /// it). In case of an error, nullptr is returned and the Python error is cleared. - static array_t ensure(handle h) { - auto result = reinterpret_steal(raw_array_t(h.ptr())); - if (!result) - PyErr_Clear(); - return result; - } - - static bool check_(handle h) { - const auto &api = detail::npy_api::get(); - return api.PyArray_Check_(h.ptr()) - && api.PyArray_EquivTypes_(detail::array_proxy(h.ptr())->descr, dtype::of().ptr()); - } - -protected: - /// Create array from any object -- always returns a new reference - static PyObject *raw_array_t(PyObject *ptr) { - if (ptr == nullptr) { - PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr"); - return nullptr; - } - return detail::npy_api::get().PyArray_FromAny_( - ptr, dtype::of().release().ptr(), 0, 0, - detail::npy_api::NPY_ARRAY_ENSUREARRAY_ | ExtraFlags, nullptr); - } -}; - -template -struct format_descriptor::value>> { - static std::string format() { - return detail::npy_format_descriptor::type>::format(); - } -}; - -template struct format_descriptor { - static std::string format() { return std::to_string(N) + "s"; } -}; -template struct format_descriptor> { - static std::string format() { return std::to_string(N) + "s"; } -}; - -template -struct format_descriptor::value>> { - static std::string format() { - return format_descriptor< - typename std::remove_cv::type>::type>::format(); - } -}; - -template -struct format_descriptor::is_array>> { - static std::string format() { - using detail::_; - PYBIND11_DESCR extents = _("(") + detail::array_info::extents() + _(")"); - return extents.text() + format_descriptor>::format(); - } -}; - -NAMESPACE_BEGIN(detail) -template -struct pyobject_caster> { - using type = array_t; - - bool load(handle src, bool convert) { - if (!convert && !type::check_(src)) - return false; - value = type::ensure(src); - return static_cast(value); - } - - static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) { - return src.inc_ref(); - } - PYBIND11_TYPE_CASTER(type, handle_type_name::name()); -}; - -template -struct compare_buffer_info::value>> { - static bool compare(const buffer_info& b) { - return npy_api::get().PyArray_EquivTypes_(dtype::of().ptr(), dtype(b).ptr()); - } -}; - -template struct npy_format_descriptor::value>> { -private: - // NB: the order here must match the one in common.h - constexpr static const int values[15] = { - npy_api::NPY_BOOL_, - npy_api::NPY_BYTE_, npy_api::NPY_UBYTE_, npy_api::NPY_SHORT_, npy_api::NPY_USHORT_, - npy_api::NPY_INT_, npy_api::NPY_UINT_, npy_api::NPY_LONGLONG_, npy_api::NPY_ULONGLONG_, - npy_api::NPY_FLOAT_, npy_api::NPY_DOUBLE_, npy_api::NPY_LONGDOUBLE_, - npy_api::NPY_CFLOAT_, npy_api::NPY_CDOUBLE_, npy_api::NPY_CLONGDOUBLE_ - }; - -public: - static constexpr int value = values[detail::is_fmt_numeric::index]; - - static pybind11::dtype dtype() { - if (auto ptr = npy_api::get().PyArray_DescrFromType_(value)) - return reinterpret_borrow(ptr); - pybind11_fail("Unsupported buffer format!"); - } - template ::value, int> = 0> - static PYBIND11_DESCR name() { - return _::value>(_("bool"), - _::value>("int", "uint") + _()); - } - template ::value, int> = 0> - static PYBIND11_DESCR name() { - return _::value || std::is_same::value>( - _("float") + _(), _("longdouble")); - } - template ::value, int> = 0> - static PYBIND11_DESCR name() { - return _::value || std::is_same::value>( - _("complex") + _(), _("longcomplex")); - } -}; - -#define PYBIND11_DECL_CHAR_FMT \ - static PYBIND11_DESCR name() { return _("S") + _(); } \ - static pybind11::dtype dtype() { return pybind11::dtype(std::string("S") + std::to_string(N)); } -template struct npy_format_descriptor { PYBIND11_DECL_CHAR_FMT }; -template struct npy_format_descriptor> { PYBIND11_DECL_CHAR_FMT }; -#undef PYBIND11_DECL_CHAR_FMT - -template struct npy_format_descriptor::is_array>> { -private: - using base_descr = npy_format_descriptor::type>; -public: - static_assert(!array_info::is_empty, "Zero-sized arrays are not supported"); - - static PYBIND11_DESCR name() { return _("(") + array_info::extents() + _(")") + base_descr::name(); } - static pybind11::dtype dtype() { - list shape; - array_info::append_extents(shape); - return pybind11::dtype::from_args(pybind11::make_tuple(base_descr::dtype(), shape)); - } -}; - -template struct npy_format_descriptor::value>> { -private: - using base_descr = npy_format_descriptor::type>; -public: - static PYBIND11_DESCR name() { return base_descr::name(); } - static pybind11::dtype dtype() { return base_descr::dtype(); } -}; - -struct field_descriptor { - const char *name; - ssize_t offset; - ssize_t size; - std::string format; - dtype descr; -}; - -inline PYBIND11_NOINLINE void register_structured_dtype( - const std::initializer_list& fields, - const std::type_info& tinfo, ssize_t itemsize, - bool (*direct_converter)(PyObject *, void *&)) { - - auto& numpy_internals = get_numpy_internals(); - if (numpy_internals.get_type_info(tinfo, false)) - pybind11_fail("NumPy: dtype is already registered"); - - list names, formats, offsets; - for (auto field : fields) { - if (!field.descr) - pybind11_fail(std::string("NumPy: unsupported field dtype: `") + - field.name + "` @ " + tinfo.name()); - names.append(PYBIND11_STR_TYPE(field.name)); - formats.append(field.descr); - offsets.append(pybind11::int_(field.offset)); - } - auto dtype_ptr = pybind11::dtype(names, formats, offsets, itemsize).release().ptr(); - - // There is an existing bug in NumPy (as of v1.11): trailing bytes are - // not encoded explicitly into the format string. This will supposedly - // get fixed in v1.12; for further details, see these: - // - https://github.com/numpy/numpy/issues/7797 - // - https://github.com/numpy/numpy/pull/7798 - // Because of this, we won't use numpy's logic to generate buffer format - // strings and will just do it ourselves. - std::vector ordered_fields(fields); - std::sort(ordered_fields.begin(), ordered_fields.end(), - [](const field_descriptor &a, const field_descriptor &b) { return a.offset < b.offset; }); - ssize_t offset = 0; - std::ostringstream oss; - // mark the structure as unaligned with '^', because numpy and C++ don't - // always agree about alignment (particularly for complex), and we're - // explicitly listing all our padding. This depends on none of the fields - // overriding the endianness. Putting the ^ in front of individual fields - // isn't guaranteed to work due to https://github.com/numpy/numpy/issues/9049 - oss << "^T{"; - for (auto& field : ordered_fields) { - if (field.offset > offset) - oss << (field.offset - offset) << 'x'; - oss << field.format << ':' << field.name << ':'; - offset = field.offset + field.size; - } - if (itemsize > offset) - oss << (itemsize - offset) << 'x'; - oss << '}'; - auto format_str = oss.str(); - - // Sanity check: verify that NumPy properly parses our buffer format string - auto& api = npy_api::get(); - auto arr = array(buffer_info(nullptr, itemsize, format_str, 1)); - if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) - pybind11_fail("NumPy: invalid buffer descriptor!"); - - auto tindex = std::type_index(tinfo); - numpy_internals.registered_dtypes[tindex] = { dtype_ptr, format_str }; - get_internals().direct_conversions[tindex].push_back(direct_converter); -} - -template struct npy_format_descriptor { - static_assert(is_pod_struct::value, "Attempt to use a non-POD or unimplemented POD type as a numpy dtype"); - - static PYBIND11_DESCR name() { return make_caster::name(); } - - static pybind11::dtype dtype() { - return reinterpret_borrow(dtype_ptr()); - } - - static std::string format() { - static auto format_str = get_numpy_internals().get_type_info(true)->format_str; - return format_str; - } - - static void register_dtype(const std::initializer_list& fields) { - register_structured_dtype(fields, typeid(typename std::remove_cv::type), - sizeof(T), &direct_converter); - } - -private: - static PyObject* dtype_ptr() { - static PyObject* ptr = get_numpy_internals().get_type_info(true)->dtype_ptr; - return ptr; - } - - static bool direct_converter(PyObject *obj, void*& value) { - auto& api = npy_api::get(); - if (!PyObject_TypeCheck(obj, api.PyVoidArrType_Type_)) - return false; - if (auto descr = reinterpret_steal(api.PyArray_DescrFromScalar_(obj))) { - if (api.PyArray_EquivTypes_(dtype_ptr(), descr.ptr())) { - value = ((PyVoidScalarObject_Proxy *) obj)->obval; - return true; - } - } - return false; - } -}; - -#ifdef __CLION_IDE__ // replace heavy macro with dummy code for the IDE (doesn't affect code) -# define PYBIND11_NUMPY_DTYPE(Type, ...) ((void)0) -# define PYBIND11_NUMPY_DTYPE_EX(Type, ...) ((void)0) -#else - -#define PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, Name) \ - ::pybind11::detail::field_descriptor { \ - Name, offsetof(T, Field), sizeof(decltype(std::declval().Field)), \ - ::pybind11::format_descriptor().Field)>::format(), \ - ::pybind11::detail::npy_format_descriptor().Field)>::dtype() \ - } - -// Extract name, offset and format descriptor for a struct field -#define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, #Field) - -// The main idea of this macro is borrowed from https://github.com/swansontec/map-macro -// (C) William Swanson, Paul Fultz -#define PYBIND11_EVAL0(...) __VA_ARGS__ -#define PYBIND11_EVAL1(...) PYBIND11_EVAL0 (PYBIND11_EVAL0 (PYBIND11_EVAL0 (__VA_ARGS__))) -#define PYBIND11_EVAL2(...) PYBIND11_EVAL1 (PYBIND11_EVAL1 (PYBIND11_EVAL1 (__VA_ARGS__))) -#define PYBIND11_EVAL3(...) PYBIND11_EVAL2 (PYBIND11_EVAL2 (PYBIND11_EVAL2 (__VA_ARGS__))) -#define PYBIND11_EVAL4(...) PYBIND11_EVAL3 (PYBIND11_EVAL3 (PYBIND11_EVAL3 (__VA_ARGS__))) -#define PYBIND11_EVAL(...) PYBIND11_EVAL4 (PYBIND11_EVAL4 (PYBIND11_EVAL4 (__VA_ARGS__))) -#define PYBIND11_MAP_END(...) -#define PYBIND11_MAP_OUT -#define PYBIND11_MAP_COMMA , -#define PYBIND11_MAP_GET_END() 0, PYBIND11_MAP_END -#define PYBIND11_MAP_NEXT0(test, next, ...) next PYBIND11_MAP_OUT -#define PYBIND11_MAP_NEXT1(test, next) PYBIND11_MAP_NEXT0 (test, next, 0) -#define PYBIND11_MAP_NEXT(test, next) PYBIND11_MAP_NEXT1 (PYBIND11_MAP_GET_END test, next) -#ifdef _MSC_VER // MSVC is not as eager to expand macros, hence this workaround -#define PYBIND11_MAP_LIST_NEXT1(test, next) \ - PYBIND11_EVAL0 (PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0)) -#else -#define PYBIND11_MAP_LIST_NEXT1(test, next) \ - PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0) -#endif -#define PYBIND11_MAP_LIST_NEXT(test, next) \ - PYBIND11_MAP_LIST_NEXT1 (PYBIND11_MAP_GET_END test, next) -#define PYBIND11_MAP_LIST0(f, t, x, peek, ...) \ - f(t, x) PYBIND11_MAP_LIST_NEXT (peek, PYBIND11_MAP_LIST1) (f, t, peek, __VA_ARGS__) -#define PYBIND11_MAP_LIST1(f, t, x, peek, ...) \ - f(t, x) PYBIND11_MAP_LIST_NEXT (peek, PYBIND11_MAP_LIST0) (f, t, peek, __VA_ARGS__) -// PYBIND11_MAP_LIST(f, t, a1, a2, ...) expands to f(t, a1), f(t, a2), ... -#define PYBIND11_MAP_LIST(f, t, ...) \ - PYBIND11_EVAL (PYBIND11_MAP_LIST1 (f, t, __VA_ARGS__, (), 0)) - -#define PYBIND11_NUMPY_DTYPE(Type, ...) \ - ::pybind11::detail::npy_format_descriptor::register_dtype \ - ({PYBIND11_MAP_LIST (PYBIND11_FIELD_DESCRIPTOR, Type, __VA_ARGS__)}) - -#ifdef _MSC_VER -#define PYBIND11_MAP2_LIST_NEXT1(test, next) \ - PYBIND11_EVAL0 (PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0)) -#else -#define PYBIND11_MAP2_LIST_NEXT1(test, next) \ - PYBIND11_MAP_NEXT0 (test, PYBIND11_MAP_COMMA next, 0) -#endif -#define PYBIND11_MAP2_LIST_NEXT(test, next) \ - PYBIND11_MAP2_LIST_NEXT1 (PYBIND11_MAP_GET_END test, next) -#define PYBIND11_MAP2_LIST0(f, t, x1, x2, peek, ...) \ - f(t, x1, x2) PYBIND11_MAP2_LIST_NEXT (peek, PYBIND11_MAP2_LIST1) (f, t, peek, __VA_ARGS__) -#define PYBIND11_MAP2_LIST1(f, t, x1, x2, peek, ...) \ - f(t, x1, x2) PYBIND11_MAP2_LIST_NEXT (peek, PYBIND11_MAP2_LIST0) (f, t, peek, __VA_ARGS__) -// PYBIND11_MAP2_LIST(f, t, a1, a2, ...) expands to f(t, a1, a2), f(t, a3, a4), ... -#define PYBIND11_MAP2_LIST(f, t, ...) \ - PYBIND11_EVAL (PYBIND11_MAP2_LIST1 (f, t, __VA_ARGS__, (), 0)) - -#define PYBIND11_NUMPY_DTYPE_EX(Type, ...) \ - ::pybind11::detail::npy_format_descriptor::register_dtype \ - ({PYBIND11_MAP2_LIST (PYBIND11_FIELD_DESCRIPTOR_EX, Type, __VA_ARGS__)}) - -#endif // __CLION_IDE__ - -template -using array_iterator = typename std::add_pointer::type; - -template -array_iterator array_begin(const buffer_info& buffer) { - return array_iterator(reinterpret_cast(buffer.ptr)); -} - -template -array_iterator array_end(const buffer_info& buffer) { - return array_iterator(reinterpret_cast(buffer.ptr) + buffer.size); -} - -class common_iterator { -public: - using container_type = std::vector; - using value_type = container_type::value_type; - using size_type = container_type::size_type; - - common_iterator() : p_ptr(0), m_strides() {} - - common_iterator(void* ptr, const container_type& strides, const container_type& shape) - : p_ptr(reinterpret_cast(ptr)), m_strides(strides.size()) { - m_strides.back() = static_cast(strides.back()); - for (size_type i = m_strides.size() - 1; i != 0; --i) { - size_type j = i - 1; - value_type s = static_cast(shape[i]); - m_strides[j] = strides[j] + m_strides[i] - strides[i] * s; - } - } - - void increment(size_type dim) { - p_ptr += m_strides[dim]; - } - - void* data() const { - return p_ptr; - } - -private: - char* p_ptr; - container_type m_strides; -}; - -template class multi_array_iterator { -public: - using container_type = std::vector; - - multi_array_iterator(const std::array &buffers, - const container_type &shape) - : m_shape(shape.size()), m_index(shape.size(), 0), - m_common_iterator() { - - // Manual copy to avoid conversion warning if using std::copy - for (size_t i = 0; i < shape.size(); ++i) - m_shape[i] = shape[i]; - - container_type strides(shape.size()); - for (size_t i = 0; i < N; ++i) - init_common_iterator(buffers[i], shape, m_common_iterator[i], strides); - } - - multi_array_iterator& operator++() { - for (size_t j = m_index.size(); j != 0; --j) { - size_t i = j - 1; - if (++m_index[i] != m_shape[i]) { - increment_common_iterator(i); - break; - } else { - m_index[i] = 0; - } - } - return *this; - } - - template T* data() const { - return reinterpret_cast(m_common_iterator[K].data()); - } - -private: - - using common_iter = common_iterator; - - void init_common_iterator(const buffer_info &buffer, - const container_type &shape, - common_iter &iterator, - container_type &strides) { - auto buffer_shape_iter = buffer.shape.rbegin(); - auto buffer_strides_iter = buffer.strides.rbegin(); - auto shape_iter = shape.rbegin(); - auto strides_iter = strides.rbegin(); - - while (buffer_shape_iter != buffer.shape.rend()) { - if (*shape_iter == *buffer_shape_iter) - *strides_iter = *buffer_strides_iter; - else - *strides_iter = 0; - - ++buffer_shape_iter; - ++buffer_strides_iter; - ++shape_iter; - ++strides_iter; - } - - std::fill(strides_iter, strides.rend(), 0); - iterator = common_iter(buffer.ptr, strides, shape); - } - - void increment_common_iterator(size_t dim) { - for (auto &iter : m_common_iterator) - iter.increment(dim); - } - - container_type m_shape; - container_type m_index; - std::array m_common_iterator; -}; - -enum class broadcast_trivial { non_trivial, c_trivial, f_trivial }; - -// Populates the shape and number of dimensions for the set of buffers. Returns a broadcast_trivial -// enum value indicating whether the broadcast is "trivial"--that is, has each buffer being either a -// singleton or a full-size, C-contiguous (`c_trivial`) or Fortran-contiguous (`f_trivial`) storage -// buffer; returns `non_trivial` otherwise. -template -broadcast_trivial broadcast(const std::array &buffers, ssize_t &ndim, std::vector &shape) { - ndim = std::accumulate(buffers.begin(), buffers.end(), ssize_t(0), [](ssize_t res, const buffer_info &buf) { - return std::max(res, buf.ndim); - }); - - shape.clear(); - shape.resize((size_t) ndim, 1); - - // Figure out the output size, and make sure all input arrays conform (i.e. are either size 1 or - // the full size). - for (size_t i = 0; i < N; ++i) { - auto res_iter = shape.rbegin(); - auto end = buffers[i].shape.rend(); - for (auto shape_iter = buffers[i].shape.rbegin(); shape_iter != end; ++shape_iter, ++res_iter) { - const auto &dim_size_in = *shape_iter; - auto &dim_size_out = *res_iter; - - // Each input dimension can either be 1 or `n`, but `n` values must match across buffers - if (dim_size_out == 1) - dim_size_out = dim_size_in; - else if (dim_size_in != 1 && dim_size_in != dim_size_out) - pybind11_fail("pybind11::vectorize: incompatible size/dimension of inputs!"); - } - } - - bool trivial_broadcast_c = true; - bool trivial_broadcast_f = true; - for (size_t i = 0; i < N && (trivial_broadcast_c || trivial_broadcast_f); ++i) { - if (buffers[i].size == 1) - continue; - - // Require the same number of dimensions: - if (buffers[i].ndim != ndim) - return broadcast_trivial::non_trivial; - - // Require all dimensions be full-size: - if (!std::equal(buffers[i].shape.cbegin(), buffers[i].shape.cend(), shape.cbegin())) - return broadcast_trivial::non_trivial; - - // Check for C contiguity (but only if previous inputs were also C contiguous) - if (trivial_broadcast_c) { - ssize_t expect_stride = buffers[i].itemsize; - auto end = buffers[i].shape.crend(); - for (auto shape_iter = buffers[i].shape.crbegin(), stride_iter = buffers[i].strides.crbegin(); - trivial_broadcast_c && shape_iter != end; ++shape_iter, ++stride_iter) { - if (expect_stride == *stride_iter) - expect_stride *= *shape_iter; - else - trivial_broadcast_c = false; - } - } - - // Check for Fortran contiguity (if previous inputs were also F contiguous) - if (trivial_broadcast_f) { - ssize_t expect_stride = buffers[i].itemsize; - auto end = buffers[i].shape.cend(); - for (auto shape_iter = buffers[i].shape.cbegin(), stride_iter = buffers[i].strides.cbegin(); - trivial_broadcast_f && shape_iter != end; ++shape_iter, ++stride_iter) { - if (expect_stride == *stride_iter) - expect_stride *= *shape_iter; - else - trivial_broadcast_f = false; - } - } - } - - return - trivial_broadcast_c ? broadcast_trivial::c_trivial : - trivial_broadcast_f ? broadcast_trivial::f_trivial : - broadcast_trivial::non_trivial; -} - -template -struct vectorize_arg { - static_assert(!std::is_rvalue_reference::value, "Functions with rvalue reference arguments cannot be vectorized"); - // The wrapped function gets called with this type: - using call_type = remove_reference_t; - // Is this a vectorized argument? - static constexpr bool vectorize = - satisfies_any_of::value && - satisfies_none_of::value && - (!std::is_reference::value || - (std::is_lvalue_reference::value && std::is_const::value)); - // Accept this type: an array for vectorized types, otherwise the type as-is: - using type = conditional_t, array::forcecast>, T>; -}; - -template -struct vectorize_helper { -private: - static constexpr size_t N = sizeof...(Args); - static constexpr size_t NVectorized = constexpr_sum(vectorize_arg::vectorize...); - static_assert(NVectorized >= 1, - "pybind11::vectorize(...) requires a function with at least one vectorizable argument"); - -public: - template - explicit vectorize_helper(T &&f) : f(std::forward(f)) { } - - object operator()(typename vectorize_arg::type... args) { - return run(args..., - make_index_sequence(), - select_indices::vectorize...>(), - make_index_sequence()); - } - -private: - remove_reference_t f; - - template using param_n_t = typename pack_element::call_type...>::type; - - // Runs a vectorized function given arguments tuple and three index sequences: - // - Index is the full set of 0 ... (N-1) argument indices; - // - VIndex is the subset of argument indices with vectorized parameters, letting us access - // vectorized arguments (anything not in this sequence is passed through) - // - BIndex is a incremental sequence (beginning at 0) of the same size as VIndex, so that - // we can store vectorized buffer_infos in an array (argument VIndex has its buffer at - // index BIndex in the array). - template object run( - typename vectorize_arg::type &...args, - index_sequence i_seq, index_sequence vi_seq, index_sequence bi_seq) { - - // Pointers to values the function was called with; the vectorized ones set here will start - // out as array_t pointers, but they will be changed them to T pointers before we make - // call the wrapped function. Non-vectorized pointers are left as-is. - std::array params{{ &args... }}; - - // The array of `buffer_info`s of vectorized arguments: - std::array buffers{{ reinterpret_cast(params[VIndex])->request()... }}; - - /* Determine dimensions parameters of output array */ - ssize_t nd = 0; - std::vector shape(0); - auto trivial = broadcast(buffers, nd, shape); - size_t ndim = (size_t) nd; - - size_t size = std::accumulate(shape.begin(), shape.end(), (size_t) 1, std::multiplies()); - - // If all arguments are 0-dimension arrays (i.e. single values) return a plain value (i.e. - // not wrapped in an array). - if (size == 1 && ndim == 0) { - PYBIND11_EXPAND_SIDE_EFFECTS(params[VIndex] = buffers[BIndex].ptr); - return cast(f(*reinterpret_cast *>(params[Index])...)); - } - - array_t result; - if (trivial == broadcast_trivial::f_trivial) result = array_t(shape); - else result = array_t(shape); - - if (size == 0) return result; - - /* Call the function */ - if (trivial == broadcast_trivial::non_trivial) - apply_broadcast(buffers, params, result, i_seq, vi_seq, bi_seq); - else - apply_trivial(buffers, params, result.mutable_data(), size, i_seq, vi_seq, bi_seq); - - return result; - } - - template - void apply_trivial(std::array &buffers, - std::array ¶ms, - Return *out, - size_t size, - index_sequence, index_sequence, index_sequence) { - - // Initialize an array of mutable byte references and sizes with references set to the - // appropriate pointer in `params`; as we iterate, we'll increment each pointer by its size - // (except for singletons, which get an increment of 0). - std::array, NVectorized> vecparams{{ - std::pair( - reinterpret_cast(params[VIndex] = buffers[BIndex].ptr), - buffers[BIndex].size == 1 ? 0 : sizeof(param_n_t) - )... - }}; - - for (size_t i = 0; i < size; ++i) { - out[i] = f(*reinterpret_cast *>(params[Index])...); - for (auto &x : vecparams) x.first += x.second; - } - } - - template - void apply_broadcast(std::array &buffers, - std::array ¶ms, - array_t &output_array, - index_sequence, index_sequence, index_sequence) { - - buffer_info output = output_array.request(); - multi_array_iterator input_iter(buffers, output.shape); - - for (array_iterator iter = array_begin(output), end = array_end(output); - iter != end; - ++iter, ++input_iter) { - PYBIND11_EXPAND_SIDE_EFFECTS(( - params[VIndex] = input_iter.template data() - )); - *iter = f(*reinterpret_cast *>(std::get(params))...); - } - } -}; - -template -vectorize_helper -vectorize_extractor(const Func &f, Return (*) (Args ...)) { - return detail::vectorize_helper(f); -} - -template struct handle_type_name> { - static PYBIND11_DESCR name() { - return _("numpy.ndarray[") + npy_format_descriptor::name() + _("]"); - } -}; - -NAMESPACE_END(detail) - -// Vanilla pointer vectorizer: -template -detail::vectorize_helper -vectorize(Return (*f) (Args ...)) { - return detail::vectorize_helper(f); -} - -// lambda vectorizer: -template ::operator())>::type> -auto vectorize(Func &&f) -> decltype( - detail::vectorize_extractor(std::forward(f), (FuncType *) nullptr)) { - return detail::vectorize_extractor(std::forward(f), (FuncType *) nullptr); -} - -// Vectorize a class method (non-const): -template ())), Return, Class *, Args...>> -Helper vectorize(Return (Class::*f)(Args...)) { - return Helper(std::mem_fn(f)); -} - -// Vectorize a class method (non-const): -template ())), Return, const Class *, Args...>> -Helper vectorize(Return (Class::*f)(Args...) const) { - return Helper(std::mem_fn(f)); -} - -NAMESPACE_END(pybind11) - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/ppocr/postprocess/lanms/include/pybind11/operators.h b/ppocr/postprocess/lanms/include/pybind11/operators.h deleted file mode 100644 index 562987b8..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/operators.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - pybind11/operator.h: Metatemplates for operator overloading - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" - -#if defined(__clang__) && !defined(__INTEL_COMPILER) -# pragma clang diagnostic ignored "-Wunsequenced" // multiple unsequenced modifications to 'self' (when using def(py::self OP Type())) -#elif defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant -#endif - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -/// Enumeration with all supported operator types -enum op_id : int { - op_add, op_sub, op_mul, op_div, op_mod, op_divmod, op_pow, op_lshift, - op_rshift, op_and, op_xor, op_or, op_neg, op_pos, op_abs, op_invert, - op_int, op_long, op_float, op_str, op_cmp, op_gt, op_ge, op_lt, op_le, - op_eq, op_ne, op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift, - op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, op_nonzero, - op_repr, op_truediv, op_itruediv -}; - -enum op_type : int { - op_l, /* base type on left */ - op_r, /* base type on right */ - op_u /* unary operator */ -}; - -struct self_t { }; -static const self_t self = self_t(); - -/// Type for an unused type slot -struct undefined_t { }; - -/// Don't warn about an unused variable -inline self_t __self() { return self; } - -/// base template of operator implementations -template struct op_impl { }; - -/// Operator implementation generator -template struct op_ { - template void execute(Class &cl, const Extra&... extra) const { - using Base = typename Class::type; - using L_type = conditional_t::value, Base, L>; - using R_type = conditional_t::value, Base, R>; - using op = op_impl; - cl.def(op::name(), &op::execute, is_operator(), extra...); - #if PY_MAJOR_VERSION < 3 - if (id == op_truediv || id == op_itruediv) - cl.def(id == op_itruediv ? "__idiv__" : ot == op_l ? "__div__" : "__rdiv__", - &op::execute, is_operator(), extra...); - #endif - } - template void execute_cast(Class &cl, const Extra&... extra) const { - using Base = typename Class::type; - using L_type = conditional_t::value, Base, L>; - using R_type = conditional_t::value, Base, R>; - using op = op_impl; - cl.def(op::name(), &op::execute_cast, is_operator(), extra...); - #if PY_MAJOR_VERSION < 3 - if (id == op_truediv || id == op_itruediv) - cl.def(id == op_itruediv ? "__idiv__" : ot == op_l ? "__div__" : "__rdiv__", - &op::execute, is_operator(), extra...); - #endif - } -}; - -#define PYBIND11_BINARY_OPERATOR(id, rid, op, expr) \ -template struct op_impl { \ - static char const* name() { return "__" #id "__"; } \ - static auto execute(const L &l, const R &r) -> decltype(expr) { return (expr); } \ - static B execute_cast(const L &l, const R &r) { return B(expr); } \ -}; \ -template struct op_impl { \ - static char const* name() { return "__" #rid "__"; } \ - static auto execute(const R &r, const L &l) -> decltype(expr) { return (expr); } \ - static B execute_cast(const R &r, const L &l) { return B(expr); } \ -}; \ -inline op_ op(const self_t &, const self_t &) { \ - return op_(); \ -} \ -template op_ op(const self_t &, const T &) { \ - return op_(); \ -} \ -template op_ op(const T &, const self_t &) { \ - return op_(); \ -} - -#define PYBIND11_INPLACE_OPERATOR(id, op, expr) \ -template struct op_impl { \ - static char const* name() { return "__" #id "__"; } \ - static auto execute(L &l, const R &r) -> decltype(expr) { return expr; } \ - static B execute_cast(L &l, const R &r) { return B(expr); } \ -}; \ -template op_ op(const self_t &, const T &) { \ - return op_(); \ -} - -#define PYBIND11_UNARY_OPERATOR(id, op, expr) \ -template struct op_impl { \ - static char const* name() { return "__" #id "__"; } \ - static auto execute(const L &l) -> decltype(expr) { return expr; } \ - static B execute_cast(const L &l) { return B(expr); } \ -}; \ -inline op_ op(const self_t &) { \ - return op_(); \ -} - -PYBIND11_BINARY_OPERATOR(sub, rsub, operator-, l - r) -PYBIND11_BINARY_OPERATOR(add, radd, operator+, l + r) -PYBIND11_BINARY_OPERATOR(mul, rmul, operator*, l * r) -PYBIND11_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r) -PYBIND11_BINARY_OPERATOR(mod, rmod, operator%, l % r) -PYBIND11_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r) -PYBIND11_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r) -PYBIND11_BINARY_OPERATOR(and, rand, operator&, l & r) -PYBIND11_BINARY_OPERATOR(xor, rxor, operator^, l ^ r) -PYBIND11_BINARY_OPERATOR(eq, eq, operator==, l == r) -PYBIND11_BINARY_OPERATOR(ne, ne, operator!=, l != r) -PYBIND11_BINARY_OPERATOR(or, ror, operator|, l | r) -PYBIND11_BINARY_OPERATOR(gt, lt, operator>, l > r) -PYBIND11_BINARY_OPERATOR(ge, le, operator>=, l >= r) -PYBIND11_BINARY_OPERATOR(lt, gt, operator<, l < r) -PYBIND11_BINARY_OPERATOR(le, ge, operator<=, l <= r) -//PYBIND11_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r)) -PYBIND11_INPLACE_OPERATOR(iadd, operator+=, l += r) -PYBIND11_INPLACE_OPERATOR(isub, operator-=, l -= r) -PYBIND11_INPLACE_OPERATOR(imul, operator*=, l *= r) -PYBIND11_INPLACE_OPERATOR(itruediv, operator/=, l /= r) -PYBIND11_INPLACE_OPERATOR(imod, operator%=, l %= r) -PYBIND11_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r) -PYBIND11_INPLACE_OPERATOR(irshift, operator>>=, l >>= r) -PYBIND11_INPLACE_OPERATOR(iand, operator&=, l &= r) -PYBIND11_INPLACE_OPERATOR(ixor, operator^=, l ^= r) -PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r) -PYBIND11_UNARY_OPERATOR(neg, operator-, -l) -PYBIND11_UNARY_OPERATOR(pos, operator+, +l) -PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l)) -PYBIND11_UNARY_OPERATOR(invert, operator~, (~l)) -PYBIND11_UNARY_OPERATOR(bool, operator!, !!l) -PYBIND11_UNARY_OPERATOR(int, int_, (int) l) -PYBIND11_UNARY_OPERATOR(float, float_, (double) l) - -#undef PYBIND11_BINARY_OPERATOR -#undef PYBIND11_INPLACE_OPERATOR -#undef PYBIND11_UNARY_OPERATOR -NAMESPACE_END(detail) - -using detail::self; - -NAMESPACE_END(pybind11) - -#if defined(_MSC_VER) -# pragma warning(pop) -#endif diff --git a/ppocr/postprocess/lanms/include/pybind11/options.h b/ppocr/postprocess/lanms/include/pybind11/options.h deleted file mode 100644 index 3105551d..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/options.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - pybind11/options.h: global settings that are configurable at runtime. - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "common.h" - -NAMESPACE_BEGIN(pybind11) - -class options { -public: - - // Default RAII constructor, which leaves settings as they currently are. - options() : previous_state(global_state()) {} - - // Class is non-copyable. - options(const options&) = delete; - options& operator=(const options&) = delete; - - // Destructor, which restores settings that were in effect before. - ~options() { - global_state() = previous_state; - } - - // Setter methods (affect the global state): - - options& disable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = false; return *this; } - - options& enable_user_defined_docstrings() & { global_state().show_user_defined_docstrings = true; return *this; } - - options& disable_function_signatures() & { global_state().show_function_signatures = false; return *this; } - - options& enable_function_signatures() & { global_state().show_function_signatures = true; return *this; } - - // Getter methods (return the global state): - - static bool show_user_defined_docstrings() { return global_state().show_user_defined_docstrings; } - - static bool show_function_signatures() { return global_state().show_function_signatures; } - - // This type is not meant to be allocated on the heap. - void* operator new(size_t) = delete; - -private: - - struct state { - bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings. - bool show_function_signatures = true; //< Include auto-generated function signatures in docstrings. - }; - - static state &global_state() { - static state instance; - return instance; - } - - state previous_state; -}; - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/pybind11.h b/ppocr/postprocess/lanms/include/pybind11/pybind11.h deleted file mode 100644 index d3f34ee6..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/pybind11.h +++ /dev/null @@ -1,1869 +0,0 @@ -/* - pybind11/pybind11.h: Main header file of the C++11 python - binding generator library - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#if defined(_MSC_VER) -# pragma warning(push) -# pragma warning(disable: 4100) // warning C4100: Unreferenced formal parameter -# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant -# pragma warning(disable: 4512) // warning C4512: Assignment operator was implicitly defined as deleted -# pragma warning(disable: 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning) -# pragma warning(disable: 4996) // warning C4996: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name -# pragma warning(disable: 4702) // warning C4702: unreachable code -# pragma warning(disable: 4522) // warning C4522: multiple assignment operators specified -#elif defined(__INTEL_COMPILER) -# pragma warning(push) -# pragma warning(disable: 68) // integer conversion resulted in a change of sign -# pragma warning(disable: 186) // pointless comparison of unsigned integer with zero -# pragma warning(disable: 878) // incompatible exception specifications -# pragma warning(disable: 1334) // the "template" keyword used for syntactic disambiguation may only be used within a template -# pragma warning(disable: 1682) // implicit conversion of a 64-bit integral type to a smaller integral type (potential portability problem) -# pragma warning(disable: 1875) // offsetof applied to non-POD (Plain Old Data) types is nonstandard -# pragma warning(disable: 2196) // warning #2196: routine is both "inline" and "noinline" -#elif defined(__GNUG__) && !defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-but-set-parameter" -# pragma GCC diagnostic ignored "-Wunused-but-set-variable" -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -# pragma GCC diagnostic ignored "-Wstrict-aliasing" -# pragma GCC diagnostic ignored "-Wattributes" -# if __GNUC__ >= 7 -# pragma GCC diagnostic ignored "-Wnoexcept-type" -# endif -#endif - -#include "attr.h" -#include "options.h" -#include "class_support.h" - -NAMESPACE_BEGIN(pybind11) - -/// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object -class cpp_function : public function { -public: - cpp_function() { } - - /// Construct a cpp_function from a vanilla function pointer - template - cpp_function(Return (*f)(Args...), const Extra&... extra) { - initialize(f, f, extra...); - } - - /// Construct a cpp_function from a lambda function (possibly with internal state) - template , - std::is_function, std::is_pointer, std::is_member_pointer - >::value> - > - cpp_function(Func &&f, const Extra&... extra) { - using FuncType = typename detail::remove_class::operator())>::type; - initialize(std::forward(f), - (FuncType *) nullptr, extra...); - } - - /// Construct a cpp_function from a class method (non-const) - template - cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) { - initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); }, - (Return (*) (Class *, Arg...)) nullptr, extra...); - } - - /// Construct a cpp_function from a class method (const) - template - cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) { - initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); }, - (Return (*)(const Class *, Arg ...)) nullptr, extra...); - } - - /// Return the function name - object name() const { return attr("__name__"); } - -protected: - /// Space optimization: don't inline this frequently instantiated fragment - PYBIND11_NOINLINE detail::function_record *make_function_record() { - return new detail::function_record(); - } - - /// Special internal constructor for functors, lambda functions, etc. - template - void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) { - - struct capture { detail::remove_reference_t f; }; - - /* Store the function including any extra state it might have (e.g. a lambda capture object) */ - auto rec = make_function_record(); - - /* Store the capture object directly in the function record if there is enough space */ - if (sizeof(capture) <= sizeof(rec->data)) { - /* Without these pragmas, GCC warns that there might not be - enough space to use the placement new operator. However, the - 'if' statement above ensures that this is the case. */ -#if defined(__GNUG__) && !defined(__clang__) && __GNUC__ >= 6 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wplacement-new" -#endif - new ((capture *) &rec->data) capture { std::forward(f) }; -#if defined(__GNUG__) && !defined(__clang__) && __GNUC__ >= 6 -# pragma GCC diagnostic pop -#endif - if (!std::is_trivially_destructible::value) - rec->free_data = [](detail::function_record *r) { ((capture *) &r->data)->~capture(); }; - } else { - rec->data[0] = new capture { std::forward(f) }; - rec->free_data = [](detail::function_record *r) { delete ((capture *) r->data[0]); }; - } - - /* Type casters for the function arguments and return value */ - using cast_in = detail::argument_loader; - using cast_out = detail::make_caster< - detail::conditional_t::value, detail::void_type, Return> - >; - - static_assert(detail::expected_num_args(sizeof...(Args), cast_in::has_args, cast_in::has_kwargs), - "The number of argument annotations does not match the number of function arguments"); - - /* Dispatch code which converts function arguments and performs the actual function call */ - rec->impl = [](detail::function_call &call) -> handle { - cast_in args_converter; - - /* Try to cast the function arguments into the C++ domain */ - if (!args_converter.load_args(call)) - return PYBIND11_TRY_NEXT_OVERLOAD; - - /* Invoke call policy pre-call hook */ - detail::process_attributes::precall(call); - - /* Get a pointer to the capture object */ - auto data = (sizeof(capture) <= sizeof(call.func.data) - ? &call.func.data : call.func.data[0]); - capture *cap = const_cast(reinterpret_cast(data)); - - /* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */ - const auto policy = detail::return_value_policy_override::policy(call.func.policy); - - /* Function scope guard -- defaults to the compile-to-nothing `void_type` */ - using Guard = detail::extract_guard_t; - - /* Perform the function call */ - handle result = cast_out::cast( - std::move(args_converter).template call(cap->f), policy, call.parent); - - /* Invoke call policy post-call hook */ - detail::process_attributes::postcall(call, result); - - return result; - }; - - /* Process any user-provided function attributes */ - detail::process_attributes::init(extra..., rec); - - /* Generate a readable signature describing the function's arguments and return value types */ - using detail::descr; using detail::_; - PYBIND11_DESCR signature = _("(") + cast_in::arg_names() + _(") -> ") + cast_out::name(); - - /* Register the function with Python from generic (non-templated) code */ - initialize_generic(rec, signature.text(), signature.types(), sizeof...(Args)); - - if (cast_in::has_args) rec->has_args = true; - if (cast_in::has_kwargs) rec->has_kwargs = true; - - /* Stash some additional information used by an important optimization in 'functional.h' */ - using FunctionType = Return (*)(Args...); - constexpr bool is_function_ptr = - std::is_convertible::value && - sizeof(capture) == sizeof(void *); - if (is_function_ptr) { - rec->is_stateless = true; - rec->data[1] = const_cast(reinterpret_cast(&typeid(FunctionType))); - } - } - - /// Register a function call with Python (generic non-templated code goes here) - void initialize_generic(detail::function_record *rec, const char *text, - const std::type_info *const *types, size_t args) { - - /* Create copies of all referenced C-style strings */ - rec->name = strdup(rec->name ? rec->name : ""); - if (rec->doc) rec->doc = strdup(rec->doc); - for (auto &a: rec->args) { - if (a.name) - a.name = strdup(a.name); - if (a.descr) - a.descr = strdup(a.descr); - else if (a.value) - a.descr = strdup(a.value.attr("__repr__")().cast().c_str()); - } - - /* Generate a proper function signature */ - std::string signature; - size_t type_depth = 0, char_index = 0, type_index = 0, arg_index = 0; - while (true) { - char c = text[char_index++]; - if (c == '\0') - break; - - if (c == '{') { - // Write arg name for everything except *args, **kwargs and return type. - if (type_depth == 0 && text[char_index] != '*' && arg_index < args) { - if (!rec->args.empty() && rec->args[arg_index].name) { - signature += rec->args[arg_index].name; - } else if (arg_index == 0 && rec->is_method) { - signature += "self"; - } else { - signature += "arg" + std::to_string(arg_index - (rec->is_method ? 1 : 0)); - } - signature += ": "; - } - ++type_depth; - } else if (c == '}') { - --type_depth; - if (type_depth == 0) { - if (arg_index < rec->args.size() && rec->args[arg_index].descr) { - signature += "="; - signature += rec->args[arg_index].descr; - } - arg_index++; - } - } else if (c == '%') { - const std::type_info *t = types[type_index++]; - if (!t) - pybind11_fail("Internal error while parsing type signature (1)"); - if (auto tinfo = detail::get_type_info(*t)) { -#if defined(PYPY_VERSION) - signature += handle((PyObject *) tinfo->type) - .attr("__module__") - .cast() + "."; -#endif - signature += tinfo->type->tp_name; - } else { - std::string tname(t->name()); - detail::clean_type_id(tname); - signature += tname; - } - } else { - signature += c; - } - } - if (type_depth != 0 || types[type_index] != nullptr) - pybind11_fail("Internal error while parsing type signature (2)"); - - #if !defined(PYBIND11_CONSTEXPR_DESCR) - delete[] types; - delete[] text; - #endif - -#if PY_MAJOR_VERSION < 3 - if (strcmp(rec->name, "__next__") == 0) { - std::free(rec->name); - rec->name = strdup("next"); - } else if (strcmp(rec->name, "__bool__") == 0) { - std::free(rec->name); - rec->name = strdup("__nonzero__"); - } -#endif - rec->signature = strdup(signature.c_str()); - rec->args.shrink_to_fit(); - rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__"); - rec->nargs = (std::uint16_t) args; - - if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) - rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr()); - - detail::function_record *chain = nullptr, *chain_start = rec; - if (rec->sibling) { - if (PyCFunction_Check(rec->sibling.ptr())) { - auto rec_capsule = reinterpret_borrow(PyCFunction_GET_SELF(rec->sibling.ptr())); - chain = (detail::function_record *) rec_capsule; - /* Never append a method to an overload chain of a parent class; - instead, hide the parent's overloads in this case */ - if (!chain->scope.is(rec->scope)) - chain = nullptr; - } - // Don't trigger for things like the default __init__, which are wrapper_descriptors that we are intentionally replacing - else if (!rec->sibling.is_none() && rec->name[0] != '_') - pybind11_fail("Cannot overload existing non-function object \"" + std::string(rec->name) + - "\" with a function of the same name"); - } - - if (!chain) { - /* No existing overload was found, create a new function object */ - rec->def = new PyMethodDef(); - std::memset(rec->def, 0, sizeof(PyMethodDef)); - rec->def->ml_name = rec->name; - rec->def->ml_meth = reinterpret_cast(*dispatcher); - rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; - - capsule rec_capsule(rec, [](void *ptr) { - destruct((detail::function_record *) ptr); - }); - - object scope_module; - if (rec->scope) { - if (hasattr(rec->scope, "__module__")) { - scope_module = rec->scope.attr("__module__"); - } else if (hasattr(rec->scope, "__name__")) { - scope_module = rec->scope.attr("__name__"); - } - } - - m_ptr = PyCFunction_NewEx(rec->def, rec_capsule.ptr(), scope_module.ptr()); - if (!m_ptr) - pybind11_fail("cpp_function::cpp_function(): Could not allocate function object"); - } else { - /* Append at the end of the overload chain */ - m_ptr = rec->sibling.ptr(); - inc_ref(); - chain_start = chain; - if (chain->is_method != rec->is_method) - pybind11_fail("overloading a method with both static and instance methods is not supported; " - #if defined(NDEBUG) - "compile in debug mode for more details" - #else - "error while attempting to bind " + std::string(rec->is_method ? "instance" : "static") + " method " + - std::string(pybind11::str(rec->scope.attr("__name__"))) + "." + std::string(rec->name) + signature - #endif - ); - while (chain->next) - chain = chain->next; - chain->next = rec; - } - - std::string signatures; - int index = 0; - /* Create a nice pydoc rec including all signatures and - docstrings of the functions in the overload chain */ - if (chain && options::show_function_signatures()) { - // First a generic signature - signatures += rec->name; - signatures += "(*args, **kwargs)\n"; - signatures += "Overloaded function.\n\n"; - } - // Then specific overload signatures - bool first_user_def = true; - for (auto it = chain_start; it != nullptr; it = it->next) { - if (options::show_function_signatures()) { - if (index > 0) signatures += "\n"; - if (chain) - signatures += std::to_string(++index) + ". "; - signatures += rec->name; - signatures += it->signature; - signatures += "\n"; - } - if (it->doc && strlen(it->doc) > 0 && options::show_user_defined_docstrings()) { - // If we're appending another docstring, and aren't printing function signatures, we - // need to append a newline first: - if (!options::show_function_signatures()) { - if (first_user_def) first_user_def = false; - else signatures += "\n"; - } - if (options::show_function_signatures()) signatures += "\n"; - signatures += it->doc; - if (options::show_function_signatures()) signatures += "\n"; - } - } - - /* Install docstring */ - PyCFunctionObject *func = (PyCFunctionObject *) m_ptr; - if (func->m_ml->ml_doc) - std::free(const_cast(func->m_ml->ml_doc)); - func->m_ml->ml_doc = strdup(signatures.c_str()); - - if (rec->is_method) { - m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr()); - if (!m_ptr) - pybind11_fail("cpp_function::cpp_function(): Could not allocate instance method object"); - Py_DECREF(func); - } - } - - /// When a cpp_function is GCed, release any memory allocated by pybind11 - static void destruct(detail::function_record *rec) { - while (rec) { - detail::function_record *next = rec->next; - if (rec->free_data) - rec->free_data(rec); - std::free((char *) rec->name); - std::free((char *) rec->doc); - std::free((char *) rec->signature); - for (auto &arg: rec->args) { - std::free(const_cast(arg.name)); - std::free(const_cast(arg.descr)); - arg.value.dec_ref(); - } - if (rec->def) { - std::free(const_cast(rec->def->ml_doc)); - delete rec->def; - } - delete rec; - rec = next; - } - } - - /// Main dispatch logic for calls to functions bound using pybind11 - static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) { - using namespace detail; - - /* Iterator over the list of potentially admissible overloads */ - function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr), - *it = overloads; - - /* Need to know how many arguments + keyword arguments there are to pick the right overload */ - const size_t n_args_in = (size_t) PyTuple_GET_SIZE(args_in); - - handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr, - result = PYBIND11_TRY_NEXT_OVERLOAD; - - try { - // We do this in two passes: in the first pass, we load arguments with `convert=false`; - // in the second, we allow conversion (except for arguments with an explicit - // py::arg().noconvert()). This lets us prefer calls without conversion, with - // conversion as a fallback. - std::vector second_pass; - - // However, if there are no overloads, we can just skip the no-convert pass entirely - const bool overloaded = it != nullptr && it->next != nullptr; - - for (; it != nullptr; it = it->next) { - - /* For each overload: - 1. Copy all positional arguments we were given, also checking to make sure that - named positional arguments weren't *also* specified via kwarg. - 2. If we weren't given enough, try to make up the omitted ones by checking - whether they were provided by a kwarg matching the `py::arg("name")` name. If - so, use it (and remove it from kwargs; if not, see if the function binding - provided a default that we can use. - 3. Ensure that either all keyword arguments were "consumed", or that the function - takes a kwargs argument to accept unconsumed kwargs. - 4. Any positional arguments still left get put into a tuple (for args), and any - leftover kwargs get put into a dict. - 5. Pack everything into a vector; if we have py::args or py::kwargs, they are an - extra tuple or dict at the end of the positional arguments. - 6. Call the function call dispatcher (function_record::impl) - - If one of these fail, move on to the next overload and keep trying until we get a - result other than PYBIND11_TRY_NEXT_OVERLOAD. - */ - - function_record &func = *it; - size_t pos_args = func.nargs; // Number of positional arguments that we need - if (func.has_args) --pos_args; // (but don't count py::args - if (func.has_kwargs) --pos_args; // or py::kwargs) - - if (!func.has_args && n_args_in > pos_args) - continue; // Too many arguments for this overload - - if (n_args_in < pos_args && func.args.size() < pos_args) - continue; // Not enough arguments given, and not enough defaults to fill in the blanks - - function_call call(func, parent); - - size_t args_to_copy = std::min(pos_args, n_args_in); - size_t args_copied = 0; - - // 1. Copy any position arguments given. - bool bad_arg = false; - for (; args_copied < args_to_copy; ++args_copied) { - argument_record *arg_rec = args_copied < func.args.size() ? &func.args[args_copied] : nullptr; - if (kwargs_in && arg_rec && arg_rec->name && PyDict_GetItemString(kwargs_in, arg_rec->name)) { - bad_arg = true; - break; - } - - handle arg(PyTuple_GET_ITEM(args_in, args_copied)); - if (arg_rec && !arg_rec->none && arg.is_none()) { - bad_arg = true; - break; - } - call.args.push_back(arg); - call.args_convert.push_back(arg_rec ? arg_rec->convert : true); - } - if (bad_arg) - continue; // Maybe it was meant for another overload (issue #688) - - // We'll need to copy this if we steal some kwargs for defaults - dict kwargs = reinterpret_borrow(kwargs_in); - - // 2. Check kwargs and, failing that, defaults that may help complete the list - if (args_copied < pos_args) { - bool copied_kwargs = false; - - for (; args_copied < pos_args; ++args_copied) { - const auto &arg = func.args[args_copied]; - - handle value; - if (kwargs_in && arg.name) - value = PyDict_GetItemString(kwargs.ptr(), arg.name); - - if (value) { - // Consume a kwargs value - if (!copied_kwargs) { - kwargs = reinterpret_steal(PyDict_Copy(kwargs.ptr())); - copied_kwargs = true; - } - PyDict_DelItemString(kwargs.ptr(), arg.name); - } else if (arg.value) { - value = arg.value; - } - - if (value) { - call.args.push_back(value); - call.args_convert.push_back(arg.convert); - } - else - break; - } - - if (args_copied < pos_args) - continue; // Not enough arguments, defaults, or kwargs to fill the positional arguments - } - - // 3. Check everything was consumed (unless we have a kwargs arg) - if (kwargs && kwargs.size() > 0 && !func.has_kwargs) - continue; // Unconsumed kwargs, but no py::kwargs argument to accept them - - // 4a. If we have a py::args argument, create a new tuple with leftovers - tuple extra_args; - if (func.has_args) { - if (args_to_copy == 0) { - // We didn't copy out any position arguments from the args_in tuple, so we - // can reuse it directly without copying: - extra_args = reinterpret_borrow(args_in); - } else if (args_copied >= n_args_in) { - extra_args = tuple(0); - } else { - size_t args_size = n_args_in - args_copied; - extra_args = tuple(args_size); - for (size_t i = 0; i < args_size; ++i) { - handle item = PyTuple_GET_ITEM(args_in, args_copied + i); - extra_args[i] = item.inc_ref().ptr(); - } - } - call.args.push_back(extra_args); - call.args_convert.push_back(false); - } - - // 4b. If we have a py::kwargs, pass on any remaining kwargs - if (func.has_kwargs) { - if (!kwargs.ptr()) - kwargs = dict(); // If we didn't get one, send an empty one - call.args.push_back(kwargs); - call.args_convert.push_back(false); - } - - // 5. Put everything in a vector. Not technically step 5, we've been building it - // in `call.args` all along. - #if !defined(NDEBUG) - if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) - pybind11_fail("Internal error: function call dispatcher inserted wrong number of arguments!"); - #endif - - std::vector second_pass_convert; - if (overloaded) { - // We're in the first no-convert pass, so swap out the conversion flags for a - // set of all-false flags. If the call fails, we'll swap the flags back in for - // the conversion-allowed call below. - second_pass_convert.resize(func.nargs, false); - call.args_convert.swap(second_pass_convert); - } - - // 6. Call the function. - try { - loader_life_support guard{}; - result = func.impl(call); - } catch (reference_cast_error &) { - result = PYBIND11_TRY_NEXT_OVERLOAD; - } - - if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) - break; - - if (overloaded) { - // The (overloaded) call failed; if the call has at least one argument that - // permits conversion (i.e. it hasn't been explicitly specified `.noconvert()`) - // then add this call to the list of second pass overloads to try. - for (size_t i = func.is_method ? 1 : 0; i < pos_args; i++) { - if (second_pass_convert[i]) { - // Found one: swap the converting flags back in and store the call for - // the second pass. - call.args_convert.swap(second_pass_convert); - second_pass.push_back(std::move(call)); - break; - } - } - } - } - - if (overloaded && !second_pass.empty() && result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { - // The no-conversion pass finished without success, try again with conversion allowed - for (auto &call : second_pass) { - try { - loader_life_support guard{}; - result = call.func.impl(call); - } catch (reference_cast_error &) { - result = PYBIND11_TRY_NEXT_OVERLOAD; - } - - if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD) - break; - } - } - } catch (error_already_set &e) { - e.restore(); - return nullptr; - } catch (...) { - /* When an exception is caught, give each registered exception - translator a chance to translate it to a Python exception - in reverse order of registration. - - A translator may choose to do one of the following: - - - catch the exception and call PyErr_SetString or PyErr_SetObject - to set a standard (or custom) Python exception, or - - do nothing and let the exception fall through to the next translator, or - - delegate translation to the next translator by throwing a new type of exception. */ - - auto last_exception = std::current_exception(); - auto ®istered_exception_translators = get_internals().registered_exception_translators; - for (auto& translator : registered_exception_translators) { - try { - translator(last_exception); - } catch (...) { - last_exception = std::current_exception(); - continue; - } - return nullptr; - } - PyErr_SetString(PyExc_SystemError, "Exception escaped from default exception translator!"); - return nullptr; - } - - if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { - if (overloads->is_operator) - return handle(Py_NotImplemented).inc_ref().ptr(); - - std::string msg = std::string(overloads->name) + "(): incompatible " + - std::string(overloads->is_constructor ? "constructor" : "function") + - " arguments. The following argument types are supported:\n"; - - int ctr = 0; - for (function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) { - msg += " "+ std::to_string(++ctr) + ". "; - - bool wrote_sig = false; - if (overloads->is_constructor) { - // For a constructor, rewrite `(self: Object, arg0, ...) -> NoneType` as `Object(arg0, ...)` - std::string sig = it2->signature; - size_t start = sig.find('(') + 7; // skip "(self: " - if (start < sig.size()) { - // End at the , for the next argument - size_t end = sig.find(", "), next = end + 2; - size_t ret = sig.rfind(" -> "); - // Or the ), if there is no comma: - if (end >= sig.size()) next = end = sig.find(')'); - if (start < end && next < sig.size()) { - msg.append(sig, start, end - start); - msg += '('; - msg.append(sig, next, ret - next); - wrote_sig = true; - } - } - } - if (!wrote_sig) msg += it2->signature; - - msg += "\n"; - } - msg += "\nInvoked with: "; - auto args_ = reinterpret_borrow(args_in); - bool some_args = false; - for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { - if (!some_args) some_args = true; - else msg += ", "; - msg += pybind11::repr(args_[ti]); - } - if (kwargs_in) { - auto kwargs = reinterpret_borrow(kwargs_in); - if (kwargs.size() > 0) { - if (some_args) msg += "; "; - msg += "kwargs: "; - bool first = true; - for (auto kwarg : kwargs) { - if (first) first = false; - else msg += ", "; - msg += pybind11::str("{}={!r}").format(kwarg.first, kwarg.second); - } - } - } - - PyErr_SetString(PyExc_TypeError, msg.c_str()); - return nullptr; - } else if (!result) { - std::string msg = "Unable to convert function return value to a " - "Python type! The signature was\n\t"; - msg += it->signature; - PyErr_SetString(PyExc_TypeError, msg.c_str()); - return nullptr; - } else { - if (overloads->is_constructor) { - auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr()); - tinfo->init_instance(reinterpret_cast(parent.ptr()), nullptr); - } - return result.ptr(); - } - } -}; - -/// Wrapper for Python extension modules -class module : public object { -public: - PYBIND11_OBJECT_DEFAULT(module, object, PyModule_Check) - - /// Create a new top-level Python module with the given name and docstring - explicit module(const char *name, const char *doc = nullptr) { - if (!options::show_user_defined_docstrings()) doc = nullptr; -#if PY_MAJOR_VERSION >= 3 - PyModuleDef *def = new PyModuleDef(); - std::memset(def, 0, sizeof(PyModuleDef)); - def->m_name = name; - def->m_doc = doc; - def->m_size = -1; - Py_INCREF(def); - m_ptr = PyModule_Create(def); -#else - m_ptr = Py_InitModule3(name, nullptr, doc); -#endif - if (m_ptr == nullptr) - pybind11_fail("Internal error in module::module()"); - inc_ref(); - } - - /** \rst - Create Python binding for a new function within the module scope. ``Func`` - can be a plain C++ function, a function pointer, or a lambda function. For - details on the ``Extra&& ... extra`` argument, see section :ref:`extras`. - \endrst */ - template - module &def(const char *name_, Func &&f, const Extra& ... extra) { - cpp_function func(std::forward(f), name(name_), scope(*this), - sibling(getattr(*this, name_, none())), extra...); - // NB: allow overwriting here because cpp_function sets up a chain with the intention of - // overwriting (and has already checked internally that it isn't overwriting non-functions). - add_object(name_, func, true /* overwrite */); - return *this; - } - - /** \rst - Create and return a new Python submodule with the given name and docstring. - This also works recursively, i.e. - - .. code-block:: cpp - - py::module m("example", "pybind11 example plugin"); - py::module m2 = m.def_submodule("sub", "A submodule of 'example'"); - py::module m3 = m2.def_submodule("subsub", "A submodule of 'example.sub'"); - \endrst */ - module def_submodule(const char *name, const char *doc = nullptr) { - std::string full_name = std::string(PyModule_GetName(m_ptr)) - + std::string(".") + std::string(name); - auto result = reinterpret_borrow(PyImport_AddModule(full_name.c_str())); - if (doc && options::show_user_defined_docstrings()) - result.attr("__doc__") = pybind11::str(doc); - attr(name) = result; - return result; - } - - /// Import and return a module or throws `error_already_set`. - static module import(const char *name) { - PyObject *obj = PyImport_ImportModule(name); - if (!obj) - throw error_already_set(); - return reinterpret_steal(obj); - } - - // Adds an object to the module using the given name. Throws if an object with the given name - // already exists. - // - // overwrite should almost always be false: attempting to overwrite objects that pybind11 has - // established will, in most cases, break things. - PYBIND11_NOINLINE void add_object(const char *name, handle obj, bool overwrite = false) { - if (!overwrite && hasattr(*this, name)) - pybind11_fail("Error during initialization: multiple incompatible definitions with name \"" + - std::string(name) + "\""); - - PyModule_AddObject(ptr(), name, obj.inc_ref().ptr() /* steals a reference */); - } -}; - -/// \ingroup python_builtins -/// Return a dictionary representing the global variables in the current execution frame, -/// or ``__main__.__dict__`` if there is no frame (usually when the interpreter is embedded). -inline dict globals() { - PyObject *p = PyEval_GetGlobals(); - return reinterpret_borrow(p ? p : module::import("__main__").attr("__dict__").ptr()); -} - -NAMESPACE_BEGIN(detail) -/// Generic support for creating new Python heap types -class generic_type : public object { - template friend class class_; -public: - PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) -protected: - void initialize(const type_record &rec) { - if (rec.scope && hasattr(rec.scope, rec.name)) - pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) + - "\": an object with that name is already defined"); - - if (get_type_info(*rec.type)) - pybind11_fail("generic_type: type \"" + std::string(rec.name) + - "\" is already registered!"); - - m_ptr = make_new_python_type(rec); - - /* Register supplemental type information in C++ dict */ - auto *tinfo = new detail::type_info(); - tinfo->type = (PyTypeObject *) m_ptr; - tinfo->cpptype = rec.type; - tinfo->type_size = rec.type_size; - tinfo->operator_new = rec.operator_new; - tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size); - tinfo->init_instance = rec.init_instance; - tinfo->dealloc = rec.dealloc; - tinfo->simple_type = true; - tinfo->simple_ancestors = true; - - auto &internals = get_internals(); - auto tindex = std::type_index(*rec.type); - tinfo->direct_conversions = &internals.direct_conversions[tindex]; - tinfo->default_holder = rec.default_holder; - internals.registered_types_cpp[tindex] = tinfo; - internals.registered_types_py[(PyTypeObject *) m_ptr] = { tinfo }; - - if (rec.bases.size() > 1 || rec.multiple_inheritance) { - mark_parents_nonsimple(tinfo->type); - tinfo->simple_ancestors = false; - } - else if (rec.bases.size() == 1) { - auto parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr()); - tinfo->simple_ancestors = parent_tinfo->simple_ancestors; - } - } - - /// Helper function which tags all parents of a type using mult. inheritance - void mark_parents_nonsimple(PyTypeObject *value) { - auto t = reinterpret_borrow(value->tp_bases); - for (handle h : t) { - auto tinfo2 = get_type_info((PyTypeObject *) h.ptr()); - if (tinfo2) - tinfo2->simple_type = false; - mark_parents_nonsimple((PyTypeObject *) h.ptr()); - } - } - - void install_buffer_funcs( - buffer_info *(*get_buffer)(PyObject *, void *), - void *get_buffer_data) { - PyHeapTypeObject *type = (PyHeapTypeObject*) m_ptr; - auto tinfo = detail::get_type_info(&type->ht_type); - - if (!type->ht_type.tp_as_buffer) - pybind11_fail( - "To be able to register buffer protocol support for the type '" + - std::string(tinfo->type->tp_name) + - "' the associated class<>(..) invocation must " - "include the pybind11::buffer_protocol() annotation!"); - - tinfo->get_buffer = get_buffer; - tinfo->get_buffer_data = get_buffer_data; - } - - void def_property_static_impl(const char *name, - handle fget, handle fset, - detail::function_record *rec_fget) { - const auto is_static = !(rec_fget->is_method && rec_fget->scope); - const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings(); - - auto property = handle((PyObject *) (is_static ? get_internals().static_property_type - : &PyProperty_Type)); - attr(name) = property(fget.ptr() ? fget : none(), - fset.ptr() ? fset : none(), - /*deleter*/none(), - pybind11::str(has_doc ? rec_fget->doc : "")); - } -}; - -/// Set the pointer to operator new if it exists. The cast is needed because it can be overloaded. -template (T::operator new))>> -void set_operator_new(type_record *r) { r->operator_new = &T::operator new; } - -template void set_operator_new(...) { } - -template struct has_operator_delete : std::false_type { }; -template struct has_operator_delete(T::operator delete))>> - : std::true_type { }; -template struct has_operator_delete_size : std::false_type { }; -template struct has_operator_delete_size(T::operator delete))>> - : std::true_type { }; -/// Call class-specific delete if it exists or global otherwise. Can also be an overload set. -template ::value, int> = 0> -void call_operator_delete(T *p, size_t) { T::operator delete(p); } -template ::value && has_operator_delete_size::value, int> = 0> -void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); } - -inline void call_operator_delete(void *p, size_t) { ::operator delete(p); } - -NAMESPACE_END(detail) - -/// Given a pointer to a member function, cast it to its `Derived` version. -/// Forward everything else unchanged. -template -auto method_adaptor(F &&f) -> decltype(std::forward(f)) { return std::forward(f); } - -template -auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(Args...) { return pmf; } - -template -auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(Args...) const { return pmf; } - -template -class class_ : public detail::generic_type { - template using is_holder = detail::is_holder_type; - template using is_subtype = detail::is_strict_base_of; - template using is_base = detail::is_strict_base_of; - // struct instead of using here to help MSVC: - template struct is_valid_class_option : - detail::any_of, is_subtype, is_base> {}; - -public: - using type = type_; - using type_alias = detail::exactly_one_t; - constexpr static bool has_alias = !std::is_void::value; - using holder_type = detail::exactly_one_t, options...>; - - static_assert(detail::all_of...>::value, - "Unknown/invalid class_ template parameters provided"); - - PYBIND11_OBJECT(class_, generic_type, PyType_Check) - - template - class_(handle scope, const char *name, const Extra &... extra) { - using namespace detail; - - // MI can only be specified via class_ template options, not constructor parameters - static_assert( - none_of...>::value || // no base class arguments, or: - ( constexpr_sum(is_pyobject::value...) == 1 && // Exactly one base - constexpr_sum(is_base::value...) == 0 && // no template option bases - none_of...>::value), // no multiple_inheritance attr - "Error: multiple inheritance bases must be specified via class_ template options"); - - type_record record; - record.scope = scope; - record.name = name; - record.type = &typeid(type); - record.type_size = sizeof(conditional_t); - record.holder_size = sizeof(holder_type); - record.init_instance = init_instance; - record.dealloc = dealloc; - record.default_holder = std::is_same>::value; - - set_operator_new(&record); - - /* Register base classes specified via template arguments to class_, if any */ - PYBIND11_EXPAND_SIDE_EFFECTS(add_base(record)); - - /* Process optional arguments, if any */ - process_attributes::init(extra..., &record); - - generic_type::initialize(record); - - if (has_alias) { - auto &instances = get_internals().registered_types_cpp; - instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; - } - } - - template ::value, int> = 0> - static void add_base(detail::type_record &rec) { - rec.add_base(typeid(Base), [](void *src) -> void * { - return static_cast(reinterpret_cast(src)); - }); - } - - template ::value, int> = 0> - static void add_base(detail::type_record &) { } - - template - class_ &def(const char *name_, Func&& f, const Extra&... extra) { - cpp_function cf(method_adaptor(std::forward(f)), name(name_), is_method(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; - } - - template class_ & - def_static(const char *name_, Func &&f, const Extra&... extra) { - static_assert(!std::is_member_function_pointer::value, - "def_static(...) called with a non-static member function pointer"); - cpp_function cf(std::forward(f), name(name_), scope(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; - } - - template - class_ &def(const detail::op_ &op, const Extra&... extra) { - op.execute(*this, extra...); - return *this; - } - - template - class_ & def_cast(const detail::op_ &op, const Extra&... extra) { - op.execute_cast(*this, extra...); - return *this; - } - - template - class_ &def(const detail::init &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; - } - - template - class_ &def(const detail::init_alias &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; - } - - template class_& def_buffer(Func &&func) { - struct capture { Func func; }; - capture *ptr = new capture { std::forward(func) }; - install_buffer_funcs([](PyObject *obj, void *ptr) -> buffer_info* { - detail::make_caster caster; - if (!caster.load(obj, false)) - return nullptr; - return new buffer_info(((capture *) ptr)->func(caster)); - }, ptr); - return *this; - } - - template - class_ &def_buffer(Return (Class::*func)(Args...)) { - return def_buffer([func] (type &obj) { return (obj.*func)(); }); - } - - template - class_ &def_buffer(Return (Class::*func)(Args...) const) { - return def_buffer([func] (const type &obj) { return (obj.*func)(); }); - } - - template - class_ &def_readwrite(const char *name, D C::*pm, const Extra&... extra) { - static_assert(std::is_base_of::value, "def_readwrite() requires a class member (or base class member)"); - cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)), - fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this)); - def_property(name, fget, fset, return_value_policy::reference_internal, extra...); - return *this; - } - - template - class_ &def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { - static_assert(std::is_base_of::value, "def_readonly() requires a class member (or base class member)"); - cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)); - def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); - return *this; - } - - template - class_ &def_readwrite_static(const char *name, D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)), - fset([pm](object, const D &value) { *pm = value; }, scope(*this)); - def_property_static(name, fget, fset, return_value_policy::reference, extra...); - return *this; - } - - template - class_ &def_readonly_static(const char *name, const D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)); - def_property_readonly_static(name, fget, return_value_policy::reference, extra...); - return *this; - } - - /// Uses return_value_policy::reference_internal by default - template - class_ &def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly(name, cpp_function(method_adaptor(fget)), - return_value_policy::reference_internal, extra...); - } - - /// Uses cpp_function's return_value_policy by default - template - class_ &def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property(name, fget, cpp_function(), extra...); - } - - /// Uses return_value_policy::reference by default - template - class_ &def_property_readonly_static(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly_static(name, cpp_function(fget), return_value_policy::reference, extra...); - } - - /// Uses cpp_function's return_value_policy by default - template - class_ &def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property_static(name, fget, cpp_function(), extra...); - } - - /// Uses return_value_policy::reference_internal by default - template - class_ &def_property(const char *name, const Getter &fget, const Setter &fset, const Extra& ...extra) { - return def_property(name, fget, cpp_function(method_adaptor(fset)), extra...); - } - template - class_ &def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property(name, cpp_function(method_adaptor(fget)), fset, - return_value_policy::reference_internal, extra...); - } - - /// Uses cpp_function's return_value_policy by default - template - class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, fget, fset, is_method(*this), extra...); - } - - /// Uses return_value_policy::reference by default - template - class_ &def_property_static(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, cpp_function(fget), fset, return_value_policy::reference, extra...); - } - - /// Uses cpp_function's return_value_policy by default - template - class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - auto rec_fget = get_function_record(fget), rec_fset = get_function_record(fset); - char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific documentation string */ - detail::process_attributes::init(extra..., rec_fget); - if (rec_fget->doc && rec_fget->doc != doc_prev) { - free(doc_prev); - rec_fget->doc = strdup(rec_fget->doc); - } - if (rec_fset) { - doc_prev = rec_fset->doc; - detail::process_attributes::init(extra..., rec_fset); - if (rec_fset->doc && rec_fset->doc != doc_prev) { - free(doc_prev); - rec_fset->doc = strdup(rec_fset->doc); - } - } - def_property_static_impl(name, fget, fset, rec_fget); - return *this; - } - -private: - /// Initialize holder object, variant 1: object derives from enable_shared_from_this - template - static void init_holder(detail::instance *inst, detail::value_and_holder &v_h, - const holder_type * /* unused */, const std::enable_shared_from_this * /* dummy */) { - try { - auto sh = std::dynamic_pointer_cast( - v_h.value_ptr()->shared_from_this()); - if (sh) { - new (&v_h.holder()) holder_type(std::move(sh)); - v_h.set_holder_constructed(); - } - } catch (const std::bad_weak_ptr &) {} - - if (!v_h.holder_constructed() && inst->owned) { - new (&v_h.holder()) holder_type(v_h.value_ptr()); - v_h.set_holder_constructed(); - } - } - - static void init_holder_from_existing(const detail::value_and_holder &v_h, - const holder_type *holder_ptr, std::true_type /*is_copy_constructible*/) { - new (&v_h.holder()) holder_type(*reinterpret_cast(holder_ptr)); - } - - static void init_holder_from_existing(const detail::value_and_holder &v_h, - const holder_type *holder_ptr, std::false_type /*is_copy_constructible*/) { - new (&v_h.holder()) holder_type(std::move(*const_cast(holder_ptr))); - } - - /// Initialize holder object, variant 2: try to construct from existing holder object, if possible - static void init_holder(detail::instance *inst, detail::value_and_holder &v_h, - const holder_type *holder_ptr, const void * /* dummy -- not enable_shared_from_this) */) { - if (holder_ptr) { - init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible()); - v_h.set_holder_constructed(); - } else if (inst->owned || detail::always_construct_holder::value) { - new (&v_h.holder()) holder_type(v_h.value_ptr()); - v_h.set_holder_constructed(); - } - } - - /// Performs instance initialization including constructing a holder and registering the known - /// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes an - /// optional pointer to an existing holder to use; if not specified and the instance is - /// `.owned`, a new holder will be constructed to manage the value pointer. - static void init_instance(detail::instance *inst, const void *holder_ptr) { - auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); - if (!v_h.instance_registered()) { - register_instance(inst, v_h.value_ptr(), v_h.type); - v_h.set_instance_registered(); - } - init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr()); - } - - /// Deallocates an instance; via holder, if constructed; otherwise via operator delete. - static void dealloc(const detail::value_and_holder &v_h) { - if (v_h.holder_constructed()) - v_h.holder().~holder_type(); - else - detail::call_operator_delete(v_h.value_ptr(), v_h.type->type_size); - } - - static detail::function_record *get_function_record(handle h) { - h = detail::get_function(h); - return h ? (detail::function_record *) reinterpret_borrow(PyCFunction_GET_SELF(h.ptr())) - : nullptr; - } -}; - -/// Binds C++ enumerations and enumeration classes to Python -template class enum_ : public class_ { -public: - using class_::def; - using class_::def_property_readonly_static; - using Scalar = typename std::underlying_type::type; - - template - enum_(const handle &scope, const char *name, const Extra&... extra) - : class_(scope, name, extra...), m_entries(), m_parent(scope) { - - constexpr bool is_arithmetic = detail::any_of...>::value; - - auto m_entries_ptr = m_entries.inc_ref().ptr(); - def("__repr__", [name, m_entries_ptr](Type value) -> pybind11::str { - for (const auto &kv : reinterpret_borrow(m_entries_ptr)) { - if (pybind11::cast(kv.second) == value) - return pybind11::str("{}.{}").format(name, kv.first); - } - return pybind11::str("{}.???").format(name); - }); - def_property_readonly_static("__members__", [m_entries_ptr](object /* self */) { - dict m; - for (const auto &kv : reinterpret_borrow(m_entries_ptr)) - m[kv.first] = kv.second; - return m; - }, return_value_policy::copy); - def("__init__", [](Type& value, Scalar i) { value = (Type)i; }); - def("__int__", [](Type value) { return (Scalar) value; }); - #if PY_MAJOR_VERSION < 3 - def("__long__", [](Type value) { return (Scalar) value; }); - #endif - def("__eq__", [](const Type &value, Type *value2) { return value2 && value == *value2; }); - def("__ne__", [](const Type &value, Type *value2) { return !value2 || value != *value2; }); - if (is_arithmetic) { - def("__lt__", [](const Type &value, Type *value2) { return value2 && value < *value2; }); - def("__gt__", [](const Type &value, Type *value2) { return value2 && value > *value2; }); - def("__le__", [](const Type &value, Type *value2) { return value2 && value <= *value2; }); - def("__ge__", [](const Type &value, Type *value2) { return value2 && value >= *value2; }); - } - if (std::is_convertible::value) { - // Don't provide comparison with the underlying type if the enum isn't convertible, - // i.e. if Type is a scoped enum, mirroring the C++ behaviour. (NB: we explicitly - // convert Type to Scalar below anyway because this needs to compile). - def("__eq__", [](const Type &value, Scalar value2) { return (Scalar) value == value2; }); - def("__ne__", [](const Type &value, Scalar value2) { return (Scalar) value != value2; }); - if (is_arithmetic) { - def("__lt__", [](const Type &value, Scalar value2) { return (Scalar) value < value2; }); - def("__gt__", [](const Type &value, Scalar value2) { return (Scalar) value > value2; }); - def("__le__", [](const Type &value, Scalar value2) { return (Scalar) value <= value2; }); - def("__ge__", [](const Type &value, Scalar value2) { return (Scalar) value >= value2; }); - def("__invert__", [](const Type &value) { return ~((Scalar) value); }); - def("__and__", [](const Type &value, Scalar value2) { return (Scalar) value & value2; }); - def("__or__", [](const Type &value, Scalar value2) { return (Scalar) value | value2; }); - def("__xor__", [](const Type &value, Scalar value2) { return (Scalar) value ^ value2; }); - def("__rand__", [](const Type &value, Scalar value2) { return (Scalar) value & value2; }); - def("__ror__", [](const Type &value, Scalar value2) { return (Scalar) value | value2; }); - def("__rxor__", [](const Type &value, Scalar value2) { return (Scalar) value ^ value2; }); - def("__and__", [](const Type &value, const Type &value2) { return (Scalar) value & (Scalar) value2; }); - def("__or__", [](const Type &value, const Type &value2) { return (Scalar) value | (Scalar) value2; }); - def("__xor__", [](const Type &value, const Type &value2) { return (Scalar) value ^ (Scalar) value2; }); - } - } - def("__hash__", [](const Type &value) { return (Scalar) value; }); - // Pickling and unpickling -- needed for use with the 'multiprocessing' module - def("__getstate__", [](const Type &value) { return pybind11::make_tuple((Scalar) value); }); - def("__setstate__", [](Type &p, tuple t) { new (&p) Type((Type) t[0].cast()); }); - } - - /// Export enumeration entries into the parent scope - enum_& export_values() { - for (const auto &kv : m_entries) - m_parent.attr(kv.first) = kv.second; - return *this; - } - - /// Add an enumeration entry - enum_& value(char const* name, Type value) { - auto v = pybind11::cast(value, return_value_policy::copy); - this->attr(name) = v; - m_entries[pybind11::str(name)] = v; - return *this; - } - -private: - dict m_entries; - handle m_parent; -}; - -NAMESPACE_BEGIN(detail) -template struct init { - template = 0> - static void execute(Class &cl, const Extra&... extra) { - using Base = typename Class::type; - /// Function which calls a specific C++ in-place constructor - cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...); - } - - template ::value, int> = 0> - static void execute(Class &cl, const Extra&... extra) { - using Base = typename Class::type; - using Alias = typename Class::type_alias; - handle cl_type = cl; - cl.def("__init__", [cl_type](handle self_, Args... args) { - if (self_.get_type().is(cl_type)) - new (self_.cast()) Base(args...); - else - new (self_.cast()) Alias(args...); - }, extra...); - } - - template ::value, int> = 0> - static void execute(Class &cl, const Extra&... extra) { - init_alias::execute(cl, extra...); - } -}; -template struct init_alias { - template ::value, int> = 0> - static void execute(Class &cl, const Extra&... extra) { - using Alias = typename Class::type_alias; - cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...); - } -}; - - -inline void keep_alive_impl(handle nurse, handle patient) { - if (!nurse || !patient) - pybind11_fail("Could not activate keep_alive!"); - - if (patient.is_none() || nurse.is_none()) - return; /* Nothing to keep alive or nothing to be kept alive by */ - - auto tinfo = all_type_info(Py_TYPE(nurse.ptr())); - if (!tinfo.empty()) { - /* It's a pybind-registered type, so we can store the patient in the - * internal list. */ - add_patient(nurse.ptr(), patient.ptr()); - } - else { - /* Fall back to clever approach based on weak references taken from - * Boost.Python. This is not used for pybind-registered types because - * the objects can be destroyed out-of-order in a GC pass. */ - cpp_function disable_lifesupport( - [patient](handle weakref) { patient.dec_ref(); weakref.dec_ref(); }); - - weakref wr(nurse, disable_lifesupport); - - patient.inc_ref(); /* reference patient and leak the weak reference */ - (void) wr.release(); - } -} - -PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) { - keep_alive_impl( - Nurse == 0 ? ret : Nurse <= call.args.size() ? call.args[Nurse - 1] : handle(), - Patient == 0 ? ret : Patient <= call.args.size() ? call.args[Patient - 1] : handle() - ); -} - -inline std::pair all_type_info_get_cache(PyTypeObject *type) { - auto res = get_internals().registered_types_py -#ifdef __cpp_lib_unordered_map_try_emplace - .try_emplace(type); -#else - .emplace(type, std::vector()); -#endif - if (res.second) { - // New cache entry created; set up a weak reference to automatically remove it if the type - // gets destroyed: - weakref((PyObject *) type, cpp_function([type](handle wr) { - get_internals().registered_types_py.erase(type); - wr.dec_ref(); - })).release(); - } - - return res; -} - -template -struct iterator_state { - Iterator it; - Sentinel end; - bool first_or_done; -}; - -NAMESPACE_END(detail) - -template detail::init init() { return detail::init(); } -template detail::init_alias init_alias() { return detail::init_alias(); } - -/// Makes a python iterator from a first and past-the-end C++ InputIterator. -template ()), - typename... Extra> -iterator make_iterator(Iterator first, Sentinel last, Extra &&... extra) { - typedef detail::iterator_state state; - - if (!detail::get_type_info(typeid(state), false)) { - class_(handle(), "iterator") - .def("__iter__", [](state &s) -> state& { return s; }) - .def("__next__", [](state &s) -> ValueType { - if (!s.first_or_done) - ++s.it; - else - s.first_or_done = false; - if (s.it == s.end) { - s.first_or_done = true; - throw stop_iteration(); - } - return *s.it; - }, std::forward(extra)..., Policy); - } - - return cast(state{first, last, true}); -} - -/// Makes an python iterator over the keys (`.first`) of a iterator over pairs from a -/// first and past-the-end InputIterator. -template ()).first), - typename... Extra> -iterator make_key_iterator(Iterator first, Sentinel last, Extra &&... extra) { - typedef detail::iterator_state state; - - if (!detail::get_type_info(typeid(state), false)) { - class_(handle(), "iterator") - .def("__iter__", [](state &s) -> state& { return s; }) - .def("__next__", [](state &s) -> KeyType { - if (!s.first_or_done) - ++s.it; - else - s.first_or_done = false; - if (s.it == s.end) { - s.first_or_done = true; - throw stop_iteration(); - } - return (*s.it).first; - }, std::forward(extra)..., Policy); - } - - return cast(state{first, last, true}); -} - -/// Makes an iterator over values of an stl container or other container supporting -/// `std::begin()`/`std::end()` -template iterator make_iterator(Type &value, Extra&&... extra) { - return make_iterator(std::begin(value), std::end(value), extra...); -} - -/// Makes an iterator over the keys (`.first`) of a stl map-like container supporting -/// `std::begin()`/`std::end()` -template iterator make_key_iterator(Type &value, Extra&&... extra) { - return make_key_iterator(std::begin(value), std::end(value), extra...); -} - -template void implicitly_convertible() { - auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * { - if (!detail::make_caster().load(obj, false)) - return nullptr; - tuple args(1); - args[0] = obj; - PyObject *result = PyObject_Call((PyObject *) type, args.ptr(), nullptr); - if (result == nullptr) - PyErr_Clear(); - return result; - }; - - if (auto tinfo = detail::get_type_info(typeid(OutputType))) - tinfo->implicit_conversions.push_back(implicit_caster); - else - pybind11_fail("implicitly_convertible: Unable to find type " + type_id()); -} - -template -void register_exception_translator(ExceptionTranslator&& translator) { - detail::get_internals().registered_exception_translators.push_front( - std::forward(translator)); -} - -/** - * Wrapper to generate a new Python exception type. - * - * This should only be used with PyErr_SetString for now. - * It is not (yet) possible to use as a py::base. - * Template type argument is reserved for future use. - */ -template -class exception : public object { -public: - exception(handle scope, const char *name, PyObject *base = PyExc_Exception) { - std::string full_name = scope.attr("__name__").cast() + - std::string(".") + name; - m_ptr = PyErr_NewException(const_cast(full_name.c_str()), base, NULL); - if (hasattr(scope, name)) - pybind11_fail("Error during initialization: multiple incompatible " - "definitions with name \"" + std::string(name) + "\""); - scope.attr(name) = *this; - } - - // Sets the current python exception to this exception object with the given message - void operator()(const char *message) { - PyErr_SetString(m_ptr, message); - } -}; - -/** - * Registers a Python exception in `m` of the given `name` and installs an exception translator to - * translate the C++ exception to the created Python exception using the exceptions what() method. - * This is intended for simple exception translations; for more complex translation, register the - * exception object and translator directly. - */ -template -exception ®ister_exception(handle scope, - const char *name, - PyObject *base = PyExc_Exception) { - static exception ex(scope, name, base); - register_exception_translator([](std::exception_ptr p) { - if (!p) return; - try { - std::rethrow_exception(p); - } catch (const CppException &e) { - ex(e.what()); - } - }); - return ex; -} - -NAMESPACE_BEGIN(detail) -PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) { - auto strings = tuple(args.size()); - for (size_t i = 0; i < args.size(); ++i) { - strings[i] = str(args[i]); - } - auto sep = kwargs.contains("sep") ? kwargs["sep"] : cast(" "); - auto line = sep.attr("join")(strings); - - object file; - if (kwargs.contains("file")) { - file = kwargs["file"].cast(); - } else { - try { - file = module::import("sys").attr("stdout"); - } catch (const error_already_set &) { - /* If print() is called from code that is executed as - part of garbage collection during interpreter shutdown, - importing 'sys' can fail. Give up rather than crashing the - interpreter in this case. */ - return; - } - } - - auto write = file.attr("write"); - write(line); - write(kwargs.contains("end") ? kwargs["end"] : cast("\n")); - - if (kwargs.contains("flush") && kwargs["flush"].cast()) - file.attr("flush")(); -} -NAMESPACE_END(detail) - -template -void print(Args &&...args) { - auto c = detail::collect_arguments(std::forward(args)...); - detail::print(c.args(), c.kwargs()); -} - -#if defined(WITH_THREAD) && !defined(PYPY_VERSION) - -/* The functions below essentially reproduce the PyGILState_* API using a RAII - * pattern, but there are a few important differences: - * - * 1. When acquiring the GIL from an non-main thread during the finalization - * phase, the GILState API blindly terminates the calling thread, which - * is often not what is wanted. This API does not do this. - * - * 2. The gil_scoped_release function can optionally cut the relationship - * of a PyThreadState and its associated thread, which allows moving it to - * another thread (this is a fairly rare/advanced use case). - * - * 3. The reference count of an acquired thread state can be controlled. This - * can be handy to prevent cases where callbacks issued from an external - * thread would otherwise constantly construct and destroy thread state data - * structures. - * - * See the Python bindings of NanoGUI (http://github.com/wjakob/nanogui) for an - * example which uses features 2 and 3 to migrate the Python thread of - * execution to another thread (to run the event loop on the original thread, - * in this case). - */ - -class gil_scoped_acquire { -public: - PYBIND11_NOINLINE gil_scoped_acquire() { - auto const &internals = detail::get_internals(); - tstate = (PyThreadState *) PyThread_get_key_value(internals.tstate); - - if (!tstate) { - tstate = PyThreadState_New(internals.istate); - #if !defined(NDEBUG) - if (!tstate) - pybind11_fail("scoped_acquire: could not create thread state!"); - #endif - tstate->gilstate_counter = 0; - #if PY_MAJOR_VERSION < 3 - PyThread_delete_key_value(internals.tstate); - #endif - PyThread_set_key_value(internals.tstate, tstate); - } else { - release = detail::get_thread_state_unchecked() != tstate; - } - - if (release) { - /* Work around an annoying assertion in PyThreadState_Swap */ - #if defined(Py_DEBUG) - PyInterpreterState *interp = tstate->interp; - tstate->interp = nullptr; - #endif - PyEval_AcquireThread(tstate); - #if defined(Py_DEBUG) - tstate->interp = interp; - #endif - } - - inc_ref(); - } - - void inc_ref() { - ++tstate->gilstate_counter; - } - - PYBIND11_NOINLINE void dec_ref() { - --tstate->gilstate_counter; - #if !defined(NDEBUG) - if (detail::get_thread_state_unchecked() != tstate) - pybind11_fail("scoped_acquire::dec_ref(): thread state must be current!"); - if (tstate->gilstate_counter < 0) - pybind11_fail("scoped_acquire::dec_ref(): reference count underflow!"); - #endif - if (tstate->gilstate_counter == 0) { - #if !defined(NDEBUG) - if (!release) - pybind11_fail("scoped_acquire::dec_ref(): internal error!"); - #endif - PyThreadState_Clear(tstate); - PyThreadState_DeleteCurrent(); - PyThread_delete_key_value(detail::get_internals().tstate); - release = false; - } - } - - PYBIND11_NOINLINE ~gil_scoped_acquire() { - dec_ref(); - if (release) - PyEval_SaveThread(); - } -private: - PyThreadState *tstate = nullptr; - bool release = true; -}; - -class gil_scoped_release { -public: - explicit gil_scoped_release(bool disassoc = false) : disassoc(disassoc) { - // `get_internals()` must be called here unconditionally in order to initialize - // `internals.tstate` for subsequent `gil_scoped_acquire` calls. Otherwise, an - // initialization race could occur as multiple threads try `gil_scoped_acquire`. - const auto &internals = detail::get_internals(); - tstate = PyEval_SaveThread(); - if (disassoc) { - auto key = internals.tstate; - #if PY_MAJOR_VERSION < 3 - PyThread_delete_key_value(key); - #else - PyThread_set_key_value(key, nullptr); - #endif - } - } - ~gil_scoped_release() { - if (!tstate) - return; - PyEval_RestoreThread(tstate); - if (disassoc) { - auto key = detail::get_internals().tstate; - #if PY_MAJOR_VERSION < 3 - PyThread_delete_key_value(key); - #endif - PyThread_set_key_value(key, tstate); - } - } -private: - PyThreadState *tstate; - bool disassoc; -}; -#elif defined(PYPY_VERSION) -class gil_scoped_acquire { - PyGILState_STATE state; -public: - gil_scoped_acquire() { state = PyGILState_Ensure(); } - ~gil_scoped_acquire() { PyGILState_Release(state); } -}; - -class gil_scoped_release { - PyThreadState *state; -public: - gil_scoped_release() { state = PyEval_SaveThread(); } - ~gil_scoped_release() { PyEval_RestoreThread(state); } -}; -#else -class gil_scoped_acquire { }; -class gil_scoped_release { }; -#endif - -error_already_set::~error_already_set() { - if (type) { - gil_scoped_acquire gil; - type.release().dec_ref(); - value.release().dec_ref(); - trace.release().dec_ref(); - } -} - -inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) { - handle self = detail::get_object_handle(this_ptr, this_type); - if (!self) - return function(); - handle type = self.get_type(); - auto key = std::make_pair(type.ptr(), name); - - /* Cache functions that aren't overloaded in Python to avoid - many costly Python dictionary lookups below */ - auto &cache = detail::get_internals().inactive_overload_cache; - if (cache.find(key) != cache.end()) - return function(); - - function overload = getattr(self, name, function()); - if (overload.is_cpp_function()) { - cache.insert(key); - return function(); - } - - /* Don't call dispatch code if invoked from overridden function. - Unfortunately this doesn't work on PyPy. */ -#if !defined(PYPY_VERSION) - PyFrameObject *frame = PyThreadState_Get()->frame; - if (frame && (std::string) str(frame->f_code->co_name) == name && - frame->f_code->co_argcount > 0) { - PyFrame_FastToLocals(frame); - PyObject *self_caller = PyDict_GetItem( - frame->f_locals, PyTuple_GET_ITEM(frame->f_code->co_varnames, 0)); - if (self_caller == self.ptr()) - return function(); - } -#else - /* PyPy currently doesn't provide a detailed cpyext emulation of - frame objects, so we have to emulate this using Python. This - is going to be slow..*/ - dict d; d["self"] = self; d["name"] = pybind11::str(name); - PyObject *result = PyRun_String( - "import inspect\n" - "frame = inspect.currentframe()\n" - "if frame is not None:\n" - " frame = frame.f_back\n" - " if frame is not None and str(frame.f_code.co_name) == name and " - "frame.f_code.co_argcount > 0:\n" - " self_caller = frame.f_locals[frame.f_code.co_varnames[0]]\n" - " if self_caller == self:\n" - " self = None\n", - Py_file_input, d.ptr(), d.ptr()); - if (result == nullptr) - throw error_already_set(); - if (d["self"].is_none()) - return function(); - Py_DECREF(result); -#endif - - return overload; -} - -template function get_overload(const T *this_ptr, const char *name) { - auto tinfo = detail::get_type_info(typeid(T)); - return tinfo ? get_type_overload(this_ptr, tinfo, name) : function(); -} - -#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) { \ - pybind11::gil_scoped_acquire gil; \ - pybind11::function overload = pybind11::get_overload(static_cast(this), name); \ - if (overload) { \ - auto o = overload(__VA_ARGS__); \ - if (pybind11::detail::cast_is_temporary_value_reference::value) { \ - static pybind11::detail::overload_caster_t caster; \ - return pybind11::detail::cast_ref(std::move(o), caster); \ - } \ - else return pybind11::detail::cast_safe(std::move(o)); \ - } \ - } - -#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \ - PYBIND11_OVERLOAD_INT(ret_type, cname, name, __VA_ARGS__) \ - return cname::fn(__VA_ARGS__) - -#define PYBIND11_OVERLOAD_PURE_NAME(ret_type, cname, name, fn, ...) \ - PYBIND11_OVERLOAD_INT(ret_type, cname, name, __VA_ARGS__) \ - pybind11::pybind11_fail("Tried to call pure virtual function \"" #cname "::" name "\""); - -#define PYBIND11_OVERLOAD(ret_type, cname, fn, ...) \ - PYBIND11_OVERLOAD_NAME(ret_type, cname, #fn, fn, __VA_ARGS__) - -#define PYBIND11_OVERLOAD_PURE(ret_type, cname, fn, ...) \ - PYBIND11_OVERLOAD_PURE_NAME(ret_type, cname, #fn, fn, __VA_ARGS__) - -NAMESPACE_END(pybind11) - -#if defined(_MSC_VER) -# pragma warning(pop) -#elif defined(__INTEL_COMPILER) -/* Leave ignored warnings on */ -#elif defined(__GNUG__) && !defined(__clang__) -# pragma GCC diagnostic pop -#endif diff --git a/ppocr/postprocess/lanms/include/pybind11/pytypes.h b/ppocr/postprocess/lanms/include/pybind11/pytypes.h deleted file mode 100644 index 095d40f1..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/pytypes.h +++ /dev/null @@ -1,1318 +0,0 @@ -/* - pybind11/typeid.h: Convenience wrapper classes for basic Python types - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "common.h" -#include "buffer_info.h" -#include -#include - -NAMESPACE_BEGIN(pybind11) - -/* A few forward declarations */ -class handle; class object; -class str; class iterator; -struct arg; struct arg_v; - -NAMESPACE_BEGIN(detail) -class args_proxy; -inline bool isinstance_generic(handle obj, const std::type_info &tp); - -// Accessor forward declarations -template class accessor; -namespace accessor_policies { - struct obj_attr; - struct str_attr; - struct generic_item; - struct sequence_item; - struct list_item; - struct tuple_item; -} -using obj_attr_accessor = accessor; -using str_attr_accessor = accessor; -using item_accessor = accessor; -using sequence_accessor = accessor; -using list_accessor = accessor; -using tuple_accessor = accessor; - -/// Tag and check to identify a class which implements the Python object API -class pyobject_tag { }; -template using is_pyobject = std::is_base_of>; - -/** \rst - A mixin class which adds common functions to `handle`, `object` and various accessors. - The only requirement for `Derived` is to implement ``PyObject *Derived::ptr() const``. -\endrst */ -template -class object_api : public pyobject_tag { - const Derived &derived() const { return static_cast(*this); } - -public: - /** \rst - Return an iterator equivalent to calling ``iter()`` in Python. The object - must be a collection which supports the iteration protocol. - \endrst */ - iterator begin() const; - /// Return a sentinel which ends iteration. - iterator end() const; - - /** \rst - Return an internal functor to invoke the object's sequence protocol. Casting - the returned ``detail::item_accessor`` instance to a `handle` or `object` - subclass causes a corresponding call to ``__getitem__``. Assigning a `handle` - or `object` subclass causes a call to ``__setitem__``. - \endrst */ - item_accessor operator[](handle key) const; - /// See above (the only difference is that they key is provided as a string literal) - item_accessor operator[](const char *key) const; - - /** \rst - Return an internal functor to access the object's attributes. Casting the - returned ``detail::obj_attr_accessor`` instance to a `handle` or `object` - subclass causes a corresponding call to ``getattr``. Assigning a `handle` - or `object` subclass causes a call to ``setattr``. - \endrst */ - obj_attr_accessor attr(handle key) const; - /// See above (the only difference is that they key is provided as a string literal) - str_attr_accessor attr(const char *key) const; - - /** \rst - Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` - or ``list`` for a function call. Applying another * to the result yields - ** unpacking, e.g. to unpack a dict as function keyword arguments. - See :ref:`calling_python_functions`. - \endrst */ - args_proxy operator*() const; - - /// Check if the given item is contained within this object, i.e. ``item in obj``. - template bool contains(T &&item) const; - - /** \rst - Assuming the Python object is a function or implements the ``__call__`` - protocol, ``operator()`` invokes the underlying function, passing an - arbitrary set of parameters. The result is returned as a `object` and - may need to be converted back into a Python object using `handle::cast()`. - - When some of the arguments cannot be converted to Python objects, the - function will throw a `cast_error` exception. When the Python function - call fails, a `error_already_set` exception is thrown. - \endrst */ - template - object operator()(Args &&...args) const; - template - PYBIND11_DEPRECATED("call(...) was deprecated in favor of operator()(...)") - object call(Args&&... args) const; - - /// Equivalent to ``obj is other`` in Python. - bool is(object_api const& other) const { return derived().ptr() == other.derived().ptr(); } - /// Equivalent to ``obj is None`` in Python. - bool is_none() const { return derived().ptr() == Py_None; } - PYBIND11_DEPRECATED("Use py::str(obj) instead") - pybind11::str str() const; - - /// Get or set the object's docstring, i.e. ``obj.__doc__``. - str_attr_accessor doc() const; - - /// Return the object's current reference count - int ref_count() const { return static_cast(Py_REFCNT(derived().ptr())); } - /// Return a handle to the Python type object underlying the instance - handle get_type() const; -}; - -NAMESPACE_END(detail) - -/** \rst - Holds a reference to a Python object (no reference counting) - - The `handle` class is a thin wrapper around an arbitrary Python object (i.e. a - ``PyObject *`` in Python's C API). It does not perform any automatic reference - counting and merely provides a basic C++ interface to various Python API functions. - - .. seealso:: - The `object` class inherits from `handle` and adds automatic reference - counting features. -\endrst */ -class handle : public detail::object_api { -public: - /// The default constructor creates a handle with a ``nullptr``-valued pointer - handle() = default; - /// Creates a ``handle`` from the given raw Python object pointer - handle(PyObject *ptr) : m_ptr(ptr) { } // Allow implicit conversion from PyObject* - - /// Return the underlying ``PyObject *`` pointer - PyObject *ptr() const { return m_ptr; } - PyObject *&ptr() { return m_ptr; } - - /** \rst - Manually increase the reference count of the Python object. Usually, it is - preferable to use the `object` class which derives from `handle` and calls - this function automatically. Returns a reference to itself. - \endrst */ - const handle& inc_ref() const & { Py_XINCREF(m_ptr); return *this; } - - /** \rst - Manually decrease the reference count of the Python object. Usually, it is - preferable to use the `object` class which derives from `handle` and calls - this function automatically. Returns a reference to itself. - \endrst */ - const handle& dec_ref() const & { Py_XDECREF(m_ptr); return *this; } - - /** \rst - Attempt to cast the Python object into the given C++ type. A `cast_error` - will be throw upon failure. - \endrst */ - template T cast() const; - /// Return ``true`` when the `handle` wraps a valid Python object - explicit operator bool() const { return m_ptr != nullptr; } - /** \rst - Deprecated: Check that the underlying pointers are the same. - Equivalent to ``obj1 is obj2`` in Python. - \endrst */ - PYBIND11_DEPRECATED("Use obj1.is(obj2) instead") - bool operator==(const handle &h) const { return m_ptr == h.m_ptr; } - PYBIND11_DEPRECATED("Use !obj1.is(obj2) instead") - bool operator!=(const handle &h) const { return m_ptr != h.m_ptr; } - PYBIND11_DEPRECATED("Use handle::operator bool() instead") - bool check() const { return m_ptr != nullptr; } -protected: - PyObject *m_ptr = nullptr; -}; - -/** \rst - Holds a reference to a Python object (with reference counting) - - Like `handle`, the `object` class is a thin wrapper around an arbitrary Python - object (i.e. a ``PyObject *`` in Python's C API). In contrast to `handle`, it - optionally increases the object's reference count upon construction, and it - *always* decreases the reference count when the `object` instance goes out of - scope and is destructed. When using `object` instances consistently, it is much - easier to get reference counting right at the first attempt. -\endrst */ -class object : public handle { -public: - object() = default; - PYBIND11_DEPRECATED("Use reinterpret_borrow() or reinterpret_steal()") - object(handle h, bool is_borrowed) : handle(h) { if (is_borrowed) inc_ref(); } - /// Copy constructor; always increases the reference count - object(const object &o) : handle(o) { inc_ref(); } - /// Move constructor; steals the object from ``other`` and preserves its reference count - object(object &&other) noexcept { m_ptr = other.m_ptr; other.m_ptr = nullptr; } - /// Destructor; automatically calls `handle::dec_ref()` - ~object() { dec_ref(); } - - /** \rst - Resets the internal pointer to ``nullptr`` without without decreasing the - object's reference count. The function returns a raw handle to the original - Python object. - \endrst */ - handle release() { - PyObject *tmp = m_ptr; - m_ptr = nullptr; - return handle(tmp); - } - - object& operator=(const object &other) { - other.inc_ref(); - dec_ref(); - m_ptr = other.m_ptr; - return *this; - } - - object& operator=(object &&other) noexcept { - if (this != &other) { - handle temp(m_ptr); - m_ptr = other.m_ptr; - other.m_ptr = nullptr; - temp.dec_ref(); - } - return *this; - } - - // Calling cast() on an object lvalue just copies (via handle::cast) - template T cast() const &; - // Calling on an object rvalue does a move, if needed and/or possible - template T cast() &&; - -protected: - // Tags for choosing constructors from raw PyObject * - struct borrowed_t { }; - struct stolen_t { }; - - template friend T reinterpret_borrow(handle); - template friend T reinterpret_steal(handle); - -public: - // Only accessible from derived classes and the reinterpret_* functions - object(handle h, borrowed_t) : handle(h) { inc_ref(); } - object(handle h, stolen_t) : handle(h) { } -}; - -/** \rst - Declare that a `handle` or ``PyObject *`` is a certain type and borrow the reference. - The target type ``T`` must be `object` or one of its derived classes. The function - doesn't do any conversions or checks. It's up to the user to make sure that the - target type is correct. - - .. code-block:: cpp - - PyObject *p = PyList_GetItem(obj, index); - py::object o = reinterpret_borrow(p); - // or - py::tuple t = reinterpret_borrow(p); // <-- `p` must be already be a `tuple` -\endrst */ -template T reinterpret_borrow(handle h) { return {h, object::borrowed_t{}}; } - -/** \rst - Like `reinterpret_borrow`, but steals the reference. - - .. code-block:: cpp - - PyObject *p = PyObject_Str(obj); - py::str s = reinterpret_steal(p); // <-- `p` must be already be a `str` -\endrst */ -template T reinterpret_steal(handle h) { return {h, object::stolen_t{}}; } - -NAMESPACE_BEGIN(detail) -inline std::string error_string(); -NAMESPACE_END(detail) - -/// Fetch and hold an error which was already set in Python. An instance of this is typically -/// thrown to propagate python-side errors back through C++ which can either be caught manually or -/// else falls back to the function dispatcher (which then raises the captured error back to -/// python). -class error_already_set : public std::runtime_error { -public: - /// Constructs a new exception from the current Python error indicator, if any. The current - /// Python error indicator will be cleared. - error_already_set() : std::runtime_error(detail::error_string()) { - PyErr_Fetch(&type.ptr(), &value.ptr(), &trace.ptr()); - } - - inline ~error_already_set(); - - /// Give the currently-held error back to Python, if any. If there is currently a Python error - /// already set it is cleared first. After this call, the current object no longer stores the - /// error variables (but the `.what()` string is still available). - void restore() { PyErr_Restore(type.release().ptr(), value.release().ptr(), trace.release().ptr()); } - - // Does nothing; provided for backwards compatibility. - PYBIND11_DEPRECATED("Use of error_already_set.clear() is deprecated") - void clear() {} - - /// Check if the currently trapped error type matches the given Python exception class (or a - /// subclass thereof). May also be passed a tuple to search for any exception class matches in - /// the given tuple. - bool matches(handle ex) const { return PyErr_GivenExceptionMatches(ex.ptr(), type.ptr()); } - -private: - object type, value, trace; -}; - -/** \defgroup python_builtins _ - Unless stated otherwise, the following C++ functions behave the same - as their Python counterparts. - */ - -/** \ingroup python_builtins - \rst - Return true if ``obj`` is an instance of ``T``. Type ``T`` must be a subclass of - `object` or a class which was exposed to Python as ``py::class_``. -\endrst */ -template ::value, int> = 0> -bool isinstance(handle obj) { return T::check_(obj); } - -template ::value, int> = 0> -bool isinstance(handle obj) { return detail::isinstance_generic(obj, typeid(T)); } - -template <> inline bool isinstance(handle obj) = delete; -template <> inline bool isinstance(handle obj) { return obj.ptr() != nullptr; } - -/// \ingroup python_builtins -/// Return true if ``obj`` is an instance of the ``type``. -inline bool isinstance(handle obj, handle type) { - const auto result = PyObject_IsInstance(obj.ptr(), type.ptr()); - if (result == -1) - throw error_already_set(); - return result != 0; -} - -/// \addtogroup python_builtins -/// @{ -inline bool hasattr(handle obj, handle name) { - return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1; -} - -inline bool hasattr(handle obj, const char *name) { - return PyObject_HasAttrString(obj.ptr(), name) == 1; -} - -inline object getattr(handle obj, handle name) { - PyObject *result = PyObject_GetAttr(obj.ptr(), name.ptr()); - if (!result) { throw error_already_set(); } - return reinterpret_steal(result); -} - -inline object getattr(handle obj, const char *name) { - PyObject *result = PyObject_GetAttrString(obj.ptr(), name); - if (!result) { throw error_already_set(); } - return reinterpret_steal(result); -} - -inline object getattr(handle obj, handle name, handle default_) { - if (PyObject *result = PyObject_GetAttr(obj.ptr(), name.ptr())) { - return reinterpret_steal(result); - } else { - PyErr_Clear(); - return reinterpret_borrow(default_); - } -} - -inline object getattr(handle obj, const char *name, handle default_) { - if (PyObject *result = PyObject_GetAttrString(obj.ptr(), name)) { - return reinterpret_steal(result); - } else { - PyErr_Clear(); - return reinterpret_borrow(default_); - } -} - -inline void setattr(handle obj, handle name, handle value) { - if (PyObject_SetAttr(obj.ptr(), name.ptr(), value.ptr()) != 0) { throw error_already_set(); } -} - -inline void setattr(handle obj, const char *name, handle value) { - if (PyObject_SetAttrString(obj.ptr(), name, value.ptr()) != 0) { throw error_already_set(); } -} -/// @} python_builtins - -NAMESPACE_BEGIN(detail) -inline handle get_function(handle value) { - if (value) { -#if PY_MAJOR_VERSION >= 3 - if (PyInstanceMethod_Check(value.ptr())) - value = PyInstanceMethod_GET_FUNCTION(value.ptr()); - else -#endif - if (PyMethod_Check(value.ptr())) - value = PyMethod_GET_FUNCTION(value.ptr()); - } - return value; -} - -// Helper aliases/functions to support implicit casting of values given to python accessors/methods. -// When given a pyobject, this simply returns the pyobject as-is; for other C++ type, the value goes -// through pybind11::cast(obj) to convert it to an `object`. -template ::value, int> = 0> -auto object_or_cast(T &&o) -> decltype(std::forward(o)) { return std::forward(o); } -// The following casting version is implemented in cast.h: -template ::value, int> = 0> -object object_or_cast(T &&o); -// Match a PyObject*, which we want to convert directly to handle via its converting constructor -inline handle object_or_cast(PyObject *ptr) { return ptr; } - - -template -class accessor : public object_api> { - using key_type = typename Policy::key_type; - -public: - accessor(handle obj, key_type key) : obj(obj), key(std::move(key)) { } - accessor(const accessor &a) = default; - accessor(accessor &&a) = default; - - // accessor overload required to override default assignment operator (templates are not allowed - // to replace default compiler-generated assignments). - void operator=(const accessor &a) && { std::move(*this).operator=(handle(a)); } - void operator=(const accessor &a) & { operator=(handle(a)); } - - template void operator=(T &&value) && { - Policy::set(obj, key, object_or_cast(std::forward(value))); - } - template void operator=(T &&value) & { - get_cache() = reinterpret_borrow(object_or_cast(std::forward(value))); - } - - template - PYBIND11_DEPRECATED("Use of obj.attr(...) as bool is deprecated in favor of pybind11::hasattr(obj, ...)") - explicit operator enable_if_t::value || - std::is_same::value, bool>() const { - return hasattr(obj, key); - } - template - PYBIND11_DEPRECATED("Use of obj[key] as bool is deprecated in favor of obj.contains(key)") - explicit operator enable_if_t::value, bool>() const { - return obj.contains(key); - } - - operator object() const { return get_cache(); } - PyObject *ptr() const { return get_cache().ptr(); } - template T cast() const { return get_cache().template cast(); } - -private: - object &get_cache() const { - if (!cache) { cache = Policy::get(obj, key); } - return cache; - } - -private: - handle obj; - key_type key; - mutable object cache; -}; - -NAMESPACE_BEGIN(accessor_policies) -struct obj_attr { - using key_type = object; - static object get(handle obj, handle key) { return getattr(obj, key); } - static void set(handle obj, handle key, handle val) { setattr(obj, key, val); } -}; - -struct str_attr { - using key_type = const char *; - static object get(handle obj, const char *key) { return getattr(obj, key); } - static void set(handle obj, const char *key, handle val) { setattr(obj, key, val); } -}; - -struct generic_item { - using key_type = object; - - static object get(handle obj, handle key) { - PyObject *result = PyObject_GetItem(obj.ptr(), key.ptr()); - if (!result) { throw error_already_set(); } - return reinterpret_steal(result); - } - - static void set(handle obj, handle key, handle val) { - if (PyObject_SetItem(obj.ptr(), key.ptr(), val.ptr()) != 0) { throw error_already_set(); } - } -}; - -struct sequence_item { - using key_type = size_t; - - static object get(handle obj, size_t index) { - PyObject *result = PySequence_GetItem(obj.ptr(), static_cast(index)); - if (!result) { throw error_already_set(); } - return reinterpret_steal(result); - } - - static void set(handle obj, size_t index, handle val) { - // PySequence_SetItem does not steal a reference to 'val' - if (PySequence_SetItem(obj.ptr(), static_cast(index), val.ptr()) != 0) { - throw error_already_set(); - } - } -}; - -struct list_item { - using key_type = size_t; - - static object get(handle obj, size_t index) { - PyObject *result = PyList_GetItem(obj.ptr(), static_cast(index)); - if (!result) { throw error_already_set(); } - return reinterpret_borrow(result); - } - - static void set(handle obj, size_t index, handle val) { - // PyList_SetItem steals a reference to 'val' - if (PyList_SetItem(obj.ptr(), static_cast(index), val.inc_ref().ptr()) != 0) { - throw error_already_set(); - } - } -}; - -struct tuple_item { - using key_type = size_t; - - static object get(handle obj, size_t index) { - PyObject *result = PyTuple_GetItem(obj.ptr(), static_cast(index)); - if (!result) { throw error_already_set(); } - return reinterpret_borrow(result); - } - - static void set(handle obj, size_t index, handle val) { - // PyTuple_SetItem steals a reference to 'val' - if (PyTuple_SetItem(obj.ptr(), static_cast(index), val.inc_ref().ptr()) != 0) { - throw error_already_set(); - } - } -}; -NAMESPACE_END(accessor_policies) - -/// STL iterator template used for tuple, list, sequence and dict -template -class generic_iterator : public Policy { - using It = generic_iterator; - -public: - using difference_type = ssize_t; - using iterator_category = typename Policy::iterator_category; - using value_type = typename Policy::value_type; - using reference = typename Policy::reference; - using pointer = typename Policy::pointer; - - generic_iterator() = default; - generic_iterator(handle seq, ssize_t index) : Policy(seq, index) { } - - reference operator*() const { return Policy::dereference(); } - reference operator[](difference_type n) const { return *(*this + n); } - pointer operator->() const { return **this; } - - It &operator++() { Policy::increment(); return *this; } - It operator++(int) { auto copy = *this; Policy::increment(); return copy; } - It &operator--() { Policy::decrement(); return *this; } - It operator--(int) { auto copy = *this; Policy::decrement(); return copy; } - It &operator+=(difference_type n) { Policy::advance(n); return *this; } - It &operator-=(difference_type n) { Policy::advance(-n); return *this; } - - friend It operator+(const It &a, difference_type n) { auto copy = a; return copy += n; } - friend It operator+(difference_type n, const It &b) { return b + n; } - friend It operator-(const It &a, difference_type n) { auto copy = a; return copy -= n; } - friend difference_type operator-(const It &a, const It &b) { return a.distance_to(b); } - - friend bool operator==(const It &a, const It &b) { return a.equal(b); } - friend bool operator!=(const It &a, const It &b) { return !(a == b); } - friend bool operator< (const It &a, const It &b) { return b - a > 0; } - friend bool operator> (const It &a, const It &b) { return b < a; } - friend bool operator>=(const It &a, const It &b) { return !(a < b); } - friend bool operator<=(const It &a, const It &b) { return !(a > b); } -}; - -NAMESPACE_BEGIN(iterator_policies) -/// Quick proxy class needed to implement ``operator->`` for iterators which can't return pointers -template -struct arrow_proxy { - T value; - - arrow_proxy(T &&value) : value(std::move(value)) { } - T *operator->() const { return &value; } -}; - -/// Lightweight iterator policy using just a simple pointer: see ``PySequence_Fast_ITEMS`` -class sequence_fast_readonly { -protected: - using iterator_category = std::random_access_iterator_tag; - using value_type = handle; - using reference = const handle; - using pointer = arrow_proxy; - - sequence_fast_readonly(handle obj, ssize_t n) : ptr(PySequence_Fast_ITEMS(obj.ptr()) + n) { } - - reference dereference() const { return *ptr; } - void increment() { ++ptr; } - void decrement() { --ptr; } - void advance(ssize_t n) { ptr += n; } - bool equal(const sequence_fast_readonly &b) const { return ptr == b.ptr; } - ssize_t distance_to(const sequence_fast_readonly &b) const { return ptr - b.ptr; } - -private: - PyObject **ptr; -}; - -/// Full read and write access using the sequence protocol: see ``detail::sequence_accessor`` -class sequence_slow_readwrite { -protected: - using iterator_category = std::random_access_iterator_tag; - using value_type = object; - using reference = sequence_accessor; - using pointer = arrow_proxy; - - sequence_slow_readwrite(handle obj, ssize_t index) : obj(obj), index(index) { } - - reference dereference() const { return {obj, static_cast(index)}; } - void increment() { ++index; } - void decrement() { --index; } - void advance(ssize_t n) { index += n; } - bool equal(const sequence_slow_readwrite &b) const { return index == b.index; } - ssize_t distance_to(const sequence_slow_readwrite &b) const { return index - b.index; } - -private: - handle obj; - ssize_t index; -}; - -/// Python's dictionary protocol permits this to be a forward iterator -class dict_readonly { -protected: - using iterator_category = std::forward_iterator_tag; - using value_type = std::pair; - using reference = const value_type; - using pointer = arrow_proxy; - - dict_readonly() = default; - dict_readonly(handle obj, ssize_t pos) : obj(obj), pos(pos) { increment(); } - - reference dereference() const { return {key, value}; } - void increment() { if (!PyDict_Next(obj.ptr(), &pos, &key, &value)) { pos = -1; } } - bool equal(const dict_readonly &b) const { return pos == b.pos; } - -private: - handle obj; - PyObject *key, *value; - ssize_t pos = -1; -}; -NAMESPACE_END(iterator_policies) - -#if !defined(PYPY_VERSION) -using tuple_iterator = generic_iterator; -using list_iterator = generic_iterator; -#else -using tuple_iterator = generic_iterator; -using list_iterator = generic_iterator; -#endif - -using sequence_iterator = generic_iterator; -using dict_iterator = generic_iterator; - -inline bool PyIterable_Check(PyObject *obj) { - PyObject *iter = PyObject_GetIter(obj); - if (iter) { - Py_DECREF(iter); - return true; - } else { - PyErr_Clear(); - return false; - } -} - -inline bool PyNone_Check(PyObject *o) { return o == Py_None; } - -inline bool PyUnicode_Check_Permissive(PyObject *o) { return PyUnicode_Check(o) || PYBIND11_BYTES_CHECK(o); } - -class kwargs_proxy : public handle { -public: - explicit kwargs_proxy(handle h) : handle(h) { } -}; - -class args_proxy : public handle { -public: - explicit args_proxy(handle h) : handle(h) { } - kwargs_proxy operator*() const { return kwargs_proxy(*this); } -}; - -/// Python argument categories (using PEP 448 terms) -template using is_keyword = std::is_base_of; -template using is_s_unpacking = std::is_same; // * unpacking -template using is_ds_unpacking = std::is_same; // ** unpacking -template using is_positional = satisfies_none_of; -template using is_keyword_or_ds = satisfies_any_of; - -// Call argument collector forward declarations -template -class simple_collector; -template -class unpacking_collector; - -NAMESPACE_END(detail) - -// TODO: After the deprecated constructors are removed, this macro can be simplified by -// inheriting ctors: `using Parent::Parent`. It's not an option right now because -// the `using` statement triggers the parent deprecation warning even if the ctor -// isn't even used. -#define PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ - public: \ - PYBIND11_DEPRECATED("Use reinterpret_borrow<"#Name">() or reinterpret_steal<"#Name">()") \ - Name(handle h, bool is_borrowed) : Parent(is_borrowed ? Parent(h, borrowed_t{}) : Parent(h, stolen_t{})) { } \ - Name(handle h, borrowed_t) : Parent(h, borrowed_t{}) { } \ - Name(handle h, stolen_t) : Parent(h, stolen_t{}) { } \ - PYBIND11_DEPRECATED("Use py::isinstance(obj) instead") \ - bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \ - static bool check_(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); } - -#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ - PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ - /* This is deliberately not 'explicit' to allow implicit conversion from object: */ \ - Name(const object &o) : Parent(ConvertFun(o.ptr()), stolen_t{}) { if (!m_ptr) throw error_already_set(); } - -#define PYBIND11_OBJECT(Name, Parent, CheckFun) \ - PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ - /* This is deliberately not 'explicit' to allow implicit conversion from object: */ \ - Name(const object &o) : Parent(o) { } \ - Name(object &&o) : Parent(std::move(o)) { } - -#define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \ - PYBIND11_OBJECT(Name, Parent, CheckFun) \ - Name() : Parent() { } - -/// \addtogroup pytypes -/// @{ - -/** \rst - Wraps a Python iterator so that it can also be used as a C++ input iterator - - Caveat: copying an iterator does not (and cannot) clone the internal - state of the Python iterable. This also applies to the post-increment - operator. This iterator should only be used to retrieve the current - value using ``operator*()``. -\endrst */ -class iterator : public object { -public: - using iterator_category = std::input_iterator_tag; - using difference_type = ssize_t; - using value_type = handle; - using reference = const handle; - using pointer = const handle *; - - PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check) - - iterator& operator++() { - advance(); - return *this; - } - - iterator operator++(int) { - auto rv = *this; - advance(); - return rv; - } - - reference operator*() const { - if (m_ptr && !value.ptr()) { - auto& self = const_cast(*this); - self.advance(); - } - return value; - } - - pointer operator->() const { operator*(); return &value; } - - /** \rst - The value which marks the end of the iteration. ``it == iterator::sentinel()`` - is equivalent to catching ``StopIteration`` in Python. - - .. code-block:: cpp - - void foo(py::iterator it) { - while (it != py::iterator::sentinel()) { - // use `*it` - ++it; - } - } - \endrst */ - static iterator sentinel() { return {}; } - - friend bool operator==(const iterator &a, const iterator &b) { return a->ptr() == b->ptr(); } - friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); } - -private: - void advance() { - value = reinterpret_steal(PyIter_Next(m_ptr)); - if (PyErr_Occurred()) { throw error_already_set(); } - } - -private: - object value = {}; -}; - -class iterable : public object { -public: - PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check) -}; - -class bytes; - -class str : public object { -public: - PYBIND11_OBJECT_CVT(str, object, detail::PyUnicode_Check_Permissive, raw_str) - - str(const char *c, size_t n) - : object(PyUnicode_FromStringAndSize(c, (ssize_t) n), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate string object!"); - } - - // 'explicit' is explicitly omitted from the following constructors to allow implicit conversion to py::str from C++ string-like objects - str(const char *c = "") - : object(PyUnicode_FromString(c), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate string object!"); - } - - str(const std::string &s) : str(s.data(), s.size()) { } - - explicit str(const bytes &b); - - /** \rst - Return a string representation of the object. This is analogous to - the ``str()`` function in Python. - \endrst */ - explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) { } - - operator std::string() const { - object temp = *this; - if (PyUnicode_Check(m_ptr)) { - temp = reinterpret_steal(PyUnicode_AsUTF8String(m_ptr)); - if (!temp) - pybind11_fail("Unable to extract string contents! (encoding issue)"); - } - char *buffer; - ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(temp.ptr(), &buffer, &length)) - pybind11_fail("Unable to extract string contents! (invalid type)"); - return std::string(buffer, (size_t) length); - } - - template - str format(Args &&...args) const { - return attr("format")(std::forward(args)...); - } - -private: - /// Return string representation -- always returns a new reference, even if already a str - static PyObject *raw_str(PyObject *op) { - PyObject *str_value = PyObject_Str(op); -#if PY_MAJOR_VERSION < 3 - if (!str_value) throw error_already_set(); - PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); - Py_XDECREF(str_value); str_value = unicode; -#endif - return str_value; - } -}; -/// @} pytypes - -inline namespace literals { -/** \rst - String literal version of `str` - \endrst */ -inline str operator"" _s(const char *s, size_t size) { return {s, size}; } -} - -/// \addtogroup pytypes -/// @{ -class bytes : public object { -public: - PYBIND11_OBJECT(bytes, object, PYBIND11_BYTES_CHECK) - - // Allow implicit conversion: - bytes(const char *c = "") - : object(PYBIND11_BYTES_FROM_STRING(c), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate bytes object!"); - } - - bytes(const char *c, size_t n) - : object(PYBIND11_BYTES_FROM_STRING_AND_SIZE(c, (ssize_t) n), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate bytes object!"); - } - - // Allow implicit conversion: - bytes(const std::string &s) : bytes(s.data(), s.size()) { } - - explicit bytes(const pybind11::str &s); - - operator std::string() const { - char *buffer; - ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(m_ptr, &buffer, &length)) - pybind11_fail("Unable to extract bytes contents!"); - return std::string(buffer, (size_t) length); - } -}; - -inline bytes::bytes(const pybind11::str &s) { - object temp = s; - if (PyUnicode_Check(s.ptr())) { - temp = reinterpret_steal(PyUnicode_AsUTF8String(s.ptr())); - if (!temp) - pybind11_fail("Unable to extract string contents! (encoding issue)"); - } - char *buffer; - ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(temp.ptr(), &buffer, &length)) - pybind11_fail("Unable to extract string contents! (invalid type)"); - auto obj = reinterpret_steal(PYBIND11_BYTES_FROM_STRING_AND_SIZE(buffer, length)); - if (!obj) - pybind11_fail("Could not allocate bytes object!"); - m_ptr = obj.release().ptr(); -} - -inline str::str(const bytes& b) { - char *buffer; - ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(b.ptr(), &buffer, &length)) - pybind11_fail("Unable to extract bytes contents!"); - auto obj = reinterpret_steal(PyUnicode_FromStringAndSize(buffer, (ssize_t) length)); - if (!obj) - pybind11_fail("Could not allocate string object!"); - m_ptr = obj.release().ptr(); -} - -class none : public object { -public: - PYBIND11_OBJECT(none, object, detail::PyNone_Check) - none() : object(Py_None, borrowed_t{}) { } -}; - -class bool_ : public object { -public: - PYBIND11_OBJECT_CVT(bool_, object, PyBool_Check, raw_bool) - bool_() : object(Py_False, borrowed_t{}) { } - // Allow implicit conversion from and to `bool`: - bool_(bool value) : object(value ? Py_True : Py_False, borrowed_t{}) { } - operator bool() const { return m_ptr && PyLong_AsLong(m_ptr) != 0; } - -private: - /// Return the truth value of an object -- always returns a new reference - static PyObject *raw_bool(PyObject *op) { - const auto value = PyObject_IsTrue(op); - if (value == -1) return nullptr; - return handle(value ? Py_True : Py_False).inc_ref().ptr(); - } -}; - -NAMESPACE_BEGIN(detail) -// Converts a value to the given unsigned type. If an error occurs, you get back (Unsigned) -1; -// otherwise you get back the unsigned long or unsigned long long value cast to (Unsigned). -// (The distinction is critically important when casting a returned -1 error value to some other -// unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes). -template -Unsigned as_unsigned(PyObject *o) { - if (sizeof(Unsigned) <= sizeof(unsigned long) -#if PY_VERSION_HEX < 0x03000000 - || PyInt_Check(o) -#endif - ) { - unsigned long v = PyLong_AsUnsignedLong(o); - return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; - } - else { - unsigned long long v = PyLong_AsUnsignedLongLong(o); - return v == (unsigned long long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v; - } -} -NAMESPACE_END(detail) - -class int_ : public object { -public: - PYBIND11_OBJECT_CVT(int_, object, PYBIND11_LONG_CHECK, PyNumber_Long) - int_() : object(PyLong_FromLong(0), stolen_t{}) { } - // Allow implicit conversion from C++ integral types: - template ::value, int> = 0> - int_(T value) { - if (sizeof(T) <= sizeof(long)) { - if (std::is_signed::value) - m_ptr = PyLong_FromLong((long) value); - else - m_ptr = PyLong_FromUnsignedLong((unsigned long) value); - } else { - if (std::is_signed::value) - m_ptr = PyLong_FromLongLong((long long) value); - else - m_ptr = PyLong_FromUnsignedLongLong((unsigned long long) value); - } - if (!m_ptr) pybind11_fail("Could not allocate int object!"); - } - - template ::value, int> = 0> - operator T() const { - return std::is_unsigned::value - ? detail::as_unsigned(m_ptr) - : sizeof(T) <= sizeof(long) - ? (T) PyLong_AsLong(m_ptr) - : (T) PYBIND11_LONG_AS_LONGLONG(m_ptr); - } -}; - -class float_ : public object { -public: - PYBIND11_OBJECT_CVT(float_, object, PyFloat_Check, PyNumber_Float) - // Allow implicit conversion from float/double: - float_(float value) : object(PyFloat_FromDouble((double) value), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate float object!"); - } - float_(double value = .0) : object(PyFloat_FromDouble((double) value), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate float object!"); - } - operator float() const { return (float) PyFloat_AsDouble(m_ptr); } - operator double() const { return (double) PyFloat_AsDouble(m_ptr); } -}; - -class weakref : public object { -public: - PYBIND11_OBJECT_DEFAULT(weakref, object, PyWeakref_Check) - explicit weakref(handle obj, handle callback = {}) - : object(PyWeakref_NewRef(obj.ptr(), callback.ptr()), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate weak reference!"); - } -}; - -class slice : public object { -public: - PYBIND11_OBJECT_DEFAULT(slice, object, PySlice_Check) - slice(ssize_t start_, ssize_t stop_, ssize_t step_) { - int_ start(start_), stop(stop_), step(step_); - m_ptr = PySlice_New(start.ptr(), stop.ptr(), step.ptr()); - if (!m_ptr) pybind11_fail("Could not allocate slice object!"); - } - bool compute(size_t length, size_t *start, size_t *stop, size_t *step, - size_t *slicelength) const { - return PySlice_GetIndicesEx((PYBIND11_SLICE_OBJECT *) m_ptr, - (ssize_t) length, (ssize_t *) start, - (ssize_t *) stop, (ssize_t *) step, - (ssize_t *) slicelength) == 0; - } -}; - -class capsule : public object { -public: - PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) - PYBIND11_DEPRECATED("Use reinterpret_borrow() or reinterpret_steal()") - capsule(PyObject *ptr, bool is_borrowed) : object(is_borrowed ? object(ptr, borrowed_t{}) : object(ptr, stolen_t{})) { } - - explicit capsule(const void *value, const char *name = nullptr, void (*destructor)(PyObject *) = nullptr) - : object(PyCapsule_New(const_cast(value), name, destructor), stolen_t{}) { - if (!m_ptr) - pybind11_fail("Could not allocate capsule object!"); - } - - PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input") - capsule(const void *value, void (*destruct)(PyObject *)) - : object(PyCapsule_New(const_cast(value), nullptr, destruct), stolen_t{}) { - if (!m_ptr) - pybind11_fail("Could not allocate capsule object!"); - } - - capsule(const void *value, void (*destructor)(void *)) { - m_ptr = PyCapsule_New(const_cast(value), nullptr, [](PyObject *o) { - auto destructor = reinterpret_cast(PyCapsule_GetContext(o)); - void *ptr = PyCapsule_GetPointer(o, nullptr); - destructor(ptr); - }); - - if (!m_ptr) - pybind11_fail("Could not allocate capsule object!"); - - if (PyCapsule_SetContext(m_ptr, (void *) destructor) != 0) - pybind11_fail("Could not set capsule context!"); - } - - capsule(void (*destructor)()) { - m_ptr = PyCapsule_New(reinterpret_cast(destructor), nullptr, [](PyObject *o) { - auto destructor = reinterpret_cast(PyCapsule_GetPointer(o, nullptr)); - destructor(); - }); - - if (!m_ptr) - pybind11_fail("Could not allocate capsule object!"); - } - - template operator T *() const { - auto name = this->name(); - T * result = static_cast(PyCapsule_GetPointer(m_ptr, name)); - if (!result) pybind11_fail("Unable to extract capsule contents!"); - return result; - } - - const char *name() const { return PyCapsule_GetName(m_ptr); } -}; - -class tuple : public object { -public: - PYBIND11_OBJECT_CVT(tuple, object, PyTuple_Check, PySequence_Tuple) - explicit tuple(size_t size = 0) : object(PyTuple_New((ssize_t) size), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate tuple object!"); - } - size_t size() const { return (size_t) PyTuple_Size(m_ptr); } - detail::tuple_accessor operator[](size_t index) const { return {*this, index}; } - detail::tuple_iterator begin() const { return {*this, 0}; } - detail::tuple_iterator end() const { return {*this, PyTuple_GET_SIZE(m_ptr)}; } -}; - -class dict : public object { -public: - PYBIND11_OBJECT_CVT(dict, object, PyDict_Check, raw_dict) - dict() : object(PyDict_New(), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate dict object!"); - } - template ...>::value>, - // MSVC workaround: it can't compile an out-of-line definition, so defer the collector - typename collector = detail::deferred_t, Args...>> - explicit dict(Args &&...args) : dict(collector(std::forward(args)...).kwargs()) { } - - size_t size() const { return (size_t) PyDict_Size(m_ptr); } - detail::dict_iterator begin() const { return {*this, 0}; } - detail::dict_iterator end() const { return {}; } - void clear() const { PyDict_Clear(ptr()); } - bool contains(handle key) const { return PyDict_Contains(ptr(), key.ptr()) == 1; } - bool contains(const char *key) const { return PyDict_Contains(ptr(), pybind11::str(key).ptr()) == 1; } - -private: - /// Call the `dict` Python type -- always returns a new reference - static PyObject *raw_dict(PyObject *op) { - if (PyDict_Check(op)) - return handle(op).inc_ref().ptr(); - return PyObject_CallFunctionObjArgs((PyObject *) &PyDict_Type, op, nullptr); - } -}; - -class sequence : public object { -public: - PYBIND11_OBJECT_DEFAULT(sequence, object, PySequence_Check) - size_t size() const { return (size_t) PySequence_Size(m_ptr); } - detail::sequence_accessor operator[](size_t index) const { return {*this, index}; } - detail::sequence_iterator begin() const { return {*this, 0}; } - detail::sequence_iterator end() const { return {*this, PySequence_Size(m_ptr)}; } -}; - -class list : public object { -public: - PYBIND11_OBJECT_CVT(list, object, PyList_Check, PySequence_List) - explicit list(size_t size = 0) : object(PyList_New((ssize_t) size), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate list object!"); - } - size_t size() const { return (size_t) PyList_Size(m_ptr); } - detail::list_accessor operator[](size_t index) const { return {*this, index}; } - detail::list_iterator begin() const { return {*this, 0}; } - detail::list_iterator end() const { return {*this, PyList_GET_SIZE(m_ptr)}; } - template void append(T &&val) const { - PyList_Append(m_ptr, detail::object_or_cast(std::forward(val)).ptr()); - } -}; - -class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) }; -class kwargs : public dict { PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check) }; - -class set : public object { -public: - PYBIND11_OBJECT_CVT(set, object, PySet_Check, PySet_New) - set() : object(PySet_New(nullptr), stolen_t{}) { - if (!m_ptr) pybind11_fail("Could not allocate set object!"); - } - size_t size() const { return (size_t) PySet_Size(m_ptr); } - template bool add(T &&val) const { - return PySet_Add(m_ptr, detail::object_or_cast(std::forward(val)).ptr()) == 0; - } - void clear() const { PySet_Clear(m_ptr); } -}; - -class function : public object { -public: - PYBIND11_OBJECT_DEFAULT(function, object, PyCallable_Check) - handle cpp_function() const { - handle fun = detail::get_function(m_ptr); - if (fun && PyCFunction_Check(fun.ptr())) - return fun; - return handle(); - } - bool is_cpp_function() const { return (bool) cpp_function(); } -}; - -class buffer : public object { -public: - PYBIND11_OBJECT_DEFAULT(buffer, object, PyObject_CheckBuffer) - - buffer_info request(bool writable = false) { - int flags = PyBUF_STRIDES | PyBUF_FORMAT; - if (writable) flags |= PyBUF_WRITABLE; - Py_buffer *view = new Py_buffer(); - if (PyObject_GetBuffer(m_ptr, view, flags) != 0) { - delete view; - throw error_already_set(); - } - return buffer_info(view); - } -}; - -class memoryview : public object { -public: - explicit memoryview(const buffer_info& info) { - static Py_buffer buf { }; - // Py_buffer uses signed sizes, strides and shape!.. - static std::vector py_strides { }; - static std::vector py_shape { }; - buf.buf = info.ptr; - buf.itemsize = info.itemsize; - buf.format = const_cast(info.format.c_str()); - buf.ndim = (int) info.ndim; - buf.len = info.size; - py_strides.clear(); - py_shape.clear(); - for (size_t i = 0; i < (size_t) info.ndim; ++i) { - py_strides.push_back(info.strides[i]); - py_shape.push_back(info.shape[i]); - } - buf.strides = py_strides.data(); - buf.shape = py_shape.data(); - buf.suboffsets = nullptr; - buf.readonly = false; - buf.internal = nullptr; - - m_ptr = PyMemoryView_FromBuffer(&buf); - if (!m_ptr) - pybind11_fail("Unable to create memoryview from buffer descriptor"); - } - - PYBIND11_OBJECT_CVT(memoryview, object, PyMemoryView_Check, PyMemoryView_FromObject) -}; -/// @} pytypes - -/// \addtogroup python_builtins -/// @{ -inline size_t len(handle h) { - ssize_t result = PyObject_Length(h.ptr()); - if (result < 0) - pybind11_fail("Unable to compute length of object"); - return (size_t) result; -} - -inline str repr(handle h) { - PyObject *str_value = PyObject_Repr(h.ptr()); - if (!str_value) throw error_already_set(); -#if PY_MAJOR_VERSION < 3 - PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); - Py_XDECREF(str_value); str_value = unicode; - if (!str_value) throw error_already_set(); -#endif - return reinterpret_steal(str_value); -} - -inline iterator iter(handle obj) { - PyObject *result = PyObject_GetIter(obj.ptr()); - if (!result) { throw error_already_set(); } - return reinterpret_steal(result); -} -/// @} python_builtins - -NAMESPACE_BEGIN(detail) -template iterator object_api::begin() const { return iter(derived()); } -template iterator object_api::end() const { return iterator::sentinel(); } -template item_accessor object_api::operator[](handle key) const { - return {derived(), reinterpret_borrow(key)}; -} -template item_accessor object_api::operator[](const char *key) const { - return {derived(), pybind11::str(key)}; -} -template obj_attr_accessor object_api::attr(handle key) const { - return {derived(), reinterpret_borrow(key)}; -} -template str_attr_accessor object_api::attr(const char *key) const { - return {derived(), key}; -} -template args_proxy object_api::operator*() const { - return args_proxy(derived().ptr()); -} -template template bool object_api::contains(T &&item) const { - return attr("__contains__")(std::forward(item)).template cast(); -} - -template -pybind11::str object_api::str() const { return pybind11::str(derived()); } - -template -str_attr_accessor object_api::doc() const { return attr("__doc__"); } - -template -handle object_api::get_type() const { return (PyObject *) Py_TYPE(derived().ptr()); } - -NAMESPACE_END(detail) -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/stl.h b/ppocr/postprocess/lanms/include/pybind11/stl.h deleted file mode 100644 index d07a81f9..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/stl.h +++ /dev/null @@ -1,367 +0,0 @@ -/* - pybind11/stl.h: Transparent conversion for STL data types - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "pybind11.h" -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant -#endif - -#ifdef __has_include -// std::optional (but including it in c++14 mode isn't allowed) -# if defined(PYBIND11_CPP17) && __has_include() -# include -# define PYBIND11_HAS_OPTIONAL 1 -# endif -// std::experimental::optional (but not allowed in c++11 mode) -# if defined(PYBIND11_CPP14) && __has_include() -# include -# define PYBIND11_HAS_EXP_OPTIONAL 1 -# endif -// std::variant -# if defined(PYBIND11_CPP17) && __has_include() -# include -# define PYBIND11_HAS_VARIANT 1 -# endif -#elif defined(_MSC_VER) && defined(PYBIND11_CPP17) -# include -# include -# define PYBIND11_HAS_OPTIONAL 1 -# define PYBIND11_HAS_VARIANT 1 -#endif - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for -/// forwarding a container element). Typically used indirect via forwarded_type(), below. -template -using forwarded_type = conditional_t< - std::is_lvalue_reference::value, remove_reference_t &, remove_reference_t &&>; - -/// Forwards a value U as rvalue or lvalue according to whether T is rvalue or lvalue; typically -/// used for forwarding a container's elements. -template -forwarded_type forward_like(U &&u) { - return std::forward>(std::forward(u)); -} - -template struct set_caster { - using type = Type; - using key_conv = make_caster; - - bool load(handle src, bool convert) { - if (!isinstance(src)) - return false; - auto s = reinterpret_borrow(src); - value.clear(); - for (auto entry : s) { - key_conv conv; - if (!conv.load(entry, convert)) - return false; - value.insert(cast_op(std::move(conv))); - } - return true; - } - - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - pybind11::set s; - for (auto &value: src) { - auto value_ = reinterpret_steal(key_conv::cast(forward_like(value), policy, parent)); - if (!value_ || !s.add(value_)) - return handle(); - } - return s.release(); - } - - PYBIND11_TYPE_CASTER(type, _("Set[") + key_conv::name() + _("]")); -}; - -template struct map_caster { - using key_conv = make_caster; - using value_conv = make_caster; - - bool load(handle src, bool convert) { - if (!isinstance(src)) - return false; - auto d = reinterpret_borrow(src); - value.clear(); - for (auto it : d) { - key_conv kconv; - value_conv vconv; - if (!kconv.load(it.first.ptr(), convert) || - !vconv.load(it.second.ptr(), convert)) - return false; - value.emplace(cast_op(std::move(kconv)), cast_op(std::move(vconv))); - } - return true; - } - - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - dict d; - for (auto &kv: src) { - auto key = reinterpret_steal(key_conv::cast(forward_like(kv.first), policy, parent)); - auto value = reinterpret_steal(value_conv::cast(forward_like(kv.second), policy, parent)); - if (!key || !value) - return handle(); - d[key] = value; - } - return d.release(); - } - - PYBIND11_TYPE_CASTER(Type, _("Dict[") + key_conv::name() + _(", ") + value_conv::name() + _("]")); -}; - -template struct list_caster { - using value_conv = make_caster; - - bool load(handle src, bool convert) { - if (!isinstance(src)) - return false; - auto s = reinterpret_borrow(src); - value.clear(); - reserve_maybe(s, &value); - for (auto it : s) { - value_conv conv; - if (!conv.load(it, convert)) - return false; - value.push_back(cast_op(std::move(conv))); - } - return true; - } - -private: - template ().reserve(0)), void>::value, int> = 0> - void reserve_maybe(sequence s, Type *) { value.reserve(s.size()); } - void reserve_maybe(sequence, void *) { } - -public: - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - list l(src.size()); - size_t index = 0; - for (auto &value: src) { - auto value_ = reinterpret_steal(value_conv::cast(forward_like(value), policy, parent)); - if (!value_) - return handle(); - PyList_SET_ITEM(l.ptr(), (ssize_t) index++, value_.release().ptr()); // steals a reference - } - return l.release(); - } - - PYBIND11_TYPE_CASTER(Type, _("List[") + value_conv::name() + _("]")); -}; - -template struct type_caster> - : list_caster, Type> { }; - -template struct type_caster> - : list_caster, Type> { }; - -template struct array_caster { - using value_conv = make_caster; - -private: - template - bool require_size(enable_if_t size) { - if (value.size() != size) - value.resize(size); - return true; - } - template - bool require_size(enable_if_t size) { - return size == Size; - } - -public: - bool load(handle src, bool convert) { - if (!isinstance(src)) - return false; - auto l = reinterpret_borrow(src); - if (!require_size(l.size())) - return false; - size_t ctr = 0; - for (auto it : l) { - value_conv conv; - if (!conv.load(it, convert)) - return false; - value[ctr++] = cast_op(std::move(conv)); - } - return true; - } - - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - list l(src.size()); - size_t index = 0; - for (auto &value: src) { - auto value_ = reinterpret_steal(value_conv::cast(forward_like(value), policy, parent)); - if (!value_) - return handle(); - PyList_SET_ITEM(l.ptr(), (ssize_t) index++, value_.release().ptr()); // steals a reference - } - return l.release(); - } - - PYBIND11_TYPE_CASTER(ArrayType, _("List[") + value_conv::name() + _(_(""), _("[") + _() + _("]")) + _("]")); -}; - -template struct type_caster> - : array_caster, Type, false, Size> { }; - -template struct type_caster> - : array_caster, Type, true> { }; - -template struct type_caster> - : set_caster, Key> { }; - -template struct type_caster> - : set_caster, Key> { }; - -template struct type_caster> - : map_caster, Key, Value> { }; - -template struct type_caster> - : map_caster, Key, Value> { }; - -// This type caster is intended to be used for std::optional and std::experimental::optional -template struct optional_caster { - using value_conv = make_caster; - - template - static handle cast(T_ &&src, return_value_policy policy, handle parent) { - if (!src) - return none().inc_ref(); - return value_conv::cast(*std::forward(src), policy, parent); - } - - bool load(handle src, bool convert) { - if (!src) { - return false; - } else if (src.is_none()) { - return true; // default-constructed value is already empty - } - value_conv inner_caster; - if (!inner_caster.load(src, convert)) - return false; - - value.emplace(cast_op(std::move(inner_caster))); - return true; - } - - PYBIND11_TYPE_CASTER(T, _("Optional[") + value_conv::name() + _("]")); -}; - -#if PYBIND11_HAS_OPTIONAL -template struct type_caster> - : public optional_caster> {}; - -template<> struct type_caster - : public void_caster {}; -#endif - -#if PYBIND11_HAS_EXP_OPTIONAL -template struct type_caster> - : public optional_caster> {}; - -template<> struct type_caster - : public void_caster {}; -#endif - -/// Visit a variant and cast any found type to Python -struct variant_caster_visitor { - return_value_policy policy; - handle parent; - - template - handle operator()(T &&src) const { - return make_caster::cast(std::forward(src), policy, parent); - } -}; - -/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar -/// `namespace::variant` types which provide a `namespace::visit()` function are handled here -/// automatically using argument-dependent lookup. Users can provide specializations for other -/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`. -template class Variant> -struct visit_helper { - template - static auto call(Args &&...args) -> decltype(visit(std::forward(args)...)) { - return visit(std::forward(args)...); - } -}; - -/// Generic variant caster -template struct variant_caster; - -template class V, typename... Ts> -struct variant_caster> { - static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative."); - - template - bool load_alternative(handle src, bool convert, type_list) { - auto caster = make_caster(); - if (caster.load(src, convert)) { - value = cast_op(caster); - return true; - } - return load_alternative(src, convert, type_list{}); - } - - bool load_alternative(handle, bool, type_list<>) { return false; } - - bool load(handle src, bool convert) { - // Do a first pass without conversions to improve constructor resolution. - // E.g. `py::int_(1).cast>()` needs to fill the `int` - // slot of the variant. Without two-pass loading `double` would be filled - // because it appears first and a conversion is possible. - if (convert && load_alternative(src, false, type_list{})) - return true; - return load_alternative(src, convert, type_list{}); - } - - template - static handle cast(Variant &&src, return_value_policy policy, handle parent) { - return visit_helper::call(variant_caster_visitor{policy, parent}, - std::forward(src)); - } - - using Type = V; - PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster::name()...) + _("]")); -}; - -#if PYBIND11_HAS_VARIANT -template -struct type_caster> : variant_caster> { }; -#endif -NAMESPACE_END(detail) - -inline std::ostream &operator<<(std::ostream &os, const handle &obj) { - os << (std::string) str(obj); - return os; -} - -NAMESPACE_END(pybind11) - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/ppocr/postprocess/lanms/include/pybind11/stl_bind.h b/ppocr/postprocess/lanms/include/pybind11/stl_bind.h deleted file mode 100644 index f16e9d22..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/stl_bind.h +++ /dev/null @@ -1,585 +0,0 @@ -/* - pybind11/std_bind.h: Binding generators for STL data types - - Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include "common.h" -#include "operators.h" - -#include -#include - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) - -/* SFINAE helper class used by 'is_comparable */ -template struct container_traits { - template static std::true_type test_comparable(decltype(std::declval() == std::declval())*); - template static std::false_type test_comparable(...); - template static std::true_type test_value(typename T2::value_type *); - template static std::false_type test_value(...); - template static std::true_type test_pair(typename T2::first_type *, typename T2::second_type *); - template static std::false_type test_pair(...); - - static constexpr const bool is_comparable = std::is_same(nullptr))>::value; - static constexpr const bool is_pair = std::is_same(nullptr, nullptr))>::value; - static constexpr const bool is_vector = std::is_same(nullptr))>::value; - static constexpr const bool is_element = !is_pair && !is_vector; -}; - -/* Default: is_comparable -> std::false_type */ -template -struct is_comparable : std::false_type { }; - -/* For non-map data structures, check whether operator== can be instantiated */ -template -struct is_comparable< - T, enable_if_t::is_element && - container_traits::is_comparable>> - : std::true_type { }; - -/* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ -template -struct is_comparable::is_vector>> { - static constexpr const bool value = - is_comparable::value; -}; - -/* For pairs, recursively check the two data types */ -template -struct is_comparable::is_pair>> { - static constexpr const bool value = - is_comparable::value && - is_comparable::value; -}; - -/* Fallback functions */ -template void vector_if_copy_constructible(const Args &...) { } -template void vector_if_equal_operator(const Args &...) { } -template void vector_if_insertion_operator(const Args &...) { } -template void vector_modifiers(const Args &...) { } - -template -void vector_if_copy_constructible(enable_if_t::value, Class_> &cl) { - cl.def(init(), "Copy constructor"); -} - -template -void vector_if_equal_operator(enable_if_t::value, Class_> &cl) { - using T = typename Vector::value_type; - - cl.def(self == self); - cl.def(self != self); - - cl.def("count", - [](const Vector &v, const T &x) { - return std::count(v.begin(), v.end(), x); - }, - arg("x"), - "Return the number of times ``x`` appears in the list" - ); - - cl.def("remove", [](Vector &v, const T &x) { - auto p = std::find(v.begin(), v.end(), x); - if (p != v.end()) - v.erase(p); - else - throw value_error(); - }, - arg("x"), - "Remove the first item from the list whose value is x. " - "It is an error if there is no such item." - ); - - cl.def("__contains__", - [](const Vector &v, const T &x) { - return std::find(v.begin(), v.end(), x) != v.end(); - }, - arg("x"), - "Return true the container contains ``x``" - ); -} - -// Vector modifiers -- requires a copyable vector_type: -// (Technically, some of these (pop and __delitem__) don't actually require copyability, but it seems -// silly to allow deletion but not insertion, so include them here too.) -template -void vector_modifiers(enable_if_t::value, Class_> &cl) { - using T = typename Vector::value_type; - using SizeType = typename Vector::size_type; - using DiffType = typename Vector::difference_type; - - cl.def("append", - [](Vector &v, const T &value) { v.push_back(value); }, - arg("x"), - "Add an item to the end of the list"); - - cl.def("__init__", [](Vector &v, iterable it) { - new (&v) Vector(); - try { - v.reserve(len(it)); - for (handle h : it) - v.push_back(h.cast()); - } catch (...) { - v.~Vector(); - throw; - } - }); - - cl.def("extend", - [](Vector &v, const Vector &src) { - v.insert(v.end(), src.begin(), src.end()); - }, - arg("L"), - "Extend the list by appending all the items in the given list" - ); - - cl.def("insert", - [](Vector &v, SizeType i, const T &x) { - if (i > v.size()) - throw index_error(); - v.insert(v.begin() + (DiffType) i, x); - }, - arg("i") , arg("x"), - "Insert an item at a given position." - ); - - cl.def("pop", - [](Vector &v) { - if (v.empty()) - throw index_error(); - T t = v.back(); - v.pop_back(); - return t; - }, - "Remove and return the last item" - ); - - cl.def("pop", - [](Vector &v, SizeType i) { - if (i >= v.size()) - throw index_error(); - T t = v[i]; - v.erase(v.begin() + (DiffType) i); - return t; - }, - arg("i"), - "Remove and return the item at index ``i``" - ); - - cl.def("__setitem__", - [](Vector &v, SizeType i, const T &t) { - if (i >= v.size()) - throw index_error(); - v[i] = t; - } - ); - - /// Slicing protocol - cl.def("__getitem__", - [](const Vector &v, slice slice) -> Vector * { - size_t start, stop, step, slicelength; - - if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) - throw error_already_set(); - - Vector *seq = new Vector(); - seq->reserve((size_t) slicelength); - - for (size_t i=0; ipush_back(v[start]); - start += step; - } - return seq; - }, - arg("s"), - "Retrieve list elements using a slice object" - ); - - cl.def("__setitem__", - [](Vector &v, slice slice, const Vector &value) { - size_t start, stop, step, slicelength; - if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) - throw error_already_set(); - - if (slicelength != value.size()) - throw std::runtime_error("Left and right hand size of slice assignment have different sizes!"); - - for (size_t i=0; i= v.size()) - throw index_error(); - v.erase(v.begin() + DiffType(i)); - }, - "Delete the list elements at index ``i``" - ); - - cl.def("__delitem__", - [](Vector &v, slice slice) { - size_t start, stop, step, slicelength; - - if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) - throw error_already_set(); - - if (step == 1 && false) { - v.erase(v.begin() + (DiffType) start, v.begin() + DiffType(start + slicelength)); - } else { - for (size_t i = 0; i < slicelength; ++i) { - v.erase(v.begin() + DiffType(start)); - start += step - 1; - } - } - }, - "Delete list elements using a slice object" - ); - -} - -// If the type has an operator[] that doesn't return a reference (most notably std::vector), -// we have to access by copying; otherwise we return by reference. -template using vector_needs_copy = negation< - std::is_same()[typename Vector::size_type()]), typename Vector::value_type &>>; - -// The usual case: access and iterate by reference -template -void vector_accessor(enable_if_t::value, Class_> &cl) { - using T = typename Vector::value_type; - using SizeType = typename Vector::size_type; - using ItType = typename Vector::iterator; - - cl.def("__getitem__", - [](Vector &v, SizeType i) -> T & { - if (i >= v.size()) - throw index_error(); - return v[i]; - }, - return_value_policy::reference_internal // ref + keepalive - ); - - cl.def("__iter__", - [](Vector &v) { - return make_iterator< - return_value_policy::reference_internal, ItType, ItType, T&>( - v.begin(), v.end()); - }, - keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ - ); -} - -// The case for special objects, like std::vector, that have to be returned-by-copy: -template -void vector_accessor(enable_if_t::value, Class_> &cl) { - using T = typename Vector::value_type; - using SizeType = typename Vector::size_type; - using ItType = typename Vector::iterator; - cl.def("__getitem__", - [](const Vector &v, SizeType i) -> T { - if (i >= v.size()) - throw index_error(); - return v[i]; - } - ); - - cl.def("__iter__", - [](Vector &v) { - return make_iterator< - return_value_policy::copy, ItType, ItType, T>( - v.begin(), v.end()); - }, - keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ - ); -} - -template auto vector_if_insertion_operator(Class_ &cl, std::string const &name) - -> decltype(std::declval() << std::declval(), void()) { - using size_type = typename Vector::size_type; - - cl.def("__repr__", - [name](Vector &v) { - std::ostringstream s; - s << name << '['; - for (size_type i=0; i < v.size(); ++i) { - s << v[i]; - if (i != v.size() - 1) - s << ", "; - } - s << ']'; - return s.str(); - }, - "Return the canonical string representation of this list." - ); -} - -// Provide the buffer interface for vectors if we have data() and we have a format for it -// GCC seems to have "void std::vector::data()" - doing SFINAE on the existence of data() is insufficient, we need to check it returns an appropriate pointer -template -struct vector_has_data_and_format : std::false_type {}; -template -struct vector_has_data_and_format::format(), std::declval().data()), typename Vector::value_type*>::value>> : std::true_type {}; - -// Add the buffer interface to a vector -template -enable_if_t...>::value> -vector_buffer(Class_& cl) { - using T = typename Vector::value_type; - - static_assert(vector_has_data_and_format::value, "There is not an appropriate format descriptor for this vector"); - - // numpy.h declares this for arbitrary types, but it may raise an exception and crash hard at runtime if PYBIND11_NUMPY_DTYPE hasn't been called, so check here - format_descriptor::format(); - - cl.def_buffer([](Vector& v) -> buffer_info { - return buffer_info(v.data(), static_cast(sizeof(T)), format_descriptor::format(), 1, {v.size()}, {sizeof(T)}); - }); - - cl.def("__init__", [](Vector& vec, buffer buf) { - auto info = buf.request(); - if (info.ndim != 1 || info.strides[0] % static_cast(sizeof(T))) - throw type_error("Only valid 1D buffers can be copied to a vector"); - if (!detail::compare_buffer_info::compare(info) || (ssize_t) sizeof(T) != info.itemsize) - throw type_error("Format mismatch (Python: " + info.format + " C++: " + format_descriptor::format() + ")"); - new (&vec) Vector(); - vec.reserve((size_t) info.shape[0]); - T *p = static_cast(info.ptr); - ssize_t step = info.strides[0] / static_cast(sizeof(T)); - T *end = p + info.shape[0] * step; - for (; p != end; p += step) - vec.push_back(*p); - }); - - return; -} - -template -enable_if_t...>::value> vector_buffer(Class_&) {} - -NAMESPACE_END(detail) - -// -// std::vector -// -template , typename... Args> -class_ bind_vector(module &m, std::string const &name, Args&&... args) { - using Class_ = class_; - - Class_ cl(m, name.c_str(), std::forward(args)...); - - // Declare the buffer interface if a buffer_protocol() is passed in - detail::vector_buffer(cl); - - cl.def(init<>()); - - // Register copy constructor (if possible) - detail::vector_if_copy_constructible(cl); - - // Register comparison-related operators and functions (if possible) - detail::vector_if_equal_operator(cl); - - // Register stream insertion operator (if possible) - detail::vector_if_insertion_operator(cl, name); - - // Modifiers require copyable vector value type - detail::vector_modifiers(cl); - - // Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive - detail::vector_accessor(cl); - - cl.def("__bool__", - [](const Vector &v) -> bool { - return !v.empty(); - }, - "Check whether the list is nonempty" - ); - - cl.def("__len__", &Vector::size); - - - - -#if 0 - // C++ style functions deprecated, leaving it here as an example - cl.def(init()); - - cl.def("resize", - (void (Vector::*) (size_type count)) & Vector::resize, - "changes the number of elements stored"); - - cl.def("erase", - [](Vector &v, SizeType i) { - if (i >= v.size()) - throw index_error(); - v.erase(v.begin() + i); - }, "erases element at index ``i``"); - - cl.def("empty", &Vector::empty, "checks whether the container is empty"); - cl.def("size", &Vector::size, "returns the number of elements"); - cl.def("push_back", (void (Vector::*)(const T&)) &Vector::push_back, "adds an element to the end"); - cl.def("pop_back", &Vector::pop_back, "removes the last element"); - - cl.def("max_size", &Vector::max_size, "returns the maximum possible number of elements"); - cl.def("reserve", &Vector::reserve, "reserves storage"); - cl.def("capacity", &Vector::capacity, "returns the number of elements that can be held in currently allocated storage"); - cl.def("shrink_to_fit", &Vector::shrink_to_fit, "reduces memory usage by freeing unused memory"); - - cl.def("clear", &Vector::clear, "clears the contents"); - cl.def("swap", &Vector::swap, "swaps the contents"); - - cl.def("front", [](Vector &v) { - if (v.size()) return v.front(); - else throw index_error(); - }, "access the first element"); - - cl.def("back", [](Vector &v) { - if (v.size()) return v.back(); - else throw index_error(); - }, "access the last element "); - -#endif - - return cl; -} - - - -// -// std::map, std::unordered_map -// - -NAMESPACE_BEGIN(detail) - -/* Fallback functions */ -template void map_if_insertion_operator(const Args &...) { } -template void map_assignment(const Args &...) { } - -// Map assignment when copy-assignable: just copy the value -template -void map_assignment(enable_if_t::value, Class_> &cl) { - using KeyType = typename Map::key_type; - using MappedType = typename Map::mapped_type; - - cl.def("__setitem__", - [](Map &m, const KeyType &k, const MappedType &v) { - auto it = m.find(k); - if (it != m.end()) it->second = v; - else m.emplace(k, v); - } - ); -} - -// Not copy-assignable, but still copy-constructible: we can update the value by erasing and reinserting -template -void map_assignment(enable_if_t< - !std::is_copy_assignable::value && - is_copy_constructible::value, - Class_> &cl) { - using KeyType = typename Map::key_type; - using MappedType = typename Map::mapped_type; - - cl.def("__setitem__", - [](Map &m, const KeyType &k, const MappedType &v) { - // We can't use m[k] = v; because value type might not be default constructable - auto r = m.emplace(k, v); - if (!r.second) { - // value type is not copy assignable so the only way to insert it is to erase it first... - m.erase(r.first); - m.emplace(k, v); - } - } - ); -} - - -template auto map_if_insertion_operator(Class_ &cl, std::string const &name) --> decltype(std::declval() << std::declval() << std::declval(), void()) { - - cl.def("__repr__", - [name](Map &m) { - std::ostringstream s; - s << name << '{'; - bool f = false; - for (auto const &kv : m) { - if (f) - s << ", "; - s << kv.first << ": " << kv.second; - f = true; - } - s << '}'; - return s.str(); - }, - "Return the canonical string representation of this map." - ); -} - - -NAMESPACE_END(detail) - -template , typename... Args> -class_ bind_map(module &m, const std::string &name, Args&&... args) { - using KeyType = typename Map::key_type; - using MappedType = typename Map::mapped_type; - using Class_ = class_; - - Class_ cl(m, name.c_str(), std::forward(args)...); - - cl.def(init<>()); - - // Register stream insertion operator (if possible) - detail::map_if_insertion_operator(cl, name); - - cl.def("__bool__", - [](const Map &m) -> bool { return !m.empty(); }, - "Check whether the map is nonempty" - ); - - cl.def("__iter__", - [](Map &m) { return make_key_iterator(m.begin(), m.end()); }, - keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ - ); - - cl.def("items", - [](Map &m) { return make_iterator(m.begin(), m.end()); }, - keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ - ); - - cl.def("__getitem__", - [](Map &m, const KeyType &k) -> MappedType & { - auto it = m.find(k); - if (it == m.end()) - throw key_error(); - return it->second; - }, - return_value_policy::reference_internal // ref + keepalive - ); - - // Assignment provided only if the type is copyable - detail::map_assignment(cl); - - cl.def("__delitem__", - [](Map &m, const KeyType &k) { - auto it = m.find(k); - if (it == m.end()) - throw key_error(); - return m.erase(it); - } - ); - - cl.def("__len__", &Map::size); - - return cl; -} - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/include/pybind11/typeid.h b/ppocr/postprocess/lanms/include/pybind11/typeid.h deleted file mode 100644 index c903fb14..00000000 --- a/ppocr/postprocess/lanms/include/pybind11/typeid.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - pybind11/typeid.h: Compiler-independent access to type identifiers - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#pragma once - -#include -#include - -#if defined(__GNUG__) -#include -#endif - -NAMESPACE_BEGIN(pybind11) -NAMESPACE_BEGIN(detail) -/// Erase all occurrences of a substring -inline void erase_all(std::string &string, const std::string &search) { - for (size_t pos = 0;;) { - pos = string.find(search, pos); - if (pos == std::string::npos) break; - string.erase(pos, search.length()); - } -} - -PYBIND11_NOINLINE inline void clean_type_id(std::string &name) { -#if defined(__GNUG__) - int status = 0; - std::unique_ptr res { - abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free }; - if (status == 0) - name = res.get(); -#else - detail::erase_all(name, "class "); - detail::erase_all(name, "struct "); - detail::erase_all(name, "enum "); -#endif - detail::erase_all(name, "pybind11::"); -} -NAMESPACE_END(detail) - -/// Return a string representation of a C++ type -template static std::string type_id() { - std::string name(typeid(T).name()); - detail::clean_type_id(name); - return name; -} - -NAMESPACE_END(pybind11) diff --git a/ppocr/postprocess/lanms/lanms.h b/ppocr/postprocess/lanms/lanms.h deleted file mode 100644 index 679666ca..00000000 --- a/ppocr/postprocess/lanms/lanms.h +++ /dev/null @@ -1,234 +0,0 @@ -#pragma once - -#include "clipper/clipper.hpp" - -// locality-aware NMS -namespace lanms { - - namespace cl = ClipperLib; - - struct Polygon { - cl::Path poly; - float score; - }; - - float paths_area(const ClipperLib::Paths &ps) { - float area = 0; - for (auto &&p: ps) - area += cl::Area(p); - return area; - } - - float poly_iou(const Polygon &a, const Polygon &b) { - cl::Clipper clpr; - clpr.AddPath(a.poly, cl::ptSubject, true); - clpr.AddPath(b.poly, cl::ptClip, true); - - cl::Paths inter, uni; - clpr.Execute(cl::ctIntersection, inter, cl::pftEvenOdd); - clpr.Execute(cl::ctUnion, uni, cl::pftEvenOdd); - - auto inter_area = paths_area(inter), - uni_area = paths_area(uni); - return std::abs(inter_area) / std::max(std::abs(uni_area), 1.0f); - } - - bool should_merge(const Polygon &a, const Polygon &b, float iou_threshold) { - return poly_iou(a, b) > iou_threshold; - } - - /** - * Incrementally merge polygons - */ - class PolyMerger { - public: - PolyMerger(): score(0), nr_polys(0) { - memset(data, 0, sizeof(data)); - } - - /** - * Add a new polygon to be merged. - */ - void add(const Polygon &p_given) { - Polygon p; - if (nr_polys > 0) { - // vertices of two polygons to merge may not in the same order; - // we match their vertices by choosing the ordering that - // minimizes the total squared distance. - // see function normalize_poly for details. - p = normalize_poly(get(), p_given); - } else { - p = p_given; - } - assert(p.poly.size() == 4); - auto &poly = p.poly; - auto s = p.score; - data[0] += poly[0].X * s; - data[1] += poly[0].Y * s; - - data[2] += poly[1].X * s; - data[3] += poly[1].Y * s; - - data[4] += poly[2].X * s; - data[5] += poly[2].Y * s; - - data[6] += poly[3].X * s; - data[7] += poly[3].Y * s; - - score += p.score; - - nr_polys += 1; - } - - inline std::int64_t sqr(std::int64_t x) { return x * x; } - - Polygon normalize_poly( - const Polygon &ref, - const Polygon &p) { - - std::int64_t min_d = std::numeric_limits::max(); - size_t best_start = 0, best_order = 0; - - for (size_t start = 0; start < 4; start ++) { - size_t j = start; - std::int64_t d = ( - sqr(ref.poly[(j + 0) % 4].X - p.poly[(j + 0) % 4].X) - + sqr(ref.poly[(j + 0) % 4].Y - p.poly[(j + 0) % 4].Y) - + sqr(ref.poly[(j + 1) % 4].X - p.poly[(j + 1) % 4].X) - + sqr(ref.poly[(j + 1) % 4].Y - p.poly[(j + 1) % 4].Y) - + sqr(ref.poly[(j + 2) % 4].X - p.poly[(j + 2) % 4].X) - + sqr(ref.poly[(j + 2) % 4].Y - p.poly[(j + 2) % 4].Y) - + sqr(ref.poly[(j + 3) % 4].X - p.poly[(j + 3) % 4].X) - + sqr(ref.poly[(j + 3) % 4].Y - p.poly[(j + 3) % 4].Y) - ); - if (d < min_d) { - min_d = d; - best_start = start; - best_order = 0; - } - - d = ( - sqr(ref.poly[(j + 0) % 4].X - p.poly[(j + 3) % 4].X) - + sqr(ref.poly[(j + 0) % 4].Y - p.poly[(j + 3) % 4].Y) - + sqr(ref.poly[(j + 1) % 4].X - p.poly[(j + 2) % 4].X) - + sqr(ref.poly[(j + 1) % 4].Y - p.poly[(j + 2) % 4].Y) - + sqr(ref.poly[(j + 2) % 4].X - p.poly[(j + 1) % 4].X) - + sqr(ref.poly[(j + 2) % 4].Y - p.poly[(j + 1) % 4].Y) - + sqr(ref.poly[(j + 3) % 4].X - p.poly[(j + 0) % 4].X) - + sqr(ref.poly[(j + 3) % 4].Y - p.poly[(j + 0) % 4].Y) - ); - if (d < min_d) { - min_d = d; - best_start = start; - best_order = 1; - } - } - - Polygon r; - r.poly.resize(4); - auto j = best_start; - if (best_order == 0) { - for (size_t i = 0; i < 4; i ++) - r.poly[i] = p.poly[(j + i) % 4]; - } else { - for (size_t i = 0; i < 4; i ++) - r.poly[i] = p.poly[(j + 4 - i - 1) % 4]; - } - r.score = p.score; - return r; - } - - Polygon get() const { - Polygon p; - - auto &poly = p.poly; - poly.resize(4); - auto score_inv = 1.0f / std::max(1e-8f, score); - poly[0].X = data[0] * score_inv; - poly[0].Y = data[1] * score_inv; - poly[1].X = data[2] * score_inv; - poly[1].Y = data[3] * score_inv; - poly[2].X = data[4] * score_inv; - poly[2].Y = data[5] * score_inv; - poly[3].X = data[6] * score_inv; - poly[3].Y = data[7] * score_inv; - - assert(score > 0); - p.score = score; - - return p; - } - - private: - std::int64_t data[8]; - float score; - std::int32_t nr_polys; - }; - - - /** - * The standard NMS algorithm. - */ - std::vector standard_nms(std::vector &polys, float iou_threshold) { - size_t n = polys.size(); - if (n == 0) - return {}; - std::vector indices(n); - std::iota(std::begin(indices), std::end(indices), 0); - std::sort(std::begin(indices), std::end(indices), [&](size_t i, size_t j) { return polys[i].score > polys[j].score; }); - - std::vector keep; - while (indices.size()) { - size_t p = 0, cur = indices[0]; - keep.emplace_back(cur); - for (size_t i = 1; i < indices.size(); i ++) { - if (!should_merge(polys[cur], polys[indices[i]], iou_threshold)) { - indices[p ++] = indices[i]; - } - } - indices.resize(p); - } - - std::vector ret; - for (auto &&i: keep) { - ret.emplace_back(polys[i]); - } - return ret; - } - - std::vector - merge_quadrangle_n9(const float *data, size_t n, float iou_threshold) { - using cInt = cl::cInt; - - // first pass - std::vector polys; - for (size_t i = 0; i < n; i ++) { - auto p = data + i * 9; - Polygon poly{ - { - {cInt(p[0]), cInt(p[1])}, - {cInt(p[2]), cInt(p[3])}, - {cInt(p[4]), cInt(p[5])}, - {cInt(p[6]), cInt(p[7])}, - }, - p[8], - }; - - if (polys.size()) { - // merge with the last one - auto &bpoly = polys.back(); - if (should_merge(poly, bpoly, iou_threshold)) { - PolyMerger merger; - merger.add(bpoly); - merger.add(poly); - bpoly = merger.get(); - } else { - polys.emplace_back(poly); - } - } else { - polys.emplace_back(poly); - } - } - return standard_nms(polys, iou_threshold); - } -} diff --git a/ppocr/postprocess/locality_aware_nms.py b/ppocr/postprocess/locality_aware_nms.py deleted file mode 100644 index 1220ffa0..00000000 --- a/ppocr/postprocess/locality_aware_nms.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -Locality aware nms. -""" - -import numpy as np -from shapely.geometry import Polygon - - -def intersection(g, p): - """ - Intersection. - """ - g = Polygon(g[:8].reshape((4, 2))) - p = Polygon(p[:8].reshape((4, 2))) - g = g.buffer(0) - p = p.buffer(0) - if not g.is_valid or not p.is_valid: - return 0 - inter = Polygon(g).intersection(Polygon(p)).area - union = g.area + p.area - inter - if union == 0: - return 0 - else: - return inter / union - - -def intersection_iog(g, p): - """ - Intersection_iog. - """ - g = Polygon(g[:8].reshape((4, 2))) - p = Polygon(p[:8].reshape((4, 2))) - if not g.is_valid or not p.is_valid: - return 0 - inter = Polygon(g).intersection(Polygon(p)).area - #union = g.area + p.area - inter - union = p.area - if union == 0: - print("p_area is very small") - return 0 - else: - return inter / union - - -def weighted_merge(g, p): - """ - Weighted merge. - """ - g[:8] = (g[8] * g[:8] + p[8] * p[:8]) / (g[8] + p[8]) - g[8] = (g[8] + p[8]) - return g - - -def standard_nms(S, thres): - """ - Standard nms. - """ - order = np.argsort(S[:, 8])[::-1] - keep = [] - while order.size > 0: - i = order[0] - keep.append(i) - ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) - - inds = np.where(ovr <= thres)[0] - order = order[inds + 1] - - return S[keep] - - -def standard_nms_inds(S, thres): - """ - Standard nms, retun inds. - """ - order = np.argsort(S[:, 8])[::-1] - keep = [] - while order.size > 0: - i = order[0] - keep.append(i) - ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) - - inds = np.where(ovr <= thres)[0] - order = order[inds + 1] - - return keep - - -def nms(S, thres): - """ - nms. - """ - order = np.argsort(S[:, 8])[::-1] - keep = [] - while order.size > 0: - i = order[0] - keep.append(i) - ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) - - inds = np.where(ovr <= thres)[0] - order = order[inds + 1] - - return keep - - -def soft_nms(boxes_in, Nt_thres=0.3, threshold=0.8, sigma=0.5, method=2): - """ - soft_nms - :para boxes_in, N x 9 (coords + score) - :para threshould, eliminate cases min score(0.001) - :para Nt_thres, iou_threshi - :para sigma, gaussian weght - :method, linear or gaussian - """ - boxes = boxes_in.copy() - N = boxes.shape[0] - if N is None or N < 1: - return np.array([]) - pos, maxpos = 0, 0 - weight = 0.0 - inds = np.arange(N) - tbox, sbox = boxes[0].copy(), boxes[0].copy() - for i in range(N): - maxscore = boxes[i, 8] - maxpos = i - tbox = boxes[i].copy() - ti = inds[i] - pos = i + 1 - #get max box - while pos < N: - if maxscore < boxes[pos, 8]: - maxscore = boxes[pos, 8] - maxpos = pos - pos = pos + 1 - #add max box as a detection - boxes[i, :] = boxes[maxpos, :] - inds[i] = inds[maxpos] - #swap - boxes[maxpos, :] = tbox - inds[maxpos] = ti - tbox = boxes[i].copy() - pos = i + 1 - #NMS iteration - while pos < N: - sbox = boxes[pos].copy() - ts_iou_val = intersection(tbox, sbox) - if ts_iou_val > 0: - if method == 1: - if ts_iou_val > Nt_thres: - weight = 1 - ts_iou_val - else: - weight = 1 - elif method == 2: - weight = np.exp(-1.0 * ts_iou_val**2 / sigma) - else: - if ts_iou_val > Nt_thres: - weight = 0 - else: - weight = 1 - boxes[pos, 8] = weight * boxes[pos, 8] - #if box score falls below thresold, discard the box by - #swaping last box update N - if boxes[pos, 8] < threshold: - boxes[pos, :] = boxes[N - 1, :] - inds[pos] = inds[N - 1] - N = N - 1 - pos = pos - 1 - pos = pos + 1 - - return boxes[:N] - - -def nms_locality(polys, thres=0.3): - """ - locality aware nms of EAST - :param polys: a N*9 numpy array. first 8 coordinates, then prob - :return: boxes after nms - """ - S = [] - p = None - for g in polys: - if p is not None and intersection(g, p) > thres: - p = weighted_merge(g, p) - else: - if p is not None: - S.append(p) - p = g - if p is not None: - S.append(p) - - if len(S) == 0: - return np.array([]) - return standard_nms(np.array(S), thres) - - -if __name__ == '__main__': - # 343,350,448,135,474,143,369,359 - print( - Polygon(np.array([[343, 350], [448, 135], [474, 143], [369, 359]])) - .area) diff --git a/ppocr/postprocess/rec_postprocess.py b/ppocr/postprocess/rec_postprocess.py new file mode 100644 index 00000000..fae4f3d9 --- /dev/null +++ b/ppocr/postprocess/rec_postprocess.py @@ -0,0 +1,174 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import paddle +from paddle.nn import functional as F + + +class BaseRecLabelDecode(object): + """ Convert between text-label and text-index """ + + def __init__(self, + character_dict_path=None, + character_type='ch', + use_space_char=False): + support_character_type = ['ch', 'en', 'en_sensitive'] + assert character_type in support_character_type, "Only {} are supported now but get {}".format( + support_character_type, self.character_str) + + if character_type == "en": + self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz" + dict_character = list(self.character_str) + elif character_type == "ch": + self.character_str = "" + assert character_dict_path is not None, "character_dict_path should not be None when character_type is ch" + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode('utf-8').strip("\n").strip("\r\n") + self.character_str += line + if use_space_char: + self.character_str += " " + dict_character = list(self.character_str) + elif character_type == "en_sensitive": + # same with ASTER setting (use 94 char). + import string + self.character_str = string.printable[:-6] + dict_character = list(self.character_str) + else: + raise NotImplementedError + self.character_type = character_type + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.character = dict_character + + def add_special_char(self, dict_character): + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=True): + """ convert text-index into text-label. """ + result_list = [] + ignored_tokens = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if is_remove_duplicate: + if idx > 0 and text_index[batch_idx][idx - 1] == text_index[ + batch_idx][idx]: + continue + char_list.append(self.character[int(text_index[batch_idx][ + idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = ''.join(char_list) + result_list.append((text, conf_list)) + return result_list + + def get_ignored_tokens(self): + return [0] # for ctc blank + + +class CTCLabelDecode(BaseRecLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, + character_dict_path=None, + character_type='ch', + use_space_char=False, + **kwargs): + super(CTCLabelDecode, self).__init__(character_dict_path, + character_type, use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + # out = self.decode_preds(preds) + + preds = F.softmax(preds, axis=2).numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['blank'] + dict_character + return dict_character + + def decode_preds(self, preds): + probs = F.softmax(preds, axis=2).numpy() + probs_ind = np.argmax(probs, axis=2) + + B, N, _ = preds.shape + l = np.ones(B).astype(np.int64) * N + length = paddle.to_variable(l) + out = paddle.fluid.layers.ctc_greedy_decoder(preds, 0, length) + batch_res = [ + x[:idx[0]] for x, idx in zip(out[0].numpy(), out[1].numpy()) + ] + + result_list = [] + for sample_idx, ind, prob in zip(batch_res, probs_ind, probs): + char_list = [self.character[idx] for idx in sample_idx] + valid_ind = np.where(ind != 0)[0] + if len(valid_ind) == 0: + continue + conf_list = prob[valid_ind, ind[valid_ind]] + result_list.append((''.join(char_list), conf_list)) + return result_list + + +class AttnLabelDecode(BaseRecLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, + character_dict_path=None, + character_type='ch', + use_space_char=False, + **kwargs): + super(AttnLabelDecode, self).__init__(character_dict_path, + character_type, use_space_char) + self.beg_str = "sos" + self.end_str = "eos" + + def add_special_char(self, dict_character): + dict_character = [self.beg_str, self.end_str] + dict_character + return dict_character + + def __call__(self, text): + text = self.decode(text) + return text + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "unsupport type %s in get_beg_end_flag_idx" \ + % beg_or_end + return idx diff --git a/ppocr/postprocess/sast_postprocess.py b/ppocr/postprocess/sast_postprocess.py deleted file mode 100644 index ddce6554..00000000 --- a/ppocr/postprocess/sast_postprocess.py +++ /dev/null @@ -1,289 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys -__dir__ = os.path.dirname(__file__) -sys.path.append(__dir__) -sys.path.append(os.path.join(__dir__, '..')) - -import numpy as np -from .locality_aware_nms import nms_locality -# import lanms -import cv2 -import time - - -class SASTPostProcess(object): - """ - The post process for SAST. - """ - - def __init__(self, params): - self.score_thresh = params.get('score_thresh', 0.5) - self.nms_thresh = params.get('nms_thresh', 0.2) - self.sample_pts_num = params.get('sample_pts_num', 2) - self.shrink_ratio_of_width = params.get('shrink_ratio_of_width', 0.3) - self.expand_scale = params.get('expand_scale', 1.0) - self.tcl_map_thresh = 0.5 - - # c++ la-nms is faster, but only support python 3.5 - self.is_python35 = False - if sys.version_info.major == 3 and sys.version_info.minor == 5: - self.is_python35 = True - - def point_pair2poly(self, point_pair_list): - """ - Transfer vertical point_pairs into poly point in clockwise. - """ - # constract poly - point_num = len(point_pair_list) * 2 - point_list = [0] * point_num - for idx, point_pair in enumerate(point_pair_list): - point_list[idx] = point_pair[0] - point_list[point_num - 1 - idx] = point_pair[1] - return np.array(point_list).reshape(-1, 2) - - def shrink_quad_along_width(self, quad, begin_width_ratio=0., end_width_ratio=1.): - """ - Generate shrink_quad_along_width. - """ - ratio_pair = np.array([[begin_width_ratio], [end_width_ratio]], dtype=np.float32) - p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair - p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair - return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) - - def expand_poly_along_width(self, poly, shrink_ratio_of_width=0.3): - """ - expand poly along width. - """ - point_num = poly.shape[0] - left_quad = np.array([poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32) - left_ratio = -shrink_ratio_of_width * np.linalg.norm(left_quad[0] - left_quad[3]) / \ - (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6) - left_quad_expand = self.shrink_quad_along_width(left_quad, left_ratio, 1.0) - right_quad = np.array([poly[point_num // 2 - 2], poly[point_num // 2 - 1], - poly[point_num // 2], poly[point_num // 2 + 1]], dtype=np.float32) - right_ratio = 1.0 + \ - shrink_ratio_of_width * np.linalg.norm(right_quad[0] - right_quad[3]) / \ - (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6) - right_quad_expand = self.shrink_quad_along_width(right_quad, 0.0, right_ratio) - poly[0] = left_quad_expand[0] - poly[-1] = left_quad_expand[-1] - poly[point_num // 2 - 1] = right_quad_expand[1] - poly[point_num // 2] = right_quad_expand[2] - return poly - - def restore_quad(self, tcl_map, tcl_map_thresh, tvo_map): - """Restore quad.""" - xy_text = np.argwhere(tcl_map[:, :, 0] > tcl_map_thresh) - xy_text = xy_text[:, ::-1] # (n, 2) - - # Sort the text boxes via the y axis - xy_text = xy_text[np.argsort(xy_text[:, 1])] - - scores = tcl_map[xy_text[:, 1], xy_text[:, 0], 0] - scores = scores[:, np.newaxis] - - # Restore - point_num = int(tvo_map.shape[-1] / 2) - assert point_num == 4 - tvo_map = tvo_map[xy_text[:, 1], xy_text[:, 0], :] - xy_text_tile = np.tile(xy_text, (1, point_num)) # (n, point_num * 2) - quads = xy_text_tile - tvo_map - - return scores, quads, xy_text - - def quad_area(self, quad): - """ - compute area of a quad. - """ - edge = [ - (quad[1][0] - quad[0][0]) * (quad[1][1] + quad[0][1]), - (quad[2][0] - quad[1][0]) * (quad[2][1] + quad[1][1]), - (quad[3][0] - quad[2][0]) * (quad[3][1] + quad[2][1]), - (quad[0][0] - quad[3][0]) * (quad[0][1] + quad[3][1]) - ] - return np.sum(edge) / 2. - - def nms(self, dets): - if self.is_python35: - import lanms - dets = lanms.merge_quadrangle_n9(dets, self.nms_thresh) - else: - dets = nms_locality(dets, self.nms_thresh) - return dets - - def cluster_by_quads_tco(self, tcl_map, tcl_map_thresh, quads, tco_map): - """ - Cluster pixels in tcl_map based on quads. - """ - instance_count = quads.shape[0] + 1 # contain background - instance_label_map = np.zeros(tcl_map.shape[:2], dtype=np.int32) - if instance_count == 1: - return instance_count, instance_label_map - - # predict text center - xy_text = np.argwhere(tcl_map[:, :, 0] > tcl_map_thresh) - n = xy_text.shape[0] - xy_text = xy_text[:, ::-1] # (n, 2) - tco = tco_map[xy_text[:, 1], xy_text[:, 0], :] # (n, 2) - pred_tc = xy_text - tco - - # get gt text center - m = quads.shape[0] - gt_tc = np.mean(quads, axis=1) # (m, 2) - - pred_tc_tile = np.tile(pred_tc[:, np.newaxis, :], (1, m, 1)) # (n, m, 2) - gt_tc_tile = np.tile(gt_tc[np.newaxis, :, :], (n, 1, 1)) # (n, m, 2) - dist_mat = np.linalg.norm(pred_tc_tile - gt_tc_tile, axis=2) # (n, m) - xy_text_assign = np.argmin(dist_mat, axis=1) + 1 # (n,) - - instance_label_map[xy_text[:, 1], xy_text[:, 0]] = xy_text_assign - return instance_count, instance_label_map - - def estimate_sample_pts_num(self, quad, xy_text): - """ - Estimate sample points number. - """ - eh = (np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2])) / 2.0 - ew = (np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3])) / 2.0 - - dense_sample_pts_num = max(2, int(ew)) - dense_xy_center_line = xy_text[np.linspace(0, xy_text.shape[0] - 1, dense_sample_pts_num, - endpoint=True, dtype=np.float32).astype(np.int32)] - - dense_xy_center_line_diff = dense_xy_center_line[1:] - dense_xy_center_line[:-1] - estimate_arc_len = np.sum(np.linalg.norm(dense_xy_center_line_diff, axis=1)) - - sample_pts_num = max(2, int(estimate_arc_len / eh)) - return sample_pts_num - - def detect_sast(self, tcl_map, tvo_map, tbo_map, tco_map, ratio_w, ratio_h, src_w, src_h, - shrink_ratio_of_width=0.3, tcl_map_thresh=0.5, offset_expand=1.0, out_strid=4.0): - """ - first resize the tcl_map, tvo_map and tbo_map to the input_size, then restore the polys - """ - # restore quad - scores, quads, xy_text = self.restore_quad(tcl_map, tcl_map_thresh, tvo_map) - dets = np.hstack((quads, scores)).astype(np.float32, copy=False) - dets = self.nms(dets) - if dets.shape[0] == 0: - return [] - quads = dets[:, :-1].reshape(-1, 4, 2) - - # Compute quad area - quad_areas = [] - for quad in quads: - quad_areas.append(-self.quad_area(quad)) - - # instance segmentation - # instance_count, instance_label_map = cv2.connectedComponents(tcl_map.astype(np.uint8), connectivity=8) - instance_count, instance_label_map = self.cluster_by_quads_tco(tcl_map, tcl_map_thresh, quads, tco_map) - - # restore single poly with tcl instance. - poly_list = [] - for instance_idx in range(1, instance_count): - xy_text = np.argwhere(instance_label_map == instance_idx)[:, ::-1] - quad = quads[instance_idx - 1] - q_area = quad_areas[instance_idx - 1] - if q_area < 5: - continue - - # - len1 = float(np.linalg.norm(quad[0] -quad[1])) - len2 = float(np.linalg.norm(quad[1] -quad[2])) - min_len = min(len1, len2) - if min_len < 3: - continue - - # filter small CC - if xy_text.shape[0] <= 0: - continue - - # filter low confidence instance - xy_text_scores = tcl_map[xy_text[:, 1], xy_text[:, 0], 0] - if np.sum(xy_text_scores) / quad_areas[instance_idx - 1] < 0.1: - # if np.sum(xy_text_scores) / quad_areas[instance_idx - 1] < 0.05: - continue - - # sort xy_text - left_center_pt = np.array([[(quad[0, 0] + quad[-1, 0]) / 2.0, - (quad[0, 1] + quad[-1, 1]) / 2.0]]) # (1, 2) - right_center_pt = np.array([[(quad[1, 0] + quad[2, 0]) / 2.0, - (quad[1, 1] + quad[2, 1]) / 2.0]]) # (1, 2) - proj_unit_vec = (right_center_pt - left_center_pt) / \ - (np.linalg.norm(right_center_pt - left_center_pt) + 1e-6) - proj_value = np.sum(xy_text * proj_unit_vec, axis=1) - xy_text = xy_text[np.argsort(proj_value)] - - # Sample pts in tcl map - if self.sample_pts_num == 0: - sample_pts_num = self.estimate_sample_pts_num(quad, xy_text) - else: - sample_pts_num = self.sample_pts_num - xy_center_line = xy_text[np.linspace(0, xy_text.shape[0] - 1, sample_pts_num, - endpoint=True, dtype=np.float32).astype(np.int32)] - - point_pair_list = [] - for x, y in xy_center_line: - # get corresponding offset - offset = tbo_map[y, x, :].reshape(2, 2) - if offset_expand != 1.0: - offset_length = np.linalg.norm(offset, axis=1, keepdims=True) - expand_length = np.clip(offset_length * (offset_expand - 1), a_min=0.5, a_max=3.0) - offset_detal = offset / offset_length * expand_length - offset = offset + offset_detal - # original point - ori_yx = np.array([y, x], dtype=np.float32) - point_pair = (ori_yx + offset)[:, ::-1]* out_strid / np.array([ratio_w, ratio_h]).reshape(-1, 2) - point_pair_list.append(point_pair) - - # ndarry: (x, 2), expand poly along width - detected_poly = self.point_pair2poly(point_pair_list) - detected_poly = self.expand_poly_along_width(detected_poly, shrink_ratio_of_width) - detected_poly[:, 0] = np.clip(detected_poly[:, 0], a_min=0, a_max=src_w) - detected_poly[:, 1] = np.clip(detected_poly[:, 1], a_min=0, a_max=src_h) - poly_list.append(detected_poly) - - return poly_list - - def __call__(self, outs_dict, ratio_list): - score_list = outs_dict['f_score'] - border_list = outs_dict['f_border'] - tvo_list = outs_dict['f_tvo'] - tco_list = outs_dict['f_tco'] - - img_num = len(ratio_list) - poly_lists = [] - for ino in range(img_num): - p_score = score_list[ino].transpose((1,2,0)) - p_border = border_list[ino].transpose((1,2,0)) - p_tvo = tvo_list[ino].transpose((1,2,0)) - p_tco = tco_list[ino].transpose((1,2,0)) - # print(p_score.shape, p_border.shape, p_tvo.shape, p_tco.shape) - ratio_h, ratio_w, src_h, src_w = ratio_list[ino] - - poly_list = self.detect_sast(p_score, p_tvo, p_border, p_tco, ratio_w, ratio_h, src_w, src_h, - shrink_ratio_of_width=self.shrink_ratio_of_width, - tcl_map_thresh=self.tcl_map_thresh, offset_expand=self.expand_scale) - - poly_lists.append(poly_list) - - return poly_lists - diff --git a/ppocr/utils/check.py b/ppocr/utils/check.py index dc0482f0..3a0b1406 100755 --- a/ppocr/utils/check.py +++ b/ppocr/utils/check.py @@ -19,8 +19,6 @@ from __future__ import unicode_literals import sys -import paddle.fluid as fluid - import logging logger = logging.getLogger(__name__) diff --git a/ppocr/utils/logging.py b/ppocr/utils/logging.py new file mode 100644 index 00000000..150538a7 --- /dev/null +++ b/ppocr/utils/logging.py @@ -0,0 +1,67 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import logging +import functools +import paddle.distributed as dist + +logger_initialized = {} + + +@functools.lru_cache() +def get_logger(name='ppocr', log_file=None, log_level=logging.INFO): + """Initialize and get a logger by name. + If the logger has not been initialized, this method will initialize the + logger by adding one or two handlers, otherwise the initialized logger will + be directly returned. During initialization, a StreamHandler will always be + added. If `log_file` is specified a FileHandler will also be added. + Args: + name (str): Logger name. + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the logger. + log_level (int): The logger level. Note that only the process of + rank 0 is affected, and other processes will set the level to + "Error" thus be silent most of the time. + Returns: + logging.Logger: The expected logger. + """ + logger = logging.getLogger(name) + if name in logger_initialized: + return logger + for logger_name in logger_initialized: + if name.startswith(logger_name): + return logger + + formatter = logging.Formatter( + '[%(asctime)s] %(name)s %(levelname)s: %(message)s', + datefmt="%Y/%m/%d %H:%M:%S") + + stream_handler = logging.StreamHandler(stream=sys.stdout) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + + if log_file is not None and dist.get_rank() == 0: + log_file_folder = os.path.split(log_file)[0] + os.makedirs(log_file_folder, exist_ok=True) + file_handler = logging.FileHandler(log_file, 'a') + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + if dist.get_rank() == 0: + logger.setLevel(log_level) + else: + logger.setLevel(logging.ERROR) + logger_initialized[name] = True + return logger diff --git a/ppocr/utils/save_load.py b/ppocr/utils/save_load.py index f2346b3d..291962f3 100644 --- a/ppocr/utils/save_load.py +++ b/ppocr/utils/save_load.py @@ -1,16 +1,16 @@ # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. # -#Licensed under the Apache License, Version 2.0 (the "License"); -#you may not use this file except in compliance with the License. -#You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # -#Unless required by applicable law or agreed to in writing, software -#distributed under the License is distributed on an "AS IS" BASIS, -#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -#See the License for the specific language governing permissions and -#limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from __future__ import absolute_import from __future__ import division @@ -18,17 +18,15 @@ from __future__ import print_function import errno import os -import shutil -import tempfile +import pickle +import six -import paddle.fluid as fluid +import paddle -from .utility import initial_logger -import re -logger = initial_logger() +__all__ = ['init_model', 'save_model', 'load_dygraph_pretrain'] -def _mkdir_if_not_exist(path): +def _mkdir_if_not_exist(path, logger): """ mkdir if not exists, ignore the exception when multiprocess mkdir together """ @@ -44,90 +42,113 @@ def _mkdir_if_not_exist(path): raise OSError('Failed to mkdir {}'.format(path)) -def _load_state(path): - if os.path.exists(path + '.pdopt'): - # XXX another hack to ignore the optimizer state - tmp = tempfile.mkdtemp() - dst = os.path.join(tmp, os.path.basename(os.path.normpath(path))) - shutil.copy(path + '.pdparams', dst + '.pdparams') - state = fluid.io.load_program_state(dst) - shutil.rmtree(tmp) - else: - state = fluid.io.load_program_state(path) - return state - - -def load_params(exe, prog, path, ignore_params=[]): - """ - Load model from the given path. - Args: - exe (fluid.Executor): The fluid.Executor object. - prog (fluid.Program): load weight to which Program object. - path (string): URL string or loca model path. - ignore_params (list): ignore variable to load when finetuning. - It can be specified by finetune_exclude_pretrained_params - and the usage can refer to docs/advanced_tutorials/TRANSFER_LEARNING.md - """ +def load_dygraph_pretrain( + model, + logger, + path=None, + load_static_weights=False, ): if not (os.path.isdir(path) or os.path.exists(path + '.pdparams')): raise ValueError("Model pretrain path {} does not " "exists.".format(path)) + if load_static_weights: + pre_state_dict = paddle.io.load_program_state(path) + param_state_dict = {} + model_dict = model.state_dict() + for key in model_dict.keys(): + weight_name = model_dict[key].name + weight_name = weight_name.replace('binarize', '').replace( + 'thresh', '') # for DB + if weight_name in pre_state_dict.keys(): + logger.info('Load weight: {}, shape: {}'.format( + weight_name, pre_state_dict[weight_name].shape)) + if 'encoder_rnn' in key: + # delete axis which is 1 + pre_state_dict[weight_name] = pre_state_dict[ + weight_name].squeeze() + # change axis + if len(pre_state_dict[weight_name].shape) > 1: + pre_state_dict[weight_name] = pre_state_dict[ + weight_name].transpose((1, 0)) + param_state_dict[key] = pre_state_dict[weight_name] + else: + param_state_dict[key] = model_dict[key] + model.set_dict(param_state_dict) + return + + param_state_dict, optim_state_dict = paddle.load(path) + model.set_dict(param_state_dict) + return - logger.info('Loading parameters from {}...'.format(path)) - - ignore_set = set() - state = _load_state(path) - - # ignore the parameter which mismatch the shape - # between the model and pretrain weight. - all_var_shape = {} - for block in prog.blocks: - for param in block.all_parameters(): - all_var_shape[param.name] = param.shape - ignore_set.update([ - name for name, shape in all_var_shape.items() - if name in state and shape != state[name].shape - ]) - - if ignore_params: - all_var_names = [var.name for var in prog.list_vars()] - ignore_list = filter( - lambda var: any([re.match(name, var) for name in ignore_params]), - all_var_names) - ignore_set.update(list(ignore_list)) - - if len(ignore_set) > 0: - for k in ignore_set: - if k in state: - logger.warning('variable {} not used'.format(k)) - del state[k] - fluid.io.set_program_state(prog, state) - - -def init_model(config, program, exe): + +def init_model(config, model, logger, optimizer=None, lr_scheduler=None): """ load model from checkpoint or pretrained_model """ - checkpoints = config['Global'].get('checkpoints') + gloabl_config = config['Global'] + checkpoints = gloabl_config.get('checkpoints') + pretrained_model = gloabl_config.get('pretrained_model') + best_model_dict = {} if checkpoints: - if os.path.exists(checkpoints + '.pdparams'): - path = checkpoints - fluid.load(program, path, exe) - logger.info("Finish initing model from {}".format(path)) - else: - raise ValueError("Model checkpoints {} does not exists," - "check if you lost the file prefix.".format( - checkpoints + '.pdparams')) + assert os.path.exists(checkpoints + ".pdparams"), \ + "Given dir {}.pdparams not exist.".format(checkpoints) + assert os.path.exists(checkpoints + ".pdopt"), \ + "Given dir {}.pdopt not exist.".format(checkpoints) + para_dict, opti_dict = paddle.load(checkpoints) + model.set_dict(para_dict) + if optimizer is not None: + optimizer.set_state_dict(opti_dict) + + if os.path.exists(checkpoints + '.states'): + with open(checkpoints + '.states', 'rb') as f: + states_dict = pickle.load(f) if six.PY2 else pickle.load( + f, encoding='latin1') + best_model_dict = states_dict.get('best_model_dict', {}) + if 'epoch' in states_dict: + best_model_dict['start_epoch'] = states_dict['epoch'] + 1 + best_model_dict['start_epoch'] = best_model_dict['best_epoch'] + 1 + + logger.info("resume from {}".format(checkpoints)) + elif pretrained_model: + load_static_weights = gloabl_config.get('load_static_weights', False) + if pretrained_model: + if not isinstance(pretrained_model, list): + pretrained_model = [pretrained_model] + if not isinstance(load_static_weights, list): + load_static_weights = [load_static_weights] * len( + pretrained_model) + for idx, pretrained in enumerate(pretrained_model): + load_static = load_static_weights[idx] + load_dygraph_pretrain( + model, + logger, + path=pretrained, + load_static_weights=load_static) + logger.info("load pretrained model from {}".format( + pretrained_model)) else: - pretrain_weights = config['Global'].get('pretrain_weights') - if pretrain_weights: - path = pretrain_weights - load_params(exe, program, path) - logger.info("Finish initing model from {}".format(path)) + logger.info('train from scratch') + return best_model_dict -def save_model(program, model_path): +def save_model(net, + optimizer, + model_path, + logger, + is_best=False, + prefix='ppocr', + **kwargs): """ save model to the target path """ - fluid.save(program, model_path) - logger.info("Already save model in {}".format(model_path)) + _mkdir_if_not_exist(model_path, logger) + model_prefix = os.path.join(model_path, prefix) + paddle.save(net.state_dict(), model_prefix) + paddle.save(optimizer.state_dict(), model_prefix) + + # save metric and config + with open(model_prefix + '.states', 'wb') as f: + pickle.dump(kwargs, f, protocol=2) + if is_best: + logger.info('save best model is to {}'.format(model_prefix)) + else: + logger.info("save model in {}".format(model_prefix)) diff --git a/ppocr/utils/stats.py b/ppocr/utils/stats.py index 826c1c82..4b1ac98a 100755 --- a/ppocr/utils/stats.py +++ b/ppocr/utils/stats.py @@ -40,14 +40,18 @@ def Time(): class TrainingStats(object): def __init__(self, window_size, stats_keys): + self.window_size = window_size self.smoothed_losses_and_metrics = { key: SmoothedValue(window_size) for key in stats_keys } def update(self, stats): - for k, v in self.smoothed_losses_and_metrics.items(): - v.add_value(stats[k]) + for k, v in stats.items(): + if k not in self.smoothed_losses_and_metrics: + self.smoothed_losses_and_metrics[k] = SmoothedValue( + self.window_size) + self.smoothed_losses_and_metrics[k].add_value(v) def get(self, extras=None): stats = collections.OrderedDict() diff --git a/ppocr/utils/utility.py b/ppocr/utils/utility.py index e27dd1d8..28fbc2b1 100755 --- a/ppocr/utils/utility.py +++ b/ppocr/utils/utility.py @@ -16,49 +16,36 @@ import logging import os import imghdr import cv2 -from paddle import fluid -def initial_logger(): - FORMAT = '%(asctime)s-%(levelname)s: %(message)s' - logging.basicConfig(level=logging.INFO, format=FORMAT) - logger = logging.getLogger(__name__) - return logger - - -import importlib - - -def create_module(module_str): - tmpss = module_str.split(",") - assert len(tmpss) == 2, "Error formate\ - of the module path: {}".format(module_str) - module_name, function_name = tmpss[0], tmpss[1] - somemodule = importlib.import_module(module_name, __package__) - function = getattr(somemodule, function_name) - return function +def print_dict(d, logger, delimiter=0): + """ + Recursively visualize a dict and + indenting acrrording by the relationship of keys. + """ + for k, v in sorted(d.items()): + if isinstance(v, dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + print_dict(v, logger, delimiter + 4) + elif isinstance(v, list) and len(v) >= 1 and isinstance(v[0], dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + for value in v: + print_dict(value, logger, delimiter + 4) + else: + logger.info("{}{} : {}".format(delimiter * " ", k, v)) def get_check_global_params(mode): - check_params = ['use_gpu', 'max_text_length', 'image_shape',\ - 'image_shape', 'character_type', 'loss_type'] + check_params = ['use_gpu', 'max_text_length', 'image_shape', \ + 'image_shape', 'character_type', 'loss_type'] if mode == "train_eval": - check_params = check_params + [\ + check_params = check_params + [ \ 'train_batch_size_per_card', 'test_batch_size_per_card'] elif mode == "test": check_params = check_params + ['test_batch_size_per_card'] return check_params -def get_check_reader_params(mode): - check_params = [] - if mode == "train_eval": - check_params = ['TrainReader', 'EvalReader'] - elif mode == "test": - check_params = ['TestReader'] - return check_params - - def get_image_file_list(img_file): imgs_lists = [] if img_file is None or not os.path.exists(img_file): @@ -82,23 +69,11 @@ def check_and_read_gif(img_path): gif = cv2.VideoCapture(img_path) ret, frame = gif.read() if not ret: - logging.info("Cannot read {}. This gif image maybe corrupted.") + logger = logging.getLogger('ppocr') + logger.info("Cannot read {}. This gif image maybe corrupted.") return None, False if len(frame.shape) == 2 or frame.shape[-1] == 1: frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) imgvalue = frame[:, :, ::-1] return imgvalue, True - return None, False - - -def create_multi_devices_program(program, loss_var_name): - build_strategy = fluid.BuildStrategy() - build_strategy.memory_optimize = False - build_strategy.enable_inplace = True - exec_strategy = fluid.ExecutionStrategy() - exec_strategy.num_iteration_per_drop_scope = 1 - compile_program = fluid.CompiledProgram(program).with_data_parallel( - loss_name=loss_var_name, - build_strategy=build_strategy, - exec_strategy=exec_strategy) - return compile_program + return None, False \ No newline at end of file diff --git a/requirments.txt b/requirements.txt similarity index 51% rename from requirments.txt rename to requirements.txt index ec538138..76305d0d 100644 --- a/requirments.txt +++ b/requirements.txt @@ -3,4 +3,6 @@ imgaug pyclipper lmdb tqdm -numpy \ No newline at end of file +numpy +visualdl +python-Levenshtein \ No newline at end of file diff --git a/setup.py b/setup.py index 7141f170..6b503ce3 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ from setuptools import setup from io import open -with open('requirments.txt', encoding="utf-8-sig") as f: +with open('requirements.txt', encoding="utf-8-sig") as f: requirements = f.readlines() requirements.append('tqdm') diff --git a/tools/eval.py b/tools/eval.py index 22185911..07181ee7 100755 --- a/tools/eval.py +++ b/tools/eval.py @@ -18,71 +18,61 @@ from __future__ import print_function import os import sys + __dir__ = os.path.dirname(os.path.abspath(__file__)) sys.path.append(__dir__) sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) +import paddle +# paddle.manual_seed(2) -def set_paddle_flags(**kwargs): - for key, value in kwargs.items(): - if os.environ.get(key, None) is None: - os.environ[key] = str(value) +from ppocr.utils.logging import get_logger +from ppocr.data import build_dataloader +from ppocr.modeling import build_model +from ppocr.postprocess import build_post_process +from ppocr.metrics import build_metric +from ppocr.utils.save_load import init_model +from ppocr.utils.utility import print_dict +import tools.program as program -# NOTE(paddle-dev): All of these flags should be -# set before `import paddle`. Otherwise, it would -# not take any effect. -set_paddle_flags( - FLAGS_eager_delete_tensor_gb=0, # enable GC to save memory -) +def main(): + global_config = config['Global'] + # build dataloader + eval_loader, _ = build_dataloader(config['EVAL'], device, False, + global_config) -import program -from paddle import fluid -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from ppocr.data.reader_main import reader_main -from ppocr.utils.save_load import init_model -from eval_utils.eval_det_utils import eval_det_run -from eval_utils.eval_rec_utils import test_rec_benchmark -from eval_utils.eval_rec_utils import eval_rec_run + # build post process + post_process_class = build_post_process(config['PostProcess'], + global_config) + # build model + # for rec algorithm + if hasattr(post_process_class, 'character'): + config['Architecture']["Head"]['out_channels'] = len( + getattr(post_process_class, 'character')) + model = build_model(config['Architecture']) -def main(): - startup_prog, eval_program, place, config, train_alg_type = program.preprocess() - eval_build_outputs = program.build( - config, eval_program, startup_prog, mode='test') - eval_fetch_name_list = eval_build_outputs[1] - eval_fetch_varname_list = eval_build_outputs[2] - eval_program = eval_program.clone(for_test=True) - exe = fluid.Executor(place) - exe.run(startup_prog) + best_model_dict = init_model(config, model, logger) + if len(best_model_dict): + logger.info('metric in ckpt ***************') + for k, v in best_model_dict.items(): + logger.info('{}:{}'.format(k, v)) - init_model(config, eval_program, exe) + # build metric + eval_class = build_metric(config['Metric']) - if train_alg_type == 'det': - eval_reader = reader_main(config=config, mode="eval") - eval_info_dict = {'program':eval_program,\ - 'reader':eval_reader,\ - 'fetch_name_list':eval_fetch_name_list,\ - 'fetch_varname_list':eval_fetch_varname_list} - metrics = eval_det_run(exe, config, eval_info_dict, "eval") - logger.info("Eval result: {}".format(metrics)) - else: - reader_type = config['Global']['reader_yml'] - if "benchmark" not in reader_type: - eval_reader = reader_main(config=config, mode="eval") - eval_info_dict = {'program': eval_program, \ - 'reader': eval_reader, \ - 'fetch_name_list': eval_fetch_name_list, \ - 'fetch_varname_list': eval_fetch_varname_list} - metrics = eval_rec_run(exe, config, eval_info_dict, "eval") - logger.info("Eval result: {}".format(metrics)) - else: - eval_info_dict = {'program':eval_program,\ - 'fetch_name_list':eval_fetch_name_list,\ - 'fetch_varname_list':eval_fetch_varname_list} - test_rec_benchmark(exe, config, eval_info_dict) + # start eval + metirc = program.eval(model, eval_loader, post_process_class, eval_class) + logger.info('metric eval ***************') + for k, v in metirc.items(): + logger.info('{}:{}'.format(k, v)) if __name__ == '__main__': + device, config = program.preprocess() + paddle.disable_static(device) + + logger = get_logger() + print_dict(config, logger) main() diff --git a/tools/eval_utils/eval_det_utils.py b/tools/eval_utils/eval_det_utils.py deleted file mode 100644 index 276e6c31..00000000 --- a/tools/eval_utils/eval_det_utils.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import logging -import numpy as np - -import paddle.fluid as fluid - -__all__ = ['eval_det_run'] - -import logging -FORMAT = '%(asctime)s-%(levelname)s: %(message)s' -logging.basicConfig(level=logging.INFO, format=FORMAT) -logger = logging.getLogger(__name__) - -from ppocr.utils.utility import create_module -from .eval_det_iou import DetectionIoUEvaluator -import json -from copy import deepcopy -import cv2 -from ppocr.data.reader_main import reader_main -import os - - -def cal_det_res(exe, config, eval_info_dict): - global_params = config['Global'] - save_res_path = global_params['save_res_path'] - postprocess_params = deepcopy(config["PostProcess"]) - postprocess_params.update(global_params) - postprocess = create_module(postprocess_params['function']) \ - (params=postprocess_params) - if not os.path.exists(os.path.dirname(save_res_path)): - os.makedirs(os.path.dirname(save_res_path)) - with open(save_res_path, "wb") as fout: - tackling_num = 0 - for data in eval_info_dict['reader'](): - img_num = len(data) - tackling_num = tackling_num + img_num - logger.info("test tackling num:%d", tackling_num) - img_list = [] - ratio_list = [] - img_name_list = [] - for ino in range(img_num): - img_list.append(data[ino][0]) - ratio_list.append(data[ino][1]) - img_name_list.append(data[ino][2]) - try: - img_list = np.concatenate(img_list, axis=0) - except: - err = "concatenate error usually caused by different input image shapes in evaluation or testing.\n \ - Please set \"test_batch_size_per_card\" in main yml as 1\n \ - or add \"test_image_shape: [h, w]\" in reader yml for EvalReader." - - raise Exception(err) - outs = exe.run(eval_info_dict['program'], \ - feed={'image': img_list}, \ - fetch_list=eval_info_dict['fetch_varname_list']) - outs_dict = {} - for tno in range(len(outs)): - fetch_name = eval_info_dict['fetch_name_list'][tno] - fetch_value = np.array(outs[tno]) - outs_dict[fetch_name] = fetch_value - dt_boxes_list = postprocess(outs_dict, ratio_list) - for ino in range(img_num): - dt_boxes = dt_boxes_list[ino] - img_name = img_name_list[ino] - dt_boxes_json = [] - for box in dt_boxes: - tmp_json = {"transcription": ""} - tmp_json['points'] = box.tolist() - dt_boxes_json.append(tmp_json) - otstr = img_name + "\t" + json.dumps(dt_boxes_json) + "\n" - fout.write(otstr.encode()) - return - - -def load_label_infor(label_file_path, do_ignore=False): - img_name_label_dict = {} - with open(label_file_path, "rb") as fin: - lines = fin.readlines() - for line in lines: - substr = line.decode().strip("\n").split("\t") - bbox_infor = json.loads(substr[1]) - bbox_num = len(bbox_infor) - for bno in range(bbox_num): - text = bbox_infor[bno]['transcription'] - ignore = False - if text == "###" and do_ignore: - ignore = True - bbox_infor[bno]['ignore'] = ignore - img_name_label_dict[os.path.basename(substr[0])] = bbox_infor - return img_name_label_dict - - -def cal_det_metrics(gt_label_path, save_res_path): - """ - calculate the detection metrics - Args: - gt_label_path(string): The groundtruth detection label file path - save_res_path(string): The saved predicted detection label path - return: - claculated metrics including Hmean, precision and recall - """ - evaluator = DetectionIoUEvaluator() - gt_label_infor = load_label_infor(gt_label_path, do_ignore=True) - dt_label_infor = load_label_infor(save_res_path) - results = [] - for img_name in gt_label_infor: - gt_label = gt_label_infor[img_name] - if img_name not in dt_label_infor: - dt_label = [] - else: - dt_label = dt_label_infor[img_name] - result = evaluator.evaluate_image(gt_label, dt_label) - results.append(result) - methodMetrics = evaluator.combine_results(results) - return methodMetrics - - -def eval_det_run(exe, config, eval_info_dict, mode): - cal_det_res(exe, config, eval_info_dict) - - save_res_path = config['Global']['save_res_path'] - if mode == "eval": - gt_label_path = config['EvalReader']['label_file_path'] - metrics = cal_det_metrics(gt_label_path, save_res_path) - else: - gt_label_path = config['TestReader']['label_file_path'] - do_eval = config['TestReader']['do_eval'] - if do_eval: - metrics = cal_det_metrics(gt_label_path, save_res_path) - else: - metrics = {} - return metrics diff --git a/tools/eval_utils/eval_rec_utils.py b/tools/eval_utils/eval_rec_utils.py deleted file mode 100644 index 4479d9df..00000000 --- a/tools/eval_utils/eval_rec_utils.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import logging -import numpy as np - -import paddle.fluid as fluid - -__all__ = ['eval_rec_run', 'test_rec_benchmark'] - -import logging - -FORMAT = '%(asctime)s-%(levelname)s: %(message)s' -logging.basicConfig(level=logging.INFO, format=FORMAT) -logger = logging.getLogger(__name__) - -from ppocr.utils.character import cal_predicts_accuracy, cal_predicts_accuracy_srn -from ppocr.utils.character import convert_rec_label_to_lod -from ppocr.utils.character import convert_rec_attention_infer_res -from ppocr.utils.utility import create_module -import json -from copy import deepcopy -import cv2 -from ppocr.data.reader_main import reader_main - - -def eval_rec_run(exe, config, eval_info_dict, mode): - """ - Run evaluation program, return program outputs. - """ - char_ops = config['Global']['char_ops'] - total_loss = 0 - total_sample_num = 0 - total_acc_num = 0 - total_batch_num = 0 - if mode == "eval": - is_remove_duplicate = False - else: - is_remove_duplicate = True - - for data in eval_info_dict['reader'](): - img_num = len(data) - img_list = [] - label_list = [] - for ino in range(img_num): - img_list.append(data[ino][0]) - label_list.append(data[ino][1]) - - if config['Global']['loss_type'] != "srn": - img_list = np.concatenate(img_list, axis=0) - outs = exe.run(eval_info_dict['program'], \ - feed={'image': img_list}, \ - fetch_list=eval_info_dict['fetch_varname_list'], \ - return_numpy=False) - preds = np.array(outs[0]) - - if config['Global']['loss_type'] == "attention": - preds, preds_lod = convert_rec_attention_infer_res(preds) - else: - preds_lod = outs[0].lod()[0] - labels, labels_lod = convert_rec_label_to_lod(label_list) - acc, acc_num, sample_num = cal_predicts_accuracy( - char_ops, preds, preds_lod, labels, labels_lod, - is_remove_duplicate) - else: - encoder_word_pos_list = [] - gsrm_word_pos_list = [] - gsrm_slf_attn_bias1_list = [] - gsrm_slf_attn_bias2_list = [] - for ino in range(img_num): - encoder_word_pos_list.append(data[ino][2]) - gsrm_word_pos_list.append(data[ino][3]) - gsrm_slf_attn_bias1_list.append(data[ino][4]) - gsrm_slf_attn_bias2_list.append(data[ino][5]) - - img_list = np.concatenate(img_list, axis=0) - label_list = np.concatenate(label_list, axis=0) - encoder_word_pos_list = np.concatenate( - encoder_word_pos_list, axis=0).astype(np.int64) - gsrm_word_pos_list = np.concatenate( - gsrm_word_pos_list, axis=0).astype(np.int64) - gsrm_slf_attn_bias1_list = np.concatenate( - gsrm_slf_attn_bias1_list, axis=0).astype(np.float32) - gsrm_slf_attn_bias2_list = np.concatenate( - gsrm_slf_attn_bias2_list, axis=0).astype(np.float32) - - labels = label_list - - outs = exe.run(eval_info_dict['program'], \ - feed={'image': img_list, 'encoder_word_pos': encoder_word_pos_list, - 'gsrm_word_pos': gsrm_word_pos_list, 'gsrm_slf_attn_bias1': gsrm_slf_attn_bias1_list, - 'gsrm_slf_attn_bias2': gsrm_slf_attn_bias2_list}, \ - fetch_list=eval_info_dict['fetch_varname_list'], \ - return_numpy=False) - preds = np.array(outs[0]) - acc, acc_num, sample_num = cal_predicts_accuracy_srn( - char_ops, preds, labels, config['Global']['max_text_length']) - - total_acc_num += acc_num - total_sample_num += sample_num - #logger.info("eval batch id: {}, acc: {}".format(total_batch_num, acc)) - total_batch_num += 1 - avg_acc = total_acc_num * 1.0 / total_sample_num - metrics = {'avg_acc': avg_acc, "total_acc_num": total_acc_num, \ - "total_sample_num": total_sample_num} - return metrics - - -def test_rec_benchmark(exe, config, eval_info_dict): - " Evaluate lmdb dataset " - eval_data_list = ['IIIT5k_3000', 'SVT', 'IC03_860', 'IC03_867', \ - 'IC13_857', 'IC13_1015', 'IC15_1811', 'IC15_2077', 'SVTP', 'CUTE80'] - eval_data_dir = config['TestReader']['lmdb_sets_dir'] - total_evaluation_data_number = 0 - total_correct_number = 0 - eval_data_acc_info = {} - for eval_data in eval_data_list: - config['TestReader']['lmdb_sets_dir'] = \ - eval_data_dir + "/" + eval_data - eval_reader = reader_main(config=config, mode="test") - eval_info_dict['reader'] = eval_reader - metrics = eval_rec_run(exe, config, eval_info_dict, "test") - total_evaluation_data_number += metrics['total_sample_num'] - total_correct_number += metrics['total_acc_num'] - eval_data_acc_info[eval_data] = metrics - - avg_acc = total_correct_number * 1.0 / total_evaluation_data_number - logger.info('-' * 50) - strs = "" - for eval_data in eval_data_list: - eval_acc = eval_data_acc_info[eval_data]['avg_acc'] - strs += "\n {}, accuracy:{:.6f}".format(eval_data, eval_acc) - strs += "\n average, accuracy:{:.6f}".format(avg_acc) - logger.info(strs) - logger.info('-' * 50) diff --git a/tools/export_model.py b/tools/export_model.py deleted file mode 100644 index 0bd06b98..00000000 --- a/tools/export_model.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys -__dir__ = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(__dir__) -sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) - - -def set_paddle_flags(**kwargs): - for key, value in kwargs.items(): - if os.environ.get(key, None) is None: - os.environ[key] = str(value) - - -# NOTE(paddle-dev): All of these flags should be -# set before `import paddle`. Otherwise, it would -# not take any effect. -set_paddle_flags( - FLAGS_eager_delete_tensor_gb=0, # enable GC to save memory -) - -import program -from paddle import fluid -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from ppocr.utils.save_load import init_model - - - -def main(): - startup_prog, eval_program, place, config, _ = program.preprocess() - - feeded_var_names, target_vars, fetches_var_name = program.build_export( - config, eval_program, startup_prog) - eval_program = eval_program.clone(for_test=True) - exe = fluid.Executor(place) - exe.run(startup_prog) - - init_model(config, eval_program, exe) - - save_inference_dir = config['Global']['save_inference_dir'] - if not os.path.exists(save_inference_dir): - os.makedirs(save_inference_dir) - fluid.io.save_inference_model( - dirname=save_inference_dir, - feeded_var_names=feeded_var_names, - main_program=eval_program, - target_vars=target_vars, - executor=exe, - model_filename='model', - params_filename='params') - print("inference model saved in {}/model and {}/params".format( - save_inference_dir, save_inference_dir)) - print("save success, output_name_list:", fetches_var_name) - - -if __name__ == '__main__': - main() diff --git a/tools/infer/predict_det.py b/tools/infer/predict_det.py index 625f87ab..561627af 100755 --- a/tools/infer/predict_det.py +++ b/tools/infer/predict_det.py @@ -13,71 +13,63 @@ # limitations under the License. import os import sys + __dir__ = os.path.dirname(os.path.abspath(__file__)) sys.path.append(__dir__) sys.path.append(os.path.abspath(os.path.join(__dir__, '../..'))) import cv2 -import copy import numpy as np -import math import time import sys -import paddle.fluid as fluid +import paddle import tools.infer.utility as utility -from ppocr.utils.utility import initial_logger -logger = initial_logger() +from ppocr.utils.logging import get_logger from ppocr.utils.utility import get_image_file_list, check_and_read_gif -from ppocr.data.det.sast_process import SASTProcessTest -from ppocr.data.det.east_process import EASTProcessTest -from ppocr.data.det.db_process import DBProcessTest -from ppocr.postprocess.db_postprocess import DBPostProcess -from ppocr.postprocess.east_postprocess import EASTPostPocess -from ppocr.postprocess.sast_postprocess import SASTPostProcess +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process class TextDetector(object): def __init__(self, args): - max_side_len = args.det_max_side_len self.det_algorithm = args.det_algorithm self.use_zero_copy_run = args.use_zero_copy_run - preprocess_params = {'max_side_len': max_side_len} postprocess_params = {} if self.det_algorithm == "DB": - self.preprocess_op = DBProcessTest(preprocess_params) + pre_process_list = [{ + 'ResizeForTest': { + 'limit_side_len': args.det_limit_side_len, + 'limit_type': args.det_limit_type + } + }, { + 'NormalizeImage': { + 'std': [0.229, 0.224, 0.225], + 'mean': [0.485, 0.456, 0.406], + 'scale': '1./255.', + 'order': 'hwc' + } + }, { + 'ToCHWImage': None + }, { + 'keepKeys': { + 'keep_keys': ['image', 'shape'] + } + }] + postprocess_params['name'] = 'DBPostProcess' postprocess_params["thresh"] = args.det_db_thresh postprocess_params["box_thresh"] = args.det_db_box_thresh postprocess_params["max_candidates"] = 1000 postprocess_params["unclip_ratio"] = args.det_db_unclip_ratio - self.postprocess_op = DBPostProcess(postprocess_params) - elif self.det_algorithm == "EAST": - self.preprocess_op = EASTProcessTest(preprocess_params) - postprocess_params["score_thresh"] = args.det_east_score_thresh - postprocess_params["cover_thresh"] = args.det_east_cover_thresh - postprocess_params["nms_thresh"] = args.det_east_nms_thresh - self.postprocess_op = EASTPostPocess(postprocess_params) - elif self.det_algorithm == "SAST": - self.preprocess_op = SASTProcessTest(preprocess_params) - postprocess_params["score_thresh"] = args.det_sast_score_thresh - postprocess_params["nms_thresh"] = args.det_sast_nms_thresh - self.det_sast_polygon = args.det_sast_polygon - if self.det_sast_polygon: - postprocess_params["sample_pts_num"] = 6 - postprocess_params["expand_scale"] = 1.2 - postprocess_params["shrink_ratio_of_width"] = 0.2 - else: - postprocess_params["sample_pts_num"] = 2 - postprocess_params["expand_scale"] = 1.0 - postprocess_params["shrink_ratio_of_width"] = 0.3 - self.postprocess_op = SASTPostProcess(postprocess_params) else: logger.info("unknown det_algorithm:{}".format(self.det_algorithm)) sys.exit(0) - self.predictor, self.input_tensor, self.output_tensors =\ - utility.create_predictor(args, mode="det") + self.preprocess_op = create_operators(pre_process_list) + self.postprocess_op = build_post_process(postprocess_params) + self.predictor = paddle.jit.load(args.det_model_dir) + self.predictor.eval() def order_points_clockwise(self, pts): """ @@ -134,46 +126,31 @@ class TextDetector(object): def __call__(self, img): ori_im = img.copy() - im, ratio_list = self.preprocess_op(img) - if im is None: + data = {'image': img} + data = transform(data, self.preprocess_op) + img, shape_list = data + if img is None: return None, 0 - im = im.copy() + img = np.expand_dims(img, axis=0) + shape_list = np.expand_dims(shape_list, axis=0) starttime = time.time() - if self.use_zero_copy_run: - self.input_tensor.copy_from_cpu(im) - self.predictor.zero_copy_run() - else: - im = fluid.core.PaddleTensor(im) - self.predictor.run([im]) - outputs = [] - for output_tensor in self.output_tensors: - output = output_tensor.copy_to_cpu() - outputs.append(output) - outs_dict = {} - if self.det_algorithm == "EAST": - outs_dict['f_geo'] = outputs[0] - outs_dict['f_score'] = outputs[1] - elif self.det_algorithm == 'SAST': - outs_dict['f_border'] = outputs[0] - outs_dict['f_score'] = outputs[1] - outs_dict['f_tco'] = outputs[2] - outs_dict['f_tvo'] = outputs[3] - else: - outs_dict['maps'] = outputs[0] - dt_boxes_list = self.postprocess_op(outs_dict, [ratio_list]) - dt_boxes = dt_boxes_list[0] - if self.det_algorithm == "SAST" and self.det_sast_polygon: - dt_boxes = self.filter_tag_det_res_only_clip(dt_boxes, ori_im.shape) - else: - dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape) + preds = self.predictor(img) + post_result = self.postprocess_op(preds, shape_list) + + dt_boxes = post_result[0]['points'] + dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape) elapse = time.time() - starttime return dt_boxes, elapse if __name__ == "__main__": args = utility.parse_args() + place = paddle.CPUPlace() + paddle.disable_static(place) + image_file_list = get_image_file_list(args.image_dir) + logger = get_logger() text_detector = TextDetector(args) count = 0 total_time = 0 @@ -187,6 +164,7 @@ if __name__ == "__main__": if img is None: logger.info("error in loading image:{}".format(image_file)) continue + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) dt_boxes, elapse = text_detector(img) if count > 0: total_time += elapse diff --git a/tools/infer/utility.py b/tools/infer/utility.py index 9d7ce13d..dab06349 100755 --- a/tools/infer/utility.py +++ b/tools/infer/utility.py @@ -13,12 +13,7 @@ # limitations under the License. import argparse -import os, sys -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from paddle.fluid.core import PaddleTensor -from paddle.fluid.core import AnalysisConfig -from paddle.fluid.core import create_paddle_predictor +import os import cv2 import numpy as np import json @@ -41,7 +36,8 @@ def parse_args(): parser.add_argument("--image_dir", type=str) parser.add_argument("--det_algorithm", type=str, default='DB') parser.add_argument("--det_model_dir", type=str) - parser.add_argument("--det_max_side_len", type=float, default=960) + parser.add_argument("--det_limit_side_len", type=float, default=960) + parser.add_argument("--det_limit_type", type=str, default='max') #DB parmas parser.add_argument("--det_db_thresh", type=float, default=0.3) @@ -75,54 +71,6 @@ def parse_args(): return parser.parse_args() -def create_predictor(args, mode): - if mode == "det": - model_dir = args.det_model_dir - else: - model_dir = args.rec_model_dir - - if model_dir is None: - logger.info("not find {} model file path {}".format(mode, model_dir)) - sys.exit(0) - model_file_path = model_dir + "/model" - params_file_path = model_dir + "/params" - if not os.path.exists(model_file_path): - logger.info("not find model file path {}".format(model_file_path)) - sys.exit(0) - if not os.path.exists(params_file_path): - logger.info("not find params file path {}".format(params_file_path)) - sys.exit(0) - - config = AnalysisConfig(model_file_path, params_file_path) - - if args.use_gpu: - config.enable_use_gpu(args.gpu_mem, 0) - else: - config.disable_gpu() - config.set_cpu_math_library_num_threads(6) - if args.enable_mkldnn: - config.enable_mkldnn() - - #config.enable_memory_optim() - config.disable_glog_info() - - if args.use_zero_copy_run: - config.delete_pass("conv_transpose_eltwiseadd_bn_fuse_pass") - config.switch_use_feed_fetch_ops(False) - else: - config.switch_use_feed_fetch_ops(True) - - predictor = create_paddle_predictor(config) - input_names = predictor.get_input_names() - input_tensor = predictor.get_input_tensor(input_names[0]) - output_names = predictor.get_output_names() - output_tensors = [] - for output_name in output_names: - output_tensor = predictor.get_output_tensor(output_name) - output_tensors.append(output_tensor) - return predictor, input_tensor, output_tensors - - def draw_text_det_res(dt_boxes, img_path): src_im = cv2.imread(img_path) for box in dt_boxes: @@ -139,8 +87,8 @@ def resize_img(img, input_size=600): im_shape = img.shape im_size_max = np.max(im_shape[0:2]) im_scale = float(input_size) / float(im_size_max) - im = cv2.resize(img, None, None, fx=im_scale, fy=im_scale) - return im + img = cv2.resize(img, None, None, fx=im_scale, fy=im_scale) + return img def draw_ocr(image, diff --git a/tools/infer_det.py b/tools/infer_det.py index 1e7fdcc4..8e6b6b21 100755 --- a/tools/infer_det.py +++ b/tools/infer_det.py @@ -17,38 +17,25 @@ from __future__ import division from __future__ import print_function import numpy as np -from copy import deepcopy -import json import os import sys + __dir__ = os.path.dirname(os.path.abspath(__file__)) sys.path.append(__dir__) sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) - -def set_paddle_flags(**kwargs): - for key, value in kwargs.items(): - if os.environ.get(key, None) is None: - os.environ[key] = str(value) - - -# NOTE(paddle-dev): All of these flags should be -# set before `import paddle`. Otherwise, it would -# not take any effect. -set_paddle_flags( - FLAGS_eager_delete_tensor_gb=0, # enable GC to save memory -) - -from paddle import fluid -from ppocr.utils.utility import create_module, get_image_file_list -import program -from ppocr.utils.save_load import init_model -from ppocr.data.reader_main import reader_main import cv2 +import json +import paddle -from ppocr.utils.utility import initial_logger -logger = initial_logger() +from ppocr.utils.logging import get_logger +from ppocr.data import create_operators, transform +from ppocr.modeling import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import init_model +from ppocr.utils.utility import print_dict, get_image_file_list +import tools.program as program def draw_det_res(dt_boxes, config, img, img_name): @@ -68,94 +55,68 @@ def draw_det_res(dt_boxes, config, img, img_name): def main(): - config = program.load_config(FLAGS.config) - program.merge_config(FLAGS.opt) - logger.info(config) - - # check if set use_gpu=True in paddlepaddle cpu version - use_gpu = config['Global']['use_gpu'] - program.check_gpu(use_gpu) - - place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace() - exe = fluid.Executor(place) - - det_model = create_module(config['Architecture']['function'])(params=config) - - startup_prog = fluid.Program() - eval_prog = fluid.Program() - with fluid.program_guard(eval_prog, startup_prog): - with fluid.unique_name.guard(): - _, eval_outputs = det_model(mode="test") - fetch_name_list = list(eval_outputs.keys()) - eval_fetch_list = [eval_outputs[v].name for v in fetch_name_list] - - eval_prog = eval_prog.clone(for_test=True) - exe.run(startup_prog) - - # load checkpoints - checkpoints = config['Global'].get('checkpoints') - if checkpoints: - path = checkpoints - fluid.load(eval_prog, path, exe) - logger.info("Finish initing model from {}".format(path)) - else: - raise Exception("{} not exists!".format(checkpoints)) + global_config = config['Global'] + + # build model + model = build_model(config['Architecture']) + + init_model(config, model, logger) + + # build post process + post_process_class = build_post_process(config['PostProcess']) + + # create data ops + transforms = [] + for op in config['EVAL']['dataset']['transforms']: + op_name = list(op)[0] + if 'Label' in op_name: + continue + elif op_name == 'keepKeys': + op[op_name]['keep_keys'] = ['image', 'shape'] + transforms.append(op) + + ops = create_operators(transforms, global_config) save_res_path = config['Global']['save_res_path'] if not os.path.exists(os.path.dirname(save_res_path)): os.makedirs(os.path.dirname(save_res_path)) - with open(save_res_path, "wb") as fout: - test_reader = reader_main(config=config, mode='test') - tackling_num = 0 - for data in test_reader(): - img_num = len(data) - tackling_num = tackling_num + img_num - logger.info("tackling_num:%d", tackling_num) - img_list = [] - ratio_list = [] - img_name_list = [] - for ino in range(img_num): - img_list.append(data[ino][0]) - ratio_list.append(data[ino][1]) - img_name_list.append(data[ino][2]) - - img_list = np.concatenate(img_list, axis=0) - outs = exe.run(eval_prog,\ - feed={'image': img_list},\ - fetch_list=eval_fetch_list) - - global_params = config['Global'] - postprocess_params = deepcopy(config["PostProcess"]) - postprocess_params.update(global_params) - postprocess = create_module(postprocess_params['function'])\ - (params=postprocess_params) - if config['Global']['algorithm'] == 'EAST': - dic = {'f_score': outs[0], 'f_geo': outs[1]} - elif config['Global']['algorithm'] == 'DB': - dic = {'maps': outs[0]} - elif config['Global']['algorithm'] == 'SAST': - dic = {'f_score': outs[0], 'f_border': outs[1], 'f_tvo': outs[2], 'f_tco': outs[3]} - else: - raise Exception("only support algorithm: ['EAST', 'DB', 'SAST']") - dt_boxes_list = postprocess(dic, ratio_list) - for ino in range(img_num): - dt_boxes = dt_boxes_list[ino] - img_name = img_name_list[ino] - dt_boxes_json = [] - for box in dt_boxes: - tmp_json = {"transcription": ""} - tmp_json['points'] = box.tolist() - dt_boxes_json.append(tmp_json) - otstr = img_name + "\t" + json.dumps(dt_boxes_json) + "\n" - fout.write(otstr.encode()) - src_img = cv2.imread(img_name) - draw_det_res(dt_boxes, config, src_img, img_name) - + model.eval() + with open(save_res_path, "wb") as fout: + for file in get_image_file_list(config['Global']['infer_img']): + logger.info("infer_img: {}".format(file)) + with open(file, 'rb') as f: + img = f.read() + data = {'image': img} + batch = transform(data, ops) + + images = np.expand_dims(batch[0], axis=0) + shape_list = np.expand_dims(batch[1], axis=0) + images = paddle.to_variable(images) + print(images.shape) + preds = model(images) + post_result = post_process_class(preds, shape_list) + boxes = post_result[0]['points'] + # write resule + dt_boxes_json = [] + for box in boxes: + tmp_json = {"transcription": ""} + tmp_json['points'] = box.tolist() + dt_boxes_json.append(tmp_json) + otstr = file + "\t" + json.dumps(dt_boxes_json) + "\n" + fout.write(otstr.encode()) + src_img = cv2.imread(file) + draw_det_res(boxes, config, src_img, file) logger.info("success!") + # save inference model + # paddle.jit.save(model, 'output/model') + if __name__ == '__main__': - parser = program.ArgsParser() - FLAGS = parser.parse_args() + place, config = program.preprocess() + paddle.disable_static(place) + + logger = get_logger() + print_dict(config, logger) main() diff --git a/tools/infer_rec.py b/tools/infer_rec.py index fd70cd66..239d2dcb 100755 --- a/tools/infer_rec.py +++ b/tools/infer_rec.py @@ -17,160 +17,80 @@ from __future__ import division from __future__ import print_function import numpy as np + import os import sys + __dir__ = os.path.dirname(os.path.abspath(__file__)) sys.path.append(__dir__) sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) +import paddle -def set_paddle_flags(**kwargs): - for key, value in kwargs.items(): - if os.environ.get(key, None) is None: - os.environ[key] = str(value) - - -# NOTE(paddle-dev): All of these flags should be -# set before `import paddle`. Otherwise, it would -# not take any effect. -set_paddle_flags( - FLAGS_eager_delete_tensor_gb=0, # enable GC to save memory -) - -import tools.program as program -from paddle import fluid -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from ppocr.data.reader_main import reader_main +from ppocr.utils.logging import get_logger +from ppocr.data import create_operators, transform +from ppocr.modeling import build_model +from ppocr.postprocess import build_post_process from ppocr.utils.save_load import init_model -from ppocr.utils.character import CharacterOps -from ppocr.utils.utility import create_module -from ppocr.utils.utility import get_image_file_list +from ppocr.utils.utility import print_dict, get_image_file_list +import tools.program as program def main(): - config = program.load_config(FLAGS.config) - program.merge_config(FLAGS.opt) - logger.info(config) - char_ops = CharacterOps(config['Global']) - loss_type = config['Global']['loss_type'] - config['Global']['char_ops'] = char_ops - - # check if set use_gpu=True in paddlepaddle cpu version - use_gpu = config['Global']['use_gpu'] - # check_gpu(use_gpu) - - place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace() - exe = fluid.Executor(place) - - rec_model = create_module(config['Architecture']['function'])(params=config) - startup_prog = fluid.Program() - eval_prog = fluid.Program() - with fluid.program_guard(eval_prog, startup_prog): - with fluid.unique_name.guard(): - _, outputs = rec_model(mode="test") - fetch_name_list = list(outputs.keys()) - fetch_varname_list = [outputs[v].name for v in fetch_name_list] - eval_prog = eval_prog.clone(for_test=True) - exe.run(startup_prog) - - init_model(config, eval_prog, exe) - - blobs = reader_main(config, 'test')() - infer_img = config['Global']['infer_img'] - infer_list = get_image_file_list(infer_img) - max_img_num = len(infer_list) - if len(infer_list) == 0: - logger.info("Can not find img in infer_img dir.") - for i in range(max_img_num): - logger.info("infer_img:%s" % infer_list[i]) - img = next(blobs) - if loss_type != "srn": - predict = exe.run(program=eval_prog, - feed={"image": img}, - fetch_list=fetch_varname_list, - return_numpy=False) - else: - encoder_word_pos_list = [] - gsrm_word_pos_list = [] - gsrm_slf_attn_bias1_list = [] - gsrm_slf_attn_bias2_list = [] - encoder_word_pos_list.append(img[1]) - gsrm_word_pos_list.append(img[2]) - gsrm_slf_attn_bias1_list.append(img[3]) - gsrm_slf_attn_bias2_list.append(img[4]) - - encoder_word_pos_list = np.concatenate( - encoder_word_pos_list, axis=0).astype(np.int64) - gsrm_word_pos_list = np.concatenate( - gsrm_word_pos_list, axis=0).astype(np.int64) - gsrm_slf_attn_bias1_list = np.concatenate( - gsrm_slf_attn_bias1_list, axis=0).astype(np.float32) - gsrm_slf_attn_bias2_list = np.concatenate( - gsrm_slf_attn_bias2_list, axis=0).astype(np.float32) - - predict = exe.run(program=eval_prog, \ - feed={'image': img[0], 'encoder_word_pos': encoder_word_pos_list, - 'gsrm_word_pos': gsrm_word_pos_list, 'gsrm_slf_attn_bias1': gsrm_slf_attn_bias1_list, - 'gsrm_slf_attn_bias2': gsrm_slf_attn_bias2_list}, \ - fetch_list=fetch_varname_list, \ - return_numpy=False) - if loss_type == "ctc": - preds = np.array(predict[0]) - preds = preds.reshape(-1) - preds_lod = predict[0].lod()[0] - preds_text = char_ops.decode(preds) - probs = np.array(predict[1]) - ind = np.argmax(probs, axis=1) - blank = probs.shape[1] - valid_ind = np.where(ind != (blank - 1))[0] - if len(valid_ind) == 0: - continue - score = np.mean(probs[valid_ind, ind[valid_ind]]) - elif loss_type == "attention": - preds = np.array(predict[0]) - probs = np.array(predict[1]) - end_pos = np.where(preds[0, :] == 1)[0] - if len(end_pos) <= 1: - preds = preds[0, 1:] - score = np.mean(probs[0, 1:]) - else: - preds = preds[0, 1:end_pos[1]] - score = np.mean(probs[0, 1:end_pos[1]]) - preds = preds.reshape(-1) - preds_text = char_ops.decode(preds) - elif loss_type == "srn": - char_num = char_ops.get_char_num() - preds = np.array(predict[0]) - preds = preds.reshape(-1) - probs = np.array(predict[1]) - ind = np.argmax(probs, axis=1) - valid_ind = np.where(preds != int(char_num-1))[0] - if len(valid_ind) == 0: - continue - score = np.mean(probs[valid_ind, ind[valid_ind]]) - preds = preds[:valid_ind[-1] + 1] - preds_text = char_ops.decode(preds) - logger.info("\t index: {}".format(preds)) - logger.info("\t word : {}".format(preds_text)) - logger.info("\t score: {}".format(score)) - - # save for inference model - target_var = [] - for key, values in outputs.items(): - target_var.append(values) - - fluid.io.save_inference_model( - "./output/", - feeded_var_names=['image'], - target_vars=target_var, - executor=exe, - main_program=eval_prog, - model_filename="model", - params_filename="params") + global_config = config['Global'] + + # build post process + post_process_class = build_post_process(config['PostProcess'], + global_config) + + # build model + if hasattr(post_process_class, 'character'): + config['Architecture']["Head"]['out_channels'] = len( + getattr(post_process_class, 'character')) + + model = build_model(config['Architecture']) + + init_model(config, model, logger) + + # create data ops + transforms = [] + for op in config['EVAL']['dataset']['transforms']: + op_name = list(op)[0] + if 'Label' in op_name: + continue + elif op_name in ['RecResizeImg']: + op[op_name]['infer_mode'] = True + elif op_name == 'keepKeys': + op[op_name]['keep_keys'] = ['image'] + transforms.append(op) + global_config['infer_mode'] = True + ops = create_operators(transforms, global_config) + + model.eval() + for file in get_image_file_list(config['Global']['infer_img']): + logger.info("infer_img: {}".format(file)) + with open(file, 'rb') as f: + img = f.read() + data = {'image': img} + batch = transform(data, ops) + + images = np.expand_dims(batch[0], axis=0) + images = paddle.to_variable(images) + preds = model(images) + post_result = post_process_class(preds) + for rec_reuslt in post_result: + logger.info('\t result: {}'.format(rec_reuslt)) + logger.info("success!") + + # save inference model + # currently, paddle.jit.to_static not support rnn + # paddle.jit.save(model, 'output/rec/model') if __name__ == '__main__': - parser = program.ArgsParser() - FLAGS = parser.parse_args() + place, config = program.preprocess() + paddle.disable_static(place) + + logger = get_logger() + print_dict(config, logger) main() diff --git a/tools/program.py b/tools/program.py index 6d8b9937..7282ff67 100755 --- a/tools/program.py +++ b/tools/program.py @@ -16,23 +16,18 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from argparse import ArgumentParser, RawDescriptionHelpFormatter +import os import sys import yaml -import os -from ppocr.utils.utility import create_module -from ppocr.utils.utility import initial_logger - -logger = initial_logger() - -import paddle.fluid as fluid import time +import shutil +import paddle +import paddle.distributed as dist +from tqdm import tqdm +from argparse import ArgumentParser, RawDescriptionHelpFormatter + from ppocr.utils.stats import TrainingStats -from eval_utils.eval_det_utils import eval_det_run -from eval_utils.eval_rec_utils import eval_rec_run from ppocr.utils.save_load import save_model -import numpy as np -from ppocr.utils.character import cal_predicts_accuracy, cal_predicts_accuracy_srn, CharacterOps class ArgsParser(ArgumentParser): @@ -89,13 +84,7 @@ def load_config(file_path): merge_config(default_config) _, ext = os.path.splitext(file_path) assert ext in ['.yml', '.yaml'], "only support yaml files for now" - merge_config(yaml.load(open(file_path), Loader=yaml.Loader)) - assert "reader_yml" in global_config['Global'],\ - "absence reader_yml in global" - reader_file_path = global_config['Global']['reader_yml'] - _, ext = os.path.splitext(reader_file_path) - assert ext in ['.yml', '.yaml'], "only support yaml files for reader" - merge_config(yaml.load(open(reader_file_path), Loader=yaml.Loader)) + merge_config(yaml.load(open(file_path, 'rb'), Loader=yaml.Loader)) return global_config @@ -139,102 +128,34 @@ def check_gpu(use_gpu): "model on CPU" try: - if use_gpu and not fluid.is_compiled_with_cuda(): - logger.error(err) + if use_gpu and not paddle.fluid.is_compiled_with_cuda(): + print(err) sys.exit(1) except Exception as e: pass -def build(config, main_prog, startup_prog, mode): - """ - Build a program using a model and an optimizer - 1. create feeds - 2. create a dataloader - 3. create a model - 4. create fetchs - 5. create an optimizer - Args: - config(dict): config - main_prog(): main program - startup_prog(): startup program - is_train(bool): train or valid - Returns: - dataloader(): a bridge between the model and the data - fetchs(dict): dict of model outputs(included loss and measures) - """ - with fluid.program_guard(main_prog, startup_prog): - with fluid.unique_name.guard(): - func_infor = config['Architecture']['function'] - model = create_module(func_infor)(params=config) - dataloader, outputs = model(mode=mode) - fetch_name_list = list(outputs.keys()) - fetch_varname_list = [outputs[v].name for v in fetch_name_list] - opt_loss_name = None - model_average = None - img_loss_name = None - word_loss_name = None - if mode == "train": - opt_loss = outputs['total_loss'] - # srn loss - #img_loss = outputs['img_loss'] - #word_loss = outputs['word_loss'] - #img_loss_name = img_loss.name - #word_loss_name = word_loss.name - opt_params = config['Optimizer'] - optimizer = create_module(opt_params['function'])(opt_params) - optimizer.minimize(opt_loss) - opt_loss_name = opt_loss.name - global_lr = optimizer._global_learning_rate() - fetch_name_list.insert(0, "lr") - fetch_varname_list.insert(0, global_lr.name) - if "loss_type" in config["Global"]: - if config['Global']["loss_type"] == 'srn': - model_average = fluid.optimizer.ModelAverage( - config['Global']['average_window'], - min_average_window=config['Global'][ - 'min_average_window'], - max_average_window=config['Global'][ - 'max_average_window']) - - return (dataloader, fetch_name_list, fetch_varname_list, opt_loss_name, - model_average) - - -def build_export(config, main_prog, startup_prog): - """ - """ - with fluid.program_guard(main_prog, startup_prog): - with fluid.unique_name.guard(): - func_infor = config['Architecture']['function'] - model = create_module(func_infor)(params=config) - image, outputs = model(mode='export') - fetches_var_name = sorted([name for name in outputs.keys()]) - fetches_var = [outputs[name] for name in fetches_var_name] - feeded_var_names = [image.name] - target_vars = fetches_var - return feeded_var_names, target_vars, fetches_var_name - - -def create_multi_devices_program(program, loss_var_name): - build_strategy = fluid.BuildStrategy() - build_strategy.memory_optimize = False - build_strategy.enable_inplace = True - exec_strategy = fluid.ExecutionStrategy() - exec_strategy.num_iteration_per_drop_scope = 1 - compile_program = fluid.CompiledProgram(program).with_data_parallel( - loss_name=loss_var_name, - build_strategy=build_strategy, - exec_strategy=exec_strategy) - return compile_program - - -def train_eval_det_run(config, exe, train_info_dict, eval_info_dict): - train_batch_id = 0 +def train(config, + model, + loss_class, + optimizer, + lr_scheduler, + train_dataloader, + valid_dataloader, + post_process_class, + eval_class, + pre_best_model_dict, + logger, + vdl_writer=None): + global_step = 0 + + cal_metric_during_train = config['Global'].get('cal_metric_during_train', + False) log_smooth_window = config['Global']['log_smooth_window'] epoch_num = config['Global']['epoch_num'] print_batch_step = config['Global']['print_batch_step'] eval_batch_step = config['Global']['eval_batch_step'] + start_eval_step = 0 if type(eval_batch_step) == list and len(eval_batch_step) >= 2: start_eval_step = eval_batch_step[0] @@ -246,180 +167,173 @@ def train_eval_det_run(config, exe, train_info_dict, eval_info_dict): save_model_dir = config['Global']['save_model_dir'] if not os.path.exists(save_model_dir): os.makedirs(save_model_dir) - train_stats = TrainingStats(log_smooth_window, - train_info_dict['fetch_name_list']) - best_eval_hmean = -1 - best_batch_id = 0 - best_epoch = 0 - train_loader = train_info_dict['reader'] - for epoch in range(epoch_num): - train_loader.start() - try: - while True: - t1 = time.time() - train_outs = exe.run( - program=train_info_dict['compile_program'], - fetch_list=train_info_dict['fetch_varname_list'], - return_numpy=False) - stats = {} - for tno in range(len(train_outs)): - fetch_name = train_info_dict['fetch_name_list'][tno] - fetch_value = np.mean(np.array(train_outs[tno])) - stats[fetch_name] = fetch_value - t2 = time.time() - train_batch_elapse = t2 - t1 - train_stats.update(stats) - if train_batch_id > 0 and train_batch_id \ - % print_batch_step == 0: - logs = train_stats.log() - strs = 'epoch: {}, iter: {}, {}, time: {:.3f}'.format( - epoch, train_batch_id, logs, train_batch_elapse) - logger.info(strs) - - if train_batch_id > start_eval_step and\ - (train_batch_id - start_eval_step) % eval_batch_step == 0: - metrics = eval_det_run(exe, config, eval_info_dict, "eval") - hmean = metrics['hmean'] - if hmean >= best_eval_hmean: - best_eval_hmean = hmean - best_batch_id = train_batch_id - best_epoch = epoch - save_path = save_model_dir + "/best_accuracy" - save_model(train_info_dict['train_program'], save_path) - strs = 'Test iter: {}, metrics:{}, best_hmean:{:.6f}, best_epoch:{}, best_batch_id:{}'.format( - train_batch_id, metrics, best_eval_hmean, best_epoch, - best_batch_id) - logger.info(strs) - train_batch_id += 1 - - except fluid.core.EOFException: - train_loader.reset() - if epoch == 0 and save_epoch_step == 1: - save_path = save_model_dir + "/iter_epoch_0" - save_model(train_info_dict['train_program'], save_path) - if epoch > 0 and epoch % save_epoch_step == 0: - save_path = save_model_dir + "/iter_epoch_%d" % (epoch) - save_model(train_info_dict['train_program'], save_path) + main_indicator = eval_class.main_indicator + best_model_dict = {main_indicator: 0} + best_model_dict.update(pre_best_model_dict) + train_stats = TrainingStats(log_smooth_window, ['lr']) + model.train() + + if 'start_epoch' in best_model_dict: + start_epoch = best_model_dict['start_epoch'] + else: + start_epoch = 0 + + for epoch in range(start_epoch, epoch_num): + for idx, batch in enumerate(train_dataloader): + if idx >= len(train_dataloader): + break + if not isinstance(lr_scheduler, float): + lr_scheduler.step() + lr = optimizer.get_lr() + t1 = time.time() + batch = [paddle.to_variable(x) for x in batch] + images = batch[0] + preds = model(images) + loss = loss_class(preds, batch) + avg_loss = loss['loss'] + if config['Global']['distributed']: + avg_loss = model.scale_loss(avg_loss) + avg_loss.backward() + model.apply_collective_grads() + else: + avg_loss.backward() + optimizer.step() + optimizer.clear_grad() + + # logger and visualdl + stats = {k: v.numpy().mean() for k, v in loss.items()} + stats['lr'] = lr + train_stats.update(stats) + + if cal_metric_during_train: # onlt rec and cls need + batch = [item.numpy() for item in batch] + post_result = post_process_class(preds, batch[1]) + eval_class(post_result, batch) + metirc = eval_class.get_metric() + train_stats.update(metirc) + + t2 = time.time() + train_batch_elapse = t2 - t1 + + if vdl_writer is not None and dist.get_rank() == 0: + for k, v in train_stats.get().items(): + vdl_writer.add_scalar('TRAIN/{}'.format(k), v, global_step) + vdl_writer.add_scalar('TRAIN/lr', lr, global_step) + + if global_step > 0 and global_step % print_batch_step == 0: + logs = train_stats.log() + strs = 'epoch: [{}/{}], iter: {}, {}, time: {:.3f}'.format( + epoch, epoch_num, global_step, logs, train_batch_elapse) + logger.info(strs) + # eval + if global_step > start_eval_step and \ + (global_step - start_eval_step) % eval_batch_step == 0 and dist.get_rank() == 0: + cur_metirc = eval(model, valid_dataloader, post_process_class, + eval_class) + cur_metirc_str = 'cur metirc, {}'.format(', '.join( + ['{}: {}'.format(k, v) for k, v in cur_metirc.items()])) + logger.info(cur_metirc_str) + + # logger metric + if vdl_writer is not None: + for k, v in cur_metirc.items(): + if isinstance(v, (float, int)): + vdl_writer.add_scalar('EVAL/{}'.format(k), + cur_metirc[k], global_step) + if cur_metirc[main_indicator] >= best_model_dict[ + main_indicator]: + best_model_dict.update(cur_metirc) + best_model_dict['best_epoch'] = epoch + save_model( + model, + optimizer, + save_model_dir, + logger, + is_best=True, + prefix='best_accuracy', + best_model_dict=best_model_dict, + epoch=epoch) + best_str = 'best metirc, {}'.format(', '.join([ + '{}: {}'.format(k, v) for k, v in best_model_dict.items() + ])) + logger.info(best_str) + # logger best metric + if vdl_writer is not None: + vdl_writer.add_scalar('EVAL/best_{}'.format(main_indicator), + best_model_dict[main_indicator], + global_step) + global_step += 1 + if dist.get_rank() == 0: + save_model( + model, + optimizer, + save_model_dir, + logger, + is_best=False, + prefix='latest', + best_model_dict=best_model_dict, + epoch=epoch) + if dist.get_rank() == 0 and epoch > 0 and epoch % save_epoch_step == 0: + save_model( + model, + optimizer, + save_model_dir, + logger, + is_best=False, + prefix='iter_epoch_{}'.format(epoch), + best_model_dict=best_model_dict, + epoch=epoch) + best_str = 'best metirc, {}'.format(', '.join( + ['{}: {}'.format(k, v) for k, v in best_model_dict.items()])) + logger.info(best_str) + if dist.get_rank() == 0 and vdl_writer is not None: + vdl_writer.close() return -def train_eval_rec_run(config, exe, train_info_dict, eval_info_dict): - train_batch_id = 0 - log_smooth_window = config['Global']['log_smooth_window'] - epoch_num = config['Global']['epoch_num'] - print_batch_step = config['Global']['print_batch_step'] - eval_batch_step = config['Global']['eval_batch_step'] - start_eval_step = 0 - if type(eval_batch_step) == list and len(eval_batch_step) >= 2: - start_eval_step = eval_batch_step[0] - eval_batch_step = eval_batch_step[1] - logger.info( - "During the training process, after the {}th iteration, an evaluation is run every {} iterations". - format(start_eval_step, eval_batch_step)) - save_epoch_step = config['Global']['save_epoch_step'] - save_model_dir = config['Global']['save_model_dir'] - if not os.path.exists(save_model_dir): - os.makedirs(save_model_dir) - train_stats = TrainingStats(log_smooth_window, ['loss', 'acc']) - best_eval_acc = -1 - best_batch_id = 0 - best_epoch = 0 - train_loader = train_info_dict['reader'] - for epoch in range(epoch_num): - train_loader.start() - try: - while True: - t1 = time.time() - train_outs = exe.run( - program=train_info_dict['compile_program'], - fetch_list=train_info_dict['fetch_varname_list'], - return_numpy=False) - fetch_map = dict( - zip(train_info_dict['fetch_name_list'], - range(len(train_outs)))) - - loss = np.mean(np.array(train_outs[fetch_map['total_loss']])) - lr = np.mean(np.array(train_outs[fetch_map['lr']])) - preds_idx = fetch_map['decoded_out'] - preds = np.array(train_outs[preds_idx]) - labels_idx = fetch_map['label'] - labels = np.array(train_outs[labels_idx]) - - if config['Global']['loss_type'] != 'srn': - preds_lod = train_outs[preds_idx].lod()[0] - labels_lod = train_outs[labels_idx].lod()[0] - - acc, acc_num, img_num = cal_predicts_accuracy( - config['Global']['char_ops'], preds, preds_lod, labels, - labels_lod) - else: - acc, acc_num, img_num = cal_predicts_accuracy_srn( - config['Global']['char_ops'], preds, labels, - config['Global']['max_text_length']) - t2 = time.time() - train_batch_elapse = t2 - t1 - stats = {'loss': loss, 'acc': acc} - train_stats.update(stats) - if train_batch_id > start_eval_step and (train_batch_id - start_eval_step) \ - % print_batch_step == 0: - logs = train_stats.log() - strs = 'epoch: {}, iter: {}, lr: {:.6f}, {}, time: {:.3f}'.format( - epoch, train_batch_id, lr, logs, train_batch_elapse) - logger.info(strs) - - if train_batch_id > 0 and\ - train_batch_id % eval_batch_step == 0: - model_average = train_info_dict['model_average'] - if model_average != None: - model_average.apply(exe) - metrics = eval_rec_run(exe, config, eval_info_dict, "eval") - eval_acc = metrics['avg_acc'] - eval_sample_num = metrics['total_sample_num'] - if eval_acc > best_eval_acc: - best_eval_acc = eval_acc - best_batch_id = train_batch_id - best_epoch = epoch - save_path = save_model_dir + "/best_accuracy" - save_model(train_info_dict['train_program'], save_path) - strs = 'Test iter: {}, acc:{:.6f}, best_acc:{:.6f}, best_epoch:{}, best_batch_id:{}, eval_sample_num:{}'.format( - train_batch_id, eval_acc, best_eval_acc, best_epoch, - best_batch_id, eval_sample_num) - logger.info(strs) - train_batch_id += 1 - - except fluid.core.EOFException: - train_loader.reset() - if epoch == 0 and save_epoch_step == 1: - save_path = save_model_dir + "/iter_epoch_0" - save_model(train_info_dict['train_program'], save_path) - if epoch > 0 and epoch % save_epoch_step == 0: - save_path = save_model_dir + "/iter_epoch_%d" % (epoch) - save_model(train_info_dict['train_program'], save_path) - return +def eval(model, valid_dataloader, post_process_class, eval_class): + model.eval() + with paddle.no_grad(): + total_frame = 0.0 + total_time = 0.0 + pbar = tqdm(total=len(valid_dataloader), desc='eval model: ') + for idx, batch in enumerate(valid_dataloader): + if idx >= len(valid_dataloader): + break + images = paddle.to_variable(batch[0]) + start = time.time() + preds = model(images) + + batch = [item.numpy() for item in batch] + # Obtain usable results from post-processing methods + post_result = post_process_class(preds, batch[1]) + total_time += time.time() - start + # Evaluate the results of the current batch + eval_class(post_result, batch) + pbar.update(1) + total_frame += len(images) + # Get final metirc,eg. acc or hmean + metirc = eval_class.get_metric() + pbar.close() + model.train() + metirc['fps'] = total_frame / total_time + return metirc def preprocess(): FLAGS = ArgsParser().parse_args() config = load_config(FLAGS.config) merge_config(FLAGS.opt) - logger.info(config) # check if set use_gpu=True in paddlepaddle cpu version use_gpu = config['Global']['use_gpu'] check_gpu(use_gpu) - alg = config['Global']['algorithm'] - assert alg in ['EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN'] - if alg in ['Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN']: - config['Global']['char_ops'] = CharacterOps(config['Global']) - - place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace() - startup_program = fluid.Program() - train_program = fluid.Program() - - if alg in ['EAST', 'DB', 'SAST']: - train_alg_type = 'det' - else: - train_alg_type = 'rec' + alg = config['Architecture']['algorithm'] + assert alg in [ + 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN' + ] - return startup_program, train_program, place, config, train_alg_type + device = 'gpu:{}'.format(dist.ParallelEnv().dev_id) if use_gpu else 'cpu' + device = paddle.set_device(device) + return device, config diff --git a/tools/train.py b/tools/train.py index 300705e0..7886316b 100755 --- a/tools/train.py +++ b/tools/train.py @@ -18,107 +18,122 @@ from __future__ import print_function import os import sys + __dir__ = os.path.dirname(os.path.abspath(__file__)) sys.path.append(__dir__) sys.path.append(os.path.abspath(os.path.join(__dir__, '..'))) +import yaml +import paddle +import paddle.distributed as dist -def set_paddle_flags(**kwargs): - for key, value in kwargs.items(): - if os.environ.get(key, None) is None: - os.environ[key] = str(value) +paddle.manual_seed(2) +from ppocr.utils.logging import get_logger +from ppocr.data import build_dataloader +from ppocr.modeling import build_model, build_loss +from ppocr.optimizer import build_optimizer +from ppocr.postprocess import build_post_process +from ppocr.metrics import build_metric +from ppocr.utils.save_load import init_model +from ppocr.utils.utility import print_dict +import tools.program as program -# NOTE(paddle-dev): All of these flags should be -# set before `import paddle`. Otherwise, it would -# not take any effect. -set_paddle_flags( - FLAGS_eager_delete_tensor_gb=0, # enable GC to save memory -) +dist.get_world_size() -import tools.program as program -from paddle import fluid -from ppocr.utils.utility import initial_logger -logger = initial_logger() -from ppocr.data.reader_main import reader_main -from ppocr.utils.save_load import init_model -from paddle.fluid.contrib.model_stat import summary - - -def main(): - train_build_outputs = program.build( - config, train_program, startup_program, mode='train') - train_loader = train_build_outputs[0] - train_fetch_name_list = train_build_outputs[1] - train_fetch_varname_list = train_build_outputs[2] - train_opt_loss_name = train_build_outputs[3] - model_average = train_build_outputs[-1] - - eval_program = fluid.Program() - eval_build_outputs = program.build( - config, eval_program, startup_program, mode='eval') - eval_fetch_name_list = eval_build_outputs[1] - eval_fetch_varname_list = eval_build_outputs[2] - eval_program = eval_program.clone(for_test=True) - - train_reader = reader_main(config=config, mode="train") - train_loader.set_sample_list_generator(train_reader, places=place) - - eval_reader = reader_main(config=config, mode="eval") - - exe = fluid.Executor(place) - exe.run(startup_program) - - # compile program for multi-devices - train_compile_program = program.create_multi_devices_program( - train_program, train_opt_loss_name) - - # dump mode structure - if config['Global']['debug']: - if train_alg_type == 'rec' and 'attention' in config['Global']['loss_type']: - logger.warning('Does not suport dump attention...') - else: - summary(train_program) - - init_model(config, train_program, exe) - - train_info_dict = {'compile_program':train_compile_program,\ - 'train_program':train_program,\ - 'reader':train_loader,\ - 'fetch_name_list':train_fetch_name_list,\ - 'fetch_varname_list':train_fetch_varname_list,\ - 'model_average': model_average} - - eval_info_dict = {'program':eval_program,\ - 'reader':eval_reader,\ - 'fetch_name_list':eval_fetch_name_list,\ - 'fetch_varname_list':eval_fetch_varname_list} - - if train_alg_type == 'det': - program.train_eval_det_run(config, exe, train_info_dict, eval_info_dict) - else: - program.train_eval_rec_run(config, exe, train_info_dict, eval_info_dict) +def main(config, device, logger, vdl_writer): + # init dist environment + if config['Global']['distributed']: + dist.init_parallel_env() -def test_reader(): - logger.info(config) - train_reader = reader_main(config=config, mode="train") + global_config = config['Global'] + # build dataloader + train_loader, train_info_dict = build_dataloader( + config['TRAIN'], device, global_config['distributed'], global_config) + if config['EVAL']: + eval_loader, _ = build_dataloader(config['EVAL'], device, False, + global_config) + else: + eval_loader = None + # build post process + post_process_class = build_post_process(config['PostProcess'], + global_config) + # build model + # for rec algorithm + if hasattr(post_process_class, 'character'): + config['Architecture']["Head"]['out_channels'] = len( + getattr(post_process_class, 'character')) + model = build_model(config['Architecture']) + if config['Global']['distributed']: + model = paddle.DataParallel(model) + + # build optim + optimizer, lr_scheduler = build_optimizer( + config['Optimizer'], + epochs=config['Global']['epoch_num'], + step_each_epoch=len(train_loader), + parameters=model.parameters()) + + best_model_dict = init_model(config, model, logger, optimizer) + + # build loss + loss_class = build_loss(config['Loss']) + # build metric + eval_class = build_metric(config['Metric']) + + # start train + program.train(config, model, loss_class, optimizer, lr_scheduler, + train_loader, eval_loader, post_process_class, eval_class, + best_model_dict, logger, vdl_writer) + + +def test_reader(config, place, logger): + train_loader = build_dataloader(config['TRAIN'], place) import time starttime = time.time() count = 0 try: - for data in train_reader(): + for data in train_loader(): count += 1 if count % 1 == 0: batch_time = time.time() - starttime starttime = time.time() - logger.info("reader:", count, len(data), batch_time) + logger.info("reader: {}, {}, {}".format(count, + len(data), batch_time)) except Exception as e: logger.info(e) logger.info("finish reader: {}, Success!".format(count)) +def dis_main(): + device, config = program.preprocess() + config['Global']['distributed'] = dist.get_world_size() != 1 + paddle.disable_static(device) + + # save_config + os.makedirs(config['Global']['save_model_dir'], exist_ok=True) + with open( + os.path.join(config['Global']['save_model_dir'], 'config.yml'), + 'w') as f: + yaml.dump(dict(config), f, default_flow_style=False, sort_keys=False) + + logger = get_logger( + log_file='{}/train.log'.format(config['Global']['save_model_dir'])) + if config['Global']['use_visualdl']: + from visualdl import LogWriter + vdl_writer = LogWriter(logdir=config['Global']['save_model_dir']) + else: + vdl_writer = None + print_dict(config, logger) + logger.info('train with paddle {} and device {}'.format(paddle.__version__, + device)) + + main(config, device, logger, vdl_writer) + # test_reader(config, place, logger) + + if __name__ == '__main__': - startup_program, train_program, place, config, train_alg_type = program.preprocess() - main() -# test_reader() + # main() + # dist.spawn(dis_main, nprocs=2, selelcted_gpus='6,7') + dis_main() diff --git a/train.sh b/train.sh new file mode 100644 index 00000000..0e628f92 --- /dev/null +++ b/train.sh @@ -0,0 +1 @@ + python -m paddle.distributed.launch --selected_gpus '0,1,2,3,4,5,6,7' tools/train.py -c configs/det/det_mv3_db.yml \ No newline at end of file -- GitLab