From cb5b43b0fe7cd9931c64f4e90cb22db1d3d22c9f Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Thu, 24 Nov 2022 16:24:39 +0800 Subject: [PATCH 01/16] WS3 --- research/cv/WS3/README.md | 292 + research/cv/WS3/__init__.py | 0 research/cv/WS3/eval_ascend.py | 252 + research/cv/WS3/eval_gpu.py | 240 + research/cv/WS3/figs/WS3_framwork.png | Bin 0 -> 327046 bytes research/cv/WS3/requirements.txt | 3 + research/cv/WS3/scripts/eval_s3dis_ascend.sh | 22 + research/cv/WS3/scripts/eval_s3dis_gpu.sh | 22 + research/cv/WS3/scripts/train_s3dis_ascend.sh | 30 + research/cv/WS3/scripts/train_s3dis_gpu.sh | 26 + research/cv/WS3/src/data/S3DIS_dataset.py | 305 + .../cv/WS3/src/data/S3DIS_dataset_test.py | 296 + research/cv/WS3/src/data/__init__.py | 0 research/cv/WS3/src/model/__init__.py | 0 research/cv/WS3/src/model/base_model.py | 246 + .../WS3/src/model/base_model_remove_bias.py | 246 + research/cv/WS3/src/model/model_s3dis.py | 357 + .../WS3/src/model/model_s3dis_remove_bias.py | 366 + research/cv/WS3/src/utils/__init__.py | 0 .../cv/WS3/src/utils/data_prepare_s3dis.py | 80 + research/cv/WS3/src/utils/helper_ply.py | 356 + research/cv/WS3/src/utils/logger.py | 88 + research/cv/WS3/src/utils/metrics.py | 68 + research/cv/WS3/src/utils/tools.py | 183 + research/cv/WS3/third_party/cloud.cpp | 67 + research/cv/WS3/third_party/cloud.h | 158 + research/cv/WS3/third_party/compile_op.sh | 7 + .../cv/WS3/third_party/grid_subsampling.cpp | 106 + .../cv/WS3/third_party/grid_subsampling.h | 92 + .../cv/WS3/third_party/meta/anno_paths.txt | 272 + .../cv/WS3/third_party/meta/class_names.txt | 13 + research/cv/WS3/third_party/nanoflann.hpp | 2043 ++++ research/cv/WS3/third_party/setup.py | 29 + .../WS3/third_party/src/KDTreeTableAdaptor.h | 189 + research/cv/WS3/third_party/src/knn.cpp | 9696 +++++++++++++++++ research/cv/WS3/third_party/src/knn.pyx | 149 + research/cv/WS3/third_party/src/knn_.cxx | 271 + research/cv/WS3/third_party/src/knn_.h | 27 + research/cv/WS3/third_party/src/nanoflann.hpp | 1990 ++++ research/cv/WS3/third_party/src/setup.py | 19 + research/cv/WS3/third_party/src/test.py | 15 + research/cv/WS3/third_party/wrapper.cpp | 286 + research/cv/WS3/train_ascend.py | 381 + research/cv/WS3/train_gpu.py | 323 + research/cv/__init__.py | 0 45 files changed, 19611 insertions(+) create mode 100644 research/cv/WS3/README.md create mode 100644 research/cv/WS3/__init__.py create mode 100644 research/cv/WS3/eval_ascend.py create mode 100644 research/cv/WS3/eval_gpu.py create mode 100644 research/cv/WS3/figs/WS3_framwork.png create mode 100644 research/cv/WS3/requirements.txt create mode 100644 research/cv/WS3/scripts/eval_s3dis_ascend.sh create mode 100644 research/cv/WS3/scripts/eval_s3dis_gpu.sh create mode 100644 research/cv/WS3/scripts/train_s3dis_ascend.sh create mode 100644 research/cv/WS3/scripts/train_s3dis_gpu.sh create mode 100644 research/cv/WS3/src/data/S3DIS_dataset.py create mode 100644 research/cv/WS3/src/data/S3DIS_dataset_test.py create mode 100644 research/cv/WS3/src/data/__init__.py create mode 100644 research/cv/WS3/src/model/__init__.py create mode 100644 research/cv/WS3/src/model/base_model.py create mode 100644 research/cv/WS3/src/model/base_model_remove_bias.py create mode 100644 research/cv/WS3/src/model/model_s3dis.py create mode 100644 research/cv/WS3/src/model/model_s3dis_remove_bias.py create mode 100644 research/cv/WS3/src/utils/__init__.py create mode 100644 research/cv/WS3/src/utils/data_prepare_s3dis.py create mode 100644 research/cv/WS3/src/utils/helper_ply.py create mode 100644 research/cv/WS3/src/utils/logger.py create mode 100644 research/cv/WS3/src/utils/metrics.py create mode 100644 research/cv/WS3/src/utils/tools.py create mode 100644 research/cv/WS3/third_party/cloud.cpp create mode 100644 research/cv/WS3/third_party/cloud.h create mode 100644 research/cv/WS3/third_party/compile_op.sh create mode 100644 research/cv/WS3/third_party/grid_subsampling.cpp create mode 100644 research/cv/WS3/third_party/grid_subsampling.h create mode 100644 research/cv/WS3/third_party/meta/anno_paths.txt create mode 100644 research/cv/WS3/third_party/meta/class_names.txt create mode 100644 research/cv/WS3/third_party/nanoflann.hpp create mode 100644 research/cv/WS3/third_party/setup.py create mode 100644 research/cv/WS3/third_party/src/KDTreeTableAdaptor.h create mode 100644 research/cv/WS3/third_party/src/knn.cpp create mode 100644 research/cv/WS3/third_party/src/knn.pyx create mode 100644 research/cv/WS3/third_party/src/knn_.cxx create mode 100644 research/cv/WS3/third_party/src/knn_.h create mode 100644 research/cv/WS3/third_party/src/nanoflann.hpp create mode 100644 research/cv/WS3/third_party/src/setup.py create mode 100644 research/cv/WS3/third_party/src/test.py create mode 100644 research/cv/WS3/third_party/wrapper.cpp create mode 100644 research/cv/WS3/train_ascend.py create mode 100644 research/cv/WS3/train_gpu.py create mode 100644 research/cv/__init__.py diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md new file mode 100644 index 000000000..6bded72b2 --- /dev/null +++ b/research/cv/WS3/README.md @@ -0,0 +1,292 @@ +# WS3: Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud + +Mindspore implementation for ***"Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud"*** + +Please read the [original paper](https://ojs.aaai.org/index.php/AAAI/article/view/16455) +or [original tensorflow implementation](https://github.com/Yachao-Zhang/WS3) for more detailed information. + +## Model Architecture + +![WS3 Framework](./figs/WS3_framwork.png) + +## Dataset + +### Preparation + +1. Download S3DIS dataset from + this [link](https://docs.google.com/forms/d/e/1FAIpQLScDimvNMCGhy_rmBA2gHfDu3naktRm6A8BPwAWWDv-Uhm6Shw/viewform?c=0&w=1) + . +2. Uncompress `Stanford3dDataset_v1.2_Aligned_Version.zip` to `dataset\S3DIS`. +3. run `compile_warppers.sh` (in `src/utils/cpp_wrappers`) to install dependency. +4. run `data_prepare_s3dis.py` (in `src/utils/data_prepare_s3dis.py`) to process data. The processed data will be stored + in `input_0.040` and `original_ply` folders. + +### Directory structure of dataset + +``` +dataset +└──S3DIS # S3DIS dataset + ├── input_0.040 + │ ├── *.ply + │ ├── *_proj.pkl + │ └── *_KDTree.pkl + ├── original_ply + │ └── *.ply + │ + └── Stanford3dDataset_v1.2_Aligned_Version +``` + +## Requirements + +- Hardware + - For Ascend: Ascend 910. + - For GPU: cuda==11.1 + +- Framework + - Mindspore = 1.7.0 + +- Third Package + - Pyhton==3.7 + - pandas==1.1.5 + - scikit-learn==0.21.3 + - numpy==1.21.6 + +### Install dependencies + +1. `pip install -r requirements.txt` +2. `cd src/utils/nearest_neighbors` & `python setup.py develop` + +## Quick Start + +For GPU: + +```shell +bash scripts/train_s3dis_gpu.sh +bash scripts/eval_s3dis_gpu.sh +``` + +For Ascend: + +```shell +bash scripts/eval_s3dis_ascend.sh +bash scripts/eval_s3dis_ascend.sh +``` + +## Script Description + +### Scripts and Sample Code + +``` +WS3 +├── scripts +│ ├── eval_s3dis_ascend.sh # Evaluting: S3DIS dataset on Ascend +│ ├── eval_s3dis_gpu.sh # Evaluting: S3DIS dataset on GPU +│ ├── train_s3dis_ascend.sh # Training: S3DIS dataset on Ascend +│ └── train_s3dis_gpu.sh # Training: S3DIS dataset on GPU +├── src +| ├── data # class and functions for Mindspore dataset +│ │ ├── S3DIS_dataset.py # dataset class for train +│ │ └── S3DIS_dataset_test.py # dataset class for test +│ ├── model # network architecture +│ │ ├── base_model.py # layers +│ │ ├── base_model_remove_bias.py # layers removing bias +│ │ ├── model_s3dis.py # combine loss function with network +│ │ └── model_s3dis_remove_bias.py # combine loss function with network removing bias +│ └── utils +│ ├── cpp_wrappers # dependency for point cloud subsampling +│ ├── meta # meta information for data processor +│ ├── nearest_neighbors # dependency for point cloud nearest_neighbors +│ ├── data_prepare_s3dis.py # data processor for s3dis dataset +│ ├── helper_ply.py # file utils +│ ├── logger.py # logger +│ ├── metrics.py # calcualte iou and accuracy +│ └── tools.py # DataProcessing and Config +│ +├── eval_gpu.py +├── eval_ascend.py +├── README.md +├── requirements.txt +├── train_gpu.py +└── train_ascend.py +``` + +### Script Parameter + +we use `train_s3dis_ascend.sh` as an example + +```shell +python train_ascend.py \ + --epochs 40 \ + --batch_size 6 \ + --val_area Area_5 \ + --dataset_dir ../dataset/S3DIS \ + --device_target Ascend \ + --device_id 0 \ + --train_steps 500 \ + --topk 500 \ + --float16 True \ + --outputs_dir ./outputs \ + --labeled_percent 1 \ + --num_training_ep0 30 \ + --name BatchS_6_Float16_PyNative_Ascend +``` + +The following table describes the arguments. You can change freely as you want. + +| Config Arguments | Explanation | +| :---------------: | :----------------------------------------------------------: | +| `--epoch` | number of epochs for training | +| `--batch_size` | batch size | +| `--val_area` | which area to validate | +| `--dataset_dir` | path of dataset | +| `--device_target` | chose "Ascend" or "GPU" | +| `--device_id` | which Ascend AI core/GPU to run(default:0) | +| `--train_steps` | the number of steps for each epoch | +| `--topk` | topk for sparse pseudo label propagation loss | +| `--float16` | whether use float16 in model| +| `--outputs_dir` | where stores log and network weights | +| `--labeled_percent` | the percent of labeled points | +| `--num_training_ep0` | start from X epoch to use sparse pseudo loss | +| `--name` | experiment name, which will combine with outputs_dir. The output files for current experiments will be stores in `outputs_dir/name` | + +## Training + +### Training Process + +For GPU: + +```shell +bash scripts/train_s3dis_gpu.sh +``` + +For Ascend: + +```shell +bash scripts/train_s3dis_ascend.sh +``` + +Log information: + +```shell +... +epoch: 38 step: 460, loss is 1.4644418954849243 +epoch: 38 step: 480, loss is 1.7821598052978516 +epoch: 38 step: 500, loss is 1.6881225109100342 +UpdateLossEpoch ==> cur_epoch_num:39, cur_training_ep:0.17547142790305748, loss_fn.c_epoch_k:1.3422978 +epoch: 39 step: 20, loss is 2.0437705516815186 +epoch: 39 step: 40, loss is 2.5041351318359375 +epoch: 39 step: 60, loss is 1.307090401649475 +... +``` + +### Training Result + +Using `bash scripts/eval_s3dis_ascend.sh` as an example: + +Training results will be stored in `/outputs/BatchS_6_Float16_PyNative_Ascend` , which is determined +by `{args.outputs_dir}/{args.name}/ckpt`. For example: + +``` +outputs +├── BatchS_6_Float16_PyNative_Ascend + ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluting: S3DIS dataset on Ascend + └── ckpt # Evaluting: S3DIS dataset on GPU + ├── randla_1_500.ckpt + ├── randla_2_500.ckpt + └── .... +``` + +## Evaluation + +### Evaluation Process 910 + +For GPU: + +```shell +bash scripts/eval_s3dis_gpu.sh +``` + +For Ascend: + +```shell +bash scripts/eval_s3dis_ascend.sh +``` + +Note: Before you start eval, please check `--model_path` in `eval_xxx.sh` and guarantee `--model_path` is equal to +`{args.outputs_dir}/{args.name}` in `train_xxx.sh`. + +### Evaluation Result 910 + +```shell +Area_5_office_19 Acc:0.8584098992023179 +Area_5_hallway_9 Acc:0.9581127867106095 +Area_5_hallway_10 Acc:0.9735772827918966 +Area_5_office_5 Acc:0.9377652641453553 +Area_5_office_39 Acc:0.8065665136231684 +Area_5_WC_2 Acc:0.8629098008590395> Provide the result of evaluation. +Area_5_hallway_12 Acc:0.9826733528883095 +Area_5_hallway_3 Acc:0.9548643367899511## Export +Area_5_office_12 Acc:0.8639117068037043 +Area_5_office_23 Acc:0.9049251547225916 +-------------------------------------------------------------------------------------- +61.49 | 91.38 96.59 79.32 0.00 22.55 59.74 42.87 75.71 81.15 62.53 68.90 67.55 51.07 +-------------------------------------------------------------------------------------- +==========end test=============== +``` + +## Performance + +### Training Performance + +| Parameters | Ascend 910 | GPU (3090) | +| -------------------------- | ------------------------------------------------------------ | ----------------------------------------------| +| Model Version | WS3 | WS3 | +| Resource | Ascend 910; CPU 2.60GHz, 24cores; Memory 96G; OS Euler2.8 | Nvidia GeForce RTX 3090 | +| uploaded Date | 11/24/2022 (month/day/year) | 11/24/2022 (month/day/year) | +| MindSpore Version | 1.7.0 | 1.8.0 | +| Dataset | S3DIS | S3DIS | +| Training Parameters | epoch=40, steps per epoch=500, batch_size = 6 | epoch=100, steps per epoch=500, batch_size = 6| +| Optimizer | Adam | Adam | +| Loss Function | Softmax Cross Entropy & Sparse Pseudo Label propagation Loss | Softmax Cross Entropy & Sparse Pseudo Label propagation Loss | +| outputs | feature vector + probability | feature vector + probability | +| Loss | 2.89 (epoch 40) | 1.85 (epoch 40) & 11.45 (epoch 100) | +| Speed | 13 ms/step(8pcs) | 29 ms/step(8pcs) | +| Total time | About 4 mins | 11 minds | +| Parameters (M) | 4,98 | 4.99 | +| Checkpoint for Fine tuning | 57.14 MB (.ckpt file) | 76.31 MB (.ckpt file) | +| Scripts | [link](https://gitee.com/mindspore/models/tree/master/official/cv/WS3) | + +### Inference Performance + +| Parameters | Ascend | GPU | +| ------------------- | --------------------------- |--------------------------- | +| Model Version | WS3 | WS3 | +| Resource | Ascend 910; OS Euler2.8 | Nvidia GeForce RTX 3090 | +| Uploaded Date | 11/24/2022 (month/day/year) | 11/24/2022 (month/day/year) | +| MindSpore Version | 1.7.0 | 1.8.0 | +| Dataset | S3DIS | S3DIS | +| batch_size | 6 | 6 | +| outputs | feature vector + probability | feature vector + probability | +| Accuracy | See following tables | See following tables | + +### S3DIS (Setting: 1% labeled points) + +| Metric | Value(Tensorflow)| Value(Mindspore, Ascend) | Value(Mindspore, GPU) | +| :----: | :------------: | :-------------------: | :-------------------: | +| mIoU | 61.8% | 61.5% | 60.3% | + +## Reference + +Please kindly cite the original paper references in your publications if it helps your research: + +``` +@inproceedings{zhang2021weakly, + title={Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud}, + author={Zhang, Yachao and Li, Zonghao and Xie, Yuan and Qu, Yanyun and Li, Cuihua and Mei, Tao}, + booktitle={Proceedings of the AAAI Conference on Artificial Intelligence}, + volume={35}, + number={4}, + pages={3421--3429}, + year={2021} +} +``` diff --git a/research/cv/WS3/__init__.py b/research/cv/WS3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/eval_ascend.py b/research/cv/WS3/eval_ascend.py new file mode 100644 index 000000000..74fb5a9f9 --- /dev/null +++ b/research/cv/WS3/eval_ascend.py @@ -0,0 +1,252 @@ +import datetime, os, time, argparse +import logging +from pathlib import Path + +import numpy as np +from pathlib import Path +from sklearn.metrics import confusion_matrix + +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops +from mindspore import dtype as mstype + +from src.data.S3DIS_dataset_test import dataloader, ms_map +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg +from src.model.model_s3dis_remove_bias import RandLANet_S3DIS +from src.utils.logger import get_logger +from src.utils.helper_ply import write_ply + +from tqdm import tqdm + + +def run_eval(args): + context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) + + logger = get_logger(args.outputs_dir, args.rank) + + # data loader + _, val_ds, dataset = dataloader( + dataset_dir=args.dataset_dir, + num_parallel_workers=8, + shuffle=False + ) + input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] + output_columns = ["features", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4"] + val_loader = val_ds.batch(batch_size=args.batch_size, + per_batch_map=ms_map, + input_columns=input_columns, + output_columns=output_columns, + drop_remainder=True) + val_ds_size = val_loader.get_dataset_size() + val_loader = val_loader.create_dict_iterator() + + # load ckpt, iterate ckpts to find the best + d_in = 6 + network = RandLANet_S3DIS(d_in, cfg.num_classes) + network.set_train(False) + if args.float16: + print("network uses float16") + network.to_float(mstype.float16) + + if '.ckpt' in args.model_path: + ckpts = [args.model_path] + else: + # ckpt_path = Path(args.model_path) + ckpt_path = Path(os.path.join(args.model_path, 'ckpt')) + ckpts = ckpt_path.glob('*.ckpt') + ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) + + best_miou = 0.0 + best_ckpt = ckpts[0] + ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) + logger.info('==========begin test===============') + for ckpt_i, ckpt in enumerate(ckpts): + # load current ckpt + logger.info('load ckpt from:{}'.format(str(ckpt))) + param_dict = load_checkpoint(str(ckpt)) + load_param_into_net(network, param_dict) + + # Number of points per class in validation set + val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) + i = 0 + for label_val in dataset.label_values: + if label_val not in dataset.ignored_labels: + val_proportions[i] = np.sum([np.sum(labels == label_val) for labels in dataset.val_labels]) + i += 1 + test_probs = [np.zeros(shape=[l.shape[0], cfg.num_classes], dtype=np.float32) + for l in dataset.input_labels['validation']] + + # Smoothing parameter for votes + test_smooth = 0.95 + + step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) + for step_i, data in enumerate(val_loader): + # begin_time = time.time() + features = data['features'] + labels = data['labels'] # (B,N) + xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] + neigh_idx = [data['n0'], data['n1'], data['n2'], data['n3'], data['n4']] + sub_idx = [data['pl0'], data['pl1'], data['pl2'], data['pl3'], data['pl4']] + interp_idx = [data['u0'], data['u1'], data['u2'], data['u3'], data['u4']] + point_idx = data['input_inds'].asnumpy() + cloud_idx = data['cloud_inds'].asnumpy() + + logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] + # logits = logits.asnumpy() + logits = logits[..., :cfg.num_classes] + + prob_logits = ops.Softmax(-1)(logits) # 0.0003s + + ##### + # TODO 在Ascend环境上, prob_logits.asnumpy() 这个操作竟然要2.7s + ##### + prob_logits = prob_logits.asnumpy() # 要 2.72~2.8ls + for j in range(np.shape(prob_logits)[0]): # 遍历每一个batch + probs = prob_logits[j] + p_idx = point_idx[j, :] # 第j个点云中所有的点的索引,这里一共有40960个点 + c_i = cloud_idx[j][0] + test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs + + correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) + acc = correct / float(np.prod(np.shape(labels))) + msg = f'Step: {str(step_i)}; acc: {str(acc)}' + post_process_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[train_modelarts_notebook_v2.py] post process 耗时: {post_process_time - network_time}s") + step_bar.set_postfix_str(msg, refresh=False) + step_bar.update() + + last_min = -0.5 + num_votes = 100 + + while last_min < num_votes: + new_min = np.min(val_ds.source.min_possibility['validation']) + logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") + # if True: + if last_min + 1 < new_min: + # Update last_min + last_min += 1 + + # Show vote results (On subcloud so it is not the good values here) + logger.info('Confusion on sub clouds') + confusion_list = [] + + num_val = len(dataset.input_labels['validation']) + + for i_test in range(num_val): + probs = test_probs[i_test] + preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) + labels = dataset.input_labels['validation'][i_test] + + # Confs + confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + + # Rescale with the right number of point per class + C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + + # Compute IoUs + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info(s + '\n') + + if int(np.ceil(new_min)) % 1 == 0: + + # Project predictions + logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) + proj_probs_list = [] + + for i_val in range(num_val): + # Reproject probs back to the evaluations points + proj_idx = dataset.val_proj[i_val] + probs = test_probs[i_val][proj_idx, :] + proj_probs_list += [probs] + + # Show vote results + logger.info('Confusion on full clouds') + confusion_list = [] + for i_test in range(num_val): + # Get the predicted labels + preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) + + # Confusion + labels = dataset.val_labels[i_test] + acc = np.sum(preds == labels) / len(labels) + logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) + + confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] + name = dataset.input_names['validation'][i_test] + '.ply' + write_ply(os.path.join(args.outputs_dir, 'val_preds', name), [preds, labels], ['pred', 'label']) + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0) + + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + if m_IoU > best_miou: + best_miou = m_IoU + best_ckpt = ckpt + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info('-' * len(s)) + logger.info(s) + logger.info('-' * len(s) + '\n') + logger.info('==========end test===============') + break + ckpt_bar.update() + + logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) + + +if __name__ == "__main__": + """Parse program arguments""" + parser = argparse.ArgumentParser( + prog='RandLA-Net', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + expr = parser.add_argument_group('Experiment parameters') + param = parser.add_argument_group('Hyperparameters') + dirs = parser.add_argument_group('Storage directories') + misc = parser.add_argument_group('Miscellaneous') + + expr.add_argument('--batch_size', type=int, help='val batch size', default=20) + + expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + + dirs.add_argument('--model_path', type=str, help='model saved path', default='runs') + + misc.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + + misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + + misc.add_argument('--rank', type=int, help='rank', default=0) + + dirs.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + + dirs.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') + + expr.add_argument('--float16', type=bool, default=False) + + args = parser.parse_args() + + base_dir = os.path.dirname(os.path.abspath(__file__)) + args.model_path = os.path.join(base_dir, args.model_path) + + if not os.path.exists(args.outputs_dir): + os.makedirs(args.outputs_dir) + + val_pred_path = os.path.join(args.outputs_dir, 'val_preds') + if not os.path.exists(val_pred_path): + os.makedirs(val_pred_path) + + run_eval(args) diff --git a/research/cv/WS3/eval_gpu.py b/research/cv/WS3/eval_gpu.py new file mode 100644 index 000000000..78b7c3a71 --- /dev/null +++ b/research/cv/WS3/eval_gpu.py @@ -0,0 +1,240 @@ +import datetime, os, time, argparse + +import numpy as np +from pathlib import Path +from sklearn.metrics import confusion_matrix + +from tqdm import tqdm +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops + +from src.data.S3DIS_dataset_test import dataloader, ms_map +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg +from src.utils.logger import get_logger +from src.utils.helper_ply import write_ply +from src.model.model_s3dis import RandLANet_S3DIS + + +def run_eval(args): + context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) + + logger = get_logger(args.outputs_dir, args.rank) + + # data loader + _, val_ds, dataset = dataloader( + dataset_dir=args.dataset_dir, + num_parallel_workers=8, + shuffle=False + ) + input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] + output_columns = ["features", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4"] + val_loader = val_ds.batch(batch_size=args.batch_size, + per_batch_map=ms_map, + input_columns=input_columns, + output_columns=output_columns, + drop_remainder=True) + val_ds_size = val_loader.get_dataset_size() + val_loader = val_loader.create_dict_iterator() + + # load ckpt, iterate ckpts to find the best + d_in = 6 + network = RandLANet_S3DIS(d_in, cfg.num_classes) + + if '.ckpt' in args.model_path: + ckpts = [args.model_path] + else: + ckpt_path = Path(os.path.join(args.model_path, 'ckpt')) + ckpts = ckpt_path.glob('*.ckpt') + ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) + # if len(ckpts) == 0: + # ckpts = ["/media/T/Codes/RnadLA_Net_mindspore/tensorflow2ms/tf2ms.ckpt"] + + best_miou = 0.0 + best_ckpt = ckpts[0] + ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) + logger.info('==========begin test===============') + for ckpt_i, ckpt in enumerate(ckpts): + # load current ckpt + logger.info('load ckpt from:{}'.format(str(ckpt))) + param_dict = load_checkpoint(str(ckpt)) + load_param_into_net(network, param_dict) + + # Number of points per class in validation set + val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) + i = 0 + for label_val in dataset.label_values: + if label_val not in dataset.ignored_labels: + val_proportions[i] = np.sum([np.sum(labels == label_val) for labels in dataset.val_labels]) + i += 1 + test_probs = [np.zeros(shape=[l.shape[0], cfg.num_classes], dtype=np.float32) + for l in dataset.input_labels['validation']] + + # Smoothing parameter for votes + test_smooth = 0.95 + + step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) + for step_i, data in enumerate(val_loader): + + features = data['features'] + labels = data['labels'] # (B,N) + xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] + neigh_idx = [data['n0'], data['n1'], data['n2'], data['n3'], data['n4']] + sub_idx = [data['pl0'], data['pl1'], data['pl2'], data['pl3'], data['pl4']] + interp_idx = [data['u0'], data['u1'], data['u2'], data['u3'], data['u4']] + point_idx = data['input_inds'].asnumpy() + cloud_idx = data['cloud_inds'].asnumpy() + + logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] + logits = logits[..., :cfg.num_classes] + prob_logits = ops.Softmax(-1)(logits).asnumpy() # (B,N,13) + + for j in range(np.shape(prob_logits)[0]): # 遍历每一个batch + probs = prob_logits[j, :, :] + p_idx = point_idx[j, :] # 第j个点云中所有的点的索引,这里一共有40960个点 + c_i = cloud_idx[j][0] + test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs + + correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) + acc = correct / float(np.prod(np.shape(labels))) + msg = f'Step: {str(step_i)}; acc: {str(acc)}' + step_bar.set_postfix_str(msg, refresh=False) + step_bar.update() + + last_min = -0.5 + num_votes = 100 + + while last_min < num_votes: + new_min = np.min(val_ds.source.min_possibility['validation']) + logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") + # if True: + if last_min + 1 < new_min: + # Update last_min + last_min += 1 + + # Show vote results (On subcloud so it is not the good values here) + logger.info('Confusion on sub clouds') + confusion_list = [] + + num_val = len(dataset.input_labels['validation']) + + for i_test in range(num_val): + probs = test_probs[i_test] + preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) + labels = dataset.input_labels['validation'][i_test] + + # Confs + confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + + # Rescale with the right number of point per class + C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + + # Compute IoUs + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info(s + '\n') + + if int(np.ceil(new_min)) % 1 == 0: + + # Project predictions + logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) + proj_probs_list = [] + + for i_val in range(num_val): + # Reproject probs back to the evaluations points + proj_idx = dataset.val_proj[i_val] + probs = test_probs[i_val][proj_idx, :] + proj_probs_list += [probs] + + # Show vote results + logger.info('Confusion on full clouds') + confusion_list = [] + for i_test in range(num_val): + # Get the predicted labels + preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) + + # Confusion + labels = dataset.val_labels[i_test] + acc = np.sum(preds == labels) / len(labels) + logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) + + confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] + name = dataset.input_names['validation'][i_test] + '.ply' + write_ply(os.path.join(args.outputs_dir, 'val_preds', name), [preds, labels], ['pred', 'label']) + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0) + + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + if m_IoU > best_miou: + best_miou = m_IoU + best_ckpt = ckpt + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info('-' * len(s)) + logger.info(s) + logger.info('-' * len(s) + '\n') + logger.info('==========end test===============') + break + ckpt_bar.update() + + logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) + + +if __name__ == "__main__": + """Parse program arguments""" + parser = argparse.ArgumentParser( + prog='RandLA-Net', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + expr = parser.add_argument_group('Experiment parameters') + param = parser.add_argument_group('Hyperparameters') + dirs = parser.add_argument_group('Storage directories') + misc = parser.add_argument_group('Miscellaneous') + + expr.add_argument('--batch_size', type=int, help='val batch size', default=20) + + expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + + dirs.add_argument('--model_path', type=str, help='model saved path', required=True, default='outputs') + + misc.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') + + misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + + dirs.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') + + misc.add_argument('--rank', type=int, help='rank', default=0) + + args = parser.parse_args() + + base_dir = os.path.dirname(os.path.abspath(__file__)) + args.model_path = os.path.join(base_dir, args.model_path) + + # test output dir + t = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) + if os.path.isdir(args.model_path): + args.outputs_dir = os.path.join(args.model_path, 'test_' + t) + else: + args.outputs_dir = os.path.join(f"test_{t}") + if not os.path.exists(args.outputs_dir): + os.makedirs(args.outputs_dir) + # val output path + + val_pred_path = os.path.join(args.outputs_dir, 'val_preds') + if not os.path.exists(val_pred_path): + os.makedirs(val_pred_path) + + # start test + run_eval(args) diff --git a/research/cv/WS3/figs/WS3_framwork.png b/research/cv/WS3/figs/WS3_framwork.png new file mode 100644 index 0000000000000000000000000000000000000000..f4636e9715709b22c9e916a522c4f58ac036b21f GIT binary patch literal 327046 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%0A4W~WJ3@2*EOSm|PG~Fr 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

U=T^^=P~pjjdGRwKovg6h(3+lQhGJ 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 0: + if '%' in self.labeled_point: + r = float(self.labeled_point[:-1]) / 100 + num_selected = max(int(num_classs * r), 1) + else: + num_selected = int(self.labeled_point) + + if self.sampling_mode == 1: + label_indx = list(range(num_classs)) + random.shuffle(label_indx) + select_labels_indx = label_indx[:num_selected] + ind_class_select = ind_class[select_labels_indx] + anchor_xyz = sub_xyz[ind_class_select].reshape([1, -1, 3]) + class_xyz = sub_xyz[ind_class].reshape([1, -1, 3]) + cluster_idx = DP.knn_search(class_xyz, anchor_xyz, 50).squeeze() # knn_search (B,N,k) + ind_class_noselect = np.delete(label_indx, cluster_idx) + ind_class_noselect = ind_class[ind_class_noselect] + sub_labels[ind_class_noselect] = 13 + all_select_label_indx.append(cluster_idx[0]) + elif self.sampling_mode == 0: + label_indx = list(range(num_classs)) + random.shuffle(label_indx) + noselect_labels_indx = label_indx[num_selected:] + select_labels_indx = label_indx[:num_selected] + ind_class_noselect = ind_class[noselect_labels_indx] + ind_class_select = ind_class[select_labels_indx] + all_select_label_indx.append(ind_class_select[0]) + sub_labels[ind_class_noselect] = 13 + ''' ***************** ''' + + # Read pkl with search tree + with open(kd_tree_file, 'rb') as f: + search_tree = pickle.load(f) + + self.input_trees[cloud_split] += [search_tree] + self.input_colors[cloud_split] += [sub_colors] + self.input_labels[cloud_split] += [sub_labels] + self.input_names[cloud_split] += [cloud_name] + if cloud_split == 'training' and self.weak_label: + self.s_indx[cloud_split] += [all_select_label_indx] # training only]: + + print('{:s} done in {:.1f}s'.format(cloud_name, time.time() - t0)) + + print('\nPreparing reprojected indices for testing') + + # Get validation and test reprojected indices + for i, file_path in enumerate(self.paths): + t0 = time.time() + cloud_name = file_path.stem + # Validation projection and labels + if self.val_split in cloud_name: + proj_file = tree_path / f"{cloud_name}_proj.pkl" + # proj_file = join(tree_path, '{:s}_proj.pkl'.format(cloud_name)) + with open(proj_file, 'rb') as f: + proj_idx, labels = pickle.load(f) + self.val_proj += [proj_idx] + self.val_labels += [labels] + print('{:s} done in {:.1f}s'.format(cloud_name, time.time() - t0)) + print('Complete data loading') + + def __getitem__(self, idx): + pass + + def __len__(self): + # Number of clouds + print(f"Length of S3DISDatasetGenerator is {self.size}") + return self.size + + +class ActiveLearningSampler(ds.Sampler): + + def __init__(self, dataset, cfg, batch_size=6, split='training'): + super(ActiveLearningSampler, self).__init__() + self.cfg = cfg + self.dataset = dataset + self.split = split + self.batch_size = batch_size + self.possibility = {} + self.min_possibility = {} + + if split == 'training': + self.n_samples = self.cfg.train_steps + else: + self.n_samples = self.cfg.val_steps + + # Random initialisation for weights + self.possibility[split] = [] + self.min_possibility[split] = [] + for i, tree in enumerate(self.dataset.input_colors[split]): + self.possibility[split] += [np.random.rand(tree.data.shape[0]) * 1e-3] + self.min_possibility[split] += [float(np.min(self.possibility[split][-1]))] + + def __iter__(self): + + return self.spatially_regular_gen() + + def __len__(self): + len = self.n_samples * self.batch_size + print(f"Length of ActiveLearningSampler is {len}") + return len # not equal to the actual size of the dataset, but enable nice progress bars + + def spatially_regular_gen(self): + # Choosing the least known point as center of a new cloud each time. + for i in range(self.n_samples * self.batch_size): # num_per_epoch + # begin_time = time.time() + # Generator loop + # Choose a random cloud + cloud_idx = int(np.argmin(self.min_possibility[self.split])) + + # choose the point with the minimum of possibility as query point + point_ind = np.argmin(self.possibility[self.split][cloud_idx]) + + # Get points from tree structure + points = np.array(self.dataset.input_trees[self.split][cloud_idx].data, copy=False) + + # Center point of input region + center_point = points[point_ind, :].reshape(1, -1) + + # Add noise to the center point + noise = np.random.normal(scale=3.5 / 10, size=center_point.shape) + pick_point = center_point + noise.astype(center_point.dtype) + + # Check if the number of points in the selected cloud is less than the predefined num_points + if len(points) < self.cfg.num_points: + queried_idx = self.dataset.input_trees[self.split][cloud_idx].query(pick_point, k=len(points))[1][0] + else: + queried_idx = \ + self.dataset.input_trees[self.split][cloud_idx].query(pick_point, k=self.cfg.num_points)[1][0] + + if self.split == 'training': + s_indx = self.dataset.s_indx[self.split][cloud_idx] # training only + # Shuffle index + queried_idx = np.concatenate([np.array(s_indx), queried_idx], 0)[:self.cfg.num_points] # training only + + # Shuffle index + queried_idx = DP.shuffle_idx(queried_idx) + # Collect points and colors + queried_pc_xyz = points[queried_idx] + queried_pc_xyz = queried_pc_xyz - pick_point + queried_pc_colors = self.dataset.input_colors[self.split][cloud_idx][queried_idx] + queried_pc_labels = self.dataset.input_labels[self.split][cloud_idx][queried_idx] + + # Update the possibility of the selected points + dists = np.sum(np.square((points[queried_idx] - pick_point).astype(np.float32)), axis=1) + delta = np.square(1 - dists / np.max(dists)) + self.possibility[self.split][cloud_idx][queried_idx] += delta + self.min_possibility[self.split][cloud_idx] = float(np.min(self.possibility[self.split][cloud_idx])) + + # up_sampled with replacement + if len(points) < self.cfg.num_points: + # 虽然叫data_aug, 但与印象中的数据增强相差甚远 + queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ + DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, self.cfg.num_points) + + queried_pc_xyz = queried_pc_xyz.astype(np.float32) + queried_pc_colors = queried_pc_colors.astype(np.float32) + queried_pc_labels = queried_pc_labels.astype(np.int32) + queried_idx = queried_idx.astype(np.int32) + cloud_idx = np.array([cloud_idx], dtype=np.int32) + # print(queried_pc_xyz.shape, queried_pc_colors.shape) + # queried_pc_xyz, queried_pc_colors = data_augment(queried_pc_xyz, queried_pc_colors) + # + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[S3DIS_dataset.py] sample{i} 耗时: {time.time() - begin_time}s") + + yield queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cloud_idx + + +def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, batchInfo): + """ + xyz => [B,N,3] + features => [B,N,d] + labels => [B,N,] + pc_idx => [B,N,] + cloud_idx => [B,] + """ + # begin_time = time.time() + + batch_xyz = np.array(batch_xyz, dtype=np.float32) + batch_features = np.array(batch_features, dtype=np.float32) + # batch_features = data_augment(batch_xyz, batch_features) + # batch_xyz = batch_features[:, :3] + batch_features = np.concatenate((batch_xyz, batch_features), axis=-1) + input_points = [] # [num_layers, B, N, 3] + input_neighbors = [] # [num_layers, B, N, 16] + input_pools = [] # [num_layers, B, N, 16] + input_up_samples = [] # [num_layers, B, N, 1] + + for i in range(cfg.num_layers): + neighbour_idx = DP.knn_search(batch_xyz, batch_xyz, cfg.k_n).astype(np.int32) + sub_points = batch_xyz[:, :batch_xyz.shape[1] // cfg.sub_sampling_ratio[i], :] + pool_i = neighbour_idx[:, :batch_xyz.shape[1] // cfg.sub_sampling_ratio[i], :] + up_i = DP.knn_search(sub_points, batch_xyz, 1).astype(np.int32) + input_points.append(batch_xyz) + input_neighbors.append(neighbour_idx) + input_pools.append(pool_i) + input_up_samples.append(up_i) + batch_xyz = sub_points + + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[S3DIS_dataset.py] ms_map 耗时: {time.time() - begin_time}s") + + return batch_features, batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, \ + input_points[0], input_points[1], input_points[2], input_points[3], input_points[4], \ + input_neighbors[0], input_neighbors[1], input_neighbors[2], input_neighbors[3], input_neighbors[4], \ + input_pools[0], input_pools[1], input_pools[2], input_pools[3], input_pools[4], \ + input_up_samples[0], input_up_samples[1], input_up_samples[2], input_up_samples[3], input_up_samples[4] + + +def dataloader(cfg, **kwargs): + dataset = S3DISDatasetGenerator(cfg.dataset_dir, cfg.labeled_percent) + val_sampler = ActiveLearningSampler( + dataset, + cfg, + batch_size=cfg.val_batch_size, + split='validation' + ) + train_sampler = ActiveLearningSampler( + dataset, + cfg, + batch_size=cfg.batch_size, + split='training' + ) + input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] + return ds.GeneratorDataset(train_sampler, input_columns, **kwargs), \ + ds.GeneratorDataset(val_sampler, input_columns, **kwargs), \ + dataset diff --git a/research/cv/WS3/src/data/S3DIS_dataset_test.py b/research/cv/WS3/src/data/S3DIS_dataset_test.py new file mode 100644 index 000000000..55cf61d8c --- /dev/null +++ b/research/cv/WS3/src/data/S3DIS_dataset_test.py @@ -0,0 +1,296 @@ +import pickle, time, warnings +import numpy as np +from pathlib import Path + +import mindspore.dataset as ds +from mindspore import Tensor, context +from mindspore import dtype as mstype +import mindspore.ops as ops + +from utils.tools import ConfigS3DIS as cfg +from utils.tools import DataProcessing as DP +from utils.helper_ply import read_ply + + +class S3DISDatasetGenerator: + def __init__(self, dataset_dir): + self.name = 'S3DIS' + self.dataset_path = Path(dataset_dir) + # self.dataset_path = Path("./datasets/S3DIS") + print(self.dataset_path.absolute()) + self.label_to_names = {0: 'ceiling', + 1: 'floor', + 2: 'wall', + 3: 'beam', + 4: 'column', + 5: 'window', + 6: 'door', + 7: 'table', + 8: 'chair', + 9: 'sofa', + 10: 'bookcase', + 11: 'board', + 12: 'clutter'} + self.num_classes = len(self.label_to_names) + self.label_values = np.sort([k for k, v in self.label_to_names.items()]) + self.label_to_idx = {l: i for i, l in enumerate(self.label_values)} + self.ignored_labels = np.array([]) + + self.val_area = 'Area_5' + self.original_ply_path = self.dataset_path / 'original_ply' + self.paths = list(self.original_ply_path.glob('*.ply')) + self.size = len(self.paths) + + # Initiate containers + self.val_proj = [] + self.val_labels = [] + self.possibility = {} + self.min_possibility = {} + self.input_trees = {'training': [], 'validation': []} + self.input_colors = {'training': [], 'validation': []} + self.input_labels = {'training': [], 'validation': []} + self.input_names = {'training': [], 'validation': []} + self.sub_grid_size = 0.04 + + self.load_data() + print('Size of training : ', len(self.input_colors['training'])) + print('Size of validation : ', len(self.input_colors['validation'])) + + def load_data(self): + tree_path = self.dataset_path / f'input_{self.sub_grid_size:.3f}' + for i, file_path in enumerate(self.paths): + t0 = time.time() + cloud_name = file_path.stem + if self.val_area in cloud_name: + cloud_split = 'validation' + else: + cloud_split = 'training' + + # Name of the input files + kd_tree_file = tree_path / '{:s}_KDTree.pkl'.format(cloud_name) + sub_ply_file = tree_path / '{:s}.ply'.format(cloud_name) + + # data = np.load(sub_npy_file, mmap_mode='r').T + data = read_ply(str(sub_ply_file)) + sub_colors = np.vstack((data['red'], data['green'], data['blue'])).T + sub_labels = data['class'] + + # Read pkl with search tree + with open(kd_tree_file, 'rb') as f: + search_tree = pickle.load(f) + + self.input_trees[cloud_split].append(search_tree) + self.input_colors[cloud_split].append(sub_colors) + self.input_labels[cloud_split].append(sub_labels) + self.input_names[cloud_split].append(cloud_name) + + size = sub_colors.shape[0] * 4 * 7 + print('{:s} {:.1f} MB loaded in {:.1f}s'.format(kd_tree_file.name, size * 1e-6, time.time() - t0)) + + print('\nPreparing reprojected indices for testing') + + # Get validation and test reprojected indices + for i, file_path in enumerate(self.paths): + t0 = time.time() + cloud_name = file_path.stem + + # Validation projection and labels + if self.val_area in cloud_name: + proj_file = tree_path / '{:s}_proj.pkl'.format(cloud_name) + with open(proj_file, 'rb') as f: + proj_idx, labels = pickle.load(f) + + self.val_proj += [proj_idx] + self.val_labels += [labels] + print('{:s} done in {:.1f}s'.format(cloud_name, time.time() - t0)) + + def __getitem__(self, idx): + pass + + def __len__(self): + # Number of clouds + return self.size + + +class ActiveLearningSampler(ds.Sampler): + + def __init__(self, dataset, batch_size=6, split='training'): + self.dataset = dataset + self.split = split + self.batch_size = batch_size + self.possibility = {} + self.min_possibility = {} + + if split == 'training': + self.n_samples = cfg.train_steps + else: + self.n_samples = cfg.val_steps + + # Random initialisation for weights + self.possibility[split] = [] + self.min_possibility[split] = [] + for i, tree in enumerate(self.dataset.input_colors[split]): + self.possibility[split] += [np.random.rand(tree.data.shape[0]) * 1e-3] + self.min_possibility[split] += [float(np.min(self.possibility[split][-1]))] + + def __iter__(self): + return self.spatially_regular_gen() + + def __len__(self): + return self.n_samples * self.batch_size # not equal to the actual size of the dataset, but enable nice progress bars + + def spatially_regular_gen(self): + # Generator loop + for i in range(self.n_samples * self.batch_size): # num_per_epoch + # Choose the cloud with the lowest probability + cloud_idx = int(np.argmin(self.min_possibility[self.split])) + + # choose the point with the minimum of possibility as query point + point_ind = np.argmin(self.possibility[self.split][cloud_idx]) + + # Get points from tree structure + points = np.array(self.dataset.input_trees[self.split][cloud_idx].data, copy=False) + + # Center point of input region + center_point = points[point_ind, :].reshape(1, -1) + + # Add noise to the center point + noise = np.random.normal(scale=3.5 / 10, size=center_point.shape) + pick_point = center_point + noise.astype(center_point.dtype) + + if len(points) < cfg.num_points: + queried_idx = self.dataset.input_trees[self.split][cloud_idx].query(pick_point, k=len(points))[1][0] + else: + queried_idx = self.dataset.input_trees[self.split][cloud_idx].query(pick_point, k=cfg.num_points)[1][0] + # Shuffle index + queried_idx = DP.shuffle_idx(queried_idx) + + # Get corresponding points and colors based on the index + queried_pc_xyz = points[queried_idx] + queried_pc_xyz = queried_pc_xyz - pick_point + queried_pc_colors = self.dataset.input_colors[self.split][cloud_idx][queried_idx] + queried_pc_labels = self.dataset.input_labels[self.split][cloud_idx][queried_idx] + + # Update the possibility of the selected points + dists = np.sum(np.square((points[queried_idx] - pick_point).astype(np.float32)), axis=1) + delta = np.square(1 - dists / np.max(dists)) + self.possibility[self.split][cloud_idx][queried_idx] += delta + self.min_possibility[self.split][cloud_idx] = float(np.min(self.possibility[self.split][cloud_idx])) + + if len(points) < cfg.num_points: + queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ + DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cfg.num_points) + + queried_pc_xyz = queried_pc_xyz.astype(np.float32) + queried_pc_colors = queried_pc_colors.astype(np.float32) + queried_pc_labels = queried_pc_labels.astype(np.int32) + queried_idx = queried_idx.astype(np.int32) + cloud_idx = np.array([cloud_idx], dtype=np.int32) + + yield queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cloud_idx + + +def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, batchInfo): + """ + xyz => [B,N,3] + features => [B,N,d] + labels => [B,N,] + pc_idx => [B,N,] + cloud_idx => [B,] + """ + batch_xyz = np.array(batch_xyz) + batch_features = np.array(batch_features) + batch_features = np.concatenate((batch_xyz, batch_features), axis=-1) + input_points = [] # [num_layers, B, N, 3] + input_neighbors = [] # [num_layers, B, N, 16] + input_pools = [] # [num_layers, B, N, 16] + input_up_samples = [] # [num_layers, B, N, 1] + + for i in range(cfg.num_layers): + neighbour_idx = DP.knn_search(batch_xyz, batch_xyz, cfg.k_n).astype(np.int32) + sub_points = batch_xyz[:, :batch_xyz.shape[1] // cfg.sub_sampling_ratio[i], :] + pool_i = neighbour_idx[:, :batch_xyz.shape[1] // cfg.sub_sampling_ratio[i], :] + up_i = DP.knn_search(sub_points, batch_xyz, 1).astype(np.int32) + + input_points.append(batch_xyz) + input_neighbors.append(neighbour_idx) + input_pools.append(pool_i) + input_up_samples.append(up_i) + batch_xyz = sub_points + + # b_f:[B, N, 3+d] + # due to the constraints of the mapping function, only the list elements can be passed back sequentially + return batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, input_points[0], input_points[1], input_points[ + 2], input_points[3], input_points[4], \ + input_neighbors[0], input_neighbors[1], input_neighbors[2], input_neighbors[3], input_neighbors[4], \ + input_pools[0], input_pools[1], input_pools[2], \ + input_pools[3], input_pools[4], input_up_samples[0], input_up_samples[1], input_up_samples[2], \ + input_up_samples[3], input_up_samples[4] + + +def dataloader(dataset_dir, **kwargs): + dataset = S3DISDatasetGenerator(dataset_dir) + val_sampler = ActiveLearningSampler( + dataset, + batch_size=cfg.val_batch_size, + split='validation' + ) + train_sampler = ActiveLearningSampler( + dataset, + batch_size=cfg.batch_size, + split='training' + ) + return ds.GeneratorDataset(train_sampler, ["xyz", "colors", "labels", "q_idx", "c_idx"], + **kwargs), \ + ds.GeneratorDataset(val_sampler, + ["xyz", "colors", "labels", "q_idx", + "c_idx"], **kwargs), dataset + + +if __name__ == '__main__': + dir = Path('/home/ubuntu/hdd1/mqh/dataset/s3dis/train_0.040') + print('generating data loader....') + + train_ds, val_ds, dataset = dataloader(dir, val_area='Area_5', num_parallel_workers=8, shuffle=False) + + '''train_loader = train_ds.batch(batch_size=4, + per_batch_map=ms_map, + input_columns=["xyz","colors","labels","q_idx","c_idx"], + output_columns=["features","labels","input_inds","cloud_inds", + "p0","p1","p2","p3","p4", + "n0","n1","n2","n3","n4", + "pl0","pl1","pl2","pl3","pl4", + "u0","u1","u2","u3","u4"], + drop_remainder=True) + train_loader = train_loader.create_dict_iterator()''' + + val_loader = val_ds.batch(batch_size=cfg.val_batch_size, + per_batch_map=ms_map, + input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], + output_columns=["features", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4"], + drop_remainder=True) + val_loader = val_loader.create_dict_iterator() + for l in dataset.input_labels['validation']: + print(l.shape) + '''for i, data in enumerate(train_loader): + features = data['features'] + labels = data['labels'] + input_inds = data['input_inds'] + cloud_inds = data['cloud_inds'] + xyz = [data['p0'],data['p1'],data['p2'],data['p3'],data['p4']] + neigh_idx = [data['n0'],data['n1'],data['n2'],data['n3'],data['n4']] + sub_idx = [data['pl0'],data['pl1'],data['pl2'],data['pl3'],data['pl4']] + interp_idx = [data['u0'],data['u1'],data['u2'],data['u3'],data['u4']] + print("i: ",i," features.shape:", features.shape, + "\nlabels.shape:", labels.shape, + "\ninput_inds.shape:", input_inds.shape, + "\ncloud_inds.shape:", cloud_inds.shape, + "\nxyz[0].shape:", xyz[0].shape, + "\nneigh_idx[0].shape:", neigh_idx[0].shape, + "\nsub_idx[0].shape:", sub_idx[0].shape, + "\ninterp_idx[0].shape:", interp_idx[0].shape) + break''' diff --git a/research/cv/WS3/src/data/__init__.py b/research/cv/WS3/src/data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/src/model/__init__.py b/research/cv/WS3/src/model/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/src/model/base_model.py b/research/cv/WS3/src/model/base_model.py new file mode 100644 index 000000000..5a598b27e --- /dev/null +++ b/research/cv/WS3/src/model/base_model.py @@ -0,0 +1,246 @@ +# -*-coding:utf-8-*- + +import mindspore as ms +import mindspore.nn as nn +import mindspore.ops as P +from mindspore.ops import composite as C +from mindspore.ops import functional as F +from mindspore.ops import operations as op +from mindspore.common.initializer import TruncatedNormal + + +class SharedMLP(nn.Cell): + def __init__( + self, + in_channels, + out_channels, + kernel_size=1, + stride=1, + transpose=False, + pad_mode='same', + bn=False, + activation_fn=None + ): + super(SharedMLP, self).__init__() + + conv_fn = nn.Conv2dTranspose if transpose else nn.Conv2d + + self.conv = conv_fn( + in_channels, + out_channels, + kernel_size, + stride=stride, + pad_mode='valid', + has_bias=True, + weight_init=TruncatedNormal(sigma=1e-3) + ) + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) if bn else None + self.activation_fn = activation_fn + + def construct(self, input): + r""" + construct method + + Parameters + ---------- + input: ms.Tensor, shape (B, d_in, N, K) + + Returns + ------- + ms.Tensor, shape (B, d_out, N, K) + """ + x = self.conv(input) + if self.batch_norm: + x = self.batch_norm(x) + if self.activation_fn: + x = self.activation_fn(x) + return x + + +class LocalSpatialEncoding(nn.Cell): + def __init__(self, in_channel=10, out_channel=1, use_pos_encoding=True): + super(LocalSpatialEncoding, self).__init__() + + self.mlp = SharedMLP(in_channel, out_channel, bn=True, activation_fn=nn.LeakyReLU(0.2)) + # self.mlp = SharedMLP(10, d, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.d = out_channel + self.use_pos_encoding = use_pos_encoding + + def construct(self, coords, features, neighbor_idx): + r""" + construct method + + Parameters + ---------- + coords: ms.Tensor, shape (B, N, 3) + coordinates of the point cloud + features: ms.Tensor, shape (B, d, N, 1) + features of the point cloud + neighbor_idx: ms.Tensor, shape (B, N, K) + + Returns + ------- + ms.Tensor, shape (B, 2*d, N, K) + """ + + idx = neighbor_idx # (4,40960,16) + extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) # (4,40960,16) -> (4,1,40960,16) -> (4,3,40960,16) + cat = P.Concat(-3) + if self.use_pos_encoding: + # finding neighboring points + xyz_tile = P.Tile()(coords.transpose(0, 2, 1).expand_dims(-1), + (1, 1, 1, idx.shape[-1])) # (4,3,40960) -> (4,3,40960,16) + neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(ms.int32)) # shape (4, 3, 40960, 16) + relative_xyz = xyz_tile - neighbor_xyz # relative_xyz + relative_dist = P.Sqrt()(P.ReduceSum(keep_dims=True)(P.Square()(relative_xyz), -3)) + + # relative point position encoding + f_xyz = cat(( + relative_dist, # (4,1,40960,16) + relative_xyz, # (4,3,40960,16) + xyz_tile, # (4,3,40960,16) + neighbor_xyz, # (4,3,40960,16) + )) # (4,10,40960,16) + + # ==========> tensorflow 源码 + # f_xyz = self.relative_pos_encoding(xyz, neigh_idx) + # def relative_pos_encoding(self, xyz, neigh_idx): + # ... + # relative_feature = tf.concat([relative_dis, relative_xyz, xyz_tile, neighbor_xyz], axis=-1) + # return relative_feature + # ==========> tensorflow 源码 + else: + f_xyz = coords + f_xyz = self.mlp(f_xyz) # (4,10,40960,16) -> (4,8,40960,16) + + f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) # (4, 8, 40960, 1) -> (4,8,40960,16) + extended_idx_for_feat = P.Tile()(idx.expand_dims(1), (1, f_xyz.shape[1], 1, 1)) + # (4,8,40960,16) -> (4,8,40960,16) + f_neighbours = P.GatherD()(f_tile, 2, extended_idx_for_feat.astype(ms.int32)) + + f_concat = cat([f_xyz, f_neighbours]) # (4,8,40960,16) & (4,8,40960,16) -> (4,16,40960,16) + + if self.use_pos_encoding: + return f_xyz, f_concat + else: + return f_concat + + +class AttentivePooling(nn.Cell): + def __init__(self, in_channels, out_channels): + super(AttentivePooling, self).__init__() + + self.score_fn = nn.SequentialCell([ + nn.Dense(in_channels, in_channels, has_bias=False), + nn.Softmax(-2) + ]) + self.mlp = SharedMLP(in_channels, out_channels, bn=True, activation_fn=nn.LeakyReLU(0.2)) + + def construct(self, x): + r""" + construct method + + Parameters + ---------- + x: ms.Tensor, shape (B, d_in, N, K) + + Returns + ------- + ms.Tensor, shape (B, d_out, N, 1) + """ + # computing attention scores + scores = self.score_fn(x.transpose(0, 2, 3, 1)).transpose(0, 3, 1, 2) + + # sum over the neighbors + features = scores * x + features = P.ReduceSum(keep_dims=True)(features, -1) # shape (B, d_in, N, 1) + + return self.mlp(features) + + +class LocalFeatureAggregation(nn.Cell): + def __init__(self, d_in, d_out): + super(LocalFeatureAggregation, self).__init__() + + self.mlp1 = SharedMLP(d_in, d_out // 2, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.mlp2 = SharedMLP(d_out, 2 * d_out, bn=True) + self.shortcut = SharedMLP(d_in, 2 * d_out, bn=True) + + self.lse1 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2, use_pos_encoding=True) + # self.lse2 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2) + self.lse2 = LocalSpatialEncoding(in_channel=d_out // 2, out_channel=d_out // 2, use_pos_encoding=False) + # self.lse2 = LocalSpatialEncoding(d_out // 2) + + self.pool1 = AttentivePooling(d_out, d_out // 2) + self.pool2 = AttentivePooling(d_out, d_out) + + self.lrelu = nn.LeakyReLU(0.2) + + def construct(self, coords, features, neighbor_idx): + r""" + construct method + + Parameters + ---------- + coords: ms.Tensor, shape (B, N, 3) + coordinates of the point cloud + features: ms.Tensor, shape (B, d, N, 1) + features of the point cloud + neighbor_idx: ms.Tensor, shape (B, N, 16) + knn neighbor idx + Returns + ------- + ms.Tensor, shape (B, 2*d_out, N, 1) + """ + f_pc = self.mlp1(features) # (4, 8, 40960, 1) + + f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) # (4, 8, 40960, 16) (4, 16, 40960, 16) + f_pc_agg = self.pool1(f_concat) # (4, 8, 40960, 1) + + f_concat = self.lse2(f_xyz, f_pc_agg, + neighbor_idx) # coords: (4, 40960, 3); x: (4, 8, 40960, 1) neighbor_idx:(4, 40960, 16) + f_pc_agg = self.pool2(f_concat) + + return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) + + +class TrainingWrapper(nn.Cell): + """Training wrapper.""" + + def __init__(self, network, optimizer, sens=1.0): + super(TrainingWrapper, self).__init__(auto_prefix=False) + self.network = network + self.network.set_grad() + self.weights = optimizer.parameters + self.optimizer = optimizer + self.grad = C.GradOperation(get_by_list=True, sens_param=True) + self.sens = sens + + def construct(self, *args): + """Build a forward graph""" + weights = self.weights + loss, logits = self.network(*args) + sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) + grads = self.grad(self.network, weights)(*args, sens) + return F.depend(loss, self.optimizer(grads)), logits + + +def get_param_groups(network): + """Param groups for optimizer.""" + decay_params = [] + no_decay_params = [] + for x in network.trainable_params(): + parameter_name = x.name + if parameter_name.endswith('.bias'): + # all bias not using weight decay + no_decay_params.append(x) + elif parameter_name.endswith('.gamma'): + # bn weight bias not using weight decay, be carefully for now x not include BN + no_decay_params.append(x) + elif parameter_name.endswith('.beta'): + # bn weight bias not using weight decay, be carefully for now x not include BN + no_decay_params.append(x) + else: + decay_params.append(x) + + return [{'params': no_decay_params, 'weight_decay': 0.0}, {'params': decay_params}] diff --git a/research/cv/WS3/src/model/base_model_remove_bias.py b/research/cv/WS3/src/model/base_model_remove_bias.py new file mode 100644 index 000000000..f7a848506 --- /dev/null +++ b/research/cv/WS3/src/model/base_model_remove_bias.py @@ -0,0 +1,246 @@ +# -*-coding:utf-8-*- + +import mindspore as ms +import mindspore.nn as nn +import mindspore.ops as P +from mindspore.ops import composite as C +from mindspore.ops import functional as F +from mindspore.ops import operations as op +from mindspore.common.initializer import TruncatedNormal +from mindspore import dtype as mstype + +class SharedMLP(nn.Cell): + def __init__( + self, + in_channels, + out_channels, + kernel_size=1, + stride=1, + transpose=False, + pad_mode='same', + bn=False, + activation_fn=None + ): + super(SharedMLP, self).__init__() + + conv_fn = nn.Conv2dTranspose if transpose else nn.Conv2d + + self.conv = conv_fn( + in_channels, + out_channels, + kernel_size, + stride=stride, + pad_mode='valid', + has_bias=False, + weight_init=TruncatedNormal(sigma=1e-3) + ) + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) if bn else None + self.activation_fn = activation_fn + + def construct(self, input): + r""" + construct method + + Parameters + ---------- + input: ms.Tensor, shape (B, d_in, N, K) + + Returns + ------- + ms.Tensor, shape (B, d_out, N, K) + """ + x = self.conv(input) + if self.batch_norm: + x = self.batch_norm(x) + if self.activation_fn: + x = self.activation_fn(x) + return x + + +class LocalSpatialEncoding(nn.Cell): + def __init__(self, in_channel=10, out_channel=1, use_pos_encoding=True): + super(LocalSpatialEncoding, self).__init__() + + self.mlp = SharedMLP(in_channel, out_channel, bn=True, activation_fn=nn.LeakyReLU(0.2)) + # self.mlp = SharedMLP(10, d, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.d = out_channel + self.use_pos_encoding = use_pos_encoding + + def construct(self, coords, features, neighbor_idx): + r""" + construct method + + Parameters + ---------- + coords: ms.Tensor, shape (B, N, 3) + coordinates of the point cloud + features: ms.Tensor, shape (B, d, N, 1) + features of the point cloud + neighbor_idx: ms.Tensor, shape (B, N, K) + + Returns + ------- + ms.Tensor, shape (B, 2*d, N, K) + """ + + idx = neighbor_idx # (4,40960,16) + extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) # (4,40960,16) -> (4,1,40960,16) -> (4,3,40960,16) + cat = P.Concat(-3) + if self.use_pos_encoding: + # finding neighboring points + xyz_tile = P.Tile()(coords.transpose(0, 2, 1).expand_dims(-1), + (1, 1, 1, idx.shape[-1])) # (4,3,40960) -> (4,3,40960,16) + neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(mstype.int32)) # shape (4, 3, 40960, 16) + relative_xyz = xyz_tile - neighbor_xyz # relative_xyz + relative_dist = P.Sqrt()(P.ReduceSum(keep_dims=True)(P.Square()(relative_xyz), -3)) + + # relative point position encoding + f_xyz = cat(( + relative_dist, # (4,1,40960,16) + relative_xyz, # (4,3,40960,16) + xyz_tile, # (4,3,40960,16) + neighbor_xyz, # (4,3,40960,16) + )) # (4,10,40960,16) + + # ==========> tensorflow 源码 + # f_xyz = self.relative_pos_encoding(xyz, neigh_idx) + # def relative_pos_encoding(self, xyz, neigh_idx): + # ... + # relative_feature = tf.concat([relative_dis, relative_xyz, xyz_tile, neighbor_xyz], axis=-1) + # return relative_feature + # ==========> tensorflow 源码 + else: + f_xyz = coords + f_xyz = self.mlp(f_xyz) # (4,10,40960,16) -> (4,8,40960,16) + + f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) # (4, 8, 40960, 1) -> (4,8,40960,16) + extended_idx_for_feat = P.Tile()(idx.expand_dims(1), (1, f_xyz.shape[1], 1, 1)) + # (4,8,40960,16) -> (4,8,40960,16) + f_neighbours = P.GatherD()(f_tile, 2, extended_idx_for_feat.astype(mstype.int32)) + + f_concat = cat([f_xyz, f_neighbours]) # (4,8,40960,16) & (4,8,40960,16) -> (4,16,40960,16) + + if self.use_pos_encoding: + return f_xyz, f_concat + else: + return f_concat + + +class AttentivePooling(nn.Cell): + def __init__(self, in_channels, out_channels): + super(AttentivePooling, self).__init__() + + self.score_fn = nn.SequentialCell([ + nn.Dense(in_channels, in_channels, has_bias=False), + nn.Softmax(-2) + ]) + self.mlp = SharedMLP(in_channels, out_channels, bn=True, activation_fn=nn.LeakyReLU(0.2)) + + def construct(self, x): + r""" + construct method + + Parameters + ---------- + x: ms.Tensor, shape (B, d_in, N, K) + + Returns + ------- + ms.Tensor, shape (B, d_out, N, 1) + """ + # computing attention scores + scores = self.score_fn(x.transpose(0, 2, 3, 1)).transpose(0, 3, 1, 2) + + # sum over the neighbors + features = scores * x + features = P.ReduceSum(keep_dims=True)(features, -1) # shape (B, d_in, N, 1) + + return self.mlp(features) + + +class LocalFeatureAggregation(nn.Cell): + def __init__(self, d_in, d_out): + super(LocalFeatureAggregation, self).__init__() + + self.mlp1 = SharedMLP(d_in, d_out // 2, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.mlp2 = SharedMLP(d_out, 2 * d_out, bn=True) + self.shortcut = SharedMLP(d_in, 2 * d_out, bn=True) + + self.lse1 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2, use_pos_encoding=True) + # self.lse2 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2) + self.lse2 = LocalSpatialEncoding(in_channel=d_out // 2, out_channel=d_out // 2, use_pos_encoding=False) + # self.lse2 = LocalSpatialEncoding(d_out // 2) + + self.pool1 = AttentivePooling(d_out, d_out // 2) + self.pool2 = AttentivePooling(d_out, d_out) + + self.lrelu = nn.LeakyReLU(0.2) + + def construct(self, coords, features, neighbor_idx): + r""" + construct method + + Parameters + ---------- + coords: ms.Tensor, shape (B, N, 3) + coordinates of the point cloud + features: ms.Tensor, shape (B, d, N, 1) + features of the point cloud + neighbor_idx: ms.Tensor, shape (B, N, 16) + knn neighbor idx + Returns + ------- + ms.Tensor, shape (B, 2*d_out, N, 1) + """ + f_pc = self.mlp1(features) # (4, 8, 40960, 1) + + f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) # (4, 8, 40960, 16) (4, 16, 40960, 16) + f_pc_agg = self.pool1(f_concat) # (4, 8, 40960, 1) + + f_concat = self.lse2(f_xyz, f_pc_agg, + neighbor_idx) # coords: (4, 40960, 3); x: (4, 8, 40960, 1) neighbor_idx:(4, 40960, 16) + f_pc_agg = self.pool2(f_concat) + + return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) + + +class TrainingWrapper(nn.Cell): + """Training wrapper.""" + + def __init__(self, network, optimizer, sens=1.0): + super(TrainingWrapper, self).__init__(auto_prefix=False) + self.network = network + self.network.set_grad() + self.weights = optimizer.parameters + self.optimizer = optimizer + self.grad = C.GradOperation(get_by_list=True, sens_param=True) + self.sens = sens + + def construct(self, *args): + """Build a forward graph""" + weights = self.weights + loss, logits = self.network(*args) + sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) + grads = self.grad(self.network, weights)(*args, sens) + return F.depend(loss, self.optimizer(grads)), logits + + +def get_param_groups(network): + """Param groups for optimizer.""" + decay_params = [] + no_decay_params = [] + for x in network.trainable_params(): + parameter_name = x.name + if parameter_name.endswith('.bias'): + # all bias not using weight decay + no_decay_params.append(x) + elif parameter_name.endswith('.gamma'): + # bn weight bias not using weight decay, be carefully for now x not include BN + no_decay_params.append(x) + elif parameter_name.endswith('.beta'): + # bn weight bias not using weight decay, be carefully for now x not include BN + no_decay_params.append(x) + else: + decay_params.append(x) + + return [{'params': no_decay_params, 'weight_decay': 0.0}, {'params': decay_params}] diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py new file mode 100644 index 000000000..e1c7f4963 --- /dev/null +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -0,0 +1,357 @@ +# -*-coding:utf-8-*- + +import mindspore as ms +import mindspore.nn as nn +import mindspore.ops as P +from mindspore.ops import composite as C +from mindspore.ops import operations as op +from .base_model import SharedMLP, LocalFeatureAggregation +from mindspore import Tensor, Parameter, ms_function +from mindspore import dtype as mstype + +import time +from time import strftime, localtime + + +class RandLANet_S3DIS(nn.Cell): + def __init__(self, d_in, num_classes): + super(RandLANet_S3DIS, self).__init__() + + self.fc_start = nn.Dense(d_in, 8) + self.bn_start = nn.SequentialCell([ + nn.BatchNorm2d(8, eps=1e-6, momentum=0.99), + nn.LeakyReLU(0.2) + ]) + + # encoding layers + self.encoder = nn.CellList([ + LocalFeatureAggregation(8, 16), + LocalFeatureAggregation(32, 64), + LocalFeatureAggregation(128, 128), + LocalFeatureAggregation(256, 256), + LocalFeatureAggregation(512, 512) + ]) + + self.mlp = SharedMLP(1024, 1024, bn=True, activation_fn=nn.LeakyReLU(0.2)) + + # decoding layers + decoder_kwargs = dict( + transpose=True, + bn=True, + activation_fn=nn.LeakyReLU(0.2) + ) + self.decoder = nn.CellList([ + SharedMLP(1536, 512, **decoder_kwargs), + SharedMLP(768, 256, **decoder_kwargs), + SharedMLP(384, 128, **decoder_kwargs), + SharedMLP(160, 32, **decoder_kwargs), + SharedMLP(64, 32, **decoder_kwargs) + ]) + + self.fc_end_fc1 = SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.fc_end_fc2 = SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.fc_end_drop = nn.Dropout() + self.fc_end_fc3 = SharedMLP(32, num_classes) + # # final semantic prediction + # self.fc_end = nn.SequentialCell([ + # SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)), + # SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)), + # nn.Dropout(), + # SharedMLP(32, num_classes) + # ]) + + def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx): + r""" + construct method + + Parameters + ---------- + xyz: list of ms.Tensor, shape (num_layer, B, N_layer, 3), each layer xyz + feature: ms.Tensor, shape (B, N, d), input feature [xyz ; feature] + neighbor_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 16), each layer knn neighbor idx + sub_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 16), each layer pooling idx + interp_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 1), each layer interp idx + + Returns + ------- + ms.Tensor, shape (B, num_classes, N) + segmentation scores for each point + """ + # print(feature.shape) + a_time = time.time() + feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) + feature = self.bn_start(feature) # shape (B, 8, N, 1) + b_time = time.time() + # print(f"[model_s3dis.py] fc_start & bn_start. Time :{b_time - a_time}") + # <<<<<<<<<< ENCODER + + f_stack = [] + for i in range(5): + # at iteration i, feature.shape = (B, d_layer, N_layer, 1) + encoder_begin_time = time.time() + f_encoder_i = self.encoder[i](xyz[i], feature, + neighbor_idx[i]) # (B,40960,3) (4, 8, 40960, 1) (4, 40960, 16) + encoder_end_time = time.time() + # print(f"[model_s3dis.py] encoder {i}. Time :{encoder_end_time - encoder_begin_time}s") + f_sampled_i = self.random_sample(f_encoder_i, sub_idx[i]) + random_sample_time = time.time() + # print(f"[model_s3dis.py] random_sample {i}. Time :{random_sample_time - encoder_end_time}s") + feature = f_sampled_i + if i == 0: + f_stack.append(f_encoder_i) + f_stack.append(f_sampled_i) + # print(f"[model_s3dis.py] append {i}. Time :{time.time() - random_sample_time}s") + c_time = time.time() + # print(f"[model_s3dis.py] encoder & random_sample. Time :{c_time - b_time}s") + # # >>>>>>>>>> ENCODER + + feature = self.mlp(f_stack[-1]) # [B, d, N, 1] + + # <<<<<<<<<< DECODER + + f_decoder_list = [] + for j in range(5): + f_interp_i = self.random_sample(feature, interp_idx[-j - 1]) # [B, d, n, 1] + cat = P.Concat(1) + f_decoder_i = self.decoder[j](cat((f_stack[-j - 2], f_interp_i))) + feature = f_decoder_i + f_decoder_list.append(f_decoder_i) + d_time = time.time() + # print(f"[model_s3dis.py] random_sample & decoder. Time :{d_time - c_time}s") + # >>>>>>>>>> DECODER + f_layer_fc1 = self.fc_end_fc1(f_decoder_list[-1]) + f_layer_fc2 = self.fc_end_fc2(f_layer_fc1) + f_layer_drop = self.fc_end_drop(f_layer_fc2) + f_layer_fc3 = self.fc_end_fc3(f_layer_drop) + + e_time = time.time() + # print(f"[model_s3dis.py] fc_end. Time :{e_time - d_time}s") + + f_layer_fc2, f_layer_fc3 = f_layer_fc2.swapaxes(1, 3), f_layer_fc3.swapaxes(1, 3) + f_layer_out = P.Concat(axis=-1)([f_layer_fc3, f_layer_fc2]) + f_out = f_layer_out.squeeze(1) # (B,N_points,13+32) + + return f_out # (B,N_points,45) + + @staticmethod + def random_sample(feature, pool_idx): + """ + :param feature: [B, d, N, 1] input features matrix + :param pool_idx: [B, N', max_num] N' < N, N' is the selected position after pooling + :return: pool_features = [B, d, N', 1] pooled features matrix + """ + b, d = feature.shape[:2] + n_ = pool_idx.shape[1] + # [B, N', max_num] --> [B, d, N', max_num] + # pool_idx = P.repeat_elements(pool_idx.expand_dims(1), feature.shape[1], 1) + pool_idx = P.Tile()(pool_idx.expand_dims(1), (1, feature.shape[1], 1, 1)) + # [B, d, N', max_num] --> [B, d, N'*max_num] + pool_idx = pool_idx.reshape((b, d, -1)) + pool_features = P.GatherD()(feature.squeeze(-1), -1, pool_idx.astype(ms.int32)) + pool_features = pool_features.reshape((b, d, n_, -1)) + pool_features = P.ReduceMax(keep_dims=True)(pool_features, -1) # [B, d, N', 1] + return pool_features + + +class RandLA_S3DIS_WithLoss(nn.Cell): + """RadnLA-net with loss""" + + def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): + super(RandLA_S3DIS_WithLoss, self).__init__() + self.network = network + self.weights = Tensor(weights, dtype=mstype.float32) + self.num_classes = num_classes + self.ignored_label_inds = ignored_label_indexs + self.c_epoch = c_epoch + self.loss3_type = loss3_type + self.topk = topk + + self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float32) + # + self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) + self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) + + def construct(self, feature, feature2, labels, input_inds, cloud_inds, + p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, + pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): + # data_begin_time = time.time() + xyz = [p0, p1, p2, p3, p4] + neighbor_idx = [n0, n1, n2, n3, n4] + sub_idx = [pl0, pl1, pl2, pl3, pl4] + interp_idx = [u0, u1, u2, u3, u4] + + # network_begin_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] data_prepare 耗时: {network_begin_time - data_begin_time}s") + logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) + # network_end_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] network 耗时: {network_end_time - network_begin_time}s") + # pred_embed = logits # (B, N, 45) + xyzrgb = feature2 # (B, N, 6) + labels = labels # (B,N) + + # 当c_epoch_k==0时,只需要计算有GT标注点的loss值 + logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) + pred_embed = logits_embed[..., self.num_classes:] # (B,N,45) -> (B,N,32) + logits = logits.reshape((-1, self.num_classes)) + pred_embed = pred_embed.reshape((-1, 32)) + # logit = logits.reshape((-1, self.num_classes)) # (B*N,13) + labels = labels.reshape((-1,)) # (b,n) -> (b*n) + xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) + + # Boolean mask of points that should be ignored + # (B*N,) + ignore_mask = P.zeros_like(labels).astype(mstype.bool_) + for ign_label in self.ignored_label_inds: + ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # + # 0为无效,1为有效 + valid_mask = P.logical_not(ignore_mask) # (B*N,) + + # (B*N,13) + one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) + weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) + # 输出Tensor各维度上的和,以达到对所有维度进行归约的目的。也可以对指定维度进行求和归约 + # (B*N, 13) -> (B*N,) + weights = P.ReduceSum()(weights, 1) # + # (B*N,) and (B*N,13) -> + unweighted_loss = self.loss_fn(logits, one_hot_labels) + weighted_loss = unweighted_loss * weights + weighted_loss = weighted_loss * valid_mask + CE_loss = P.ReduceSum()(weighted_loss) + num_valid_points = P.ReduceSum()(valid_mask.astype(mstype.float32)) + # num_valid_points = int(P.count_nonzero(valid_mask.astype(mstype.int32))) + # CE_loss = P.ReduceSum()(weighted_loss).sum() + CE_loss = CE_loss / num_valid_points + ### + if self.c_epoch_k == 0: + loss = CE_loss + self.CE_LOSS = CE_loss + self.SP_LOSS = 0 + else: + SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, + self.topk) * self.c_epoch_k + loss = CE_loss + SP_loss + + self.CE_LOSS = CE_loss + self.SP_LOSS = SP_loss + + return loss + + @staticmethod + def get_sp_loss_by_mask(embed, logits, one_hot_label, valid_mask, topk): + """ + + Args: + embed: + logits: + one_hot_label: + valid_mask: + topk: + + Returns: + + """ + invalid_mask = P.logical_not(valid_mask) # (B*N,) + num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) + topk += num_invalid_points + # 点类别的数量 + num_class = one_hot_label.shape[1] # scalar: 13 + + valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) + valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) + invalid_embed = embed * invalid_mask.reshape(-1, 1) # (B*N,32) + # => 将有效点的label矩阵进行转置:[M,class_num] -> [class_num,M] (13,B*N) + valid_one_hot_label_T = P.transpose(valid_one_hot_label, (1, 0)) + # => 每一行那个类有那些点:[class_num,B*N] * [B*N,dim] -> [class_num,dim] + sum_embed = P.matmul(valid_one_hot_label_T, valid_embed) + # => 求class的平均嵌入:[class_num,dim] + # mean_embed 为每个类别的embedding,如果这个类别没有样本,则embedding全为0 + mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) + # => 求unlabelled points 与 class embedding的相似度 + # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] + adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + # adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + + # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) + neg_adj = -adj_matrix # (B*N,13) 取负 + # 转置为了下一步 [N, M] -> [M,N] (M是有标签的点) + neg_adj_t = P.transpose(neg_adj, (1, 0)) # (13,B*N) + # 取最不相似的 top k个点 + _, nn_idx = P.TopK()(neg_adj_t, topk) + s = P.shape(neg_adj_t) # s (M,N) + row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) + ones_idx = P.Stack(axis=1)([row_idx.reshape([-1]), nn_idx.reshape([-1])]) + res = P.scatter_nd(ones_idx, P.ones(s[0] * topk, neg_adj_t.dtype), s) + nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] multi_hot + + new_valid_mask = P.reduce_sum(nn_idx_multi_hot, axis=1) > 0 # (B*N,) + new_valid_mask = new_valid_mask.reshape(-1, 1) # (B*N,1) + num_new_valid_mask = int(P.count_nonzero(new_valid_mask.astype(mstype.int32))) + + w_ij = P.exp(-1.0 * adj_matrix) # (B*N,13) + w_ij = w_ij * new_valid_mask # (B*N,13) + w_ij_label = nn_idx_multi_hot * new_valid_mask # (B*N,13) + w_ij = P.mul(w_ij, w_ij_label) # (B*N,13) + + new_soft_label_hot = nn.Softmax(axis=-1)(w_ij) # (B*N,13) + top1 = new_soft_label_hot.argmax(axis=-1) + soft_label_mask = P.OneHot()(top1, num_class, Tensor(1.0), Tensor(0.0)) + new_soft_label_hot = P.mul(new_soft_label_hot, soft_label_mask) + + logits = logits * new_valid_mask + new_soft_label_hot = new_soft_label_hot * new_valid_mask + loss = nn.SoftmaxCrossEntropyWithLogits()(logits, new_soft_label_hot) + loss = loss.sum() / num_new_valid_mask + + return loss + + @staticmethod + def double_feature(point_feature1, point_feature2): + """ + Compute pairwise distance of a point cloud. + Args: + [N,C],[M,C] + point_cloud: tensor (batch_size, num_points, num_dims) + Returns: + pairwise distance: (batch_size, num_points, num_points) + """ + point2_transpose = P.transpose(point_feature2, (1, 0)) # [C, M] + point_inner = P.matmul(point_feature1, point2_transpose) # [N,M] + point_inner = -2 * point_inner + + point1_square = P.ReduceSum(keep_dims=True)(P.square(point_feature1), axis=-1) + point2_square = P.ReduceSum(keep_dims=True)(P.square(point_feature2), axis=-1) + + point2_square_transpose = P.transpose(point2_square, (1, 0)) # [1,M] + adj_matrix = point1_square + point_inner + point2_square_transpose + + return adj_matrix + + +class TrainingWrapper(nn.Cell): + """Training wrapper.""" + + def __init__(self, network, optimizer, sens=1.0): + super(TrainingWrapper, self).__init__(auto_prefix=False) + self.network = network + self.network_logits = self.network.network + self.network.set_grad() + self.opt_weights = optimizer.parameters + self.optimizer = optimizer + self.grad = C.GradOperation(get_by_list=True, sens_param=True) + self.sens = sens + + def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx, labels): + """Build a forward graph""" + + # loss calculate + loss = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels) + logit = self.network_logits(xyz, feature, neighbor_idx, sub_idx, interp_idx) + + # opt update + opt_weights = self.opt_weights + sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) + grads = self.grad(self.network, opt_weights)(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels, sens) + res = P.depend(loss, self.optimizer(grads)) + return res, logit diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py new file mode 100644 index 000000000..cd16a1e88 --- /dev/null +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -0,0 +1,366 @@ +# -*-coding:utf-8-*- + +import mindspore as ms +import mindspore.nn as nn +import mindspore.ops as P +from mindspore.ops import composite as C +from mindspore.ops import operations as op +from mindspore import Tensor, Parameter, ms_function +from mindspore import dtype as mstype + +from .base_model_remove_bias import SharedMLP, LocalFeatureAggregation +import time +from time import strftime, localtime + + +class RandLANet_S3DIS(nn.Cell): + def __init__(self, d_in, num_classes): + super(RandLANet_S3DIS, self).__init__() + + self.fc_start = nn.Dense(d_in, 8, has_bias=False) + self.bn_start = nn.SequentialCell([ + nn.BatchNorm2d(8, eps=1e-6, momentum=0.99), + nn.LeakyReLU(0.2) + ]) + + # encoding layers + self.encoder = nn.CellList([ + LocalFeatureAggregation(8, 16), + LocalFeatureAggregation(32, 64), + LocalFeatureAggregation(128, 128), + LocalFeatureAggregation(256, 256), + LocalFeatureAggregation(512, 512) + ]) + + self.mlp = SharedMLP(1024, 1024, bn=True, activation_fn=nn.LeakyReLU(0.2)) + + # decoding layers + decoder_kwargs = dict( + transpose=True, + bn=True, + activation_fn=nn.LeakyReLU(0.2) + ) + self.decoder = nn.CellList([ + SharedMLP(1536, 512, **decoder_kwargs), + SharedMLP(768, 256, **decoder_kwargs), + SharedMLP(384, 128, **decoder_kwargs), + SharedMLP(160, 32, **decoder_kwargs), + SharedMLP(64, 32, **decoder_kwargs) + ]) + + self.fc_end_fc1 = SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.fc_end_fc2 = SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)) + self.fc_end_drop = nn.Dropout() + self.fc_end_fc3 = SharedMLP(32, num_classes) + # # final semantic prediction + # self.fc_end = nn.SequentialCell([ + # SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)), + # SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)), + # nn.Dropout(), + # SharedMLP(32, num_classes) + # ]) + + def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx): + r""" + construct method + + Parameters + ---------- + xyz: list of ms.Tensor, shape (num_layer, B, N_layer, 3), each layer xyz + feature: ms.Tensor, shape (B, N, d), input feature [xyz ; feature] + neighbor_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 16), each layer knn neighbor idx + sub_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 16), each layer pooling idx + interp_idx: list of ms.Tensor, shape (num_layer, B, N_layer, 1), each layer interp idx + + Returns + ------- + ms.Tensor, shape (B, num_classes, N) + segmentation scores for each point + """ + # print(feature.shape) + # a_time = time.time() + feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) + feature = self.bn_start(feature) # shape (B, 8, N, 1) + # b_time = time.time() + # print(f"[model_s3dis.py] fc_start & bn_start. Time :{b_time - a_time}") + # <<<<<<<<<< ENCODER + + f_stack = [] + for i in range(5): + # at iteration i, feature.shape = (B, d_layer, N_layer, 1) + encoder_begin_time = time.time() + f_encoder_i = self.encoder[i](xyz[i], feature, + neighbor_idx[i]) # (B,40960,3) (4, 8, 40960, 1) (4, 40960, 16) + encoder_end_time = time.time() + # print(f"[model_s3dis.py] encoder {i}. Time :{encoder_end_time - encoder_begin_time}s") + f_sampled_i = self.random_sample(f_encoder_i, sub_idx[i]) + random_sample_time = time.time() + # print(f"[model_s3dis.py] random_sample {i}. Time :{random_sample_time - encoder_end_time}s") + feature = f_sampled_i + if i == 0: + f_stack.append(f_encoder_i) + f_stack.append(f_sampled_i) + # print(f"[model_s3dis.py] append {i}. Time :{time.time() - random_sample_time}s") + # c_time = time.time() + # print(f"[model_s3dis.py] encoder & random_sample. Time :{c_time - b_time}s") + # # >>>>>>>>>> ENCODER + + feature = self.mlp(f_stack[-1]) # [B, d, N, 1] + + # <<<<<<<<<< DECODER + + f_decoder_list = [] + for j in range(5): + f_interp_i = self.random_sample(feature, interp_idx[-j - 1]) # [B, d, n, 1] + cat = P.Concat(1) + f_decoder_i = self.decoder[j](cat((f_stack[-j - 2], f_interp_i))) + feature = f_decoder_i + f_decoder_list.append(f_decoder_i) + # d_time = time.time() + # print(f"[model_s3dis.py] random_sample & decoder. Time :{d_time - c_time}s") + # >>>>>>>>>> DECODER + f_layer_fc1 = self.fc_end_fc1(f_decoder_list[-1]) + f_layer_fc2 = self.fc_end_fc2(f_layer_fc1) + f_layer_drop = self.fc_end_drop(f_layer_fc2) + f_layer_fc3 = self.fc_end_fc3(f_layer_drop) + + # e_time = time.time() + # print(f"[model_s3dis.py] fc_end. Time :{e_time - d_time}s") + + f_layer_fc2, f_layer_fc3 = f_layer_fc2.swapaxes(1, 3), f_layer_fc3.swapaxes(1, 3) + f_layer_out = P.Concat(axis=-1)([f_layer_fc3, f_layer_fc2]) + f_out = f_layer_out.squeeze(1) # (B,N_points,13+32) + + return f_out # (B,N_points,45) + + @staticmethod + def random_sample(feature, pool_idx): + """ + :param feature: [B, d, N, 1] input features matrix + :param pool_idx: [B, N', max_num] N' < N, N' is the selected position after pooling + :return: pool_features = [B, d, N', 1] pooled features matrix + """ + b, d = feature.shape[:2] + n_ = pool_idx.shape[1] + # [B, N', max_num] --> [B, d, N', max_num] + # pool_idx = P.repeat_elements(pool_idx.expand_dims(1), feature.shape[1], 1) + pool_idx = P.Tile()(pool_idx.expand_dims(1), (1, feature.shape[1], 1, 1)) + # [B, d, N', max_num] --> [B, d, N'*max_num] + pool_idx = pool_idx.reshape((b, d, -1)) + pool_features = P.GatherD()(feature.squeeze(-1), -1, pool_idx.astype(mstype.int32)) + pool_features = pool_features.reshape((b, d, n_, -1)) + pool_features = P.ReduceMax(keep_dims=True)(pool_features, -1) # [B, d, N', 1] + return pool_features + + +class RandLA_S3DIS_WithLoss(nn.Cell): + """RadnLA-net with loss""" + + def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): + super(RandLA_S3DIS_WithLoss, self).__init__() + self.network = network + self.weights = Tensor(weights, dtype=mstype.float16) + # self.weights = Tensor(weights, dtype=mstype.float32) + self.num_classes = num_classes + self.ignored_label_inds = ignored_label_indexs + self.c_epoch = c_epoch + self.loss3_type = loss3_type + self.topk = topk + + self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float16) + # self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float32) + # + self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float16) + # self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) + self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) + + def construct(self, feature, feature2, labels, input_inds, cloud_inds, + p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, + pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): + # data_begin_time = time.time() + xyz = [p0, p1, p2, p3, p4] + neighbor_idx = [n0, n1, n2, n3, n4] + sub_idx = [pl0, pl1, pl2, pl3, pl4] + interp_idx = [u0, u1, u2, u3, u4] + + # network_begin_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] data_prepare 耗时: {network_begin_time - data_begin_time}s") + logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) + # network_end_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] network 耗时: {network_end_time - network_begin_time}s") + # pred_embed = logits # (B, N, 45) + xyzrgb = feature2 # (B, N, 6) + labels = labels # (B,N) + + # 当c_epoch_k==0时,只需要计算有GT标注点的loss值 + logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) + pred_embed = logits_embed[..., self.num_classes:] # (B,N,45) -> (B,N,32) + logits = logits.reshape((-1, self.num_classes)) + pred_embed = pred_embed.reshape((-1, 32)) + # logit = logits.reshape((-1, self.num_classes)) # (B*N,13) + labels = labels.reshape((-1,)) # (b,n) -> (b*n) + xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) + + # Boolean mask of points that should be ignored + # (B*N,) + ignore_mask = P.zeros_like(labels).astype(mstype.bool_) + for ign_label in self.ignored_label_inds: + ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # + # 0为无效,1为有效 + valid_mask = P.logical_not(ignore_mask) # (B*N,) + + # (B*N,13) + one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) + weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) + # 输出Tensor各维度上的和,以达到对所有维度进行归约的目的。也可以对指定维度进行求和归约 + # (B*N, 13) -> (B*N,) + weights = P.ReduceSum()(weights, 1) # + # (B*N,) and (B*N,13) -> + unweighted_loss = self.loss_fn(logits, one_hot_labels) + weighted_loss = unweighted_loss * weights + weighted_loss = weighted_loss * valid_mask + CE_loss = P.ReduceSum()(weighted_loss) + num_valid_points = P.ReduceSum()(valid_mask.astype(weighted_loss.dtype)) + # num_valid_points = int(P.count_nonzero(valid_mask.astype(mstype.int32))) + # CE_loss = P.ReduceSum()(weighted_loss).sum() + CE_loss = CE_loss / num_valid_points + ### + + if self.c_epoch_k == 0: + loss = CE_loss + else: + # loss = CE_loss + SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, + self.topk) * self.c_epoch_k + loss = CE_loss + SP_loss + + # loss_end_time = time.time() + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] loss 耗时: {loss_end_time - network_end_time}s") + # + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[model_s3dis.py] RandLA_S3DIS_WithLoss 耗时: {loss_end_time - data_begin_time}s") + return loss + + def get_sp_loss_by_mask(self, embed, logits, one_hot_label, valid_mask, topk): + """ + + Args: + embed: + logits: + one_hot_label: + valid_mask: + topk: + + Returns: + + """ + invalid_mask = P.logical_not(valid_mask) # (B*N,) + num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) + topk += num_invalid_points + # 点类别的数量 + num_class = one_hot_label.shape[1] # scalar: 13 + + valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) + valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) + invalid_embed = embed * invalid_mask.reshape(-1, 1) # (B*N,32) + # => 将有效点的label矩阵进行转置:[M,class_num] -> [class_num,M] (13,B*N) + valid_one_hot_label_T = P.transpose(valid_one_hot_label, (1, 0)) + # => 每一行那个类有那些点:[class_num,B*N] * [B*N,dim] -> [class_num,dim] + sum_embed = P.matmul(valid_one_hot_label_T, valid_embed) + # => 求class的平均嵌入:[class_num,dim] + # mean_embed 为每个类别的embedding,如果这个类别没有样本,则embedding全为0 + mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) + # => 求unlabelled points 与 class embedding的相似度 + # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] + adj_matrix = self.double_feature(invalid_embed, mean_embed) + # adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + + # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) + neg_adj = -adj_matrix # (B*N,13) 取负 + # 转置为了下一步 [N, M] -> [M,N] (M是有标签的点) + neg_adj_t = P.transpose(neg_adj, (1, 0)) # (13,B*N) + # 取最不相似的 top k个点 + _, nn_idx = P.TopK()(neg_adj_t, topk) + s = P.shape(neg_adj_t) # s (M,N) + row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) + ones_idx = P.Stack(axis=1)([row_idx.reshape([-1]), nn_idx.reshape([-1])]) + res = P.scatter_nd(ones_idx, P.ones(s[0] * topk, neg_adj_t.dtype), s) + nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] multi_hot + + new_valid_mask = P.reduce_sum(nn_idx_multi_hot, axis=1) > 0 # (B*N,) + new_valid_mask = new_valid_mask.reshape(-1, 1) # (B*N,1) + num_new_valid_mask = int(P.count_nonzero(new_valid_mask.astype(mstype.int32))) + + w_ij = P.exp(-1.0 * adj_matrix) # (B*N,13) + w_ij = w_ij * new_valid_mask # (B*N,13) + w_ij_label = nn_idx_multi_hot * new_valid_mask # (B*N,13) + w_ij = P.mul(w_ij, w_ij_label) # (B*N,13) + + new_soft_label_hot = nn.Softmax(axis=-1)(w_ij) # (B*N,13) + top1 = new_soft_label_hot.argmax(axis=-1) + soft_label_mask = self.onehot(top1) + # soft_label_mask = P.OneHot()(top1, num_class, + # Tensor(1.0, dtype=mstype.float16), + # Tensor(0.0, dtype=mstype.float16)) + new_soft_label_hot = P.mul(new_soft_label_hot, soft_label_mask) + + logits = logits * new_valid_mask + new_soft_label_hot = new_soft_label_hot * new_valid_mask + loss = nn.SoftmaxCrossEntropyWithLogits()(logits, new_soft_label_hot) + loss = loss.sum() / num_new_valid_mask + + return loss + + # @ms_function() + # @staticmethod + def double_feature(self, point_feature1, point_feature2): + """ + Compute pairwise distance of a point cloud. + Args: + [N,C],[M,C] + point_cloud: tensor (batch_size, num_points, num_dims) + Returns: + pairwise distance: (batch_size, num_points, num_points) + """ + point2_transpose = P.transpose(point_feature2, (1, 0)) # [C, M] + point_inner = P.matmul(point_feature1, point2_transpose) # [N,M] + point_inner = -2 * point_inner + + point1_square = P.ReduceSum(keep_dims=True)(P.square(point_feature1), axis=-1) + point2_square = P.ReduceSum(keep_dims=True)(P.square(point_feature2), axis=-1) + + point2_square_transpose = P.transpose(point2_square, (1, 0)) # [1,M] + adj_matrix = point1_square + point_inner + point2_square_transpose + + return adj_matrix + + +class TrainingWrapper(nn.Cell): + """Training wrapper.""" + + def __init__(self, network, optimizer, sens=1.0): + super(TrainingWrapper, self).__init__(auto_prefix=False) + self.network = network + self.network_logits = self.network.network + self.network.set_grad() + self.opt_weights = optimizer.parameters + self.optimizer = optimizer + self.grad = C.GradOperation(get_by_list=True, sens_param=True) + self.sens = sens + + def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx, labels): + """Build a forward graph""" + + # loss calculate + loss = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels) + logit = self.network_logits(xyz, feature, neighbor_idx, sub_idx, interp_idx) + + # opt update + opt_weights = self.opt_weights + sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) + grads = self.grad(self.network, opt_weights)(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels, sens) + res = P.depend(loss, self.optimizer(grads)) + return res, logit diff --git a/research/cv/WS3/src/utils/__init__.py b/research/cv/WS3/src/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py new file mode 100644 index 000000000..9e4cec42d --- /dev/null +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -0,0 +1,80 @@ +from sklearn.neighbors import KDTree +from os.path import join, exists, dirname, abspath +import numpy as np +import pandas as pd +import os, sys, glob, pickle + +BASE_DIR = dirname(abspath(__file__)) +ROOT_DIR = dirname(BASE_DIR) +sys.path.append(BASE_DIR) +sys.path.append(ROOT_DIR) +from tools import DataProcessing as DP + +dataset_path = 'xxxx/xxxxx/dataset/S3DIS/Stanford3dDataset_v1.2_Aligned_Version' +anno_paths = [line.rstrip() for line in open(join(BASE_DIR, '../../third_party/meta/anno_paths.txt'))] +anno_paths = [join(dataset_path, p) for p in anno_paths] + +gt_class = [x.rstrip() for x in open(join(BASE_DIR, '../../third_party/meta/class_names.txt'))] +gt_class2label = {cls: i for i, cls in enumerate(gt_class)} + +sub_grid_size = 0.04 +original_pc_folder = join(dirname(dataset_path), 'original_npy') +sub_pc_folder = join(dirname(dataset_path), 'train_{:.3f}'.format(sub_grid_size)) +os.mkdir(original_pc_folder) if not exists(original_pc_folder) else None +os.mkdir(sub_pc_folder) if not exists(sub_pc_folder) else None +out_format = '.npy' + + +def convert_pc2npy(anno_path, save_path): + """ + Convert original dataset files to npy file (each line is XYZRGBL). + We aggregated all the points from each instance in the room. + :param anno_path: path to annotations. e.g. Area_1/office_2/Annotations/ + :param save_path: path to save original point clouds (each line is XYZRGBL) + :return: None + """ + data_list = [] + + for f in glob.glob(join(anno_path, '*.txt')): + class_name = os.path.basename(f).split('_')[0] + if class_name not in gt_class: # note: in some room there is 'staris' class.. + class_name = 'clutter' + pc = pd.read_csv(f, header=None, delim_whitespace=True).values + labels = np.ones((pc.shape[0], 1)) * gt_class2label[class_name] + data_list.append(np.concatenate([pc, labels], 1)) # Nx7 + + pc_label = np.concatenate(data_list, 0) + xyz_min = np.amin(pc_label, axis=0)[0:3] + pc_label[:, 0:3] -= xyz_min + + xyz = pc_label[:, :3].astype(np.float32) + colors = pc_label[:, 3:6].astype(np.uint8) + labels = np.expand_dims(pc_label[:, 6].astype(np.uint8),axis=1) + #print('xyz.shape:',xyz.shape,' colors.shape:',colors.shape,' labels.shape:',labels.shape) + np.save(save_path, np.concatenate((xyz, colors, labels), axis=1).T) + + # save sub_cloud and KDTree file + sub_xyz, sub_colors, sub_labels = DP.grid_sub_sampling(xyz, colors, labels, sub_grid_size) + sub_colors = sub_colors / 255.0 + sub_npy_file = join(sub_pc_folder, save_path.split('/')[-1][:-4] + out_format) + np.save(sub_npy_file, np.concatenate((sub_xyz, sub_colors, sub_labels), axis=1).T) + + search_tree = KDTree(sub_xyz) + kd_tree_file = join(sub_pc_folder, str(save_path.split('/')[-1][:-4]) + '_KDTree.pkl') + with open(kd_tree_file, 'wb') as f: + pickle.dump(search_tree, f) + + proj_idx = np.squeeze(search_tree.query(xyz, return_distance=False)) + proj_idx = proj_idx.astype(np.int32) + proj_save = join(sub_pc_folder, str(save_path.split('/')[-1][:-4]) + '_proj.pkl') + with open(proj_save, 'wb') as f: + pickle.dump([proj_idx, labels], f) + + +if __name__ == '__main__': + # Note: there is an extra character in the v1.2 data in Area_5/hallway_6. It's fixed manually. + for annotation_path in anno_paths: + print(annotation_path) + elements = str(annotation_path).split('/') + out_file_name = elements[-3] + '_' + elements[-2] + out_format + convert_pc2npy(annotation_path, join(original_pc_folder, out_file_name)) diff --git a/research/cv/WS3/src/utils/helper_ply.py b/research/cv/WS3/src/utils/helper_ply.py new file mode 100644 index 000000000..4a2aeabd9 --- /dev/null +++ b/research/cv/WS3/src/utils/helper_ply.py @@ -0,0 +1,356 @@ +# +# +# 0===============================0 +# | PLY files reader/writer | +# 0===============================0 +# +# +# ---------------------------------------------------------------------------------------------------------------------- +# +# function to read/write .ply files +# +# ---------------------------------------------------------------------------------------------------------------------- +# +# Hugues THOMAS - 10/02/2017 +# + + +# ---------------------------------------------------------------------------------------------------------------------- +# +# Imports and global variables +# \**********************************/ +# + + +# Basic libs +import numpy as np +import sys + + +# Define PLY types +ply_dtypes = dict([ + (b'int8', 'i1'), + (b'char', 'i1'), + (b'uint8', 'u1'), + (b'uchar', 'u1'), + (b'int16', 'i2'), + (b'short', 'i2'), + (b'uint16', 'u2'), + (b'ushort', 'u2'), + (b'int32', 'i4'), + (b'int', 'i4'), + (b'uint32', 'u4'), + (b'uint', 'u4'), + (b'float32', 'f4'), + (b'float', 'f4'), + (b'float64', 'f8'), + (b'double', 'f8') +]) + +# Numpy reader format +valid_formats = {'ascii': '', 'binary_big_endian': '>', + 'binary_little_endian': '<'} + + +# ---------------------------------------------------------------------------------------------------------------------- +# +# Functions +# \***************/ +# + + +def parse_header(plyfile, ext): + # Variables + line = [] + properties = [] + num_points = None + + while b'end_header' not in line and line != b'': + line = plyfile.readline() + + if b'element' in line: + line = line.split() + num_points = int(line[2]) + + elif b'property' in line: + line = line.split() + properties.append((line[2].decode(), ext + ply_dtypes[line[1]])) + + return num_points, properties + + +def parse_mesh_header(plyfile, ext): + # Variables + line = [] + vertex_properties = [] + num_points = None + num_faces = None + current_element = None + + + while b'end_header' not in line and line != b'': + line = plyfile.readline() + + # Find point element + if b'element vertex' in line: + current_element = 'vertex' + line = line.split() + num_points = int(line[2]) + + elif b'element face' in line: + current_element = 'face' + line = line.split() + num_faces = int(line[2]) + + elif b'property' in line: + if current_element == 'vertex': + line = line.split() + vertex_properties.append((line[2].decode(), ext + ply_dtypes[line[1]])) + elif current_element == 'vertex': + if not line.startswith('property list uchar int'): + raise ValueError('Unsupported faces property : ' + line) + + return num_points, num_faces, vertex_properties + + +def read_ply(filename, triangular_mesh=False): + """ + Read ".ply" files + + Parameters + ---------- + filename : string + the name of the file to read. + + Returns + ------- + result : array + data stored in the file + + Examples + -------- + Store data in file + + >>> points = np.random.rand(5, 3) + >>> values = np.random.randint(2, size=10) + >>> write_ply('example.ply', [points, values], ['x', 'y', 'z', 'values']) + + Read the file + + >>> data = read_ply('example.ply') + >>> values = data['values'] + array([0, 0, 1, 1, 0]) + + >>> points = np.vstack((data['x'], data['y'], data['z'])).T + array([[ 0.466 0.595 0.324] + [ 0.538 0.407 0.654] + [ 0.850 0.018 0.988] + [ 0.395 0.394 0.363] + [ 0.873 0.996 0.092]]) + + """ + + with open(filename, 'rb') as plyfile: + + + # Check if the file start with ply + if b'ply' not in plyfile.readline(): + raise ValueError('The file does not start whith the word ply') + + # get binary_little/big or ascii + fmt = plyfile.readline().split()[1].decode() + if fmt == "ascii": + raise ValueError('The file is not binary') + + # get extension for building the numpy dtypes + ext = valid_formats[fmt] + + # PointCloud reader vs mesh reader + if triangular_mesh: + + # Parse header + num_points, num_faces, properties = parse_mesh_header(plyfile, ext) + + # Get point data + vertex_data = np.fromfile(plyfile, dtype=properties, count=num_points) + + # Get face data + face_properties = [('k', ext + 'u1'), + ('v1', ext + 'i4'), + ('v2', ext + 'i4'), + ('v3', ext + 'i4')] + faces_data = np.fromfile(plyfile, dtype=face_properties, count=num_faces) + + # Return vertex data and concatenated faces + faces = np.vstack((faces_data['v1'], faces_data['v2'], faces_data['v3'])).T + data = [vertex_data, faces] + + else: + + # Parse header + num_points, properties = parse_header(plyfile, ext) + + # Get data + data = np.fromfile(plyfile, dtype=properties, count=num_points) + + return data + + +def header_properties(field_list, field_names): + + # List of lines to write + lines = [] + + # First line describing element vertex + lines.append('element vertex %d' % field_list[0].shape[0]) + + # Properties lines + i = 0 + for fields in field_list: + for field in fields.T: + lines.append('property %s %s' % (field.dtype.name, field_names[i])) + i += 1 + + return lines + + +def write_ply(filename, field_list, field_names, triangular_faces=None): + """ + Write ".ply" files + + Parameters + ---------- + filename : string + the name of the file to which the data is saved. A '.ply' extension will be appended to the + file name if it does no already have one. + + field_list : list, tuple, numpy array + the fields to be saved in the ply file. Either a numpy array, a list of numpy arrays or a + tuple of numpy arrays. Each 1D numpy array and each column of 2D numpy arrays are considered + as one field. + + field_names : list + the name of each fields as a list of strings. Has to be the same length as the number of + fields. + + Examples + -------- + >>> points = np.random.rand(10, 3) + >>> write_ply('example1.ply', points, ['x', 'y', 'z']) + + >>> values = np.random.randint(2, size=10) + >>> write_ply('example2.ply', [points, values], ['x', 'y', 'z', 'values']) + + >>> colors = np.random.randint(255, size=(10,3), dtype=np.uint8) + >>> field_names = ['x', 'y', 'z', 'red', 'green', 'blue', values'] + >>> write_ply('example3.ply', [points, colors, values], field_names) + + """ + + # Format list input to the right form + field_list = list(field_list) if (type(field_list) == list or type(field_list) == tuple) else list((field_list,)) + for i, field in enumerate(field_list): + if field.ndim < 2: + field_list[i] = field.reshape(-1, 1) + if field.ndim > 2: + print('fields have more than 2 dimensions') + return False + + # check all fields have the same number of data + n_points = [field.shape[0] for field in field_list] + if not np.all(np.equal(n_points, n_points[0])): + print('wrong field dimensions') + return False + + # Check if field_names and field_list have same nb of column + n_fields = np.sum([field.shape[1] for field in field_list]) + if (n_fields != len(field_names)): + print('wrong number of field names') + return False + + # Add extension if not there + if not filename.endswith('.ply'): + filename += '.ply' + + # open in text mode to write the header + with open(filename, 'w') as plyfile: + + # First magical word + header = ['ply'] + + # Encoding format + header.append('format binary_' + sys.byteorder + '_endian 1.0') + + # Points properties description + header.extend(header_properties(field_list, field_names)) + + # Add faces if needded + if triangular_faces is not None: + header.append('element face {:d}'.format(triangular_faces.shape[0])) + header.append('property list uchar int vertex_indices') + + # End of header + header.append('end_header') + + # Write all lines + for line in header: + plyfile.write("%s\n" % line) + + # open in binary/append to use tofile + with open(filename, 'ab') as plyfile: + + # Create a structured array + i = 0 + type_list = [] + for fields in field_list: + for field in fields.T: + type_list += [(field_names[i], field.dtype.str)] + i += 1 + data = np.empty(field_list[0].shape[0], dtype=type_list) + i = 0 + for fields in field_list: + for field in fields.T: + data[field_names[i]] = field + i += 1 + + data.tofile(plyfile) + + if triangular_faces is not None: + triangular_faces = triangular_faces.astype(np.int32) + type_list = [('k', 'uint8')] + [(str(ind), 'int32') for ind in range(3)] + data = np.empty(triangular_faces.shape[0], dtype=type_list) + data['k'] = np.full((triangular_faces.shape[0],), 3, dtype=np.uint8) + data['0'] = triangular_faces[:, 0] + data['1'] = triangular_faces[:, 1] + data['2'] = triangular_faces[:, 2] + data.tofile(plyfile) + + return True + + +def describe_element(name, df): + """ Takes the columns of the dataframe and builds a ply-like description + + Parameters + ---------- + name: str + df: pandas DataFrame + + Returns + ------- + element: list[str] + """ + property_formats = {'f': 'float', 'u': 'uchar', 'i': 'int'} + element = ['element ' + name + ' ' + str(len(df))] + + if name == 'face': + element.append("property list uchar int points_indices") + + else: + for i in range(len(df.columns)): + # get first letter of dtype to infer format + f = property_formats[str(df.dtypes[i])[0]] + element.append('property ' + f + ' ' + df.columns.values[i]) + + return element + diff --git a/research/cv/WS3/src/utils/logger.py b/research/cv/WS3/src/utils/logger.py new file mode 100644 index 000000000..74ee7621b --- /dev/null +++ b/research/cv/WS3/src/utils/logger.py @@ -0,0 +1,88 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# 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. +# ============================================================================ +"""Custom Logger.""" +import logging +import os +import sys +from datetime import datetime + + +class LOGGER(logging.Logger): + """ + Logger. + + Args: + logger_name: String. Logger name. + rank: Integer. Rank id. + """ + def __init__(self, logger_name, rank=0): + super(LOGGER, self).__init__(logger_name) + self.rank = rank + if rank % 8 == 0: + console = logging.StreamHandler(sys.stdout) + console.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s') + console.setFormatter(formatter) + self.addHandler(console) + + def setup_logging_file(self, log_dir, rank=0): + """Setup logging file.""" + self.rank = rank + if not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + log_name = datetime.now().strftime('%Y-%m-%d_time_%H_%M_%S') + '_rank_{}.log'.format(rank) + self.log_fn = os.path.join(log_dir, log_name) + fh = logging.FileHandler(self.log_fn) + fh.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s') + fh.setFormatter(formatter) + self.addHandler(fh) + + def info(self, msg, *args, **kwargs): + """info""" + if self.isEnabledFor(logging.INFO): + self._log(logging.INFO, msg, args, **kwargs) + + def save_args(self, args): + """save args""" + self.info('Args:') + args_dict = vars(args) + for key in args_dict.keys(): + self.info('--> %s: %s', key, args_dict[key]) + self.info('') + + def important_info(self, msg, *args, **kwargs): + """important info""" + if self.isEnabledFor(logging.INFO) and self.rank == 0: + line_width = 2 + important_msg = '\n' + important_msg += ('*'*70 + '\n')*line_width + important_msg += ('*'*line_width + '\n')*2 + important_msg += '*'*line_width + ' '*8 + msg + '\n' + important_msg += ('*'*line_width + '\n')*2 + important_msg += ('*'*70 + '\n')*line_width + self.info(important_msg, *args, **kwargs) + + +def get_logger(path, rank): + """Get Logger.""" + logger = LOGGER('RandLa-net', rank) + logger.setup_logging_file(path, rank) + return logger + + +if __name__ == '__main__': + logger = get_logger('../runs', 0) + logger.info('this is logger info') \ No newline at end of file diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py new file mode 100644 index 000000000..8a9ca6279 --- /dev/null +++ b/research/cv/WS3/src/utils/metrics.py @@ -0,0 +1,68 @@ +import mindspore.numpy as msnp +import mindspore as ms + +def accuracy(scores, labels): + r""" + Compute the per-class accuracies and the overall accuracy # TODO: complete doc + + Parameters + ---------- + scores: ms.Tensor, dtype = float32, shape (B?, C, N) + raw scores for each class + labels: ms.Tensor, dtype = int64, shape (B?, N) + ground truth labels + + Returns + ------- + list of floats of length num_classes+1 (last item is overall accuracy) + """ + num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions + + predictions = scores.argmax(axis=-2) + + accuracies = 0 + + accuracy_mask = predictions == labels + for label in range(num_classes): + label_mask = labels == label + per_class_accuracy = msnp.logical_and(accuracy_mask , label_mask).astype(ms.float32).sum() + per_class_accuracy /= label_mask.astype(ms.float32).sum() + if label==0: + accuracies = per_class_accuracy + else: + accuracies = msnp.append(accuracies, per_class_accuracy) + # overall accuracy + accuracies = msnp.append(accuracies, accuracy_mask.astype(ms.float32).mean()) + return accuracies + +def intersection_over_union(scores, labels): + r""" + Compute the per-class IoU and the mean IoU # TODO: complete doc + + Parameters + ---------- + scores: ms.Tensor, dtype = float32, shape (B?, C, N) + raw scores for each class + labels: ms.Tensor, dtype = int64, shape (B?, N) + ground truth labels + + Returns + ------- + list of floats of length num_classes+1 (last item is mIoU) + """ + num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions + + predictions = scores.argmax(axis=-2) + + ious = 0 + + for label in range(num_classes): + pred_mask = predictions == label + labels_mask = labels == label + iou = msnp.logical_and(pred_mask , labels_mask).astype(ms.float32).sum() / msnp.logical_or(pred_mask , labels_mask).astype(ms.float32).sum() + if label==0: + ious = iou + else: + ious = msnp.append(ious, iou) + ious = msnp.append(ious, msnp.nanmean(ious)) + return ious diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py new file mode 100644 index 000000000..0888a4c39 --- /dev/null +++ b/research/cv/WS3/src/utils/tools.py @@ -0,0 +1,183 @@ +import sys +import numpy as np +import os.path + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(BASE_DIR, 'utils')) + +import third_party.cpp_wrappers.cpp_subsampling.grid_subsampling as cpp_subsampling +import third_party.nearest_neighbors.lib.python.nearest_neighbors as nearest_neighbors + + + + +class ConfigPretrain: + k_n = 16 # KNN + num_layers = 5 # Number of layers + num_points = 40960 # Number of input points + num_classes = 6 # a, b, \mu_{a},\mu_{a}, \sigmoid_{a}, \sigmoid_{b} + sub_grid_size = 0.04 # preprocess_parameter + + batch_size = 6 # batch_size during training + train_steps = 1000 # Number of steps per epochs + + sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer + d_out = [16, 64, 128, 256, 512] # feature dimension + + noise_init = 3.5 # 2.0 noise initial parameter + max_epoch = 100 # maximum epoch during training + learning_rate = 1e-2 # initial learning rate + + train_sum_dir = 'train_log' + saving = True + saving_path = None + + lr_decays = 0.95 # decay rate of learning rate + loss_scale = 1.0 # loss scale + + +class ConfigS3DIS: + k_n = 16 # KNN + num_layers = 5 # Number of layers + num_points = 40960 # Number of input points + num_classes = 13 # Number of valid classes + sub_grid_size = 0.04 # preprocess_parameter + + batch_size = 6 # batch_size during training + val_batch_size = 16 # batch_size during validation and test + train_steps = 500 # Number of steps per epochs + val_steps = 100 # Number of validation steps per epoch + + sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer + d_out = [16, 64, 128, 256, 512] # feature dimension + + noise_init = 3.5 # noise initial parameter + max_epoch = 80 # maximum epoch during training + learning_rate = 1e-2 # initial learning rate + # lr_decays = {i: 0.95 for i in range(0, 500)} # decay rate of learning rate + lr_decays = 0.95 # decay rate of learning rate + loss_scale = 1.0 # loss scale + + training_ep0 = {i: 0 for i in range(0, 30)} # + training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + training_ep.update(training_ep0) + # training_ep = {i: 0 for i in range(max_epoch)} + # training_ep[2] = 1 + c_epoch = 0 + train_sum_dir = 'train_log' + saving = True + saving_path = None + # pretrain = False + # checkpoint = '../pretrain/snapshots/snap-11001' + + pretrain = True + checkpoint = './pretrain/snapshots/snap-11001' + topk = 500 + loss3_type = -1 + + +class DataProcessing: + @staticmethod + def knn_search(support_pts, query_pts, k): + """ + :param support_pts: points you have, B*N1*3 + :param query_pts: points you want to know the neighbour index, B*N2*3 + :param k: Number of neighbours in knn search + :return: neighbor_idx: neighboring points indexes, B*N2*k + """ + + neighbor_idx = nearest_neighbors.knn_batch(support_pts, query_pts, k, omp=True) + return neighbor_idx.astype(np.int32) + + @staticmethod + def data_aug(xyz, color, labels, idx, num_out): + num_in = len(xyz) + dup = np.random.choice(num_in, num_out - num_in) + xyz_dup = xyz[dup, ...] + xyz_aug = np.concatenate([xyz, xyz_dup], 0) + color_dup = color[dup, ...] + color_aug = np.concatenate([color, color_dup], 0) + idx_dup = list(range(num_in)) + list(dup) + idx_aug = idx[idx_dup] + label_aug = labels[idx_dup] + return xyz_aug, color_aug, idx_aug, label_aug + + @staticmethod + def shuffle_idx(x): + # random shuffle the index + idx = np.arange(len(x)) + np.random.shuffle(idx) + return x[idx] + + @staticmethod + def get_class_weights(dataset_name): + # pre-calculate the number of points in each category + num_per_class = [] + if dataset_name is 'S3DIS': + num_per_class = np.array([3370714, 2856755, 4919229, 318158, 375640, 478001, 974733, + 650464, 791496, 88727, 1284130, 229758, 2272837], dtype=np.int32) + elif dataset_name is 'Semantic3D': + num_per_class = np.array([5181602, 5012952, 6830086, 1311528, 10476365, 946982, 334860, 269353], + dtype=np.int32) + elif dataset_name is 'SemanticKITTI': + num_per_class = np.array([55437630, 320797, 541736, 2578735, 3274484, 552662, 184064, 78858, + 240942562, 17294618, 170599734, 6369672, 230413074, 101130274, 476491114, + 9833174, 129609852, 4506626, 1168181]) + elif dataset_name is 'ScanNet': + num_per_class = np.array( + [13327131, 11989728, 1909991, 1291399, 3122500, 1197818, 1818311, 1942293, 1332850, 1000934, + 227936, 244557, 892883, 800339, 246651, 125533, 134002, 110112, 162706, 1575880], dtype=np.int32) + weight = num_per_class / float(sum(num_per_class)) + ce_label_weight = 1 / (weight + 0.02) + return np.expand_dims(ce_label_weight, axis=0) + + @staticmethod + def IoU_from_confusions(confusions): + """ + Computes IoU from confusion matrices. + :param confusions: ([..., n_c, n_c] np.int32). Can be any dimension, the confusion matrices should be described by + the last axes. n_c = number of classes + :return: ([..., n_c] np.float32) IoU score + """ + + # Compute TP, FP, FN. This assume that the second to last axis counts the truths (like the first axis of a + # confusion matrix), and that the last axis counts the predictions (like the second axis of a confusion matrix) + TP = np.diagonal(confusions, axis1=-2, axis2=-1) + TP_plus_FN = np.sum(confusions, axis=-1) + TP_plus_FP = np.sum(confusions, axis=-2) + + # Compute IoU + IoU = TP / (TP_plus_FP + TP_plus_FN - TP + 1e-6) + + # Compute mIoU with only the actual classes + mask = TP_plus_FN < 1e-3 + counts = np.sum(1 - mask, axis=-1, keepdims=True) + mIoU = np.sum(IoU, axis=-1, keepdims=True) / (counts + 1e-6) + + # If class is absent, place mIoU in place of 0 IoU to get the actual mean later + IoU += mask * mIoU + return IoU + + @staticmethod + def grid_sub_sampling(points, features=None, labels=None, grid_size=0.1, verbose=0): + """ + CPP wrapper for a grid sub_sampling (method = barycenter for points and features + :param points: (N, 3) matrix of input points + :param features: optional (N, d) matrix of features (floating number) + :param labels: optional (N,) matrix of integer labels + :param grid_size: parameter defining the size of grid voxels + :param verbose: 1 to display + :return: sub_sampled points, with features and/or labels depending of the input + """ + + if (features is None) and (labels is None): + return cpp_subsampling.compute(points, sampleDl=grid_size, verbose=verbose) + elif labels is None: + return cpp_subsampling.compute(points, features=features, sampleDl=grid_size, verbose=verbose) + elif features is None: + return cpp_subsampling.compute(points, classes=labels, sampleDl=grid_size, verbose=verbose) + else: + return cpp_subsampling.compute(points, features=features, classes=labels, sampleDl=grid_size, + verbose=verbose) diff --git a/research/cv/WS3/third_party/cloud.cpp b/research/cv/WS3/third_party/cloud.cpp new file mode 100644 index 000000000..bdb65679f --- /dev/null +++ b/research/cv/WS3/third_party/cloud.cpp @@ -0,0 +1,67 @@ +// +// +// 0==========================0 +// | Local feature test | +// 0==========================0 +// +// version 1.0 : +// > +// +//--------------------------------------------------- +// +// Cloud source : +// Define usefull Functions/Methods +// +//---------------------------------------------------- +// +// Hugues THOMAS - 10/02/2017 +// + + +#include "cloud.h" + + +// Getters +// ******* + +PointXYZ max_point(std::vector points) +{ + // Initiate limits + PointXYZ maxP(points[0]); + + // Loop over all points + for (auto p : points) + { + if (p.x > maxP.x) + maxP.x = p.x; + + if (p.y > maxP.y) + maxP.y = p.y; + + if (p.z > maxP.z) + maxP.z = p.z; + } + + return maxP; +} + +PointXYZ min_point(std::vector points) +{ + // Initiate limits + PointXYZ minP(points[0]); + + // Loop over all points + for (auto p : points) + { + if (p.x < minP.x) + minP.x = p.x; + + if (p.y < minP.y) + minP.y = p.y; + + if (p.z < minP.z) + minP.z = p.z; + } + + return minP; +} \ No newline at end of file diff --git a/research/cv/WS3/third_party/cloud.h b/research/cv/WS3/third_party/cloud.h new file mode 100644 index 000000000..39ab05b68 --- /dev/null +++ b/research/cv/WS3/third_party/cloud.h @@ -0,0 +1,158 @@ +// +// +// 0==========================0 +// | Local feature test | +// 0==========================0 +// +// version 1.0 : +// > +// +//--------------------------------------------------- +// +// Cloud header +// +//---------------------------------------------------- +// +// Hugues THOMAS - 10/02/2017 +// + + +# pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + + +// Point class +// *********** + + +class PointXYZ +{ +public: + + // Elements + // ******** + + float x, y, z; + + + // Methods + // ******* + + // Constructor + PointXYZ() { x = 0; y = 0; z = 0; } + PointXYZ(float x0, float y0, float z0) { x = x0; y = y0; z = z0; } + + // array type accessor + float operator [] (int i) const + { + if (i == 0) return x; + else if (i == 1) return y; + else return z; + } + + // opperations + float dot(const PointXYZ P) const + { + return x * P.x + y * P.y + z * P.z; + } + + float sq_norm() + { + return x*x + y*y + z*z; + } + + PointXYZ cross(const PointXYZ P) const + { + return PointXYZ(y*P.z - z*P.y, z*P.x - x*P.z, x*P.y - y*P.x); + } + + PointXYZ& operator+=(const PointXYZ& P) + { + x += P.x; + y += P.y; + z += P.z; + return *this; + } + + PointXYZ& operator-=(const PointXYZ& P) + { + x -= P.x; + y -= P.y; + z -= P.z; + return *this; + } + + PointXYZ& operator*=(const float& a) + { + x *= a; + y *= a; + z *= a; + return *this; + } +}; + + +// Point Opperations +// ***************** + +inline PointXYZ operator + (const PointXYZ A, const PointXYZ B) +{ + return PointXYZ(A.x + B.x, A.y + B.y, A.z + B.z); +} + +inline PointXYZ operator - (const PointXYZ A, const PointXYZ B) +{ + return PointXYZ(A.x - B.x, A.y - B.y, A.z - B.z); +} + +inline PointXYZ operator * (const PointXYZ P, const float a) +{ + return PointXYZ(P.x * a, P.y * a, P.z * a); +} + +inline PointXYZ operator * (const float a, const PointXYZ P) +{ + return PointXYZ(P.x * a, P.y * a, P.z * a); +} + +inline std::ostream& operator << (std::ostream& os, const PointXYZ P) +{ + return os << "[" << P.x << ", " << P.y << ", " << P.z << "]"; +} + +inline bool operator == (const PointXYZ A, const PointXYZ B) +{ + return A.x == B.x && A.y == B.y && A.z == B.z; +} + +inline PointXYZ floor(const PointXYZ P) +{ + return PointXYZ(std::floor(P.x), std::floor(P.y), std::floor(P.z)); +} + + +PointXYZ max_point(std::vector points); +PointXYZ min_point(std::vector points); + + + + + + + + + + + diff --git a/research/cv/WS3/third_party/compile_op.sh b/research/cv/WS3/third_party/compile_op.sh new file mode 100644 index 000000000..2a7dda3b4 --- /dev/null +++ b/research/cv/WS3/third_party/compile_op.sh @@ -0,0 +1,7 @@ +cd nearest_neighbors +python setup.py install --home="." +cd ../ + +cd cpp_wrappers +sh compile_wrappers.sh +cd ../../ diff --git a/research/cv/WS3/third_party/grid_subsampling.cpp b/research/cv/WS3/third_party/grid_subsampling.cpp new file mode 100644 index 000000000..7c00396fe --- /dev/null +++ b/research/cv/WS3/third_party/grid_subsampling.cpp @@ -0,0 +1,106 @@ + +#include "grid_subsampling.h" + + +void grid_subsampling(vector& original_points, + vector& subsampled_points, + vector& original_features, + vector& subsampled_features, + vector& original_classes, + vector& subsampled_classes, + float sampleDl, + int verbose) { + + // Initiate variables + // ****************** + + // Number of points in the cloud + size_t N = original_points.size(); + + // Dimension of the features + size_t fdim = original_features.size() / N; + size_t ldim = original_classes.size() / N; + + // Limits of the cloud + PointXYZ minCorner = min_point(original_points); + PointXYZ maxCorner = max_point(original_points); + PointXYZ originCorner = floor(minCorner * (1/sampleDl)) * sampleDl; + + // Dimensions of the grid + size_t sampleNX = (size_t)floor((maxCorner.x - originCorner.x) / sampleDl) + 1; + size_t sampleNY = (size_t)floor((maxCorner.y - originCorner.y) / sampleDl) + 1; + //size_t sampleNZ = (size_t)floor((maxCorner.z - originCorner.z) / sampleDl) + 1; + + // Check if features and classes need to be processed + bool use_feature = original_features.size() > 0; + bool use_classes = original_classes.size() > 0; + + + // Create the sampled map + // ********************** + + // Verbose parameters + int i = 0; + int nDisp = N / 100; + + // Initiate variables + size_t iX, iY, iZ, mapIdx; + unordered_map data; + + for (auto& p : original_points) + { + // Position of point in sample map + iX = (size_t)floor((p.x - originCorner.x) / sampleDl); + iY = (size_t)floor((p.y - originCorner.y) / sampleDl); + iZ = (size_t)floor((p.z - originCorner.z) / sampleDl); + mapIdx = iX + sampleNX*iY + sampleNX*sampleNY*iZ; + + // If not already created, create key + if (data.count(mapIdx) < 1) + data.emplace(mapIdx, SampledData(fdim, ldim)); + + // Fill the sample map + if (use_feature && use_classes) + data[mapIdx].update_all(p, original_features.begin() + i * fdim, original_classes.begin() + i * ldim); + else if (use_feature) + data[mapIdx].update_features(p, original_features.begin() + i * fdim); + else if (use_classes) + data[mapIdx].update_classes(p, original_classes.begin() + i * ldim); + else + data[mapIdx].update_points(p); + + // Display + i++; + if (verbose > 1 && i%nDisp == 0) + std::cout << "\rSampled Map : " << std::setw(3) << i / nDisp << "%"; + + } + + // Divide for barycentre and transfer to a vector + subsampled_points.reserve(data.size()); + if (use_feature) + subsampled_features.reserve(data.size() * fdim); + if (use_classes) + subsampled_classes.reserve(data.size() * ldim); + for (auto& v : data) + { + subsampled_points.push_back(v.second.point * (1.0 / v.second.count)); + if (use_feature) + { + float count = (float)v.second.count; + transform(v.second.features.begin(), + v.second.features.end(), + v.second.features.begin(), + [count](float f) { return f / count;}); + subsampled_features.insert(subsampled_features.end(),v.second.features.begin(),v.second.features.end()); + } + if (use_classes) + { + for (int i = 0; i < ldim; i++) + subsampled_classes.push_back(max_element(v.second.labels[i].begin(), v.second.labels[i].end(), + [](const pair&a, const pair&b){return a.second < b.second;})->first); + } + } + + return; +} diff --git a/research/cv/WS3/third_party/grid_subsampling.h b/research/cv/WS3/third_party/grid_subsampling.h new file mode 100644 index 000000000..b1c84d1b3 --- /dev/null +++ b/research/cv/WS3/third_party/grid_subsampling.h @@ -0,0 +1,92 @@ + + +#include "../../cpp_utils/cloud/cloud.h" + +#include +#include + +using namespace std; + +class SampledData +{ +public: + + // Elements + // ******** + + int count; + PointXYZ point; + vector features; + vector> labels; + + + // Methods + // ******* + + // Constructor + SampledData() + { + count = 0; + point = PointXYZ(); + } + + SampledData(const size_t fdim, const size_t ldim) + { + count = 0; + point = PointXYZ(); + features = vector(fdim); + labels = vector>(ldim); + } + + // Method Update + void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_features(const PointXYZ p, vector::iterator f_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + return; + } + void update_classes(const PointXYZ p, vector::iterator l_begin) + { + count += 1; + point += p; + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_points(const PointXYZ p) + { + count += 1; + point += p; + return; + } +}; + + + +void grid_subsampling(vector& original_points, + vector& subsampled_points, + vector& original_features, + vector& subsampled_features, + vector& original_classes, + vector& subsampled_classes, + float sampleDl, + int verbose); + diff --git a/research/cv/WS3/third_party/meta/anno_paths.txt b/research/cv/WS3/third_party/meta/anno_paths.txt new file mode 100644 index 000000000..0ad2f2599 --- /dev/null +++ b/research/cv/WS3/third_party/meta/anno_paths.txt @@ -0,0 +1,272 @@ +Area_1/conferenceRoom_1/Annotations +Area_1/conferenceRoom_2/Annotations +Area_1/copyRoom_1/Annotations +Area_1/hallway_1/Annotations +Area_1/hallway_2/Annotations +Area_1/hallway_3/Annotations +Area_1/hallway_4/Annotations +Area_1/hallway_5/Annotations +Area_1/hallway_6/Annotations +Area_1/hallway_7/Annotations +Area_1/hallway_8/Annotations +Area_1/office_10/Annotations +Area_1/office_11/Annotations +Area_1/office_12/Annotations +Area_1/office_13/Annotations +Area_1/office_14/Annotations +Area_1/office_15/Annotations +Area_1/office_16/Annotations +Area_1/office_17/Annotations +Area_1/office_18/Annotations +Area_1/office_19/Annotations +Area_1/office_1/Annotations +Area_1/office_20/Annotations +Area_1/office_21/Annotations +Area_1/office_22/Annotations +Area_1/office_23/Annotations +Area_1/office_24/Annotations +Area_1/office_25/Annotations +Area_1/office_26/Annotations +Area_1/office_27/Annotations +Area_1/office_28/Annotations +Area_1/office_29/Annotations +Area_1/office_2/Annotations +Area_1/office_30/Annotations +Area_1/office_31/Annotations +Area_1/office_3/Annotations +Area_1/office_4/Annotations +Area_1/office_5/Annotations +Area_1/office_6/Annotations +Area_1/office_7/Annotations +Area_1/office_8/Annotations +Area_1/office_9/Annotations +Area_1/pantry_1/Annotations +Area_1/WC_1/Annotations +Area_2/auditorium_1/Annotations +Area_2/auditorium_2/Annotations +Area_2/conferenceRoom_1/Annotations +Area_2/hallway_10/Annotations +Area_2/hallway_11/Annotations +Area_2/hallway_12/Annotations +Area_2/hallway_1/Annotations +Area_2/hallway_2/Annotations +Area_2/hallway_3/Annotations +Area_2/hallway_4/Annotations +Area_2/hallway_5/Annotations +Area_2/hallway_6/Annotations +Area_2/hallway_7/Annotations +Area_2/hallway_8/Annotations +Area_2/hallway_9/Annotations +Area_2/office_10/Annotations +Area_2/office_11/Annotations +Area_2/office_12/Annotations +Area_2/office_13/Annotations +Area_2/office_14/Annotations +Area_2/office_1/Annotations +Area_2/office_2/Annotations +Area_2/office_3/Annotations +Area_2/office_4/Annotations +Area_2/office_5/Annotations +Area_2/office_6/Annotations +Area_2/office_7/Annotations +Area_2/office_8/Annotations +Area_2/office_9/Annotations +Area_2/storage_1/Annotations +Area_2/storage_2/Annotations +Area_2/storage_3/Annotations +Area_2/storage_4/Annotations +Area_2/storage_5/Annotations +Area_2/storage_6/Annotations +Area_2/storage_7/Annotations +Area_2/storage_8/Annotations +Area_2/storage_9/Annotations +Area_2/WC_1/Annotations +Area_2/WC_2/Annotations +Area_3/conferenceRoom_1/Annotations +Area_3/hallway_1/Annotations +Area_3/hallway_2/Annotations +Area_3/hallway_3/Annotations +Area_3/hallway_4/Annotations +Area_3/hallway_5/Annotations +Area_3/hallway_6/Annotations +Area_3/lounge_1/Annotations +Area_3/lounge_2/Annotations +Area_3/office_10/Annotations +Area_3/office_1/Annotations +Area_3/office_2/Annotations +Area_3/office_3/Annotations +Area_3/office_4/Annotations +Area_3/office_5/Annotations +Area_3/office_6/Annotations +Area_3/office_7/Annotations +Area_3/office_8/Annotations +Area_3/office_9/Annotations +Area_3/storage_1/Annotations +Area_3/storage_2/Annotations +Area_3/WC_1/Annotations +Area_3/WC_2/Annotations +Area_4/conferenceRoom_1/Annotations +Area_4/conferenceRoom_2/Annotations +Area_4/conferenceRoom_3/Annotations +Area_4/hallway_10/Annotations +Area_4/hallway_11/Annotations +Area_4/hallway_12/Annotations +Area_4/hallway_13/Annotations +Area_4/hallway_14/Annotations +Area_4/hallway_1/Annotations +Area_4/hallway_2/Annotations +Area_4/hallway_3/Annotations +Area_4/hallway_4/Annotations +Area_4/hallway_5/Annotations +Area_4/hallway_6/Annotations +Area_4/hallway_7/Annotations +Area_4/hallway_8/Annotations +Area_4/hallway_9/Annotations +Area_4/lobby_1/Annotations +Area_4/lobby_2/Annotations +Area_4/office_10/Annotations +Area_4/office_11/Annotations +Area_4/office_12/Annotations +Area_4/office_13/Annotations +Area_4/office_14/Annotations +Area_4/office_15/Annotations +Area_4/office_16/Annotations +Area_4/office_17/Annotations +Area_4/office_18/Annotations +Area_4/office_19/Annotations +Area_4/office_1/Annotations +Area_4/office_20/Annotations +Area_4/office_21/Annotations +Area_4/office_22/Annotations +Area_4/office_2/Annotations +Area_4/office_3/Annotations +Area_4/office_4/Annotations +Area_4/office_5/Annotations +Area_4/office_6/Annotations +Area_4/office_7/Annotations +Area_4/office_8/Annotations +Area_4/office_9/Annotations +Area_4/storage_1/Annotations +Area_4/storage_2/Annotations +Area_4/storage_3/Annotations +Area_4/storage_4/Annotations +Area_4/WC_1/Annotations +Area_4/WC_2/Annotations +Area_4/WC_3/Annotations +Area_4/WC_4/Annotations +Area_5/conferenceRoom_1/Annotations +Area_5/conferenceRoom_2/Annotations +Area_5/conferenceRoom_3/Annotations +Area_5/hallway_10/Annotations +Area_5/hallway_11/Annotations +Area_5/hallway_12/Annotations +Area_5/hallway_13/Annotations +Area_5/hallway_14/Annotations +Area_5/hallway_15/Annotations +Area_5/hallway_1/Annotations +Area_5/hallway_2/Annotations +Area_5/hallway_3/Annotations +Area_5/hallway_4/Annotations +Area_5/hallway_5/Annotations +Area_5/hallway_6/Annotations +Area_5/hallway_7/Annotations +Area_5/hallway_8/Annotations +Area_5/hallway_9/Annotations +Area_5/lobby_1/Annotations +Area_5/office_10/Annotations +Area_5/office_11/Annotations +Area_5/office_12/Annotations +Area_5/office_13/Annotations +Area_5/office_14/Annotations +Area_5/office_15/Annotations +Area_5/office_16/Annotations +Area_5/office_17/Annotations +Area_5/office_18/Annotations +Area_5/office_19/Annotations +Area_5/office_1/Annotations +Area_5/office_20/Annotations +Area_5/office_21/Annotations +Area_5/office_22/Annotations +Area_5/office_23/Annotations +Area_5/office_24/Annotations +Area_5/office_25/Annotations +Area_5/office_26/Annotations +Area_5/office_27/Annotations +Area_5/office_28/Annotations +Area_5/office_29/Annotations +Area_5/office_2/Annotations +Area_5/office_30/Annotations +Area_5/office_31/Annotations +Area_5/office_32/Annotations +Area_5/office_33/Annotations +Area_5/office_34/Annotations +Area_5/office_35/Annotations +Area_5/office_36/Annotations +Area_5/office_37/Annotations +Area_5/office_38/Annotations +Area_5/office_39/Annotations +Area_5/office_3/Annotations +Area_5/office_40/Annotations +Area_5/office_41/Annotations +Area_5/office_42/Annotations +Area_5/office_4/Annotations +Area_5/office_5/Annotations +Area_5/office_6/Annotations +Area_5/office_7/Annotations +Area_5/office_8/Annotations +Area_5/office_9/Annotations +Area_5/pantry_1/Annotations +Area_5/storage_1/Annotations +Area_5/storage_2/Annotations +Area_5/storage_3/Annotations +Area_5/storage_4/Annotations +Area_5/WC_1/Annotations +Area_5/WC_2/Annotations +Area_6/conferenceRoom_1/Annotations +Area_6/copyRoom_1/Annotations +Area_6/hallway_1/Annotations +Area_6/hallway_2/Annotations +Area_6/hallway_3/Annotations +Area_6/hallway_4/Annotations +Area_6/hallway_5/Annotations +Area_6/hallway_6/Annotations +Area_6/lounge_1/Annotations +Area_6/office_10/Annotations +Area_6/office_11/Annotations +Area_6/office_12/Annotations +Area_6/office_13/Annotations +Area_6/office_14/Annotations +Area_6/office_15/Annotations +Area_6/office_16/Annotations +Area_6/office_17/Annotations +Area_6/office_18/Annotations +Area_6/office_19/Annotations +Area_6/office_1/Annotations +Area_6/office_20/Annotations +Area_6/office_21/Annotations +Area_6/office_22/Annotations +Area_6/office_23/Annotations +Area_6/office_24/Annotations +Area_6/office_25/Annotations +Area_6/office_26/Annotations +Area_6/office_27/Annotations +Area_6/office_28/Annotations +Area_6/office_29/Annotations +Area_6/office_2/Annotations +Area_6/office_30/Annotations +Area_6/office_31/Annotations +Area_6/office_32/Annotations +Area_6/office_33/Annotations +Area_6/office_34/Annotations +Area_6/office_35/Annotations +Area_6/office_36/Annotations +Area_6/office_37/Annotations +Area_6/office_3/Annotations +Area_6/office_4/Annotations +Area_6/office_5/Annotations +Area_6/office_6/Annotations +Area_6/office_7/Annotations +Area_6/office_8/Annotations +Area_6/office_9/Annotations +Area_6/openspace_1/Annotations +Area_6/pantry_1/Annotations diff --git a/research/cv/WS3/third_party/meta/class_names.txt b/research/cv/WS3/third_party/meta/class_names.txt new file mode 100644 index 000000000..ca1d17882 --- /dev/null +++ b/research/cv/WS3/third_party/meta/class_names.txt @@ -0,0 +1,13 @@ +ceiling +floor +wall +beam +column +window +door +table +chair +sofa +bookcase +board +clutter diff --git a/research/cv/WS3/third_party/nanoflann.hpp b/research/cv/WS3/third_party/nanoflann.hpp new file mode 100644 index 000000000..8d2ab6cc6 --- /dev/null +++ b/research/cv/WS3/third_party/nanoflann.hpp @@ -0,0 +1,2043 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved. + * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved. + * Copyright 2011-2016 Jose Luis Blanco (joseluisblancoc@gmail.com). + * All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +/** \mainpage nanoflann C++ API documentation + * nanoflann is a C++ header-only library for building KD-Trees, mostly + * optimized for 2D or 3D point clouds. + * + * nanoflann does not require compiling or installing, just an + * #include in your code. + * + * See: + * - C++ API organized by modules + * - Online README + * - Doxygen + * documentation + */ + +#ifndef NANOFLANN_HPP_ +#define NANOFLANN_HPP_ + +#include +#include +#include +#include // for abs() +#include // for fwrite() +#include // for abs() +#include +#include // std::reference_wrapper +#include +#include + +/** Library version: 0xMmP (M=Major,m=minor,P=patch) */ +#define NANOFLANN_VERSION 0x130 + +// Avoid conflicting declaration of min/max macros in windows headers +#if !defined(NOMINMAX) && \ + (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64)) +#define NOMINMAX +#ifdef max +#undef max +#undef min +#endif +#endif + +namespace nanoflann { +/** @addtogroup nanoflann_grp nanoflann C++ library for ANN + * @{ */ + +/** the PI constant (required to avoid MSVC missing symbols) */ +template T pi_const() { + return static_cast(3.14159265358979323846); +} + +/** + * Traits if object is resizable and assignable (typically has a resize | assign + * method) + */ +template struct has_resize : std::false_type {}; + +template +struct has_resize().resize(1), 0)> + : std::true_type {}; + +template struct has_assign : std::false_type {}; + +template +struct has_assign().assign(1, 0), 0)> + : std::true_type {}; + +/** + * Free function to resize a resizable object + */ +template +inline typename std::enable_if::value, void>::type +resize(Container &c, const size_t nElements) { + c.resize(nElements); +} + +/** + * Free function that has no effects on non resizable containers (e.g. + * std::array) It raises an exception if the expected size does not match + */ +template +inline typename std::enable_if::value, void>::type +resize(Container &c, const size_t nElements) { + if (nElements != c.size()) + throw std::logic_error("Try to change the size of a std::array."); +} + +/** + * Free function to assign to a container + */ +template +inline typename std::enable_if::value, void>::type +assign(Container &c, const size_t nElements, const T &value) { + c.assign(nElements, value); +} + +/** + * Free function to assign to a std::array + */ +template +inline typename std::enable_if::value, void>::type +assign(Container &c, const size_t nElements, const T &value) { + for (size_t i = 0; i < nElements; i++) + c[i] = value; +} + +/** @addtogroup result_sets_grp Result set classes + * @{ */ +template +class KNNResultSet { +public: + typedef _DistanceType DistanceType; + typedef _IndexType IndexType; + typedef _CountType CountType; + +private: + IndexType *indices; + DistanceType *dists; + CountType capacity; + CountType count; + +public: + inline KNNResultSet(CountType capacity_) + : indices(0), dists(0), capacity(capacity_), count(0) {} + + inline void init(IndexType *indices_, DistanceType *dists_) { + indices = indices_; + dists = dists_; + count = 0; + if (capacity) + dists[capacity - 1] = (std::numeric_limits::max)(); + } + + inline CountType size() const { return count; } + + inline bool full() const { return count == capacity; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + inline bool addPoint(DistanceType dist, IndexType index) { + CountType i; + for (i = count; i > 0; --i) { +#ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same + // distance, the one with the lowest-index will be + // returned first. + if ((dists[i - 1] > dist) || + ((dist == dists[i - 1]) && (indices[i - 1] > index))) { +#else + if (dists[i - 1] > dist) { +#endif + if (i < capacity) { + dists[i] = dists[i - 1]; + indices[i] = indices[i - 1]; + } + } else + break; + } + if (i < capacity) { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) + count++; + + // tell caller that the search shall continue + return true; + } + + inline DistanceType worstDist() const { return dists[capacity - 1]; } +}; + +/** operator "<" for std::sort() */ +struct IndexDist_Sorter { + /** PairType will be typically: std::pair */ + template + inline bool operator()(const PairType &p1, const PairType &p2) const { + return p1.second < p2.second; + } +}; + +/** + * A result-set class used when performing a radius based search. + */ +template +class RadiusResultSet { +public: + typedef _DistanceType DistanceType; + typedef _IndexType IndexType; + +public: + const DistanceType radius; + + std::vector> &m_indices_dists; + + inline RadiusResultSet( + DistanceType radius_, + std::vector> &indices_dists) + : radius(radius_), m_indices_dists(indices_dists) { + init(); + } + + inline void init() { clear(); } + inline void clear() { m_indices_dists.clear(); } + + inline size_t size() const { return m_indices_dists.size(); } + + inline bool full() const { return true; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + inline bool addPoint(DistanceType dist, IndexType index) { + if (dist < radius) + m_indices_dists.push_back(std::make_pair(index, dist)); + return true; + } + + inline DistanceType worstDist() const { return radius; } + + /** + * Find the worst result (furtherest neighbor) without copying or sorting + * Pre-conditions: size() > 0 + */ + std::pair worst_item() const { + if (m_indices_dists.empty()) + throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on " + "an empty list of results."); + typedef + typename std::vector>::const_iterator + DistIt; + DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(), + IndexDist_Sorter()); + return *it; + } +}; + +/** @} */ + +/** @addtogroup loadsave_grp Load/save auxiliary functions + * @{ */ +template +void save_value(FILE *stream, const T &value, size_t count = 1) { + fwrite(&value, sizeof(value), count, stream); +} + +template +void save_value(FILE *stream, const std::vector &value) { + size_t size = value.size(); + fwrite(&size, sizeof(size_t), 1, stream); + fwrite(&value[0], sizeof(T), size, stream); +} + +template +void load_value(FILE *stream, T &value, size_t count = 1) { + size_t read_cnt = fread(&value, sizeof(value), count, stream); + if (read_cnt != count) { + throw std::runtime_error("Cannot read from file"); + } +} + +template void load_value(FILE *stream, std::vector &value) { + size_t size; + size_t read_cnt = fread(&size, sizeof(size_t), 1, stream); + if (read_cnt != 1) { + throw std::runtime_error("Cannot read from file"); + } + value.resize(size); + read_cnt = fread(&value[0], sizeof(T), size, stream); + if (read_cnt != size) { + throw std::runtime_error("Cannot read from file"); + } +} +/** @} */ + +/** @addtogroup metric_grp Metric (distance) classes + * @{ */ + +struct Metric {}; + +/** Manhattan distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L1 \tparam T Type of the elements (e.g. double, float, + * uint8_t) \tparam _DistanceType Type of distance variables (must be signed) + * (e.g. float, double, int64_t) + */ +template +struct L1_Adaptor { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size, + DistanceType worst_dist = -1) const { + DistanceType result = DistanceType(); + const T *last = a + size; + const T *lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = + std::abs(a[0] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff1 = + std::abs(a[1] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff2 = + std::abs(a[2] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff3 = + std::abs(a[3] - data_source.kdtree_get_pt(b_idx, d++)); + result += diff0 + diff1 + diff2 + diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + result += std::abs(*a++ - data_source.kdtree_get_pt(b_idx, d++)); + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const { + return std::abs(a - b); + } +}; + +/** Squared Euclidean distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L2 \tparam T Type of the elements (e.g. double, float, + * uint8_t) \tparam _DistanceType Type of distance variables (must be signed) + * (e.g. float, double, int64_t) + */ +template +struct L2_Adaptor { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size, + DistanceType worst_dist = -1) const { + DistanceType result = DistanceType(); + const T *last = a + size; + const T *lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx, d++); + result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++); + result += diff0 * diff0; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const { + return (a - b) * (a - b); + } +}; + +/** Squared Euclidean (L2) distance functor (suitable for low-dimensionality + * datasets, like 2D or 3D point clouds) Corresponding distance traits: + * nanoflann::metric_L2_Simple \tparam T Type of the elements (e.g. double, + * float, uint8_t) \tparam _DistanceType Type of distance variables (must be + * signed) (e.g. float, double, int64_t) + */ +template +struct L2_Simple_Adaptor { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Simple_Adaptor(const DataSource &_data_source) + : data_source(_data_source) {} + + inline DistanceType evalMetric(const T *a, const size_t b_idx, + size_t size) const { + DistanceType result = DistanceType(); + for (size_t i = 0; i < size; ++i) { + const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); + result += diff * diff; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const { + return (a - b) * (a - b); + } +}; + +/** SO2 distance functor + * Corresponding distance traits: nanoflann::metric_SO2 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) orientation is constrained to be in [-pi, pi] + */ +template +struct SO2_Adaptor { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {} + + inline DistanceType evalMetric(const T *a, const size_t b_idx, + size_t size) const { + return accum_dist(a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1), + size - 1); + } + + /** Note: this assumes that input angles are already in the range [-pi,pi] */ + template + inline DistanceType accum_dist(const U a, const V b, const size_t) const { + DistanceType result = DistanceType(), PI = pi_const(); + result = b - a; + if (result > PI) + result -= 2 * PI; + else if (result < -PI) + result += 2 * PI; + return result; + } +}; + +/** SO3 distance functor (Uses L2_Simple) + * Corresponding distance traits: nanoflann::metric_SO3 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) + */ +template +struct SO3_Adaptor { + typedef T ElementType; + typedef _DistanceType DistanceType; + + L2_Simple_Adaptor distance_L2_Simple; + + SO3_Adaptor(const DataSource &_data_source) + : distance_L2_Simple(_data_source) {} + + inline DistanceType evalMetric(const T *a, const size_t b_idx, + size_t size) const { + return distance_L2_Simple.evalMetric(a, b_idx, size); + } + + template + inline DistanceType accum_dist(const U a, const V b, const size_t idx) const { + return distance_L2_Simple.accum_dist(a, b, idx); + } +}; + +/** Metaprogramming helper traits class for the L1 (Manhattan) metric */ +struct metric_L1 : public Metric { + template struct traits { + typedef L1_Adaptor distance_t; + }; +}; +/** Metaprogramming helper traits class for the L2 (Euclidean) metric */ +struct metric_L2 : public Metric { + template struct traits { + typedef L2_Adaptor distance_t; + }; +}; +/** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */ +struct metric_L2_Simple : public Metric { + template struct traits { + typedef L2_Simple_Adaptor distance_t; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO2 : public Metric { + template struct traits { + typedef SO2_Adaptor distance_t; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO3 : public Metric { + template struct traits { + typedef SO3_Adaptor distance_t; + }; +}; + +/** @} */ + +/** @addtogroup param_grp Parameter structs + * @{ */ + +/** Parameters (see README.md) */ +struct KDTreeSingleIndexAdaptorParams { + KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10) + : leaf_max_size(_leaf_max_size) {} + + size_t leaf_max_size; +}; + +/** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ +struct SearchParams { + /** Note: The first argument (checks_IGNORED_) is ignored, but kept for + * compatibility with the FLANN interface */ + SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true) + : checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {} + + int checks; //!< Ignored parameter (Kept for compatibility with the FLANN + //!< interface). + float eps; //!< search for eps-approximate neighbours (default: 0) + bool sorted; //!< only for radius search, require neighbours sorted by + //!< distance (default: true) +}; +/** @} */ + +/** @addtogroup memalloc_grp Memory allocation + * @{ */ + +/** + * Allocates (using C's malloc) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ +template inline T *allocate(size_t count = 1) { + T *mem = static_cast(::malloc(sizeof(T) * count)); + return mem; +} + +/** + * Pooled storage allocator + * + * The following routines allow for the efficient allocation of storage in + * small chunks from a specified pool. Rather than allowing each structure + * to be freed individually, an entire pool of storage is freed at once. + * This method has two advantages over just using malloc() and free(). First, + * it is far more efficient for allocating small objects, as there is + * no overhead for remembering all the information needed to free each + * object or consolidating fragmented memory. Second, the decision about + * how long to keep an object is made at the time of allocation, and there + * is no need to track down all the objects to free them. + * + */ + +const size_t WORDSIZE = 16; +const size_t BLOCKSIZE = 8192; + +class PooledAllocator { + /* We maintain memory alignment to word boundaries by requiring that all + allocations be in multiples of the machine wordsize. */ + /* Size of machine word in bytes. Must be power of 2. */ + /* Minimum number of bytes requested at a time from the system. Must be + * multiple of WORDSIZE. */ + + size_t remaining; /* Number of bytes left in current block of storage. */ + void *base; /* Pointer to base of current block of storage. */ + void *loc; /* Current location in block to next allocate memory. */ + + void internal_init() { + remaining = 0; + base = NULL; + usedMemory = 0; + wastedMemory = 0; + } + +public: + size_t usedMemory; + size_t wastedMemory; + + /** + Default constructor. Initializes a new pool. + */ + PooledAllocator() { internal_init(); } + + /** + * Destructor. Frees all the memory allocated in this pool. + */ + ~PooledAllocator() { free_all(); } + + /** Frees all allocated memory chunks */ + void free_all() { + while (base != NULL) { + void *prev = + *(static_cast(base)); /* Get pointer to prev block. */ + ::free(base); + base = prev; + } + internal_init(); + } + + /** + * Returns a pointer to a piece of new memory of the given size in bytes + * allocated from the pool. + */ + void *malloc(const size_t req_size) { + /* Round size up to a multiple of wordsize. The following expression + only works for WORDSIZE that is a power of 2, by masking last bits of + incremented size to zero. + */ + const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); + + /* Check whether a new block must be allocated. Note that the first word + of a block is reserved for a pointer to the previous block. + */ + if (size > remaining) { + + wastedMemory += remaining; + + /* Allocate new storage. */ + const size_t blocksize = + (size + sizeof(void *) + (WORDSIZE - 1) > BLOCKSIZE) + ? size + sizeof(void *) + (WORDSIZE - 1) + : BLOCKSIZE; + + // use the standard C malloc to allocate memory + void *m = ::malloc(blocksize); + if (!m) { + fprintf(stderr, "Failed to allocate memory.\n"); + return NULL; + } + + /* Fill first word of new block with pointer to previous block. */ + static_cast(m)[0] = base; + base = m; + + size_t shift = 0; + // int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) & + // (WORDSIZE-1))) & (WORDSIZE-1); + + remaining = blocksize - sizeof(void *) - shift; + loc = (static_cast(m) + sizeof(void *) + shift); + } + void *rloc = loc; + loc = static_cast(loc) + size; + remaining -= size; + + usedMemory += size; + + return rloc; + } + + /** + * Allocates (using this pool) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template T *allocate(const size_t count = 1) { + T *mem = static_cast(this->malloc(sizeof(T) * count)); + return mem; + } +}; +/** @} */ + +/** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff + * @{ */ + +/** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors + * when DIM=-1. Fixed size version for a generic DIM: + */ +template struct array_or_vector_selector { + typedef std::array container_t; +}; +/** Dynamic size version */ +template struct array_or_vector_selector<-1, T> { + typedef std::vector container_t; +}; + +/** @} */ + +/** kd-tree base-class + * + * Contains the member functions common to the classes KDTreeSingleIndexAdaptor + * and KDTreeSingleIndexDynamicAdaptor_. + * + * \tparam Derived The name of the class which inherits this class. + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use, these are all classes derived + * from nanoflann::Metric \tparam DIM Dimensionality of data points (e.g. 3 for + * 3D points) \tparam IndexType Will be typically size_t or int + */ + +template +class KDTreeBaseClass { + +public: + /** Frees the previously-built index. Automatically called within + * buildIndex(). */ + void freeIndex(Derived &obj) { + obj.pool.free_all(); + obj.root_node = NULL; + obj.m_size_at_index_build = 0; + } + + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + + /*--------------------- Internal Data Structures --------------------------*/ + struct Node { + /** Union used because a node can be either a LEAF node or a non-leaf node, + * so both data fields are never used simultaneously */ + union { + struct leaf { + IndexType left, right; //!< Indices of points in leaf node + } lr; + struct nonleaf { + int divfeat; //!< Dimension used for subdivision. + DistanceType divlow, divhigh; //!< The values used for subdivision. + } sub; + } node_type; + Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node) + }; + + typedef Node *NodePtr; + + struct Interval { + ElementType low, high; + }; + + /** + * Array of indices to vectors in the dataset. + */ + std::vector vind; + + NodePtr root_node; + + size_t m_leaf_max_size; + + size_t m_size; //!< Number of current points in the dataset + size_t m_size_at_index_build; //!< Number of points in the dataset when the + //!< index was built + int dim; //!< Dimensionality of each data point + + /** Define "BoundingBox" as a fixed-size or variable-size container depending + * on "DIM" */ + typedef + typename array_or_vector_selector::container_t BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + typedef typename array_or_vector_selector::container_t + distance_vector_t; + + /** The KD-tree used to find neighbours */ + + BoundingBox root_bbox; + + /** + * Pooled memory allocator. + * + * Using a pooled memory allocator is more efficient + * than allocating memory directly when there is a large + * number small of memory allocations. + */ + PooledAllocator pool; + + /** Returns number of points in dataset */ + size_t size(const Derived &obj) const { return obj.m_size; } + + /** Returns the length of each point in the dataset */ + size_t veclen(const Derived &obj) { + return static_cast(DIM > 0 ? DIM : obj.dim); + } + + /// Helper accessor to the dataset points: + inline ElementType dataset_get(const Derived &obj, size_t idx, + int component) const { + return obj.dataset.kdtree_get_pt(idx, component); + } + + /** + * Computes the inde memory usage + * Returns: memory used by the index + */ + size_t usedMemory(Derived &obj) { + return obj.pool.usedMemory + obj.pool.wastedMemory + + obj.dataset.kdtree_get_point_count() * + sizeof(IndexType); // pool memory and vind array memory + } + + void computeMinMax(const Derived &obj, IndexType *ind, IndexType count, + int element, ElementType &min_elem, + ElementType &max_elem) { + min_elem = dataset_get(obj, ind[0], element); + max_elem = dataset_get(obj, ind[0], element); + for (IndexType i = 1; i < count; ++i) { + ElementType val = dataset_get(obj, ind[i], element); + if (val < min_elem) + min_elem = val; + if (val > max_elem) + max_elem = val; + } + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] + * to vind[last]. The routine is called recursively on each sublist. + * + * @param left index of the first vector + * @param right index of the last vector + */ + NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right, + BoundingBox &bbox) { + NodePtr node = obj.pool.template allocate(); // allocate memory + + /* If too few exemplars remain, then make this a leaf node. */ + if ((right - left) <= static_cast(obj.m_leaf_max_size)) { + node->child1 = node->child2 = NULL; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = dataset_get(obj, obj.vind[left], i); + bbox[i].high = dataset_get(obj, obj.vind[left], i); + } + for (IndexType k = left + 1; k < right; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + if (bbox[i].low > dataset_get(obj, obj.vind[k], i)) + bbox[i].low = dataset_get(obj, obj.vind[k], i); + if (bbox[i].high < dataset_get(obj, obj.vind[k], i)) + bbox[i].high = dataset_get(obj, obj.vind[k], i); + } + } + } else { + IndexType idx; + int cutfeat; + DistanceType cutval; + middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval, + bbox); + + node->node_type.sub.divfeat = cutfeat; + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = divideTree(obj, left, left + idx, left_bbox); + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + node->child2 = divideTree(obj, left + idx, right, right_bbox); + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + void middleSplit_(Derived &obj, IndexType *ind, IndexType count, + IndexType &index, int &cutfeat, DistanceType &cutval, + const BoundingBox &bbox) { + const DistanceType EPS = static_cast(0.00001); + ElementType max_span = bbox[0].high - bbox[0].low; + for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high - bbox[i].low; + if (span > max_span) { + max_span = span; + } + } + ElementType max_spread = -1; + cutfeat = 0; + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high - bbox[i].low; + if (span > (1 - EPS) * max_span) { + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, i, min_elem, max_elem); + ElementType spread = max_elem - min_elem; + ; + if (spread > max_spread) { + cutfeat = i; + max_spread = spread; + } + } + } + // split in the middle + DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem); + + if (split_val < min_elem) + cutval = min_elem; + else if (split_val > max_elem) + cutval = max_elem; + else + cutval = split_val; + + IndexType lim1, lim2; + planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); + + if (lim1 > count / 2) + index = lim1; + else if (lim2 < count / 2) + index = lim2; + else + index = count / 2; + } + + /** + * Subdivide the list of points by a plane perpendicular on axe corresponding + * to the 'cutfeat' dimension at 'cutval' position. + * + * On return: + * dataset[ind[0..lim1-1]][cutfeat]cutval + */ + void planeSplit(Derived &obj, IndexType *ind, const IndexType count, + int cutfeat, DistanceType &cutval, IndexType &lim1, + IndexType &lim2) { + /* Move vector indices for left subtree to front of list. */ + IndexType left = 0; + IndexType right = count - 1; + for (;;) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval) + ++left; + while (right && left <= right && + dataset_get(obj, ind[right], cutfeat) >= cutval) + --right; + if (left > right || !right) + break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + /* If either list is empty, it means that all remaining features + * are identical. Split in the middle to maintain a balanced tree. + */ + lim1 = left; + right = count - 1; + for (;;) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval) + ++left; + while (right && left <= right && + dataset_get(obj, ind[right], cutfeat) > cutval) + --right; + if (left > right || !right) + break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + lim2 = left; + } + + DistanceType computeInitialDistances(const Derived &obj, + const ElementType *vec, + distance_vector_t &dists) const { + assert(vec); + DistanceType distsq = DistanceType(); + + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + if (vec[i] < obj.root_bbox[i].low) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i); + distsq += dists[i]; + } + if (vec[i] > obj.root_bbox[i].high) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i); + distsq += dists[i]; + } + } + return distsq; + } + + void save_tree(Derived &obj, FILE *stream, NodePtr tree) { + save_value(stream, *tree); + if (tree->child1 != NULL) { + save_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + save_tree(obj, stream, tree->child2); + } + } + + void load_tree(Derived &obj, FILE *stream, NodePtr &tree) { + tree = obj.pool.template allocate(); + load_value(stream, *tree); + if (tree->child1 != NULL) { + load_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + load_tree(obj, stream, tree->child2); + } + } + + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when + * loading the index object it must be constructed associated to the same + * source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex_(Derived &obj, FILE *stream) { + save_value(stream, obj.m_size); + save_value(stream, obj.dim); + save_value(stream, obj.root_bbox); + save_value(stream, obj.m_leaf_max_size); + save_value(stream, obj.vind); + save_tree(obj, stream, obj.root_node); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the + * index object must be constructed associated to the same source of data + * points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex_(Derived &obj, FILE *stream) { + load_value(stream, obj.m_size); + load_value(stream, obj.dim); + load_value(stream, obj.root_bbox); + load_value(stream, obj.m_leaf_max_size); + load_value(stream, obj.vind); + load_tree(obj, stream, obj.root_node); + } +}; + +/** @addtogroup kdtrees_grp KD-tree classes and adaptors + * @{ */ + +/** kd-tree static index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will + * be typically size_t or int + */ +template +class KDTreeSingleIndexAdaptor + : public KDTreeBaseClass< + KDTreeSingleIndexAdaptor, + Distance, DatasetAdaptor, DIM, IndexType> { +public: + /** Deleted copy constructor*/ + KDTreeSingleIndexAdaptor( + const KDTreeSingleIndexAdaptor + &) = delete; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + const KDTreeSingleIndexAdaptorParams index_params; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexAdaptor, + Distance, DatasetAdaptor, DIM, IndexType> + BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node *NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending + * on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 + * for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexAdaptor(const int dimensionality, + const DatasetAdaptor &inputData, + const KDTreeSingleIndexAdaptorParams ¶ms = + KDTreeSingleIndexAdaptorParams()) + : dataset(inputData), index_params(params), distance(inputData) { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + BaseClassRef::dim = dimensionality; + if (DIM > 0) + BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + + // Create a permutable array of indices to the input vectors. + init_vind(); + } + + /** + * Builds the index + */ + void buildIndex() { + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + init_vind(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if (BaseClassRef::m_size == 0) + return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = + this->divideTree(*this, 0, BaseClassRef::m_size, + BaseClassRef::root_bbox); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET &result, const ElementType *vec, + const SearchParams &searchParams) const { + assert(vec); + if (this->size(*this) == 0) + return false; + if (!BaseClassRef::root_node) + throw std::runtime_error( + "[nanoflann] findNeighbors() called before building the index."); + float epsError = 1 + searchParams.eps; + + distance_vector_t + dists; // fixed or variable-sized container (depending on DIM) + auto zero = static_cast(0); + assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim), + zero); // Fill it with zeros. + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, + epsError); // "count_leaf" parameter removed since was neither + // used nor returned to the user. + + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices are stored inside the result object. \sa radiusSearch, + * findNeighbors \note nChecks_IGNORED is ignored but kept for compatibility + * with the original FLANN interface. \return Number `N` of valid points in + * the result set. Only the first `N` entries in `out_indices` and + * `out_distances_sq` will be valid. Return may be less than `num_closest` + * only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, + IndexType *out_indices, DistanceType *out_distances_sq, + const int /* nChecks_IGNORED */ = 10) const { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a + * point index and the second the corresponding distance. Previous contents of + * \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector + * if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + */ + size_t + radiusSearch(const ElementType *query_point, const DistanceType &radius, + std::vector> &IndicesDists, + const SearchParams &searchParams) const { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = + radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter()); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as a + * start point for your own classes. \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback( + const ElementType *query_point, SEARCH_CALLBACK &resultSet, + const SearchParams &searchParams = SearchParams()) const { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + +public: + /** Make sure the auxiliary list \a vind has the same size than the current + * dataset, and re-generate if size has changed. */ + void init_vind() { + // Create a permutable array of indices to the input vectors. + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + if (BaseClassRef::vind.size() != BaseClassRef::m_size) + BaseClassRef::vind.resize(BaseClassRef::m_size); + for (size_t i = 0; i < BaseClassRef::m_size; i++) + BaseClassRef::vind[i] = i; + } + + void computeBoundingBox(BoundingBox &bbox) { + resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (dataset.kdtree_get_bbox(bbox)) { + // Done! It was implemented in derived class + } else { + const size_t N = dataset.kdtree_get_point_count(); + if (!N) + throw std::runtime_error("[nanoflann] computeBoundingBox() called but " + "no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = bbox[i].high = this->dataset_get(*this, 0, i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, k, i) < bbox[i].low) + bbox[i].low = this->dataset_get(*this, k, i); + if (this->dataset_get(*this, k, i) > bbox[i].high) + bbox[i].high = this->dataset_get(*this, k, i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + * \return true if the search should be continued, false if the results are + * sufficient + */ + template + bool searchLevel(RESULTSET &result_set, const ElementType *vec, + const NodePtr node, DistanceType mindistsq, + distance_vector_t &dists, const float epsError) const { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + // count_leaf += (node->lr.right-node->lr.left); // Removed since was + // neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; + ++i) { + const IndexType index = BaseClassRef::vind[i]; // reorder... : i; + DistanceType dist = distance.evalMetric( + vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (dist < worst_dist) { + if (!result_set.addPoint(dist, BaseClassRef::vind[i])) { + // the resultset doesn't want to receive any more points, we're done + // searching! + return false; + } + } + } + return true; + } + + /* Which child branch should be taken first? */ + int idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + if (!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) { + // the resultset doesn't want to receive any more points, we're done + // searching! + return false; + } + + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq * epsError <= result_set.worstDist()) { + if (!searchLevel(result_set, vec, otherChild, mindistsq, dists, + epsError)) { + // the resultset doesn't want to receive any more points, we're done + // searching! + return false; + } + } + dists[idx] = dst; + return true; + } + +public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when + * loading the index object it must be constructed associated to the same + * source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the + * index object must be constructed associated to the same source of data + * points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); } + +}; // class KDTree + +/** kd-tree dynamic index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will + * be typically size_t or int + */ +template +class KDTreeSingleIndexDynamicAdaptor_ + : public KDTreeBaseClass, + Distance, DatasetAdaptor, DIM, IndexType> { +public: + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + KDTreeSingleIndexAdaptorParams index_params; + + std::vector &treeIndex; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexDynamicAdaptor_, + Distance, DatasetAdaptor, DIM, IndexType> + BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node *NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending + * on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 + * for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor_( + const int dimensionality, const DatasetAdaptor &inputData, + std::vector &treeIndex_, + const KDTreeSingleIndexAdaptorParams ¶ms = + KDTreeSingleIndexAdaptorParams()) + : dataset(inputData), index_params(params), treeIndex(treeIndex_), + distance(inputData) { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = 0; + BaseClassRef::m_size_at_index_build = 0; + BaseClassRef::dim = dimensionality; + if (DIM > 0) + BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + } + + /** Assignment operator definiton */ + KDTreeSingleIndexDynamicAdaptor_ + operator=(const KDTreeSingleIndexDynamicAdaptor_ &rhs) { + KDTreeSingleIndexDynamicAdaptor_ tmp(rhs); + std::swap(BaseClassRef::vind, tmp.BaseClassRef::vind); + std::swap(BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size); + std::swap(index_params, tmp.index_params); + std::swap(treeIndex, tmp.treeIndex); + std::swap(BaseClassRef::m_size, tmp.BaseClassRef::m_size); + std::swap(BaseClassRef::m_size_at_index_build, + tmp.BaseClassRef::m_size_at_index_build); + std::swap(BaseClassRef::root_node, tmp.BaseClassRef::root_node); + std::swap(BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox); + std::swap(BaseClassRef::pool, tmp.BaseClassRef::pool); + return *this; + } + + /** + * Builds the index + */ + void buildIndex() { + BaseClassRef::m_size = BaseClassRef::vind.size(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if (BaseClassRef::m_size == 0) + return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = + this->divideTree(*this, 0, BaseClassRef::m_size, + BaseClassRef::root_bbox); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET &result, const ElementType *vec, + const SearchParams &searchParams) const { + assert(vec); + if (this->size(*this) == 0) + return false; + if (!BaseClassRef::root_node) + return false; + float epsError = 1 + searchParams.eps; + + // fixed or variable-sized container (depending on DIM) + distance_vector_t dists; + // Fill it with zeros. + assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim), + static_cast(0)); + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, + epsError); // "count_leaf" parameter removed since was neither + // used nor returned to the user. + + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices are stored inside the result object. \sa radiusSearch, + * findNeighbors \note nChecks_IGNORED is ignored but kept for compatibility + * with the original FLANN interface. \return Number `N` of valid points in + * the result set. Only the first `N` entries in `out_indices` and + * `out_distances_sq` will be valid. Return may be less than `num_closest` + * only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, + IndexType *out_indices, DistanceType *out_distances_sq, + const int /* nChecks_IGNORED */ = 10) const { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a + * point index and the second the corresponding distance. Previous contents of + * \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector + * if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + */ + size_t + radiusSearch(const ElementType *query_point, const DistanceType &radius, + std::vector> &IndicesDists, + const SearchParams &searchParams) const { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = + radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter()); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as a + * start point for your own classes. \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback( + const ElementType *query_point, SEARCH_CALLBACK &resultSet, + const SearchParams &searchParams = SearchParams()) const { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + +public: + void computeBoundingBox(BoundingBox &bbox) { + resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim)); + + if (dataset.kdtree_get_bbox(bbox)) { + // Done! It was implemented in derived class + } else { + const size_t N = BaseClassRef::m_size; + if (!N) + throw std::runtime_error("[nanoflann] computeBoundingBox() called but " + "no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = bbox[i].high = + this->dataset_get(*this, BaseClassRef::vind[0], i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low) + bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i); + if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high) + bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + */ + template + void searchLevel(RESULTSET &result_set, const ElementType *vec, + const NodePtr node, DistanceType mindistsq, + distance_vector_t &dists, const float epsError) const { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + // count_leaf += (node->lr.right-node->lr.left); // Removed since was + // neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; + ++i) { + const IndexType index = BaseClassRef::vind[i]; // reorder... : i; + if (treeIndex[index] == -1) + continue; + DistanceType dist = distance.evalMetric( + vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (dist < worst_dist) { + if (!result_set.addPoint( + static_cast(dist), + static_cast( + BaseClassRef::vind[i]))) { + // the resultset doesn't want to receive any more points, we're done + // searching! + return; // false; + } + } + } + return; + } + + /* Which child branch should be taken first? */ + int idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError); + + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq * epsError <= result_set.worstDist()) { + searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError); + } + dists[idx] = dst; + } + +public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when + * loading the index object it must be constructed associated to the same + * source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the + * index object must be constructed associated to the same source of data + * points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); } +}; + +/** kd-tree dynaimic index + * + * class to create multiple static index and merge their results to behave as + * single dynamic index as proposed in Logarithmic Approach. + * + * Example of usage: + * examples/dynamic_pointcloud_example.cpp + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will + * be typically size_t or int + */ +template +class KDTreeSingleIndexDynamicAdaptor { +public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + +protected: + size_t m_leaf_max_size; + size_t treeCount; + size_t pointCount; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which + //!< point at idx is stored. treeIndex[idx]=-1 + //!< means that point has been removed. + + KDTreeSingleIndexAdaptorParams index_params; + + int dim; //!< Dimensionality of each data point + + typedef KDTreeSingleIndexDynamicAdaptor_ + index_container_t; + std::vector index; + +public: + /** Get a const ref to the internal list of indices; the number of indices is + * adapted dynamically as the dataset grows in size. */ + const std::vector &getAllIndices() const { return index; } + +private: + /** finds position of least significant unset bit */ + int First0Bit(IndexType num) { + int pos = 0; + while (num & 1) { + num = num >> 1; + pos++; + } + return pos; + } + + /** Creates multiple empty trees to handle dynamic support */ + void init() { + typedef KDTreeSingleIndexDynamicAdaptor_ + my_kd_tree_t; + std::vector index_( + treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params)); + index = index_; + } + +public: + Distance distance; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 + * for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor(const int dimensionality, + const DatasetAdaptor &inputData, + const KDTreeSingleIndexAdaptorParams ¶ms = + KDTreeSingleIndexAdaptorParams(), + const size_t maximumPointCount = 1000000000U) + : dataset(inputData), index_params(params), distance(inputData) { + treeCount = static_cast(std::log2(maximumPointCount)); + pointCount = 0U; + dim = dimensionality; + treeIndex.clear(); + if (DIM > 0) + dim = DIM; + m_leaf_max_size = params.leaf_max_size; + init(); + const size_t num_initial_points = dataset.kdtree_get_point_count(); + if (num_initial_points > 0) { + addPoints(0, num_initial_points - 1); + } + } + + /** Deleted copy constructor*/ + KDTreeSingleIndexDynamicAdaptor( + const KDTreeSingleIndexDynamicAdaptor &) = delete; + + /** Add points to the set, Inserts all points from [start, end] */ + void addPoints(IndexType start, IndexType end) { + size_t count = end - start + 1; + treeIndex.resize(treeIndex.size() + count); + for (IndexType idx = start; idx <= end; idx++) { + int pos = First0Bit(pointCount); + index[pos].vind.clear(); + treeIndex[pointCount] = pos; + for (int i = 0; i < pos; i++) { + for (int j = 0; j < static_cast(index[i].vind.size()); j++) { + index[pos].vind.push_back(index[i].vind[j]); + if (treeIndex[index[i].vind[j]] != -1) + treeIndex[index[i].vind[j]] = pos; + } + index[i].vind.clear(); + index[i].freeIndex(index[i]); + } + index[pos].vind.push_back(idx); + index[pos].buildIndex(); + pointCount++; + } + } + + /** Remove a point from the set (Lazy Deletion) */ + void removePoint(size_t idx) { + if (idx >= pointCount) + return; + treeIndex[idx] = -1; + } + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET &result, const ElementType *vec, + const SearchParams &searchParams) const { + for (size_t i = 0; i < treeCount; i++) { + index[i].findNeighbors(result, &vec[0], searchParams); + } + return result.full(); + } +}; + +/** An L2-metric KD-tree adaptor for working with data directly stored in an + * Eigen Matrix, without duplicating the data storage. Each row in the matrix + * represents a point in the state space. + * + * Example of usage: + * \code + * Eigen::Matrix mat; + * // Fill out "mat"... + * + * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > + * my_kd_tree_t; const int max_leaf = 10; my_kd_tree_t mat_index(mat, max_leaf + * ); mat_index.index->buildIndex(); mat_index.index->... \endcode + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality + * for the points in the data set, allowing more compiler optimizations. \tparam + * Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + */ +template +struct KDTreeEigenMatrixAdaptor { + typedef KDTreeEigenMatrixAdaptor self_t; + typedef typename MatrixType::Scalar num_t; + typedef typename MatrixType::Index IndexType; + typedef + typename Distance::template traits::distance_t metric_t; + typedef KDTreeSingleIndexAdaptor + index_t; + + index_t *index; //! The kd-tree index for the user to call its methods as + //! usual with any other FLANN index. + + /// Constructor: takes a const ref to the matrix object with the data points + KDTreeEigenMatrixAdaptor(const size_t dimensionality, + const std::reference_wrapper &mat, + const int leaf_max_size = 10) + : m_data_matrix(mat) { + const auto dims = mat.get().cols(); + if (size_t(dims) != dimensionality) + throw std::runtime_error( + "Error: 'dimensionality' must match column count in data matrix"); + if (DIM > 0 && int(dims) != DIM) + throw std::runtime_error( + "Data set dimensionality does not match the 'DIM' template argument"); + index = + new index_t(static_cast(dims), *this /* adaptor */, + nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size)); + index->buildIndex(); + } + +public: + /** Deleted copy constructor */ + KDTreeEigenMatrixAdaptor(const self_t &) = delete; + + ~KDTreeEigenMatrixAdaptor() { delete index; } + + const std::reference_wrapper m_data_matrix; + + /** Query for the \a num_closest closest points to a given point (entered as + * query_point[0:dim-1]). Note that this is a short-cut method for + * index->findNeighbors(). The user can also call index->... methods as + * desired. \note nChecks_IGNORED is ignored but kept for compatibility with + * the original FLANN interface. + */ + inline void query(const num_t *query_point, const size_t num_closest, + IndexType *out_indices, num_t *out_distances_sq, + const int /* nChecks_IGNORED */ = 10) const { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t &derived() const { return *this; } + self_t &derived() { return *this; } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return m_data_matrix.get().rows(); + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const IndexType idx, size_t dim) const { + return m_data_matrix.get().coeff(idx, IndexType(dim)); + } + + // Optional bounding-box computation: return false to default to a standard + // bbox computation loop. + // Return true if the BBOX was already computed by the class and returned in + // "bb" so it can be avoided to redo it again. Look at bb.size() to find out + // the expected dimensionality (e.g. 2 or 3 for point clouds) + template bool kdtree_get_bbox(BBOX & /*bb*/) const { + return false; + } + + /** @} */ + +}; // end of KDTreeEigenMatrixAdaptor + /** @} */ + +/** @} */ // end of grouping +} // namespace nanoflann + +#endif /* NANOFLANN_HPP_ */ diff --git a/research/cv/WS3/third_party/setup.py b/research/cv/WS3/third_party/setup.py new file mode 100644 index 000000000..083d9d4fb --- /dev/null +++ b/research/cv/WS3/third_party/setup.py @@ -0,0 +1,29 @@ +from distutils.core import setup, Extension +import numpy.distutils.misc_util + +# Adding OpenCV to project +# ************************ + +# Adding sources of the project +# ***************************** + +m_name = "grid_subsampling" + +SOURCES = ["../cpp_utils/cloud/cloud.cpp", + "grid_subsampling/grid_subsampling.cpp", + "wrapper.cpp"] + +module = Extension(m_name, + sources=SOURCES, + extra_compile_args=['-std=c++11', + '-D_GLIBCXX_USE_CXX11_ABI=0']) + +setup(ext_modules=[module], include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs()) + + + + + + + + diff --git a/research/cv/WS3/third_party/src/KDTreeTableAdaptor.h b/research/cv/WS3/third_party/src/KDTreeTableAdaptor.h new file mode 100644 index 000000000..8cdc787fb --- /dev/null +++ b/research/cv/WS3/third_party/src/KDTreeTableAdaptor.h @@ -0,0 +1,189 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2011-16 Jose Luis Blanco (joseluisblancoc@gmail.com). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +#pragma once + +#include "nanoflann.hpp" + +// #include + +// ===== This example shows how to use nanoflann with these types of containers: ======= +//typedef std::vector > my_vector_of_vectors_t; +//typedef std::vector my_vector_of_vectors_t; // This requires #include +// ===================================================================================== + + +/** A simple vector-of-vectors adaptor for nanoflann, without duplicating the storage. + * The i'th vector represents a point in the state space. + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. + * \tparam num_t The type of the point coordinates (typically, double or float). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam IndexType The type for indices in the KD-tree index (typically, size_t of int) + */ +// template +// struct KDTreeVectorAdaptor +// { +// typedef KDTreeVectorAdaptor self_t; +// typedef typename Distance::template traits::distance_t metric_t; +// typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; + +// index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. +// size_t dims; + +// /// Constructor: takes a const ref to the vector of vectors object with the data points +// KDTreeVectorAdaptor(const size_t dims /* dimensionality */, const VectorType &mat, const int leaf_max_size = 10) : m_data(mat) +// { +// assert(mat.size() != 0); +// this->dims= dims; +// index = new index_t( static_cast(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); +// index->buildIndex(); +// } + +// ~KDTreeVectorAdaptor() { +// delete index; +// } + +// const VectorType &m_data; + +// /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). +// * Note that this is a short-cut method for index->findNeighbors(). +// * The user can also call index->... methods as desired. +// * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. +// */ +// inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const +// { +// nanoflann::KNNResultSet resultSet(num_closest); +// resultSet.init(out_indices, out_distances_sq); +// index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); +// } + +// /** @name Interface expected by KDTreeSingleIndexAdaptor +// * @{ */ + +// const self_t & derived() const { +// return *this; +// } +// self_t & derived() { +// return *this; +// } + +// // Must return the number of data points +// inline size_t kdtree_get_point_count() const { +// return m_data.size()/this->dims; +// } + +// // Returns the dim'th component of the idx'th point in the class: +// inline num_t kdtree_get_pt(const size_t idx, const size_t dim) const { +// return m_data[idx*this->dims + dim]; +// } + +// // Optional bounding-box computation: return false to default to a standard bbox computation loop. +// // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. +// // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) +// template +// bool kdtree_get_bbox(BBOX & /*bb*/) const { +// return false; +// } + +// /** @} */ + +// }; // end of KDTreeVectorOfVectorsAdaptor + + + + +template +struct KDTreeTableAdaptor +{ + typedef KDTreeTableAdaptor self_t; + typedef typename Distance::template traits::distance_t metric_t; + typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; + + index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. + size_t dim; + size_t npts; + const TableType* m_data; + + /// Constructor: takes a const ref to the vector of vectors object with the data points + KDTreeTableAdaptor(const size_t npts, const size_t dim, const TableType* mat, const int leaf_max_size = 10) : m_data(mat), dim(dim), npts(npts) + { + assert(npts != 0); + index = new index_t( static_cast(dim), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); + index->buildIndex(); + } + + ~KDTreeTableAdaptor() { + delete index; + } + + + /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). + * Note that this is a short-cut method for index->findNeighbors(). + * The user can also call index->... methods as desired. + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + */ + inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t & derived() const { + return *this; + } + self_t & derived() { + return *this; + } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return this->npts; + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const size_t pts_id, const size_t coord_id) const { + return m_data[pts_id*this->dim + coord_id]; + } + + // Optional bounding-box computation: return false to default to a standard bbox computation loop. + // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + bool kdtree_get_bbox(BBOX & /*bb*/) const { + return false; + } + + /** @} */ + +}; // end of KDTreeVectorOfVectorsAdaptor + diff --git a/research/cv/WS3/third_party/src/knn.cpp b/research/cv/WS3/third_party/src/knn.cpp new file mode 100644 index 000000000..7b99a91bc --- /dev/null +++ b/research/cv/WS3/third_party/src/knn.cpp @@ -0,0 +1,9696 @@ +/* Generated by Cython 0.29.19 */ + +#define PY_SSIZE_T_CLEAN +#include "Python.h" +#ifndef Py_PYTHON_H + #error Python headers needed to compile C extensions, please install development version of Python. +#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000) + #error Cython requires Python 2.6+ or Python 3.3+. +#else +#define CYTHON_ABI "0_29_19" +#define CYTHON_HEX_VERSION 0x001D13F0 +#define CYTHON_FUTURE_DIVISION 0 +#include +#ifndef offsetof + #define offsetof(type, member) ( (size_t) & ((type*)0) -> member ) +#endif +#if !defined(WIN32) && !defined(MS_WINDOWS) + #ifndef __stdcall + #define __stdcall + #endif + #ifndef __cdecl + #define __cdecl + #endif + #ifndef __fastcall + #define __fastcall + #endif +#endif +#ifndef DL_IMPORT + #define DL_IMPORT(t) t +#endif +#ifndef DL_EXPORT + #define DL_EXPORT(t) t +#endif +#define __PYX_COMMA , +#ifndef HAVE_LONG_LONG + #if PY_VERSION_HEX >= 0x02070000 + #define HAVE_LONG_LONG + #endif +#endif +#ifndef PY_LONG_LONG + #define PY_LONG_LONG LONG_LONG +#endif +#ifndef Py_HUGE_VAL + #define Py_HUGE_VAL HUGE_VAL +#endif +#ifdef PYPY_VERSION + #define CYTHON_COMPILING_IN_PYPY 1 + #define CYTHON_COMPILING_IN_PYSTON 0 + #define CYTHON_COMPILING_IN_CPYTHON 0 + #undef CYTHON_USE_TYPE_SLOTS + #define CYTHON_USE_TYPE_SLOTS 0 + #undef CYTHON_USE_PYTYPE_LOOKUP + #define CYTHON_USE_PYTYPE_LOOKUP 0 + #if PY_VERSION_HEX < 0x03050000 + #undef CYTHON_USE_ASYNC_SLOTS + #define CYTHON_USE_ASYNC_SLOTS 0 + #elif !defined(CYTHON_USE_ASYNC_SLOTS) + #define CYTHON_USE_ASYNC_SLOTS 1 + #endif + #undef CYTHON_USE_PYLIST_INTERNALS + #define CYTHON_USE_PYLIST_INTERNALS 0 + #undef CYTHON_USE_UNICODE_INTERNALS + #define CYTHON_USE_UNICODE_INTERNALS 0 + #undef CYTHON_USE_UNICODE_WRITER + #define CYTHON_USE_UNICODE_WRITER 0 + #undef CYTHON_USE_PYLONG_INTERNALS + #define CYTHON_USE_PYLONG_INTERNALS 0 + #undef CYTHON_AVOID_BORROWED_REFS + #define CYTHON_AVOID_BORROWED_REFS 1 + #undef CYTHON_ASSUME_SAFE_MACROS + #define CYTHON_ASSUME_SAFE_MACROS 0 + #undef CYTHON_UNPACK_METHODS + #define CYTHON_UNPACK_METHODS 0 + #undef CYTHON_FAST_THREAD_STATE + #define CYTHON_FAST_THREAD_STATE 0 + #undef CYTHON_FAST_PYCALL + #define CYTHON_FAST_PYCALL 0 + #undef CYTHON_PEP489_MULTI_PHASE_INIT + #define CYTHON_PEP489_MULTI_PHASE_INIT 0 + #undef CYTHON_USE_TP_FINALIZE + #define CYTHON_USE_TP_FINALIZE 0 + #undef CYTHON_USE_DICT_VERSIONS + #define CYTHON_USE_DICT_VERSIONS 0 + #undef CYTHON_USE_EXC_INFO_STACK + #define CYTHON_USE_EXC_INFO_STACK 0 +#elif defined(PYSTON_VERSION) + #define CYTHON_COMPILING_IN_PYPY 0 + #define CYTHON_COMPILING_IN_PYSTON 1 + #define CYTHON_COMPILING_IN_CPYTHON 0 + #ifndef CYTHON_USE_TYPE_SLOTS + #define CYTHON_USE_TYPE_SLOTS 1 + #endif + #undef CYTHON_USE_PYTYPE_LOOKUP + #define CYTHON_USE_PYTYPE_LOOKUP 0 + #undef CYTHON_USE_ASYNC_SLOTS + #define CYTHON_USE_ASYNC_SLOTS 0 + #undef CYTHON_USE_PYLIST_INTERNALS + #define CYTHON_USE_PYLIST_INTERNALS 0 + #ifndef CYTHON_USE_UNICODE_INTERNALS + #define CYTHON_USE_UNICODE_INTERNALS 1 + #endif + #undef CYTHON_USE_UNICODE_WRITER + #define CYTHON_USE_UNICODE_WRITER 0 + #undef CYTHON_USE_PYLONG_INTERNALS + #define CYTHON_USE_PYLONG_INTERNALS 0 + #ifndef CYTHON_AVOID_BORROWED_REFS + #define CYTHON_AVOID_BORROWED_REFS 0 + #endif + #ifndef CYTHON_ASSUME_SAFE_MACROS + #define CYTHON_ASSUME_SAFE_MACROS 1 + #endif + #ifndef CYTHON_UNPACK_METHODS + #define CYTHON_UNPACK_METHODS 1 + #endif + #undef CYTHON_FAST_THREAD_STATE + #define CYTHON_FAST_THREAD_STATE 0 + #undef CYTHON_FAST_PYCALL + #define CYTHON_FAST_PYCALL 0 + #undef CYTHON_PEP489_MULTI_PHASE_INIT + #define CYTHON_PEP489_MULTI_PHASE_INIT 0 + #undef CYTHON_USE_TP_FINALIZE + #define CYTHON_USE_TP_FINALIZE 0 + #undef CYTHON_USE_DICT_VERSIONS + #define CYTHON_USE_DICT_VERSIONS 0 + #undef CYTHON_USE_EXC_INFO_STACK + #define CYTHON_USE_EXC_INFO_STACK 0 +#else + #define CYTHON_COMPILING_IN_PYPY 0 + #define CYTHON_COMPILING_IN_PYSTON 0 + #define CYTHON_COMPILING_IN_CPYTHON 1 + #ifndef CYTHON_USE_TYPE_SLOTS + #define CYTHON_USE_TYPE_SLOTS 1 + #endif + #if PY_VERSION_HEX < 0x02070000 + #undef CYTHON_USE_PYTYPE_LOOKUP + #define CYTHON_USE_PYTYPE_LOOKUP 0 + #elif !defined(CYTHON_USE_PYTYPE_LOOKUP) + #define CYTHON_USE_PYTYPE_LOOKUP 1 + #endif + #if PY_MAJOR_VERSION < 3 + #undef CYTHON_USE_ASYNC_SLOTS + #define CYTHON_USE_ASYNC_SLOTS 0 + #elif !defined(CYTHON_USE_ASYNC_SLOTS) + #define CYTHON_USE_ASYNC_SLOTS 1 + #endif + #if PY_VERSION_HEX < 0x02070000 + #undef CYTHON_USE_PYLONG_INTERNALS + #define CYTHON_USE_PYLONG_INTERNALS 0 + #elif !defined(CYTHON_USE_PYLONG_INTERNALS) + #define CYTHON_USE_PYLONG_INTERNALS 1 + #endif + #ifndef CYTHON_USE_PYLIST_INTERNALS + #define CYTHON_USE_PYLIST_INTERNALS 1 + #endif + #ifndef CYTHON_USE_UNICODE_INTERNALS + #define CYTHON_USE_UNICODE_INTERNALS 1 + #endif + #if PY_VERSION_HEX < 0x030300F0 + #undef CYTHON_USE_UNICODE_WRITER + #define CYTHON_USE_UNICODE_WRITER 0 + #elif !defined(CYTHON_USE_UNICODE_WRITER) + #define CYTHON_USE_UNICODE_WRITER 1 + #endif + #ifndef CYTHON_AVOID_BORROWED_REFS + #define CYTHON_AVOID_BORROWED_REFS 0 + #endif + #ifndef CYTHON_ASSUME_SAFE_MACROS + #define CYTHON_ASSUME_SAFE_MACROS 1 + #endif + #ifndef CYTHON_UNPACK_METHODS + #define CYTHON_UNPACK_METHODS 1 + #endif + #ifndef CYTHON_FAST_THREAD_STATE + #define CYTHON_FAST_THREAD_STATE 1 + #endif + #ifndef CYTHON_FAST_PYCALL + #define CYTHON_FAST_PYCALL 1 + #endif + #ifndef CYTHON_PEP489_MULTI_PHASE_INIT + #define CYTHON_PEP489_MULTI_PHASE_INIT (PY_VERSION_HEX >= 0x03050000) + #endif + #ifndef CYTHON_USE_TP_FINALIZE + #define CYTHON_USE_TP_FINALIZE (PY_VERSION_HEX >= 0x030400a1) + #endif + #ifndef CYTHON_USE_DICT_VERSIONS + #define CYTHON_USE_DICT_VERSIONS (PY_VERSION_HEX >= 0x030600B1) + #endif + #ifndef CYTHON_USE_EXC_INFO_STACK + #define CYTHON_USE_EXC_INFO_STACK (PY_VERSION_HEX >= 0x030700A3) + #endif +#endif +#if !defined(CYTHON_FAST_PYCCALL) +#define CYTHON_FAST_PYCCALL (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1) +#endif +#if CYTHON_USE_PYLONG_INTERNALS + #include "longintrepr.h" + #undef SHIFT + #undef BASE + #undef MASK + #ifdef SIZEOF_VOID_P + enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) }; + #endif +#endif +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif +#ifndef __has_cpp_attribute + #define __has_cpp_attribute(x) 0 +#endif +#ifndef CYTHON_RESTRICT + #if defined(__GNUC__) + #define CYTHON_RESTRICT __restrict__ + #elif defined(_MSC_VER) && _MSC_VER >= 1400 + #define CYTHON_RESTRICT __restrict + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + #define CYTHON_RESTRICT restrict + #else + #define CYTHON_RESTRICT + #endif +#endif +#ifndef CYTHON_UNUSED +# if defined(__GNUC__) +# if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) +# define CYTHON_UNUSED __attribute__ ((__unused__)) +# else +# define CYTHON_UNUSED +# endif +# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER)) +# define CYTHON_UNUSED __attribute__ ((__unused__)) +# else +# define CYTHON_UNUSED +# endif +#endif +#ifndef CYTHON_MAYBE_UNUSED_VAR +# if defined(__cplusplus) + template void CYTHON_MAYBE_UNUSED_VAR( const T& ) { } +# else +# define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x) +# endif +#endif +#ifndef CYTHON_NCP_UNUSED +# if CYTHON_COMPILING_IN_CPYTHON +# define CYTHON_NCP_UNUSED +# else +# define CYTHON_NCP_UNUSED CYTHON_UNUSED +# endif +#endif +#define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None) +#ifdef _MSC_VER + #ifndef _MSC_STDINT_H_ + #if _MSC_VER < 1300 + typedef unsigned char uint8_t; + typedef unsigned int uint32_t; + #else + typedef unsigned __int8 uint8_t; + typedef unsigned __int32 uint32_t; + #endif + #endif +#else + #include +#endif +#ifndef CYTHON_FALLTHROUGH + #if defined(__cplusplus) && __cplusplus >= 201103L + #if __has_cpp_attribute(fallthrough) + #define CYTHON_FALLTHROUGH [[fallthrough]] + #elif __has_cpp_attribute(clang::fallthrough) + #define CYTHON_FALLTHROUGH [[clang::fallthrough]] + #elif __has_cpp_attribute(gnu::fallthrough) + #define CYTHON_FALLTHROUGH [[gnu::fallthrough]] + #endif + #endif + #ifndef CYTHON_FALLTHROUGH + #if __has_attribute(fallthrough) + #define CYTHON_FALLTHROUGH __attribute__((fallthrough)) + #else + #define CYTHON_FALLTHROUGH + #endif + #endif + #if defined(__clang__ ) && defined(__apple_build_version__) + #if __apple_build_version__ < 7000000 + #undef CYTHON_FALLTHROUGH + #define CYTHON_FALLTHROUGH + #endif + #endif +#endif + +#ifndef __cplusplus + #error "Cython files generated with the C++ option must be compiled with a C++ compiler." +#endif +#ifndef CYTHON_INLINE + #if defined(__clang__) + #define CYTHON_INLINE __inline__ __attribute__ ((__unused__)) + #else + #define CYTHON_INLINE inline + #endif +#endif +template +void __Pyx_call_destructor(T& x) { + x.~T(); +} +template +class __Pyx_FakeReference { + public: + __Pyx_FakeReference() : ptr(NULL) { } + __Pyx_FakeReference(const T& ref) : ptr(const_cast(&ref)) { } + T *operator->() { return ptr; } + T *operator&() { return ptr; } + operator T&() { return *ptr; } + template bool operator ==(U other) { return *ptr == other; } + template bool operator !=(U other) { return *ptr != other; } + private: + T *ptr; +}; + +#if CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x02070600 && !defined(Py_OptimizeFlag) + #define Py_OptimizeFlag 0 +#endif +#define __PYX_BUILD_PY_SSIZE_T "n" +#define CYTHON_FORMAT_SSIZE_T "z" +#if PY_MAJOR_VERSION < 3 + #define __Pyx_BUILTIN_MODULE_NAME "__builtin__" + #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ + PyCode_New(a+k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) + #define __Pyx_DefaultClassType PyClass_Type +#else + #define __Pyx_BUILTIN_MODULE_NAME "builtins" +#if PY_VERSION_HEX >= 0x030800A4 && PY_VERSION_HEX < 0x030800B2 + #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ + PyCode_New(a, 0, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) +#else + #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\ + PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos) +#endif + #define __Pyx_DefaultClassType PyType_Type +#endif +#ifndef Py_TPFLAGS_CHECKTYPES + #define Py_TPFLAGS_CHECKTYPES 0 +#endif +#ifndef Py_TPFLAGS_HAVE_INDEX + #define Py_TPFLAGS_HAVE_INDEX 0 +#endif +#ifndef Py_TPFLAGS_HAVE_NEWBUFFER + #define Py_TPFLAGS_HAVE_NEWBUFFER 0 +#endif +#ifndef Py_TPFLAGS_HAVE_FINALIZE + #define Py_TPFLAGS_HAVE_FINALIZE 0 +#endif +#ifndef METH_STACKLESS + #define METH_STACKLESS 0 +#endif +#if PY_VERSION_HEX <= 0x030700A3 || !defined(METH_FASTCALL) + #ifndef METH_FASTCALL + #define METH_FASTCALL 0x80 + #endif + typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject *const *args, Py_ssize_t nargs); + typedef PyObject *(*__Pyx_PyCFunctionFastWithKeywords) (PyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames); +#else + #define __Pyx_PyCFunctionFast _PyCFunctionFast + #define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords +#endif +#if CYTHON_FAST_PYCCALL +#define __Pyx_PyFastCFunction_Check(func)\ + ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_KEYWORDS | METH_STACKLESS))))) +#else +#define __Pyx_PyFastCFunction_Check(func) 0 +#endif +#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Malloc) + #define PyObject_Malloc(s) PyMem_Malloc(s) + #define PyObject_Free(p) PyMem_Free(p) + #define PyObject_Realloc(p) PyMem_Realloc(p) +#endif +#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030400A1 + #define PyMem_RawMalloc(n) PyMem_Malloc(n) + #define PyMem_RawRealloc(p, n) PyMem_Realloc(p, n) + #define PyMem_RawFree(p) PyMem_Free(p) +#endif +#if CYTHON_COMPILING_IN_PYSTON + #define __Pyx_PyCode_HasFreeVars(co) PyCode_HasFreeVars(co) + #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno) +#else + #define __Pyx_PyCode_HasFreeVars(co) (PyCode_GetNumFree(co) > 0) + #define __Pyx_PyFrame_SetLineNumber(frame, lineno) (frame)->f_lineno = (lineno) +#endif +#if !CYTHON_FAST_THREAD_STATE || PY_VERSION_HEX < 0x02070000 + #define __Pyx_PyThreadState_Current PyThreadState_GET() +#elif PY_VERSION_HEX >= 0x03060000 + #define __Pyx_PyThreadState_Current _PyThreadState_UncheckedGet() +#elif PY_VERSION_HEX >= 0x03000000 + #define __Pyx_PyThreadState_Current PyThreadState_GET() +#else + #define __Pyx_PyThreadState_Current _PyThreadState_Current +#endif +#if PY_VERSION_HEX < 0x030700A2 && !defined(PyThread_tss_create) && !defined(Py_tss_NEEDS_INIT) +#include "pythread.h" +#define Py_tss_NEEDS_INIT 0 +typedef int Py_tss_t; +static CYTHON_INLINE int PyThread_tss_create(Py_tss_t *key) { + *key = PyThread_create_key(); + return 0; +} +static CYTHON_INLINE Py_tss_t * PyThread_tss_alloc(void) { + Py_tss_t *key = (Py_tss_t *)PyObject_Malloc(sizeof(Py_tss_t)); + *key = Py_tss_NEEDS_INIT; + return key; +} +static CYTHON_INLINE void PyThread_tss_free(Py_tss_t *key) { + PyObject_Free(key); +} +static CYTHON_INLINE int PyThread_tss_is_created(Py_tss_t *key) { + return *key != Py_tss_NEEDS_INIT; +} +static CYTHON_INLINE void PyThread_tss_delete(Py_tss_t *key) { + PyThread_delete_key(*key); + *key = Py_tss_NEEDS_INIT; +} +static CYTHON_INLINE int PyThread_tss_set(Py_tss_t *key, void *value) { + return PyThread_set_key_value(*key, value); +} +static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) { + return PyThread_get_key_value(*key); +} +#endif +#if CYTHON_COMPILING_IN_CPYTHON || defined(_PyDict_NewPresized) +#define __Pyx_PyDict_NewPresized(n) ((n <= 8) ? PyDict_New() : _PyDict_NewPresized(n)) +#else +#define __Pyx_PyDict_NewPresized(n) PyDict_New() +#endif +#if PY_MAJOR_VERSION >= 3 || CYTHON_FUTURE_DIVISION + #define __Pyx_PyNumber_Divide(x,y) PyNumber_TrueDivide(x,y) + #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceTrueDivide(x,y) +#else + #define __Pyx_PyNumber_Divide(x,y) PyNumber_Divide(x,y) + #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceDivide(x,y) +#endif +#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 && CYTHON_USE_UNICODE_INTERNALS +#define __Pyx_PyDict_GetItemStr(dict, name) _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash) +#else +#define __Pyx_PyDict_GetItemStr(dict, name) PyDict_GetItem(dict, name) +#endif +#if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND) + #define CYTHON_PEP393_ENABLED 1 + #define __Pyx_PyUnicode_READY(op) (likely(PyUnicode_IS_READY(op)) ?\ + 0 : _PyUnicode_Ready((PyObject *)(op))) + #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_LENGTH(u) + #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i) + #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) PyUnicode_MAX_CHAR_VALUE(u) + #define __Pyx_PyUnicode_KIND(u) PyUnicode_KIND(u) + #define __Pyx_PyUnicode_DATA(u) PyUnicode_DATA(u) + #define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i) + #define __Pyx_PyUnicode_WRITE(k, d, i, ch) PyUnicode_WRITE(k, d, i, ch) + #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u))) +#else + #define CYTHON_PEP393_ENABLED 0 + #define PyUnicode_1BYTE_KIND 1 + #define PyUnicode_2BYTE_KIND 2 + #define PyUnicode_4BYTE_KIND 4 + #define __Pyx_PyUnicode_READY(op) (0) + #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_SIZE(u) + #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i])) + #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111) + #define __Pyx_PyUnicode_KIND(u) (sizeof(Py_UNICODE)) + #define __Pyx_PyUnicode_DATA(u) ((void*)PyUnicode_AS_UNICODE(u)) + #define __Pyx_PyUnicode_READ(k, d, i) ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i])) + #define __Pyx_PyUnicode_WRITE(k, d, i, ch) (((void)(k)), ((Py_UNICODE*)d)[i] = ch) + #define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_SIZE(u)) +#endif +#if CYTHON_COMPILING_IN_PYPY + #define __Pyx_PyUnicode_Concat(a, b) PyNumber_Add(a, b) + #define __Pyx_PyUnicode_ConcatSafe(a, b) PyNumber_Add(a, b) +#else + #define __Pyx_PyUnicode_Concat(a, b) PyUnicode_Concat(a, b) + #define __Pyx_PyUnicode_ConcatSafe(a, b) ((unlikely((a) == Py_None) || unlikely((b) == Py_None)) ?\ + PyNumber_Add(a, b) : __Pyx_PyUnicode_Concat(a, b)) +#endif +#if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains) + #define PyUnicode_Contains(u, s) PySequence_Contains(u, s) +#endif +#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check) + #define PyByteArray_Check(obj) PyObject_TypeCheck(obj, &PyByteArray_Type) +#endif +#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format) + #define PyObject_Format(obj, fmt) PyObject_CallMethod(obj, "__format__", "O", fmt) +#endif +#define __Pyx_PyString_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyString_Check(b) && !PyString_CheckExact(b)))) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b)) +#define __Pyx_PyUnicode_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyUnicode_Check(b) && !PyUnicode_CheckExact(b)))) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b)) +#if PY_MAJOR_VERSION >= 3 + #define __Pyx_PyString_Format(a, b) PyUnicode_Format(a, b) +#else + #define __Pyx_PyString_Format(a, b) PyString_Format(a, b) +#endif +#if PY_MAJOR_VERSION < 3 && !defined(PyObject_ASCII) + #define PyObject_ASCII(o) PyObject_Repr(o) +#endif +#if PY_MAJOR_VERSION >= 3 + #define PyBaseString_Type PyUnicode_Type + #define PyStringObject PyUnicodeObject + #define PyString_Type PyUnicode_Type + #define PyString_Check PyUnicode_Check + #define PyString_CheckExact PyUnicode_CheckExact +#ifndef PyObject_Unicode + #define PyObject_Unicode PyObject_Str +#endif +#endif +#if PY_MAJOR_VERSION >= 3 + #define __Pyx_PyBaseString_Check(obj) PyUnicode_Check(obj) + #define __Pyx_PyBaseString_CheckExact(obj) PyUnicode_CheckExact(obj) +#else + #define __Pyx_PyBaseString_Check(obj) (PyString_Check(obj) || PyUnicode_Check(obj)) + #define __Pyx_PyBaseString_CheckExact(obj) (PyString_CheckExact(obj) || PyUnicode_CheckExact(obj)) +#endif +#ifndef PySet_CheckExact + #define PySet_CheckExact(obj) (Py_TYPE(obj) == &PySet_Type) +#endif +#if CYTHON_ASSUME_SAFE_MACROS + #define __Pyx_PySequence_SIZE(seq) Py_SIZE(seq) +#else + #define __Pyx_PySequence_SIZE(seq) PySequence_Size(seq) +#endif +#if PY_MAJOR_VERSION >= 3 + #define PyIntObject PyLongObject + #define PyInt_Type PyLong_Type + #define PyInt_Check(op) PyLong_Check(op) + #define PyInt_CheckExact(op) PyLong_CheckExact(op) + #define PyInt_FromString PyLong_FromString + #define PyInt_FromUnicode PyLong_FromUnicode + #define PyInt_FromLong PyLong_FromLong + #define PyInt_FromSize_t PyLong_FromSize_t + #define PyInt_FromSsize_t PyLong_FromSsize_t + #define PyInt_AsLong PyLong_AsLong + #define PyInt_AS_LONG PyLong_AS_LONG + #define PyInt_AsSsize_t PyLong_AsSsize_t + #define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask + #define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask + #define PyNumber_Int PyNumber_Long +#endif +#if PY_MAJOR_VERSION >= 3 + #define PyBoolObject PyLongObject +#endif +#if PY_MAJOR_VERSION >= 3 && CYTHON_COMPILING_IN_PYPY + #ifndef PyUnicode_InternFromString + #define PyUnicode_InternFromString(s) PyUnicode_FromString(s) + #endif +#endif +#if PY_VERSION_HEX < 0x030200A4 + typedef long Py_hash_t; + #define __Pyx_PyInt_FromHash_t PyInt_FromLong + #define __Pyx_PyInt_AsHash_t PyInt_AsLong +#else + #define __Pyx_PyInt_FromHash_t PyInt_FromSsize_t + #define __Pyx_PyInt_AsHash_t PyInt_AsSsize_t +#endif +#if PY_MAJOR_VERSION >= 3 + #define __Pyx_PyMethod_New(func, self, klass) ((self) ? PyMethod_New(func, self) : (Py_INCREF(func), func)) +#else + #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass) +#endif +#if CYTHON_USE_ASYNC_SLOTS + #if PY_VERSION_HEX >= 0x030500B1 + #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods + #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async) + #else + #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved)) + #endif +#else + #define __Pyx_PyType_AsAsync(obj) NULL +#endif +#ifndef __Pyx_PyAsyncMethodsStruct + typedef struct { + unaryfunc am_await; + unaryfunc am_aiter; + unaryfunc am_anext; + } __Pyx_PyAsyncMethodsStruct; +#endif + +#if defined(WIN32) || defined(MS_WINDOWS) + #define _USE_MATH_DEFINES +#endif +#include +#ifdef NAN +#define __PYX_NAN() ((float) NAN) +#else +static CYTHON_INLINE float __PYX_NAN() { + float value; + memset(&value, 0xFF, sizeof(value)); + return value; +} +#endif +#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL) +#define __Pyx_truncl trunc +#else +#define __Pyx_truncl truncl +#endif + +#define __PYX_MARK_ERR_POS(f_index, lineno) \ + { __pyx_filename = __pyx_f[f_index]; (void)__pyx_filename; __pyx_lineno = lineno; (void)__pyx_lineno; __pyx_clineno = __LINE__; (void)__pyx_clineno; } +#define __PYX_ERR(f_index, lineno, Ln_error) \ + { __PYX_MARK_ERR_POS(f_index, lineno) goto Ln_error; } + +#ifndef __PYX_EXTERN_C + #ifdef __cplusplus + #define __PYX_EXTERN_C extern "C" + #else + #define __PYX_EXTERN_C extern + #endif +#endif + +#define __PYX_HAVE__nearest_neighbors +#define __PYX_HAVE_API__nearest_neighbors +/* Early includes */ +#include +#include +#include "numpy/arrayobject.h" +#include "numpy/ufuncobject.h" +#include "knn_.h" +#ifdef _OPENMP +#include +#endif /* _OPENMP */ + +#if defined(PYREX_WITHOUT_ASSERTIONS) && !defined(CYTHON_WITHOUT_ASSERTIONS) +#define CYTHON_WITHOUT_ASSERTIONS +#endif + +typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding; + const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry; + +#define __PYX_DEFAULT_STRING_ENCODING_IS_ASCII 0 +#define __PYX_DEFAULT_STRING_ENCODING_IS_UTF8 0 +#define __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT (PY_MAJOR_VERSION >= 3 && __PYX_DEFAULT_STRING_ENCODING_IS_UTF8) +#define __PYX_DEFAULT_STRING_ENCODING "" +#define __Pyx_PyObject_FromString __Pyx_PyBytes_FromString +#define __Pyx_PyObject_FromStringAndSize __Pyx_PyBytes_FromStringAndSize +#define __Pyx_uchar_cast(c) ((unsigned char)c) +#define __Pyx_long_cast(x) ((long)x) +#define __Pyx_fits_Py_ssize_t(v, type, is_signed) (\ + (sizeof(type) < sizeof(Py_ssize_t)) ||\ + (sizeof(type) > sizeof(Py_ssize_t) &&\ + likely(v < (type)PY_SSIZE_T_MAX ||\ + v == (type)PY_SSIZE_T_MAX) &&\ + (!is_signed || likely(v > (type)PY_SSIZE_T_MIN ||\ + v == (type)PY_SSIZE_T_MIN))) ||\ + (sizeof(type) == sizeof(Py_ssize_t) &&\ + (is_signed || likely(v < (type)PY_SSIZE_T_MAX ||\ + v == (type)PY_SSIZE_T_MAX))) ) +static CYTHON_INLINE int __Pyx_is_valid_index(Py_ssize_t i, Py_ssize_t limit) { + return (size_t) i < (size_t) limit; +} +#if defined (__cplusplus) && __cplusplus >= 201103L + #include + #define __Pyx_sst_abs(value) std::abs(value) +#elif SIZEOF_INT >= SIZEOF_SIZE_T + #define __Pyx_sst_abs(value) abs(value) +#elif SIZEOF_LONG >= SIZEOF_SIZE_T + #define __Pyx_sst_abs(value) labs(value) +#elif defined (_MSC_VER) + #define __Pyx_sst_abs(value) ((Py_ssize_t)_abs64(value)) +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + #define __Pyx_sst_abs(value) llabs(value) +#elif defined (__GNUC__) + #define __Pyx_sst_abs(value) __builtin_llabs(value) +#else + #define __Pyx_sst_abs(value) ((value<0) ? -value : value) +#endif +static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject*); +static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject*, Py_ssize_t* length); +#define __Pyx_PyByteArray_FromString(s) PyByteArray_FromStringAndSize((const char*)s, strlen((const char*)s)) +#define __Pyx_PyByteArray_FromStringAndSize(s, l) PyByteArray_FromStringAndSize((const char*)s, l) +#define __Pyx_PyBytes_FromString PyBytes_FromString +#define __Pyx_PyBytes_FromStringAndSize PyBytes_FromStringAndSize +static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char*); +#if PY_MAJOR_VERSION < 3 + #define __Pyx_PyStr_FromString __Pyx_PyBytes_FromString + #define __Pyx_PyStr_FromStringAndSize __Pyx_PyBytes_FromStringAndSize +#else + #define __Pyx_PyStr_FromString __Pyx_PyUnicode_FromString + #define __Pyx_PyStr_FromStringAndSize __Pyx_PyUnicode_FromStringAndSize +#endif +#define __Pyx_PyBytes_AsWritableString(s) ((char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyBytes_AsWritableSString(s) ((signed char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyBytes_AsWritableUString(s) ((unsigned char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyBytes_AsString(s) ((const char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyBytes_AsSString(s) ((const signed char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyBytes_AsUString(s) ((const unsigned char*) PyBytes_AS_STRING(s)) +#define __Pyx_PyObject_AsWritableString(s) ((char*) __Pyx_PyObject_AsString(s)) +#define __Pyx_PyObject_AsWritableSString(s) ((signed char*) __Pyx_PyObject_AsString(s)) +#define __Pyx_PyObject_AsWritableUString(s) ((unsigned char*) __Pyx_PyObject_AsString(s)) +#define __Pyx_PyObject_AsSString(s) ((const signed char*) __Pyx_PyObject_AsString(s)) +#define __Pyx_PyObject_AsUString(s) ((const unsigned char*) __Pyx_PyObject_AsString(s)) +#define __Pyx_PyObject_FromCString(s) __Pyx_PyObject_FromString((const char*)s) +#define __Pyx_PyBytes_FromCString(s) __Pyx_PyBytes_FromString((const char*)s) +#define __Pyx_PyByteArray_FromCString(s) __Pyx_PyByteArray_FromString((const char*)s) +#define __Pyx_PyStr_FromCString(s) __Pyx_PyStr_FromString((const char*)s) +#define __Pyx_PyUnicode_FromCString(s) __Pyx_PyUnicode_FromString((const char*)s) +static CYTHON_INLINE size_t __Pyx_Py_UNICODE_strlen(const Py_UNICODE *u) { + const Py_UNICODE *u_end = u; + while (*u_end++) ; + return (size_t)(u_end - u - 1); +} +#define __Pyx_PyUnicode_FromUnicode(u) PyUnicode_FromUnicode(u, __Pyx_Py_UNICODE_strlen(u)) +#define __Pyx_PyUnicode_FromUnicodeAndLength PyUnicode_FromUnicode +#define __Pyx_PyUnicode_AsUnicode PyUnicode_AsUnicode +#define __Pyx_NewRef(obj) (Py_INCREF(obj), obj) +#define __Pyx_Owned_Py_None(b) __Pyx_NewRef(Py_None) +static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b); +static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*); +static CYTHON_INLINE int __Pyx_PyObject_IsTrueAndDecref(PyObject*); +static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x); +#define __Pyx_PySequence_Tuple(obj)\ + (likely(PyTuple_CheckExact(obj)) ? __Pyx_NewRef(obj) : PySequence_Tuple(obj)) +static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*); +static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t); +#if CYTHON_ASSUME_SAFE_MACROS +#define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x)) +#else +#define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x) +#endif +#define __pyx_PyFloat_AsFloat(x) ((float) __pyx_PyFloat_AsDouble(x)) +#if PY_MAJOR_VERSION >= 3 +#define __Pyx_PyNumber_Int(x) (PyLong_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Long(x)) +#else +#define __Pyx_PyNumber_Int(x) (PyInt_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Int(x)) +#endif +#define __Pyx_PyNumber_Float(x) (PyFloat_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Float(x)) +#if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII +static int __Pyx_sys_getdefaultencoding_not_ascii; +static int __Pyx_init_sys_getdefaultencoding_params(void) { + PyObject* sys; + PyObject* default_encoding = NULL; + PyObject* ascii_chars_u = NULL; + PyObject* ascii_chars_b = NULL; + const char* default_encoding_c; + sys = PyImport_ImportModule("sys"); + if (!sys) goto bad; + default_encoding = PyObject_CallMethod(sys, (char*) "getdefaultencoding", NULL); + Py_DECREF(sys); + if (!default_encoding) goto bad; + default_encoding_c = PyBytes_AsString(default_encoding); + if (!default_encoding_c) goto bad; + if (strcmp(default_encoding_c, "ascii") == 0) { + __Pyx_sys_getdefaultencoding_not_ascii = 0; + } else { + char ascii_chars[128]; + int c; + for (c = 0; c < 128; c++) { + ascii_chars[c] = c; + } + __Pyx_sys_getdefaultencoding_not_ascii = 1; + ascii_chars_u = PyUnicode_DecodeASCII(ascii_chars, 128, NULL); + if (!ascii_chars_u) goto bad; + ascii_chars_b = PyUnicode_AsEncodedString(ascii_chars_u, default_encoding_c, NULL); + if (!ascii_chars_b || !PyBytes_Check(ascii_chars_b) || memcmp(ascii_chars, PyBytes_AS_STRING(ascii_chars_b), 128) != 0) { + PyErr_Format( + PyExc_ValueError, + "This module compiled with c_string_encoding=ascii, but default encoding '%.200s' is not a superset of ascii.", + default_encoding_c); + goto bad; + } + Py_DECREF(ascii_chars_u); + Py_DECREF(ascii_chars_b); + } + Py_DECREF(default_encoding); + return 0; +bad: + Py_XDECREF(default_encoding); + Py_XDECREF(ascii_chars_u); + Py_XDECREF(ascii_chars_b); + return -1; +} +#endif +#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT && PY_MAJOR_VERSION >= 3 +#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_DecodeUTF8(c_str, size, NULL) +#else +#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_Decode(c_str, size, __PYX_DEFAULT_STRING_ENCODING, NULL) +#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT +static char* __PYX_DEFAULT_STRING_ENCODING; +static int __Pyx_init_sys_getdefaultencoding_params(void) { + PyObject* sys; + PyObject* default_encoding = NULL; + char* default_encoding_c; + sys = PyImport_ImportModule("sys"); + if (!sys) goto bad; + default_encoding = PyObject_CallMethod(sys, (char*) (const char*) "getdefaultencoding", NULL); + Py_DECREF(sys); + if (!default_encoding) goto bad; + default_encoding_c = PyBytes_AsString(default_encoding); + if (!default_encoding_c) goto bad; + __PYX_DEFAULT_STRING_ENCODING = (char*) malloc(strlen(default_encoding_c) + 1); + if (!__PYX_DEFAULT_STRING_ENCODING) goto bad; + strcpy(__PYX_DEFAULT_STRING_ENCODING, default_encoding_c); + Py_DECREF(default_encoding); + return 0; +bad: + Py_XDECREF(default_encoding); + return -1; +} +#endif +#endif + + +/* Test for GCC > 2.95 */ +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))) + #define likely(x) __builtin_expect(!!(x), 1) + #define unlikely(x) __builtin_expect(!!(x), 0) +#else /* !__GNUC__ or GCC < 2.95 */ + #define likely(x) (x) + #define unlikely(x) (x) +#endif /* __GNUC__ */ +static CYTHON_INLINE void __Pyx_pretend_to_initialize(void* ptr) { (void)ptr; } + +static PyObject *__pyx_m = NULL; +static PyObject *__pyx_d; +static PyObject *__pyx_b; +static PyObject *__pyx_cython_runtime = NULL; +static PyObject *__pyx_empty_tuple; +static PyObject *__pyx_empty_bytes; +static PyObject *__pyx_empty_unicode; +static int __pyx_lineno; +static int __pyx_clineno = 0; +static const char * __pyx_cfilenm= __FILE__; +static const char *__pyx_filename; + +/* Header.proto */ +#if !defined(CYTHON_CCOMPLEX) + #if defined(__cplusplus) + #define CYTHON_CCOMPLEX 1 + #elif defined(_Complex_I) + #define CYTHON_CCOMPLEX 1 + #else + #define CYTHON_CCOMPLEX 0 + #endif +#endif +#if CYTHON_CCOMPLEX + #ifdef __cplusplus + #include + #else + #include + #endif +#endif +#if CYTHON_CCOMPLEX && !defined(__cplusplus) && defined(__sun__) && defined(__GNUC__) + #undef _Complex_I + #define _Complex_I 1.0fj +#endif + + +static const char *__pyx_f[] = { + "knn.pyx", + "__init__.pxd", + "type.pxd", +}; +/* BufferFormatStructs.proto */ +#define IS_UNSIGNED(type) (((type) -1) > 0) +struct __Pyx_StructField_; +#define __PYX_BUF_FLAGS_PACKED_STRUCT (1 << 0) +typedef struct { + const char* name; + struct __Pyx_StructField_* fields; + size_t size; + size_t arraysize[8]; + int ndim; + char typegroup; + char is_unsigned; + int flags; +} __Pyx_TypeInfo; +typedef struct __Pyx_StructField_ { + __Pyx_TypeInfo* type; + const char* name; + size_t offset; +} __Pyx_StructField; +typedef struct { + __Pyx_StructField* field; + size_t parent_offset; +} __Pyx_BufFmt_StackElem; +typedef struct { + __Pyx_StructField root; + __Pyx_BufFmt_StackElem* head; + size_t fmt_offset; + size_t new_count, enc_count; + size_t struct_alignment; + int is_complex; + char enc_type; + char new_packmode; + char enc_packmode; + char is_valid_array; +} __Pyx_BufFmt_Context; + + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":775 + * # in Cython to enable them only on the right systems. + * + * ctypedef npy_int8 int8_t # <<<<<<<<<<<<<< + * ctypedef npy_int16 int16_t + * ctypedef npy_int32 int32_t + */ +typedef npy_int8 __pyx_t_5numpy_int8_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":776 + * + * ctypedef npy_int8 int8_t + * ctypedef npy_int16 int16_t # <<<<<<<<<<<<<< + * ctypedef npy_int32 int32_t + * ctypedef npy_int64 int64_t + */ +typedef npy_int16 __pyx_t_5numpy_int16_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":777 + * ctypedef npy_int8 int8_t + * ctypedef npy_int16 int16_t + * ctypedef npy_int32 int32_t # <<<<<<<<<<<<<< + * ctypedef npy_int64 int64_t + * #ctypedef npy_int96 int96_t + */ +typedef npy_int32 __pyx_t_5numpy_int32_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":778 + * ctypedef npy_int16 int16_t + * ctypedef npy_int32 int32_t + * ctypedef npy_int64 int64_t # <<<<<<<<<<<<<< + * #ctypedef npy_int96 int96_t + * #ctypedef npy_int128 int128_t + */ +typedef npy_int64 __pyx_t_5numpy_int64_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":782 + * #ctypedef npy_int128 int128_t + * + * ctypedef npy_uint8 uint8_t # <<<<<<<<<<<<<< + * ctypedef npy_uint16 uint16_t + * ctypedef npy_uint32 uint32_t + */ +typedef npy_uint8 __pyx_t_5numpy_uint8_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":783 + * + * ctypedef npy_uint8 uint8_t + * ctypedef npy_uint16 uint16_t # <<<<<<<<<<<<<< + * ctypedef npy_uint32 uint32_t + * ctypedef npy_uint64 uint64_t + */ +typedef npy_uint16 __pyx_t_5numpy_uint16_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":784 + * ctypedef npy_uint8 uint8_t + * ctypedef npy_uint16 uint16_t + * ctypedef npy_uint32 uint32_t # <<<<<<<<<<<<<< + * ctypedef npy_uint64 uint64_t + * #ctypedef npy_uint96 uint96_t + */ +typedef npy_uint32 __pyx_t_5numpy_uint32_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":785 + * ctypedef npy_uint16 uint16_t + * ctypedef npy_uint32 uint32_t + * ctypedef npy_uint64 uint64_t # <<<<<<<<<<<<<< + * #ctypedef npy_uint96 uint96_t + * #ctypedef npy_uint128 uint128_t + */ +typedef npy_uint64 __pyx_t_5numpy_uint64_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":789 + * #ctypedef npy_uint128 uint128_t + * + * ctypedef npy_float32 float32_t # <<<<<<<<<<<<<< + * ctypedef npy_float64 float64_t + * #ctypedef npy_float80 float80_t + */ +typedef npy_float32 __pyx_t_5numpy_float32_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":790 + * + * ctypedef npy_float32 float32_t + * ctypedef npy_float64 float64_t # <<<<<<<<<<<<<< + * #ctypedef npy_float80 float80_t + * #ctypedef npy_float128 float128_t + */ +typedef npy_float64 __pyx_t_5numpy_float64_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":799 + * # The int types are mapped a bit surprising -- + * # numpy.int corresponds to 'l' and numpy.long to 'q' + * ctypedef npy_long int_t # <<<<<<<<<<<<<< + * ctypedef npy_longlong long_t + * ctypedef npy_longlong longlong_t + */ +typedef npy_long __pyx_t_5numpy_int_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":800 + * # numpy.int corresponds to 'l' and numpy.long to 'q' + * ctypedef npy_long int_t + * ctypedef npy_longlong long_t # <<<<<<<<<<<<<< + * ctypedef npy_longlong longlong_t + * + */ +typedef npy_longlong __pyx_t_5numpy_long_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":801 + * ctypedef npy_long int_t + * ctypedef npy_longlong long_t + * ctypedef npy_longlong longlong_t # <<<<<<<<<<<<<< + * + * ctypedef npy_ulong uint_t + */ +typedef npy_longlong __pyx_t_5numpy_longlong_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":803 + * ctypedef npy_longlong longlong_t + * + * ctypedef npy_ulong uint_t # <<<<<<<<<<<<<< + * ctypedef npy_ulonglong ulong_t + * ctypedef npy_ulonglong ulonglong_t + */ +typedef npy_ulong __pyx_t_5numpy_uint_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":804 + * + * ctypedef npy_ulong uint_t + * ctypedef npy_ulonglong ulong_t # <<<<<<<<<<<<<< + * ctypedef npy_ulonglong ulonglong_t + * + */ +typedef npy_ulonglong __pyx_t_5numpy_ulong_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":805 + * ctypedef npy_ulong uint_t + * ctypedef npy_ulonglong ulong_t + * ctypedef npy_ulonglong ulonglong_t # <<<<<<<<<<<<<< + * + * ctypedef npy_intp intp_t + */ +typedef npy_ulonglong __pyx_t_5numpy_ulonglong_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":807 + * ctypedef npy_ulonglong ulonglong_t + * + * ctypedef npy_intp intp_t # <<<<<<<<<<<<<< + * ctypedef npy_uintp uintp_t + * + */ +typedef npy_intp __pyx_t_5numpy_intp_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":808 + * + * ctypedef npy_intp intp_t + * ctypedef npy_uintp uintp_t # <<<<<<<<<<<<<< + * + * ctypedef npy_double float_t + */ +typedef npy_uintp __pyx_t_5numpy_uintp_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":810 + * ctypedef npy_uintp uintp_t + * + * ctypedef npy_double float_t # <<<<<<<<<<<<<< + * ctypedef npy_double double_t + * ctypedef npy_longdouble longdouble_t + */ +typedef npy_double __pyx_t_5numpy_float_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":811 + * + * ctypedef npy_double float_t + * ctypedef npy_double double_t # <<<<<<<<<<<<<< + * ctypedef npy_longdouble longdouble_t + * + */ +typedef npy_double __pyx_t_5numpy_double_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":812 + * ctypedef npy_double float_t + * ctypedef npy_double double_t + * ctypedef npy_longdouble longdouble_t # <<<<<<<<<<<<<< + * + * ctypedef npy_cfloat cfloat_t + */ +typedef npy_longdouble __pyx_t_5numpy_longdouble_t; +/* Declarations.proto */ +#if CYTHON_CCOMPLEX + #ifdef __cplusplus + typedef ::std::complex< float > __pyx_t_float_complex; + #else + typedef float _Complex __pyx_t_float_complex; + #endif +#else + typedef struct { float real, imag; } __pyx_t_float_complex; +#endif +static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float, float); + +/* Declarations.proto */ +#if CYTHON_CCOMPLEX + #ifdef __cplusplus + typedef ::std::complex< double > __pyx_t_double_complex; + #else + typedef double _Complex __pyx_t_double_complex; + #endif +#else + typedef struct { double real, imag; } __pyx_t_double_complex; +#endif +static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double, double); + + +/*--- Type declarations ---*/ + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":814 + * ctypedef npy_longdouble longdouble_t + * + * ctypedef npy_cfloat cfloat_t # <<<<<<<<<<<<<< + * ctypedef npy_cdouble cdouble_t + * ctypedef npy_clongdouble clongdouble_t + */ +typedef npy_cfloat __pyx_t_5numpy_cfloat_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":815 + * + * ctypedef npy_cfloat cfloat_t + * ctypedef npy_cdouble cdouble_t # <<<<<<<<<<<<<< + * ctypedef npy_clongdouble clongdouble_t + * + */ +typedef npy_cdouble __pyx_t_5numpy_cdouble_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":816 + * ctypedef npy_cfloat cfloat_t + * ctypedef npy_cdouble cdouble_t + * ctypedef npy_clongdouble clongdouble_t # <<<<<<<<<<<<<< + * + * ctypedef npy_cdouble complex_t + */ +typedef npy_clongdouble __pyx_t_5numpy_clongdouble_t; + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":818 + * ctypedef npy_clongdouble clongdouble_t + * + * ctypedef npy_cdouble complex_t # <<<<<<<<<<<<<< + * + * cdef inline object PyArray_MultiIterNew1(a): + */ +typedef npy_cdouble __pyx_t_5numpy_complex_t; + +/* --- Runtime support code (head) --- */ +/* Refnanny.proto */ +#ifndef CYTHON_REFNANNY + #define CYTHON_REFNANNY 0 +#endif +#if CYTHON_REFNANNY + typedef struct { + void (*INCREF)(void*, PyObject*, int); + void (*DECREF)(void*, PyObject*, int); + void (*GOTREF)(void*, PyObject*, int); + void (*GIVEREF)(void*, PyObject*, int); + void* (*SetupContext)(const char*, int, const char*); + void (*FinishContext)(void**); + } __Pyx_RefNannyAPIStruct; + static __Pyx_RefNannyAPIStruct *__Pyx_RefNanny = NULL; + static __Pyx_RefNannyAPIStruct *__Pyx_RefNannyImportAPI(const char *modname); + #define __Pyx_RefNannyDeclarations void *__pyx_refnanny = NULL; +#ifdef WITH_THREAD + #define __Pyx_RefNannySetupContext(name, acquire_gil)\ + if (acquire_gil) {\ + PyGILState_STATE __pyx_gilstate_save = PyGILState_Ensure();\ + __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\ + PyGILState_Release(__pyx_gilstate_save);\ + } else {\ + __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\ + } +#else + #define __Pyx_RefNannySetupContext(name, acquire_gil)\ + __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__) +#endif + #define __Pyx_RefNannyFinishContext()\ + __Pyx_RefNanny->FinishContext(&__pyx_refnanny) + #define __Pyx_INCREF(r) __Pyx_RefNanny->INCREF(__pyx_refnanny, (PyObject *)(r), __LINE__) + #define __Pyx_DECREF(r) __Pyx_RefNanny->DECREF(__pyx_refnanny, (PyObject *)(r), __LINE__) + #define __Pyx_GOTREF(r) __Pyx_RefNanny->GOTREF(__pyx_refnanny, (PyObject *)(r), __LINE__) + #define __Pyx_GIVEREF(r) __Pyx_RefNanny->GIVEREF(__pyx_refnanny, (PyObject *)(r), __LINE__) + #define __Pyx_XINCREF(r) do { if((r) != NULL) {__Pyx_INCREF(r); }} while(0) + #define __Pyx_XDECREF(r) do { if((r) != NULL) {__Pyx_DECREF(r); }} while(0) + #define __Pyx_XGOTREF(r) do { if((r) != NULL) {__Pyx_GOTREF(r); }} while(0) + #define __Pyx_XGIVEREF(r) do { if((r) != NULL) {__Pyx_GIVEREF(r);}} while(0) +#else + #define __Pyx_RefNannyDeclarations + #define __Pyx_RefNannySetupContext(name, acquire_gil) + #define __Pyx_RefNannyFinishContext() + #define __Pyx_INCREF(r) Py_INCREF(r) + #define __Pyx_DECREF(r) Py_DECREF(r) + #define __Pyx_GOTREF(r) + #define __Pyx_GIVEREF(r) + #define __Pyx_XINCREF(r) Py_XINCREF(r) + #define __Pyx_XDECREF(r) Py_XDECREF(r) + #define __Pyx_XGOTREF(r) + #define __Pyx_XGIVEREF(r) +#endif +#define __Pyx_XDECREF_SET(r, v) do {\ + PyObject *tmp = (PyObject *) r;\ + r = v; __Pyx_XDECREF(tmp);\ + } while (0) +#define __Pyx_DECREF_SET(r, v) do {\ + PyObject *tmp = (PyObject *) r;\ + r = v; __Pyx_DECREF(tmp);\ + } while (0) +#define __Pyx_CLEAR(r) do { PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);} while(0) +#define __Pyx_XCLEAR(r) do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0) + +/* RaiseArgTupleInvalid.proto */ +static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact, + Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found); + +/* RaiseDoubleKeywords.proto */ +static void __Pyx_RaiseDoubleKeywordsError(const char* func_name, PyObject* kw_name); + +/* ParseKeywords.proto */ +static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\ + PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args,\ + const char* function_name); + +/* PyObjectGetAttrStr.proto */ +#if CYTHON_USE_TYPE_SLOTS +static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name); +#else +#define __Pyx_PyObject_GetAttrStr(o,n) PyObject_GetAttr(o,n) +#endif + +/* GetItemInt.proto */ +#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ + (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ + __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) :\ + (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) :\ + __Pyx_GetItemInt_Generic(o, to_py_func(i)))) +#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ + (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ + __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\ + (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL)) +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i, + int wraparound, int boundscheck); +#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\ + (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\ + __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\ + (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL)) +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i, + int wraparound, int boundscheck); +static PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j); +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, + int is_list, int wraparound, int boundscheck); + +/* GetBuiltinName.proto */ +static PyObject *__Pyx_GetBuiltinName(PyObject *name); + +/* PyDictVersioning.proto */ +#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_TYPE_SLOTS +#define __PYX_DICT_VERSION_INIT ((PY_UINT64_T) -1) +#define __PYX_GET_DICT_VERSION(dict) (((PyDictObject*)(dict))->ma_version_tag) +#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var)\ + (version_var) = __PYX_GET_DICT_VERSION(dict);\ + (cache_var) = (value); +#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) {\ + static PY_UINT64_T __pyx_dict_version = 0;\ + static PyObject *__pyx_dict_cached_value = NULL;\ + if (likely(__PYX_GET_DICT_VERSION(DICT) == __pyx_dict_version)) {\ + (VAR) = __pyx_dict_cached_value;\ + } else {\ + (VAR) = __pyx_dict_cached_value = (LOOKUP);\ + __pyx_dict_version = __PYX_GET_DICT_VERSION(DICT);\ + }\ +} +static CYTHON_INLINE PY_UINT64_T __Pyx_get_tp_dict_version(PyObject *obj); +static CYTHON_INLINE PY_UINT64_T __Pyx_get_object_dict_version(PyObject *obj); +static CYTHON_INLINE int __Pyx_object_dict_version_matches(PyObject* obj, PY_UINT64_T tp_dict_version, PY_UINT64_T obj_dict_version); +#else +#define __PYX_GET_DICT_VERSION(dict) (0) +#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var) +#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) (VAR) = (LOOKUP); +#endif + +/* GetModuleGlobalName.proto */ +#if CYTHON_USE_DICT_VERSIONS +#define __Pyx_GetModuleGlobalName(var, name) {\ + static PY_UINT64_T __pyx_dict_version = 0;\ + static PyObject *__pyx_dict_cached_value = NULL;\ + (var) = (likely(__pyx_dict_version == __PYX_GET_DICT_VERSION(__pyx_d))) ?\ + (likely(__pyx_dict_cached_value) ? __Pyx_NewRef(__pyx_dict_cached_value) : __Pyx_GetBuiltinName(name)) :\ + __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ +} +#define __Pyx_GetModuleGlobalNameUncached(var, name) {\ + PY_UINT64_T __pyx_dict_version;\ + PyObject *__pyx_dict_cached_value;\ + (var) = __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ +} +static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value); +#else +#define __Pyx_GetModuleGlobalName(var, name) (var) = __Pyx__GetModuleGlobalName(name) +#define __Pyx_GetModuleGlobalNameUncached(var, name) (var) = __Pyx__GetModuleGlobalName(name) +static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name); +#endif + +/* PyObjectCall.proto */ +#if CYTHON_COMPILING_IN_CPYTHON +static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw); +#else +#define __Pyx_PyObject_Call(func, arg, kw) PyObject_Call(func, arg, kw) +#endif + +/* ExtTypeTest.proto */ +static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type); + +/* IsLittleEndian.proto */ +static CYTHON_INLINE int __Pyx_Is_Little_Endian(void); + +/* BufferFormatCheck.proto */ +static const char* __Pyx_BufFmt_CheckString(__Pyx_BufFmt_Context* ctx, const char* ts); +static void __Pyx_BufFmt_Init(__Pyx_BufFmt_Context* ctx, + __Pyx_BufFmt_StackElem* stack, + __Pyx_TypeInfo* type); + +/* BufferGetAndValidate.proto */ +#define __Pyx_GetBufferAndValidate(buf, obj, dtype, flags, nd, cast, stack)\ + ((obj == Py_None || obj == NULL) ?\ + (__Pyx_ZeroBuffer(buf), 0) :\ + __Pyx__GetBufferAndValidate(buf, obj, dtype, flags, nd, cast, stack)) +static int __Pyx__GetBufferAndValidate(Py_buffer* buf, PyObject* obj, + __Pyx_TypeInfo* dtype, int flags, int nd, int cast, __Pyx_BufFmt_StackElem* stack); +static void __Pyx_ZeroBuffer(Py_buffer* buf); +static CYTHON_INLINE void __Pyx_SafeReleaseBuffer(Py_buffer* info); +static Py_ssize_t __Pyx_minusones[] = { -1, -1, -1, -1, -1, -1, -1, -1 }; +static Py_ssize_t __Pyx_zeros[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +/* BufferFallbackError.proto */ +static void __Pyx_RaiseBufferFallbackError(void); + +/* PyThreadStateGet.proto */ +#if CYTHON_FAST_THREAD_STATE +#define __Pyx_PyThreadState_declare PyThreadState *__pyx_tstate; +#define __Pyx_PyThreadState_assign __pyx_tstate = __Pyx_PyThreadState_Current; +#define __Pyx_PyErr_Occurred() __pyx_tstate->curexc_type +#else +#define __Pyx_PyThreadState_declare +#define __Pyx_PyThreadState_assign +#define __Pyx_PyErr_Occurred() PyErr_Occurred() +#endif + +/* PyErrFetchRestore.proto */ +#if CYTHON_FAST_THREAD_STATE +#define __Pyx_PyErr_Clear() __Pyx_ErrRestore(NULL, NULL, NULL) +#define __Pyx_ErrRestoreWithState(type, value, tb) __Pyx_ErrRestoreInState(PyThreadState_GET(), type, value, tb) +#define __Pyx_ErrFetchWithState(type, value, tb) __Pyx_ErrFetchInState(PyThreadState_GET(), type, value, tb) +#define __Pyx_ErrRestore(type, value, tb) __Pyx_ErrRestoreInState(__pyx_tstate, type, value, tb) +#define __Pyx_ErrFetch(type, value, tb) __Pyx_ErrFetchInState(__pyx_tstate, type, value, tb) +static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb); +static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); +#if CYTHON_COMPILING_IN_CPYTHON +#define __Pyx_PyErr_SetNone(exc) (Py_INCREF(exc), __Pyx_ErrRestore((exc), NULL, NULL)) +#else +#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc) +#endif +#else +#define __Pyx_PyErr_Clear() PyErr_Clear() +#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc) +#define __Pyx_ErrRestoreWithState(type, value, tb) PyErr_Restore(type, value, tb) +#define __Pyx_ErrFetchWithState(type, value, tb) PyErr_Fetch(type, value, tb) +#define __Pyx_ErrRestoreInState(tstate, type, value, tb) PyErr_Restore(type, value, tb) +#define __Pyx_ErrFetchInState(tstate, type, value, tb) PyErr_Fetch(type, value, tb) +#define __Pyx_ErrRestore(type, value, tb) PyErr_Restore(type, value, tb) +#define __Pyx_ErrFetch(type, value, tb) PyErr_Fetch(type, value, tb) +#endif + +/* RaiseException.proto */ +static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause); + +/* PyCFunctionFastCall.proto */ +#if CYTHON_FAST_PYCCALL +static CYTHON_INLINE PyObject *__Pyx_PyCFunction_FastCall(PyObject *func, PyObject **args, Py_ssize_t nargs); +#else +#define __Pyx_PyCFunction_FastCall(func, args, nargs) (assert(0), NULL) +#endif + +/* PyFunctionFastCall.proto */ +#if CYTHON_FAST_PYCALL +#define __Pyx_PyFunction_FastCall(func, args, nargs)\ + __Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL) +#if 1 || PY_VERSION_HEX < 0x030600B1 +static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs, PyObject *kwargs); +#else +#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs) +#endif +#define __Pyx_BUILD_ASSERT_EXPR(cond)\ + (sizeof(char [1 - 2*!(cond)]) - 1) +#ifndef Py_MEMBER_SIZE +#define Py_MEMBER_SIZE(type, member) sizeof(((type *)0)->member) +#endif + static size_t __pyx_pyframe_localsplus_offset = 0; + #include "frameobject.h" + #define __Pxy_PyFrame_Initialize_Offsets()\ + ((void)__Pyx_BUILD_ASSERT_EXPR(sizeof(PyFrameObject) == offsetof(PyFrameObject, f_localsplus) + Py_MEMBER_SIZE(PyFrameObject, f_localsplus)),\ + (void)(__pyx_pyframe_localsplus_offset = ((size_t)PyFrame_Type.tp_basicsize) - Py_MEMBER_SIZE(PyFrameObject, f_localsplus))) + #define __Pyx_PyFrame_GetLocalsplus(frame)\ + (assert(__pyx_pyframe_localsplus_offset), (PyObject **)(((char *)(frame)) + __pyx_pyframe_localsplus_offset)) +#endif + +/* PyObjectCallMethO.proto */ +#if CYTHON_COMPILING_IN_CPYTHON +static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg); +#endif + +/* PyObjectCallOneArg.proto */ +static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg); + +/* DictGetItem.proto */ +#if PY_MAJOR_VERSION >= 3 && !CYTHON_COMPILING_IN_PYPY +static PyObject *__Pyx_PyDict_GetItem(PyObject *d, PyObject* key); +#define __Pyx_PyObject_Dict_GetItem(obj, name)\ + (likely(PyDict_CheckExact(obj)) ?\ + __Pyx_PyDict_GetItem(obj, name) : PyObject_GetItem(obj, name)) +#else +#define __Pyx_PyDict_GetItem(d, key) PyObject_GetItem(d, key) +#define __Pyx_PyObject_Dict_GetItem(obj, name) PyObject_GetItem(obj, name) +#endif + +/* RaiseTooManyValuesToUnpack.proto */ +static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected); + +/* RaiseNeedMoreValuesToUnpack.proto */ +static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index); + +/* RaiseNoneIterError.proto */ +static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void); + +/* GetTopmostException.proto */ +#if CYTHON_USE_EXC_INFO_STACK +static _PyErr_StackItem * __Pyx_PyErr_GetTopmostException(PyThreadState *tstate); +#endif + +/* SaveResetException.proto */ +#if CYTHON_FAST_THREAD_STATE +#define __Pyx_ExceptionSave(type, value, tb) __Pyx__ExceptionSave(__pyx_tstate, type, value, tb) +static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); +#define __Pyx_ExceptionReset(type, value, tb) __Pyx__ExceptionReset(__pyx_tstate, type, value, tb) +static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb); +#else +#define __Pyx_ExceptionSave(type, value, tb) PyErr_GetExcInfo(type, value, tb) +#define __Pyx_ExceptionReset(type, value, tb) PyErr_SetExcInfo(type, value, tb) +#endif + +/* PyErrExceptionMatches.proto */ +#if CYTHON_FAST_THREAD_STATE +#define __Pyx_PyErr_ExceptionMatches(err) __Pyx_PyErr_ExceptionMatchesInState(__pyx_tstate, err) +static CYTHON_INLINE int __Pyx_PyErr_ExceptionMatchesInState(PyThreadState* tstate, PyObject* err); +#else +#define __Pyx_PyErr_ExceptionMatches(err) PyErr_ExceptionMatches(err) +#endif + +/* GetException.proto */ +#if CYTHON_FAST_THREAD_STATE +#define __Pyx_GetException(type, value, tb) __Pyx__GetException(__pyx_tstate, type, value, tb) +static int __Pyx__GetException(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb); +#else +static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb); +#endif + +/* TypeImport.proto */ +#ifndef __PYX_HAVE_RT_ImportType_proto +#define __PYX_HAVE_RT_ImportType_proto +enum __Pyx_ImportType_CheckSize { + __Pyx_ImportType_CheckSize_Error = 0, + __Pyx_ImportType_CheckSize_Warn = 1, + __Pyx_ImportType_CheckSize_Ignore = 2 +}; +static PyTypeObject *__Pyx_ImportType(PyObject* module, const char *module_name, const char *class_name, size_t size, enum __Pyx_ImportType_CheckSize check_size); +#endif + +/* Import.proto */ +static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level); + +/* CLineInTraceback.proto */ +#ifdef CYTHON_CLINE_IN_TRACEBACK +#define __Pyx_CLineForTraceback(tstate, c_line) (((CYTHON_CLINE_IN_TRACEBACK)) ? c_line : 0) +#else +static int __Pyx_CLineForTraceback(PyThreadState *tstate, int c_line); +#endif + +/* CodeObjectCache.proto */ +typedef struct { + PyCodeObject* code_object; + int code_line; +} __Pyx_CodeObjectCacheEntry; +struct __Pyx_CodeObjectCache { + int count; + int max_count; + __Pyx_CodeObjectCacheEntry* entries; +}; +static struct __Pyx_CodeObjectCache __pyx_code_cache = {0,0,NULL}; +static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line); +static PyCodeObject *__pyx_find_code_object(int code_line); +static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object); + +/* AddTraceback.proto */ +static void __Pyx_AddTraceback(const char *funcname, int c_line, + int py_line, const char *filename); + +/* BufferStructDeclare.proto */ +typedef struct { + Py_ssize_t shape, strides, suboffsets; +} __Pyx_Buf_DimInfo; +typedef struct { + size_t refcount; + Py_buffer pybuffer; +} __Pyx_Buffer; +typedef struct { + __Pyx_Buffer *rcbuffer; + char *data; + __Pyx_Buf_DimInfo diminfo[8]; +} __Pyx_LocalBuf_ND; + +#if PY_MAJOR_VERSION < 3 + static int __Pyx_GetBuffer(PyObject *obj, Py_buffer *view, int flags); + static void __Pyx_ReleaseBuffer(Py_buffer *view); +#else + #define __Pyx_GetBuffer PyObject_GetBuffer + #define __Pyx_ReleaseBuffer PyBuffer_Release +#endif + + +/* CIntToPy.proto */ +static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value); + +/* CIntToPy.proto */ +static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value); + +/* RealImag.proto */ +#if CYTHON_CCOMPLEX + #ifdef __cplusplus + #define __Pyx_CREAL(z) ((z).real()) + #define __Pyx_CIMAG(z) ((z).imag()) + #else + #define __Pyx_CREAL(z) (__real__(z)) + #define __Pyx_CIMAG(z) (__imag__(z)) + #endif +#else + #define __Pyx_CREAL(z) ((z).real) + #define __Pyx_CIMAG(z) ((z).imag) +#endif +#if defined(__cplusplus) && CYTHON_CCOMPLEX\ + && (defined(_WIN32) || defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 5 || __GNUC__ == 4 && __GNUC_MINOR__ >= 4 )) || __cplusplus >= 201103) + #define __Pyx_SET_CREAL(z,x) ((z).real(x)) + #define __Pyx_SET_CIMAG(z,y) ((z).imag(y)) +#else + #define __Pyx_SET_CREAL(z,x) __Pyx_CREAL(z) = (x) + #define __Pyx_SET_CIMAG(z,y) __Pyx_CIMAG(z) = (y) +#endif + +/* Arithmetic.proto */ +#if CYTHON_CCOMPLEX + #define __Pyx_c_eq_float(a, b) ((a)==(b)) + #define __Pyx_c_sum_float(a, b) ((a)+(b)) + #define __Pyx_c_diff_float(a, b) ((a)-(b)) + #define __Pyx_c_prod_float(a, b) ((a)*(b)) + #define __Pyx_c_quot_float(a, b) ((a)/(b)) + #define __Pyx_c_neg_float(a) (-(a)) + #ifdef __cplusplus + #define __Pyx_c_is_zero_float(z) ((z)==(float)0) + #define __Pyx_c_conj_float(z) (::std::conj(z)) + #if 1 + #define __Pyx_c_abs_float(z) (::std::abs(z)) + #define __Pyx_c_pow_float(a, b) (::std::pow(a, b)) + #endif + #else + #define __Pyx_c_is_zero_float(z) ((z)==0) + #define __Pyx_c_conj_float(z) (conjf(z)) + #if 1 + #define __Pyx_c_abs_float(z) (cabsf(z)) + #define __Pyx_c_pow_float(a, b) (cpowf(a, b)) + #endif + #endif +#else + static CYTHON_INLINE int __Pyx_c_eq_float(__pyx_t_float_complex, __pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_sum_float(__pyx_t_float_complex, __pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_diff_float(__pyx_t_float_complex, __pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_prod_float(__pyx_t_float_complex, __pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex, __pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_neg_float(__pyx_t_float_complex); + static CYTHON_INLINE int __Pyx_c_is_zero_float(__pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_conj_float(__pyx_t_float_complex); + #if 1 + static CYTHON_INLINE float __Pyx_c_abs_float(__pyx_t_float_complex); + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_pow_float(__pyx_t_float_complex, __pyx_t_float_complex); + #endif +#endif + +/* Arithmetic.proto */ +#if CYTHON_CCOMPLEX + #define __Pyx_c_eq_double(a, b) ((a)==(b)) + #define __Pyx_c_sum_double(a, b) ((a)+(b)) + #define __Pyx_c_diff_double(a, b) ((a)-(b)) + #define __Pyx_c_prod_double(a, b) ((a)*(b)) + #define __Pyx_c_quot_double(a, b) ((a)/(b)) + #define __Pyx_c_neg_double(a) (-(a)) + #ifdef __cplusplus + #define __Pyx_c_is_zero_double(z) ((z)==(double)0) + #define __Pyx_c_conj_double(z) (::std::conj(z)) + #if 1 + #define __Pyx_c_abs_double(z) (::std::abs(z)) + #define __Pyx_c_pow_double(a, b) (::std::pow(a, b)) + #endif + #else + #define __Pyx_c_is_zero_double(z) ((z)==0) + #define __Pyx_c_conj_double(z) (conj(z)) + #if 1 + #define __Pyx_c_abs_double(z) (cabs(z)) + #define __Pyx_c_pow_double(a, b) (cpow(a, b)) + #endif + #endif +#else + static CYTHON_INLINE int __Pyx_c_eq_double(__pyx_t_double_complex, __pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_sum_double(__pyx_t_double_complex, __pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_diff_double(__pyx_t_double_complex, __pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_prod_double(__pyx_t_double_complex, __pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex, __pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_neg_double(__pyx_t_double_complex); + static CYTHON_INLINE int __Pyx_c_is_zero_double(__pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_conj_double(__pyx_t_double_complex); + #if 1 + static CYTHON_INLINE double __Pyx_c_abs_double(__pyx_t_double_complex); + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_pow_double(__pyx_t_double_complex, __pyx_t_double_complex); + #endif +#endif + +/* CIntToPy.proto */ +static CYTHON_INLINE PyObject* __Pyx_PyInt_From_enum__NPY_TYPES(enum NPY_TYPES value); + +/* CIntFromPy.proto */ +static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *); + +/* CIntFromPy.proto */ +static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *); + +/* CIntFromPy.proto */ +static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *); + +/* FastTypeChecks.proto */ +#if CYTHON_COMPILING_IN_CPYTHON +#define __Pyx_TypeCheck(obj, type) __Pyx_IsSubtype(Py_TYPE(obj), (PyTypeObject *)type) +static CYTHON_INLINE int __Pyx_IsSubtype(PyTypeObject *a, PyTypeObject *b); +static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches(PyObject *err, PyObject *type); +static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches2(PyObject *err, PyObject *type1, PyObject *type2); +#else +#define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type) +#define __Pyx_PyErr_GivenExceptionMatches(err, type) PyErr_GivenExceptionMatches(err, type) +#define __Pyx_PyErr_GivenExceptionMatches2(err, type1, type2) (PyErr_GivenExceptionMatches(err, type1) || PyErr_GivenExceptionMatches(err, type2)) +#endif +#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception) + +/* CheckBinaryVersion.proto */ +static int __Pyx_check_binary_version(void); + +/* InitStrings.proto */ +static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); + + +/* Module declarations from 'cpython.buffer' */ + +/* Module declarations from 'libc.string' */ + +/* Module declarations from 'libc.stdio' */ + +/* Module declarations from '__builtin__' */ + +/* Module declarations from 'cpython.type' */ +static PyTypeObject *__pyx_ptype_7cpython_4type_type = 0; + +/* Module declarations from 'cpython' */ + +/* Module declarations from 'cpython.object' */ + +/* Module declarations from 'cpython.ref' */ + +/* Module declarations from 'cpython.mem' */ + +/* Module declarations from 'numpy' */ + +/* Module declarations from 'numpy' */ +static PyTypeObject *__pyx_ptype_5numpy_dtype = 0; +static PyTypeObject *__pyx_ptype_5numpy_flatiter = 0; +static PyTypeObject *__pyx_ptype_5numpy_broadcast = 0; +static PyTypeObject *__pyx_ptype_5numpy_ndarray = 0; +static PyTypeObject *__pyx_ptype_5numpy_ufunc = 0; +static CYTHON_INLINE char *__pyx_f_5numpy__util_dtypestring(PyArray_Descr *, char *, char *, int *); /*proto*/ + +/* Module declarations from 'cython' */ + +/* Module declarations from 'nearest_neighbors' */ +static __Pyx_TypeInfo __Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t = { "float32_t", NULL, sizeof(__pyx_t_5numpy_float32_t), { 0 }, 0, 'R', 0, 0 }; +static __Pyx_TypeInfo __Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t = { "int64_t", NULL, sizeof(__pyx_t_5numpy_int64_t), { 0 }, 0, IS_UNSIGNED(__pyx_t_5numpy_int64_t) ? 'U' : 'I', IS_UNSIGNED(__pyx_t_5numpy_int64_t), 0 }; +#define __Pyx_MODULE_NAME "nearest_neighbors" +extern int __pyx_module_is_main_nearest_neighbors; +int __pyx_module_is_main_nearest_neighbors = 0; + +/* Implementation of 'nearest_neighbors' */ +static PyObject *__pyx_builtin_ValueError; +static PyObject *__pyx_builtin_range; +static PyObject *__pyx_builtin_RuntimeError; +static PyObject *__pyx_builtin_ImportError; +static const char __pyx_k_K[] = "K"; +static const char __pyx_k_np[] = "np"; +static const char __pyx_k_dim[] = "dim"; +static const char __pyx_k_knn[] = "knn"; +static const char __pyx_k_omp[] = "omp"; +static const char __pyx_k_pts[] = "pts"; +static const char __pyx_k_long[] = "long"; +static const char __pyx_k_main[] = "__main__"; +static const char __pyx_k_name[] = "__name__"; +static const char __pyx_k_npts[] = "npts"; +static const char __pyx_k_test[] = "__test__"; +static const char __pyx_k_K_cpp[] = "K_cpp"; +static const char __pyx_k_dtype[] = "dtype"; +static const char __pyx_k_int64[] = "int64"; +static const char __pyx_k_numpy[] = "numpy"; +static const char __pyx_k_range[] = "range"; +static const char __pyx_k_shape[] = "shape"; +static const char __pyx_k_zeros[] = "zeros"; +static const char __pyx_k_import[] = "__import__"; +static const char __pyx_k_float32[] = "float32"; +static const char __pyx_k_indices[] = "indices"; +static const char __pyx_k_knn_pyx[] = "knn.pyx"; +static const char __pyx_k_pts_cpp[] = "pts_cpp"; +static const char __pyx_k_queries[] = "queries"; +static const char __pyx_k_nqueries[] = "nqueries"; +static const char __pyx_k_knn_batch[] = "knn_batch"; +static const char __pyx_k_ValueError[] = "ValueError"; +static const char __pyx_k_batch_size[] = "batch_size"; +static const char __pyx_k_ImportError[] = "ImportError"; +static const char __pyx_k_indices_cpp[] = "indices_cpp"; +static const char __pyx_k_queries_cpp[] = "queries_cpp"; +static const char __pyx_k_RuntimeError[] = "RuntimeError"; +static const char __pyx_k_nqueries_cpp[] = "nqueries_cpp"; +static const char __pyx_k_ascontiguousarray[] = "ascontiguousarray"; +static const char __pyx_k_nearest_neighbors[] = "nearest_neighbors"; +static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback"; +static const char __pyx_k_knn_batch_distance_pick[] = "knn_batch_distance_pick"; +static const char __pyx_k_ndarray_is_not_C_contiguous[] = "ndarray is not C contiguous"; +static const char __pyx_k_numpy_core_multiarray_failed_to[] = "numpy.core.multiarray failed to import"; +static const char __pyx_k_unknown_dtype_code_in_numpy_pxd[] = "unknown dtype code in numpy.pxd (%d)"; +static const char __pyx_k_Format_string_allocated_too_shor[] = "Format string allocated too short, see comment in numpy.pxd"; +static const char __pyx_k_Non_native_byte_order_not_suppor[] = "Non-native byte order not supported"; +static const char __pyx_k_ndarray_is_not_Fortran_contiguou[] = "ndarray is not Fortran contiguous"; +static const char __pyx_k_numpy_core_umath_failed_to_impor[] = "numpy.core.umath failed to import"; +static const char __pyx_k_Format_string_allocated_too_shor_2[] = "Format string allocated too short."; +static PyObject *__pyx_kp_u_Format_string_allocated_too_shor; +static PyObject *__pyx_kp_u_Format_string_allocated_too_shor_2; +static PyObject *__pyx_n_s_ImportError; +static PyObject *__pyx_n_s_K; +static PyObject *__pyx_n_s_K_cpp; +static PyObject *__pyx_kp_u_Non_native_byte_order_not_suppor; +static PyObject *__pyx_n_s_RuntimeError; +static PyObject *__pyx_n_s_ValueError; +static PyObject *__pyx_n_s_ascontiguousarray; +static PyObject *__pyx_n_s_batch_size; +static PyObject *__pyx_n_s_cline_in_traceback; +static PyObject *__pyx_n_s_dim; +static PyObject *__pyx_n_s_dtype; +static PyObject *__pyx_n_s_float32; +static PyObject *__pyx_n_s_import; +static PyObject *__pyx_n_s_indices; +static PyObject *__pyx_n_s_indices_cpp; +static PyObject *__pyx_n_s_int64; +static PyObject *__pyx_n_s_knn; +static PyObject *__pyx_n_s_knn_batch; +static PyObject *__pyx_n_s_knn_batch_distance_pick; +static PyObject *__pyx_kp_s_knn_pyx; +static PyObject *__pyx_n_s_long; +static PyObject *__pyx_n_s_main; +static PyObject *__pyx_n_s_name; +static PyObject *__pyx_kp_u_ndarray_is_not_C_contiguous; +static PyObject *__pyx_kp_u_ndarray_is_not_Fortran_contiguou; +static PyObject *__pyx_n_s_nearest_neighbors; +static PyObject *__pyx_n_s_np; +static PyObject *__pyx_n_s_npts; +static PyObject *__pyx_n_s_nqueries; +static PyObject *__pyx_n_s_nqueries_cpp; +static PyObject *__pyx_n_s_numpy; +static PyObject *__pyx_kp_s_numpy_core_multiarray_failed_to; +static PyObject *__pyx_kp_s_numpy_core_umath_failed_to_impor; +static PyObject *__pyx_n_s_omp; +static PyObject *__pyx_n_s_pts; +static PyObject *__pyx_n_s_pts_cpp; +static PyObject *__pyx_n_s_queries; +static PyObject *__pyx_n_s_queries_cpp; +static PyObject *__pyx_n_s_range; +static PyObject *__pyx_n_s_shape; +static PyObject *__pyx_n_s_test; +static PyObject *__pyx_kp_u_unknown_dtype_code_in_numpy_pxd; +static PyObject *__pyx_n_s_zeros; +static PyObject *__pyx_pf_17nearest_neighbors_knn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_queries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp); /* proto */ +static PyObject *__pyx_pf_17nearest_neighbors_2knn_batch(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_queries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp); /* proto */ +static PyObject *__pyx_pf_17nearest_neighbors_4knn_batch_distance_pick(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_nqueries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp); /* proto */ +static int __pyx_pf_5numpy_7ndarray___getbuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /* proto */ +static void __pyx_pf_5numpy_7ndarray_2__releasebuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info); /* proto */ +static PyObject *__pyx_tuple_; +static PyObject *__pyx_tuple__2; +static PyObject *__pyx_tuple__3; +static PyObject *__pyx_tuple__4; +static PyObject *__pyx_tuple__5; +static PyObject *__pyx_tuple__6; +static PyObject *__pyx_tuple__7; +static PyObject *__pyx_tuple__8; +static PyObject *__pyx_tuple__10; +static PyObject *__pyx_tuple__12; +static PyObject *__pyx_codeobj__9; +static PyObject *__pyx_codeobj__11; +static PyObject *__pyx_codeobj__13; +/* Late includes */ + +/* "knn.pyx":33 + * const size_t K, long* batch_indices) + * + * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + +/* Python wrapper */ +static PyObject *__pyx_pw_17nearest_neighbors_1knn(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_17nearest_neighbors_1knn = {"knn", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_17nearest_neighbors_1knn, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_17nearest_neighbors_1knn(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { + PyObject *__pyx_v_pts = 0; + PyObject *__pyx_v_queries = 0; + PyObject *__pyx_v_K = 0; + PyObject *__pyx_v_omp = 0; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + PyObject *__pyx_r = 0; + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("knn (wrapper)", 0); + { + static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_pts,&__pyx_n_s_queries,&__pyx_n_s_K,&__pyx_n_s_omp,0}; + PyObject* values[4] = {0,0,0,0}; + values[3] = ((PyObject *)Py_False); + if (unlikely(__pyx_kwds)) { + Py_ssize_t kw_args; + const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args); + switch (pos_args) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + CYTHON_FALLTHROUGH; + case 2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + CYTHON_FALLTHROUGH; + case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + CYTHON_FALLTHROUGH; + case 0: break; + default: goto __pyx_L5_argtuple_error; + } + kw_args = PyDict_Size(__pyx_kwds); + switch (pos_args) { + case 0: + if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_pts)) != 0)) kw_args--; + else goto __pyx_L5_argtuple_error; + CYTHON_FALLTHROUGH; + case 1: + if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_queries)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn", 0, 3, 4, 1); __PYX_ERR(0, 33, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 2: + if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_K)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn", 0, 3, 4, 2); __PYX_ERR(0, 33, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 3: + if (kw_args > 0) { + PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_omp); + if (value) { values[3] = value; kw_args--; } + } + } + if (unlikely(kw_args > 0)) { + if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "knn") < 0)) __PYX_ERR(0, 33, __pyx_L3_error) + } + } else { + switch (PyTuple_GET_SIZE(__pyx_args)) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + break; + default: goto __pyx_L5_argtuple_error; + } + } + __pyx_v_pts = values[0]; + __pyx_v_queries = values[1]; + __pyx_v_K = values[2]; + __pyx_v_omp = values[3]; + } + goto __pyx_L4_argument_unpacking_done; + __pyx_L5_argtuple_error:; + __Pyx_RaiseArgtupleInvalid("knn", 0, 3, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 33, __pyx_L3_error) + __pyx_L3_error:; + __Pyx_AddTraceback("nearest_neighbors.knn", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_RefNannyFinishContext(); + return NULL; + __pyx_L4_argument_unpacking_done:; + __pyx_r = __pyx_pf_17nearest_neighbors_knn(__pyx_self, __pyx_v_pts, __pyx_v_queries, __pyx_v_K, __pyx_v_omp); + + /* function exit code */ + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +static PyObject *__pyx_pf_17nearest_neighbors_knn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_queries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp) { + int __pyx_v_npts; + int __pyx_v_dim; + int __pyx_v_K_cpp; + int __pyx_v_nqueries; + PyArrayObject *__pyx_v_pts_cpp = 0; + PyArrayObject *__pyx_v_queries_cpp = 0; + PyArrayObject *__pyx_v_indices_cpp = 0; + PyObject *__pyx_v_indices = NULL; + __Pyx_LocalBuf_ND __pyx_pybuffernd_indices_cpp; + __Pyx_Buffer __pyx_pybuffer_indices_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_pts_cpp; + __Pyx_Buffer __pyx_pybuffer_pts_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_queries_cpp; + __Pyx_Buffer __pyx_pybuffer_queries_cpp; + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + int __pyx_t_3; + PyObject *__pyx_t_4 = NULL; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyArrayObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + PyObject *__pyx_t_9 = NULL; + PyObject *__pyx_t_10 = NULL; + PyArrayObject *__pyx_t_11 = NULL; + int __pyx_t_12; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("knn", 0); + __pyx_pybuffer_pts_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_pts_cpp.refcount = 0; + __pyx_pybuffernd_pts_cpp.data = NULL; + __pyx_pybuffernd_pts_cpp.rcbuffer = &__pyx_pybuffer_pts_cpp; + __pyx_pybuffer_queries_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_queries_cpp.refcount = 0; + __pyx_pybuffernd_queries_cpp.data = NULL; + __pyx_pybuffernd_queries_cpp.rcbuffer = &__pyx_pybuffer_queries_cpp; + __pyx_pybuffer_indices_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_indices_cpp.refcount = 0; + __pyx_pybuffernd_indices_cpp.data = NULL; + __pyx_pybuffernd_indices_cpp.rcbuffer = &__pyx_pybuffer_indices_cpp; + + /* "knn.pyx":47 + * + * # set shape values + * npts = pts.shape[0] # <<<<<<<<<<<<<< + * nqueries = queries.shape[0] + * dim = pts.shape[1] + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 47, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 47, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 47, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_npts = __pyx_t_3; + + /* "knn.pyx":48 + * # set shape values + * npts = pts.shape[0] + * nqueries = queries.shape[0] # <<<<<<<<<<<<<< + * dim = pts.shape[1] + * K_cpp = K + */ + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_queries, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 48, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_GetItemInt(__pyx_t_2, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 48, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 48, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_nqueries = __pyx_t_3; + + /* "knn.pyx":49 + * npts = pts.shape[0] + * nqueries = queries.shape[0] + * dim = pts.shape[1] # <<<<<<<<<<<<<< + * K_cpp = K + * + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 49, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 49, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 49, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_dim = __pyx_t_3; + + /* "knn.pyx":50 + * nqueries = queries.shape[0] + * dim = pts.shape[1] + * K_cpp = K # <<<<<<<<<<<<<< + * + * # create indices tensor + */ + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_K); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 50, __pyx_L1_error) + __pyx_v_K_cpp = __pyx_t_3; + + /* "knn.pyx":53 + * + * # create indices tensor + * indices = np.zeros((queries.shape[0], K), dtype=np.int64) # <<<<<<<<<<<<<< + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + */ + __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_zeros); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_queries, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_4 = __Pyx_GetItemInt(__pyx_t_2, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_GIVEREF(__pyx_t_4); + PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_4); + __Pyx_INCREF(__pyx_v_K); + __Pyx_GIVEREF(__pyx_v_K); + PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_v_K); + __pyx_t_4 = 0; + __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_GIVEREF(__pyx_t_2); + PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_2); + __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_int64); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + if (PyDict_SetItem(__pyx_t_2, __pyx_n_s_dtype, __pyx_t_6) < 0) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_4, __pyx_t_2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 53, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_indices = __pyx_t_6; + __pyx_t_6 = 0; + + /* "knn.pyx":55 + * indices = np.zeros((queries.shape[0], K), dtype=np.int64) + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) # <<<<<<<<<<<<<< + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices + */ + __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = PyTuple_New(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_INCREF(__pyx_v_pts); + __Pyx_GIVEREF(__pyx_v_pts); + PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_pts); + __pyx_t_4 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_float32); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_dtype, __pyx_t_5) < 0) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_6, __pyx_t_4); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 55, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (!(likely(((__pyx_t_5) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_5, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 55, __pyx_L1_error) + __pyx_t_7 = ((PyArrayObject *)__pyx_t_5); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_7, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_pts_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_pts_cpp.diminfo[0].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_pts_cpp.diminfo[0].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_pts_cpp.diminfo[1].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_pts_cpp.diminfo[1].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[1]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 55, __pyx_L1_error) + } + __pyx_t_7 = 0; + __pyx_v_pts_cpp = ((PyArrayObject *)__pyx_t_5); + __pyx_t_5 = 0; + + /* "knn.pyx":56 + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) # <<<<<<<<<<<<<< + * indices_cpp = indices + * + */ + __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_INCREF(__pyx_v_queries); + __Pyx_GIVEREF(__pyx_v_queries); + PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_queries); + __pyx_t_6 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_float32); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + if (PyDict_SetItem(__pyx_t_6, __pyx_n_s_dtype, __pyx_t_1) < 0) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_4, __pyx_t_5, __pyx_t_6); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 56, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + if (!(likely(((__pyx_t_1) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_1, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 56, __pyx_L1_error) + __pyx_t_11 = ((PyArrayObject *)__pyx_t_1); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_11, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_10, &__pyx_t_9, &__pyx_t_8); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_queries_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_10); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_8); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_10, __pyx_t_9, __pyx_t_8); + } + __pyx_t_10 = __pyx_t_9 = __pyx_t_8 = 0; + } + __pyx_pybuffernd_queries_cpp.diminfo[0].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_queries_cpp.diminfo[0].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_queries_cpp.diminfo[1].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_queries_cpp.diminfo[1].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[1]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 56, __pyx_L1_error) + } + __pyx_t_11 = 0; + __pyx_v_queries_cpp = ((PyArrayObject *)__pyx_t_1); + __pyx_t_1 = 0; + + /* "knn.pyx":57 + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices # <<<<<<<<<<<<<< + * + * # normal estimation + */ + if (!(likely(((__pyx_v_indices) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_indices, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 57, __pyx_L1_error) + __pyx_t_1 = __pyx_v_indices; + __Pyx_INCREF(__pyx_t_1); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)((PyArrayObject *)__pyx_t_1), &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_indices_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 2, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_indices_cpp.diminfo[0].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_indices_cpp.diminfo[0].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_indices_cpp.diminfo[1].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_indices_cpp.diminfo[1].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[1]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 57, __pyx_L1_error) + } + __pyx_v_indices_cpp = ((PyArrayObject *)__pyx_t_1); + __pyx_t_1 = 0; + + /* "knn.pyx":60 + * + * # normal estimation + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_omp( pts_cpp.data, npts, dim, + * queries_cpp.data, nqueries, + */ + __pyx_t_12 = __Pyx_PyObject_IsTrue(__pyx_v_omp); if (unlikely(__pyx_t_12 < 0)) __PYX_ERR(0, 60, __pyx_L1_error) + if (__pyx_t_12) { + + /* "knn.pyx":61 + * # normal estimation + * if omp: + * cpp_knn_omp( pts_cpp.data, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + cpp_knn_omp(((float *)__pyx_v_pts_cpp->data), __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_v_nqueries, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + + /* "knn.pyx":60 + * + * # normal estimation + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_omp( pts_cpp.data, npts, dim, + * queries_cpp.data, nqueries, + */ + goto __pyx_L3; + } + + /* "knn.pyx":65 + * K_cpp, indices_cpp.data) + * else: + * cpp_knn( pts_cpp.data, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + /*else*/ { + + /* "knn.pyx":67 + * cpp_knn( pts_cpp.data, npts, dim, + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) # <<<<<<<<<<<<<< + * + * return indices + */ + cpp_knn(((float *)__pyx_v_pts_cpp->data), __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_v_nqueries, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + } + __pyx_L3:; + + /* "knn.pyx":69 + * K_cpp, indices_cpp.data) + * + * return indices # <<<<<<<<<<<<<< + * + * def knn_batch(pts, queries, K, omp=False): + */ + __Pyx_XDECREF(__pyx_r); + __Pyx_INCREF(__pyx_v_indices); + __pyx_r = __pyx_v_indices; + goto __pyx_L0; + + /* "knn.pyx":33 + * const size_t K, long* batch_indices) + * + * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_XDECREF(__pyx_t_2); + __Pyx_XDECREF(__pyx_t_4); + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + { PyObject *__pyx_type, *__pyx_value, *__pyx_tb; + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);} + __Pyx_AddTraceback("nearest_neighbors.knn", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = NULL; + goto __pyx_L2; + __pyx_L0:; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_L2:; + __Pyx_XDECREF((PyObject *)__pyx_v_pts_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_queries_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_indices_cpp); + __Pyx_XDECREF(__pyx_v_indices); + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "knn.pyx":71 + * return indices + * + * def knn_batch(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + +/* Python wrapper */ +static PyObject *__pyx_pw_17nearest_neighbors_3knn_batch(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_17nearest_neighbors_3knn_batch = {"knn_batch", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_17nearest_neighbors_3knn_batch, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_17nearest_neighbors_3knn_batch(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { + PyObject *__pyx_v_pts = 0; + PyObject *__pyx_v_queries = 0; + PyObject *__pyx_v_K = 0; + PyObject *__pyx_v_omp = 0; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + PyObject *__pyx_r = 0; + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("knn_batch (wrapper)", 0); + { + static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_pts,&__pyx_n_s_queries,&__pyx_n_s_K,&__pyx_n_s_omp,0}; + PyObject* values[4] = {0,0,0,0}; + values[3] = ((PyObject *)Py_False); + if (unlikely(__pyx_kwds)) { + Py_ssize_t kw_args; + const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args); + switch (pos_args) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + CYTHON_FALLTHROUGH; + case 2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + CYTHON_FALLTHROUGH; + case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + CYTHON_FALLTHROUGH; + case 0: break; + default: goto __pyx_L5_argtuple_error; + } + kw_args = PyDict_Size(__pyx_kwds); + switch (pos_args) { + case 0: + if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_pts)) != 0)) kw_args--; + else goto __pyx_L5_argtuple_error; + CYTHON_FALLTHROUGH; + case 1: + if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_queries)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn_batch", 0, 3, 4, 1); __PYX_ERR(0, 71, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 2: + if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_K)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn_batch", 0, 3, 4, 2); __PYX_ERR(0, 71, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 3: + if (kw_args > 0) { + PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_omp); + if (value) { values[3] = value; kw_args--; } + } + } + if (unlikely(kw_args > 0)) { + if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "knn_batch") < 0)) __PYX_ERR(0, 71, __pyx_L3_error) + } + } else { + switch (PyTuple_GET_SIZE(__pyx_args)) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + break; + default: goto __pyx_L5_argtuple_error; + } + } + __pyx_v_pts = values[0]; + __pyx_v_queries = values[1]; + __pyx_v_K = values[2]; + __pyx_v_omp = values[3]; + } + goto __pyx_L4_argument_unpacking_done; + __pyx_L5_argtuple_error:; + __Pyx_RaiseArgtupleInvalid("knn_batch", 0, 3, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 71, __pyx_L3_error) + __pyx_L3_error:; + __Pyx_AddTraceback("nearest_neighbors.knn_batch", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_RefNannyFinishContext(); + return NULL; + __pyx_L4_argument_unpacking_done:; + __pyx_r = __pyx_pf_17nearest_neighbors_2knn_batch(__pyx_self, __pyx_v_pts, __pyx_v_queries, __pyx_v_K, __pyx_v_omp); + + /* function exit code */ + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +static PyObject *__pyx_pf_17nearest_neighbors_2knn_batch(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_queries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp) { + int __pyx_v_batch_size; + int __pyx_v_npts; + int __pyx_v_nqueries; + int __pyx_v_K_cpp; + int __pyx_v_dim; + PyArrayObject *__pyx_v_pts_cpp = 0; + PyArrayObject *__pyx_v_queries_cpp = 0; + PyArrayObject *__pyx_v_indices_cpp = 0; + PyObject *__pyx_v_indices = NULL; + __Pyx_LocalBuf_ND __pyx_pybuffernd_indices_cpp; + __Pyx_Buffer __pyx_pybuffer_indices_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_pts_cpp; + __Pyx_Buffer __pyx_pybuffer_pts_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_queries_cpp; + __Pyx_Buffer __pyx_pybuffer_queries_cpp; + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + int __pyx_t_3; + PyObject *__pyx_t_4 = NULL; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyArrayObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + PyObject *__pyx_t_9 = NULL; + PyObject *__pyx_t_10 = NULL; + PyArrayObject *__pyx_t_11 = NULL; + int __pyx_t_12; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("knn_batch", 0); + __pyx_pybuffer_pts_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_pts_cpp.refcount = 0; + __pyx_pybuffernd_pts_cpp.data = NULL; + __pyx_pybuffernd_pts_cpp.rcbuffer = &__pyx_pybuffer_pts_cpp; + __pyx_pybuffer_queries_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_queries_cpp.refcount = 0; + __pyx_pybuffernd_queries_cpp.data = NULL; + __pyx_pybuffernd_queries_cpp.rcbuffer = &__pyx_pybuffer_queries_cpp; + __pyx_pybuffer_indices_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_indices_cpp.refcount = 0; + __pyx_pybuffernd_indices_cpp.data = NULL; + __pyx_pybuffernd_indices_cpp.rcbuffer = &__pyx_pybuffer_indices_cpp; + + /* "knn.pyx":86 + * + * # set shape values + * batch_size = pts.shape[0] # <<<<<<<<<<<<<< + * npts = pts.shape[1] + * dim = pts.shape[2] + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 86, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 86, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 86, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_batch_size = __pyx_t_3; + + /* "knn.pyx":87 + * # set shape values + * batch_size = pts.shape[0] + * npts = pts.shape[1] # <<<<<<<<<<<<<< + * dim = pts.shape[2] + * nqueries = queries.shape[1] + */ + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 87, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_GetItemInt(__pyx_t_2, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 87, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 87, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_npts = __pyx_t_3; + + /* "knn.pyx":88 + * batch_size = pts.shape[0] + * npts = pts.shape[1] + * dim = pts.shape[2] # <<<<<<<<<<<<<< + * nqueries = queries.shape[1] + * K_cpp = K + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 88, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 2, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 88, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 88, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_dim = __pyx_t_3; + + /* "knn.pyx":89 + * npts = pts.shape[1] + * dim = pts.shape[2] + * nqueries = queries.shape[1] # <<<<<<<<<<<<<< + * K_cpp = K + * + */ + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_queries, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 89, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_GetItemInt(__pyx_t_2, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 89, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 89, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_nqueries = __pyx_t_3; + + /* "knn.pyx":90 + * dim = pts.shape[2] + * nqueries = queries.shape[1] + * K_cpp = K # <<<<<<<<<<<<<< + * + * # create indices tensor + */ + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_K); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 90, __pyx_L1_error) + __pyx_v_K_cpp = __pyx_t_3; + + /* "knn.pyx":93 + * + * # create indices tensor + * indices = np.zeros((pts.shape[0], queries.shape[1], K), dtype=np.int64) # <<<<<<<<<<<<<< + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + */ + __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_zeros); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_4 = __Pyx_GetItemInt(__pyx_t_1, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_queries, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_5 = __Pyx_GetItemInt(__pyx_t_1, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_1 = PyTuple_New(3); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_GIVEREF(__pyx_t_4); + PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_4); + __Pyx_GIVEREF(__pyx_t_5); + PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_t_5); + __Pyx_INCREF(__pyx_v_K); + __Pyx_GIVEREF(__pyx_v_K); + PyTuple_SET_ITEM(__pyx_t_1, 2, __pyx_v_K); + __pyx_t_4 = 0; + __pyx_t_5 = 0; + __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GIVEREF(__pyx_t_1); + PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_1); + __pyx_t_1 = 0; + __pyx_t_1 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_int64); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_dtype, __pyx_t_6) < 0) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_5, __pyx_t_1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 93, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_indices = __pyx_t_6; + __pyx_t_6 = 0; + + /* "knn.pyx":95 + * indices = np.zeros((pts.shape[0], queries.shape[1], K), dtype=np.int64) + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) # <<<<<<<<<<<<<< + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices + */ + __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = PyTuple_New(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_INCREF(__pyx_v_pts); + __Pyx_GIVEREF(__pyx_v_pts); + PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_v_pts); + __pyx_t_5 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_float32); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_dtype, __pyx_t_4) < 0) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_6, __pyx_t_5); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 95, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + if (!(likely(((__pyx_t_4) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_4, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 95, __pyx_L1_error) + __pyx_t_7 = ((PyArrayObject *)__pyx_t_4); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_7, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_pts_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_pts_cpp.diminfo[0].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_pts_cpp.diminfo[0].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_pts_cpp.diminfo[1].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_pts_cpp.diminfo[1].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_pts_cpp.diminfo[2].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_pts_cpp.diminfo[2].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 95, __pyx_L1_error) + } + __pyx_t_7 = 0; + __pyx_v_pts_cpp = ((PyArrayObject *)__pyx_t_4); + __pyx_t_4 = 0; + + /* "knn.pyx":96 + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) # <<<<<<<<<<<<<< + * indices_cpp = indices + * + */ + __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_INCREF(__pyx_v_queries); + __Pyx_GIVEREF(__pyx_v_queries); + PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_queries); + __pyx_t_6 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_float32); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + if (PyDict_SetItem(__pyx_t_6, __pyx_n_s_dtype, __pyx_t_2) < 0) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_5, __pyx_t_4, __pyx_t_6); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 96, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + if (!(likely(((__pyx_t_2) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_2, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 96, __pyx_L1_error) + __pyx_t_11 = ((PyArrayObject *)__pyx_t_2); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_11, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_10, &__pyx_t_9, &__pyx_t_8); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_queries_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_10); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_8); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_10, __pyx_t_9, __pyx_t_8); + } + __pyx_t_10 = __pyx_t_9 = __pyx_t_8 = 0; + } + __pyx_pybuffernd_queries_cpp.diminfo[0].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_queries_cpp.diminfo[0].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_queries_cpp.diminfo[1].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_queries_cpp.diminfo[1].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_queries_cpp.diminfo[2].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_queries_cpp.diminfo[2].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 96, __pyx_L1_error) + } + __pyx_t_11 = 0; + __pyx_v_queries_cpp = ((PyArrayObject *)__pyx_t_2); + __pyx_t_2 = 0; + + /* "knn.pyx":97 + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices # <<<<<<<<<<<<<< + * + * # normal estimation + */ + if (!(likely(((__pyx_v_indices) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_indices, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 97, __pyx_L1_error) + __pyx_t_2 = __pyx_v_indices; + __Pyx_INCREF(__pyx_t_2); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)((PyArrayObject *)__pyx_t_2), &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_indices_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_indices_cpp.diminfo[0].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_indices_cpp.diminfo[0].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_indices_cpp.diminfo[1].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_indices_cpp.diminfo[1].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_indices_cpp.diminfo[2].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_indices_cpp.diminfo[2].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 97, __pyx_L1_error) + } + __pyx_v_indices_cpp = ((PyArrayObject *)__pyx_t_2); + __pyx_t_2 = 0; + + /* "knn.pyx":100 + * + * # normal estimation + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_batch_omp( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, + */ + __pyx_t_12 = __Pyx_PyObject_IsTrue(__pyx_v_omp); if (unlikely(__pyx_t_12 < 0)) __PYX_ERR(0, 100, __pyx_L1_error) + if (__pyx_t_12) { + + /* "knn.pyx":101 + * # normal estimation + * if omp: + * cpp_knn_batch_omp( pts_cpp.data, batch_size, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + cpp_knn_batch_omp(((float *)__pyx_v_pts_cpp->data), __pyx_v_batch_size, __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_v_nqueries, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + + /* "knn.pyx":100 + * + * # normal estimation + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_batch_omp( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, + */ + goto __pyx_L3; + } + + /* "knn.pyx":105 + * K_cpp, indices_cpp.data) + * else: + * cpp_knn_batch( pts_cpp.data, batch_size, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + /*else*/ { + + /* "knn.pyx":107 + * cpp_knn_batch( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) # <<<<<<<<<<<<<< + * + * return indices + */ + cpp_knn_batch(((float *)__pyx_v_pts_cpp->data), __pyx_v_batch_size, __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_v_nqueries, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + } + __pyx_L3:; + + /* "knn.pyx":109 + * K_cpp, indices_cpp.data) + * + * return indices # <<<<<<<<<<<<<< + * + * def knn_batch_distance_pick(pts, nqueries, K, omp=False): + */ + __Pyx_XDECREF(__pyx_r); + __Pyx_INCREF(__pyx_v_indices); + __pyx_r = __pyx_v_indices; + goto __pyx_L0; + + /* "knn.pyx":71 + * return indices + * + * def knn_batch(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_XDECREF(__pyx_t_2); + __Pyx_XDECREF(__pyx_t_4); + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + { PyObject *__pyx_type, *__pyx_value, *__pyx_tb; + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);} + __Pyx_AddTraceback("nearest_neighbors.knn_batch", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = NULL; + goto __pyx_L2; + __pyx_L0:; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_L2:; + __Pyx_XDECREF((PyObject *)__pyx_v_pts_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_queries_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_indices_cpp); + __Pyx_XDECREF(__pyx_v_indices); + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "knn.pyx":111 + * return indices + * + * def knn_batch_distance_pick(pts, nqueries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + +/* Python wrapper */ +static PyObject *__pyx_pw_17nearest_neighbors_5knn_batch_distance_pick(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_17nearest_neighbors_5knn_batch_distance_pick = {"knn_batch_distance_pick", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_17nearest_neighbors_5knn_batch_distance_pick, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_17nearest_neighbors_5knn_batch_distance_pick(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { + PyObject *__pyx_v_pts = 0; + PyObject *__pyx_v_nqueries = 0; + PyObject *__pyx_v_K = 0; + PyObject *__pyx_v_omp = 0; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + PyObject *__pyx_r = 0; + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("knn_batch_distance_pick (wrapper)", 0); + { + static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_pts,&__pyx_n_s_nqueries,&__pyx_n_s_K,&__pyx_n_s_omp,0}; + PyObject* values[4] = {0,0,0,0}; + values[3] = ((PyObject *)Py_False); + if (unlikely(__pyx_kwds)) { + Py_ssize_t kw_args; + const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args); + switch (pos_args) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + CYTHON_FALLTHROUGH; + case 2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + CYTHON_FALLTHROUGH; + case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + CYTHON_FALLTHROUGH; + case 0: break; + default: goto __pyx_L5_argtuple_error; + } + kw_args = PyDict_Size(__pyx_kwds); + switch (pos_args) { + case 0: + if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_pts)) != 0)) kw_args--; + else goto __pyx_L5_argtuple_error; + CYTHON_FALLTHROUGH; + case 1: + if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_nqueries)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn_batch_distance_pick", 0, 3, 4, 1); __PYX_ERR(0, 111, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 2: + if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_K)) != 0)) kw_args--; + else { + __Pyx_RaiseArgtupleInvalid("knn_batch_distance_pick", 0, 3, 4, 2); __PYX_ERR(0, 111, __pyx_L3_error) + } + CYTHON_FALLTHROUGH; + case 3: + if (kw_args > 0) { + PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_omp); + if (value) { values[3] = value; kw_args--; } + } + } + if (unlikely(kw_args > 0)) { + if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "knn_batch_distance_pick") < 0)) __PYX_ERR(0, 111, __pyx_L3_error) + } + } else { + switch (PyTuple_GET_SIZE(__pyx_args)) { + case 4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3); + CYTHON_FALLTHROUGH; + case 3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2); + values[1] = PyTuple_GET_ITEM(__pyx_args, 1); + values[0] = PyTuple_GET_ITEM(__pyx_args, 0); + break; + default: goto __pyx_L5_argtuple_error; + } + } + __pyx_v_pts = values[0]; + __pyx_v_nqueries = values[1]; + __pyx_v_K = values[2]; + __pyx_v_omp = values[3]; + } + goto __pyx_L4_argument_unpacking_done; + __pyx_L5_argtuple_error:; + __Pyx_RaiseArgtupleInvalid("knn_batch_distance_pick", 0, 3, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 111, __pyx_L3_error) + __pyx_L3_error:; + __Pyx_AddTraceback("nearest_neighbors.knn_batch_distance_pick", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_RefNannyFinishContext(); + return NULL; + __pyx_L4_argument_unpacking_done:; + __pyx_r = __pyx_pf_17nearest_neighbors_4knn_batch_distance_pick(__pyx_self, __pyx_v_pts, __pyx_v_nqueries, __pyx_v_K, __pyx_v_omp); + + /* function exit code */ + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +static PyObject *__pyx_pf_17nearest_neighbors_4knn_batch_distance_pick(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pts, PyObject *__pyx_v_nqueries, PyObject *__pyx_v_K, PyObject *__pyx_v_omp) { + int __pyx_v_batch_size; + int __pyx_v_npts; + CYTHON_UNUSED int __pyx_v_nqueries_cpp; + int __pyx_v_K_cpp; + int __pyx_v_dim; + PyArrayObject *__pyx_v_pts_cpp = 0; + PyArrayObject *__pyx_v_queries_cpp = 0; + PyArrayObject *__pyx_v_indices_cpp = 0; + PyObject *__pyx_v_indices = NULL; + PyObject *__pyx_v_queries = NULL; + __Pyx_LocalBuf_ND __pyx_pybuffernd_indices_cpp; + __Pyx_Buffer __pyx_pybuffer_indices_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_pts_cpp; + __Pyx_Buffer __pyx_pybuffer_pts_cpp; + __Pyx_LocalBuf_ND __pyx_pybuffernd_queries_cpp; + __Pyx_Buffer __pyx_pybuffer_queries_cpp; + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + int __pyx_t_3; + PyObject *__pyx_t_4 = NULL; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyArrayObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + PyObject *__pyx_t_9 = NULL; + PyObject *__pyx_t_10 = NULL; + PyArrayObject *__pyx_t_11 = NULL; + int __pyx_t_12; + size_t __pyx_t_13; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("knn_batch_distance_pick", 0); + __pyx_pybuffer_pts_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_pts_cpp.refcount = 0; + __pyx_pybuffernd_pts_cpp.data = NULL; + __pyx_pybuffernd_pts_cpp.rcbuffer = &__pyx_pybuffer_pts_cpp; + __pyx_pybuffer_queries_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_queries_cpp.refcount = 0; + __pyx_pybuffernd_queries_cpp.data = NULL; + __pyx_pybuffernd_queries_cpp.rcbuffer = &__pyx_pybuffer_queries_cpp; + __pyx_pybuffer_indices_cpp.pybuffer.buf = NULL; + __pyx_pybuffer_indices_cpp.refcount = 0; + __pyx_pybuffernd_indices_cpp.data = NULL; + __pyx_pybuffernd_indices_cpp.rcbuffer = &__pyx_pybuffer_indices_cpp; + + /* "knn.pyx":126 + * + * # set shape values + * batch_size = pts.shape[0] # <<<<<<<<<<<<<< + * npts = pts.shape[1] + * dim = pts.shape[2] + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 126, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 126, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 126, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_batch_size = __pyx_t_3; + + /* "knn.pyx":127 + * # set shape values + * batch_size = pts.shape[0] + * npts = pts.shape[1] # <<<<<<<<<<<<<< + * dim = pts.shape[2] + * nqueries_cpp = nqueries + */ + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 127, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_GetItemInt(__pyx_t_2, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 127, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_1); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 127, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_npts = __pyx_t_3; + + /* "knn.pyx":128 + * batch_size = pts.shape[0] + * npts = pts.shape[1] + * dim = pts.shape[2] # <<<<<<<<<<<<<< + * nqueries_cpp = nqueries + * K_cpp = K + */ + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 128, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 2, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 128, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_t_2); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 128, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_dim = __pyx_t_3; + + /* "knn.pyx":129 + * npts = pts.shape[1] + * dim = pts.shape[2] + * nqueries_cpp = nqueries # <<<<<<<<<<<<<< + * K_cpp = K + * + */ + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_nqueries); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 129, __pyx_L1_error) + __pyx_v_nqueries_cpp = __pyx_t_3; + + /* "knn.pyx":130 + * dim = pts.shape[2] + * nqueries_cpp = nqueries + * K_cpp = K # <<<<<<<<<<<<<< + * + * # create indices tensor + */ + __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_K); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 130, __pyx_L1_error) + __pyx_v_K_cpp = __pyx_t_3; + + /* "knn.pyx":133 + * + * # create indices tensor + * indices = np.zeros((pts.shape[0], nqueries, K), dtype=np.long) # <<<<<<<<<<<<<< + * queries = np.zeros((pts.shape[0], nqueries, dim), dtype=np.float32) + * + */ + __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_zeros); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_4 = __Pyx_GetItemInt(__pyx_t_2, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = PyTuple_New(3); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_GIVEREF(__pyx_t_4); + PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_t_4); + __Pyx_INCREF(__pyx_v_nqueries); + __Pyx_GIVEREF(__pyx_v_nqueries); + PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_v_nqueries); + __Pyx_INCREF(__pyx_v_K); + __Pyx_GIVEREF(__pyx_v_K); + PyTuple_SET_ITEM(__pyx_t_2, 2, __pyx_v_K); + __pyx_t_4 = 0; + __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_GIVEREF(__pyx_t_2); + PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_2); + __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_long); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + if (PyDict_SetItem(__pyx_t_2, __pyx_n_s_dtype, __pyx_t_6) < 0) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_4, __pyx_t_2); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 133, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_v_indices = __pyx_t_6; + __pyx_t_6 = 0; + + /* "knn.pyx":134 + * # create indices tensor + * indices = np.zeros((pts.shape[0], nqueries, K), dtype=np.long) + * queries = np.zeros((pts.shape[0], nqueries, dim), dtype=np.float32) # <<<<<<<<<<<<<< + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + */ + __Pyx_GetModuleGlobalName(__pyx_t_6, __pyx_n_s_np); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_6, __pyx_n_s_zeros); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_v_pts, __pyx_n_s_shape); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __pyx_t_4 = __Pyx_GetItemInt(__pyx_t_6, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __pyx_t_6 = __Pyx_PyInt_From_int(__pyx_v_dim); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __pyx_t_1 = PyTuple_New(3); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_GIVEREF(__pyx_t_4); + PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_4); + __Pyx_INCREF(__pyx_v_nqueries); + __Pyx_GIVEREF(__pyx_v_nqueries); + PyTuple_SET_ITEM(__pyx_t_1, 1, __pyx_v_nqueries); + __Pyx_GIVEREF(__pyx_t_6); + PyTuple_SET_ITEM(__pyx_t_1, 2, __pyx_t_6); + __pyx_t_4 = 0; + __pyx_t_6 = 0; + __pyx_t_6 = PyTuple_New(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GIVEREF(__pyx_t_1); + PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_1); + __pyx_t_1 = 0; + __pyx_t_1 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_5 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_float32); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (PyDict_SetItem(__pyx_t_1, __pyx_n_s_dtype, __pyx_t_5) < 0) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __pyx_t_5 = __Pyx_PyObject_Call(__pyx_t_2, __pyx_t_6, __pyx_t_1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 134, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_v_queries = __pyx_t_5; + __pyx_t_5 = 0; + + /* "knn.pyx":136 + * queries = np.zeros((pts.shape[0], nqueries, dim), dtype=np.float32) + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) # <<<<<<<<<<<<<< + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices + */ + __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_n_s_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_INCREF(__pyx_v_pts); + __Pyx_GIVEREF(__pyx_v_pts); + PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_v_pts); + __pyx_t_6 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_np); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_float32); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + if (PyDict_SetItem(__pyx_t_6, __pyx_n_s_dtype, __pyx_t_4) < 0) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_4 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_5, __pyx_t_6); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 136, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + if (!(likely(((__pyx_t_4) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_4, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 136, __pyx_L1_error) + __pyx_t_7 = ((PyArrayObject *)__pyx_t_4); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_7, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_pts_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_pts_cpp.diminfo[0].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_pts_cpp.diminfo[0].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_pts_cpp.diminfo[1].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_pts_cpp.diminfo[1].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_pts_cpp.diminfo[2].strides = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_pts_cpp.diminfo[2].shape = __pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 136, __pyx_L1_error) + } + __pyx_t_7 = 0; + __pyx_v_pts_cpp = ((PyArrayObject *)__pyx_t_4); + __pyx_t_4 = 0; + + /* "knn.pyx":137 + * + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) # <<<<<<<<<<<<<< + * indices_cpp = indices + * + */ + __Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_np); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_6); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_INCREF(__pyx_v_queries); + __Pyx_GIVEREF(__pyx_v_queries); + PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_queries); + __pyx_t_5 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GetModuleGlobalName(__pyx_t_1, __pyx_n_s_np); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s_float32); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_dtype, __pyx_t_2) < 0) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; + __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_6, __pyx_t_4, __pyx_t_5); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 137, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; + if (!(likely(((__pyx_t_2) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_2, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 137, __pyx_L1_error) + __pyx_t_11 = ((PyArrayObject *)__pyx_t_2); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_t_11, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_10, &__pyx_t_9, &__pyx_t_8); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_queries_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_float32_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_10); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_8); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_10, __pyx_t_9, __pyx_t_8); + } + __pyx_t_10 = __pyx_t_9 = __pyx_t_8 = 0; + } + __pyx_pybuffernd_queries_cpp.diminfo[0].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_queries_cpp.diminfo[0].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_queries_cpp.diminfo[1].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_queries_cpp.diminfo[1].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_queries_cpp.diminfo[2].strides = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_queries_cpp.diminfo[2].shape = __pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 137, __pyx_L1_error) + } + __pyx_t_11 = 0; + __pyx_v_queries_cpp = ((PyArrayObject *)__pyx_t_2); + __pyx_t_2 = 0; + + /* "knn.pyx":138 + * pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + * queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + * indices_cpp = indices # <<<<<<<<<<<<<< + * + * if omp: + */ + if (!(likely(((__pyx_v_indices) == Py_None) || likely(__Pyx_TypeTest(__pyx_v_indices, __pyx_ptype_5numpy_ndarray))))) __PYX_ERR(0, 138, __pyx_L1_error) + __pyx_t_2 = __pyx_v_indices; + __Pyx_INCREF(__pyx_t_2); + { + __Pyx_BufFmt_StackElem __pyx_stack[1]; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __pyx_t_3 = __Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)((PyArrayObject *)__pyx_t_2), &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack); + if (unlikely(__pyx_t_3 < 0)) { + PyErr_Fetch(&__pyx_t_8, &__pyx_t_9, &__pyx_t_10); + if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer, (PyObject*)__pyx_v_indices_cpp, &__Pyx_TypeInfo_nn___pyx_t_5numpy_int64_t, PyBUF_FORMAT| PyBUF_STRIDES, 3, 0, __pyx_stack) == -1)) { + Py_XDECREF(__pyx_t_8); Py_XDECREF(__pyx_t_9); Py_XDECREF(__pyx_t_10); + __Pyx_RaiseBufferFallbackError(); + } else { + PyErr_Restore(__pyx_t_8, __pyx_t_9, __pyx_t_10); + } + __pyx_t_8 = __pyx_t_9 = __pyx_t_10 = 0; + } + __pyx_pybuffernd_indices_cpp.diminfo[0].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_indices_cpp.diminfo[0].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[0]; __pyx_pybuffernd_indices_cpp.diminfo[1].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[1]; __pyx_pybuffernd_indices_cpp.diminfo[1].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[1]; __pyx_pybuffernd_indices_cpp.diminfo[2].strides = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.strides[2]; __pyx_pybuffernd_indices_cpp.diminfo[2].shape = __pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer.shape[2]; + if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 138, __pyx_L1_error) + } + __pyx_v_indices_cpp = ((PyArrayObject *)__pyx_t_2); + __pyx_t_2 = 0; + + /* "knn.pyx":140 + * indices_cpp = indices + * + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_batch_distance_pick_omp( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, + */ + __pyx_t_12 = __Pyx_PyObject_IsTrue(__pyx_v_omp); if (unlikely(__pyx_t_12 < 0)) __PYX_ERR(0, 140, __pyx_L1_error) + if (__pyx_t_12) { + + /* "knn.pyx":142 + * if omp: + * cpp_knn_batch_distance_pick_omp( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, # <<<<<<<<<<<<<< + * K_cpp, indices_cpp.data) + * else: + */ + __pyx_t_13 = __Pyx_PyInt_As_size_t(__pyx_v_nqueries); if (unlikely((__pyx_t_13 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 142, __pyx_L1_error) + + /* "knn.pyx":141 + * + * if omp: + * cpp_knn_batch_distance_pick_omp( pts_cpp.data, batch_size, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + cpp_knn_batch_distance_pick_omp(((float *)__pyx_v_pts_cpp->data), __pyx_v_batch_size, __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_t_13, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + + /* "knn.pyx":140 + * indices_cpp = indices + * + * if omp: # <<<<<<<<<<<<<< + * cpp_knn_batch_distance_pick_omp( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, + */ + goto __pyx_L3; + } + + /* "knn.pyx":145 + * K_cpp, indices_cpp.data) + * else: + * cpp_knn_batch_distance_pick( pts_cpp.data, batch_size, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + /*else*/ { + + /* "knn.pyx":146 + * else: + * cpp_knn_batch_distance_pick( pts_cpp.data, batch_size, npts, dim, + * queries_cpp.data, nqueries, # <<<<<<<<<<<<<< + * K_cpp, indices_cpp.data) + * + */ + __pyx_t_13 = __Pyx_PyInt_As_size_t(__pyx_v_nqueries); if (unlikely((__pyx_t_13 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 146, __pyx_L1_error) + + /* "knn.pyx":145 + * K_cpp, indices_cpp.data) + * else: + * cpp_knn_batch_distance_pick( pts_cpp.data, batch_size, npts, dim, # <<<<<<<<<<<<<< + * queries_cpp.data, nqueries, + * K_cpp, indices_cpp.data) + */ + cpp_knn_batch_distance_pick(((float *)__pyx_v_pts_cpp->data), __pyx_v_batch_size, __pyx_v_npts, __pyx_v_dim, ((float *)__pyx_v_queries_cpp->data), __pyx_t_13, __pyx_v_K_cpp, ((long *)__pyx_v_indices_cpp->data)); + } + __pyx_L3:; + + /* "knn.pyx":149 + * K_cpp, indices_cpp.data) + * + * return indices, queries # <<<<<<<<<<<<<< + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_2 = PyTuple_New(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 149, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_2); + __Pyx_INCREF(__pyx_v_indices); + __Pyx_GIVEREF(__pyx_v_indices); + PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_indices); + __Pyx_INCREF(__pyx_v_queries); + __Pyx_GIVEREF(__pyx_v_queries); + PyTuple_SET_ITEM(__pyx_t_2, 1, __pyx_v_queries); + __pyx_r = __pyx_t_2; + __pyx_t_2 = 0; + goto __pyx_L0; + + /* "knn.pyx":111 + * return indices + * + * def knn_batch_distance_pick(pts, nqueries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_XDECREF(__pyx_t_2); + __Pyx_XDECREF(__pyx_t_4); + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + { PyObject *__pyx_type, *__pyx_value, *__pyx_tb; + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);} + __Pyx_AddTraceback("nearest_neighbors.knn_batch_distance_pick", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = NULL; + goto __pyx_L2; + __pyx_L0:; + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_indices_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_pts_cpp.rcbuffer->pybuffer); + __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_queries_cpp.rcbuffer->pybuffer); + __pyx_L2:; + __Pyx_XDECREF((PyObject *)__pyx_v_pts_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_queries_cpp); + __Pyx_XDECREF((PyObject *)__pyx_v_indices_cpp); + __Pyx_XDECREF(__pyx_v_indices); + __Pyx_XDECREF(__pyx_v_queries); + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":258 + * # experimental exception made for __getbuffer__ and __releasebuffer__ + * # -- the details of this may change. + * def __getbuffer__(ndarray self, Py_buffer* info, int flags): # <<<<<<<<<<<<<< + * # This implementation of getbuffer is geared towards Cython + * # requirements, and does not yet fulfill the PEP. + */ + +/* Python wrapper */ +static CYTHON_UNUSED int __pyx_pw_5numpy_7ndarray_1__getbuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /*proto*/ +static CYTHON_UNUSED int __pyx_pw_5numpy_7ndarray_1__getbuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags) { + int __pyx_r; + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__getbuffer__ (wrapper)", 0); + __pyx_r = __pyx_pf_5numpy_7ndarray___getbuffer__(((PyArrayObject *)__pyx_v_self), ((Py_buffer *)__pyx_v_info), ((int)__pyx_v_flags)); + + /* function exit code */ + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +static int __pyx_pf_5numpy_7ndarray___getbuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags) { + int __pyx_v_i; + int __pyx_v_ndim; + int __pyx_v_endian_detector; + int __pyx_v_little_endian; + int __pyx_v_t; + char *__pyx_v_f; + PyArray_Descr *__pyx_v_descr = 0; + int __pyx_v_offset; + int __pyx_r; + __Pyx_RefNannyDeclarations + int __pyx_t_1; + int __pyx_t_2; + PyObject *__pyx_t_3 = NULL; + int __pyx_t_4; + int __pyx_t_5; + int __pyx_t_6; + PyArray_Descr *__pyx_t_7; + PyObject *__pyx_t_8 = NULL; + char *__pyx_t_9; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + if (__pyx_v_info == NULL) { + PyErr_SetString(PyExc_BufferError, "PyObject_GetBuffer: view==NULL argument is obsolete"); + return -1; + } + __Pyx_RefNannySetupContext("__getbuffer__", 0); + __pyx_v_info->obj = Py_None; __Pyx_INCREF(Py_None); + __Pyx_GIVEREF(__pyx_v_info->obj); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":265 + * + * cdef int i, ndim + * cdef int endian_detector = 1 # <<<<<<<<<<<<<< + * cdef bint little_endian = ((&endian_detector)[0] != 0) + * + */ + __pyx_v_endian_detector = 1; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":266 + * cdef int i, ndim + * cdef int endian_detector = 1 + * cdef bint little_endian = ((&endian_detector)[0] != 0) # <<<<<<<<<<<<<< + * + * ndim = PyArray_NDIM(self) + */ + __pyx_v_little_endian = ((((char *)(&__pyx_v_endian_detector))[0]) != 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":268 + * cdef bint little_endian = ((&endian_detector)[0] != 0) + * + * ndim = PyArray_NDIM(self) # <<<<<<<<<<<<<< + * + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) + */ + __pyx_v_ndim = PyArray_NDIM(__pyx_v_self); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":270 + * ndim = PyArray_NDIM(self) + * + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): + * raise ValueError(u"ndarray is not C contiguous") + */ + __pyx_t_2 = (((__pyx_v_flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) != 0); + if (__pyx_t_2) { + } else { + __pyx_t_1 = __pyx_t_2; + goto __pyx_L4_bool_binop_done; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":271 + * + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): # <<<<<<<<<<<<<< + * raise ValueError(u"ndarray is not C contiguous") + * + */ + __pyx_t_2 = ((!(PyArray_CHKFLAGS(__pyx_v_self, NPY_ARRAY_C_CONTIGUOUS) != 0)) != 0); + __pyx_t_1 = __pyx_t_2; + __pyx_L4_bool_binop_done:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":270 + * ndim = PyArray_NDIM(self) + * + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): + * raise ValueError(u"ndarray is not C contiguous") + */ + if (unlikely(__pyx_t_1)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":272 + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): + * raise ValueError(u"ndarray is not C contiguous") # <<<<<<<<<<<<<< + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + */ + __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple_, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 272, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 272, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":270 + * ndim = PyArray_NDIM(self) + * + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): + * raise ValueError(u"ndarray is not C contiguous") + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":274 + * raise ValueError(u"ndarray is not C contiguous") + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): + * raise ValueError(u"ndarray is not Fortran contiguous") + */ + __pyx_t_2 = (((__pyx_v_flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) != 0); + if (__pyx_t_2) { + } else { + __pyx_t_1 = __pyx_t_2; + goto __pyx_L7_bool_binop_done; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":275 + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): # <<<<<<<<<<<<<< + * raise ValueError(u"ndarray is not Fortran contiguous") + * + */ + __pyx_t_2 = ((!(PyArray_CHKFLAGS(__pyx_v_self, NPY_ARRAY_F_CONTIGUOUS) != 0)) != 0); + __pyx_t_1 = __pyx_t_2; + __pyx_L7_bool_binop_done:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":274 + * raise ValueError(u"ndarray is not C contiguous") + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): + * raise ValueError(u"ndarray is not Fortran contiguous") + */ + if (unlikely(__pyx_t_1)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":276 + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): + * raise ValueError(u"ndarray is not Fortran contiguous") # <<<<<<<<<<<<<< + * + * info.buf = PyArray_DATA(self) + */ + __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__2, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 276, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 276, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":274 + * raise ValueError(u"ndarray is not C contiguous") + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) # <<<<<<<<<<<<<< + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): + * raise ValueError(u"ndarray is not Fortran contiguous") + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":278 + * raise ValueError(u"ndarray is not Fortran contiguous") + * + * info.buf = PyArray_DATA(self) # <<<<<<<<<<<<<< + * info.ndim = ndim + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + */ + __pyx_v_info->buf = PyArray_DATA(__pyx_v_self); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":279 + * + * info.buf = PyArray_DATA(self) + * info.ndim = ndim # <<<<<<<<<<<<<< + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + * # Allocate new buffer for strides and shape info. + */ + __pyx_v_info->ndim = __pyx_v_ndim; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":280 + * info.buf = PyArray_DATA(self) + * info.ndim = ndim + * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< + * # Allocate new buffer for strides and shape info. + * # This is allocated as one block, strides first. + */ + __pyx_t_1 = (((sizeof(npy_intp)) != (sizeof(Py_ssize_t))) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":283 + * # Allocate new buffer for strides and shape info. + * # This is allocated as one block, strides first. + * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) # <<<<<<<<<<<<<< + * info.shape = info.strides + ndim + * for i in range(ndim): + */ + __pyx_v_info->strides = ((Py_ssize_t *)PyObject_Malloc((((sizeof(Py_ssize_t)) * 2) * ((size_t)__pyx_v_ndim)))); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":284 + * # This is allocated as one block, strides first. + * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) + * info.shape = info.strides + ndim # <<<<<<<<<<<<<< + * for i in range(ndim): + * info.strides[i] = PyArray_STRIDES(self)[i] + */ + __pyx_v_info->shape = (__pyx_v_info->strides + __pyx_v_ndim); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":285 + * info.strides = PyObject_Malloc(sizeof(Py_ssize_t) * 2 * ndim) + * info.shape = info.strides + ndim + * for i in range(ndim): # <<<<<<<<<<<<<< + * info.strides[i] = PyArray_STRIDES(self)[i] + * info.shape[i] = PyArray_DIMS(self)[i] + */ + __pyx_t_4 = __pyx_v_ndim; + __pyx_t_5 = __pyx_t_4; + for (__pyx_t_6 = 0; __pyx_t_6 < __pyx_t_5; __pyx_t_6+=1) { + __pyx_v_i = __pyx_t_6; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":286 + * info.shape = info.strides + ndim + * for i in range(ndim): + * info.strides[i] = PyArray_STRIDES(self)[i] # <<<<<<<<<<<<<< + * info.shape[i] = PyArray_DIMS(self)[i] + * else: + */ + (__pyx_v_info->strides[__pyx_v_i]) = (PyArray_STRIDES(__pyx_v_self)[__pyx_v_i]); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":287 + * for i in range(ndim): + * info.strides[i] = PyArray_STRIDES(self)[i] + * info.shape[i] = PyArray_DIMS(self)[i] # <<<<<<<<<<<<<< + * else: + * info.strides = PyArray_STRIDES(self) + */ + (__pyx_v_info->shape[__pyx_v_i]) = (PyArray_DIMS(__pyx_v_self)[__pyx_v_i]); + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":280 + * info.buf = PyArray_DATA(self) + * info.ndim = ndim + * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< + * # Allocate new buffer for strides and shape info. + * # This is allocated as one block, strides first. + */ + goto __pyx_L9; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":289 + * info.shape[i] = PyArray_DIMS(self)[i] + * else: + * info.strides = PyArray_STRIDES(self) # <<<<<<<<<<<<<< + * info.shape = PyArray_DIMS(self) + * info.suboffsets = NULL + */ + /*else*/ { + __pyx_v_info->strides = ((Py_ssize_t *)PyArray_STRIDES(__pyx_v_self)); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":290 + * else: + * info.strides = PyArray_STRIDES(self) + * info.shape = PyArray_DIMS(self) # <<<<<<<<<<<<<< + * info.suboffsets = NULL + * info.itemsize = PyArray_ITEMSIZE(self) + */ + __pyx_v_info->shape = ((Py_ssize_t *)PyArray_DIMS(__pyx_v_self)); + } + __pyx_L9:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":291 + * info.strides = PyArray_STRIDES(self) + * info.shape = PyArray_DIMS(self) + * info.suboffsets = NULL # <<<<<<<<<<<<<< + * info.itemsize = PyArray_ITEMSIZE(self) + * info.readonly = not PyArray_ISWRITEABLE(self) + */ + __pyx_v_info->suboffsets = NULL; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":292 + * info.shape = PyArray_DIMS(self) + * info.suboffsets = NULL + * info.itemsize = PyArray_ITEMSIZE(self) # <<<<<<<<<<<<<< + * info.readonly = not PyArray_ISWRITEABLE(self) + * + */ + __pyx_v_info->itemsize = PyArray_ITEMSIZE(__pyx_v_self); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":293 + * info.suboffsets = NULL + * info.itemsize = PyArray_ITEMSIZE(self) + * info.readonly = not PyArray_ISWRITEABLE(self) # <<<<<<<<<<<<<< + * + * cdef int t + */ + __pyx_v_info->readonly = (!(PyArray_ISWRITEABLE(__pyx_v_self) != 0)); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":296 + * + * cdef int t + * cdef char* f = NULL # <<<<<<<<<<<<<< + * cdef dtype descr = PyArray_DESCR(self) + * cdef int offset + */ + __pyx_v_f = NULL; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":297 + * cdef int t + * cdef char* f = NULL + * cdef dtype descr = PyArray_DESCR(self) # <<<<<<<<<<<<<< + * cdef int offset + * + */ + __pyx_t_7 = PyArray_DESCR(__pyx_v_self); + __pyx_t_3 = ((PyObject *)__pyx_t_7); + __Pyx_INCREF(__pyx_t_3); + __pyx_v_descr = ((PyArray_Descr *)__pyx_t_3); + __pyx_t_3 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":300 + * cdef int offset + * + * info.obj = self # <<<<<<<<<<<<<< + * + * if not PyDataType_HASFIELDS(descr): + */ + __Pyx_INCREF(((PyObject *)__pyx_v_self)); + __Pyx_GIVEREF(((PyObject *)__pyx_v_self)); + __Pyx_GOTREF(__pyx_v_info->obj); + __Pyx_DECREF(__pyx_v_info->obj); + __pyx_v_info->obj = ((PyObject *)__pyx_v_self); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":302 + * info.obj = self + * + * if not PyDataType_HASFIELDS(descr): # <<<<<<<<<<<<<< + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or + */ + __pyx_t_1 = ((!(PyDataType_HASFIELDS(__pyx_v_descr) != 0)) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":303 + * + * if not PyDataType_HASFIELDS(descr): + * t = descr.type_num # <<<<<<<<<<<<<< + * if ((descr.byteorder == c'>' and little_endian) or + * (descr.byteorder == c'<' and not little_endian)): + */ + __pyx_t_4 = __pyx_v_descr->type_num; + __pyx_v_t = __pyx_t_4; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":304 + * if not PyDataType_HASFIELDS(descr): + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + __pyx_t_2 = ((__pyx_v_descr->byteorder == '>') != 0); + if (!__pyx_t_2) { + goto __pyx_L15_next_or; + } else { + } + __pyx_t_2 = (__pyx_v_little_endian != 0); + if (!__pyx_t_2) { + } else { + __pyx_t_1 = __pyx_t_2; + goto __pyx_L14_bool_binop_done; + } + __pyx_L15_next_or:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":305 + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or + * (descr.byteorder == c'<' and not little_endian)): # <<<<<<<<<<<<<< + * raise ValueError(u"Non-native byte order not supported") + * if t == NPY_BYTE: f = "b" + */ + __pyx_t_2 = ((__pyx_v_descr->byteorder == '<') != 0); + if (__pyx_t_2) { + } else { + __pyx_t_1 = __pyx_t_2; + goto __pyx_L14_bool_binop_done; + } + __pyx_t_2 = ((!(__pyx_v_little_endian != 0)) != 0); + __pyx_t_1 = __pyx_t_2; + __pyx_L14_bool_binop_done:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":304 + * if not PyDataType_HASFIELDS(descr): + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + if (unlikely(__pyx_t_1)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":306 + * if ((descr.byteorder == c'>' and little_endian) or + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< + * if t == NPY_BYTE: f = "b" + * elif t == NPY_UBYTE: f = "B" + */ + __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 306, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 306, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":304 + * if not PyDataType_HASFIELDS(descr): + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":307 + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + * if t == NPY_BYTE: f = "b" # <<<<<<<<<<<<<< + * elif t == NPY_UBYTE: f = "B" + * elif t == NPY_SHORT: f = "h" + */ + switch (__pyx_v_t) { + case NPY_BYTE: + __pyx_v_f = ((char *)"b"); + break; + case NPY_UBYTE: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":308 + * raise ValueError(u"Non-native byte order not supported") + * if t == NPY_BYTE: f = "b" + * elif t == NPY_UBYTE: f = "B" # <<<<<<<<<<<<<< + * elif t == NPY_SHORT: f = "h" + * elif t == NPY_USHORT: f = "H" + */ + __pyx_v_f = ((char *)"B"); + break; + case NPY_SHORT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":309 + * if t == NPY_BYTE: f = "b" + * elif t == NPY_UBYTE: f = "B" + * elif t == NPY_SHORT: f = "h" # <<<<<<<<<<<<<< + * elif t == NPY_USHORT: f = "H" + * elif t == NPY_INT: f = "i" + */ + __pyx_v_f = ((char *)"h"); + break; + case NPY_USHORT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":310 + * elif t == NPY_UBYTE: f = "B" + * elif t == NPY_SHORT: f = "h" + * elif t == NPY_USHORT: f = "H" # <<<<<<<<<<<<<< + * elif t == NPY_INT: f = "i" + * elif t == NPY_UINT: f = "I" + */ + __pyx_v_f = ((char *)"H"); + break; + case NPY_INT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":311 + * elif t == NPY_SHORT: f = "h" + * elif t == NPY_USHORT: f = "H" + * elif t == NPY_INT: f = "i" # <<<<<<<<<<<<<< + * elif t == NPY_UINT: f = "I" + * elif t == NPY_LONG: f = "l" + */ + __pyx_v_f = ((char *)"i"); + break; + case NPY_UINT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":312 + * elif t == NPY_USHORT: f = "H" + * elif t == NPY_INT: f = "i" + * elif t == NPY_UINT: f = "I" # <<<<<<<<<<<<<< + * elif t == NPY_LONG: f = "l" + * elif t == NPY_ULONG: f = "L" + */ + __pyx_v_f = ((char *)"I"); + break; + case NPY_LONG: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":313 + * elif t == NPY_INT: f = "i" + * elif t == NPY_UINT: f = "I" + * elif t == NPY_LONG: f = "l" # <<<<<<<<<<<<<< + * elif t == NPY_ULONG: f = "L" + * elif t == NPY_LONGLONG: f = "q" + */ + __pyx_v_f = ((char *)"l"); + break; + case NPY_ULONG: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":314 + * elif t == NPY_UINT: f = "I" + * elif t == NPY_LONG: f = "l" + * elif t == NPY_ULONG: f = "L" # <<<<<<<<<<<<<< + * elif t == NPY_LONGLONG: f = "q" + * elif t == NPY_ULONGLONG: f = "Q" + */ + __pyx_v_f = ((char *)"L"); + break; + case NPY_LONGLONG: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":315 + * elif t == NPY_LONG: f = "l" + * elif t == NPY_ULONG: f = "L" + * elif t == NPY_LONGLONG: f = "q" # <<<<<<<<<<<<<< + * elif t == NPY_ULONGLONG: f = "Q" + * elif t == NPY_FLOAT: f = "f" + */ + __pyx_v_f = ((char *)"q"); + break; + case NPY_ULONGLONG: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":316 + * elif t == NPY_ULONG: f = "L" + * elif t == NPY_LONGLONG: f = "q" + * elif t == NPY_ULONGLONG: f = "Q" # <<<<<<<<<<<<<< + * elif t == NPY_FLOAT: f = "f" + * elif t == NPY_DOUBLE: f = "d" + */ + __pyx_v_f = ((char *)"Q"); + break; + case NPY_FLOAT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":317 + * elif t == NPY_LONGLONG: f = "q" + * elif t == NPY_ULONGLONG: f = "Q" + * elif t == NPY_FLOAT: f = "f" # <<<<<<<<<<<<<< + * elif t == NPY_DOUBLE: f = "d" + * elif t == NPY_LONGDOUBLE: f = "g" + */ + __pyx_v_f = ((char *)"f"); + break; + case NPY_DOUBLE: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":318 + * elif t == NPY_ULONGLONG: f = "Q" + * elif t == NPY_FLOAT: f = "f" + * elif t == NPY_DOUBLE: f = "d" # <<<<<<<<<<<<<< + * elif t == NPY_LONGDOUBLE: f = "g" + * elif t == NPY_CFLOAT: f = "Zf" + */ + __pyx_v_f = ((char *)"d"); + break; + case NPY_LONGDOUBLE: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":319 + * elif t == NPY_FLOAT: f = "f" + * elif t == NPY_DOUBLE: f = "d" + * elif t == NPY_LONGDOUBLE: f = "g" # <<<<<<<<<<<<<< + * elif t == NPY_CFLOAT: f = "Zf" + * elif t == NPY_CDOUBLE: f = "Zd" + */ + __pyx_v_f = ((char *)"g"); + break; + case NPY_CFLOAT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":320 + * elif t == NPY_DOUBLE: f = "d" + * elif t == NPY_LONGDOUBLE: f = "g" + * elif t == NPY_CFLOAT: f = "Zf" # <<<<<<<<<<<<<< + * elif t == NPY_CDOUBLE: f = "Zd" + * elif t == NPY_CLONGDOUBLE: f = "Zg" + */ + __pyx_v_f = ((char *)"Zf"); + break; + case NPY_CDOUBLE: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":321 + * elif t == NPY_LONGDOUBLE: f = "g" + * elif t == NPY_CFLOAT: f = "Zf" + * elif t == NPY_CDOUBLE: f = "Zd" # <<<<<<<<<<<<<< + * elif t == NPY_CLONGDOUBLE: f = "Zg" + * elif t == NPY_OBJECT: f = "O" + */ + __pyx_v_f = ((char *)"Zd"); + break; + case NPY_CLONGDOUBLE: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":322 + * elif t == NPY_CFLOAT: f = "Zf" + * elif t == NPY_CDOUBLE: f = "Zd" + * elif t == NPY_CLONGDOUBLE: f = "Zg" # <<<<<<<<<<<<<< + * elif t == NPY_OBJECT: f = "O" + * else: + */ + __pyx_v_f = ((char *)"Zg"); + break; + case NPY_OBJECT: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":323 + * elif t == NPY_CDOUBLE: f = "Zd" + * elif t == NPY_CLONGDOUBLE: f = "Zg" + * elif t == NPY_OBJECT: f = "O" # <<<<<<<<<<<<<< + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) + */ + __pyx_v_f = ((char *)"O"); + break; + default: + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":325 + * elif t == NPY_OBJECT: f = "O" + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) # <<<<<<<<<<<<<< + * info.format = f + * return + */ + __pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_t); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 325, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_8 = PyUnicode_Format(__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_t_3); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 325, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_8); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_3 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_8); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 325, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 325, __pyx_L1_error) + break; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":326 + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) + * info.format = f # <<<<<<<<<<<<<< + * return + * else: + */ + __pyx_v_info->format = __pyx_v_f; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":327 + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) + * info.format = f + * return # <<<<<<<<<<<<<< + * else: + * info.format = PyObject_Malloc(_buffer_format_string_len) + */ + __pyx_r = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":302 + * info.obj = self + * + * if not PyDataType_HASFIELDS(descr): # <<<<<<<<<<<<<< + * t = descr.type_num + * if ((descr.byteorder == c'>' and little_endian) or + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":329 + * return + * else: + * info.format = PyObject_Malloc(_buffer_format_string_len) # <<<<<<<<<<<<<< + * info.format[0] = c'^' # Native data types, manual alignment + * offset = 0 + */ + /*else*/ { + __pyx_v_info->format = ((char *)PyObject_Malloc(0xFF)); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":330 + * else: + * info.format = PyObject_Malloc(_buffer_format_string_len) + * info.format[0] = c'^' # Native data types, manual alignment # <<<<<<<<<<<<<< + * offset = 0 + * f = _util_dtypestring(descr, info.format + 1, + */ + (__pyx_v_info->format[0]) = '^'; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":331 + * info.format = PyObject_Malloc(_buffer_format_string_len) + * info.format[0] = c'^' # Native data types, manual alignment + * offset = 0 # <<<<<<<<<<<<<< + * f = _util_dtypestring(descr, info.format + 1, + * info.format + _buffer_format_string_len, + */ + __pyx_v_offset = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":332 + * info.format[0] = c'^' # Native data types, manual alignment + * offset = 0 + * f = _util_dtypestring(descr, info.format + 1, # <<<<<<<<<<<<<< + * info.format + _buffer_format_string_len, + * &offset) + */ + __pyx_t_9 = __pyx_f_5numpy__util_dtypestring(__pyx_v_descr, (__pyx_v_info->format + 1), (__pyx_v_info->format + 0xFF), (&__pyx_v_offset)); if (unlikely(__pyx_t_9 == ((char *)NULL))) __PYX_ERR(1, 332, __pyx_L1_error) + __pyx_v_f = __pyx_t_9; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":335 + * info.format + _buffer_format_string_len, + * &offset) + * f[0] = c'\0' # Terminate format string # <<<<<<<<<<<<<< + * + * def __releasebuffer__(ndarray self, Py_buffer* info): + */ + (__pyx_v_f[0]) = '\x00'; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":258 + * # experimental exception made for __getbuffer__ and __releasebuffer__ + * # -- the details of this may change. + * def __getbuffer__(ndarray self, Py_buffer* info, int flags): # <<<<<<<<<<<<<< + * # This implementation of getbuffer is geared towards Cython + * # requirements, and does not yet fulfill the PEP. + */ + + /* function exit code */ + __pyx_r = 0; + goto __pyx_L0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_3); + __Pyx_XDECREF(__pyx_t_8); + __Pyx_AddTraceback("numpy.ndarray.__getbuffer__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = -1; + if (__pyx_v_info->obj != NULL) { + __Pyx_GOTREF(__pyx_v_info->obj); + __Pyx_DECREF(__pyx_v_info->obj); __pyx_v_info->obj = 0; + } + goto __pyx_L2; + __pyx_L0:; + if (__pyx_v_info->obj == Py_None) { + __Pyx_GOTREF(__pyx_v_info->obj); + __Pyx_DECREF(__pyx_v_info->obj); __pyx_v_info->obj = 0; + } + __pyx_L2:; + __Pyx_XDECREF((PyObject *)__pyx_v_descr); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":337 + * f[0] = c'\0' # Terminate format string + * + * def __releasebuffer__(ndarray self, Py_buffer* info): # <<<<<<<<<<<<<< + * if PyArray_HASFIELDS(self): + * PyObject_Free(info.format) + */ + +/* Python wrapper */ +static CYTHON_UNUSED void __pyx_pw_5numpy_7ndarray_3__releasebuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info); /*proto*/ +static CYTHON_UNUSED void __pyx_pw_5numpy_7ndarray_3__releasebuffer__(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__releasebuffer__ (wrapper)", 0); + __pyx_pf_5numpy_7ndarray_2__releasebuffer__(((PyArrayObject *)__pyx_v_self), ((Py_buffer *)__pyx_v_info)); + + /* function exit code */ + __Pyx_RefNannyFinishContext(); +} + +static void __pyx_pf_5numpy_7ndarray_2__releasebuffer__(PyArrayObject *__pyx_v_self, Py_buffer *__pyx_v_info) { + __Pyx_RefNannyDeclarations + int __pyx_t_1; + __Pyx_RefNannySetupContext("__releasebuffer__", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":338 + * + * def __releasebuffer__(ndarray self, Py_buffer* info): + * if PyArray_HASFIELDS(self): # <<<<<<<<<<<<<< + * PyObject_Free(info.format) + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + */ + __pyx_t_1 = (PyArray_HASFIELDS(__pyx_v_self) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":339 + * def __releasebuffer__(ndarray self, Py_buffer* info): + * if PyArray_HASFIELDS(self): + * PyObject_Free(info.format) # <<<<<<<<<<<<<< + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + * PyObject_Free(info.strides) + */ + PyObject_Free(__pyx_v_info->format); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":338 + * + * def __releasebuffer__(ndarray self, Py_buffer* info): + * if PyArray_HASFIELDS(self): # <<<<<<<<<<<<<< + * PyObject_Free(info.format) + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":340 + * if PyArray_HASFIELDS(self): + * PyObject_Free(info.format) + * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< + * PyObject_Free(info.strides) + * # info.shape was stored after info.strides in the same block + */ + __pyx_t_1 = (((sizeof(npy_intp)) != (sizeof(Py_ssize_t))) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":341 + * PyObject_Free(info.format) + * if sizeof(npy_intp) != sizeof(Py_ssize_t): + * PyObject_Free(info.strides) # <<<<<<<<<<<<<< + * # info.shape was stored after info.strides in the same block + * + */ + PyObject_Free(__pyx_v_info->strides); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":340 + * if PyArray_HASFIELDS(self): + * PyObject_Free(info.format) + * if sizeof(npy_intp) != sizeof(Py_ssize_t): # <<<<<<<<<<<<<< + * PyObject_Free(info.strides) + * # info.shape was stored after info.strides in the same block + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":337 + * f[0] = c'\0' # Terminate format string + * + * def __releasebuffer__(ndarray self, Py_buffer* info): # <<<<<<<<<<<<<< + * if PyArray_HASFIELDS(self): + * PyObject_Free(info.format) + */ + + /* function exit code */ + __Pyx_RefNannyFinishContext(); +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":820 + * ctypedef npy_cdouble complex_t + * + * cdef inline object PyArray_MultiIterNew1(a): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(1, a) + * + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew1(PyObject *__pyx_v_a) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("PyArray_MultiIterNew1", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":821 + * + * cdef inline object PyArray_MultiIterNew1(a): + * return PyArray_MultiIterNew(1, a) # <<<<<<<<<<<<<< + * + * cdef inline object PyArray_MultiIterNew2(a, b): + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_1 = PyArray_MultiIterNew(1, ((void *)__pyx_v_a)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 821, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_r = __pyx_t_1; + __pyx_t_1 = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":820 + * ctypedef npy_cdouble complex_t + * + * cdef inline object PyArray_MultiIterNew1(a): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(1, a) + * + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_AddTraceback("numpy.PyArray_MultiIterNew1", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = 0; + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":823 + * return PyArray_MultiIterNew(1, a) + * + * cdef inline object PyArray_MultiIterNew2(a, b): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(2, a, b) + * + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew2(PyObject *__pyx_v_a, PyObject *__pyx_v_b) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("PyArray_MultiIterNew2", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":824 + * + * cdef inline object PyArray_MultiIterNew2(a, b): + * return PyArray_MultiIterNew(2, a, b) # <<<<<<<<<<<<<< + * + * cdef inline object PyArray_MultiIterNew3(a, b, c): + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_1 = PyArray_MultiIterNew(2, ((void *)__pyx_v_a), ((void *)__pyx_v_b)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 824, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_r = __pyx_t_1; + __pyx_t_1 = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":823 + * return PyArray_MultiIterNew(1, a) + * + * cdef inline object PyArray_MultiIterNew2(a, b): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(2, a, b) + * + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_AddTraceback("numpy.PyArray_MultiIterNew2", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = 0; + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":826 + * return PyArray_MultiIterNew(2, a, b) + * + * cdef inline object PyArray_MultiIterNew3(a, b, c): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(3, a, b, c) + * + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew3(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("PyArray_MultiIterNew3", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":827 + * + * cdef inline object PyArray_MultiIterNew3(a, b, c): + * return PyArray_MultiIterNew(3, a, b, c) # <<<<<<<<<<<<<< + * + * cdef inline object PyArray_MultiIterNew4(a, b, c, d): + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_1 = PyArray_MultiIterNew(3, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 827, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_r = __pyx_t_1; + __pyx_t_1 = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":826 + * return PyArray_MultiIterNew(2, a, b) + * + * cdef inline object PyArray_MultiIterNew3(a, b, c): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(3, a, b, c) + * + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_AddTraceback("numpy.PyArray_MultiIterNew3", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = 0; + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":829 + * return PyArray_MultiIterNew(3, a, b, c) + * + * cdef inline object PyArray_MultiIterNew4(a, b, c, d): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(4, a, b, c, d) + * + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew4(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("PyArray_MultiIterNew4", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":830 + * + * cdef inline object PyArray_MultiIterNew4(a, b, c, d): + * return PyArray_MultiIterNew(4, a, b, c, d) # <<<<<<<<<<<<<< + * + * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_1 = PyArray_MultiIterNew(4, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c), ((void *)__pyx_v_d)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 830, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_r = __pyx_t_1; + __pyx_t_1 = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":829 + * return PyArray_MultiIterNew(3, a, b, c) + * + * cdef inline object PyArray_MultiIterNew4(a, b, c, d): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(4, a, b, c, d) + * + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_AddTraceback("numpy.PyArray_MultiIterNew4", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = 0; + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":832 + * return PyArray_MultiIterNew(4, a, b, c, d) + * + * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(5, a, b, c, d, e) + * + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyArray_MultiIterNew5(PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_c, PyObject *__pyx_v_d, PyObject *__pyx_v_e) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("PyArray_MultiIterNew5", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":833 + * + * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): + * return PyArray_MultiIterNew(5, a, b, c, d, e) # <<<<<<<<<<<<<< + * + * cdef inline tuple PyDataType_SHAPE(dtype d): + */ + __Pyx_XDECREF(__pyx_r); + __pyx_t_1 = PyArray_MultiIterNew(5, ((void *)__pyx_v_a), ((void *)__pyx_v_b), ((void *)__pyx_v_c), ((void *)__pyx_v_d), ((void *)__pyx_v_e)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 833, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_r = __pyx_t_1; + __pyx_t_1 = 0; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":832 + * return PyArray_MultiIterNew(4, a, b, c, d) + * + * cdef inline object PyArray_MultiIterNew5(a, b, c, d, e): # <<<<<<<<<<<<<< + * return PyArray_MultiIterNew(5, a, b, c, d, e) + * + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_AddTraceback("numpy.PyArray_MultiIterNew5", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = 0; + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":835 + * return PyArray_MultiIterNew(5, a, b, c, d, e) + * + * cdef inline tuple PyDataType_SHAPE(dtype d): # <<<<<<<<<<<<<< + * if PyDataType_HASSUBARRAY(d): + * return d.subarray.shape + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_PyDataType_SHAPE(PyArray_Descr *__pyx_v_d) { + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + int __pyx_t_1; + __Pyx_RefNannySetupContext("PyDataType_SHAPE", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":836 + * + * cdef inline tuple PyDataType_SHAPE(dtype d): + * if PyDataType_HASSUBARRAY(d): # <<<<<<<<<<<<<< + * return d.subarray.shape + * else: + */ + __pyx_t_1 = (PyDataType_HASSUBARRAY(__pyx_v_d) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":837 + * cdef inline tuple PyDataType_SHAPE(dtype d): + * if PyDataType_HASSUBARRAY(d): + * return d.subarray.shape # <<<<<<<<<<<<<< + * else: + * return () + */ + __Pyx_XDECREF(__pyx_r); + __Pyx_INCREF(((PyObject*)__pyx_v_d->subarray->shape)); + __pyx_r = ((PyObject*)__pyx_v_d->subarray->shape); + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":836 + * + * cdef inline tuple PyDataType_SHAPE(dtype d): + * if PyDataType_HASSUBARRAY(d): # <<<<<<<<<<<<<< + * return d.subarray.shape + * else: + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":839 + * return d.subarray.shape + * else: + * return () # <<<<<<<<<<<<<< + * + * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: + */ + /*else*/ { + __Pyx_XDECREF(__pyx_r); + __Pyx_INCREF(__pyx_empty_tuple); + __pyx_r = __pyx_empty_tuple; + goto __pyx_L0; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":835 + * return PyArray_MultiIterNew(5, a, b, c, d, e) + * + * cdef inline tuple PyDataType_SHAPE(dtype d): # <<<<<<<<<<<<<< + * if PyDataType_HASSUBARRAY(d): + * return d.subarray.shape + */ + + /* function exit code */ + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":841 + * return () + * + * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: # <<<<<<<<<<<<<< + * # Recursive utility function used in __getbuffer__ to get format + * # string. The new location in the format string is returned. + */ + +static CYTHON_INLINE char *__pyx_f_5numpy__util_dtypestring(PyArray_Descr *__pyx_v_descr, char *__pyx_v_f, char *__pyx_v_end, int *__pyx_v_offset) { + PyArray_Descr *__pyx_v_child = 0; + int __pyx_v_endian_detector; + int __pyx_v_little_endian; + PyObject *__pyx_v_fields = 0; + PyObject *__pyx_v_childname = NULL; + PyObject *__pyx_v_new_offset = NULL; + PyObject *__pyx_v_t = NULL; + char *__pyx_r; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + Py_ssize_t __pyx_t_2; + PyObject *__pyx_t_3 = NULL; + PyObject *__pyx_t_4 = NULL; + int __pyx_t_5; + int __pyx_t_6; + int __pyx_t_7; + long __pyx_t_8; + char *__pyx_t_9; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("_util_dtypestring", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":846 + * + * cdef dtype child + * cdef int endian_detector = 1 # <<<<<<<<<<<<<< + * cdef bint little_endian = ((&endian_detector)[0] != 0) + * cdef tuple fields + */ + __pyx_v_endian_detector = 1; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":847 + * cdef dtype child + * cdef int endian_detector = 1 + * cdef bint little_endian = ((&endian_detector)[0] != 0) # <<<<<<<<<<<<<< + * cdef tuple fields + * + */ + __pyx_v_little_endian = ((((char *)(&__pyx_v_endian_detector))[0]) != 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":850 + * cdef tuple fields + * + * for childname in descr.names: # <<<<<<<<<<<<<< + * fields = descr.fields[childname] + * child, new_offset = fields + */ + if (unlikely(__pyx_v_descr->names == Py_None)) { + PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable"); + __PYX_ERR(1, 850, __pyx_L1_error) + } + __pyx_t_1 = __pyx_v_descr->names; __Pyx_INCREF(__pyx_t_1); __pyx_t_2 = 0; + for (;;) { + if (__pyx_t_2 >= PyTuple_GET_SIZE(__pyx_t_1)) break; + #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS + __pyx_t_3 = PyTuple_GET_ITEM(__pyx_t_1, __pyx_t_2); __Pyx_INCREF(__pyx_t_3); __pyx_t_2++; if (unlikely(0 < 0)) __PYX_ERR(1, 850, __pyx_L1_error) + #else + __pyx_t_3 = PySequence_ITEM(__pyx_t_1, __pyx_t_2); __pyx_t_2++; if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 850, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + #endif + __Pyx_XDECREF_SET(__pyx_v_childname, __pyx_t_3); + __pyx_t_3 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":851 + * + * for childname in descr.names: + * fields = descr.fields[childname] # <<<<<<<<<<<<<< + * child, new_offset = fields + * + */ + if (unlikely(__pyx_v_descr->fields == Py_None)) { + PyErr_SetString(PyExc_TypeError, "'NoneType' object is not subscriptable"); + __PYX_ERR(1, 851, __pyx_L1_error) + } + __pyx_t_3 = __Pyx_PyDict_GetItem(__pyx_v_descr->fields, __pyx_v_childname); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 851, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + if (!(likely(PyTuple_CheckExact(__pyx_t_3))||((__pyx_t_3) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_t_3)->tp_name), 0))) __PYX_ERR(1, 851, __pyx_L1_error) + __Pyx_XDECREF_SET(__pyx_v_fields, ((PyObject*)__pyx_t_3)); + __pyx_t_3 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":852 + * for childname in descr.names: + * fields = descr.fields[childname] + * child, new_offset = fields # <<<<<<<<<<<<<< + * + * if (end - f) - (new_offset - offset[0]) < 15: + */ + if (likely(__pyx_v_fields != Py_None)) { + PyObject* sequence = __pyx_v_fields; + Py_ssize_t size = __Pyx_PySequence_SIZE(sequence); + if (unlikely(size != 2)) { + if (size > 2) __Pyx_RaiseTooManyValuesError(2); + else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size); + __PYX_ERR(1, 852, __pyx_L1_error) + } + #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS + __pyx_t_3 = PyTuple_GET_ITEM(sequence, 0); + __pyx_t_4 = PyTuple_GET_ITEM(sequence, 1); + __Pyx_INCREF(__pyx_t_3); + __Pyx_INCREF(__pyx_t_4); + #else + __pyx_t_3 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 852, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 852, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + #endif + } else { + __Pyx_RaiseNoneNotIterableError(); __PYX_ERR(1, 852, __pyx_L1_error) + } + if (!(likely(((__pyx_t_3) == Py_None) || likely(__Pyx_TypeTest(__pyx_t_3, __pyx_ptype_5numpy_dtype))))) __PYX_ERR(1, 852, __pyx_L1_error) + __Pyx_XDECREF_SET(__pyx_v_child, ((PyArray_Descr *)__pyx_t_3)); + __pyx_t_3 = 0; + __Pyx_XDECREF_SET(__pyx_v_new_offset, __pyx_t_4); + __pyx_t_4 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":854 + * child, new_offset = fields + * + * if (end - f) - (new_offset - offset[0]) < 15: # <<<<<<<<<<<<<< + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") + * + */ + __pyx_t_4 = __Pyx_PyInt_From_int((__pyx_v_offset[0])); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 854, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyNumber_Subtract(__pyx_v_new_offset, __pyx_t_4); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 854, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_5 = __Pyx_PyInt_As_int(__pyx_t_3); if (unlikely((__pyx_t_5 == (int)-1) && PyErr_Occurred())) __PYX_ERR(1, 854, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = ((((__pyx_v_end - __pyx_v_f) - ((int)__pyx_t_5)) < 15) != 0); + if (unlikely(__pyx_t_6)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":855 + * + * if (end - f) - (new_offset - offset[0]) < 15: + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") # <<<<<<<<<<<<<< + * + * if ((child.byteorder == c'>' and little_endian) or + */ + __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_RuntimeError, __pyx_tuple__4, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 855, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 855, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":854 + * child, new_offset = fields + * + * if (end - f) - (new_offset - offset[0]) < 15: # <<<<<<<<<<<<<< + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") + * + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":857 + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") + * + * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (child.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + __pyx_t_7 = ((__pyx_v_child->byteorder == '>') != 0); + if (!__pyx_t_7) { + goto __pyx_L8_next_or; + } else { + } + __pyx_t_7 = (__pyx_v_little_endian != 0); + if (!__pyx_t_7) { + } else { + __pyx_t_6 = __pyx_t_7; + goto __pyx_L7_bool_binop_done; + } + __pyx_L8_next_or:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":858 + * + * if ((child.byteorder == c'>' and little_endian) or + * (child.byteorder == c'<' and not little_endian)): # <<<<<<<<<<<<<< + * raise ValueError(u"Non-native byte order not supported") + * # One could encode it in the format string and have Cython + */ + __pyx_t_7 = ((__pyx_v_child->byteorder == '<') != 0); + if (__pyx_t_7) { + } else { + __pyx_t_6 = __pyx_t_7; + goto __pyx_L7_bool_binop_done; + } + __pyx_t_7 = ((!(__pyx_v_little_endian != 0)) != 0); + __pyx_t_6 = __pyx_t_7; + __pyx_L7_bool_binop_done:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":857 + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") + * + * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (child.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + if (unlikely(__pyx_t_6)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":859 + * if ((child.byteorder == c'>' and little_endian) or + * (child.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< + * # One could encode it in the format string and have Cython + * # complain instead, BUT: < and > in format strings also imply + */ + __pyx_t_3 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 859, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __Pyx_Raise(__pyx_t_3, 0, 0, 0); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __PYX_ERR(1, 859, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":857 + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") + * + * if ((child.byteorder == c'>' and little_endian) or # <<<<<<<<<<<<<< + * (child.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":869 + * + * # Output padding bytes + * while offset[0] < new_offset: # <<<<<<<<<<<<<< + * f[0] = 120 # "x"; pad byte + * f += 1 + */ + while (1) { + __pyx_t_3 = __Pyx_PyInt_From_int((__pyx_v_offset[0])); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 869, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_t_3, __pyx_v_new_offset, Py_LT); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 869, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 869, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (!__pyx_t_6) break; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":870 + * # Output padding bytes + * while offset[0] < new_offset: + * f[0] = 120 # "x"; pad byte # <<<<<<<<<<<<<< + * f += 1 + * offset[0] += 1 + */ + (__pyx_v_f[0]) = 0x78; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":871 + * while offset[0] < new_offset: + * f[0] = 120 # "x"; pad byte + * f += 1 # <<<<<<<<<<<<<< + * offset[0] += 1 + * + */ + __pyx_v_f = (__pyx_v_f + 1); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":872 + * f[0] = 120 # "x"; pad byte + * f += 1 + * offset[0] += 1 # <<<<<<<<<<<<<< + * + * offset[0] += child.itemsize + */ + __pyx_t_8 = 0; + (__pyx_v_offset[__pyx_t_8]) = ((__pyx_v_offset[__pyx_t_8]) + 1); + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":874 + * offset[0] += 1 + * + * offset[0] += child.itemsize # <<<<<<<<<<<<<< + * + * if not PyDataType_HASFIELDS(child): + */ + __pyx_t_8 = 0; + (__pyx_v_offset[__pyx_t_8]) = ((__pyx_v_offset[__pyx_t_8]) + __pyx_v_child->elsize); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":876 + * offset[0] += child.itemsize + * + * if not PyDataType_HASFIELDS(child): # <<<<<<<<<<<<<< + * t = child.type_num + * if end - f < 5: + */ + __pyx_t_6 = ((!(PyDataType_HASFIELDS(__pyx_v_child) != 0)) != 0); + if (__pyx_t_6) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":877 + * + * if not PyDataType_HASFIELDS(child): + * t = child.type_num # <<<<<<<<<<<<<< + * if end - f < 5: + * raise RuntimeError(u"Format string allocated too short.") + */ + __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_child->type_num); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 877, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_XDECREF_SET(__pyx_v_t, __pyx_t_4); + __pyx_t_4 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":878 + * if not PyDataType_HASFIELDS(child): + * t = child.type_num + * if end - f < 5: # <<<<<<<<<<<<<< + * raise RuntimeError(u"Format string allocated too short.") + * + */ + __pyx_t_6 = (((__pyx_v_end - __pyx_v_f) < 5) != 0); + if (unlikely(__pyx_t_6)) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":879 + * t = child.type_num + * if end - f < 5: + * raise RuntimeError(u"Format string allocated too short.") # <<<<<<<<<<<<<< + * + * # Until ticket #99 is fixed, use integers to avoid warnings + */ + __pyx_t_4 = __Pyx_PyObject_Call(__pyx_builtin_RuntimeError, __pyx_tuple__5, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 879, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_Raise(__pyx_t_4, 0, 0, 0); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __PYX_ERR(1, 879, __pyx_L1_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":878 + * if not PyDataType_HASFIELDS(child): + * t = child.type_num + * if end - f < 5: # <<<<<<<<<<<<<< + * raise RuntimeError(u"Format string allocated too short.") + * + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":882 + * + * # Until ticket #99 is fixed, use integers to avoid warnings + * if t == NPY_BYTE: f[0] = 98 #"b" # <<<<<<<<<<<<<< + * elif t == NPY_UBYTE: f[0] = 66 #"B" + * elif t == NPY_SHORT: f[0] = 104 #"h" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_BYTE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 882, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 882, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 882, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 98; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":883 + * # Until ticket #99 is fixed, use integers to avoid warnings + * if t == NPY_BYTE: f[0] = 98 #"b" + * elif t == NPY_UBYTE: f[0] = 66 #"B" # <<<<<<<<<<<<<< + * elif t == NPY_SHORT: f[0] = 104 #"h" + * elif t == NPY_USHORT: f[0] = 72 #"H" + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_UBYTE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 883, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 883, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 883, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 66; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":884 + * if t == NPY_BYTE: f[0] = 98 #"b" + * elif t == NPY_UBYTE: f[0] = 66 #"B" + * elif t == NPY_SHORT: f[0] = 104 #"h" # <<<<<<<<<<<<<< + * elif t == NPY_USHORT: f[0] = 72 #"H" + * elif t == NPY_INT: f[0] = 105 #"i" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_SHORT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 884, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 884, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 884, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x68; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":885 + * elif t == NPY_UBYTE: f[0] = 66 #"B" + * elif t == NPY_SHORT: f[0] = 104 #"h" + * elif t == NPY_USHORT: f[0] = 72 #"H" # <<<<<<<<<<<<<< + * elif t == NPY_INT: f[0] = 105 #"i" + * elif t == NPY_UINT: f[0] = 73 #"I" + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_USHORT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 885, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 885, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 885, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 72; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":886 + * elif t == NPY_SHORT: f[0] = 104 #"h" + * elif t == NPY_USHORT: f[0] = 72 #"H" + * elif t == NPY_INT: f[0] = 105 #"i" # <<<<<<<<<<<<<< + * elif t == NPY_UINT: f[0] = 73 #"I" + * elif t == NPY_LONG: f[0] = 108 #"l" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_INT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 886, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 886, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 886, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x69; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":887 + * elif t == NPY_USHORT: f[0] = 72 #"H" + * elif t == NPY_INT: f[0] = 105 #"i" + * elif t == NPY_UINT: f[0] = 73 #"I" # <<<<<<<<<<<<<< + * elif t == NPY_LONG: f[0] = 108 #"l" + * elif t == NPY_ULONG: f[0] = 76 #"L" + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_UINT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 887, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 887, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 887, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 73; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":888 + * elif t == NPY_INT: f[0] = 105 #"i" + * elif t == NPY_UINT: f[0] = 73 #"I" + * elif t == NPY_LONG: f[0] = 108 #"l" # <<<<<<<<<<<<<< + * elif t == NPY_ULONG: f[0] = 76 #"L" + * elif t == NPY_LONGLONG: f[0] = 113 #"q" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONG); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 888, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 888, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 888, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x6C; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":889 + * elif t == NPY_UINT: f[0] = 73 #"I" + * elif t == NPY_LONG: f[0] = 108 #"l" + * elif t == NPY_ULONG: f[0] = 76 #"L" # <<<<<<<<<<<<<< + * elif t == NPY_LONGLONG: f[0] = 113 #"q" + * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_ULONG); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 889, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 889, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 889, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 76; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":890 + * elif t == NPY_LONG: f[0] = 108 #"l" + * elif t == NPY_ULONG: f[0] = 76 #"L" + * elif t == NPY_LONGLONG: f[0] = 113 #"q" # <<<<<<<<<<<<<< + * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" + * elif t == NPY_FLOAT: f[0] = 102 #"f" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONGLONG); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 890, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 890, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 890, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x71; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":891 + * elif t == NPY_ULONG: f[0] = 76 #"L" + * elif t == NPY_LONGLONG: f[0] = 113 #"q" + * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" # <<<<<<<<<<<<<< + * elif t == NPY_FLOAT: f[0] = 102 #"f" + * elif t == NPY_DOUBLE: f[0] = 100 #"d" + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_ULONGLONG); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 891, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 891, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 891, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 81; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":892 + * elif t == NPY_LONGLONG: f[0] = 113 #"q" + * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" + * elif t == NPY_FLOAT: f[0] = 102 #"f" # <<<<<<<<<<<<<< + * elif t == NPY_DOUBLE: f[0] = 100 #"d" + * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_FLOAT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 892, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 892, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 892, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x66; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":893 + * elif t == NPY_ULONGLONG: f[0] = 81 #"Q" + * elif t == NPY_FLOAT: f[0] = 102 #"f" + * elif t == NPY_DOUBLE: f[0] = 100 #"d" # <<<<<<<<<<<<<< + * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" + * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_DOUBLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 893, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 893, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 893, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x64; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":894 + * elif t == NPY_FLOAT: f[0] = 102 #"f" + * elif t == NPY_DOUBLE: f[0] = 100 #"d" + * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" # <<<<<<<<<<<<<< + * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf + * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_LONGDOUBLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 894, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 894, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 894, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 0x67; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":895 + * elif t == NPY_DOUBLE: f[0] = 100 #"d" + * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" + * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf # <<<<<<<<<<<<<< + * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd + * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CFLOAT); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 895, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 895, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 895, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 90; + (__pyx_v_f[1]) = 0x66; + __pyx_v_f = (__pyx_v_f + 1); + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":896 + * elif t == NPY_LONGDOUBLE: f[0] = 103 #"g" + * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf + * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd # <<<<<<<<<<<<<< + * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg + * elif t == NPY_OBJECT: f[0] = 79 #"O" + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CDOUBLE); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 896, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 896, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 896, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 90; + (__pyx_v_f[1]) = 0x64; + __pyx_v_f = (__pyx_v_f + 1); + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":897 + * elif t == NPY_CFLOAT: f[0] = 90; f[1] = 102; f += 1 # Zf + * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd + * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg # <<<<<<<<<<<<<< + * elif t == NPY_OBJECT: f[0] = 79 #"O" + * else: + */ + __pyx_t_3 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_CLONGDOUBLE); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 897, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = PyObject_RichCompare(__pyx_v_t, __pyx_t_3, Py_EQ); __Pyx_XGOTREF(__pyx_t_4); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 897, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_4); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 897, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + if (__pyx_t_6) { + (__pyx_v_f[0]) = 90; + (__pyx_v_f[1]) = 0x67; + __pyx_v_f = (__pyx_v_f + 1); + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":898 + * elif t == NPY_CDOUBLE: f[0] = 90; f[1] = 100; f += 1 # Zd + * elif t == NPY_CLONGDOUBLE: f[0] = 90; f[1] = 103; f += 1 # Zg + * elif t == NPY_OBJECT: f[0] = 79 #"O" # <<<<<<<<<<<<<< + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) + */ + __pyx_t_4 = __Pyx_PyInt_From_enum__NPY_TYPES(NPY_OBJECT); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 898, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __pyx_t_3 = PyObject_RichCompare(__pyx_v_t, __pyx_t_4, Py_EQ); __Pyx_XGOTREF(__pyx_t_3); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 898, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_t_3); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(1, 898, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + if (likely(__pyx_t_6)) { + (__pyx_v_f[0]) = 79; + goto __pyx_L15; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":900 + * elif t == NPY_OBJECT: f[0] = 79 #"O" + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) # <<<<<<<<<<<<<< + * f += 1 + * else: + */ + /*else*/ { + __pyx_t_3 = __Pyx_PyUnicode_FormatSafe(__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_v_t); if (unlikely(!__pyx_t_3)) __PYX_ERR(1, 900, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_3); + __pyx_t_4 = __Pyx_PyObject_CallOneArg(__pyx_builtin_ValueError, __pyx_t_3); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 900, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_4); + __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; + __Pyx_Raise(__pyx_t_4, 0, 0, 0); + __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; + __PYX_ERR(1, 900, __pyx_L1_error) + } + __pyx_L15:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":901 + * else: + * raise ValueError(u"unknown dtype code in numpy.pxd (%d)" % t) + * f += 1 # <<<<<<<<<<<<<< + * else: + * # Cython ignores struct boundary information ("T{...}"), + */ + __pyx_v_f = (__pyx_v_f + 1); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":876 + * offset[0] += child.itemsize + * + * if not PyDataType_HASFIELDS(child): # <<<<<<<<<<<<<< + * t = child.type_num + * if end - f < 5: + */ + goto __pyx_L13; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":905 + * # Cython ignores struct boundary information ("T{...}"), + * # so don't output it + * f = _util_dtypestring(child, f, end, offset) # <<<<<<<<<<<<<< + * return f + * + */ + /*else*/ { + __pyx_t_9 = __pyx_f_5numpy__util_dtypestring(__pyx_v_child, __pyx_v_f, __pyx_v_end, __pyx_v_offset); if (unlikely(__pyx_t_9 == ((char *)NULL))) __PYX_ERR(1, 905, __pyx_L1_error) + __pyx_v_f = __pyx_t_9; + } + __pyx_L13:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":850 + * cdef tuple fields + * + * for childname in descr.names: # <<<<<<<<<<<<<< + * fields = descr.fields[childname] + * child, new_offset = fields + */ + } + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":906 + * # so don't output it + * f = _util_dtypestring(child, f, end, offset) + * return f # <<<<<<<<<<<<<< + * + * + */ + __pyx_r = __pyx_v_f; + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":841 + * return () + * + * cdef inline char* _util_dtypestring(dtype descr, char* f, char* end, int* offset) except NULL: # <<<<<<<<<<<<<< + * # Recursive utility function used in __getbuffer__ to get format + * # string. The new location in the format string is returned. + */ + + /* function exit code */ + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_XDECREF(__pyx_t_3); + __Pyx_XDECREF(__pyx_t_4); + __Pyx_AddTraceback("numpy._util_dtypestring", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = NULL; + __pyx_L0:; + __Pyx_XDECREF((PyObject *)__pyx_v_child); + __Pyx_XDECREF(__pyx_v_fields); + __Pyx_XDECREF(__pyx_v_childname); + __Pyx_XDECREF(__pyx_v_new_offset); + __Pyx_XDECREF(__pyx_v_t); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1021 + * int _import_umath() except -1 + * + * cdef inline void set_array_base(ndarray arr, object base): # <<<<<<<<<<<<<< + * Py_INCREF(base) # important to do this before stealing the reference below! + * PyArray_SetBaseObject(arr, base) + */ + +static CYTHON_INLINE void __pyx_f_5numpy_set_array_base(PyArrayObject *__pyx_v_arr, PyObject *__pyx_v_base) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("set_array_base", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1022 + * + * cdef inline void set_array_base(ndarray arr, object base): + * Py_INCREF(base) # important to do this before stealing the reference below! # <<<<<<<<<<<<<< + * PyArray_SetBaseObject(arr, base) + * + */ + Py_INCREF(__pyx_v_base); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1023 + * cdef inline void set_array_base(ndarray arr, object base): + * Py_INCREF(base) # important to do this before stealing the reference below! + * PyArray_SetBaseObject(arr, base) # <<<<<<<<<<<<<< + * + * cdef inline object get_array_base(ndarray arr): + */ + (void)(PyArray_SetBaseObject(__pyx_v_arr, __pyx_v_base)); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1021 + * int _import_umath() except -1 + * + * cdef inline void set_array_base(ndarray arr, object base): # <<<<<<<<<<<<<< + * Py_INCREF(base) # important to do this before stealing the reference below! + * PyArray_SetBaseObject(arr, base) + */ + + /* function exit code */ + __Pyx_RefNannyFinishContext(); +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1025 + * PyArray_SetBaseObject(arr, base) + * + * cdef inline object get_array_base(ndarray arr): # <<<<<<<<<<<<<< + * base = PyArray_BASE(arr) + * if base is NULL: + */ + +static CYTHON_INLINE PyObject *__pyx_f_5numpy_get_array_base(PyArrayObject *__pyx_v_arr) { + PyObject *__pyx_v_base; + PyObject *__pyx_r = NULL; + __Pyx_RefNannyDeclarations + int __pyx_t_1; + __Pyx_RefNannySetupContext("get_array_base", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1026 + * + * cdef inline object get_array_base(ndarray arr): + * base = PyArray_BASE(arr) # <<<<<<<<<<<<<< + * if base is NULL: + * return None + */ + __pyx_v_base = PyArray_BASE(__pyx_v_arr); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1027 + * cdef inline object get_array_base(ndarray arr): + * base = PyArray_BASE(arr) + * if base is NULL: # <<<<<<<<<<<<<< + * return None + * return base + */ + __pyx_t_1 = ((__pyx_v_base == NULL) != 0); + if (__pyx_t_1) { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1028 + * base = PyArray_BASE(arr) + * if base is NULL: + * return None # <<<<<<<<<<<<<< + * return base + * + */ + __Pyx_XDECREF(__pyx_r); + __pyx_r = Py_None; __Pyx_INCREF(Py_None); + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1027 + * cdef inline object get_array_base(ndarray arr): + * base = PyArray_BASE(arr) + * if base is NULL: # <<<<<<<<<<<<<< + * return None + * return base + */ + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1029 + * if base is NULL: + * return None + * return base # <<<<<<<<<<<<<< + * + * # Versions of the import_* functions which are more suitable for + */ + __Pyx_XDECREF(__pyx_r); + __Pyx_INCREF(((PyObject *)__pyx_v_base)); + __pyx_r = ((PyObject *)__pyx_v_base); + goto __pyx_L0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1025 + * PyArray_SetBaseObject(arr, base) + * + * cdef inline object get_array_base(ndarray arr): # <<<<<<<<<<<<<< + * base = PyArray_BASE(arr) + * if base is NULL: + */ + + /* function exit code */ + __pyx_L0:; + __Pyx_XGIVEREF(__pyx_r); + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1033 + * # Versions of the import_* functions which are more suitable for + * # Cython code. + * cdef inline int import_array() except -1: # <<<<<<<<<<<<<< + * try: + * _import_array() + */ + +static CYTHON_INLINE int __pyx_f_5numpy_import_array(void) { + int __pyx_r; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + PyObject *__pyx_t_3 = NULL; + int __pyx_t_4; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("import_array", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1034 + * # Cython code. + * cdef inline int import_array() except -1: + * try: # <<<<<<<<<<<<<< + * _import_array() + * except Exception: + */ + { + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); + __Pyx_XGOTREF(__pyx_t_1); + __Pyx_XGOTREF(__pyx_t_2); + __Pyx_XGOTREF(__pyx_t_3); + /*try:*/ { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1035 + * cdef inline int import_array() except -1: + * try: + * _import_array() # <<<<<<<<<<<<<< + * except Exception: + * raise ImportError("numpy.core.multiarray failed to import") + */ + __pyx_t_4 = _import_array(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1035, __pyx_L3_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1034 + * # Cython code. + * cdef inline int import_array() except -1: + * try: # <<<<<<<<<<<<<< + * _import_array() + * except Exception: + */ + } + __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; + goto __pyx_L8_try_end; + __pyx_L3_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1036 + * try: + * _import_array() + * except Exception: # <<<<<<<<<<<<<< + * raise ImportError("numpy.core.multiarray failed to import") + * + */ + __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); + if (__pyx_t_4) { + __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename); + if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1036, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GOTREF(__pyx_t_7); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1037 + * _import_array() + * except Exception: + * raise ImportError("numpy.core.multiarray failed to import") # <<<<<<<<<<<<<< + * + * cdef inline int import_umath() except -1: + */ + __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__6, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1037, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_8); + __Pyx_Raise(__pyx_t_8, 0, 0, 0); + __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; + __PYX_ERR(1, 1037, __pyx_L5_except_error) + } + goto __pyx_L5_except_error; + __pyx_L5_except_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1034 + * # Cython code. + * cdef inline int import_array() except -1: + * try: # <<<<<<<<<<<<<< + * _import_array() + * except Exception: + */ + __Pyx_XGIVEREF(__pyx_t_1); + __Pyx_XGIVEREF(__pyx_t_2); + __Pyx_XGIVEREF(__pyx_t_3); + __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); + goto __pyx_L1_error; + __pyx_L8_try_end:; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1033 + * # Versions of the import_* functions which are more suitable for + * # Cython code. + * cdef inline int import_array() except -1: # <<<<<<<<<<<<<< + * try: + * _import_array() + */ + + /* function exit code */ + __pyx_r = 0; + goto __pyx_L0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + __Pyx_XDECREF(__pyx_t_7); + __Pyx_XDECREF(__pyx_t_8); + __Pyx_AddTraceback("numpy.import_array", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = -1; + __pyx_L0:; + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1039 + * raise ImportError("numpy.core.multiarray failed to import") + * + * cdef inline int import_umath() except -1: # <<<<<<<<<<<<<< + * try: + * _import_umath() + */ + +static CYTHON_INLINE int __pyx_f_5numpy_import_umath(void) { + int __pyx_r; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + PyObject *__pyx_t_3 = NULL; + int __pyx_t_4; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("import_umath", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1040 + * + * cdef inline int import_umath() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + { + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); + __Pyx_XGOTREF(__pyx_t_1); + __Pyx_XGOTREF(__pyx_t_2); + __Pyx_XGOTREF(__pyx_t_3); + /*try:*/ { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1041 + * cdef inline int import_umath() except -1: + * try: + * _import_umath() # <<<<<<<<<<<<<< + * except Exception: + * raise ImportError("numpy.core.umath failed to import") + */ + __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1041, __pyx_L3_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1040 + * + * cdef inline int import_umath() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + } + __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; + goto __pyx_L8_try_end; + __pyx_L3_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1042 + * try: + * _import_umath() + * except Exception: # <<<<<<<<<<<<<< + * raise ImportError("numpy.core.umath failed to import") + * + */ + __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); + if (__pyx_t_4) { + __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename); + if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1042, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GOTREF(__pyx_t_7); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1043 + * _import_umath() + * except Exception: + * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< + * + * cdef inline int import_ufunc() except -1: + */ + __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__7, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1043, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_8); + __Pyx_Raise(__pyx_t_8, 0, 0, 0); + __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; + __PYX_ERR(1, 1043, __pyx_L5_except_error) + } + goto __pyx_L5_except_error; + __pyx_L5_except_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1040 + * + * cdef inline int import_umath() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + __Pyx_XGIVEREF(__pyx_t_1); + __Pyx_XGIVEREF(__pyx_t_2); + __Pyx_XGIVEREF(__pyx_t_3); + __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); + goto __pyx_L1_error; + __pyx_L8_try_end:; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1039 + * raise ImportError("numpy.core.multiarray failed to import") + * + * cdef inline int import_umath() except -1: # <<<<<<<<<<<<<< + * try: + * _import_umath() + */ + + /* function exit code */ + __pyx_r = 0; + goto __pyx_L0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + __Pyx_XDECREF(__pyx_t_7); + __Pyx_XDECREF(__pyx_t_8); + __Pyx_AddTraceback("numpy.import_umath", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = -1; + __pyx_L0:; + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +/* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1045 + * raise ImportError("numpy.core.umath failed to import") + * + * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< + * try: + * _import_umath() + */ + +static CYTHON_INLINE int __pyx_f_5numpy_import_ufunc(void) { + int __pyx_r; + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + PyObject *__pyx_t_2 = NULL; + PyObject *__pyx_t_3 = NULL; + int __pyx_t_4; + PyObject *__pyx_t_5 = NULL; + PyObject *__pyx_t_6 = NULL; + PyObject *__pyx_t_7 = NULL; + PyObject *__pyx_t_8 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("import_ufunc", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1046 + * + * cdef inline int import_ufunc() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + { + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3); + __Pyx_XGOTREF(__pyx_t_1); + __Pyx_XGOTREF(__pyx_t_2); + __Pyx_XGOTREF(__pyx_t_3); + /*try:*/ { + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1047 + * cdef inline int import_ufunc() except -1: + * try: + * _import_umath() # <<<<<<<<<<<<<< + * except Exception: + * raise ImportError("numpy.core.umath failed to import") + */ + __pyx_t_4 = _import_umath(); if (unlikely(__pyx_t_4 == ((int)-1))) __PYX_ERR(1, 1047, __pyx_L3_error) + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1046 + * + * cdef inline int import_ufunc() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + } + __Pyx_XDECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0; + __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0; + goto __pyx_L8_try_end; + __pyx_L3_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1048 + * try: + * _import_umath() + * except Exception: # <<<<<<<<<<<<<< + * raise ImportError("numpy.core.umath failed to import") + */ + __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(((PyObject *)(&((PyTypeObject*)PyExc_Exception)[0]))); + if (__pyx_t_4) { + __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename); + if (__Pyx_GetException(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) __PYX_ERR(1, 1048, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_5); + __Pyx_GOTREF(__pyx_t_6); + __Pyx_GOTREF(__pyx_t_7); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1049 + * _import_umath() + * except Exception: + * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< + */ + __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_ImportError, __pyx_tuple__7, NULL); if (unlikely(!__pyx_t_8)) __PYX_ERR(1, 1049, __pyx_L5_except_error) + __Pyx_GOTREF(__pyx_t_8); + __Pyx_Raise(__pyx_t_8, 0, 0, 0); + __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; + __PYX_ERR(1, 1049, __pyx_L5_except_error) + } + goto __pyx_L5_except_error; + __pyx_L5_except_error:; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1046 + * + * cdef inline int import_ufunc() except -1: + * try: # <<<<<<<<<<<<<< + * _import_umath() + * except Exception: + */ + __Pyx_XGIVEREF(__pyx_t_1); + __Pyx_XGIVEREF(__pyx_t_2); + __Pyx_XGIVEREF(__pyx_t_3); + __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3); + goto __pyx_L1_error; + __pyx_L8_try_end:; + } + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1045 + * raise ImportError("numpy.core.umath failed to import") + * + * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< + * try: + * _import_umath() + */ + + /* function exit code */ + __pyx_r = 0; + goto __pyx_L0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_5); + __Pyx_XDECREF(__pyx_t_6); + __Pyx_XDECREF(__pyx_t_7); + __Pyx_XDECREF(__pyx_t_8); + __Pyx_AddTraceback("numpy.import_ufunc", __pyx_clineno, __pyx_lineno, __pyx_filename); + __pyx_r = -1; + __pyx_L0:; + __Pyx_RefNannyFinishContext(); + return __pyx_r; +} + +static PyMethodDef __pyx_methods[] = { + {0, 0, 0, 0} +}; + +#if PY_MAJOR_VERSION >= 3 +#if CYTHON_PEP489_MULTI_PHASE_INIT +static PyObject* __pyx_pymod_create(PyObject *spec, PyModuleDef *def); /*proto*/ +static int __pyx_pymod_exec_nearest_neighbors(PyObject* module); /*proto*/ +static PyModuleDef_Slot __pyx_moduledef_slots[] = { + {Py_mod_create, (void*)__pyx_pymod_create}, + {Py_mod_exec, (void*)__pyx_pymod_exec_nearest_neighbors}, + {0, NULL} +}; +#endif + +static struct PyModuleDef __pyx_moduledef = { + PyModuleDef_HEAD_INIT, + "nearest_neighbors", + 0, /* m_doc */ + #if CYTHON_PEP489_MULTI_PHASE_INIT + 0, /* m_size */ + #else + -1, /* m_size */ + #endif + __pyx_methods /* m_methods */, + #if CYTHON_PEP489_MULTI_PHASE_INIT + __pyx_moduledef_slots, /* m_slots */ + #else + NULL, /* m_reload */ + #endif + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL /* m_free */ +}; +#endif +#ifndef CYTHON_SMALL_CODE +#if defined(__clang__) + #define CYTHON_SMALL_CODE +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define CYTHON_SMALL_CODE __attribute__((cold)) +#else + #define CYTHON_SMALL_CODE +#endif +#endif + +static __Pyx_StringTabEntry __pyx_string_tab[] = { + {&__pyx_kp_u_Format_string_allocated_too_shor, __pyx_k_Format_string_allocated_too_shor, sizeof(__pyx_k_Format_string_allocated_too_shor), 0, 1, 0, 0}, + {&__pyx_kp_u_Format_string_allocated_too_shor_2, __pyx_k_Format_string_allocated_too_shor_2, sizeof(__pyx_k_Format_string_allocated_too_shor_2), 0, 1, 0, 0}, + {&__pyx_n_s_ImportError, __pyx_k_ImportError, sizeof(__pyx_k_ImportError), 0, 0, 1, 1}, + {&__pyx_n_s_K, __pyx_k_K, sizeof(__pyx_k_K), 0, 0, 1, 1}, + {&__pyx_n_s_K_cpp, __pyx_k_K_cpp, sizeof(__pyx_k_K_cpp), 0, 0, 1, 1}, + {&__pyx_kp_u_Non_native_byte_order_not_suppor, __pyx_k_Non_native_byte_order_not_suppor, sizeof(__pyx_k_Non_native_byte_order_not_suppor), 0, 1, 0, 0}, + {&__pyx_n_s_RuntimeError, __pyx_k_RuntimeError, sizeof(__pyx_k_RuntimeError), 0, 0, 1, 1}, + {&__pyx_n_s_ValueError, __pyx_k_ValueError, sizeof(__pyx_k_ValueError), 0, 0, 1, 1}, + {&__pyx_n_s_ascontiguousarray, __pyx_k_ascontiguousarray, sizeof(__pyx_k_ascontiguousarray), 0, 0, 1, 1}, + {&__pyx_n_s_batch_size, __pyx_k_batch_size, sizeof(__pyx_k_batch_size), 0, 0, 1, 1}, + {&__pyx_n_s_cline_in_traceback, __pyx_k_cline_in_traceback, sizeof(__pyx_k_cline_in_traceback), 0, 0, 1, 1}, + {&__pyx_n_s_dim, __pyx_k_dim, sizeof(__pyx_k_dim), 0, 0, 1, 1}, + {&__pyx_n_s_dtype, __pyx_k_dtype, sizeof(__pyx_k_dtype), 0, 0, 1, 1}, + {&__pyx_n_s_float32, __pyx_k_float32, sizeof(__pyx_k_float32), 0, 0, 1, 1}, + {&__pyx_n_s_import, __pyx_k_import, sizeof(__pyx_k_import), 0, 0, 1, 1}, + {&__pyx_n_s_indices, __pyx_k_indices, sizeof(__pyx_k_indices), 0, 0, 1, 1}, + {&__pyx_n_s_indices_cpp, __pyx_k_indices_cpp, sizeof(__pyx_k_indices_cpp), 0, 0, 1, 1}, + {&__pyx_n_s_int64, __pyx_k_int64, sizeof(__pyx_k_int64), 0, 0, 1, 1}, + {&__pyx_n_s_knn, __pyx_k_knn, sizeof(__pyx_k_knn), 0, 0, 1, 1}, + {&__pyx_n_s_knn_batch, __pyx_k_knn_batch, sizeof(__pyx_k_knn_batch), 0, 0, 1, 1}, + {&__pyx_n_s_knn_batch_distance_pick, __pyx_k_knn_batch_distance_pick, sizeof(__pyx_k_knn_batch_distance_pick), 0, 0, 1, 1}, + {&__pyx_kp_s_knn_pyx, __pyx_k_knn_pyx, sizeof(__pyx_k_knn_pyx), 0, 0, 1, 0}, + {&__pyx_n_s_long, __pyx_k_long, sizeof(__pyx_k_long), 0, 0, 1, 1}, + {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1}, + {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1}, + {&__pyx_kp_u_ndarray_is_not_C_contiguous, __pyx_k_ndarray_is_not_C_contiguous, sizeof(__pyx_k_ndarray_is_not_C_contiguous), 0, 1, 0, 0}, + {&__pyx_kp_u_ndarray_is_not_Fortran_contiguou, __pyx_k_ndarray_is_not_Fortran_contiguou, sizeof(__pyx_k_ndarray_is_not_Fortran_contiguou), 0, 1, 0, 0}, + {&__pyx_n_s_nearest_neighbors, __pyx_k_nearest_neighbors, sizeof(__pyx_k_nearest_neighbors), 0, 0, 1, 1}, + {&__pyx_n_s_np, __pyx_k_np, sizeof(__pyx_k_np), 0, 0, 1, 1}, + {&__pyx_n_s_npts, __pyx_k_npts, sizeof(__pyx_k_npts), 0, 0, 1, 1}, + {&__pyx_n_s_nqueries, __pyx_k_nqueries, sizeof(__pyx_k_nqueries), 0, 0, 1, 1}, + {&__pyx_n_s_nqueries_cpp, __pyx_k_nqueries_cpp, sizeof(__pyx_k_nqueries_cpp), 0, 0, 1, 1}, + {&__pyx_n_s_numpy, __pyx_k_numpy, sizeof(__pyx_k_numpy), 0, 0, 1, 1}, + {&__pyx_kp_s_numpy_core_multiarray_failed_to, __pyx_k_numpy_core_multiarray_failed_to, sizeof(__pyx_k_numpy_core_multiarray_failed_to), 0, 0, 1, 0}, + {&__pyx_kp_s_numpy_core_umath_failed_to_impor, __pyx_k_numpy_core_umath_failed_to_impor, sizeof(__pyx_k_numpy_core_umath_failed_to_impor), 0, 0, 1, 0}, + {&__pyx_n_s_omp, __pyx_k_omp, sizeof(__pyx_k_omp), 0, 0, 1, 1}, + {&__pyx_n_s_pts, __pyx_k_pts, sizeof(__pyx_k_pts), 0, 0, 1, 1}, + {&__pyx_n_s_pts_cpp, __pyx_k_pts_cpp, sizeof(__pyx_k_pts_cpp), 0, 0, 1, 1}, + {&__pyx_n_s_queries, __pyx_k_queries, sizeof(__pyx_k_queries), 0, 0, 1, 1}, + {&__pyx_n_s_queries_cpp, __pyx_k_queries_cpp, sizeof(__pyx_k_queries_cpp), 0, 0, 1, 1}, + {&__pyx_n_s_range, __pyx_k_range, sizeof(__pyx_k_range), 0, 0, 1, 1}, + {&__pyx_n_s_shape, __pyx_k_shape, sizeof(__pyx_k_shape), 0, 0, 1, 1}, + {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1}, + {&__pyx_kp_u_unknown_dtype_code_in_numpy_pxd, __pyx_k_unknown_dtype_code_in_numpy_pxd, sizeof(__pyx_k_unknown_dtype_code_in_numpy_pxd), 0, 1, 0, 0}, + {&__pyx_n_s_zeros, __pyx_k_zeros, sizeof(__pyx_k_zeros), 0, 0, 1, 1}, + {0, 0, 0, 0, 0, 0, 0} +}; +static CYTHON_SMALL_CODE int __Pyx_InitCachedBuiltins(void) { + __pyx_builtin_ValueError = __Pyx_GetBuiltinName(__pyx_n_s_ValueError); if (!__pyx_builtin_ValueError) __PYX_ERR(1, 272, __pyx_L1_error) + __pyx_builtin_range = __Pyx_GetBuiltinName(__pyx_n_s_range); if (!__pyx_builtin_range) __PYX_ERR(1, 285, __pyx_L1_error) + __pyx_builtin_RuntimeError = __Pyx_GetBuiltinName(__pyx_n_s_RuntimeError); if (!__pyx_builtin_RuntimeError) __PYX_ERR(1, 855, __pyx_L1_error) + __pyx_builtin_ImportError = __Pyx_GetBuiltinName(__pyx_n_s_ImportError); if (!__pyx_builtin_ImportError) __PYX_ERR(1, 1037, __pyx_L1_error) + return 0; + __pyx_L1_error:; + return -1; +} + +static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":272 + * if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)): + * raise ValueError(u"ndarray is not C contiguous") # <<<<<<<<<<<<<< + * + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + */ + __pyx_tuple_ = PyTuple_Pack(1, __pyx_kp_u_ndarray_is_not_C_contiguous); if (unlikely(!__pyx_tuple_)) __PYX_ERR(1, 272, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple_); + __Pyx_GIVEREF(__pyx_tuple_); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":276 + * if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS) + * and not PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)): + * raise ValueError(u"ndarray is not Fortran contiguous") # <<<<<<<<<<<<<< + * + * info.buf = PyArray_DATA(self) + */ + __pyx_tuple__2 = PyTuple_Pack(1, __pyx_kp_u_ndarray_is_not_Fortran_contiguou); if (unlikely(!__pyx_tuple__2)) __PYX_ERR(1, 276, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__2); + __Pyx_GIVEREF(__pyx_tuple__2); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":306 + * if ((descr.byteorder == c'>' and little_endian) or + * (descr.byteorder == c'<' and not little_endian)): + * raise ValueError(u"Non-native byte order not supported") # <<<<<<<<<<<<<< + * if t == NPY_BYTE: f = "b" + * elif t == NPY_UBYTE: f = "B" + */ + __pyx_tuple__3 = PyTuple_Pack(1, __pyx_kp_u_Non_native_byte_order_not_suppor); if (unlikely(!__pyx_tuple__3)) __PYX_ERR(1, 306, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__3); + __Pyx_GIVEREF(__pyx_tuple__3); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":855 + * + * if (end - f) - (new_offset - offset[0]) < 15: + * raise RuntimeError(u"Format string allocated too short, see comment in numpy.pxd") # <<<<<<<<<<<<<< + * + * if ((child.byteorder == c'>' and little_endian) or + */ + __pyx_tuple__4 = PyTuple_Pack(1, __pyx_kp_u_Format_string_allocated_too_shor); if (unlikely(!__pyx_tuple__4)) __PYX_ERR(1, 855, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__4); + __Pyx_GIVEREF(__pyx_tuple__4); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":879 + * t = child.type_num + * if end - f < 5: + * raise RuntimeError(u"Format string allocated too short.") # <<<<<<<<<<<<<< + * + * # Until ticket #99 is fixed, use integers to avoid warnings + */ + __pyx_tuple__5 = PyTuple_Pack(1, __pyx_kp_u_Format_string_allocated_too_shor_2); if (unlikely(!__pyx_tuple__5)) __PYX_ERR(1, 879, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__5); + __Pyx_GIVEREF(__pyx_tuple__5); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1037 + * _import_array() + * except Exception: + * raise ImportError("numpy.core.multiarray failed to import") # <<<<<<<<<<<<<< + * + * cdef inline int import_umath() except -1: + */ + __pyx_tuple__6 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_multiarray_failed_to); if (unlikely(!__pyx_tuple__6)) __PYX_ERR(1, 1037, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__6); + __Pyx_GIVEREF(__pyx_tuple__6); + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1043 + * _import_umath() + * except Exception: + * raise ImportError("numpy.core.umath failed to import") # <<<<<<<<<<<<<< + * + * cdef inline int import_ufunc() except -1: + */ + __pyx_tuple__7 = PyTuple_Pack(1, __pyx_kp_s_numpy_core_umath_failed_to_impor); if (unlikely(!__pyx_tuple__7)) __PYX_ERR(1, 1043, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__7); + __Pyx_GIVEREF(__pyx_tuple__7); + + /* "knn.pyx":33 + * const size_t K, long* batch_indices) + * + * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_tuple__8 = PyTuple_Pack(12, __pyx_n_s_pts, __pyx_n_s_queries, __pyx_n_s_K, __pyx_n_s_omp, __pyx_n_s_npts, __pyx_n_s_dim, __pyx_n_s_K_cpp, __pyx_n_s_nqueries, __pyx_n_s_pts_cpp, __pyx_n_s_queries_cpp, __pyx_n_s_indices_cpp, __pyx_n_s_indices); if (unlikely(!__pyx_tuple__8)) __PYX_ERR(0, 33, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__8); + __Pyx_GIVEREF(__pyx_tuple__8); + __pyx_codeobj__9 = (PyObject*)__Pyx_PyCode_New(4, 0, 12, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__8, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_knn_pyx, __pyx_n_s_knn, 33, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__9)) __PYX_ERR(0, 33, __pyx_L1_error) + + /* "knn.pyx":71 + * return indices + * + * def knn_batch(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_tuple__10 = PyTuple_Pack(13, __pyx_n_s_pts, __pyx_n_s_queries, __pyx_n_s_K, __pyx_n_s_omp, __pyx_n_s_batch_size, __pyx_n_s_npts, __pyx_n_s_nqueries, __pyx_n_s_K_cpp, __pyx_n_s_dim, __pyx_n_s_pts_cpp, __pyx_n_s_queries_cpp, __pyx_n_s_indices_cpp, __pyx_n_s_indices); if (unlikely(!__pyx_tuple__10)) __PYX_ERR(0, 71, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__10); + __Pyx_GIVEREF(__pyx_tuple__10); + __pyx_codeobj__11 = (PyObject*)__Pyx_PyCode_New(4, 0, 13, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__10, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_knn_pyx, __pyx_n_s_knn_batch, 71, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__11)) __PYX_ERR(0, 71, __pyx_L1_error) + + /* "knn.pyx":111 + * return indices + * + * def knn_batch_distance_pick(pts, nqueries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_tuple__12 = PyTuple_Pack(14, __pyx_n_s_pts, __pyx_n_s_nqueries, __pyx_n_s_K, __pyx_n_s_omp, __pyx_n_s_batch_size, __pyx_n_s_npts, __pyx_n_s_nqueries_cpp, __pyx_n_s_K_cpp, __pyx_n_s_dim, __pyx_n_s_pts_cpp, __pyx_n_s_queries_cpp, __pyx_n_s_indices_cpp, __pyx_n_s_indices, __pyx_n_s_queries); if (unlikely(!__pyx_tuple__12)) __PYX_ERR(0, 111, __pyx_L1_error) + __Pyx_GOTREF(__pyx_tuple__12); + __Pyx_GIVEREF(__pyx_tuple__12); + __pyx_codeobj__13 = (PyObject*)__Pyx_PyCode_New(4, 0, 14, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__12, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_knn_pyx, __pyx_n_s_knn_batch_distance_pick, 111, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__13)) __PYX_ERR(0, 111, __pyx_L1_error) + __Pyx_RefNannyFinishContext(); + return 0; + __pyx_L1_error:; + __Pyx_RefNannyFinishContext(); + return -1; +} + +static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) { + if (__Pyx_InitStrings(__pyx_string_tab) < 0) __PYX_ERR(0, 1, __pyx_L1_error); + return 0; + __pyx_L1_error:; + return -1; +} + +static CYTHON_SMALL_CODE int __Pyx_modinit_global_init_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_variable_export_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_function_export_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_type_init_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_type_import_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_variable_import_code(void); /*proto*/ +static CYTHON_SMALL_CODE int __Pyx_modinit_function_import_code(void); /*proto*/ + +static int __Pyx_modinit_global_init_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_global_init_code", 0); + /*--- Global init code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + +static int __Pyx_modinit_variable_export_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_variable_export_code", 0); + /*--- Variable export code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + +static int __Pyx_modinit_function_export_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_function_export_code", 0); + /*--- Function export code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + +static int __Pyx_modinit_type_init_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_type_init_code", 0); + /*--- Type init code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + +static int __Pyx_modinit_type_import_code(void) { + __Pyx_RefNannyDeclarations + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannySetupContext("__Pyx_modinit_type_import_code", 0); + /*--- Type import code ---*/ + __pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 9, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_ptype_7cpython_4type_type = __Pyx_ImportType(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", + #if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000 + sizeof(PyTypeObject), + #else + sizeof(PyHeapTypeObject), + #endif + __Pyx_ImportType_CheckSize_Warn); + if (!__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __pyx_t_1 = PyImport_ImportModule("numpy"); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 206, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + __pyx_ptype_5numpy_dtype = __Pyx_ImportType(__pyx_t_1, "numpy", "dtype", sizeof(PyArray_Descr), __Pyx_ImportType_CheckSize_Ignore); + if (!__pyx_ptype_5numpy_dtype) __PYX_ERR(1, 206, __pyx_L1_error) + __pyx_ptype_5numpy_flatiter = __Pyx_ImportType(__pyx_t_1, "numpy", "flatiter", sizeof(PyArrayIterObject), __Pyx_ImportType_CheckSize_Warn); + if (!__pyx_ptype_5numpy_flatiter) __PYX_ERR(1, 229, __pyx_L1_error) + __pyx_ptype_5numpy_broadcast = __Pyx_ImportType(__pyx_t_1, "numpy", "broadcast", sizeof(PyArrayMultiIterObject), __Pyx_ImportType_CheckSize_Warn); + if (!__pyx_ptype_5numpy_broadcast) __PYX_ERR(1, 233, __pyx_L1_error) + __pyx_ptype_5numpy_ndarray = __Pyx_ImportType(__pyx_t_1, "numpy", "ndarray", sizeof(PyArrayObject), __Pyx_ImportType_CheckSize_Ignore); + if (!__pyx_ptype_5numpy_ndarray) __PYX_ERR(1, 242, __pyx_L1_error) + __pyx_ptype_5numpy_ufunc = __Pyx_ImportType(__pyx_t_1, "numpy", "ufunc", sizeof(PyUFuncObject), __Pyx_ImportType_CheckSize_Warn); + if (!__pyx_ptype_5numpy_ufunc) __PYX_ERR(1, 917, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + __Pyx_RefNannyFinishContext(); + return 0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + __Pyx_RefNannyFinishContext(); + return -1; +} + +static int __Pyx_modinit_variable_import_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_variable_import_code", 0); + /*--- Variable import code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + +static int __Pyx_modinit_function_import_code(void) { + __Pyx_RefNannyDeclarations + __Pyx_RefNannySetupContext("__Pyx_modinit_function_import_code", 0); + /*--- Function import code ---*/ + __Pyx_RefNannyFinishContext(); + return 0; +} + + +#ifndef CYTHON_NO_PYINIT_EXPORT +#define __Pyx_PyMODINIT_FUNC PyMODINIT_FUNC +#elif PY_MAJOR_VERSION < 3 +#ifdef __cplusplus +#define __Pyx_PyMODINIT_FUNC extern "C" void +#else +#define __Pyx_PyMODINIT_FUNC void +#endif +#else +#ifdef __cplusplus +#define __Pyx_PyMODINIT_FUNC extern "C" PyObject * +#else +#define __Pyx_PyMODINIT_FUNC PyObject * +#endif +#endif + + +#if PY_MAJOR_VERSION < 3 +__Pyx_PyMODINIT_FUNC initnearest_neighbors(void) CYTHON_SMALL_CODE; /*proto*/ +__Pyx_PyMODINIT_FUNC initnearest_neighbors(void) +#else +__Pyx_PyMODINIT_FUNC PyInit_nearest_neighbors(void) CYTHON_SMALL_CODE; /*proto*/ +__Pyx_PyMODINIT_FUNC PyInit_nearest_neighbors(void) +#if CYTHON_PEP489_MULTI_PHASE_INIT +{ + return PyModuleDef_Init(&__pyx_moduledef); +} +static CYTHON_SMALL_CODE int __Pyx_check_single_interpreter(void) { + #if PY_VERSION_HEX >= 0x030700A1 + static PY_INT64_T main_interpreter_id = -1; + PY_INT64_T current_id = PyInterpreterState_GetID(PyThreadState_Get()->interp); + if (main_interpreter_id == -1) { + main_interpreter_id = current_id; + return (unlikely(current_id == -1)) ? -1 : 0; + } else if (unlikely(main_interpreter_id != current_id)) + #else + static PyInterpreterState *main_interpreter = NULL; + PyInterpreterState *current_interpreter = PyThreadState_Get()->interp; + if (!main_interpreter) { + main_interpreter = current_interpreter; + } else if (unlikely(main_interpreter != current_interpreter)) + #endif + { + PyErr_SetString( + PyExc_ImportError, + "Interpreter change detected - this module can only be loaded into one interpreter per process."); + return -1; + } + return 0; +} +static CYTHON_SMALL_CODE int __Pyx_copy_spec_to_module(PyObject *spec, PyObject *moddict, const char* from_name, const char* to_name, int allow_none) { + PyObject *value = PyObject_GetAttrString(spec, from_name); + int result = 0; + if (likely(value)) { + if (allow_none || value != Py_None) { + result = PyDict_SetItemString(moddict, to_name, value); + } + Py_DECREF(value); + } else if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + } else { + result = -1; + } + return result; +} +static CYTHON_SMALL_CODE PyObject* __pyx_pymod_create(PyObject *spec, CYTHON_UNUSED PyModuleDef *def) { + PyObject *module = NULL, *moddict, *modname; + if (__Pyx_check_single_interpreter()) + return NULL; + if (__pyx_m) + return __Pyx_NewRef(__pyx_m); + modname = PyObject_GetAttrString(spec, "name"); + if (unlikely(!modname)) goto bad; + module = PyModule_NewObject(modname); + Py_DECREF(modname); + if (unlikely(!module)) goto bad; + moddict = PyModule_GetDict(module); + if (unlikely(!moddict)) goto bad; + if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "loader", "__loader__", 1) < 0)) goto bad; + if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "origin", "__file__", 1) < 0)) goto bad; + if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "parent", "__package__", 1) < 0)) goto bad; + if (unlikely(__Pyx_copy_spec_to_module(spec, moddict, "submodule_search_locations", "__path__", 0) < 0)) goto bad; + return module; +bad: + Py_XDECREF(module); + return NULL; +} + + +static CYTHON_SMALL_CODE int __pyx_pymod_exec_nearest_neighbors(PyObject *__pyx_pyinit_module) +#endif +#endif +{ + PyObject *__pyx_t_1 = NULL; + int __pyx_lineno = 0; + const char *__pyx_filename = NULL; + int __pyx_clineno = 0; + __Pyx_RefNannyDeclarations + #if CYTHON_PEP489_MULTI_PHASE_INIT + if (__pyx_m) { + if (__pyx_m == __pyx_pyinit_module) return 0; + PyErr_SetString(PyExc_RuntimeError, "Module 'nearest_neighbors' has already been imported. Re-initialisation is not supported."); + return -1; + } + #elif PY_MAJOR_VERSION >= 3 + if (__pyx_m) return __Pyx_NewRef(__pyx_m); + #endif + #if CYTHON_REFNANNY +__Pyx_RefNanny = __Pyx_RefNannyImportAPI("refnanny"); +if (!__Pyx_RefNanny) { + PyErr_Clear(); + __Pyx_RefNanny = __Pyx_RefNannyImportAPI("Cython.Runtime.refnanny"); + if (!__Pyx_RefNanny) + Py_FatalError("failed to import 'refnanny' module"); +} +#endif + __Pyx_RefNannySetupContext("__Pyx_PyMODINIT_FUNC PyInit_nearest_neighbors(void)", 0); + if (__Pyx_check_binary_version() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #ifdef __Pxy_PyFrame_Initialize_Offsets + __Pxy_PyFrame_Initialize_Offsets(); + #endif + __pyx_empty_tuple = PyTuple_New(0); if (unlikely(!__pyx_empty_tuple)) __PYX_ERR(0, 1, __pyx_L1_error) + __pyx_empty_bytes = PyBytes_FromStringAndSize("", 0); if (unlikely(!__pyx_empty_bytes)) __PYX_ERR(0, 1, __pyx_L1_error) + __pyx_empty_unicode = PyUnicode_FromStringAndSize("", 0); if (unlikely(!__pyx_empty_unicode)) __PYX_ERR(0, 1, __pyx_L1_error) + #ifdef __Pyx_CyFunction_USED + if (__pyx_CyFunction_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + #ifdef __Pyx_FusedFunction_USED + if (__pyx_FusedFunction_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + #ifdef __Pyx_Coroutine_USED + if (__pyx_Coroutine_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + #ifdef __Pyx_Generator_USED + if (__pyx_Generator_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + #ifdef __Pyx_AsyncGen_USED + if (__pyx_AsyncGen_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + #ifdef __Pyx_StopAsyncIteration_USED + if (__pyx_StopAsyncIteration_init() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + /*--- Library function declarations ---*/ + /*--- Threads initialization code ---*/ + #if defined(__PYX_FORCE_INIT_THREADS) && __PYX_FORCE_INIT_THREADS + #ifdef WITH_THREAD /* Python build with threading support? */ + PyEval_InitThreads(); + #endif + #endif + /*--- Module creation code ---*/ + #if CYTHON_PEP489_MULTI_PHASE_INIT + __pyx_m = __pyx_pyinit_module; + Py_INCREF(__pyx_m); + #else + #if PY_MAJOR_VERSION < 3 + __pyx_m = Py_InitModule4("nearest_neighbors", __pyx_methods, 0, 0, PYTHON_API_VERSION); Py_XINCREF(__pyx_m); + #else + __pyx_m = PyModule_Create(&__pyx_moduledef); + #endif + if (unlikely(!__pyx_m)) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + __pyx_d = PyModule_GetDict(__pyx_m); if (unlikely(!__pyx_d)) __PYX_ERR(0, 1, __pyx_L1_error) + Py_INCREF(__pyx_d); + __pyx_b = PyImport_AddModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_b)) __PYX_ERR(0, 1, __pyx_L1_error) + Py_INCREF(__pyx_b); + __pyx_cython_runtime = PyImport_AddModule((char *) "cython_runtime"); if (unlikely(!__pyx_cython_runtime)) __PYX_ERR(0, 1, __pyx_L1_error) + Py_INCREF(__pyx_cython_runtime); + if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) __PYX_ERR(0, 1, __pyx_L1_error); + /*--- Initialize various global constants etc. ---*/ + if (__Pyx_InitGlobals() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT) + if (__Pyx_init_sys_getdefaultencoding_params() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + if (__pyx_module_is_main_nearest_neighbors) { + if (PyObject_SetAttr(__pyx_m, __pyx_n_s_name, __pyx_n_s_main) < 0) __PYX_ERR(0, 1, __pyx_L1_error) + } + #if PY_MAJOR_VERSION >= 3 + { + PyObject *modules = PyImport_GetModuleDict(); if (unlikely(!modules)) __PYX_ERR(0, 1, __pyx_L1_error) + if (!PyDict_GetItemString(modules, "nearest_neighbors")) { + if (unlikely(PyDict_SetItemString(modules, "nearest_neighbors", __pyx_m) < 0)) __PYX_ERR(0, 1, __pyx_L1_error) + } + } + #endif + /*--- Builtin init code ---*/ + if (__Pyx_InitCachedBuiltins() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + /*--- Constants init code ---*/ + if (__Pyx_InitCachedConstants() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + /*--- Global type/function init code ---*/ + (void)__Pyx_modinit_global_init_code(); + (void)__Pyx_modinit_variable_export_code(); + (void)__Pyx_modinit_function_export_code(); + (void)__Pyx_modinit_type_init_code(); + if (unlikely(__Pyx_modinit_type_import_code() < 0)) __PYX_ERR(0, 1, __pyx_L1_error) + (void)__Pyx_modinit_variable_import_code(); + (void)__Pyx_modinit_function_import_code(); + /*--- Execution code ---*/ + #if defined(__Pyx_Generator_USED) || defined(__Pyx_Coroutine_USED) + if (__Pyx_patch_abc() < 0) __PYX_ERR(0, 1, __pyx_L1_error) + #endif + + /* "knn.pyx":4 + * # distutils: sources = knn.cxx + * + * import numpy as np # <<<<<<<<<<<<<< + * cimport numpy as np + * import cython + */ + __pyx_t_1 = __Pyx_Import(__pyx_n_s_numpy, 0, -1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 4, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + if (PyDict_SetItem(__pyx_d, __pyx_n_s_np, __pyx_t_1) < 0) __PYX_ERR(0, 4, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "knn.pyx":33 + * const size_t K, long* batch_indices) + * + * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_17nearest_neighbors_1knn, NULL, __pyx_n_s_nearest_neighbors); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 33, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + if (PyDict_SetItem(__pyx_d, __pyx_n_s_knn, __pyx_t_1) < 0) __PYX_ERR(0, 33, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "knn.pyx":71 + * return indices + * + * def knn_batch(pts, queries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_17nearest_neighbors_3knn_batch, NULL, __pyx_n_s_nearest_neighbors); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 71, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + if (PyDict_SetItem(__pyx_d, __pyx_n_s_knn_batch, __pyx_t_1) < 0) __PYX_ERR(0, 71, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "knn.pyx":111 + * return indices + * + * def knn_batch_distance_pick(pts, nqueries, K, omp=False): # <<<<<<<<<<<<<< + * + * # define shape parameters + */ + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_17nearest_neighbors_5knn_batch_distance_pick, NULL, __pyx_n_s_nearest_neighbors); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 111, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + if (PyDict_SetItem(__pyx_d, __pyx_n_s_knn_batch_distance_pick, __pyx_t_1) < 0) __PYX_ERR(0, 111, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "knn.pyx":1 + * # distutils: language = c++ # <<<<<<<<<<<<<< + * # distutils: sources = knn.cxx + * + */ + __pyx_t_1 = __Pyx_PyDict_NewPresized(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error) + __Pyx_GOTREF(__pyx_t_1); + if (PyDict_SetItem(__pyx_d, __pyx_n_s_test, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error) + __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; + + /* "../../../../../viplab/anaconda3/envs/tf/lib/python3.6/site-packages/Cython/Includes/numpy/__init__.pxd":1045 + * raise ImportError("numpy.core.umath failed to import") + * + * cdef inline int import_ufunc() except -1: # <<<<<<<<<<<<<< + * try: + * _import_umath() + */ + + /*--- Wrapped vars code ---*/ + + goto __pyx_L0; + __pyx_L1_error:; + __Pyx_XDECREF(__pyx_t_1); + if (__pyx_m) { + if (__pyx_d) { + __Pyx_AddTraceback("init nearest_neighbors", __pyx_clineno, __pyx_lineno, __pyx_filename); + } + Py_CLEAR(__pyx_m); + } else if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ImportError, "init nearest_neighbors"); + } + __pyx_L0:; + __Pyx_RefNannyFinishContext(); + #if CYTHON_PEP489_MULTI_PHASE_INIT + return (__pyx_m != NULL) ? 0 : -1; + #elif PY_MAJOR_VERSION >= 3 + return __pyx_m; + #else + return; + #endif +} + +/* --- Runtime support code --- */ +/* Refnanny */ +#if CYTHON_REFNANNY +static __Pyx_RefNannyAPIStruct *__Pyx_RefNannyImportAPI(const char *modname) { + PyObject *m = NULL, *p = NULL; + void *r = NULL; + m = PyImport_ImportModule(modname); + if (!m) goto end; + p = PyObject_GetAttrString(m, "RefNannyAPI"); + if (!p) goto end; + r = PyLong_AsVoidPtr(p); +end: + Py_XDECREF(p); + Py_XDECREF(m); + return (__Pyx_RefNannyAPIStruct *)r; +} +#endif + +/* RaiseArgTupleInvalid */ +static void __Pyx_RaiseArgtupleInvalid( + const char* func_name, + int exact, + Py_ssize_t num_min, + Py_ssize_t num_max, + Py_ssize_t num_found) +{ + Py_ssize_t num_expected; + const char *more_or_less; + if (num_found < num_min) { + num_expected = num_min; + more_or_less = "at least"; + } else { + num_expected = num_max; + more_or_less = "at most"; + } + if (exact) { + more_or_less = "exactly"; + } + PyErr_Format(PyExc_TypeError, + "%.200s() takes %.8s %" CYTHON_FORMAT_SSIZE_T "d positional argument%.1s (%" CYTHON_FORMAT_SSIZE_T "d given)", + func_name, more_or_less, num_expected, + (num_expected == 1) ? "" : "s", num_found); +} + +/* RaiseDoubleKeywords */ +static void __Pyx_RaiseDoubleKeywordsError( + const char* func_name, + PyObject* kw_name) +{ + PyErr_Format(PyExc_TypeError, + #if PY_MAJOR_VERSION >= 3 + "%s() got multiple values for keyword argument '%U'", func_name, kw_name); + #else + "%s() got multiple values for keyword argument '%s'", func_name, + PyString_AsString(kw_name)); + #endif +} + +/* ParseKeywords */ +static int __Pyx_ParseOptionalKeywords( + PyObject *kwds, + PyObject **argnames[], + PyObject *kwds2, + PyObject *values[], + Py_ssize_t num_pos_args, + const char* function_name) +{ + PyObject *key = 0, *value = 0; + Py_ssize_t pos = 0; + PyObject*** name; + PyObject*** first_kw_arg = argnames + num_pos_args; + while (PyDict_Next(kwds, &pos, &key, &value)) { + name = first_kw_arg; + while (*name && (**name != key)) name++; + if (*name) { + values[name-argnames] = value; + continue; + } + name = first_kw_arg; + #if PY_MAJOR_VERSION < 3 + if (likely(PyString_Check(key))) { + while (*name) { + if ((CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**name) == PyString_GET_SIZE(key)) + && _PyString_Eq(**name, key)) { + values[name-argnames] = value; + break; + } + name++; + } + if (*name) continue; + else { + PyObject*** argname = argnames; + while (argname != first_kw_arg) { + if ((**argname == key) || ( + (CYTHON_COMPILING_IN_PYPY || PyString_GET_SIZE(**argname) == PyString_GET_SIZE(key)) + && _PyString_Eq(**argname, key))) { + goto arg_passed_twice; + } + argname++; + } + } + } else + #endif + if (likely(PyUnicode_Check(key))) { + while (*name) { + int cmp = (**name == key) ? 0 : + #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3 + (__Pyx_PyUnicode_GET_LENGTH(**name) != __Pyx_PyUnicode_GET_LENGTH(key)) ? 1 : + #endif + PyUnicode_Compare(**name, key); + if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad; + if (cmp == 0) { + values[name-argnames] = value; + break; + } + name++; + } + if (*name) continue; + else { + PyObject*** argname = argnames; + while (argname != first_kw_arg) { + int cmp = (**argname == key) ? 0 : + #if !CYTHON_COMPILING_IN_PYPY && PY_MAJOR_VERSION >= 3 + (__Pyx_PyUnicode_GET_LENGTH(**argname) != __Pyx_PyUnicode_GET_LENGTH(key)) ? 1 : + #endif + PyUnicode_Compare(**argname, key); + if (cmp < 0 && unlikely(PyErr_Occurred())) goto bad; + if (cmp == 0) goto arg_passed_twice; + argname++; + } + } + } else + goto invalid_keyword_type; + if (kwds2) { + if (unlikely(PyDict_SetItem(kwds2, key, value))) goto bad; + } else { + goto invalid_keyword; + } + } + return 0; +arg_passed_twice: + __Pyx_RaiseDoubleKeywordsError(function_name, key); + goto bad; +invalid_keyword_type: + PyErr_Format(PyExc_TypeError, + "%.200s() keywords must be strings", function_name); + goto bad; +invalid_keyword: + PyErr_Format(PyExc_TypeError, + #if PY_MAJOR_VERSION < 3 + "%.200s() got an unexpected keyword argument '%.200s'", + function_name, PyString_AsString(key)); + #else + "%s() got an unexpected keyword argument '%U'", + function_name, key); + #endif +bad: + return -1; +} + +/* PyObjectGetAttrStr */ +#if CYTHON_USE_TYPE_SLOTS +static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name) { + PyTypeObject* tp = Py_TYPE(obj); + if (likely(tp->tp_getattro)) + return tp->tp_getattro(obj, attr_name); +#if PY_MAJOR_VERSION < 3 + if (likely(tp->tp_getattr)) + return tp->tp_getattr(obj, PyString_AS_STRING(attr_name)); +#endif + return PyObject_GetAttr(obj, attr_name); +} +#endif + +/* GetItemInt */ +static PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) { + PyObject *r; + if (!j) return NULL; + r = PyObject_GetItem(o, j); + Py_DECREF(j); + return r; +} +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i, + CYTHON_NCP_UNUSED int wraparound, + CYTHON_NCP_UNUSED int boundscheck) { +#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS + Py_ssize_t wrapped_i = i; + if (wraparound & unlikely(i < 0)) { + wrapped_i += PyList_GET_SIZE(o); + } + if ((!boundscheck) || likely(__Pyx_is_valid_index(wrapped_i, PyList_GET_SIZE(o)))) { + PyObject *r = PyList_GET_ITEM(o, wrapped_i); + Py_INCREF(r); + return r; + } + return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); +#else + return PySequence_GetItem(o, i); +#endif +} +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i, + CYTHON_NCP_UNUSED int wraparound, + CYTHON_NCP_UNUSED int boundscheck) { +#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS + Py_ssize_t wrapped_i = i; + if (wraparound & unlikely(i < 0)) { + wrapped_i += PyTuple_GET_SIZE(o); + } + if ((!boundscheck) || likely(__Pyx_is_valid_index(wrapped_i, PyTuple_GET_SIZE(o)))) { + PyObject *r = PyTuple_GET_ITEM(o, wrapped_i); + Py_INCREF(r); + return r; + } + return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); +#else + return PySequence_GetItem(o, i); +#endif +} +static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i, int is_list, + CYTHON_NCP_UNUSED int wraparound, + CYTHON_NCP_UNUSED int boundscheck) { +#if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS && CYTHON_USE_TYPE_SLOTS + if (is_list || PyList_CheckExact(o)) { + Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o); + if ((!boundscheck) || (likely(__Pyx_is_valid_index(n, PyList_GET_SIZE(o))))) { + PyObject *r = PyList_GET_ITEM(o, n); + Py_INCREF(r); + return r; + } + } + else if (PyTuple_CheckExact(o)) { + Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o); + if ((!boundscheck) || likely(__Pyx_is_valid_index(n, PyTuple_GET_SIZE(o)))) { + PyObject *r = PyTuple_GET_ITEM(o, n); + Py_INCREF(r); + return r; + } + } else { + PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence; + if (likely(m && m->sq_item)) { + if (wraparound && unlikely(i < 0) && likely(m->sq_length)) { + Py_ssize_t l = m->sq_length(o); + if (likely(l >= 0)) { + i += l; + } else { + if (!PyErr_ExceptionMatches(PyExc_OverflowError)) + return NULL; + PyErr_Clear(); + } + } + return m->sq_item(o, i); + } + } +#else + if (is_list || PySequence_Check(o)) { + return PySequence_GetItem(o, i); + } +#endif + return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i)); +} + +/* GetBuiltinName */ +static PyObject *__Pyx_GetBuiltinName(PyObject *name) { + PyObject* result = __Pyx_PyObject_GetAttrStr(__pyx_b, name); + if (unlikely(!result)) { + PyErr_Format(PyExc_NameError, +#if PY_MAJOR_VERSION >= 3 + "name '%U' is not defined", name); +#else + "name '%.200s' is not defined", PyString_AS_STRING(name)); +#endif + } + return result; +} + +/* PyDictVersioning */ +#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_TYPE_SLOTS +static CYTHON_INLINE PY_UINT64_T __Pyx_get_tp_dict_version(PyObject *obj) { + PyObject *dict = Py_TYPE(obj)->tp_dict; + return likely(dict) ? __PYX_GET_DICT_VERSION(dict) : 0; +} +static CYTHON_INLINE PY_UINT64_T __Pyx_get_object_dict_version(PyObject *obj) { + PyObject **dictptr = NULL; + Py_ssize_t offset = Py_TYPE(obj)->tp_dictoffset; + if (offset) { +#if CYTHON_COMPILING_IN_CPYTHON + dictptr = (likely(offset > 0)) ? (PyObject **) ((char *)obj + offset) : _PyObject_GetDictPtr(obj); +#else + dictptr = _PyObject_GetDictPtr(obj); +#endif + } + return (dictptr && *dictptr) ? __PYX_GET_DICT_VERSION(*dictptr) : 0; +} +static CYTHON_INLINE int __Pyx_object_dict_version_matches(PyObject* obj, PY_UINT64_T tp_dict_version, PY_UINT64_T obj_dict_version) { + PyObject *dict = Py_TYPE(obj)->tp_dict; + if (unlikely(!dict) || unlikely(tp_dict_version != __PYX_GET_DICT_VERSION(dict))) + return 0; + return obj_dict_version == __Pyx_get_object_dict_version(obj); +} +#endif + +/* GetModuleGlobalName */ +#if CYTHON_USE_DICT_VERSIONS +static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value) +#else +static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name) +#endif +{ + PyObject *result; +#if !CYTHON_AVOID_BORROWED_REFS +#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 + result = _PyDict_GetItem_KnownHash(__pyx_d, name, ((PyASCIIObject *) name)->hash); + __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) + if (likely(result)) { + return __Pyx_NewRef(result); + } else if (unlikely(PyErr_Occurred())) { + return NULL; + } +#else + result = PyDict_GetItem(__pyx_d, name); + __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) + if (likely(result)) { + return __Pyx_NewRef(result); + } +#endif +#else + result = PyObject_GetItem(__pyx_d, name); + __PYX_UPDATE_DICT_CACHE(__pyx_d, result, *dict_cached_value, *dict_version) + if (likely(result)) { + return __Pyx_NewRef(result); + } + PyErr_Clear(); +#endif + return __Pyx_GetBuiltinName(name); +} + +/* PyObjectCall */ +#if CYTHON_COMPILING_IN_CPYTHON +static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw) { + PyObject *result; + ternaryfunc call = func->ob_type->tp_call; + if (unlikely(!call)) + return PyObject_Call(func, arg, kw); + if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object"))) + return NULL; + result = (*call)(func, arg, kw); + Py_LeaveRecursiveCall(); + if (unlikely(!result) && unlikely(!PyErr_Occurred())) { + PyErr_SetString( + PyExc_SystemError, + "NULL result without error in PyObject_Call"); + } + return result; +} +#endif + +/* ExtTypeTest */ +static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) { + if (unlikely(!type)) { + PyErr_SetString(PyExc_SystemError, "Missing type object"); + return 0; + } + if (likely(__Pyx_TypeCheck(obj, type))) + return 1; + PyErr_Format(PyExc_TypeError, "Cannot convert %.200s to %.200s", + Py_TYPE(obj)->tp_name, type->tp_name); + return 0; +} + +/* IsLittleEndian */ +static CYTHON_INLINE int __Pyx_Is_Little_Endian(void) +{ + union { + uint32_t u32; + uint8_t u8[4]; + } S; + S.u32 = 0x01020304; + return S.u8[0] == 4; +} + +/* BufferFormatCheck */ +static void __Pyx_BufFmt_Init(__Pyx_BufFmt_Context* ctx, + __Pyx_BufFmt_StackElem* stack, + __Pyx_TypeInfo* type) { + stack[0].field = &ctx->root; + stack[0].parent_offset = 0; + ctx->root.type = type; + ctx->root.name = "buffer dtype"; + ctx->root.offset = 0; + ctx->head = stack; + ctx->head->field = &ctx->root; + ctx->fmt_offset = 0; + ctx->head->parent_offset = 0; + ctx->new_packmode = '@'; + ctx->enc_packmode = '@'; + ctx->new_count = 1; + ctx->enc_count = 0; + ctx->enc_type = 0; + ctx->is_complex = 0; + ctx->is_valid_array = 0; + ctx->struct_alignment = 0; + while (type->typegroup == 'S') { + ++ctx->head; + ctx->head->field = type->fields; + ctx->head->parent_offset = 0; + type = type->fields->type; + } +} +static int __Pyx_BufFmt_ParseNumber(const char** ts) { + int count; + const char* t = *ts; + if (*t < '0' || *t > '9') { + return -1; + } else { + count = *t++ - '0'; + while (*t >= '0' && *t <= '9') { + count *= 10; + count += *t++ - '0'; + } + } + *ts = t; + return count; +} +static int __Pyx_BufFmt_ExpectNumber(const char **ts) { + int number = __Pyx_BufFmt_ParseNumber(ts); + if (number == -1) + PyErr_Format(PyExc_ValueError,\ + "Does not understand character buffer dtype format string ('%c')", **ts); + return number; +} +static void __Pyx_BufFmt_RaiseUnexpectedChar(char ch) { + PyErr_Format(PyExc_ValueError, + "Unexpected format string character: '%c'", ch); +} +static const char* __Pyx_BufFmt_DescribeTypeChar(char ch, int is_complex) { + switch (ch) { + case '?': return "'bool'"; + case 'c': return "'char'"; + case 'b': return "'signed char'"; + case 'B': return "'unsigned char'"; + case 'h': return "'short'"; + case 'H': return "'unsigned short'"; + case 'i': return "'int'"; + case 'I': return "'unsigned int'"; + case 'l': return "'long'"; + case 'L': return "'unsigned long'"; + case 'q': return "'long long'"; + case 'Q': return "'unsigned long long'"; + case 'f': return (is_complex ? "'complex float'" : "'float'"); + case 'd': return (is_complex ? "'complex double'" : "'double'"); + case 'g': return (is_complex ? "'complex long double'" : "'long double'"); + case 'T': return "a struct"; + case 'O': return "Python object"; + case 'P': return "a pointer"; + case 's': case 'p': return "a string"; + case 0: return "end"; + default: return "unparseable format string"; + } +} +static size_t __Pyx_BufFmt_TypeCharToStandardSize(char ch, int is_complex) { + switch (ch) { + case '?': case 'c': case 'b': case 'B': case 's': case 'p': return 1; + case 'h': case 'H': return 2; + case 'i': case 'I': case 'l': case 'L': return 4; + case 'q': case 'Q': return 8; + case 'f': return (is_complex ? 8 : 4); + case 'd': return (is_complex ? 16 : 8); + case 'g': { + PyErr_SetString(PyExc_ValueError, "Python does not define a standard format string size for long double ('g').."); + return 0; + } + case 'O': case 'P': return sizeof(void*); + default: + __Pyx_BufFmt_RaiseUnexpectedChar(ch); + return 0; + } +} +static size_t __Pyx_BufFmt_TypeCharToNativeSize(char ch, int is_complex) { + switch (ch) { + case '?': case 'c': case 'b': case 'B': case 's': case 'p': return 1; + case 'h': case 'H': return sizeof(short); + case 'i': case 'I': return sizeof(int); + case 'l': case 'L': return sizeof(long); + #ifdef HAVE_LONG_LONG + case 'q': case 'Q': return sizeof(PY_LONG_LONG); + #endif + case 'f': return sizeof(float) * (is_complex ? 2 : 1); + case 'd': return sizeof(double) * (is_complex ? 2 : 1); + case 'g': return sizeof(long double) * (is_complex ? 2 : 1); + case 'O': case 'P': return sizeof(void*); + default: { + __Pyx_BufFmt_RaiseUnexpectedChar(ch); + return 0; + } + } +} +typedef struct { char c; short x; } __Pyx_st_short; +typedef struct { char c; int x; } __Pyx_st_int; +typedef struct { char c; long x; } __Pyx_st_long; +typedef struct { char c; float x; } __Pyx_st_float; +typedef struct { char c; double x; } __Pyx_st_double; +typedef struct { char c; long double x; } __Pyx_st_longdouble; +typedef struct { char c; void *x; } __Pyx_st_void_p; +#ifdef HAVE_LONG_LONG +typedef struct { char c; PY_LONG_LONG x; } __Pyx_st_longlong; +#endif +static size_t __Pyx_BufFmt_TypeCharToAlignment(char ch, CYTHON_UNUSED int is_complex) { + switch (ch) { + case '?': case 'c': case 'b': case 'B': case 's': case 'p': return 1; + case 'h': case 'H': return sizeof(__Pyx_st_short) - sizeof(short); + case 'i': case 'I': return sizeof(__Pyx_st_int) - sizeof(int); + case 'l': case 'L': return sizeof(__Pyx_st_long) - sizeof(long); +#ifdef HAVE_LONG_LONG + case 'q': case 'Q': return sizeof(__Pyx_st_longlong) - sizeof(PY_LONG_LONG); +#endif + case 'f': return sizeof(__Pyx_st_float) - sizeof(float); + case 'd': return sizeof(__Pyx_st_double) - sizeof(double); + case 'g': return sizeof(__Pyx_st_longdouble) - sizeof(long double); + case 'P': case 'O': return sizeof(__Pyx_st_void_p) - sizeof(void*); + default: + __Pyx_BufFmt_RaiseUnexpectedChar(ch); + return 0; + } +} +/* These are for computing the padding at the end of the struct to align + on the first member of the struct. This will probably the same as above, + but we don't have any guarantees. + */ +typedef struct { short x; char c; } __Pyx_pad_short; +typedef struct { int x; char c; } __Pyx_pad_int; +typedef struct { long x; char c; } __Pyx_pad_long; +typedef struct { float x; char c; } __Pyx_pad_float; +typedef struct { double x; char c; } __Pyx_pad_double; +typedef struct { long double x; char c; } __Pyx_pad_longdouble; +typedef struct { void *x; char c; } __Pyx_pad_void_p; +#ifdef HAVE_LONG_LONG +typedef struct { PY_LONG_LONG x; char c; } __Pyx_pad_longlong; +#endif +static size_t __Pyx_BufFmt_TypeCharToPadding(char ch, CYTHON_UNUSED int is_complex) { + switch (ch) { + case '?': case 'c': case 'b': case 'B': case 's': case 'p': return 1; + case 'h': case 'H': return sizeof(__Pyx_pad_short) - sizeof(short); + case 'i': case 'I': return sizeof(__Pyx_pad_int) - sizeof(int); + case 'l': case 'L': return sizeof(__Pyx_pad_long) - sizeof(long); +#ifdef HAVE_LONG_LONG + case 'q': case 'Q': return sizeof(__Pyx_pad_longlong) - sizeof(PY_LONG_LONG); +#endif + case 'f': return sizeof(__Pyx_pad_float) - sizeof(float); + case 'd': return sizeof(__Pyx_pad_double) - sizeof(double); + case 'g': return sizeof(__Pyx_pad_longdouble) - sizeof(long double); + case 'P': case 'O': return sizeof(__Pyx_pad_void_p) - sizeof(void*); + default: + __Pyx_BufFmt_RaiseUnexpectedChar(ch); + return 0; + } +} +static char __Pyx_BufFmt_TypeCharToGroup(char ch, int is_complex) { + switch (ch) { + case 'c': + return 'H'; + case 'b': case 'h': case 'i': + case 'l': case 'q': case 's': case 'p': + return 'I'; + case '?': case 'B': case 'H': case 'I': case 'L': case 'Q': + return 'U'; + case 'f': case 'd': case 'g': + return (is_complex ? 'C' : 'R'); + case 'O': + return 'O'; + case 'P': + return 'P'; + default: { + __Pyx_BufFmt_RaiseUnexpectedChar(ch); + return 0; + } + } +} +static void __Pyx_BufFmt_RaiseExpected(__Pyx_BufFmt_Context* ctx) { + if (ctx->head == NULL || ctx->head->field == &ctx->root) { + const char* expected; + const char* quote; + if (ctx->head == NULL) { + expected = "end"; + quote = ""; + } else { + expected = ctx->head->field->type->name; + quote = "'"; + } + PyErr_Format(PyExc_ValueError, + "Buffer dtype mismatch, expected %s%s%s but got %s", + quote, expected, quote, + __Pyx_BufFmt_DescribeTypeChar(ctx->enc_type, ctx->is_complex)); + } else { + __Pyx_StructField* field = ctx->head->field; + __Pyx_StructField* parent = (ctx->head - 1)->field; + PyErr_Format(PyExc_ValueError, + "Buffer dtype mismatch, expected '%s' but got %s in '%s.%s'", + field->type->name, __Pyx_BufFmt_DescribeTypeChar(ctx->enc_type, ctx->is_complex), + parent->type->name, field->name); + } +} +static int __Pyx_BufFmt_ProcessTypeChunk(__Pyx_BufFmt_Context* ctx) { + char group; + size_t size, offset, arraysize = 1; + if (ctx->enc_type == 0) return 0; + if (ctx->head->field->type->arraysize[0]) { + int i, ndim = 0; + if (ctx->enc_type == 's' || ctx->enc_type == 'p') { + ctx->is_valid_array = ctx->head->field->type->ndim == 1; + ndim = 1; + if (ctx->enc_count != ctx->head->field->type->arraysize[0]) { + PyErr_Format(PyExc_ValueError, + "Expected a dimension of size %zu, got %zu", + ctx->head->field->type->arraysize[0], ctx->enc_count); + return -1; + } + } + if (!ctx->is_valid_array) { + PyErr_Format(PyExc_ValueError, "Expected %d dimensions, got %d", + ctx->head->field->type->ndim, ndim); + return -1; + } + for (i = 0; i < ctx->head->field->type->ndim; i++) { + arraysize *= ctx->head->field->type->arraysize[i]; + } + ctx->is_valid_array = 0; + ctx->enc_count = 1; + } + group = __Pyx_BufFmt_TypeCharToGroup(ctx->enc_type, ctx->is_complex); + do { + __Pyx_StructField* field = ctx->head->field; + __Pyx_TypeInfo* type = field->type; + if (ctx->enc_packmode == '@' || ctx->enc_packmode == '^') { + size = __Pyx_BufFmt_TypeCharToNativeSize(ctx->enc_type, ctx->is_complex); + } else { + size = __Pyx_BufFmt_TypeCharToStandardSize(ctx->enc_type, ctx->is_complex); + } + if (ctx->enc_packmode == '@') { + size_t align_at = __Pyx_BufFmt_TypeCharToAlignment(ctx->enc_type, ctx->is_complex); + size_t align_mod_offset; + if (align_at == 0) return -1; + align_mod_offset = ctx->fmt_offset % align_at; + if (align_mod_offset > 0) ctx->fmt_offset += align_at - align_mod_offset; + if (ctx->struct_alignment == 0) + ctx->struct_alignment = __Pyx_BufFmt_TypeCharToPadding(ctx->enc_type, + ctx->is_complex); + } + if (type->size != size || type->typegroup != group) { + if (type->typegroup == 'C' && type->fields != NULL) { + size_t parent_offset = ctx->head->parent_offset + field->offset; + ++ctx->head; + ctx->head->field = type->fields; + ctx->head->parent_offset = parent_offset; + continue; + } + if ((type->typegroup == 'H' || group == 'H') && type->size == size) { + } else { + __Pyx_BufFmt_RaiseExpected(ctx); + return -1; + } + } + offset = ctx->head->parent_offset + field->offset; + if (ctx->fmt_offset != offset) { + PyErr_Format(PyExc_ValueError, + "Buffer dtype mismatch; next field is at offset %" CYTHON_FORMAT_SSIZE_T "d but %" CYTHON_FORMAT_SSIZE_T "d expected", + (Py_ssize_t)ctx->fmt_offset, (Py_ssize_t)offset); + return -1; + } + ctx->fmt_offset += size; + if (arraysize) + ctx->fmt_offset += (arraysize - 1) * size; + --ctx->enc_count; + while (1) { + if (field == &ctx->root) { + ctx->head = NULL; + if (ctx->enc_count != 0) { + __Pyx_BufFmt_RaiseExpected(ctx); + return -1; + } + break; + } + ctx->head->field = ++field; + if (field->type == NULL) { + --ctx->head; + field = ctx->head->field; + continue; + } else if (field->type->typegroup == 'S') { + size_t parent_offset = ctx->head->parent_offset + field->offset; + if (field->type->fields->type == NULL) continue; + field = field->type->fields; + ++ctx->head; + ctx->head->field = field; + ctx->head->parent_offset = parent_offset; + break; + } else { + break; + } + } + } while (ctx->enc_count); + ctx->enc_type = 0; + ctx->is_complex = 0; + return 0; +} +static PyObject * +__pyx_buffmt_parse_array(__Pyx_BufFmt_Context* ctx, const char** tsp) +{ + const char *ts = *tsp; + int i = 0, number, ndim; + ++ts; + if (ctx->new_count != 1) { + PyErr_SetString(PyExc_ValueError, + "Cannot handle repeated arrays in format string"); + return NULL; + } + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + ndim = ctx->head->field->type->ndim; + while (*ts && *ts != ')') { + switch (*ts) { + case ' ': case '\f': case '\r': case '\n': case '\t': case '\v': continue; + default: break; + } + number = __Pyx_BufFmt_ExpectNumber(&ts); + if (number == -1) return NULL; + if (i < ndim && (size_t) number != ctx->head->field->type->arraysize[i]) + return PyErr_Format(PyExc_ValueError, + "Expected a dimension of size %zu, got %d", + ctx->head->field->type->arraysize[i], number); + if (*ts != ',' && *ts != ')') + return PyErr_Format(PyExc_ValueError, + "Expected a comma in format string, got '%c'", *ts); + if (*ts == ',') ts++; + i++; + } + if (i != ndim) + return PyErr_Format(PyExc_ValueError, "Expected %d dimension(s), got %d", + ctx->head->field->type->ndim, i); + if (!*ts) { + PyErr_SetString(PyExc_ValueError, + "Unexpected end of format string, expected ')'"); + return NULL; + } + ctx->is_valid_array = 1; + ctx->new_count = 1; + *tsp = ++ts; + return Py_None; +} +static const char* __Pyx_BufFmt_CheckString(__Pyx_BufFmt_Context* ctx, const char* ts) { + int got_Z = 0; + while (1) { + switch(*ts) { + case 0: + if (ctx->enc_type != 0 && ctx->head == NULL) { + __Pyx_BufFmt_RaiseExpected(ctx); + return NULL; + } + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + if (ctx->head != NULL) { + __Pyx_BufFmt_RaiseExpected(ctx); + return NULL; + } + return ts; + case ' ': + case '\r': + case '\n': + ++ts; + break; + case '<': + if (!__Pyx_Is_Little_Endian()) { + PyErr_SetString(PyExc_ValueError, "Little-endian buffer not supported on big-endian compiler"); + return NULL; + } + ctx->new_packmode = '='; + ++ts; + break; + case '>': + case '!': + if (__Pyx_Is_Little_Endian()) { + PyErr_SetString(PyExc_ValueError, "Big-endian buffer not supported on little-endian compiler"); + return NULL; + } + ctx->new_packmode = '='; + ++ts; + break; + case '=': + case '@': + case '^': + ctx->new_packmode = *ts++; + break; + case 'T': + { + const char* ts_after_sub; + size_t i, struct_count = ctx->new_count; + size_t struct_alignment = ctx->struct_alignment; + ctx->new_count = 1; + ++ts; + if (*ts != '{') { + PyErr_SetString(PyExc_ValueError, "Buffer acquisition: Expected '{' after 'T'"); + return NULL; + } + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + ctx->enc_type = 0; + ctx->enc_count = 0; + ctx->struct_alignment = 0; + ++ts; + ts_after_sub = ts; + for (i = 0; i != struct_count; ++i) { + ts_after_sub = __Pyx_BufFmt_CheckString(ctx, ts); + if (!ts_after_sub) return NULL; + } + ts = ts_after_sub; + if (struct_alignment) ctx->struct_alignment = struct_alignment; + } + break; + case '}': + { + size_t alignment = ctx->struct_alignment; + ++ts; + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + ctx->enc_type = 0; + if (alignment && ctx->fmt_offset % alignment) { + ctx->fmt_offset += alignment - (ctx->fmt_offset % alignment); + } + } + return ts; + case 'x': + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + ctx->fmt_offset += ctx->new_count; + ctx->new_count = 1; + ctx->enc_count = 0; + ctx->enc_type = 0; + ctx->enc_packmode = ctx->new_packmode; + ++ts; + break; + case 'Z': + got_Z = 1; + ++ts; + if (*ts != 'f' && *ts != 'd' && *ts != 'g') { + __Pyx_BufFmt_RaiseUnexpectedChar('Z'); + return NULL; + } + CYTHON_FALLTHROUGH; + case '?': case 'c': case 'b': case 'B': case 'h': case 'H': case 'i': case 'I': + case 'l': case 'L': case 'q': case 'Q': + case 'f': case 'd': case 'g': + case 'O': case 'p': + if ((ctx->enc_type == *ts) && (got_Z == ctx->is_complex) && + (ctx->enc_packmode == ctx->new_packmode) && (!ctx->is_valid_array)) { + ctx->enc_count += ctx->new_count; + ctx->new_count = 1; + got_Z = 0; + ++ts; + break; + } + CYTHON_FALLTHROUGH; + case 's': + if (__Pyx_BufFmt_ProcessTypeChunk(ctx) == -1) return NULL; + ctx->enc_count = ctx->new_count; + ctx->enc_packmode = ctx->new_packmode; + ctx->enc_type = *ts; + ctx->is_complex = got_Z; + ++ts; + ctx->new_count = 1; + got_Z = 0; + break; + case ':': + ++ts; + while(*ts != ':') ++ts; + ++ts; + break; + case '(': + if (!__pyx_buffmt_parse_array(ctx, &ts)) return NULL; + break; + default: + { + int number = __Pyx_BufFmt_ExpectNumber(&ts); + if (number == -1) return NULL; + ctx->new_count = (size_t)number; + } + } + } +} + +/* BufferGetAndValidate */ + static CYTHON_INLINE void __Pyx_SafeReleaseBuffer(Py_buffer* info) { + if (unlikely(info->buf == NULL)) return; + if (info->suboffsets == __Pyx_minusones) info->suboffsets = NULL; + __Pyx_ReleaseBuffer(info); +} +static void __Pyx_ZeroBuffer(Py_buffer* buf) { + buf->buf = NULL; + buf->obj = NULL; + buf->strides = __Pyx_zeros; + buf->shape = __Pyx_zeros; + buf->suboffsets = __Pyx_minusones; +} +static int __Pyx__GetBufferAndValidate( + Py_buffer* buf, PyObject* obj, __Pyx_TypeInfo* dtype, int flags, + int nd, int cast, __Pyx_BufFmt_StackElem* stack) +{ + buf->buf = NULL; + if (unlikely(__Pyx_GetBuffer(obj, buf, flags) == -1)) { + __Pyx_ZeroBuffer(buf); + return -1; + } + if (unlikely(buf->ndim != nd)) { + PyErr_Format(PyExc_ValueError, + "Buffer has wrong number of dimensions (expected %d, got %d)", + nd, buf->ndim); + goto fail; + } + if (!cast) { + __Pyx_BufFmt_Context ctx; + __Pyx_BufFmt_Init(&ctx, stack, dtype); + if (!__Pyx_BufFmt_CheckString(&ctx, buf->format)) goto fail; + } + if (unlikely((size_t)buf->itemsize != dtype->size)) { + PyErr_Format(PyExc_ValueError, + "Item size of buffer (%" CYTHON_FORMAT_SSIZE_T "d byte%s) does not match size of '%s' (%" CYTHON_FORMAT_SSIZE_T "d byte%s)", + buf->itemsize, (buf->itemsize > 1) ? "s" : "", + dtype->name, (Py_ssize_t)dtype->size, (dtype->size > 1) ? "s" : ""); + goto fail; + } + if (buf->suboffsets == NULL) buf->suboffsets = __Pyx_minusones; + return 0; +fail:; + __Pyx_SafeReleaseBuffer(buf); + return -1; +} + +/* BufferFallbackError */ + static void __Pyx_RaiseBufferFallbackError(void) { + PyErr_SetString(PyExc_ValueError, + "Buffer acquisition failed on assignment; and then reacquiring the old buffer failed too!"); +} + +/* PyErrFetchRestore */ + #if CYTHON_FAST_THREAD_STATE +static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) { + PyObject *tmp_type, *tmp_value, *tmp_tb; + tmp_type = tstate->curexc_type; + tmp_value = tstate->curexc_value; + tmp_tb = tstate->curexc_traceback; + tstate->curexc_type = type; + tstate->curexc_value = value; + tstate->curexc_traceback = tb; + Py_XDECREF(tmp_type); + Py_XDECREF(tmp_value); + Py_XDECREF(tmp_tb); +} +static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) { + *type = tstate->curexc_type; + *value = tstate->curexc_value; + *tb = tstate->curexc_traceback; + tstate->curexc_type = 0; + tstate->curexc_value = 0; + tstate->curexc_traceback = 0; +} +#endif + +/* RaiseException */ + #if PY_MAJOR_VERSION < 3 +static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, + CYTHON_UNUSED PyObject *cause) { + __Pyx_PyThreadState_declare + Py_XINCREF(type); + if (!value || value == Py_None) + value = NULL; + else + Py_INCREF(value); + if (!tb || tb == Py_None) + tb = NULL; + else { + Py_INCREF(tb); + if (!PyTraceBack_Check(tb)) { + PyErr_SetString(PyExc_TypeError, + "raise: arg 3 must be a traceback or None"); + goto raise_error; + } + } + if (PyType_Check(type)) { +#if CYTHON_COMPILING_IN_PYPY + if (!value) { + Py_INCREF(Py_None); + value = Py_None; + } +#endif + PyErr_NormalizeException(&type, &value, &tb); + } else { + if (value) { + PyErr_SetString(PyExc_TypeError, + "instance exception may not have a separate value"); + goto raise_error; + } + value = type; + type = (PyObject*) Py_TYPE(type); + Py_INCREF(type); + if (!PyType_IsSubtype((PyTypeObject *)type, (PyTypeObject *)PyExc_BaseException)) { + PyErr_SetString(PyExc_TypeError, + "raise: exception class must be a subclass of BaseException"); + goto raise_error; + } + } + __Pyx_PyThreadState_assign + __Pyx_ErrRestore(type, value, tb); + return; +raise_error: + Py_XDECREF(value); + Py_XDECREF(type); + Py_XDECREF(tb); + return; +} +#else +static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause) { + PyObject* owned_instance = NULL; + if (tb == Py_None) { + tb = 0; + } else if (tb && !PyTraceBack_Check(tb)) { + PyErr_SetString(PyExc_TypeError, + "raise: arg 3 must be a traceback or None"); + goto bad; + } + if (value == Py_None) + value = 0; + if (PyExceptionInstance_Check(type)) { + if (value) { + PyErr_SetString(PyExc_TypeError, + "instance exception may not have a separate value"); + goto bad; + } + value = type; + type = (PyObject*) Py_TYPE(value); + } else if (PyExceptionClass_Check(type)) { + PyObject *instance_class = NULL; + if (value && PyExceptionInstance_Check(value)) { + instance_class = (PyObject*) Py_TYPE(value); + if (instance_class != type) { + int is_subclass = PyObject_IsSubclass(instance_class, type); + if (!is_subclass) { + instance_class = NULL; + } else if (unlikely(is_subclass == -1)) { + goto bad; + } else { + type = instance_class; + } + } + } + if (!instance_class) { + PyObject *args; + if (!value) + args = PyTuple_New(0); + else if (PyTuple_Check(value)) { + Py_INCREF(value); + args = value; + } else + args = PyTuple_Pack(1, value); + if (!args) + goto bad; + owned_instance = PyObject_Call(type, args, NULL); + Py_DECREF(args); + if (!owned_instance) + goto bad; + value = owned_instance; + if (!PyExceptionInstance_Check(value)) { + PyErr_Format(PyExc_TypeError, + "calling %R should have returned an instance of " + "BaseException, not %R", + type, Py_TYPE(value)); + goto bad; + } + } + } else { + PyErr_SetString(PyExc_TypeError, + "raise: exception class must be a subclass of BaseException"); + goto bad; + } + if (cause) { + PyObject *fixed_cause; + if (cause == Py_None) { + fixed_cause = NULL; + } else if (PyExceptionClass_Check(cause)) { + fixed_cause = PyObject_CallObject(cause, NULL); + if (fixed_cause == NULL) + goto bad; + } else if (PyExceptionInstance_Check(cause)) { + fixed_cause = cause; + Py_INCREF(fixed_cause); + } else { + PyErr_SetString(PyExc_TypeError, + "exception causes must derive from " + "BaseException"); + goto bad; + } + PyException_SetCause(value, fixed_cause); + } + PyErr_SetObject(type, value); + if (tb) { +#if CYTHON_COMPILING_IN_PYPY + PyObject *tmp_type, *tmp_value, *tmp_tb; + PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb); + Py_INCREF(tb); + PyErr_Restore(tmp_type, tmp_value, tb); + Py_XDECREF(tmp_tb); +#else + PyThreadState *tstate = __Pyx_PyThreadState_Current; + PyObject* tmp_tb = tstate->curexc_traceback; + if (tb != tmp_tb) { + Py_INCREF(tb); + tstate->curexc_traceback = tb; + Py_XDECREF(tmp_tb); + } +#endif + } +bad: + Py_XDECREF(owned_instance); + return; +} +#endif + +/* PyCFunctionFastCall */ + #if CYTHON_FAST_PYCCALL +static CYTHON_INLINE PyObject * __Pyx_PyCFunction_FastCall(PyObject *func_obj, PyObject **args, Py_ssize_t nargs) { + PyCFunctionObject *func = (PyCFunctionObject*)func_obj; + PyCFunction meth = PyCFunction_GET_FUNCTION(func); + PyObject *self = PyCFunction_GET_SELF(func); + int flags = PyCFunction_GET_FLAGS(func); + assert(PyCFunction_Check(func)); + assert(METH_FASTCALL == (flags & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_KEYWORDS | METH_STACKLESS))); + assert(nargs >= 0); + assert(nargs == 0 || args != NULL); + /* _PyCFunction_FastCallDict() must not be called with an exception set, + because it may clear it (directly or indirectly) and so the + caller loses its exception */ + assert(!PyErr_Occurred()); + if ((PY_VERSION_HEX < 0x030700A0) || unlikely(flags & METH_KEYWORDS)) { + return (*((__Pyx_PyCFunctionFastWithKeywords)(void*)meth)) (self, args, nargs, NULL); + } else { + return (*((__Pyx_PyCFunctionFast)(void*)meth)) (self, args, nargs); + } +} +#endif + +/* PyFunctionFastCall */ + #if CYTHON_FAST_PYCALL +static PyObject* __Pyx_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t na, + PyObject *globals) { + PyFrameObject *f; + PyThreadState *tstate = __Pyx_PyThreadState_Current; + PyObject **fastlocals; + Py_ssize_t i; + PyObject *result; + assert(globals != NULL); + /* XXX Perhaps we should create a specialized + PyFrame_New() that doesn't take locals, but does + take builtins without sanity checking them. + */ + assert(tstate != NULL); + f = PyFrame_New(tstate, co, globals, NULL); + if (f == NULL) { + return NULL; + } + fastlocals = __Pyx_PyFrame_GetLocalsplus(f); + for (i = 0; i < na; i++) { + Py_INCREF(*args); + fastlocals[i] = *args++; + } + result = PyEval_EvalFrameEx(f,0); + ++tstate->recursion_depth; + Py_DECREF(f); + --tstate->recursion_depth; + return result; +} +#if 1 || PY_VERSION_HEX < 0x030600B1 +static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs, PyObject *kwargs) { + PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func); + PyObject *globals = PyFunction_GET_GLOBALS(func); + PyObject *argdefs = PyFunction_GET_DEFAULTS(func); + PyObject *closure; +#if PY_MAJOR_VERSION >= 3 + PyObject *kwdefs; +#endif + PyObject *kwtuple, **k; + PyObject **d; + Py_ssize_t nd; + Py_ssize_t nk; + PyObject *result; + assert(kwargs == NULL || PyDict_Check(kwargs)); + nk = kwargs ? PyDict_Size(kwargs) : 0; + if (Py_EnterRecursiveCall((char*)" while calling a Python object")) { + return NULL; + } + if ( +#if PY_MAJOR_VERSION >= 3 + co->co_kwonlyargcount == 0 && +#endif + likely(kwargs == NULL || nk == 0) && + co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) { + if (argdefs == NULL && co->co_argcount == nargs) { + result = __Pyx_PyFunction_FastCallNoKw(co, args, nargs, globals); + goto done; + } + else if (nargs == 0 && argdefs != NULL + && co->co_argcount == Py_SIZE(argdefs)) { + /* function called with no arguments, but all parameters have + a default value: use default values as arguments .*/ + args = &PyTuple_GET_ITEM(argdefs, 0); + result =__Pyx_PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs), globals); + goto done; + } + } + if (kwargs != NULL) { + Py_ssize_t pos, i; + kwtuple = PyTuple_New(2 * nk); + if (kwtuple == NULL) { + result = NULL; + goto done; + } + k = &PyTuple_GET_ITEM(kwtuple, 0); + pos = i = 0; + while (PyDict_Next(kwargs, &pos, &k[i], &k[i+1])) { + Py_INCREF(k[i]); + Py_INCREF(k[i+1]); + i += 2; + } + nk = i / 2; + } + else { + kwtuple = NULL; + k = NULL; + } + closure = PyFunction_GET_CLOSURE(func); +#if PY_MAJOR_VERSION >= 3 + kwdefs = PyFunction_GET_KW_DEFAULTS(func); +#endif + if (argdefs != NULL) { + d = &PyTuple_GET_ITEM(argdefs, 0); + nd = Py_SIZE(argdefs); + } + else { + d = NULL; + nd = 0; + } +#if PY_MAJOR_VERSION >= 3 + result = PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL, + args, (int)nargs, + k, (int)nk, + d, (int)nd, kwdefs, closure); +#else + result = PyEval_EvalCodeEx(co, globals, (PyObject *)NULL, + args, (int)nargs, + k, (int)nk, + d, (int)nd, closure); +#endif + Py_XDECREF(kwtuple); +done: + Py_LeaveRecursiveCall(); + return result; +} +#endif +#endif + +/* PyObjectCallMethO */ + #if CYTHON_COMPILING_IN_CPYTHON +static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg) { + PyObject *self, *result; + PyCFunction cfunc; + cfunc = PyCFunction_GET_FUNCTION(func); + self = PyCFunction_GET_SELF(func); + if (unlikely(Py_EnterRecursiveCall((char*)" while calling a Python object"))) + return NULL; + result = cfunc(self, arg); + Py_LeaveRecursiveCall(); + if (unlikely(!result) && unlikely(!PyErr_Occurred())) { + PyErr_SetString( + PyExc_SystemError, + "NULL result without error in PyObject_Call"); + } + return result; +} +#endif + +/* PyObjectCallOneArg */ + #if CYTHON_COMPILING_IN_CPYTHON +static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) { + PyObject *result; + PyObject *args = PyTuple_New(1); + if (unlikely(!args)) return NULL; + Py_INCREF(arg); + PyTuple_SET_ITEM(args, 0, arg); + result = __Pyx_PyObject_Call(func, args, NULL); + Py_DECREF(args); + return result; +} +static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) { +#if CYTHON_FAST_PYCALL + if (PyFunction_Check(func)) { + return __Pyx_PyFunction_FastCall(func, &arg, 1); + } +#endif + if (likely(PyCFunction_Check(func))) { + if (likely(PyCFunction_GET_FLAGS(func) & METH_O)) { + return __Pyx_PyObject_CallMethO(func, arg); +#if CYTHON_FAST_PYCCALL + } else if (PyCFunction_GET_FLAGS(func) & METH_FASTCALL) { + return __Pyx_PyCFunction_FastCall(func, &arg, 1); +#endif + } + } + return __Pyx__PyObject_CallOneArg(func, arg); +} +#else +static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) { + PyObject *result; + PyObject *args = PyTuple_Pack(1, arg); + if (unlikely(!args)) return NULL; + result = __Pyx_PyObject_Call(func, args, NULL); + Py_DECREF(args); + return result; +} +#endif + +/* DictGetItem */ + #if PY_MAJOR_VERSION >= 3 && !CYTHON_COMPILING_IN_PYPY +static PyObject *__Pyx_PyDict_GetItem(PyObject *d, PyObject* key) { + PyObject *value; + value = PyDict_GetItemWithError(d, key); + if (unlikely(!value)) { + if (!PyErr_Occurred()) { + if (unlikely(PyTuple_Check(key))) { + PyObject* args = PyTuple_Pack(1, key); + if (likely(args)) { + PyErr_SetObject(PyExc_KeyError, args); + Py_DECREF(args); + } + } else { + PyErr_SetObject(PyExc_KeyError, key); + } + } + return NULL; + } + Py_INCREF(value); + return value; +} +#endif + +/* RaiseTooManyValuesToUnpack */ + static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected) { + PyErr_Format(PyExc_ValueError, + "too many values to unpack (expected %" CYTHON_FORMAT_SSIZE_T "d)", expected); +} + +/* RaiseNeedMoreValuesToUnpack */ + static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) { + PyErr_Format(PyExc_ValueError, + "need more than %" CYTHON_FORMAT_SSIZE_T "d value%.1s to unpack", + index, (index == 1) ? "" : "s"); +} + +/* RaiseNoneIterError */ + static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void) { + PyErr_SetString(PyExc_TypeError, "'NoneType' object is not iterable"); +} + +/* GetTopmostException */ + #if CYTHON_USE_EXC_INFO_STACK +static _PyErr_StackItem * +__Pyx_PyErr_GetTopmostException(PyThreadState *tstate) +{ + _PyErr_StackItem *exc_info = tstate->exc_info; + while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && + exc_info->previous_item != NULL) + { + exc_info = exc_info->previous_item; + } + return exc_info; +} +#endif + +/* SaveResetException */ + #if CYTHON_FAST_THREAD_STATE +static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) { + #if CYTHON_USE_EXC_INFO_STACK + _PyErr_StackItem *exc_info = __Pyx_PyErr_GetTopmostException(tstate); + *type = exc_info->exc_type; + *value = exc_info->exc_value; + *tb = exc_info->exc_traceback; + #else + *type = tstate->exc_type; + *value = tstate->exc_value; + *tb = tstate->exc_traceback; + #endif + Py_XINCREF(*type); + Py_XINCREF(*value); + Py_XINCREF(*tb); +} +static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb) { + PyObject *tmp_type, *tmp_value, *tmp_tb; + #if CYTHON_USE_EXC_INFO_STACK + _PyErr_StackItem *exc_info = tstate->exc_info; + tmp_type = exc_info->exc_type; + tmp_value = exc_info->exc_value; + tmp_tb = exc_info->exc_traceback; + exc_info->exc_type = type; + exc_info->exc_value = value; + exc_info->exc_traceback = tb; + #else + tmp_type = tstate->exc_type; + tmp_value = tstate->exc_value; + tmp_tb = tstate->exc_traceback; + tstate->exc_type = type; + tstate->exc_value = value; + tstate->exc_traceback = tb; + #endif + Py_XDECREF(tmp_type); + Py_XDECREF(tmp_value); + Py_XDECREF(tmp_tb); +} +#endif + +/* PyErrExceptionMatches */ + #if CYTHON_FAST_THREAD_STATE +static int __Pyx_PyErr_ExceptionMatchesTuple(PyObject *exc_type, PyObject *tuple) { + Py_ssize_t i, n; + n = PyTuple_GET_SIZE(tuple); +#if PY_MAJOR_VERSION >= 3 + for (i=0; icurexc_type; + if (exc_type == err) return 1; + if (unlikely(!exc_type)) return 0; + if (unlikely(PyTuple_Check(err))) + return __Pyx_PyErr_ExceptionMatchesTuple(exc_type, err); + return __Pyx_PyErr_GivenExceptionMatches(exc_type, err); +} +#endif + +/* GetException */ + #if CYTHON_FAST_THREAD_STATE +static int __Pyx__GetException(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb) +#else +static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb) +#endif +{ + PyObject *local_type, *local_value, *local_tb; +#if CYTHON_FAST_THREAD_STATE + PyObject *tmp_type, *tmp_value, *tmp_tb; + local_type = tstate->curexc_type; + local_value = tstate->curexc_value; + local_tb = tstate->curexc_traceback; + tstate->curexc_type = 0; + tstate->curexc_value = 0; + tstate->curexc_traceback = 0; +#else + PyErr_Fetch(&local_type, &local_value, &local_tb); +#endif + PyErr_NormalizeException(&local_type, &local_value, &local_tb); +#if CYTHON_FAST_THREAD_STATE + if (unlikely(tstate->curexc_type)) +#else + if (unlikely(PyErr_Occurred())) +#endif + goto bad; + #if PY_MAJOR_VERSION >= 3 + if (local_tb) { + if (unlikely(PyException_SetTraceback(local_value, local_tb) < 0)) + goto bad; + } + #endif + Py_XINCREF(local_tb); + Py_XINCREF(local_type); + Py_XINCREF(local_value); + *type = local_type; + *value = local_value; + *tb = local_tb; +#if CYTHON_FAST_THREAD_STATE + #if CYTHON_USE_EXC_INFO_STACK + { + _PyErr_StackItem *exc_info = tstate->exc_info; + tmp_type = exc_info->exc_type; + tmp_value = exc_info->exc_value; + tmp_tb = exc_info->exc_traceback; + exc_info->exc_type = local_type; + exc_info->exc_value = local_value; + exc_info->exc_traceback = local_tb; + } + #else + tmp_type = tstate->exc_type; + tmp_value = tstate->exc_value; + tmp_tb = tstate->exc_traceback; + tstate->exc_type = local_type; + tstate->exc_value = local_value; + tstate->exc_traceback = local_tb; + #endif + Py_XDECREF(tmp_type); + Py_XDECREF(tmp_value); + Py_XDECREF(tmp_tb); +#else + PyErr_SetExcInfo(local_type, local_value, local_tb); +#endif + return 0; +bad: + *type = 0; + *value = 0; + *tb = 0; + Py_XDECREF(local_type); + Py_XDECREF(local_value); + Py_XDECREF(local_tb); + return -1; +} + +/* TypeImport */ + #ifndef __PYX_HAVE_RT_ImportType +#define __PYX_HAVE_RT_ImportType +static PyTypeObject *__Pyx_ImportType(PyObject *module, const char *module_name, const char *class_name, + size_t size, enum __Pyx_ImportType_CheckSize check_size) +{ + PyObject *result = 0; + char warning[200]; + Py_ssize_t basicsize; +#ifdef Py_LIMITED_API + PyObject *py_basicsize; +#endif + result = PyObject_GetAttrString(module, class_name); + if (!result) + goto bad; + if (!PyType_Check(result)) { + PyErr_Format(PyExc_TypeError, + "%.200s.%.200s is not a type object", + module_name, class_name); + goto bad; + } +#ifndef Py_LIMITED_API + basicsize = ((PyTypeObject *)result)->tp_basicsize; +#else + py_basicsize = PyObject_GetAttrString(result, "__basicsize__"); + if (!py_basicsize) + goto bad; + basicsize = PyLong_AsSsize_t(py_basicsize); + Py_DECREF(py_basicsize); + py_basicsize = 0; + if (basicsize == (Py_ssize_t)-1 && PyErr_Occurred()) + goto bad; +#endif + if ((size_t)basicsize < size) { + PyErr_Format(PyExc_ValueError, + "%.200s.%.200s size changed, may indicate binary incompatibility. " + "Expected %zd from C header, got %zd from PyObject", + module_name, class_name, size, basicsize); + goto bad; + } + if (check_size == __Pyx_ImportType_CheckSize_Error && (size_t)basicsize != size) { + PyErr_Format(PyExc_ValueError, + "%.200s.%.200s size changed, may indicate binary incompatibility. " + "Expected %zd from C header, got %zd from PyObject", + module_name, class_name, size, basicsize); + goto bad; + } + else if (check_size == __Pyx_ImportType_CheckSize_Warn && (size_t)basicsize > size) { + PyOS_snprintf(warning, sizeof(warning), + "%s.%s size changed, may indicate binary incompatibility. " + "Expected %zd from C header, got %zd from PyObject", + module_name, class_name, size, basicsize); + if (PyErr_WarnEx(NULL, warning, 0) < 0) goto bad; + } + return (PyTypeObject *)result; +bad: + Py_XDECREF(result); + return NULL; +} +#endif + +/* Import */ + static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level) { + PyObject *empty_list = 0; + PyObject *module = 0; + PyObject *global_dict = 0; + PyObject *empty_dict = 0; + PyObject *list; + #if PY_MAJOR_VERSION < 3 + PyObject *py_import; + py_import = __Pyx_PyObject_GetAttrStr(__pyx_b, __pyx_n_s_import); + if (!py_import) + goto bad; + #endif + if (from_list) + list = from_list; + else { + empty_list = PyList_New(0); + if (!empty_list) + goto bad; + list = empty_list; + } + global_dict = PyModule_GetDict(__pyx_m); + if (!global_dict) + goto bad; + empty_dict = PyDict_New(); + if (!empty_dict) + goto bad; + { + #if PY_MAJOR_VERSION >= 3 + if (level == -1) { + if ((1) && (strchr(__Pyx_MODULE_NAME, '.'))) { + module = PyImport_ImportModuleLevelObject( + name, global_dict, empty_dict, list, 1); + if (!module) { + if (!PyErr_ExceptionMatches(PyExc_ImportError)) + goto bad; + PyErr_Clear(); + } + } + level = 0; + } + #endif + if (!module) { + #if PY_MAJOR_VERSION < 3 + PyObject *py_level = PyInt_FromLong(level); + if (!py_level) + goto bad; + module = PyObject_CallFunctionObjArgs(py_import, + name, global_dict, empty_dict, list, py_level, (PyObject *)NULL); + Py_DECREF(py_level); + #else + module = PyImport_ImportModuleLevelObject( + name, global_dict, empty_dict, list, level); + #endif + } + } +bad: + #if PY_MAJOR_VERSION < 3 + Py_XDECREF(py_import); + #endif + Py_XDECREF(empty_list); + Py_XDECREF(empty_dict); + return module; +} + +/* CLineInTraceback */ + #ifndef CYTHON_CLINE_IN_TRACEBACK +static int __Pyx_CLineForTraceback(CYTHON_NCP_UNUSED PyThreadState *tstate, int c_line) { + PyObject *use_cline; + PyObject *ptype, *pvalue, *ptraceback; +#if CYTHON_COMPILING_IN_CPYTHON + PyObject **cython_runtime_dict; +#endif + if (unlikely(!__pyx_cython_runtime)) { + return c_line; + } + __Pyx_ErrFetchInState(tstate, &ptype, &pvalue, &ptraceback); +#if CYTHON_COMPILING_IN_CPYTHON + cython_runtime_dict = _PyObject_GetDictPtr(__pyx_cython_runtime); + if (likely(cython_runtime_dict)) { + __PYX_PY_DICT_LOOKUP_IF_MODIFIED( + use_cline, *cython_runtime_dict, + __Pyx_PyDict_GetItemStr(*cython_runtime_dict, __pyx_n_s_cline_in_traceback)) + } else +#endif + { + PyObject *use_cline_obj = __Pyx_PyObject_GetAttrStr(__pyx_cython_runtime, __pyx_n_s_cline_in_traceback); + if (use_cline_obj) { + use_cline = PyObject_Not(use_cline_obj) ? Py_False : Py_True; + Py_DECREF(use_cline_obj); + } else { + PyErr_Clear(); + use_cline = NULL; + } + } + if (!use_cline) { + c_line = 0; + PyObject_SetAttr(__pyx_cython_runtime, __pyx_n_s_cline_in_traceback, Py_False); + } + else if (use_cline == Py_False || (use_cline != Py_True && PyObject_Not(use_cline) != 0)) { + c_line = 0; + } + __Pyx_ErrRestoreInState(tstate, ptype, pvalue, ptraceback); + return c_line; +} +#endif + +/* CodeObjectCache */ + static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line) { + int start = 0, mid = 0, end = count - 1; + if (end >= 0 && code_line > entries[end].code_line) { + return count; + } + while (start < end) { + mid = start + (end - start) / 2; + if (code_line < entries[mid].code_line) { + end = mid; + } else if (code_line > entries[mid].code_line) { + start = mid + 1; + } else { + return mid; + } + } + if (code_line <= entries[mid].code_line) { + return mid; + } else { + return mid + 1; + } +} +static PyCodeObject *__pyx_find_code_object(int code_line) { + PyCodeObject* code_object; + int pos; + if (unlikely(!code_line) || unlikely(!__pyx_code_cache.entries)) { + return NULL; + } + pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line); + if (unlikely(pos >= __pyx_code_cache.count) || unlikely(__pyx_code_cache.entries[pos].code_line != code_line)) { + return NULL; + } + code_object = __pyx_code_cache.entries[pos].code_object; + Py_INCREF(code_object); + return code_object; +} +static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object) { + int pos, i; + __Pyx_CodeObjectCacheEntry* entries = __pyx_code_cache.entries; + if (unlikely(!code_line)) { + return; + } + if (unlikely(!entries)) { + entries = (__Pyx_CodeObjectCacheEntry*)PyMem_Malloc(64*sizeof(__Pyx_CodeObjectCacheEntry)); + if (likely(entries)) { + __pyx_code_cache.entries = entries; + __pyx_code_cache.max_count = 64; + __pyx_code_cache.count = 1; + entries[0].code_line = code_line; + entries[0].code_object = code_object; + Py_INCREF(code_object); + } + return; + } + pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line); + if ((pos < __pyx_code_cache.count) && unlikely(__pyx_code_cache.entries[pos].code_line == code_line)) { + PyCodeObject* tmp = entries[pos].code_object; + entries[pos].code_object = code_object; + Py_DECREF(tmp); + return; + } + if (__pyx_code_cache.count == __pyx_code_cache.max_count) { + int new_max = __pyx_code_cache.max_count + 64; + entries = (__Pyx_CodeObjectCacheEntry*)PyMem_Realloc( + __pyx_code_cache.entries, ((size_t)new_max) * sizeof(__Pyx_CodeObjectCacheEntry)); + if (unlikely(!entries)) { + return; + } + __pyx_code_cache.entries = entries; + __pyx_code_cache.max_count = new_max; + } + for (i=__pyx_code_cache.count; i>pos; i--) { + entries[i] = entries[i-1]; + } + entries[pos].code_line = code_line; + entries[pos].code_object = code_object; + __pyx_code_cache.count++; + Py_INCREF(code_object); +} + +/* AddTraceback */ + #include "compile.h" +#include "frameobject.h" +#include "traceback.h" +static PyCodeObject* __Pyx_CreateCodeObjectForTraceback( + const char *funcname, int c_line, + int py_line, const char *filename) { + PyCodeObject *py_code = 0; + PyObject *py_srcfile = 0; + PyObject *py_funcname = 0; + #if PY_MAJOR_VERSION < 3 + py_srcfile = PyString_FromString(filename); + #else + py_srcfile = PyUnicode_FromString(filename); + #endif + if (!py_srcfile) goto bad; + if (c_line) { + #if PY_MAJOR_VERSION < 3 + py_funcname = PyString_FromFormat( "%s (%s:%d)", funcname, __pyx_cfilenm, c_line); + #else + py_funcname = PyUnicode_FromFormat( "%s (%s:%d)", funcname, __pyx_cfilenm, c_line); + #endif + } + else { + #if PY_MAJOR_VERSION < 3 + py_funcname = PyString_FromString(funcname); + #else + py_funcname = PyUnicode_FromString(funcname); + #endif + } + if (!py_funcname) goto bad; + py_code = __Pyx_PyCode_New( + 0, + 0, + 0, + 0, + 0, + __pyx_empty_bytes, /*PyObject *code,*/ + __pyx_empty_tuple, /*PyObject *consts,*/ + __pyx_empty_tuple, /*PyObject *names,*/ + __pyx_empty_tuple, /*PyObject *varnames,*/ + __pyx_empty_tuple, /*PyObject *freevars,*/ + __pyx_empty_tuple, /*PyObject *cellvars,*/ + py_srcfile, /*PyObject *filename,*/ + py_funcname, /*PyObject *name,*/ + py_line, + __pyx_empty_bytes /*PyObject *lnotab*/ + ); + Py_DECREF(py_srcfile); + Py_DECREF(py_funcname); + return py_code; +bad: + Py_XDECREF(py_srcfile); + Py_XDECREF(py_funcname); + return NULL; +} +static void __Pyx_AddTraceback(const char *funcname, int c_line, + int py_line, const char *filename) { + PyCodeObject *py_code = 0; + PyFrameObject *py_frame = 0; + PyThreadState *tstate = __Pyx_PyThreadState_Current; + if (c_line) { + c_line = __Pyx_CLineForTraceback(tstate, c_line); + } + py_code = __pyx_find_code_object(c_line ? -c_line : py_line); + if (!py_code) { + py_code = __Pyx_CreateCodeObjectForTraceback( + funcname, c_line, py_line, filename); + if (!py_code) goto bad; + __pyx_insert_code_object(c_line ? -c_line : py_line, py_code); + } + py_frame = PyFrame_New( + tstate, /*PyThreadState *tstate,*/ + py_code, /*PyCodeObject *code,*/ + __pyx_d, /*PyObject *globals,*/ + 0 /*PyObject *locals*/ + ); + if (!py_frame) goto bad; + __Pyx_PyFrame_SetLineNumber(py_frame, py_line); + PyTraceBack_Here(py_frame); +bad: + Py_XDECREF(py_code); + Py_XDECREF(py_frame); +} + +#if PY_MAJOR_VERSION < 3 +static int __Pyx_GetBuffer(PyObject *obj, Py_buffer *view, int flags) { + if (PyObject_CheckBuffer(obj)) return PyObject_GetBuffer(obj, view, flags); + if (__Pyx_TypeCheck(obj, __pyx_ptype_5numpy_ndarray)) return __pyx_pw_5numpy_7ndarray_1__getbuffer__(obj, view, flags); + PyErr_Format(PyExc_TypeError, "'%.200s' does not have the buffer interface", Py_TYPE(obj)->tp_name); + return -1; +} +static void __Pyx_ReleaseBuffer(Py_buffer *view) { + PyObject *obj = view->obj; + if (!obj) return; + if (PyObject_CheckBuffer(obj)) { + PyBuffer_Release(view); + return; + } + if ((0)) {} + else if (__Pyx_TypeCheck(obj, __pyx_ptype_5numpy_ndarray)) __pyx_pw_5numpy_7ndarray_3__releasebuffer__(obj, view); + view->obj = NULL; + Py_DECREF(obj); +} +#endif + + + /* CIntToPy */ + static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) { + const long neg_one = (long) ((long) 0 - (long) 1), const_zero = (long) 0; + const int is_unsigned = neg_one > const_zero; + if (is_unsigned) { + if (sizeof(long) < sizeof(long)) { + return PyInt_FromLong((long) value); + } else if (sizeof(long) <= sizeof(unsigned long)) { + return PyLong_FromUnsignedLong((unsigned long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) { + return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); +#endif + } + } else { + if (sizeof(long) <= sizeof(long)) { + return PyInt_FromLong((long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) { + return PyLong_FromLongLong((PY_LONG_LONG) value); +#endif + } + } + { + int one = 1; int little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&value; + return _PyLong_FromByteArray(bytes, sizeof(long), + little, !is_unsigned); + } +} + +/* CIntFromPyVerify */ + #define __PYX_VERIFY_RETURN_INT(target_type, func_type, func_value)\ + __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 0) +#define __PYX_VERIFY_RETURN_INT_EXC(target_type, func_type, func_value)\ + __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, 1) +#define __PYX__VERIFY_RETURN_INT(target_type, func_type, func_value, exc)\ + {\ + func_type value = func_value;\ + if (sizeof(target_type) < sizeof(func_type)) {\ + if (unlikely(value != (func_type) (target_type) value)) {\ + func_type zero = 0;\ + if (exc && unlikely(value == (func_type)-1 && PyErr_Occurred()))\ + return (target_type) -1;\ + if (is_unsigned && unlikely(value < zero))\ + goto raise_neg_overflow;\ + else\ + goto raise_overflow;\ + }\ + }\ + return (target_type) value;\ + } + +/* CIntToPy */ + static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) { + const int neg_one = (int) ((int) 0 - (int) 1), const_zero = (int) 0; + const int is_unsigned = neg_one > const_zero; + if (is_unsigned) { + if (sizeof(int) < sizeof(long)) { + return PyInt_FromLong((long) value); + } else if (sizeof(int) <= sizeof(unsigned long)) { + return PyLong_FromUnsignedLong((unsigned long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) { + return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); +#endif + } + } else { + if (sizeof(int) <= sizeof(long)) { + return PyInt_FromLong((long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) { + return PyLong_FromLongLong((PY_LONG_LONG) value); +#endif + } + } + { + int one = 1; int little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&value; + return _PyLong_FromByteArray(bytes, sizeof(int), + little, !is_unsigned); + } +} + +/* Declarations */ + #if CYTHON_CCOMPLEX + #ifdef __cplusplus + static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { + return ::std::complex< float >(x, y); + } + #else + static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { + return x + y*(__pyx_t_float_complex)_Complex_I; + } + #endif +#else + static CYTHON_INLINE __pyx_t_float_complex __pyx_t_float_complex_from_parts(float x, float y) { + __pyx_t_float_complex z; + z.real = x; + z.imag = y; + return z; + } +#endif + +/* Arithmetic */ + #if CYTHON_CCOMPLEX +#else + static CYTHON_INLINE int __Pyx_c_eq_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + return (a.real == b.real) && (a.imag == b.imag); + } + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_sum_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + __pyx_t_float_complex z; + z.real = a.real + b.real; + z.imag = a.imag + b.imag; + return z; + } + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_diff_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + __pyx_t_float_complex z; + z.real = a.real - b.real; + z.imag = a.imag - b.imag; + return z; + } + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_prod_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + __pyx_t_float_complex z; + z.real = a.real * b.real - a.imag * b.imag; + z.imag = a.real * b.imag + a.imag * b.real; + return z; + } + #if 1 + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + if (b.imag == 0) { + return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.real); + } else if (fabsf(b.real) >= fabsf(b.imag)) { + if (b.real == 0 && b.imag == 0) { + return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.imag); + } else { + float r = b.imag / b.real; + float s = (float)(1.0) / (b.real + b.imag * r); + return __pyx_t_float_complex_from_parts( + (a.real + a.imag * r) * s, (a.imag - a.real * r) * s); + } + } else { + float r = b.real / b.imag; + float s = (float)(1.0) / (b.imag + b.real * r); + return __pyx_t_float_complex_from_parts( + (a.real * r + a.imag) * s, (a.imag * r - a.real) * s); + } + } + #else + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_quot_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + if (b.imag == 0) { + return __pyx_t_float_complex_from_parts(a.real / b.real, a.imag / b.real); + } else { + float denom = b.real * b.real + b.imag * b.imag; + return __pyx_t_float_complex_from_parts( + (a.real * b.real + a.imag * b.imag) / denom, + (a.imag * b.real - a.real * b.imag) / denom); + } + } + #endif + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_neg_float(__pyx_t_float_complex a) { + __pyx_t_float_complex z; + z.real = -a.real; + z.imag = -a.imag; + return z; + } + static CYTHON_INLINE int __Pyx_c_is_zero_float(__pyx_t_float_complex a) { + return (a.real == 0) && (a.imag == 0); + } + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_conj_float(__pyx_t_float_complex a) { + __pyx_t_float_complex z; + z.real = a.real; + z.imag = -a.imag; + return z; + } + #if 1 + static CYTHON_INLINE float __Pyx_c_abs_float(__pyx_t_float_complex z) { + #if !defined(HAVE_HYPOT) || defined(_MSC_VER) + return sqrtf(z.real*z.real + z.imag*z.imag); + #else + return hypotf(z.real, z.imag); + #endif + } + static CYTHON_INLINE __pyx_t_float_complex __Pyx_c_pow_float(__pyx_t_float_complex a, __pyx_t_float_complex b) { + __pyx_t_float_complex z; + float r, lnr, theta, z_r, z_theta; + if (b.imag == 0 && b.real == (int)b.real) { + if (b.real < 0) { + float denom = a.real * a.real + a.imag * a.imag; + a.real = a.real / denom; + a.imag = -a.imag / denom; + b.real = -b.real; + } + switch ((int)b.real) { + case 0: + z.real = 1; + z.imag = 0; + return z; + case 1: + return a; + case 2: + return __Pyx_c_prod_float(a, a); + case 3: + z = __Pyx_c_prod_float(a, a); + return __Pyx_c_prod_float(z, a); + case 4: + z = __Pyx_c_prod_float(a, a); + return __Pyx_c_prod_float(z, z); + } + } + if (a.imag == 0) { + if (a.real == 0) { + return a; + } else if (b.imag == 0) { + z.real = powf(a.real, b.real); + z.imag = 0; + return z; + } else if (a.real > 0) { + r = a.real; + theta = 0; + } else { + r = -a.real; + theta = atan2f(0.0, -1.0); + } + } else { + r = __Pyx_c_abs_float(a); + theta = atan2f(a.imag, a.real); + } + lnr = logf(r); + z_r = expf(lnr * b.real - theta * b.imag); + z_theta = theta * b.real + lnr * b.imag; + z.real = z_r * cosf(z_theta); + z.imag = z_r * sinf(z_theta); + return z; + } + #endif +#endif + +/* Declarations */ + #if CYTHON_CCOMPLEX + #ifdef __cplusplus + static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { + return ::std::complex< double >(x, y); + } + #else + static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { + return x + y*(__pyx_t_double_complex)_Complex_I; + } + #endif +#else + static CYTHON_INLINE __pyx_t_double_complex __pyx_t_double_complex_from_parts(double x, double y) { + __pyx_t_double_complex z; + z.real = x; + z.imag = y; + return z; + } +#endif + +/* Arithmetic */ + #if CYTHON_CCOMPLEX +#else + static CYTHON_INLINE int __Pyx_c_eq_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + return (a.real == b.real) && (a.imag == b.imag); + } + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_sum_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + __pyx_t_double_complex z; + z.real = a.real + b.real; + z.imag = a.imag + b.imag; + return z; + } + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_diff_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + __pyx_t_double_complex z; + z.real = a.real - b.real; + z.imag = a.imag - b.imag; + return z; + } + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_prod_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + __pyx_t_double_complex z; + z.real = a.real * b.real - a.imag * b.imag; + z.imag = a.real * b.imag + a.imag * b.real; + return z; + } + #if 1 + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + if (b.imag == 0) { + return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.real); + } else if (fabs(b.real) >= fabs(b.imag)) { + if (b.real == 0 && b.imag == 0) { + return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.imag); + } else { + double r = b.imag / b.real; + double s = (double)(1.0) / (b.real + b.imag * r); + return __pyx_t_double_complex_from_parts( + (a.real + a.imag * r) * s, (a.imag - a.real * r) * s); + } + } else { + double r = b.real / b.imag; + double s = (double)(1.0) / (b.imag + b.real * r); + return __pyx_t_double_complex_from_parts( + (a.real * r + a.imag) * s, (a.imag * r - a.real) * s); + } + } + #else + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_quot_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + if (b.imag == 0) { + return __pyx_t_double_complex_from_parts(a.real / b.real, a.imag / b.real); + } else { + double denom = b.real * b.real + b.imag * b.imag; + return __pyx_t_double_complex_from_parts( + (a.real * b.real + a.imag * b.imag) / denom, + (a.imag * b.real - a.real * b.imag) / denom); + } + } + #endif + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_neg_double(__pyx_t_double_complex a) { + __pyx_t_double_complex z; + z.real = -a.real; + z.imag = -a.imag; + return z; + } + static CYTHON_INLINE int __Pyx_c_is_zero_double(__pyx_t_double_complex a) { + return (a.real == 0) && (a.imag == 0); + } + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_conj_double(__pyx_t_double_complex a) { + __pyx_t_double_complex z; + z.real = a.real; + z.imag = -a.imag; + return z; + } + #if 1 + static CYTHON_INLINE double __Pyx_c_abs_double(__pyx_t_double_complex z) { + #if !defined(HAVE_HYPOT) || defined(_MSC_VER) + return sqrt(z.real*z.real + z.imag*z.imag); + #else + return hypot(z.real, z.imag); + #endif + } + static CYTHON_INLINE __pyx_t_double_complex __Pyx_c_pow_double(__pyx_t_double_complex a, __pyx_t_double_complex b) { + __pyx_t_double_complex z; + double r, lnr, theta, z_r, z_theta; + if (b.imag == 0 && b.real == (int)b.real) { + if (b.real < 0) { + double denom = a.real * a.real + a.imag * a.imag; + a.real = a.real / denom; + a.imag = -a.imag / denom; + b.real = -b.real; + } + switch ((int)b.real) { + case 0: + z.real = 1; + z.imag = 0; + return z; + case 1: + return a; + case 2: + return __Pyx_c_prod_double(a, a); + case 3: + z = __Pyx_c_prod_double(a, a); + return __Pyx_c_prod_double(z, a); + case 4: + z = __Pyx_c_prod_double(a, a); + return __Pyx_c_prod_double(z, z); + } + } + if (a.imag == 0) { + if (a.real == 0) { + return a; + } else if (b.imag == 0) { + z.real = pow(a.real, b.real); + z.imag = 0; + return z; + } else if (a.real > 0) { + r = a.real; + theta = 0; + } else { + r = -a.real; + theta = atan2(0.0, -1.0); + } + } else { + r = __Pyx_c_abs_double(a); + theta = atan2(a.imag, a.real); + } + lnr = log(r); + z_r = exp(lnr * b.real - theta * b.imag); + z_theta = theta * b.real + lnr * b.imag; + z.real = z_r * cos(z_theta); + z.imag = z_r * sin(z_theta); + return z; + } + #endif +#endif + +/* CIntToPy */ + static CYTHON_INLINE PyObject* __Pyx_PyInt_From_enum__NPY_TYPES(enum NPY_TYPES value) { + const enum NPY_TYPES neg_one = (enum NPY_TYPES) ((enum NPY_TYPES) 0 - (enum NPY_TYPES) 1), const_zero = (enum NPY_TYPES) 0; + const int is_unsigned = neg_one > const_zero; + if (is_unsigned) { + if (sizeof(enum NPY_TYPES) < sizeof(long)) { + return PyInt_FromLong((long) value); + } else if (sizeof(enum NPY_TYPES) <= sizeof(unsigned long)) { + return PyLong_FromUnsignedLong((unsigned long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(enum NPY_TYPES) <= sizeof(unsigned PY_LONG_LONG)) { + return PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) value); +#endif + } + } else { + if (sizeof(enum NPY_TYPES) <= sizeof(long)) { + return PyInt_FromLong((long) value); +#ifdef HAVE_LONG_LONG + } else if (sizeof(enum NPY_TYPES) <= sizeof(PY_LONG_LONG)) { + return PyLong_FromLongLong((PY_LONG_LONG) value); +#endif + } + } + { + int one = 1; int little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&value; + return _PyLong_FromByteArray(bytes, sizeof(enum NPY_TYPES), + little, !is_unsigned); + } +} + +/* CIntFromPy */ + static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *x) { + const int neg_one = (int) ((int) 0 - (int) 1), const_zero = (int) 0; + const int is_unsigned = neg_one > const_zero; +#if PY_MAJOR_VERSION < 3 + if (likely(PyInt_Check(x))) { + if (sizeof(int) < sizeof(long)) { + __PYX_VERIFY_RETURN_INT(int, long, PyInt_AS_LONG(x)) + } else { + long val = PyInt_AS_LONG(x); + if (is_unsigned && unlikely(val < 0)) { + goto raise_neg_overflow; + } + return (int) val; + } + } else +#endif + if (likely(PyLong_Check(x))) { + if (is_unsigned) { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (int) 0; + case 1: __PYX_VERIFY_RETURN_INT(int, digit, digits[0]) + case 2: + if (8 * sizeof(int) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) >= 2 * PyLong_SHIFT) { + return (int) (((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); + } + } + break; + case 3: + if (8 * sizeof(int) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) >= 3 * PyLong_SHIFT) { + return (int) (((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); + } + } + break; + case 4: + if (8 * sizeof(int) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) >= 4 * PyLong_SHIFT) { + return (int) (((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0])); + } + } + break; + } +#endif +#if CYTHON_COMPILING_IN_CPYTHON + if (unlikely(Py_SIZE(x) < 0)) { + goto raise_neg_overflow; + } +#else + { + int result = PyObject_RichCompareBool(x, Py_False, Py_LT); + if (unlikely(result < 0)) + return (int) -1; + if (unlikely(result == 1)) + goto raise_neg_overflow; + } +#endif + if (sizeof(int) <= sizeof(unsigned long)) { + __PYX_VERIFY_RETURN_INT_EXC(int, unsigned long, PyLong_AsUnsignedLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(int) <= sizeof(unsigned PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(int, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) +#endif + } + } else { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (int) 0; + case -1: __PYX_VERIFY_RETURN_INT(int, sdigit, (sdigit) (-(sdigit)digits[0])) + case 1: __PYX_VERIFY_RETURN_INT(int, digit, +digits[0]) + case -2: + if (8 * sizeof(int) - 1 > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { + return (int) (((int)-1)*(((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + case 2: + if (8 * sizeof(int) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { + return (int) ((((((int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + case -3: + if (8 * sizeof(int) - 1 > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { + return (int) (((int)-1)*(((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + case 3: + if (8 * sizeof(int) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { + return (int) ((((((((int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + case -4: + if (8 * sizeof(int) - 1 > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 4 * PyLong_SHIFT) { + return (int) (((int)-1)*(((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + case 4: + if (8 * sizeof(int) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(int, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(int) - 1 > 4 * PyLong_SHIFT) { + return (int) ((((((((((int)digits[3]) << PyLong_SHIFT) | (int)digits[2]) << PyLong_SHIFT) | (int)digits[1]) << PyLong_SHIFT) | (int)digits[0]))); + } + } + break; + } +#endif + if (sizeof(int) <= sizeof(long)) { + __PYX_VERIFY_RETURN_INT_EXC(int, long, PyLong_AsLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(int) <= sizeof(PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(int, PY_LONG_LONG, PyLong_AsLongLong(x)) +#endif + } + } + { +#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) + PyErr_SetString(PyExc_RuntimeError, + "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); +#else + int val; + PyObject *v = __Pyx_PyNumber_IntOrLong(x); + #if PY_MAJOR_VERSION < 3 + if (likely(v) && !PyLong_Check(v)) { + PyObject *tmp = v; + v = PyNumber_Long(tmp); + Py_DECREF(tmp); + } + #endif + if (likely(v)) { + int one = 1; int is_little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&val; + int ret = _PyLong_AsByteArray((PyLongObject *)v, + bytes, sizeof(val), + is_little, !is_unsigned); + Py_DECREF(v); + if (likely(!ret)) + return val; + } +#endif + return (int) -1; + } + } else { + int val; + PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); + if (!tmp) return (int) -1; + val = __Pyx_PyInt_As_int(tmp); + Py_DECREF(tmp); + return val; + } +raise_overflow: + PyErr_SetString(PyExc_OverflowError, + "value too large to convert to int"); + return (int) -1; +raise_neg_overflow: + PyErr_SetString(PyExc_OverflowError, + "can't convert negative value to int"); + return (int) -1; +} + +/* CIntFromPy */ + static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *x) { + const size_t neg_one = (size_t) ((size_t) 0 - (size_t) 1), const_zero = (size_t) 0; + const int is_unsigned = neg_one > const_zero; +#if PY_MAJOR_VERSION < 3 + if (likely(PyInt_Check(x))) { + if (sizeof(size_t) < sizeof(long)) { + __PYX_VERIFY_RETURN_INT(size_t, long, PyInt_AS_LONG(x)) + } else { + long val = PyInt_AS_LONG(x); + if (is_unsigned && unlikely(val < 0)) { + goto raise_neg_overflow; + } + return (size_t) val; + } + } else +#endif + if (likely(PyLong_Check(x))) { + if (is_unsigned) { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (size_t) 0; + case 1: __PYX_VERIFY_RETURN_INT(size_t, digit, digits[0]) + case 2: + if (8 * sizeof(size_t) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) >= 2 * PyLong_SHIFT) { + return (size_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + } + break; + case 3: + if (8 * sizeof(size_t) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) >= 3 * PyLong_SHIFT) { + return (size_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + } + break; + case 4: + if (8 * sizeof(size_t) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) >= 4 * PyLong_SHIFT) { + return (size_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + } + break; + } +#endif +#if CYTHON_COMPILING_IN_CPYTHON + if (unlikely(Py_SIZE(x) < 0)) { + goto raise_neg_overflow; + } +#else + { + int result = PyObject_RichCompareBool(x, Py_False, Py_LT); + if (unlikely(result < 0)) + return (size_t) -1; + if (unlikely(result == 1)) + goto raise_neg_overflow; + } +#endif + if (sizeof(size_t) <= sizeof(unsigned long)) { + __PYX_VERIFY_RETURN_INT_EXC(size_t, unsigned long, PyLong_AsUnsignedLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(size_t) <= sizeof(unsigned PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(size_t, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) +#endif + } + } else { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (size_t) 0; + case -1: __PYX_VERIFY_RETURN_INT(size_t, sdigit, (sdigit) (-(sdigit)digits[0])) + case 1: __PYX_VERIFY_RETURN_INT(size_t, digit, +digits[0]) + case -2: + if (8 * sizeof(size_t) - 1 > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { + return (size_t) (((size_t)-1)*(((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + case 2: + if (8 * sizeof(size_t) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { + return (size_t) ((((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + case -3: + if (8 * sizeof(size_t) - 1 > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { + return (size_t) (((size_t)-1)*(((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + case 3: + if (8 * sizeof(size_t) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { + return (size_t) ((((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + case -4: + if (8 * sizeof(size_t) - 1 > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 4 * PyLong_SHIFT) { + return (size_t) (((size_t)-1)*(((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + case 4: + if (8 * sizeof(size_t) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(size_t, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(size_t) - 1 > 4 * PyLong_SHIFT) { + return (size_t) ((((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]))); + } + } + break; + } +#endif + if (sizeof(size_t) <= sizeof(long)) { + __PYX_VERIFY_RETURN_INT_EXC(size_t, long, PyLong_AsLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(size_t) <= sizeof(PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(size_t, PY_LONG_LONG, PyLong_AsLongLong(x)) +#endif + } + } + { +#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) + PyErr_SetString(PyExc_RuntimeError, + "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); +#else + size_t val; + PyObject *v = __Pyx_PyNumber_IntOrLong(x); + #if PY_MAJOR_VERSION < 3 + if (likely(v) && !PyLong_Check(v)) { + PyObject *tmp = v; + v = PyNumber_Long(tmp); + Py_DECREF(tmp); + } + #endif + if (likely(v)) { + int one = 1; int is_little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&val; + int ret = _PyLong_AsByteArray((PyLongObject *)v, + bytes, sizeof(val), + is_little, !is_unsigned); + Py_DECREF(v); + if (likely(!ret)) + return val; + } +#endif + return (size_t) -1; + } + } else { + size_t val; + PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); + if (!tmp) return (size_t) -1; + val = __Pyx_PyInt_As_size_t(tmp); + Py_DECREF(tmp); + return val; + } +raise_overflow: + PyErr_SetString(PyExc_OverflowError, + "value too large to convert to size_t"); + return (size_t) -1; +raise_neg_overflow: + PyErr_SetString(PyExc_OverflowError, + "can't convert negative value to size_t"); + return (size_t) -1; +} + +/* CIntFromPy */ + static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *x) { + const long neg_one = (long) ((long) 0 - (long) 1), const_zero = (long) 0; + const int is_unsigned = neg_one > const_zero; +#if PY_MAJOR_VERSION < 3 + if (likely(PyInt_Check(x))) { + if (sizeof(long) < sizeof(long)) { + __PYX_VERIFY_RETURN_INT(long, long, PyInt_AS_LONG(x)) + } else { + long val = PyInt_AS_LONG(x); + if (is_unsigned && unlikely(val < 0)) { + goto raise_neg_overflow; + } + return (long) val; + } + } else +#endif + if (likely(PyLong_Check(x))) { + if (is_unsigned) { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (long) 0; + case 1: __PYX_VERIFY_RETURN_INT(long, digit, digits[0]) + case 2: + if (8 * sizeof(long) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) >= 2 * PyLong_SHIFT) { + return (long) (((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); + } + } + break; + case 3: + if (8 * sizeof(long) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) >= 3 * PyLong_SHIFT) { + return (long) (((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); + } + } + break; + case 4: + if (8 * sizeof(long) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) >= 4 * PyLong_SHIFT) { + return (long) (((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0])); + } + } + break; + } +#endif +#if CYTHON_COMPILING_IN_CPYTHON + if (unlikely(Py_SIZE(x) < 0)) { + goto raise_neg_overflow; + } +#else + { + int result = PyObject_RichCompareBool(x, Py_False, Py_LT); + if (unlikely(result < 0)) + return (long) -1; + if (unlikely(result == 1)) + goto raise_neg_overflow; + } +#endif + if (sizeof(long) <= sizeof(unsigned long)) { + __PYX_VERIFY_RETURN_INT_EXC(long, unsigned long, PyLong_AsUnsignedLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(long) <= sizeof(unsigned PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(long, unsigned PY_LONG_LONG, PyLong_AsUnsignedLongLong(x)) +#endif + } + } else { +#if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)x)->ob_digit; + switch (Py_SIZE(x)) { + case 0: return (long) 0; + case -1: __PYX_VERIFY_RETURN_INT(long, sdigit, (sdigit) (-(sdigit)digits[0])) + case 1: __PYX_VERIFY_RETURN_INT(long, digit, +digits[0]) + case -2: + if (8 * sizeof(long) - 1 > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { + return (long) (((long)-1)*(((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + case 2: + if (8 * sizeof(long) > 1 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 2 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { + return (long) ((((((long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + case -3: + if (8 * sizeof(long) - 1 > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { + return (long) (((long)-1)*(((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + case 3: + if (8 * sizeof(long) > 2 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 3 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { + return (long) ((((((((long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + case -4: + if (8 * sizeof(long) - 1 > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, long, -(long) (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { + return (long) (((long)-1)*(((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + case 4: + if (8 * sizeof(long) > 3 * PyLong_SHIFT) { + if (8 * sizeof(unsigned long) > 4 * PyLong_SHIFT) { + __PYX_VERIFY_RETURN_INT(long, unsigned long, (((((((((unsigned long)digits[3]) << PyLong_SHIFT) | (unsigned long)digits[2]) << PyLong_SHIFT) | (unsigned long)digits[1]) << PyLong_SHIFT) | (unsigned long)digits[0]))) + } else if (8 * sizeof(long) - 1 > 4 * PyLong_SHIFT) { + return (long) ((((((((((long)digits[3]) << PyLong_SHIFT) | (long)digits[2]) << PyLong_SHIFT) | (long)digits[1]) << PyLong_SHIFT) | (long)digits[0]))); + } + } + break; + } +#endif + if (sizeof(long) <= sizeof(long)) { + __PYX_VERIFY_RETURN_INT_EXC(long, long, PyLong_AsLong(x)) +#ifdef HAVE_LONG_LONG + } else if (sizeof(long) <= sizeof(PY_LONG_LONG)) { + __PYX_VERIFY_RETURN_INT_EXC(long, PY_LONG_LONG, PyLong_AsLongLong(x)) +#endif + } + } + { +#if CYTHON_COMPILING_IN_PYPY && !defined(_PyLong_AsByteArray) + PyErr_SetString(PyExc_RuntimeError, + "_PyLong_AsByteArray() not available in PyPy, cannot convert large numbers"); +#else + long val; + PyObject *v = __Pyx_PyNumber_IntOrLong(x); + #if PY_MAJOR_VERSION < 3 + if (likely(v) && !PyLong_Check(v)) { + PyObject *tmp = v; + v = PyNumber_Long(tmp); + Py_DECREF(tmp); + } + #endif + if (likely(v)) { + int one = 1; int is_little = (int)*(unsigned char *)&one; + unsigned char *bytes = (unsigned char *)&val; + int ret = _PyLong_AsByteArray((PyLongObject *)v, + bytes, sizeof(val), + is_little, !is_unsigned); + Py_DECREF(v); + if (likely(!ret)) + return val; + } +#endif + return (long) -1; + } + } else { + long val; + PyObject *tmp = __Pyx_PyNumber_IntOrLong(x); + if (!tmp) return (long) -1; + val = __Pyx_PyInt_As_long(tmp); + Py_DECREF(tmp); + return val; + } +raise_overflow: + PyErr_SetString(PyExc_OverflowError, + "value too large to convert to long"); + return (long) -1; +raise_neg_overflow: + PyErr_SetString(PyExc_OverflowError, + "can't convert negative value to long"); + return (long) -1; +} + +/* FastTypeChecks */ + #if CYTHON_COMPILING_IN_CPYTHON +static int __Pyx_InBases(PyTypeObject *a, PyTypeObject *b) { + while (a) { + a = a->tp_base; + if (a == b) + return 1; + } + return b == &PyBaseObject_Type; +} +static CYTHON_INLINE int __Pyx_IsSubtype(PyTypeObject *a, PyTypeObject *b) { + PyObject *mro; + if (a == b) return 1; + mro = a->tp_mro; + if (likely(mro)) { + Py_ssize_t i, n; + n = PyTuple_GET_SIZE(mro); + for (i = 0; i < n; i++) { + if (PyTuple_GET_ITEM(mro, i) == (PyObject *)b) + return 1; + } + return 0; + } + return __Pyx_InBases(a, b); +} +#if PY_MAJOR_VERSION == 2 +static int __Pyx_inner_PyErr_GivenExceptionMatches2(PyObject *err, PyObject* exc_type1, PyObject* exc_type2) { + PyObject *exception, *value, *tb; + int res; + __Pyx_PyThreadState_declare + __Pyx_PyThreadState_assign + __Pyx_ErrFetch(&exception, &value, &tb); + res = exc_type1 ? PyObject_IsSubclass(err, exc_type1) : 0; + if (unlikely(res == -1)) { + PyErr_WriteUnraisable(err); + res = 0; + } + if (!res) { + res = PyObject_IsSubclass(err, exc_type2); + if (unlikely(res == -1)) { + PyErr_WriteUnraisable(err); + res = 0; + } + } + __Pyx_ErrRestore(exception, value, tb); + return res; +} +#else +static CYTHON_INLINE int __Pyx_inner_PyErr_GivenExceptionMatches2(PyObject *err, PyObject* exc_type1, PyObject *exc_type2) { + int res = exc_type1 ? __Pyx_IsSubtype((PyTypeObject*)err, (PyTypeObject*)exc_type1) : 0; + if (!res) { + res = __Pyx_IsSubtype((PyTypeObject*)err, (PyTypeObject*)exc_type2); + } + return res; +} +#endif +static int __Pyx_PyErr_GivenExceptionMatchesTuple(PyObject *exc_type, PyObject *tuple) { + Py_ssize_t i, n; + assert(PyExceptionClass_Check(exc_type)); + n = PyTuple_GET_SIZE(tuple); +#if PY_MAJOR_VERSION >= 3 + for (i=0; ip) { + #if PY_MAJOR_VERSION < 3 + if (t->is_unicode) { + *t->p = PyUnicode_DecodeUTF8(t->s, t->n - 1, NULL); + } else if (t->intern) { + *t->p = PyString_InternFromString(t->s); + } else { + *t->p = PyString_FromStringAndSize(t->s, t->n - 1); + } + #else + if (t->is_unicode | t->is_str) { + if (t->intern) { + *t->p = PyUnicode_InternFromString(t->s); + } else if (t->encoding) { + *t->p = PyUnicode_Decode(t->s, t->n - 1, t->encoding, NULL); + } else { + *t->p = PyUnicode_FromStringAndSize(t->s, t->n - 1); + } + } else { + *t->p = PyBytes_FromStringAndSize(t->s, t->n - 1); + } + #endif + if (!*t->p) + return -1; + if (PyObject_Hash(*t->p) == -1) + return -1; + ++t; + } + return 0; +} + +static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char* c_str) { + return __Pyx_PyUnicode_FromStringAndSize(c_str, (Py_ssize_t)strlen(c_str)); +} +static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject* o) { + Py_ssize_t ignore; + return __Pyx_PyObject_AsStringAndSize(o, &ignore); +} +#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT +#if !CYTHON_PEP393_ENABLED +static const char* __Pyx_PyUnicode_AsStringAndSize(PyObject* o, Py_ssize_t *length) { + char* defenc_c; + PyObject* defenc = _PyUnicode_AsDefaultEncodedString(o, NULL); + if (!defenc) return NULL; + defenc_c = PyBytes_AS_STRING(defenc); +#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII + { + char* end = defenc_c + PyBytes_GET_SIZE(defenc); + char* c; + for (c = defenc_c; c < end; c++) { + if ((unsigned char) (*c) >= 128) { + PyUnicode_AsASCIIString(o); + return NULL; + } + } + } +#endif + *length = PyBytes_GET_SIZE(defenc); + return defenc_c; +} +#else +static CYTHON_INLINE const char* __Pyx_PyUnicode_AsStringAndSize(PyObject* o, Py_ssize_t *length) { + if (unlikely(__Pyx_PyUnicode_READY(o) == -1)) return NULL; +#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII + if (likely(PyUnicode_IS_ASCII(o))) { + *length = PyUnicode_GET_LENGTH(o); + return PyUnicode_AsUTF8(o); + } else { + PyUnicode_AsASCIIString(o); + return NULL; + } +#else + return PyUnicode_AsUTF8AndSize(o, length); +#endif +} +#endif +#endif +static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject* o, Py_ssize_t *length) { +#if __PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT + if ( +#if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII + __Pyx_sys_getdefaultencoding_not_ascii && +#endif + PyUnicode_Check(o)) { + return __Pyx_PyUnicode_AsStringAndSize(o, length); + } else +#endif +#if (!CYTHON_COMPILING_IN_PYPY) || (defined(PyByteArray_AS_STRING) && defined(PyByteArray_GET_SIZE)) + if (PyByteArray_Check(o)) { + *length = PyByteArray_GET_SIZE(o); + return PyByteArray_AS_STRING(o); + } else +#endif + { + char* result; + int r = PyBytes_AsStringAndSize(o, &result, length); + if (unlikely(r < 0)) { + return NULL; + } else { + return result; + } + } +} +static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject* x) { + int is_true = x == Py_True; + if (is_true | (x == Py_False) | (x == Py_None)) return is_true; + else return PyObject_IsTrue(x); +} +static CYTHON_INLINE int __Pyx_PyObject_IsTrueAndDecref(PyObject* x) { + int retval; + if (unlikely(!x)) return -1; + retval = __Pyx_PyObject_IsTrue(x); + Py_DECREF(x); + return retval; +} +static PyObject* __Pyx_PyNumber_IntOrLongWrongResultType(PyObject* result, const char* type_name) { +#if PY_MAJOR_VERSION >= 3 + if (PyLong_Check(result)) { + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + "__int__ returned non-int (type %.200s). " + "The ability to return an instance of a strict subclass of int " + "is deprecated, and may be removed in a future version of Python.", + Py_TYPE(result)->tp_name)) { + Py_DECREF(result); + return NULL; + } + return result; + } +#endif + PyErr_Format(PyExc_TypeError, + "__%.4s__ returned non-%.4s (type %.200s)", + type_name, type_name, Py_TYPE(result)->tp_name); + Py_DECREF(result); + return NULL; +} +static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x) { +#if CYTHON_USE_TYPE_SLOTS + PyNumberMethods *m; +#endif + const char *name = NULL; + PyObject *res = NULL; +#if PY_MAJOR_VERSION < 3 + if (likely(PyInt_Check(x) || PyLong_Check(x))) +#else + if (likely(PyLong_Check(x))) +#endif + return __Pyx_NewRef(x); +#if CYTHON_USE_TYPE_SLOTS + m = Py_TYPE(x)->tp_as_number; + #if PY_MAJOR_VERSION < 3 + if (m && m->nb_int) { + name = "int"; + res = m->nb_int(x); + } + else if (m && m->nb_long) { + name = "long"; + res = m->nb_long(x); + } + #else + if (likely(m && m->nb_int)) { + name = "int"; + res = m->nb_int(x); + } + #endif +#else + if (!PyBytes_CheckExact(x) && !PyUnicode_CheckExact(x)) { + res = PyNumber_Int(x); + } +#endif + if (likely(res)) { +#if PY_MAJOR_VERSION < 3 + if (unlikely(!PyInt_Check(res) && !PyLong_Check(res))) { +#else + if (unlikely(!PyLong_CheckExact(res))) { +#endif + return __Pyx_PyNumber_IntOrLongWrongResultType(res, name); + } + } + else if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "an integer is required"); + } + return res; +} +static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject* b) { + Py_ssize_t ival; + PyObject *x; +#if PY_MAJOR_VERSION < 3 + if (likely(PyInt_CheckExact(b))) { + if (sizeof(Py_ssize_t) >= sizeof(long)) + return PyInt_AS_LONG(b); + else + return PyInt_AsSsize_t(b); + } +#endif + if (likely(PyLong_CheckExact(b))) { + #if CYTHON_USE_PYLONG_INTERNALS + const digit* digits = ((PyLongObject*)b)->ob_digit; + const Py_ssize_t size = Py_SIZE(b); + if (likely(__Pyx_sst_abs(size) <= 1)) { + ival = likely(size) ? digits[0] : 0; + if (size == -1) ival = -ival; + return ival; + } else { + switch (size) { + case 2: + if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) { + return (Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + case -2: + if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) { + return -(Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + case 3: + if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) { + return (Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + case -3: + if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) { + return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + case 4: + if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) { + return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + case -4: + if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) { + return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0])); + } + break; + } + } + #endif + return PyLong_AsSsize_t(b); + } + x = PyNumber_Index(b); + if (!x) return -1; + ival = PyInt_AsSsize_t(x); + Py_DECREF(x); + return ival; +} +static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b) { + return b ? __Pyx_NewRef(Py_True) : __Pyx_NewRef(Py_False); +} +static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) { + return PyInt_FromSize_t(ival); +} + + +#endif /* Py_PYTHON_H */ diff --git a/research/cv/WS3/third_party/src/knn.pyx b/research/cv/WS3/third_party/src/knn.pyx new file mode 100644 index 000000000..8fc30980f --- /dev/null +++ b/research/cv/WS3/third_party/src/knn.pyx @@ -0,0 +1,149 @@ +# distutils: language = c++ +# distutils: sources = knn.cxx + +import numpy as np +cimport numpy as np +import cython + +cdef extern from "knn_.h": + void cpp_knn(const float* points, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* indices) + + void cpp_knn_omp(const float* points, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* indices) + + void cpp_knn_batch(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices) + + void cpp_knn_batch_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices) + + void cpp_knn_batch_distance_pick(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + float* queries, const size_t nqueries, + const size_t K, long* batch_indices) + + void cpp_knn_batch_distance_pick_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + float* batch_queries, const size_t nqueries, + const size_t K, long* batch_indices) + +def knn(pts, queries, K, omp=False): + + # define shape parameters + cdef int npts + cdef int dim + cdef int K_cpp + cdef int nqueries + + # define tables + cdef np.ndarray[np.float32_t, ndim=2] pts_cpp + cdef np.ndarray[np.float32_t, ndim=2] queries_cpp + cdef np.ndarray[np.int64_t, ndim=2] indices_cpp + + # set shape values + npts = pts.shape[0] + nqueries = queries.shape[0] + dim = pts.shape[1] + K_cpp = K + + # create indices tensor + indices = np.zeros((queries.shape[0], K), dtype=np.int64) + + pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + indices_cpp = indices + + # normal estimation + if omp: + cpp_knn_omp( pts_cpp.data, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + else: + cpp_knn( pts_cpp.data, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + + return indices + +def knn_batch(pts, queries, K, omp=False): + + # define shape parameters + cdef int batch_size + cdef int npts + cdef int nqueries + cdef int K_cpp + cdef int dim + + # define tables + cdef np.ndarray[np.float32_t, ndim=3] pts_cpp + cdef np.ndarray[np.float32_t, ndim=3] queries_cpp + cdef np.ndarray[np.int64_t, ndim=3] indices_cpp + + # set shape values + batch_size = pts.shape[0] + npts = pts.shape[1] + dim = pts.shape[2] + nqueries = queries.shape[1] + K_cpp = K + + # create indices tensor + indices = np.zeros((pts.shape[0], queries.shape[1], K), dtype=np.int64) + + pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + indices_cpp = indices + + # normal estimation + if omp: + cpp_knn_batch_omp( pts_cpp.data, batch_size, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + else: + cpp_knn_batch( pts_cpp.data, batch_size, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + + return indices + +def knn_batch_distance_pick(pts, nqueries, K, omp=False): + + # define shape parameters + cdef int batch_size + cdef int npts + cdef int nqueries_cpp + cdef int K_cpp + cdef int dim + + # define tables + cdef np.ndarray[np.float32_t, ndim=3] pts_cpp + cdef np.ndarray[np.float32_t, ndim=3] queries_cpp + cdef np.ndarray[np.int64_t, ndim=3] indices_cpp + + # set shape values + batch_size = pts.shape[0] + npts = pts.shape[1] + dim = pts.shape[2] + nqueries_cpp = nqueries + K_cpp = K + + # create indices tensor + indices = np.zeros((pts.shape[0], nqueries, K), dtype=np.long) + queries = np.zeros((pts.shape[0], nqueries, dim), dtype=np.float32) + + pts_cpp = np.ascontiguousarray(pts, dtype=np.float32) + queries_cpp = np.ascontiguousarray(queries, dtype=np.float32) + indices_cpp = indices + + if omp: + cpp_knn_batch_distance_pick_omp( pts_cpp.data, batch_size, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + else: + cpp_knn_batch_distance_pick( pts_cpp.data, batch_size, npts, dim, + queries_cpp.data, nqueries, + K_cpp, indices_cpp.data) + + return indices, queries \ No newline at end of file diff --git a/research/cv/WS3/third_party/src/knn_.cxx b/research/cv/WS3/third_party/src/knn_.cxx new file mode 100644 index 000000000..58a359559 --- /dev/null +++ b/research/cv/WS3/third_party/src/knn_.cxx @@ -0,0 +1,271 @@ + +#include "knn_.h" +#include "nanoflann.hpp" +using namespace nanoflann; + +#include "KDTreeTableAdaptor.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; + + + +void cpp_knn(const float* points, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* indices){ + + // create the kdtree + typedef KDTreeTableAdaptor< float, float> KDTree; + KDTree mat_index(npts, dim, points, 10); + mat_index.index->buildIndex(); + + std::vector out_dists_sqr(K); + std::vector out_ids(K); + + // iterate over the points + for(size_t i=0; i resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; + KDTree mat_index(npts, dim, points, 10); + mat_index.index->buildIndex(); + + + // iterate over the points +# pragma omp parallel for + for(size_t i=0; i out_ids(K); + std::vector out_dists_sqr(K); + + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; + KDTree mat_index(npts, dim, points, 10); + + mat_index.index->buildIndex(); + + std::vector out_dists_sqr(K); + std::vector out_ids(K); + + // iterate over the points + for(size_t i=0; i resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; + KDTree mat_index(npts, dim, points, 10); + + mat_index.index->buildIndex(); + + std::vector out_dists_sqr(K); + std::vector out_ids(K); + + // iterate over the points + for(size_t i=0; i resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; + KDTree tree(npts, dim, points, 10); + tree.index->buildIndex(); + + vector used(npts, 0); + int current_id = 0; + for(size_t ptid=0; ptid possible_ids; + while(possible_ids.size() == 0){ + for(size_t i=0; i query(3); + for(size_t i=0; i dists(K); + std::vector ids(K); + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&ids[0], &dists[0] ); + tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); + + for(size_t i=0; i KDTree; + KDTree tree(npts, dim, points, 10); + tree.index->buildIndex(); + + vector used(npts, 0); + int current_id = 0; + for(size_t ptid=0; ptid possible_ids; + while(possible_ids.size() == 0){ + for(size_t i=0; i query(3); + for(size_t i=0; i dists(K); + std::vector ids(K); + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&ids[0], &dists[0] ); + tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); + + for(size_t i=0; i +void cpp_knn(const float* points, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* indices); + +void cpp_knn_omp(const float* points, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* indices); + + +void cpp_knn_batch(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices); + +void cpp_knn_batch_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices); + +void cpp_knn_batch_distance_pick(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + float* queries, const size_t nqueries, + const size_t K, long* batch_indices); + +void cpp_knn_batch_distance_pick_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, + float* batch_queries, const size_t nqueries, + const size_t K, long* batch_indices); \ No newline at end of file diff --git a/research/cv/WS3/third_party/src/nanoflann.hpp b/research/cv/WS3/third_party/src/nanoflann.hpp new file mode 100644 index 000000000..45c185bb4 --- /dev/null +++ b/research/cv/WS3/third_party/src/nanoflann.hpp @@ -0,0 +1,1990 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved. + * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved. + * Copyright 2011-2016 Jose Luis Blanco (joseluisblancoc@gmail.com). + * All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +/** \mainpage nanoflann C++ API documentation + * nanoflann is a C++ header-only library for building KD-Trees, mostly + * optimized for 2D or 3D point clouds. + * + * nanoflann does not require compiling or installing, just an + * #include in your code. + * + * See: + * - C++ API organized by modules + * - Online README + * - Doxygen documentation + */ + +#ifndef NANOFLANN_HPP_ +#define NANOFLANN_HPP_ + +#include +#include +#include +#include +#include // for fwrite() +#define _USE_MATH_DEFINES // Required by MSVC to define M_PI,etc. in +#include // for abs() +#include // for abs() +#include + +// Avoid conflicting declaration of min/max macros in windows headers +#if !defined(NOMINMAX) && (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64)) +# define NOMINMAX +# ifdef max +# undef max +# undef min +# endif +#endif + +namespace nanoflann +{ +/** @addtogroup nanoflann_grp nanoflann C++ library for ANN + * @{ */ + + /** Library version: 0xMmP (M=Major,m=minor,P=patch) */ + #define NANOFLANN_VERSION 0x123 + + /** @addtogroup result_sets_grp Result set classes + * @{ */ + template + class KNNResultSet + { + IndexType * indices; + DistanceType* dists; + CountType capacity; + CountType count; + + public: + inline KNNResultSet(CountType capacity_) : indices(0), dists(0), capacity(capacity_), count(0) + { + } + + inline void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; + if (capacity) + dists[capacity-1] = (std::numeric_limits::max)(); + } + + inline CountType size() const + { + return count; + } + + inline bool full() const + { + return count == capacity; + } + + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are sufficient + */ + inline bool addPoint(DistanceType dist, IndexType index) + { + CountType i; + for (i = count; i > 0; --i) { +#ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same distance, the one with the lowest-index will be returned first. + if ( (dists[i-1] > dist) || ((dist == dists[i-1]) && (indices[i-1] > index)) ) { +#else + if (dists[i-1] > dist) { +#endif + if (i < capacity) { + dists[i] = dists[i-1]; + indices[i] = indices[i-1]; + } + } + else break; + } + if (i < capacity) { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; + + // tell caller that the search shall continue + return true; + } + + inline DistanceType worstDist() const + { + return dists[capacity-1]; + } + }; + + /** operator "<" for std::sort() */ + struct IndexDist_Sorter + { + /** PairType will be typically: std::pair */ + template + inline bool operator()(const PairType &p1, const PairType &p2) const { + return p1.second < p2.second; + } + }; + + /** + * A result-set class used when performing a radius based search. + */ + template + class RadiusResultSet + { + public: + const DistanceType radius; + + std::vector > &m_indices_dists; + + inline RadiusResultSet(DistanceType radius_, std::vector > &indices_dists) : radius(radius_), m_indices_dists(indices_dists) + { + init(); + } + + inline void init() { clear(); } + inline void clear() { m_indices_dists.clear(); } + + inline size_t size() const { return m_indices_dists.size(); } + + inline bool full() const { return true; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are sufficient + */ + inline bool addPoint(DistanceType dist, IndexType index) + { + if (dist < radius) + m_indices_dists.push_back(std::make_pair(index, dist)); + return true; + } + + inline DistanceType worstDist() const { return radius; } + + /** + * Find the worst result (furtherest neighbor) without copying or sorting + * Pre-conditions: size() > 0 + */ + std::pair worst_item() const + { + if (m_indices_dists.empty()) throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on an empty list of results."); + typedef typename std::vector >::const_iterator DistIt; + DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); + return *it; + } + }; + + + /** @} */ + + + /** @addtogroup loadsave_grp Load/save auxiliary functions + * @{ */ + template + void save_value(FILE* stream, const T& value, size_t count = 1) + { + fwrite(&value, sizeof(value), count, stream); + } + + template + void save_value(FILE* stream, const std::vector& value) + { + size_t size = value.size(); + fwrite(&size, sizeof(size_t), 1, stream); + fwrite(&value[0], sizeof(T), size, stream); + } + + template + void load_value(FILE* stream, T& value, size_t count = 1) + { + size_t read_cnt = fread(&value, sizeof(value), count, stream); + if (read_cnt != count) { + throw std::runtime_error("Cannot read from file"); + } + } + + + template + void load_value(FILE* stream, std::vector& value) + { + size_t size; + size_t read_cnt = fread(&size, sizeof(size_t), 1, stream); + if (read_cnt != 1) { + throw std::runtime_error("Cannot read from file"); + } + value.resize(size); + read_cnt = fread(&value[0], sizeof(T), size, stream); + if (read_cnt != size) { + throw std::runtime_error("Cannot read from file"); + } + } + /** @} */ + + + /** @addtogroup metric_grp Metric (distance) classes + * @{ */ + + struct Metric + { + }; + + /** Manhattan distance functor (generic version, optimized for high-dimensionality data sets). + * Corresponding distance traits: nanoflann::metric_L1 + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L1_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = std::abs(a[0] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff1 = std::abs(a[1] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff2 = std::abs(a[2] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff3 = std::abs(a[3] - data_source.kdtree_get_pt(b_idx,d++)); + result += diff0 + diff1 + diff2 + diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + result += std::abs( *a++ - data_source.kdtree_get_pt(b_idx, d++) ); + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return std::abs(a-b); + } + }; + + /** Squared Euclidean distance functor (generic version, optimized for high-dimensionality data sets). + * Corresponding distance traits: nanoflann::metric_L2 + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L2_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx,d++); + result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++); + result += diff0 * diff0; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return (a - b) * (a - b); + } + }; + + /** Squared Euclidean (L2) distance functor (suitable for low-dimensionality datasets, like 2D or 3D point clouds) + * Corresponding distance traits: nanoflann::metric_L2_Simple + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L2_Simple_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Simple_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + DistanceType result = DistanceType(); + for (size_t i = 0; i < size; ++i) { + const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); + result += diff * diff; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return (a - b) * (a - b); + } + }; + + /** SO2 distance functor + * Corresponding distance traits: nanoflann::metric_SO2 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) + * orientation is constrained to be in [-pi, pi] + */ + template + struct SO2_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + return accum_dist(a[size-1], data_source.kdtree_get_pt(b_idx, size - 1) , size - 1); + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + DistanceType result = DistanceType(); + result = b - a; + if (result > M_PI) + result -= 2. * M_PI; + else if (result < -M_PI) + result += 2. * M_PI; + return result; + } + }; + + /** SO3 distance functor (Uses L2_Simple) + * Corresponding distance traits: nanoflann::metric_SO3 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) + */ + template + struct SO3_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + L2_Simple_Adaptor distance_L2_Simple; + + SO3_Adaptor(const DataSource &_data_source) : distance_L2_Simple(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + return distance_L2_Simple.evalMetric(a, b_idx, size); + } + + template + inline DistanceType accum_dist(const U a, const V b, int idx) const + { + return distance_L2_Simple.accum_dist(a, b, idx); + } + }; + + /** Metaprogramming helper traits class for the L1 (Manhattan) metric */ + struct metric_L1 : public Metric + { + template + struct traits { + typedef L1_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the L2 (Euclidean) metric */ + struct metric_L2 : public Metric + { + template + struct traits { + typedef L2_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */ + struct metric_L2_Simple : public Metric + { + template + struct traits { + typedef L2_Simple_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ + struct metric_SO2 : public Metric + { + template + struct traits { + typedef SO2_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ + struct metric_SO3 : public Metric + { + template + struct traits { + typedef SO3_Adaptor distance_t; + }; + }; + + /** @} */ + + /** @addtogroup param_grp Parameter structs + * @{ */ + + /** Parameters (see README.md) */ + struct KDTreeSingleIndexAdaptorParams + { + KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10) : + leaf_max_size(_leaf_max_size) + {} + + size_t leaf_max_size; + }; + + /** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ + struct SearchParams + { + /** Note: The first argument (checks_IGNORED_) is ignored, but kept for compatibility with the FLANN interface */ + SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true ) : + checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {} + + int checks; //!< Ignored parameter (Kept for compatibility with the FLANN interface). + float eps; //!< search for eps-approximate neighbours (default: 0) + bool sorted; //!< only for radius search, require neighbours sorted by distance (default: true) + }; + /** @} */ + + + /** @addtogroup memalloc_grp Memory allocation + * @{ */ + + /** + * Allocates (using C's malloc) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + inline T* allocate(size_t count = 1) + { + T* mem = static_cast( ::malloc(sizeof(T)*count)); + return mem; + } + + + /** + * Pooled storage allocator + * + * The following routines allow for the efficient allocation of storage in + * small chunks from a specified pool. Rather than allowing each structure + * to be freed individually, an entire pool of storage is freed at once. + * This method has two advantages over just using malloc() and free(). First, + * it is far more efficient for allocating small objects, as there is + * no overhead for remembering all the information needed to free each + * object or consolidating fragmented memory. Second, the decision about + * how long to keep an object is made at the time of allocation, and there + * is no need to track down all the objects to free them. + * + */ + + const size_t WORDSIZE = 16; + const size_t BLOCKSIZE = 8192; + + class PooledAllocator + { + /* We maintain memory alignment to word boundaries by requiring that all + allocations be in multiples of the machine wordsize. */ + /* Size of machine word in bytes. Must be power of 2. */ + /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE. */ + + + size_t remaining; /* Number of bytes left in current block of storage. */ + void* base; /* Pointer to base of current block of storage. */ + void* loc; /* Current location in block to next allocate memory. */ + + void internal_init() + { + remaining = 0; + base = NULL; + usedMemory = 0; + wastedMemory = 0; + } + + public: + size_t usedMemory; + size_t wastedMemory; + + /** + Default constructor. Initializes a new pool. + */ + PooledAllocator() { + internal_init(); + } + + /** + * Destructor. Frees all the memory allocated in this pool. + */ + ~PooledAllocator() { + free_all(); + } + + /** Frees all allocated memory chunks */ + void free_all() + { + while (base != NULL) { + void *prev = *(static_cast( base)); /* Get pointer to prev block. */ + ::free(base); + base = prev; + } + internal_init(); + } + + /** + * Returns a pointer to a piece of new memory of the given size in bytes + * allocated from the pool. + */ + void* malloc(const size_t req_size) + { + /* Round size up to a multiple of wordsize. The following expression + only works for WORDSIZE that is a power of 2, by masking last bits of + incremented size to zero. + */ + const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); + + /* Check whether a new block must be allocated. Note that the first word + of a block is reserved for a pointer to the previous block. + */ + if (size > remaining) { + + wastedMemory += remaining; + + /* Allocate new storage. */ + const size_t blocksize = (size + sizeof(void*) + (WORDSIZE - 1) > BLOCKSIZE) ? + size + sizeof(void*) + (WORDSIZE - 1) : BLOCKSIZE; + + // use the standard C malloc to allocate memory + void* m = ::malloc(blocksize); + if (!m) { + fprintf(stderr, "Failed to allocate memory.\n"); + return NULL; + } + + /* Fill first word of new block with pointer to previous block. */ + static_cast(m)[0] = base; + base = m; + + size_t shift = 0; + //int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) & (WORDSIZE-1))) & (WORDSIZE-1); + + remaining = blocksize - sizeof(void*) - shift; + loc = (static_cast(m) + sizeof(void*) + shift); + } + void* rloc = loc; + loc = static_cast(loc) + size; + remaining -= size; + + usedMemory += size; + + return rloc; + } + + /** + * Allocates (using this pool) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + T* allocate(const size_t count = 1) + { + T* mem = static_cast(this->malloc(sizeof(T)*count)); + return mem; + } + + }; + /** @} */ + + /** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff + * @{ */ + + // ---------------- CArray ------------------------- + /** A STL container (as wrapper) for arrays of constant size defined at compile time (class imported from the MRPT project) + * This code is an adapted version from Boost, modifed for its integration + * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). + * See + * http://www.josuttis.com/cppcode + * for details and the latest version. + * See + * http://www.boost.org/libs/array for Documentation. + * for documentation. + * + * (C) Copyright Nicolai M. Josuttis 2001. + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * + * 29 Jan 2004 - minor fixes (Nico Josuttis) + * 04 Dec 2003 - update to synch with library TR1 (Alisdair Meredith) + * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries. + * 05 Aug 2001 - minor update (Nico Josuttis) + * 20 Jan 2001 - STLport fix (Beman Dawes) + * 29 Sep 2000 - Initial Revision (Nico Josuttis) + * + * Jan 30, 2004 + */ + template + class CArray { + public: + T elems[N]; // fixed-size array of elements of type T + + public: + // type definitions + typedef T value_type; + typedef T* iterator; + typedef const T* const_iterator; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // iterator support + inline iterator begin() { return elems; } + inline const_iterator begin() const { return elems; } + inline iterator end() { return elems+N; } + inline const_iterator end() const { return elems+N; } + + // reverse iterator support +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS) + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310) + // workaround for broken reverse_iterator in VC7 + typedef std::reverse_iterator > reverse_iterator; + typedef std::reverse_iterator > const_reverse_iterator; +#else + // workaround for broken reverse_iterator implementations + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#endif + + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + // operator[] + inline reference operator[](size_type i) { return elems[i]; } + inline const_reference operator[](size_type i) const { return elems[i]; } + // at() with range check + reference at(size_type i) { rangecheck(i); return elems[i]; } + const_reference at(size_type i) const { rangecheck(i); return elems[i]; } + // front() and back() + reference front() { return elems[0]; } + const_reference front() const { return elems[0]; } + reference back() { return elems[N-1]; } + const_reference back() const { return elems[N-1]; } + // size is constant + static inline size_type size() { return N; } + static bool empty() { return false; } + static size_type max_size() { return N; } + enum { static_size = N }; + /** This method has no effects in this class, but raises an exception if the expected size does not match */ + inline void resize(const size_t nElements) { if (nElements!=N) throw std::logic_error("Try to change the size of a CArray."); } + // swap (note: linear complexity in N, constant for given instantiation) + void swap (CArray& y) { std::swap_ranges(begin(),end(),y.begin()); } + // direct access to data (read-only) + const T* data() const { return elems; } + // use array as C array (direct read/write access to data) + T* data() { return elems; } + // assignment with type conversion + template CArray& operator= (const CArray& rhs) { + std::copy(rhs.begin(),rhs.end(), begin()); + return *this; + } + // assign one value to all elements + inline void assign (const T& value) { for (size_t i=0;i= size()) { throw std::out_of_range("CArray<>: index out of range"); } } + }; // end of CArray + + /** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors when DIM=-1. + * Fixed size version for a generic DIM: + */ + template + struct array_or_vector_selector + { + typedef CArray container_t; + }; + /** Dynamic size version */ + template + struct array_or_vector_selector<-1, T> { + typedef std::vector container_t; + }; + + /** @} */ + + /** kd-tree base-class + * + * Contains the member functions common to the classes KDTreeSingleIndexAdaptor and KDTreeSingleIndexDynamicAdaptor_. + * + * \tparam Derived The name of the class which inherits this class. + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use, these are all classes derived from nanoflann::Metric + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + + template + class KDTreeBaseClass + { + + public: + /** Frees the previously-built index. Automatically called within buildIndex(). */ + void freeIndex(Derived &obj) + { + obj.pool.free_all(); + obj.root_node = NULL; + obj.m_size_at_index_build = 0; + } + + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + + /*--------------------- Internal Data Structures --------------------------*/ + struct Node + { + /** Union used because a node can be either a LEAF node or a non-leaf node, so both data fields are never used simultaneously */ + union { + struct leaf + { + IndexType left, right; //!< Indices of points in leaf node + } lr; + struct nonleaf + { + int divfeat; //!< Dimension used for subdivision. + DistanceType divlow, divhigh; //!< The values used for subdivision. + } sub; + } node_type; + Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node) + }; + + typedef Node* NodePtr; + + struct Interval + { + ElementType low, high; + }; + + /** + * Array of indices to vectors in the dataset. + */ + std::vector vind; + + NodePtr root_node; + + size_t m_leaf_max_size; + + size_t m_size; //!< Number of current points in the dataset + size_t m_size_at_index_build; //!< Number of points in the dataset when the index was built + int dim; //!< Dimensionality of each data point + + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename array_or_vector_selector::container_t BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename array_or_vector_selector::container_t distance_vector_t; + + /** The KD-tree used to find neighbours */ + + BoundingBox root_bbox; + + /** + * Pooled memory allocator. + * + * Using a pooled memory allocator is more efficient + * than allocating memory directly when there is a large + * number small of memory allocations. + */ + PooledAllocator pool; + + /** Returns number of points in dataset */ + size_t size(const Derived &obj) const { return obj.m_size; } + + /** Returns the length of each point in the dataset */ + size_t veclen(const Derived &obj) { + return static_cast(DIM>0 ? DIM : obj.dim); + } + + /// Helper accessor to the dataset points: + inline ElementType dataset_get(const Derived &obj, size_t idx, int component) const{ + return obj.dataset.kdtree_get_pt(idx, component); + } + + /** + * Computes the inde memory usage + * Returns: memory used by the index + */ + size_t usedMemory(Derived &obj) + { + return obj.pool.usedMemory + obj.pool.wastedMemory + obj.dataset.kdtree_get_point_count() * sizeof(IndexType); // pool memory and vind array memory + } + + void computeMinMax(const Derived &obj, IndexType* ind, IndexType count, int element, ElementType& min_elem, ElementType& max_elem) + { + min_elem = dataset_get(obj, ind[0],element); + max_elem = dataset_get(obj, ind[0],element); + for (IndexType i = 1; i < count; ++i) { + ElementType val = dataset_get(obj, ind[i], element); + if (val < min_elem) min_elem = val; + if (val > max_elem) max_elem = val; + } + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] + * to vind[last]. The routine is called recursively on each sublist. + * + * @param left index of the first vector + * @param right index of the last vector + */ + NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right, BoundingBox& bbox) + { + NodePtr node = obj.pool.template allocate(); // allocate memory + + /* If too few exemplars remain, then make this a leaf node. */ + if ( (right - left) <= static_cast(obj.m_leaf_max_size) ) { + node->child1 = node->child2 = NULL; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = dataset_get(obj, obj.vind[left], i); + bbox[i].high = dataset_get(obj, obj.vind[left], i); + } + for (IndexType k = left + 1; k < right; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + if (bbox[i].low > dataset_get(obj, obj.vind[k], i)) bbox[i].low = dataset_get(obj, obj.vind[k], i); + if (bbox[i].high < dataset_get(obj, obj.vind[k], i)) bbox[i].high = dataset_get(obj, obj.vind[k], i); + } + } + } + else { + IndexType idx; + int cutfeat; + DistanceType cutval; + middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = divideTree(obj, left, left + idx, left_bbox); + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + node->child2 = divideTree(obj, left + idx, right, right_bbox); + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + void middleSplit_(Derived &obj, IndexType* ind, IndexType count, IndexType& index, int& cutfeat, DistanceType& cutval, const BoundingBox& bbox) + { + const DistanceType EPS = static_cast(0.00001); + ElementType max_span = bbox[0].high-bbox[0].low; + for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high - bbox[i].low; + if (span > max_span) { + max_span = span; + } + } + ElementType max_spread = -1; + cutfeat = 0; + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high-bbox[i].low; + if (span > (1 - EPS) * max_span) { + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, i, min_elem, max_elem); + ElementType spread = max_elem - min_elem;; + if (spread > max_spread) { + cutfeat = i; + max_spread = spread; + } + } + } + // split in the middle + DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem); + + if (split_val < min_elem) cutval = min_elem; + else if (split_val > max_elem) cutval = max_elem; + else cutval = split_val; + + IndexType lim1, lim2; + planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); + + if (lim1 > count / 2) index = lim1; + else if (lim2 < count / 2) index = lim2; + else index = count/2; + } + + /** + * Subdivide the list of points by a plane perpendicular on axe corresponding + * to the 'cutfeat' dimension at 'cutval' position. + * + * On return: + * dataset[ind[0..lim1-1]][cutfeat]cutval + */ + void planeSplit(Derived &obj, IndexType* ind, const IndexType count, int cutfeat, DistanceType &cutval, IndexType& lim1, IndexType& lim2) + { + /* Move vector indices for left subtree to front of list. */ + IndexType left = 0; + IndexType right = count-1; + for (;; ) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval) ++left; + while (right && left <= right && dataset_get(obj, ind[right], cutfeat) >= cutval) --right; + if (left > right || !right) break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + /* If either list is empty, it means that all remaining features + * are identical. Split in the middle to maintain a balanced tree. + */ + lim1 = left; + right = count-1; + for (;; ) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval) ++left; + while (right && left <= right && dataset_get(obj, ind[right], cutfeat) > cutval) --right; + if (left > right || !right) break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + lim2 = left; + } + + DistanceType computeInitialDistances(const Derived &obj, const ElementType* vec, distance_vector_t& dists) const + { + assert(vec); + DistanceType distsq = DistanceType(); + + for (int i = 0; i < (DIM>0 ? DIM : obj.dim); ++i) { + if (vec[i] < obj.root_bbox[i].low) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i); + distsq += dists[i]; + } + if (vec[i] > obj.root_bbox[i].high) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i); + distsq += dists[i]; + } + } + return distsq; + } + + void save_tree(Derived &obj, FILE* stream, NodePtr tree) + { + save_value(stream, *tree); + if (tree->child1 != NULL) { + save_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + save_tree(obj, stream, tree->child2); + } + } + + + void load_tree(Derived &obj, FILE* stream, NodePtr& tree) + { + tree = obj.pool.template allocate(); + load_value(stream, *tree); + if (tree->child1 != NULL) { + load_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + load_tree(obj, stream, tree->child2); + } + } + + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex_(Derived &obj, FILE* stream) + { + save_value(stream, obj.m_size); + save_value(stream, obj.dim); + save_value(stream, obj.root_bbox); + save_value(stream, obj.m_leaf_max_size); + save_value(stream, obj.vind); + save_tree(obj, stream, obj.root_node); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex_(Derived &obj, FILE* stream) + { + load_value(stream, obj.m_size); + load_value(stream, obj.dim); + load_value(stream, obj.root_bbox); + load_value(stream, obj.m_leaf_max_size); + load_value(stream, obj.vind); + load_tree(obj, stream, obj.root_node); + } + + }; + + + /** @addtogroup kdtrees_grp KD-tree classes and adaptors + * @{ */ + + /** kd-tree static index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + * template + * bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexAdaptor : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> + { + public: + /** Deleted copy constructor*/ + KDTreeSingleIndexAdaptor(const KDTreeSingleIndexAdaptor&) = delete; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + const KDTreeSingleIndexAdaptorParams index_params; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node* NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() ) : + dataset(inputData), index_params(params), distance(inputData) + { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + BaseClassRef::dim = dimensionality; + if (DIM>0) BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + + // Create a permutable array of indices to the input vectors. + init_vind(); + } + + /** + * Builds the index + */ + void buildIndex() + { + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + init_vind(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if(BaseClassRef::m_size == 0) return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + assert(vec); + if (this->size(*this) == 0) + return false; + if (!BaseClassRef::root_node) + throw std::runtime_error("[nanoflann] findNeighbors() called before building the index."); + float epsError = 1 + searchParams.eps; + + distance_vector_t dists; // fixed or variable-sized container (depending on DIM) + dists.assign((DIM > 0 ? DIM : BaseClassRef::dim), 0); // Fill it with zeros. + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside + * the result object. + * \sa radiusSearch, findNeighbors + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. + * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) + */ + size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. + * See the source of RadiusResultSet<> as a start point for your own classes. + * \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const + { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + /** Make sure the auxiliary list \a vind has the same size than the current dataset, and re-generate if size has changed. */ + void init_vind() + { + // Create a permutable array of indices to the input vectors. + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + if (BaseClassRef::vind.size() != BaseClassRef::m_size) BaseClassRef::vind.resize(BaseClassRef::m_size); + for (size_t i = 0; i < BaseClassRef::m_size; i++) BaseClassRef::vind[i] = i; + } + + void computeBoundingBox(BoundingBox& bbox) + { + bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); + if (dataset.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const size_t N = dataset.kdtree_get_point_count(); + if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = + bbox[i].high = this->dataset_get(*this, 0, i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, k, i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, k, i); + if (this->dataset_get(*this, k, i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, k, i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + * \return true if the search should be continued, false if the results are sufficient + */ + template + bool searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, + distance_vector_t& dists, const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; inode_type.lr.right; ++i) { + const IndexType index = BaseClassRef::vind[i];// reorder... : i; + DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (dist < worst_dist) { + if(!result_set.addPoint(dist, BaseClassRef::vind[i])) { + // the resultset doesn't want to receive any more points, we're done searching! + return false; + } + } + } + return true; + } + + /* Which child branch should be taken first? */ + int idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + if(!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) { + // the resultset doesn't want to receive any more points, we're done searching! + return false; + } + + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq*epsError <= result_set.worstDist()) { + if(!searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError)) { + // the resultset doesn't want to receive any more points, we're done searching! + return false; + } + } + dists[idx] = dst; + return true; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex(FILE* stream) + { + this->saveIndex_(*this, stream); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex(FILE* stream) + { + this->loadIndex_(*this, stream); + } + + }; // class KDTree + + + /** kd-tree dynamic index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + * template + * bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexDynamicAdaptor_ : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> + { + public: + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + KDTreeSingleIndexAdaptorParams index_params; + + std::vector &treeIndex; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node* NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor_(const int dimensionality, const DatasetAdaptor& inputData, std::vector& treeIndex_, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams()) : + dataset(inputData), index_params(params), treeIndex(treeIndex_), distance(inputData) + { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = 0; + BaseClassRef::m_size_at_index_build = 0; + BaseClassRef::dim = dimensionality; + if (DIM>0) BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + } + + + /** Assignment operator definiton */ + KDTreeSingleIndexDynamicAdaptor_ operator=( const KDTreeSingleIndexDynamicAdaptor_& rhs ) { + KDTreeSingleIndexDynamicAdaptor_ tmp( rhs ); + std::swap( BaseClassRef::vind, tmp.BaseClassRef::vind ); + std::swap( BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size ); + std::swap( index_params, tmp.index_params ); + std::swap( treeIndex, tmp.treeIndex ); + std::swap( BaseClassRef::m_size, tmp.BaseClassRef::m_size ); + std::swap( BaseClassRef::m_size_at_index_build, tmp.BaseClassRef::m_size_at_index_build ); + std::swap( BaseClassRef::root_node, tmp.BaseClassRef::root_node ); + std::swap( BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox ); + std::swap( BaseClassRef::pool, tmp.BaseClassRef::pool ); + return *this; + } + + /** + * Builds the index + */ + void buildIndex() + { + BaseClassRef::m_size = BaseClassRef::vind.size(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if(BaseClassRef::m_size == 0) return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + assert(vec); + if (this->size(*this) == 0) + return false; + if (!BaseClassRef::root_node) + return false; + float epsError = 1 + searchParams.eps; + + distance_vector_t dists; // fixed or variable-sized container (depending on DIM) + dists.assign((DIM > 0 ? DIM : BaseClassRef::dim) , 0); // Fill it with zeros. + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside + * the result object. + * \sa radiusSearch, findNeighbors + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. + * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) + */ + size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. + * See the source of RadiusResultSet<> as a start point for your own classes. + * \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const + { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + + + void computeBoundingBox(BoundingBox& bbox) + { + bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); + if (dataset.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const size_t N = BaseClassRef::m_size; + if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = + bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[0], i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i); + if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + */ + template + void searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, + distance_vector_t& dists, const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) { + const IndexType index = BaseClassRef::vind[i];// reorder... : i; + if(treeIndex[index] == -1) + continue; + DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (distnode_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError); + + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq*epsError <= result_set.worstDist()) { + searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError); + } + dists[idx] = dst; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex(FILE* stream) + { + this->saveIndex_(*this, stream); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex(FILE* stream) + { + this->loadIndex_(*this, stream); + } + + }; + + + /** kd-tree dynaimic index + * + * class to create multiple static index and merge their results to behave as single dynamic index as proposed in Logarithmic Approach. + * + * Example of usage: + * examples/dynamic_pointcloud_example.cpp + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexDynamicAdaptor + { + public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + protected: + + size_t m_leaf_max_size; + size_t treeCount; + size_t pointCount; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which point at idx is stored. treeIndex[idx]=-1 means that point has been removed. + + KDTreeSingleIndexAdaptorParams index_params; + + int dim; //!< Dimensionality of each data point + + typedef KDTreeSingleIndexDynamicAdaptor_ index_container_t; + std::vector index; + + public: + /** Get a const ref to the internal list of indices; the number of indices is adapted dynamically as + * the dataset grows in size. */ + const std::vector & getAllIndices() const { + return index; + } + + private: + /** finds position of least significant unset bit */ + int First0Bit(IndexType num) + { + int pos = 0; + while(num&1) + { + num = num>>1; + pos++; + } + return pos; + } + + /** Creates multiple empty trees to handle dynamic support */ + void init() + { + typedef KDTreeSingleIndexDynamicAdaptor_ my_kd_tree_t; + std::vector index_(treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params)); + index=index_; + } + + public: + + Distance distance; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() , const size_t maximumPointCount = 1000000000U) : + dataset(inputData), index_params(params), distance(inputData) + { + treeCount = std::log2(maximumPointCount); + pointCount = 0U; + dim = dimensionality; + treeIndex.clear(); + if (DIM > 0) dim = DIM; + m_leaf_max_size = params.leaf_max_size; + init(); + int num_initial_points = dataset.kdtree_get_point_count(); + if (num_initial_points > 0) { + addPoints(0, num_initial_points - 1); + } + } + + /** Deleted copy constructor*/ + KDTreeSingleIndexDynamicAdaptor(const KDTreeSingleIndexDynamicAdaptor&) = delete; + + + /** Add points to the set, Inserts all points from [start, end] */ + void addPoints(IndexType start, IndexType end) + { + int count = end - start + 1; + treeIndex.resize(treeIndex.size() + count); + for(IndexType idx = start; idx <= end; idx++) { + int pos = First0Bit(pointCount); + index[pos].vind.clear(); + treeIndex[pointCount]=pos; + for(int i = 0; i < pos; i++) { + for(int j = 0; j < static_cast(index[i].vind.size()); j++) { + index[pos].vind.push_back(index[i].vind[j]); + treeIndex[index[i].vind[j]] = pos; + } + index[i].vind.clear(); + index[i].freeIndex(index[i]); + } + index[pos].vind.push_back(idx); + index[pos].buildIndex(); + pointCount++; + } + } + + /** Remove a point from the set (Lazy Deletion) */ + void removePoint(size_t idx) + { + if(idx >= pointCount) + return; + treeIndex[idx] = -1; + } + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + for(size_t i = 0; i < treeCount; i++) + { + index[i].findNeighbors(result, &vec[0], searchParams); + } + return result.full(); + } + + }; + + /** An L2-metric KD-tree adaptor for working with data directly stored in an Eigen Matrix, without duplicating the data storage. + * Each row in the matrix represents a point in the state space. + * + * Example of usage: + * \code + * Eigen::Matrix mat; + * // Fill out "mat"... + * + * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; + * const int max_leaf = 10; + * my_kd_tree_t mat_index(mat, max_leaf ); + * mat_index.index->buildIndex(); + * mat_index.index->... + * \endcode + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + */ + template + struct KDTreeEigenMatrixAdaptor + { + typedef KDTreeEigenMatrixAdaptor self_t; + typedef typename MatrixType::Scalar num_t; + typedef typename MatrixType::Index IndexType; + typedef typename Distance::template traits::distance_t metric_t; + typedef KDTreeSingleIndexAdaptor< metric_t,self_t, MatrixType::ColsAtCompileTime,IndexType> index_t; + + index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. + + /// Constructor: takes a const ref to the matrix object with the data points + KDTreeEigenMatrixAdaptor(const MatrixType &mat, const int leaf_max_size = 10) : m_data_matrix(mat) + { + const IndexType dims = mat.cols(); + index = new index_t( dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); + index->buildIndex(); + } + public: + /** Deleted copy constructor */ + KDTreeEigenMatrixAdaptor(const self_t&) = delete; + + ~KDTreeEigenMatrixAdaptor() { + delete index; + } + + const MatrixType &m_data_matrix; + + /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). + * Note that this is a short-cut method for index->findNeighbors(). + * The user can also call index->... methods as desired. + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + */ + inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t & derived() const { + return *this; + } + self_t & derived() { + return *this; + } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return m_data_matrix.rows(); + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const IndexType idx, int dim) const { + return m_data_matrix.coeff(idx, IndexType(dim)); + } + + // Optional bounding-box computation: return false to default to a standard bbox computation loop. + // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + bool kdtree_get_bbox(BBOX& /*bb*/) const { + return false; + } + + /** @} */ + + }; // end of KDTreeEigenMatrixAdaptor + /** @} */ + +/** @} */ // end of grouping +} // end of NS + + +#endif /* NANOFLANN_HPP_ */ diff --git a/research/cv/WS3/third_party/src/setup.py b/research/cv/WS3/third_party/src/setup.py new file mode 100644 index 000000000..0bfc4ca34 --- /dev/null +++ b/research/cv/WS3/third_party/src/setup.py @@ -0,0 +1,19 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext +import numpy + +ext_modules = [Extension( + "nearest_neighbors", + sources=["knn.pyx", "knn_.cxx", ], # source file(s) + include_dirs=["./", numpy.get_include()], + language="c++", + extra_compile_args=["-std=c++11", "-fopenmp", ], + extra_link_args=["-std=c++11", '-fopenmp'], +)] + +setup( + name="KNN NanoFLANN", + ext_modules=ext_modules, + cmdclass={'build_ext': build_ext}, +) diff --git a/research/cv/WS3/third_party/src/test.py b/research/cv/WS3/third_party/src/test.py new file mode 100644 index 000000000..b6c67fc98 --- /dev/null +++ b/research/cv/WS3/third_party/src/test.py @@ -0,0 +1,15 @@ +import numpy as np +import lib.python.nearest_neighbors as nearest_neighbors +import time + +batch_size = 16 +num_points = 81920 +K = 16 +pc = np.random.rand(batch_size, num_points, 3).astype(np.float32) + +# nearest neighbours +start = time.time() +neigh_idx = nearest_neighbors.knn_batch(pc, pc, K, omp=True) +print(time.time() - start) + + diff --git a/research/cv/WS3/third_party/wrapper.cpp b/research/cv/WS3/third_party/wrapper.cpp new file mode 100644 index 000000000..f879059eb --- /dev/null +++ b/research/cv/WS3/third_party/wrapper.cpp @@ -0,0 +1,286 @@ +#include +#include +#include "grid_subsampling/grid_subsampling.h" +#include + + + +// docstrings for our module +// ************************* + +static char module_docstring[] = "This module provides an interface for the subsampling of a pointcloud"; + +static char compute_docstring[] = "function subsampling a pointcloud"; + + +// Declare the functions +// ********************* + +static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObject *keywds); + + +// Specify the members of the module +// ********************************* + +static PyMethodDef module_methods[] = +{ + { "compute", (PyCFunction)grid_subsampling_compute, METH_VARARGS | METH_KEYWORDS, compute_docstring }, + {NULL, NULL, 0, NULL} +}; + + +// Initialize the module +// ********************* + +static struct PyModuleDef moduledef = +{ + PyModuleDef_HEAD_INIT, + "grid_subsampling", // m_name + module_docstring, // m_doc + -1, // m_size + module_methods, // m_methods + NULL, // m_reload + NULL, // m_traverse + NULL, // m_clear + NULL, // m_free +}; + +PyMODINIT_FUNC PyInit_grid_subsampling(void) +{ + import_array(); + return PyModule_Create(&moduledef); +} + + +// Actual wrapper +// ************** + +static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObject *keywds) +{ + + // Manage inputs + // ************* + + // Args containers + PyObject *points_obj = NULL; + PyObject *features_obj = NULL; + PyObject *classes_obj = NULL; + + // Keywords containers + static char *kwlist[] = {"points", "features", "classes", "sampleDl", "method", "verbose", NULL }; + float sampleDl = 0.1; + const char *method_buffer = "barycenters"; + int verbose = 0; + + // Parse the input + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|$OOfsi", kwlist, &points_obj, &features_obj, &classes_obj, &sampleDl, &method_buffer, &verbose)) + { + PyErr_SetString(PyExc_RuntimeError, "Error parsing arguments"); + return NULL; + } + + // Get the method argument + string method(method_buffer); + + // Interpret method + if (method.compare("barycenters") && method.compare("voxelcenters")) + { + PyErr_SetString(PyExc_RuntimeError, "Error parsing method. Valid method names are \"barycenters\" and \"voxelcenters\" "); + return NULL; + } + + // Check if using features or classes + bool use_feature = true, use_classes = true; + if (features_obj == NULL) + use_feature = false; + if (classes_obj == NULL) + use_classes = false; + + // Interpret the input objects as numpy arrays. + PyObject *points_array = PyArray_FROM_OTF(points_obj, NPY_FLOAT, NPY_IN_ARRAY); + PyObject *features_array = NULL; + PyObject *classes_array = NULL; + if (use_feature) + features_array = PyArray_FROM_OTF(features_obj, NPY_FLOAT, NPY_IN_ARRAY); + if (use_classes) + classes_array = PyArray_FROM_OTF(classes_obj, NPY_INT, NPY_IN_ARRAY); + + // Verify data was load correctly. + if (points_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input points to numpy arrays of type float32"); + return NULL; + } + if (use_feature && features_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input features to numpy arrays of type float32"); + return NULL; + } + if (use_classes && classes_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input classes to numpy arrays of type int32"); + return NULL; + } + + // Check that the input array respect the dims + if ((int)PyArray_NDIM(points_array) != 2 || (int)PyArray_DIM(points_array, 1) != 3) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : points.shape is not (N, 3)"); + return NULL; + } + if (use_feature && ((int)PyArray_NDIM(features_array) != 2)) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); + return NULL; + } + + if (use_classes && (int)PyArray_NDIM(classes_array) > 2) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); + return NULL; + } + + // Number of points + int N = (int)PyArray_DIM(points_array, 0); + + // Dimension of the features + int fdim = 0; + if (use_feature) + fdim = (int)PyArray_DIM(features_array, 1); + + //Dimension of labels + int ldim = 1; + if (use_classes && (int)PyArray_NDIM(classes_array) == 2) + ldim = (int)PyArray_DIM(classes_array, 1); + + // Check that the input array respect the number of points + if (use_feature && (int)PyArray_DIM(features_array, 0) != N) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); + return NULL; + } + if (use_classes && (int)PyArray_DIM(classes_array, 0) != N) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); + return NULL; + } + + + // Call the C++ function + // ********************* + + // Create pyramid + if (verbose > 0) + cout << "Computing cloud pyramid with support points: " << endl; + + + // Convert PyArray to Cloud C++ class + vector original_points; + vector original_features; + vector original_classes; + original_points = vector((PointXYZ*)PyArray_DATA(points_array), (PointXYZ*)PyArray_DATA(points_array) + N); + if (use_feature) + original_features = vector((float*)PyArray_DATA(features_array), (float*)PyArray_DATA(features_array) + N*fdim); + if (use_classes) + original_classes = vector((int*)PyArray_DATA(classes_array), (int*)PyArray_DATA(classes_array) + N*ldim); + + // Subsample + vector subsampled_points; + vector subsampled_features; + vector subsampled_classes; + grid_subsampling(original_points, + subsampled_points, + original_features, + subsampled_features, + original_classes, + subsampled_classes, + sampleDl, + verbose); + + // Check result + if (subsampled_points.size() < 1) + { + PyErr_SetString(PyExc_RuntimeError, "Error"); + return NULL; + } + + // Manage outputs + // ************** + + // Dimension of input containers + npy_intp* point_dims = new npy_intp[2]; + point_dims[0] = subsampled_points.size(); + point_dims[1] = 3; + npy_intp* feature_dims = new npy_intp[2]; + feature_dims[0] = subsampled_points.size(); + feature_dims[1] = fdim; + npy_intp* classes_dims = new npy_intp[2]; + classes_dims[0] = subsampled_points.size(); + classes_dims[1] = ldim; + + // Create output array + PyObject *res_points_obj = PyArray_SimpleNew(2, point_dims, NPY_FLOAT); + PyObject *res_features_obj = NULL; + PyObject *res_classes_obj = NULL; + PyObject *ret = NULL; + + // Fill output array with values + size_t size_in_bytes = subsampled_points.size() * 3 * sizeof(float); + memcpy(PyArray_DATA(res_points_obj), subsampled_points.data(), size_in_bytes); + if (use_feature) + { + size_in_bytes = subsampled_points.size() * fdim * sizeof(float); + res_features_obj = PyArray_SimpleNew(2, feature_dims, NPY_FLOAT); + memcpy(PyArray_DATA(res_features_obj), subsampled_features.data(), size_in_bytes); + } + if (use_classes) + { + size_in_bytes = subsampled_points.size() * ldim * sizeof(int); + res_classes_obj = PyArray_SimpleNew(2, classes_dims, NPY_INT); + memcpy(PyArray_DATA(res_classes_obj), subsampled_classes.data(), size_in_bytes); + } + + + // Merge results + if (use_feature && use_classes) + ret = Py_BuildValue("NNN", res_points_obj, res_features_obj, res_classes_obj); + else if (use_feature) + ret = Py_BuildValue("NN", res_points_obj, res_features_obj); + else if (use_classes) + ret = Py_BuildValue("NN", res_points_obj, res_classes_obj); + else + ret = Py_BuildValue("N", res_points_obj); + + // Clean up + // ******** + + Py_DECREF(points_array); + Py_XDECREF(features_array); + Py_XDECREF(classes_array); + + return ret; +} \ No newline at end of file diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py new file mode 100644 index 000000000..76ca0867e --- /dev/null +++ b/research/cv/WS3/train_ascend.py @@ -0,0 +1,381 @@ +# -*-coding:utf-8-*- + +import sys + +import datetime, os, argparse, pickle, shutil +from pathlib import Path +import numpy as np + +import mindspore as ms +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops, set_seed +from mindspore.nn import Adam +from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback +from mindspore.train.loss_scale_manager import FixedLossScaleManager +from mindspore import dtype as mstype +from mindspore.profiler import Profiler + +from src.data.S3DIS_dataset import dataloader, ms_map +from src.model.base_model import get_param_groups +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg +from src.utils.logger import get_logger +from src.model.model_s3dis_remove_bias import RandLANet_S3DIS, RandLA_S3DIS_WithLoss +use_custom_train_one_step_cell = True + + +class CustomTrainOneStepCell(nn.Cell): + """自定义训练网络""" + + def __init__(self, network, optimizer, sens=1.0): + """入参有三个:训练网络,优化器和反向传播缩放比例""" + super(CustomTrainOneStepCell, self).__init__(auto_prefix=False) + self.network = network # 定义前向网络 + self.network.set_grad() # 构建反向网络 + self.optimizer = optimizer # 定义优化器 + self.weights = self.optimizer.parameters # 待更新参数 + self.grad = ops.GradOperation(get_by_list=True, sens_param=True) # 反向传播获取梯度 + + def construct(self, *inputs): + loss = self.network(*inputs) + + grads = self.grad(self.network, self.weights)(*inputs, loss) + + loss = ops.depend(loss, self.optimizer(grads)) + + return loss + + +class UpdateLossEpoch(Callback): + def __init__(self, num_training_ep0=30, logger=None): + super(UpdateLossEpoch, self).__init__() + self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + self.training_ep.update({i: 0 for i in range(0, num_training_ep0)}) + self.logger = logger + + # v1.8: on_train_epoch_begin + # v1.7: epoch_begin + def epoch_begin(self, run_context): + # update_loss_time = time.time() + + cb_params = run_context.original_args() + if use_custom_train_one_step_cell: + train_network_with_loss = cb_params.network.network + else: + train_network_with_loss = cb_params.network + + cur_epoch_num = cb_params.cur_epoch_num # 从1开始 + train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] + + self.logger.info( + f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " + f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " + f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") + + # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + + # f"[train_gpu.py] UpdateLossEpoch 耗时: {time.time() - update_loss_time}s") + + def on_train_epoch_begin(self, run_context): + self.epoch_begin(run_context) + + +class S3DISLossMonitor(Callback): + def __init__(self, per_print_times=1, logger=None): + super(S3DISLossMonitor, self).__init__() + self._per_print_times = per_print_times + self._last_print_time = 0 + self.logger = logger + + # v1.8: on_train_step_end + # v1.7: step_end + def step_end(self, run_context): + """ + Print training loss at the end of step. + + Args: + run_context (RunContext): Include some information of the model. + """ + + cb_params = run_context.original_args() + loss = cb_params.net_outputs + + if isinstance(loss, (tuple, list)): + if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray): + loss = loss[0] + + if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray): + loss = float(np.mean(loss.asnumpy())) + + cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1 + + if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)): + raise ValueError(f"epoch: {cb_params.cur_epoch_num} " + f"step: {cur_step_in_epoch}. " + f"Invalid loss {loss}, terminating training.") + + # In disaster recovery scenario, the cb_params.cur_step_num may be rollback to previous step + # and be less than self._last_print_time, so self._last_print_time need to be updated. + if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): + while cb_params.cur_step_num <= self._last_print_time: + self._last_print_time -= \ + max(self._per_print_times, cb_params.batch_num if cb_params.dataset_sink_mode else 1) + + if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: + self._last_print_time = cb_params.cur_step_num + # self.train_network_with_loss = cb_params.network + + msg = f"epoch: {cb_params.cur_epoch_num} " \ + f"step: {cur_step_in_epoch}, " \ + f"loss is {loss} " + self.logger.info(msg) + + def on_train_step_end(self, run_context): + self.step_end(run_context) + + +def prepare_network(weights, cfg, args): + """Prepare Network""" + # from model.model_s3dis import RandLANet_S3DIS, RandLA_S3DIS_WithLoss + + d_in = 6 # xyzrgb + network = RandLANet_S3DIS(d_in, cfg.num_classes) + if args.ss_pretrain: + print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") + param_dict = load_checkpoint(args.ss_pretrain) + whitelist = ["encoder"] + load_all = True + new_param_dict = dict() + for key, val in param_dict.items(): + if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: + new_key = ".".join(key.split(".")[1:]) + new_param_dict[new_key] = val + load_param_into_net(network, new_param_dict, strict_load=True) + + network = RandLA_S3DIS_WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, + cfg.loss3_type, cfg.topk) + + if args.retrain_model: + print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") + param_dict = load_checkpoint(args.retrain_model) + load_param_into_net(network, param_dict, strict_load=True) + + return network + + +def train(cfg, args): + if cfg.graph_mode: + context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id) + else: + context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) + + + # profiler = Profiler(output_path='./profiler_data') + ## + logger = get_logger(args.outputs_dir, args.rank) + + logger.info("============ Args =================") + for arg in vars(args): + logger.info('%s: %s' % (arg, getattr(args, arg))) + logger.info("============ Cfg =================") + for c in vars(cfg): + logger.info('%s: %s' % (c, getattr(cfg, c))) + + train_loader, val_loader, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) + ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in + getattr(dataset, 'ignored_labels')] + cfg.ignored_label_indexs = ignored_label_indexs + weights = DP.get_class_weights("S3DIS") + + network = prepare_network(weights, cfg, args) + decay_lr = nn.ExponentialDecayLR(cfg.learning_rate, cfg.lr_decays, decay_steps=cfg.train_steps, is_stair=True) + opt = Adam( + params=get_param_groups(network), + learning_rate=decay_lr, + loss_scale=cfg.loss_scale + ) + + log = {'cur_epoch': 1, 'cur_step': 1, 'best_epoch': 1, 'besr_miou': 0.0} + if not os.path.exists(args.outputs_dir + '/log.pkl'): + f = open(args.outputs_dir + '/log.pkl', 'wb') + pickle.dump(log, f) + f.close() + + # resume checkpoint, cur_epoch, best_epoch, cur_step, best_step + if args.resume: + f = open(args.resume + '/log.pkl', 'rb') + log = pickle.load(f) + print(f"log of resume file {log}") + f.close() + param_dict = load_checkpoint(args.resume) + load_param_into_net(network, param_dict) + # load_param_into_net(network, args.resume) + + # data loader + + train_loader = train_loader.batch(batch_size=cfg.batch_size, + per_batch_map=ms_map, + input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], + output_columns=["features", "features2", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4", + # 'test_dict' + ], + drop_remainder=True) + + logger.info('==========begin training===============') + + # loss scale manager + loss_scale = cfg.loss_scale + # loss_scale = args.scale_weight + loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None + print('loss_scale:', loss_scale) + + # float 16 + if cfg.float16: + print("network uses float16") + network.to_float(mstype.float16) + + if args.scale: + model = Model(network, + loss_scale_manager=loss_scale_manager, + loss_fn=None, + optimizer=opt) + else: + if use_custom_train_one_step_cell: + network = CustomTrainOneStepCell(network, opt) + model = Model(network) + else: + model = Model(network, + loss_fn=None, + optimizer=opt, + ) + + # callback for loss & time cost + loss_cb = S3DISLossMonitor(50, logger) + time_cb = TimeMonitor(data_size=cfg.train_steps) + cbs = [loss_cb, time_cb] + + # callback for saving ckpt + config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) + ckpt_cb = ModelCheckpoint(prefix='randla', + directory=os.path.join(args.outputs_dir, 'ckpt'), + config=config_ckpt) + cbs += [ckpt_cb] + + update_loss_epoch_cb = UpdateLossEpoch(args.num_training_ep0, logger) + cbs += [update_loss_epoch_cb] + + logger.info(f"Outputs_dir:{args.outputs_dir}") + logger.info(f"Total number of epoch: {cfg.max_epoch}; " + f"Dataset capacity: {train_loader.get_dataset_size()}") + + model.train(cfg.max_epoch, + train_loader, + callbacks=cbs, # [loss_cb,time_cb,ckpt_cb,update_loss_epoch_cb] + dataset_sink_mode=False) + logger.info('==========end training===============') + + +if __name__ == "__main__": + """Parse program arguments""" + parser = argparse.ArgumentParser( + prog='RandLA-Net', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + expr = parser.add_argument_group('Experiment parameters') + param = parser.add_argument_group('Hyperparameters') + dirs = parser.add_argument_group('Storage directories') + misc = parser.add_argument_group('Miscellaneous') + + expr.add_argument('--epochs', type=int, help='max epochs', default=100) + + expr.add_argument('--batch_size', type=int, help='batch size', default=6) + + expr.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + + expr.add_argument('--outputs_dir', type=str, help='path of output', default='outputs') + + expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + + expr.add_argument('--resume', type=str, help='model to resume', default=None) + + expr.add_argument('--scale', type=bool, help='scale or not', default=False) + + # expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) + + misc.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + + misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + + misc.add_argument('--rank', type=int, help='rank', default=0) + + misc.add_argument('--name', type=str, help='name of the experiment', + default=None) + misc.add_argument('--ss_pretrain', type=str, help='name of the experiment', + default=None) + misc.add_argument('--retrain_model', type=str, help='name of the experiment', + default=None) + misc.add_argument('--float16', type=bool, default=False) + + misc.add_argument('--train_steps', type=int, default=500) + misc.add_argument('--learning_rate', type=float, default=0.01) + misc.add_argument('--lr_decays', type=float, default=0.95) + misc.add_argument('--loss_scale', type=float, default=1.0) + misc.add_argument('--topk', type=int, default=500) + misc.add_argument('--num_training_ep0', type=int, default=30) + misc.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] + misc.add_argument('--random_seed', type=int, default=888) + # misc.add_argument('--graph_mode', action='store_true', default=False) + + args = parser.parse_args() + + cfg.dataset_dir = args.dataset_dir + cfg.batch_size = args.batch_size + cfg.max_epoch = args.epochs + cfg.train_steps = args.train_steps + cfg.learning_rate = args.learning_rate + cfg.lr_decays = args.lr_decays + cfg.loss_scale = args.loss_scale + cfg.topk = args.topk + num_training_ep0 = args.num_training_ep0 + cfg.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} + cfg.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + cfg.training_ep.update(cfg.training_ep0) + cfg.labeled_percent = args.labeled_percent + cfg.random_seed = args.random_seed + cfg.graph_mode = args.graph_mode + cfg.float16 = args.float16 + + if args.name is None: + if args.resume: + args.name = Path(args.resume).split('/')[-1] + else: + time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) + args.name = f'TSteps{cfg.train_steps}_MaxEpoch{cfg.max_epoch}_BatchS{cfg.batch_size}_lr{cfg.learning_rate}' \ + f'_lrd{cfg.lr_decays}_ls{cfg.loss_scale}_Topk{cfg.topk}_NumTrainEp0{num_training_ep0}_LP_{cfg.labeled_percent}_RS_{cfg.random_seed}' + if cfg.graph_mode: + args.name += "_GraphM" + else: + args.name += "_PyNateiveM" + args.name += f'_{time_str}' + + np.random.seed(cfg.random_seed) + set_seed(cfg.random_seed) + # https://www.mindspore.cn/docs/zh-CN/r1.7/api_python/mindspore/mindspore.set_seed.html?highlight=set_seed + + # ds.config.set_auto_num_workers() + # output_dir = f"./runs/pretrain_s3dis_v13" + args.outputs_dir = os.path.join(args.outputs_dir, args.name) + + print(f"outputs_dir:{args.outputs_dir}") + if not os.path.exists(args.outputs_dir): + os.makedirs(args.outputs_dir) + + if args.resume: + args.outputs_dir = args.resume + + + + + train(cfg, args) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py new file mode 100644 index 000000000..0e2dcef64 --- /dev/null +++ b/research/cv/WS3/train_gpu.py @@ -0,0 +1,323 @@ +# -*-coding:utf-8-*- +""" + Author: chenhaomingbob + E-mail: chenhaomingbob@163.com + Time: 2022/06/23 + Description: + +""" +import datetime, os, argparse, pickle, shutil +from pathlib import Path +import numpy as np + +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops, set_seed +from mindspore.nn import Adam +from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback +from mindspore.train.loss_scale_manager import FixedLossScaleManager + +from src.data.S3DIS_dataset import dataloader, ms_map +from src.model.base_model import get_param_groups +from src.model.model_s3dis import RandLANet_S3DIS, RandLA_S3DIS_WithLoss +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg +from src.utils.logger import get_logger + + +class UpdateLossEpoch(Callback): + def __init__(self, num_training_ep0=30, logger=None): + super(UpdateLossEpoch, self).__init__() + self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + self.training_ep.update({i: 0 for i in range(0, num_training_ep0)}) + self.logger = logger + + def on_train_epoch_begin(self, run_context): + cb_params = run_context.original_args() + train_network_with_loss = cb_params.network + cur_epoch_num = cb_params.cur_epoch_num # 从1开始 + train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] + self.logger.info( + f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " + f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " + f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") + + +class S3DISLossMonitor(Callback): + def __init__(self, per_print_times=1, logger=None): + super(S3DISLossMonitor, self).__init__() + self._per_print_times = per_print_times + self._last_print_time = 0 + self.logger = logger + + def on_train_step_end(self, run_context): + """ + Print training loss at the end of step. + + Args: + run_context (RunContext): Include some information of the model. + """ + cb_params = run_context.original_args() + loss = cb_params.net_outputs + + if isinstance(loss, (tuple, list)): + if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray): + loss = loss[0] + + if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray): + loss = float(np.mean(loss.asnumpy())) + + cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1 + + if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)): + raise ValueError("epoch: {} step: {}. Invalid loss {}, terminating training." + "CE Loss {}; SP Loss {}".format(cb_params.cur_epoch_num, cur_step_in_epoch, loss, + cb_params.network.CE_LOSS.asnumpy(), + cb_params.network.SP_LOSS.asnumpy())) + + # In disaster recovery scenario, the cb_params.cur_step_num may be rollback to previous step + # and be less than self._last_print_time, so self._last_print_time need to be updated. + if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): + while cb_params.cur_step_num <= self._last_print_time: + self._last_print_time -= \ + max(self._per_print_times, cb_params.batch_num if cb_params.dataset_sink_mode else 1) + + if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: + self._last_print_time = cb_params.cur_step_num + self.train_network_with_loss = cb_params.network + + if isinstance(self.train_network_with_loss, Tensor): + msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, " \ + f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS.asnumpy()}; SP Loss:{self.train_network_with_loss.SP_LOSS.asnumpy()})" + else: + msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, " \ + f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS}; SP Loss:{self.train_network_with_loss.SP_LOSS})" + # f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS.asnumpy()}; SP Loss:{self.train_network_with_loss.SP_LOSS.asnumpy()})" + # self.train_network_with_loss.CE_LOSS.dtype == Parameters + # self.train_network_with_loss.SP_LOSS.dtype == Parameters + self.logger.info(msg) + + +def prepare_network(weights, cfg, args): + """Prepare Network""" + + d_in = 6 # xyzrgb + network = RandLANet_S3DIS(d_in, cfg.num_classes) + if args.ss_pretrain: + print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") + param_dict = load_checkpoint(args.ss_pretrain) + whitelist = ["encoder"] + load_all = True + new_param_dict = dict() + for key, val in param_dict.items(): + if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: + new_key = ".".join(key.split(".")[1:]) + new_param_dict[new_key] = val + load_param_into_net(network, new_param_dict, strict_load=True) + + network = RandLA_S3DIS_WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, + cfg.loss3_type, cfg.topk) + + if args.retrain_model: + print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") + param_dict = load_checkpoint(args.retrain_model) + load_param_into_net(network, param_dict, strict_load=True) + + return network + + +def train(cfg, args): + if cfg.graph_mode: + context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id) + else: + context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) + + logger = get_logger(args.outputs_dir, args.rank) + + logger.info("============ Args =================") + for arg in vars(args): + logger.info('%s: %s' % (arg, getattr(args, arg))) + logger.info("============ Cfg =================") + for c in vars(cfg): + logger.info('%s: %s' % (c, getattr(cfg, c))) + + train_loader, val_loader, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) + ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in + getattr(dataset, 'ignored_labels')] + cfg.ignored_label_indexs = ignored_label_indexs + weights = DP.get_class_weights("S3DIS") + network = prepare_network(weights, cfg, args) + + decay_lr = nn.ExponentialDecayLR(cfg.learning_rate, cfg.lr_decays, decay_steps=cfg.train_steps, is_stair=True) + opt = Adam( + params=get_param_groups(network), + learning_rate=decay_lr, + loss_scale=cfg.loss_scale + ) + + log = {'cur_epoch': 1, 'cur_step': 1, 'best_epoch': 1, 'besr_miou': 0.0} + if not os.path.exists(args.outputs_dir + '/log.pkl'): + f = open(args.outputs_dir + '/log.pkl', 'wb') + pickle.dump(log, f) + f.close() + + # resume checkpoint, cur_epoch, best_epoch, cur_step, best_step + if args.resume: + f = open(args.resume + '/log.pkl', 'rb') + log = pickle.load(f) + f.close() + param = load_checkpoint(args.resume) + load_param_into_net(network, args.resume) + + # data loader + + train_loader = train_loader.batch(batch_size=cfg.batch_size, + per_batch_map=ms_map, + input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], + output_columns=["features", "features2", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4", + # 'test_dict' + ], + drop_remainder=True) + + logger.info('==========begin training===============') + + # loss scale manager + loss_scale = cfg.loss_scale + # loss_scale = args.scale_weight + loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None + print('loss_scale:', loss_scale) + + if args.scale: + model = Model(network, + loss_scale_manager=loss_scale_manager, + loss_fn=None, + optimizer=opt) + else: + model = Model(network, + loss_fn=None, + optimizer=opt) + + # callback for loss & time cost + loss_cb = S3DISLossMonitor(20, logger) + time_cb = TimeMonitor(data_size=cfg.train_steps) + cbs = [loss_cb, time_cb] + + # callback for saving ckpt + config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) + ckpt_cb = ModelCheckpoint(prefix='randla', directory=os.path.join(args.outputs_dir, 'ckpt'), + config=config_ckpt) + cbs += [ckpt_cb] + + update_loss_epoch_cb = UpdateLossEpoch(args.num_training_ep0, logger) + cbs += [update_loss_epoch_cb] + + logger.info(f"Outputs_dir:{args.outputs_dir}") + logger.info(f"Total number of epoch: {cfg.max_epoch}; " + f"Dataset capacity: {train_loader.get_dataset_size()}") + + model.train(cfg.max_epoch, + train_loader, + callbacks=cbs, + dataset_sink_mode=False) + + logger.info('==========end training===============') + + +if __name__ == "__main__": + """Parse program arguments""" + parser = argparse.ArgumentParser( + prog='RandLA-Net', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + expr = parser.add_argument_group('Experiment parameters') + param = parser.add_argument_group('Hyperparameters') + dirs = parser.add_argument_group('Storage directories') + misc = parser.add_argument_group('Miscellaneous') + + expr.add_argument('--epochs', type=int, help='max epochs', default=100) + + expr.add_argument('--batch_size', type=int, help='batch size', default=6) + + expr.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') + + expr.add_argument('--outputs_dir', type=str, help='path of output', default='./outputs') + + expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + + expr.add_argument('--resume', type=str, help='model to resume', default=None) + + expr.add_argument('--scale', type=bool, help='scale or not', default=False) + + # expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) + + misc.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') + + misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + + misc.add_argument('--rank', type=int, help='rank', default=0) + + misc.add_argument('--name', type=str, help='name of the experiment', + default=None) + misc.add_argument('--ss_pretrain', type=str, help='name of the experiment', + default=None) + misc.add_argument('--retrain_model', type=str, help='name of the experiment', + default=None) + misc.add_argument('--train_steps', type=int, default=500) + misc.add_argument('--learning_rate', type=float, default=0.01) + misc.add_argument('--lr_decays', type=float, default=0.95) + misc.add_argument('--loss_scale', type=float, default=1.0) + misc.add_argument('--topk', type=int, default=500) + misc.add_argument('--num_training_ep0', type=int, default=30) + misc.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] + misc.add_argument('--random_seed', type=int, default=888) + misc.add_argument('--graph_mode', action='store_true', default=False) + + args = parser.parse_args() + + cfg.dataset_dir = args.dataset_dir + cfg.batch_size = args.batch_size + cfg.max_epoch = args.epochs + cfg.train_steps = args.train_steps + cfg.learning_rate = args.learning_rate + cfg.lr_decays = args.lr_decays + cfg.loss_scale = args.loss_scale + cfg.topk = args.topk + num_training_ep0 = args.num_training_ep0 + cfg.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} + cfg.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + cfg.training_ep.update(cfg.training_ep0) + cfg.labeled_percent = args.labeled_percent + cfg.random_seed = args.random_seed + cfg.graph_mode = args.graph_mode + + if args.name is None: + if args.resume: + args.name = Path(args.resume).split('/')[-1] + else: + time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) + args.name = f'TSteps{cfg.train_steps}_MaxEpoch{cfg.max_epoch}_BatchS{cfg.batch_size}_lr{cfg.learning_rate}' \ + f'_lrd{cfg.lr_decays}_ls{cfg.loss_scale}_Topk{cfg.topk}_NumTrainEp0{num_training_ep0}_LP_{cfg.labeled_percent}_RS_{cfg.random_seed}' + if cfg.graph_mode: + args.name += "_GraphM" + else: + args.name += "_PyNateiveM" + args.name += f'_{time_str}' + + np.random.seed(cfg.random_seed) + set_seed(cfg.random_seed) + # https://www.mindspore.cn/docs/zh-CN/r1.7/api_python/mindspore/mindspore.set_seed.html?highlight=set_seed + + # ds.config.set_auto_num_workers() + # output_dir = f"./runs/pretrain_s3dis_v13" + args.outputs_dir = os.path.join(args.outputs_dir, args.name) + + print(f"outputs_dir:{args.outputs_dir}") + if not os.path.exists(args.outputs_dir): + os.makedirs(args.outputs_dir) + + if args.resume: + args.outputs_dir = args.resume + # start train + train(cfg, args) diff --git a/research/cv/__init__.py b/research/cv/__init__.py new file mode 100644 index 000000000..e69de29bb -- Gitee From 65b6b2ae64dcf9aefb321879d714d29defc6b6e7 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Thu, 24 Nov 2022 21:05:22 +0800 Subject: [PATCH 02/16] update --- research/cv/WS3/README.md | 2 +- research/cv/WS3/src/model/model_s3dis.py | 62 +------- .../WS3/src/model/model_s3dis_remove_bias.py | 52 ++----- .../cv/WS3/src/utils/data_prepare_s3dis.py | 19 ++- research/cv/WS3/src/utils/helper_ply.py | 51 ++----- research/cv/WS3/src/utils/logger.py | 19 ++- research/cv/WS3/src/utils/metrics.py | 4 +- research/cv/WS3/src/utils/tools.py | 136 +++++++++--------- research/cv/WS3/third_party/__init__.py | 0 .../WS3/third_party/cpp_wrappers/__init__.py | 0 .../cpp_wrappers/compile_wrappers.sh | 7 + .../cpp_wrappers/cpp_subsampling/__init__.py | 0 .../grid_subsampling}/grid_subsampling.cpp | 0 .../grid_subsampling/grid_subsampling.h | 92 ++++++++++++ .../cpp_subsampling}/setup.py | 13 +- .../cpp_subsampling}/wrapper.cpp | 0 .../cpp_utils/cloud}/cloud.cpp | 0 .../cpp_utils/cloud}/cloud.h | 0 .../cpp_utils/nanoflann}/nanoflann.hpp | 0 .../KDTreeTableAdaptor.h | 0 .../third_party/nearest_neighbors/__init__.py | 0 .../{src => nearest_neighbors}/knn.cpp | 0 .../{src => nearest_neighbors}/knn.pyx | 0 .../{src => nearest_neighbors}/knn_.cxx | 0 .../{src => nearest_neighbors}/knn_.h | 0 .../python/KNN_NanoFLANN-0.0.0-py3.6.egg-info | 10 ++ .../python/KNN_NanoFLANN-0.0.0-py3.7.egg-info | 10 ++ .../{src => nearest_neighbors}/nanoflann.hpp | 0 .../{src => nearest_neighbors}/setup.py | 0 .../cv/WS3/third_party/src/grid_subsampling.h | 92 ++++++++++++ research/cv/WS3/third_party/src/test.py | 15 -- 31 files changed, 335 insertions(+), 249 deletions(-) create mode 100644 research/cv/WS3/third_party/__init__.py create mode 100644 research/cv/WS3/third_party/cpp_wrappers/__init__.py create mode 100644 research/cv/WS3/third_party/cpp_wrappers/compile_wrappers.sh create mode 100644 research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/__init__.py rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_subsampling/grid_subsampling}/grid_subsampling.cpp (100%) create mode 100644 research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_subsampling}/setup.py (83%) rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_subsampling}/wrapper.cpp (100%) rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_utils/cloud}/cloud.cpp (100%) rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_utils/cloud}/cloud.h (100%) rename research/cv/WS3/third_party/{ => cpp_wrappers/cpp_utils/nanoflann}/nanoflann.hpp (100%) rename research/cv/WS3/third_party/{src => nearest_neighbors}/KDTreeTableAdaptor.h (100%) create mode 100644 research/cv/WS3/third_party/nearest_neighbors/__init__.py rename research/cv/WS3/third_party/{src => nearest_neighbors}/knn.cpp (100%) rename research/cv/WS3/third_party/{src => nearest_neighbors}/knn.pyx (100%) rename research/cv/WS3/third_party/{src => nearest_neighbors}/knn_.cxx (100%) rename research/cv/WS3/third_party/{src => nearest_neighbors}/knn_.h (100%) create mode 100644 research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.6.egg-info create mode 100644 research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.7.egg-info rename research/cv/WS3/third_party/{src => nearest_neighbors}/nanoflann.hpp (100%) rename research/cv/WS3/third_party/{src => nearest_neighbors}/setup.py (100%) create mode 100644 research/cv/WS3/third_party/src/grid_subsampling.h delete mode 100644 research/cv/WS3/third_party/src/test.py diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 6bded72b2..3d305ae60 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -54,7 +54,7 @@ dataset ### Install dependencies 1. `pip install -r requirements.txt` -2. `cd src/utils/nearest_neighbors` & `python setup.py develop` +2. `cd third_party` & `bash compile_op.sh` ## Quick Start diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index e1c7f4963..10473a2dc 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -3,14 +3,12 @@ import mindspore as ms import mindspore.nn as nn import mindspore.ops as P +from mindspore import dtype as mstype +from mindspore import Tensor from mindspore.ops import composite as C from mindspore.ops import operations as op -from .base_model import SharedMLP, LocalFeatureAggregation -from mindspore import Tensor, Parameter, ms_function -from mindspore import dtype as mstype -import time -from time import strftime, localtime +from .base_model import SharedMLP, LocalFeatureAggregation class RandLANet_S3DIS(nn.Cell): @@ -52,13 +50,6 @@ class RandLANet_S3DIS(nn.Cell): self.fc_end_fc2 = SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)) self.fc_end_drop = nn.Dropout() self.fc_end_fc3 = SharedMLP(32, num_classes) - # # final semantic prediction - # self.fc_end = nn.SequentialCell([ - # SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)), - # SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)), - # nn.Dropout(), - # SharedMLP(32, num_classes) - # ]) def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx): r""" @@ -77,32 +68,19 @@ class RandLANet_S3DIS(nn.Cell): ms.Tensor, shape (B, num_classes, N) segmentation scores for each point """ - # print(feature.shape) - a_time = time.time() feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) feature = self.bn_start(feature) # shape (B, 8, N, 1) - b_time = time.time() - # print(f"[model_s3dis.py] fc_start & bn_start. Time :{b_time - a_time}") # <<<<<<<<<< ENCODER f_stack = [] for i in range(5): - # at iteration i, feature.shape = (B, d_layer, N_layer, 1) - encoder_begin_time = time.time() f_encoder_i = self.encoder[i](xyz[i], feature, neighbor_idx[i]) # (B,40960,3) (4, 8, 40960, 1) (4, 40960, 16) - encoder_end_time = time.time() - # print(f"[model_s3dis.py] encoder {i}. Time :{encoder_end_time - encoder_begin_time}s") f_sampled_i = self.random_sample(f_encoder_i, sub_idx[i]) - random_sample_time = time.time() - # print(f"[model_s3dis.py] random_sample {i}. Time :{random_sample_time - encoder_end_time}s") feature = f_sampled_i if i == 0: f_stack.append(f_encoder_i) f_stack.append(f_sampled_i) - # print(f"[model_s3dis.py] append {i}. Time :{time.time() - random_sample_time}s") - c_time = time.time() - # print(f"[model_s3dis.py] encoder & random_sample. Time :{c_time - b_time}s") # # >>>>>>>>>> ENCODER feature = self.mlp(f_stack[-1]) # [B, d, N, 1] @@ -116,15 +94,12 @@ class RandLANet_S3DIS(nn.Cell): f_decoder_i = self.decoder[j](cat((f_stack[-j - 2], f_interp_i))) feature = f_decoder_i f_decoder_list.append(f_decoder_i) - d_time = time.time() - # print(f"[model_s3dis.py] random_sample & decoder. Time :{d_time - c_time}s") # >>>>>>>>>> DECODER f_layer_fc1 = self.fc_end_fc1(f_decoder_list[-1]) f_layer_fc2 = self.fc_end_fc2(f_layer_fc1) f_layer_drop = self.fc_end_drop(f_layer_fc2) f_layer_fc3 = self.fc_end_fc3(f_layer_drop) - e_time = time.time() # print(f"[model_s3dis.py] fc_end. Time :{e_time - d_time}s") f_layer_fc2, f_layer_fc3 = f_layer_fc2.swapaxes(1, 3), f_layer_fc3.swapaxes(1, 3) @@ -171,6 +146,9 @@ class RandLA_S3DIS_WithLoss(nn.Cell): self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) + self.CE_LOSS = 0 + self.SP_LOSS = 0 + def construct(self, feature, feature2, labels, input_inds, cloud_inds, p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): @@ -327,31 +305,3 @@ class RandLA_S3DIS_WithLoss(nn.Cell): adj_matrix = point1_square + point_inner + point2_square_transpose return adj_matrix - - -class TrainingWrapper(nn.Cell): - """Training wrapper.""" - - def __init__(self, network, optimizer, sens=1.0): - super(TrainingWrapper, self).__init__(auto_prefix=False) - self.network = network - self.network_logits = self.network.network - self.network.set_grad() - self.opt_weights = optimizer.parameters - self.optimizer = optimizer - self.grad = C.GradOperation(get_by_list=True, sens_param=True) - self.sens = sens - - def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx, labels): - """Build a forward graph""" - - # loss calculate - loss = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels) - logit = self.network_logits(xyz, feature, neighbor_idx, sub_idx, interp_idx) - - # opt update - opt_weights = self.opt_weights - sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) - grads = self.grad(self.network, opt_weights)(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels, sens) - res = P.depend(loss, self.optimizer(grads)) - return res, logit diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index cd16a1e88..6c9b8ebdc 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -1,16 +1,13 @@ # -*-coding:utf-8-*- -import mindspore as ms import mindspore.nn as nn import mindspore.ops as P from mindspore.ops import composite as C from mindspore.ops import operations as op -from mindspore import Tensor, Parameter, ms_function +from mindspore import Tensor from mindspore import dtype as mstype from .base_model_remove_bias import SharedMLP, LocalFeatureAggregation -import time -from time import strftime, localtime class RandLANet_S3DIS(nn.Cell): @@ -77,32 +74,19 @@ class RandLANet_S3DIS(nn.Cell): ms.Tensor, shape (B, num_classes, N) segmentation scores for each point """ - # print(feature.shape) - # a_time = time.time() feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) feature = self.bn_start(feature) # shape (B, 8, N, 1) - # b_time = time.time() - # print(f"[model_s3dis.py] fc_start & bn_start. Time :{b_time - a_time}") # <<<<<<<<<< ENCODER f_stack = [] for i in range(5): - # at iteration i, feature.shape = (B, d_layer, N_layer, 1) - encoder_begin_time = time.time() f_encoder_i = self.encoder[i](xyz[i], feature, neighbor_idx[i]) # (B,40960,3) (4, 8, 40960, 1) (4, 40960, 16) - encoder_end_time = time.time() - # print(f"[model_s3dis.py] encoder {i}. Time :{encoder_end_time - encoder_begin_time}s") f_sampled_i = self.random_sample(f_encoder_i, sub_idx[i]) - random_sample_time = time.time() - # print(f"[model_s3dis.py] random_sample {i}. Time :{random_sample_time - encoder_end_time}s") feature = f_sampled_i if i == 0: f_stack.append(f_encoder_i) f_stack.append(f_sampled_i) - # print(f"[model_s3dis.py] append {i}. Time :{time.time() - random_sample_time}s") - # c_time = time.time() - # print(f"[model_s3dis.py] encoder & random_sample. Time :{c_time - b_time}s") # # >>>>>>>>>> ENCODER feature = self.mlp(f_stack[-1]) # [B, d, N, 1] @@ -116,17 +100,12 @@ class RandLANet_S3DIS(nn.Cell): f_decoder_i = self.decoder[j](cat((f_stack[-j - 2], f_interp_i))) feature = f_decoder_i f_decoder_list.append(f_decoder_i) - # d_time = time.time() - # print(f"[model_s3dis.py] random_sample & decoder. Time :{d_time - c_time}s") # >>>>>>>>>> DECODER f_layer_fc1 = self.fc_end_fc1(f_decoder_list[-1]) f_layer_fc2 = self.fc_end_fc2(f_layer_fc1) f_layer_drop = self.fc_end_drop(f_layer_fc2) f_layer_fc3 = self.fc_end_fc3(f_layer_drop) - # e_time = time.time() - # print(f"[model_s3dis.py] fc_end. Time :{e_time - d_time}s") - f_layer_fc2, f_layer_fc3 = f_layer_fc2.swapaxes(1, 3), f_layer_fc3.swapaxes(1, 3) f_layer_out = P.Concat(axis=-1)([f_layer_fc3, f_layer_fc2]) f_out = f_layer_out.squeeze(1) # (B,N_points,13+32) @@ -160,20 +139,17 @@ class RandLA_S3DIS_WithLoss(nn.Cell): super(RandLA_S3DIS_WithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float16) - # self.weights = Tensor(weights, dtype=mstype.float32) self.num_classes = num_classes self.ignored_label_inds = ignored_label_indexs - self.c_epoch = c_epoch - self.loss3_type = loss3_type self.topk = topk - self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float16) - # self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float32) + self.c_epoch_k = Tensor(c_epoch, dtype=mstype.float16) # self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float16) - # self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) + + def construct(self, feature, feature2, labels, input_inds, cloud_inds, p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): @@ -231,20 +207,14 @@ class RandLA_S3DIS_WithLoss(nn.Cell): if self.c_epoch_k == 0: loss = CE_loss else: - # loss = CE_loss SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, self.topk) * self.c_epoch_k loss = CE_loss + SP_loss - # loss_end_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] loss 耗时: {loss_end_time - network_end_time}s") - # - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] RandLA_S3DIS_WithLoss 耗时: {loss_end_time - data_begin_time}s") return loss - def get_sp_loss_by_mask(self, embed, logits, one_hot_label, valid_mask, topk): + @staticmethod + def get_sp_loss_by_mask(embed, logits, one_hot_label, valid_mask, topk): """ Args: @@ -261,7 +231,7 @@ class RandLA_S3DIS_WithLoss(nn.Cell): num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) topk += num_invalid_points # 点类别的数量 - num_class = one_hot_label.shape[1] # scalar: 13 + # num_class = one_hot_label.shape[1] # scalar: 13 valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) @@ -275,8 +245,8 @@ class RandLA_S3DIS_WithLoss(nn.Cell): mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) # => 求unlabelled points 与 class embedding的相似度 # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] - adj_matrix = self.double_feature(invalid_embed, mean_embed) - # adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + # adj_matrix = self.double_feature(invalid_embed, mean_embed) + adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) neg_adj = -adj_matrix # (B*N,13) 取负 @@ -315,8 +285,8 @@ class RandLA_S3DIS_WithLoss(nn.Cell): return loss # @ms_function() - # @staticmethod - def double_feature(self, point_feature1, point_feature2): + @staticmethod + def double_feature(point_feature1, point_feature2): """ Compute pairwise distance of a point cloud. Args: diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py index 9e4cec42d..6f5740574 100644 --- a/research/cv/WS3/src/utils/data_prepare_s3dis.py +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -1,14 +1,14 @@ -from sklearn.neighbors import KDTree +import os, sys, glob, pickle from os.path import join, exists, dirname, abspath +from sklearn.neighbors import KDTree import numpy as np import pandas as pd -import os, sys, glob, pickle +from tools import DataProcessing as DP BASE_DIR = dirname(abspath(__file__)) ROOT_DIR = dirname(BASE_DIR) sys.path.append(BASE_DIR) sys.path.append(ROOT_DIR) -from tools import DataProcessing as DP dataset_path = 'xxxx/xxxxx/dataset/S3DIS/Stanford3dDataset_v1.2_Aligned_Version' anno_paths = [line.rstrip() for line in open(join(BASE_DIR, '../../third_party/meta/anno_paths.txt'))] @@ -20,8 +20,13 @@ gt_class2label = {cls: i for i, cls in enumerate(gt_class)} sub_grid_size = 0.04 original_pc_folder = join(dirname(dataset_path), 'original_npy') sub_pc_folder = join(dirname(dataset_path), 'train_{:.3f}'.format(sub_grid_size)) -os.mkdir(original_pc_folder) if not exists(original_pc_folder) else None -os.mkdir(sub_pc_folder) if not exists(sub_pc_folder) else None +if not exists(original_pc_folder): + os.mkdir(original_pc_folder) + +if not exists(sub_pc_folder): + os.mkdir(sub_pc_folder) +# os.mkdir(original_pc_folder) if not exists(original_pc_folder) else None +# os.mkdir(sub_pc_folder) if not exists(sub_pc_folder) else None out_format = '.npy' @@ -49,8 +54,8 @@ def convert_pc2npy(anno_path, save_path): xyz = pc_label[:, :3].astype(np.float32) colors = pc_label[:, 3:6].astype(np.uint8) - labels = np.expand_dims(pc_label[:, 6].astype(np.uint8),axis=1) - #print('xyz.shape:',xyz.shape,' colors.shape:',colors.shape,' labels.shape:',labels.shape) + labels = np.expand_dims(pc_label[:, 6].astype(np.uint8), axis=1) + # print('xyz.shape:',xyz.shape,' colors.shape:',colors.shape,' labels.shape:',labels.shape) np.save(save_path, np.concatenate((xyz, colors, labels), axis=1).T) # save sub_cloud and KDTree file diff --git a/research/cv/WS3/src/utils/helper_ply.py b/research/cv/WS3/src/utils/helper_ply.py index 4a2aeabd9..d1b159539 100644 --- a/research/cv/WS3/src/utils/helper_ply.py +++ b/research/cv/WS3/src/utils/helper_ply.py @@ -1,31 +1,5 @@ -# -# -# 0===============================0 -# | PLY files reader/writer | -# 0===============================0 -# -# -# ---------------------------------------------------------------------------------------------------------------------- -# -# function to read/write .ply files -# -# ---------------------------------------------------------------------------------------------------------------------- -# -# Hugues THOMAS - 10/02/2017 -# - - -# ---------------------------------------------------------------------------------------------------------------------- -# -# Imports and global variables -# \**********************************/ -# - - -# Basic libs -import numpy as np import sys - +import numpy as np # Define PLY types ply_dtypes = dict([ @@ -87,7 +61,6 @@ def parse_mesh_header(plyfile, ext): num_faces = None current_element = None - while b'end_header' not in line and line != b'': line = plyfile.readline() @@ -152,7 +125,6 @@ def read_ply(filename, triangular_mesh=False): with open(filename, 'rb') as plyfile: - # Check if the file start with ply if b'ply' not in plyfile.readline(): raise ValueError('The file does not start whith the word ply') @@ -197,7 +169,6 @@ def read_ply(filename, triangular_mesh=False): def header_properties(field_list, field_names): - # List of lines to write lines = [] @@ -248,24 +219,25 @@ def write_ply(filename, field_list, field_names, triangular_faces=None): """ # Format list input to the right form - field_list = list(field_list) if (type(field_list) == list or type(field_list) == tuple) else list((field_list,)) + field_list = list(field_list) if (isinstance(field_list, list) or isinstance(field_list, tuple)) else list( + (field_list,)) for i, field in enumerate(field_list): if field.ndim < 2: field_list[i] = field.reshape(-1, 1) if field.ndim > 2: - print('fields have more than 2 dimensions') - return False + # print('fields have more than 2 dimensions') + return False - # check all fields have the same number of data + # check all fields have the same number of data n_points = [field.shape[0] for field in field_list] if not np.all(np.equal(n_points, n_points[0])): - print('wrong field dimensions') - return False + # print('wrong field dimensions') + return False - # Check if field_names and field_list have same nb of column + # Check if field_names and field_list have same nb of column n_fields = np.sum([field.shape[1] for field in field_list]) - if (n_fields != len(field_names)): - print('wrong number of field names') + if n_fields != len(field_names): + # print('wrong number of field names') return False # Add extension if not there @@ -353,4 +325,3 @@ def describe_element(name, df): element.append('property ' + f + ' ' + df.columns.values[i]) return element - diff --git a/research/cv/WS3/src/utils/logger.py b/research/cv/WS3/src/utils/logger.py index 74ee7621b..300a17cd9 100644 --- a/research/cv/WS3/src/utils/logger.py +++ b/research/cv/WS3/src/utils/logger.py @@ -27,9 +27,11 @@ class LOGGER(logging.Logger): logger_name: String. Logger name. rank: Integer. Rank id. """ + def __init__(self, logger_name, rank=0): super(LOGGER, self).__init__(logger_name) self.rank = rank + self.log_fn = None if rank % 8 == 0: console = logging.StreamHandler(sys.stdout) console.setLevel(logging.INFO) @@ -68,11 +70,11 @@ class LOGGER(logging.Logger): if self.isEnabledFor(logging.INFO) and self.rank == 0: line_width = 2 important_msg = '\n' - important_msg += ('*'*70 + '\n')*line_width - important_msg += ('*'*line_width + '\n')*2 - important_msg += '*'*line_width + ' '*8 + msg + '\n' - important_msg += ('*'*line_width + '\n')*2 - important_msg += ('*'*70 + '\n')*line_width + important_msg += ('*' * 70 + '\n') * line_width + important_msg += ('*' * line_width + '\n') * 2 + important_msg += '*' * line_width + ' ' * 8 + msg + '\n' + important_msg += ('*' * line_width + '\n') * 2 + important_msg += ('*' * 70 + '\n') * line_width self.info(important_msg, *args, **kwargs) @@ -80,9 +82,4 @@ def get_logger(path, rank): """Get Logger.""" logger = LOGGER('RandLa-net', rank) logger.setup_logging_file(path, rank) - return logger - - -if __name__ == '__main__': - logger = get_logger('../runs', 0) - logger.info('this is logger info') \ No newline at end of file + return logger \ No newline at end of file diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py index 8a9ca6279..ca84a9129 100644 --- a/research/cv/WS3/src/utils/metrics.py +++ b/research/cv/WS3/src/utils/metrics.py @@ -3,7 +3,7 @@ import mindspore as ms def accuracy(scores, labels): r""" - Compute the per-class accuracies and the overall accuracy # TODO: complete doc + Compute the per-class accuracies and the overall accuracy Parameters ---------- @@ -37,7 +37,7 @@ def accuracy(scores, labels): def intersection_over_union(scores, labels): r""" - Compute the per-class IoU and the mean IoU # TODO: complete doc + Compute the per-class IoU and the mean IoU Parameters ---------- diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index 0888a4c39..e48ffbc44 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -1,84 +1,86 @@ import sys -import numpy as np import os.path +import numpy as np +# import third_party.nearest_neighbors.lib.python.nearest_neighbors as nearest_neighbors +import third_party.nearest_neighbors as nearest_neighbors +import third_party.cpp_wrappers.cpp_subsampling.grid_subsampling as cpp_subsampling BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - sys.path.append(BASE_DIR) sys.path.append(os.path.join(BASE_DIR, 'utils')) -import third_party.cpp_wrappers.cpp_subsampling.grid_subsampling as cpp_subsampling -import third_party.nearest_neighbors.lib.python.nearest_neighbors as nearest_neighbors - - - -class ConfigPretrain: - k_n = 16 # KNN - num_layers = 5 # Number of layers - num_points = 40960 # Number of input points - num_classes = 6 # a, b, \mu_{a},\mu_{a}, \sigmoid_{a}, \sigmoid_{b} - sub_grid_size = 0.04 # preprocess_parameter - - batch_size = 6 # batch_size during training - train_steps = 1000 # Number of steps per epochs - - sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer - d_out = [16, 64, 128, 256, 512] # feature dimension - - noise_init = 3.5 # 2.0 noise initial parameter - max_epoch = 100 # maximum epoch during training - learning_rate = 1e-2 # initial learning rate - - train_sum_dir = 'train_log' - saving = True - saving_path = None - - lr_decays = 0.95 # decay rate of learning rate - loss_scale = 1.0 # loss scale +# class ConfigPretrain: +# +# def __init__(self): +# pass +# +# k_n = 16 # KNN +# num_layers = 5 # Number of layers +# num_points = 40960 # Number of input points +# num_classes = 6 # a, b, \mu_{a},\mu_{a}, \sigmoid_{a}, \sigmoid_{b} +# sub_grid_size = 0.04 # preprocess_parameter +# +# batch_size = 6 # batch_size during training +# train_steps = 1000 # Number of steps per epochs +# +# sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer +# d_out = [16, 64, 128, 256, 512] # feature dimension +# +# noise_init = 3.5 # 2.0 noise initial parameter +# max_epoch = 100 # maximum epoch during training +# learning_rate = 1e-2 # initial learning rate +# +# train_sum_dir = 'train_log' +# saving = True +# saving_path = None +# +# lr_decays = 0.95 # decay rate of learning rate +# loss_scale = 1.0 # loss scale class ConfigS3DIS: - k_n = 16 # KNN - num_layers = 5 # Number of layers - num_points = 40960 # Number of input points - num_classes = 13 # Number of valid classes - sub_grid_size = 0.04 # preprocess_parameter - - batch_size = 6 # batch_size during training - val_batch_size = 16 # batch_size during validation and test - train_steps = 500 # Number of steps per epochs - val_steps = 100 # Number of validation steps per epoch - - sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer - d_out = [16, 64, 128, 256, 512] # feature dimension - - noise_init = 3.5 # noise initial parameter - max_epoch = 80 # maximum epoch during training - learning_rate = 1e-2 # initial learning rate - # lr_decays = {i: 0.95 for i in range(0, 500)} # decay rate of learning rate - lr_decays = 0.95 # decay rate of learning rate - loss_scale = 1.0 # loss scale - - training_ep0 = {i: 0 for i in range(0, 30)} # - training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - training_ep.update(training_ep0) - # training_ep = {i: 0 for i in range(max_epoch)} - # training_ep[2] = 1 - c_epoch = 0 - train_sum_dir = 'train_log' - saving = True - saving_path = None - # pretrain = False - # checkpoint = '../pretrain/snapshots/snap-11001' - - pretrain = True - checkpoint = './pretrain/snapshots/snap-11001' - topk = 500 - loss3_type = -1 + def __init__(self): + self.k_n = 16 # KNN + self.num_layers = 5 # Number of layers + self.num_points = 40960 # Number of input points + self.num_classes = 13 # Number of valid classes + self.sub_grid_size = 0.04 # preprocess_parameter + + self.batch_size = 6 # batch_size during training + self.val_batch_size = 16 # batch_size during validation and test + self.train_steps = 500 # Number of steps per epochs + self.val_steps = 100 # Number of validation steps per epoch + + self.sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer + self.d_out = [16, 64, 128, 256, 512] # feature dimension + + self.noise_init = 3.5 # noise initial parameter + self.max_epoch = 80 # maximum epoch during training + self.learning_rate = 1e-2 # initial learning rate + # lr_decays = {i: 0.95 for i in range(0, 500)} # decay rate of learning rate + self.lr_decays = 0.95 # decay rate of learning rate + self.loss_scale = 1.0 # loss scale + + training_ep0 = {i: 0 for i in range(0, 30)} # + self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + self.training_ep.update(training_ep0) + + self.c_epoch = 0 + self.train_sum_dir = 'train_log' + self.saving = True + self.saving_path = None + + self.pretrain = True + self.checkpoint = './pretrain/snapshots/snap-11001' + self.topk = 500 + self.loss3_type = -1 class DataProcessing: + def __init__(self): + pass + @staticmethod def knn_search(support_pts, query_pts, k): """ diff --git a/research/cv/WS3/third_party/__init__.py b/research/cv/WS3/third_party/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/third_party/cpp_wrappers/__init__.py b/research/cv/WS3/third_party/cpp_wrappers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/third_party/cpp_wrappers/compile_wrappers.sh b/research/cv/WS3/third_party/cpp_wrappers/compile_wrappers.sh new file mode 100644 index 000000000..630fdde65 --- /dev/null +++ b/research/cv/WS3/third_party/cpp_wrappers/compile_wrappers.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Compile cpp subsampling +cd cpp_subsampling +python3 setup.py build_ext --inplace +cd .. + diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/__init__.py b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/third_party/grid_subsampling.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp similarity index 100% rename from research/cv/WS3/third_party/grid_subsampling.cpp rename to research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h new file mode 100644 index 000000000..b1c84d1b3 --- /dev/null +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h @@ -0,0 +1,92 @@ + + +#include "../../cpp_utils/cloud/cloud.h" + +#include +#include + +using namespace std; + +class SampledData +{ +public: + + // Elements + // ******** + + int count; + PointXYZ point; + vector features; + vector> labels; + + + // Methods + // ******* + + // Constructor + SampledData() + { + count = 0; + point = PointXYZ(); + } + + SampledData(const size_t fdim, const size_t ldim) + { + count = 0; + point = PointXYZ(); + features = vector(fdim); + labels = vector>(ldim); + } + + // Method Update + void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_features(const PointXYZ p, vector::iterator f_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + return; + } + void update_classes(const PointXYZ p, vector::iterator l_begin) + { + count += 1; + point += p; + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_points(const PointXYZ p) + { + count += 1; + point += p; + return; + } +}; + + + +void grid_subsampling(vector& original_points, + vector& subsampled_points, + vector& original_features, + vector& subsampled_features, + vector& original_classes, + vector& subsampled_classes, + float sampleDl, + int verbose); + diff --git a/research/cv/WS3/third_party/setup.py b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py similarity index 83% rename from research/cv/WS3/third_party/setup.py rename to research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py index 083d9d4fb..776cf7328 100644 --- a/research/cv/WS3/third_party/setup.py +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py @@ -18,12 +18,7 @@ module = Extension(m_name, extra_compile_args=['-std=c++11', '-D_GLIBCXX_USE_CXX11_ABI=0']) -setup(ext_modules=[module], include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs()) - - - - - - - - +setup( + ext_modules=[module], + include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs() +) diff --git a/research/cv/WS3/third_party/wrapper.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp similarity index 100% rename from research/cv/WS3/third_party/wrapper.cpp rename to research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp diff --git a/research/cv/WS3/third_party/cloud.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp similarity index 100% rename from research/cv/WS3/third_party/cloud.cpp rename to research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp diff --git a/research/cv/WS3/third_party/cloud.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h similarity index 100% rename from research/cv/WS3/third_party/cloud.h rename to research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h diff --git a/research/cv/WS3/third_party/nanoflann.hpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp similarity index 100% rename from research/cv/WS3/third_party/nanoflann.hpp rename to research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp diff --git a/research/cv/WS3/third_party/src/KDTreeTableAdaptor.h b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h similarity index 100% rename from research/cv/WS3/third_party/src/KDTreeTableAdaptor.h rename to research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h diff --git a/research/cv/WS3/third_party/nearest_neighbors/__init__.py b/research/cv/WS3/third_party/nearest_neighbors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/research/cv/WS3/third_party/src/knn.cpp b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp similarity index 100% rename from research/cv/WS3/third_party/src/knn.cpp rename to research/cv/WS3/third_party/nearest_neighbors/knn.cpp diff --git a/research/cv/WS3/third_party/src/knn.pyx b/research/cv/WS3/third_party/nearest_neighbors/knn.pyx similarity index 100% rename from research/cv/WS3/third_party/src/knn.pyx rename to research/cv/WS3/third_party/nearest_neighbors/knn.pyx diff --git a/research/cv/WS3/third_party/src/knn_.cxx b/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx similarity index 100% rename from research/cv/WS3/third_party/src/knn_.cxx rename to research/cv/WS3/third_party/nearest_neighbors/knn_.cxx diff --git a/research/cv/WS3/third_party/src/knn_.h b/research/cv/WS3/third_party/nearest_neighbors/knn_.h similarity index 100% rename from research/cv/WS3/third_party/src/knn_.h rename to research/cv/WS3/third_party/nearest_neighbors/knn_.h diff --git a/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.6.egg-info b/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.6.egg-info new file mode 100644 index 000000000..3d8f9397a --- /dev/null +++ b/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.6.egg-info @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: KNN NanoFLANN +Version: 0.0.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.7.egg-info b/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.7.egg-info new file mode 100644 index 000000000..3d8f9397a --- /dev/null +++ b/research/cv/WS3/third_party/nearest_neighbors/lib/python/KNN_NanoFLANN-0.0.0-py3.7.egg-info @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: KNN NanoFLANN +Version: 0.0.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/research/cv/WS3/third_party/src/nanoflann.hpp b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp similarity index 100% rename from research/cv/WS3/third_party/src/nanoflann.hpp rename to research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp diff --git a/research/cv/WS3/third_party/src/setup.py b/research/cv/WS3/third_party/nearest_neighbors/setup.py similarity index 100% rename from research/cv/WS3/third_party/src/setup.py rename to research/cv/WS3/third_party/nearest_neighbors/setup.py diff --git a/research/cv/WS3/third_party/src/grid_subsampling.h b/research/cv/WS3/third_party/src/grid_subsampling.h new file mode 100644 index 000000000..b1c84d1b3 --- /dev/null +++ b/research/cv/WS3/third_party/src/grid_subsampling.h @@ -0,0 +1,92 @@ + + +#include "../../cpp_utils/cloud/cloud.h" + +#include +#include + +using namespace std; + +class SampledData +{ +public: + + // Elements + // ******** + + int count; + PointXYZ point; + vector features; + vector> labels; + + + // Methods + // ******* + + // Constructor + SampledData() + { + count = 0; + point = PointXYZ(); + } + + SampledData(const size_t fdim, const size_t ldim) + { + count = 0; + point = PointXYZ(); + features = vector(fdim); + labels = vector>(ldim); + } + + // Method Update + void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_features(const PointXYZ p, vector::iterator f_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + return; + } + void update_classes(const PointXYZ p, vector::iterator l_begin) + { + count += 1; + point += p; + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_points(const PointXYZ p) + { + count += 1; + point += p; + return; + } +}; + + + +void grid_subsampling(vector& original_points, + vector& subsampled_points, + vector& original_features, + vector& subsampled_features, + vector& original_classes, + vector& subsampled_classes, + float sampleDl, + int verbose); + diff --git a/research/cv/WS3/third_party/src/test.py b/research/cv/WS3/third_party/src/test.py deleted file mode 100644 index b6c67fc98..000000000 --- a/research/cv/WS3/third_party/src/test.py +++ /dev/null @@ -1,15 +0,0 @@ -import numpy as np -import lib.python.nearest_neighbors as nearest_neighbors -import time - -batch_size = 16 -num_points = 81920 -K = 16 -pc = np.random.rand(batch_size, num_points, 3).astype(np.float32) - -# nearest neighbours -start = time.time() -neigh_idx = nearest_neighbors.knn_batch(pc, pc, K, omp=True) -print(time.time() - start) - - -- Gitee From 850f3227637b6164afebf1266afc64515ed2dc5b Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 16:33:24 +0800 Subject: [PATCH 03/16] update --- .jenkins/check/config/filter_cppcheck.txt | 10 +- research/cv/WS3/README.md | 2 +- research/cv/WS3/eval_ascend.py | 6 +- research/cv/WS3/eval_gpu.py | 4 +- research/cv/WS3/src/model/model_s3dis.py | 30 +----- .../WS3/src/model/model_s3dis_remove_bias.py | 36 ++------ .../cv/WS3/src/utils/data_prepare_s3dis.py | 9 +- research/cv/WS3/src/utils/metrics.py | 16 ++-- research/cv/WS3/src/utils/tools.py | 4 +- .../cv/WS3/third_party/grid_subsampling.h | 92 ------------------- .../cv/WS3/third_party/src/grid_subsampling.h | 92 ------------------- research/cv/WS3/train_ascend.py | 11 +-- research/cv/WS3/train_gpu.py | 5 +- 13 files changed, 57 insertions(+), 260 deletions(-) delete mode 100644 research/cv/WS3/third_party/grid_subsampling.h delete mode 100644 research/cv/WS3/third_party/src/grid_subsampling.h diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt index 5c58de1d7..c8104e785 100644 --- a/.jenkins/check/config/filter_cppcheck.txt +++ b/.jenkins/check/config/filter_cppcheck.txt @@ -20,4 +20,12 @@ "models/official/audio/LPCNet/third_party/src/pitch.h" "models/official/cv/YOLOX/third_party/cocoeval/cocoeval.cpp" -"models/official/cv/YOLOX/third_party/cocoeval/cocoeval.h" \ No newline at end of file +"models/official/cv/YOLOX/third_party/cocoeval/cocoeval.h" + +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" +"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" +"models/research/cv/WS3/third_party/nearest_neighbors/knn_.h" +"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" +"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 3d305ae60..1b5b505a9 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -68,7 +68,7 @@ bash scripts/eval_s3dis_gpu.sh For Ascend: ```shell -bash scripts/eval_s3dis_ascend.sh +bash scripts/train_s3dis_ascend.sh bash scripts/eval_s3dis_ascend.sh ``` diff --git a/research/cv/WS3/eval_ascend.py b/research/cv/WS3/eval_ascend.py index 74fb5a9f9..ddabc32d9 100644 --- a/research/cv/WS3/eval_ascend.py +++ b/research/cv/WS3/eval_ascend.py @@ -12,7 +12,8 @@ from mindspore import dtype as mstype from src.data.S3DIS_dataset_test import dataloader, ms_map from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg -from src.model.model_s3dis_remove_bias import RandLANet_S3DIS +# from src.model.model_s3dis_remove_bias import RandLANet_S3DIS +from src.model.model_s3dis_remove_bias import RandLANetS3DIS as RandLANet_S3DIS from src.utils.logger import get_logger from src.utils.helper_ply import write_ply @@ -48,6 +49,7 @@ def run_eval(args): d_in = 6 network = RandLANet_S3DIS(d_in, cfg.num_classes) network.set_train(False) + if args.float16: print("network uses float16") network.to_float(mstype.float16) @@ -211,7 +213,7 @@ def run_eval(args): if __name__ == "__main__": """Parse program arguments""" parser = argparse.ArgumentParser( - prog='RandLA-Net', + prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) expr = parser.add_argument_group('Experiment parameters') diff --git a/research/cv/WS3/eval_gpu.py b/research/cv/WS3/eval_gpu.py index 78b7c3a71..022e77689 100644 --- a/research/cv/WS3/eval_gpu.py +++ b/research/cv/WS3/eval_gpu.py @@ -12,7 +12,7 @@ from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg from src.utils.logger import get_logger from src.utils.helper_ply import write_ply -from src.model.model_s3dis import RandLANet_S3DIS +from src.model.model_s3dis import RandLANetS3DIS as RandLANet_S3DIS def run_eval(args): @@ -195,7 +195,7 @@ def run_eval(args): if __name__ == "__main__": """Parse program arguments""" parser = argparse.ArgumentParser( - prog='RandLA-Net', + prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) expr = parser.add_argument_group('Experiment parameters') diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index 10473a2dc..0318511b3 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -5,15 +5,13 @@ import mindspore.nn as nn import mindspore.ops as P from mindspore import dtype as mstype from mindspore import Tensor -from mindspore.ops import composite as C -from mindspore.ops import operations as op from .base_model import SharedMLP, LocalFeatureAggregation -class RandLANet_S3DIS(nn.Cell): +class RandLANetS3DIS(nn.Cell): def __init__(self, d_in, num_classes): - super(RandLANet_S3DIS, self).__init__() + super(RandLANetS3DIS, self).__init__() self.fc_start = nn.Dense(d_in, 8) self.bn_start = nn.SequentialCell([ @@ -128,11 +126,11 @@ class RandLANet_S3DIS(nn.Cell): return pool_features -class RandLA_S3DIS_WithLoss(nn.Cell): +class RandLAS3DISWithLoss(nn.Cell): """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): - super(RandLA_S3DIS_WithLoss, self).__init__() + super(RandLAS3DISWithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float32) self.num_classes = num_classes @@ -146,26 +144,15 @@ class RandLA_S3DIS_WithLoss(nn.Cell): self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) - self.CE_LOSS = 0 - self.SP_LOSS = 0 - def construct(self, feature, feature2, labels, input_inds, cloud_inds, p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): - # data_begin_time = time.time() xyz = [p0, p1, p2, p3, p4] neighbor_idx = [n0, n1, n2, n3, n4] sub_idx = [pl0, pl1, pl2, pl3, pl4] interp_idx = [u0, u1, u2, u3, u4] - # network_begin_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] data_prepare 耗时: {network_begin_time - data_begin_time}s") logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) - # network_end_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] network 耗时: {network_end_time - network_begin_time}s") - # pred_embed = logits # (B, N, 45) xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) @@ -198,22 +185,15 @@ class RandLA_S3DIS_WithLoss(nn.Cell): weighted_loss = weighted_loss * valid_mask CE_loss = P.ReduceSum()(weighted_loss) num_valid_points = P.ReduceSum()(valid_mask.astype(mstype.float32)) - # num_valid_points = int(P.count_nonzero(valid_mask.astype(mstype.int32))) - # CE_loss = P.ReduceSum()(weighted_loss).sum() CE_loss = CE_loss / num_valid_points ### if self.c_epoch_k == 0: loss = CE_loss - self.CE_LOSS = CE_loss - self.SP_LOSS = 0 else: SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, self.topk) * self.c_epoch_k loss = CE_loss + SP_loss - self.CE_LOSS = CE_loss - self.SP_LOSS = SP_loss - return loss @staticmethod @@ -248,7 +228,7 @@ class RandLA_S3DIS_WithLoss(nn.Cell): mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) # => 求unlabelled points 与 class embedding的相似度 # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] - adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + adj_matrix = RandLAS3DISWithLoss.double_feature(invalid_embed, mean_embed) # adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index 6c9b8ebdc..ea83d695f 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -10,9 +10,9 @@ from mindspore import dtype as mstype from .base_model_remove_bias import SharedMLP, LocalFeatureAggregation -class RandLANet_S3DIS(nn.Cell): +class RandLANetS3DIS(nn.Cell): def __init__(self, d_in, num_classes): - super(RandLANet_S3DIS, self).__init__() + super(RandLANetS3DIS, self).__init__() self.fc_start = nn.Dense(d_in, 8, has_bias=False) self.bn_start = nn.SequentialCell([ @@ -49,13 +49,6 @@ class RandLANet_S3DIS(nn.Cell): self.fc_end_fc2 = SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)) self.fc_end_drop = nn.Dropout() self.fc_end_fc3 = SharedMLP(32, num_classes) - # # final semantic prediction - # self.fc_end = nn.SequentialCell([ - # SharedMLP(32, 64, bn=True, activation_fn=nn.LeakyReLU(0.2)), - # SharedMLP(64, 32, bn=True, activation_fn=nn.LeakyReLU(0.2)), - # nn.Dropout(), - # SharedMLP(32, num_classes) - # ]) def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx): r""" @@ -132,11 +125,11 @@ class RandLANet_S3DIS(nn.Cell): return pool_features -class RandLA_S3DIS_WithLoss(nn.Cell): +class RandLAS3DISWithLoss(nn.Cell): """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): - super(RandLA_S3DIS_WithLoss, self).__init__() + super(RandLAS3DISWithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float16) self.num_classes = num_classes @@ -148,8 +141,6 @@ class RandLA_S3DIS_WithLoss(nn.Cell): self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float16) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) - - def construct(self, feature, feature2, labels, input_inds, cloud_inds, p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): @@ -159,14 +150,7 @@ class RandLA_S3DIS_WithLoss(nn.Cell): sub_idx = [pl0, pl1, pl2, pl3, pl4] interp_idx = [u0, u1, u2, u3, u4] - # network_begin_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] data_prepare 耗时: {network_begin_time - data_begin_time}s") logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) - # network_end_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[model_s3dis.py] network 耗时: {network_end_time - network_begin_time}s") - # pred_embed = logits # (B, N, 45) xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) @@ -231,7 +215,7 @@ class RandLA_S3DIS_WithLoss(nn.Cell): num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) topk += num_invalid_points # 点类别的数量 - # num_class = one_hot_label.shape[1] # scalar: 13 + num_class = one_hot_label.shape[1] # scalar: 13 valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) @@ -246,7 +230,7 @@ class RandLA_S3DIS_WithLoss(nn.Cell): # => 求unlabelled points 与 class embedding的相似度 # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] # adj_matrix = self.double_feature(invalid_embed, mean_embed) - adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) + adj_matrix = RandLAS3DISWithLoss.double_feature(invalid_embed, mean_embed) # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) neg_adj = -adj_matrix # (B*N,13) 取负 @@ -271,10 +255,10 @@ class RandLA_S3DIS_WithLoss(nn.Cell): new_soft_label_hot = nn.Softmax(axis=-1)(w_ij) # (B*N,13) top1 = new_soft_label_hot.argmax(axis=-1) - soft_label_mask = self.onehot(top1) - # soft_label_mask = P.OneHot()(top1, num_class, - # Tensor(1.0, dtype=mstype.float16), - # Tensor(0.0, dtype=mstype.float16)) + # soft_label_mask = self.onehot(top1) + soft_label_mask = P.OneHot()(top1, num_class, + Tensor(1.0, dtype=mstype.float16), + Tensor(0.0, dtype=mstype.float16)) new_soft_label_hot = P.mul(new_soft_label_hot, soft_label_mask) logits = logits * new_valid_mask diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py index 6f5740574..479f94df5 100644 --- a/research/cv/WS3/src/utils/data_prepare_s3dis.py +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -1,9 +1,14 @@ -import os, sys, glob, pickle +import sys +import os from os.path import join, exists, dirname, abspath +import glob +import pickle from sklearn.neighbors import KDTree import numpy as np import pandas as pd -from tools import DataProcessing as DP +from src.utils.tools import DataProcessing as DP + +# from tools import DataProcessing as DP BASE_DIR = dirname(abspath(__file__)) ROOT_DIR = dirname(BASE_DIR) diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py index ca84a9129..c44159f1a 100644 --- a/research/cv/WS3/src/utils/metrics.py +++ b/research/cv/WS3/src/utils/metrics.py @@ -1,6 +1,7 @@ import mindspore.numpy as msnp import mindspore as ms + def accuracy(scores, labels): r""" Compute the per-class accuracies and the overall accuracy @@ -16,7 +17,7 @@ def accuracy(scores, labels): ------- list of floats of length num_classes+1 (last item is overall accuracy) """ - num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions + num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions predictions = scores.argmax(axis=-2) @@ -25,9 +26,9 @@ def accuracy(scores, labels): accuracy_mask = predictions == labels for label in range(num_classes): label_mask = labels == label - per_class_accuracy = msnp.logical_and(accuracy_mask , label_mask).astype(ms.float32).sum() + per_class_accuracy = msnp.logical_and(accuracy_mask, label_mask).astype(ms.float32).sum() per_class_accuracy /= label_mask.astype(ms.float32).sum() - if label==0: + if label == 0: accuracies = per_class_accuracy else: accuracies = msnp.append(accuracies, per_class_accuracy) @@ -35,6 +36,7 @@ def accuracy(scores, labels): accuracies = msnp.append(accuracies, accuracy_mask.astype(ms.float32).mean()) return accuracies + def intersection_over_union(scores, labels): r""" Compute the per-class IoU and the mean IoU @@ -50,7 +52,7 @@ def intersection_over_union(scores, labels): ------- list of floats of length num_classes+1 (last item is mIoU) """ - num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions + num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions predictions = scores.argmax(axis=-2) @@ -59,8 +61,10 @@ def intersection_over_union(scores, labels): for label in range(num_classes): pred_mask = predictions == label labels_mask = labels == label - iou = msnp.logical_and(pred_mask , labels_mask).astype(ms.float32).sum() / msnp.logical_or(pred_mask , labels_mask).astype(ms.float32).sum() - if label==0: + iou = msnp.logical_and(pred_mask, labels_mask).astype(ms.float32).sum() / msnp.logical_or(pred_mask, + labels_mask).astype( + ms.float32).sum() + if label == 0: ious = iou else: ious = msnp.append(ious, iou) diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index e48ffbc44..e1c8749fa 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -39,7 +39,7 @@ sys.path.append(os.path.join(BASE_DIR, 'utils')) # loss_scale = 1.0 # loss scale -class ConfigS3DIS: +class ConfigS3DIS(object): def __init__(self): self.k_n = 16 # KNN self.num_layers = 5 # Number of layers @@ -77,7 +77,7 @@ class ConfigS3DIS: self.loss3_type = -1 -class DataProcessing: +class DataProcessing(object): def __init__(self): pass diff --git a/research/cv/WS3/third_party/grid_subsampling.h b/research/cv/WS3/third_party/grid_subsampling.h deleted file mode 100644 index b1c84d1b3..000000000 --- a/research/cv/WS3/third_party/grid_subsampling.h +++ /dev/null @@ -1,92 +0,0 @@ - - -#include "../../cpp_utils/cloud/cloud.h" - -#include -#include - -using namespace std; - -class SampledData -{ -public: - - // Elements - // ******** - - int count; - PointXYZ point; - vector features; - vector> labels; - - - // Methods - // ******* - - // Constructor - SampledData() - { - count = 0; - point = PointXYZ(); - } - - SampledData(const size_t fdim, const size_t ldim) - { - count = 0; - point = PointXYZ(); - features = vector(fdim); - labels = vector>(ldim); - } - - // Method Update - void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_features(const PointXYZ p, vector::iterator f_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - return; - } - void update_classes(const PointXYZ p, vector::iterator l_begin) - { - count += 1; - point += p; - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_points(const PointXYZ p) - { - count += 1; - point += p; - return; - } -}; - - - -void grid_subsampling(vector& original_points, - vector& subsampled_points, - vector& original_features, - vector& subsampled_features, - vector& original_classes, - vector& subsampled_classes, - float sampleDl, - int verbose); - diff --git a/research/cv/WS3/third_party/src/grid_subsampling.h b/research/cv/WS3/third_party/src/grid_subsampling.h deleted file mode 100644 index b1c84d1b3..000000000 --- a/research/cv/WS3/third_party/src/grid_subsampling.h +++ /dev/null @@ -1,92 +0,0 @@ - - -#include "../../cpp_utils/cloud/cloud.h" - -#include -#include - -using namespace std; - -class SampledData -{ -public: - - // Elements - // ******** - - int count; - PointXYZ point; - vector features; - vector> labels; - - - // Methods - // ******* - - // Constructor - SampledData() - { - count = 0; - point = PointXYZ(); - } - - SampledData(const size_t fdim, const size_t ldim) - { - count = 0; - point = PointXYZ(); - features = vector(fdim); - labels = vector>(ldim); - } - - // Method Update - void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_features(const PointXYZ p, vector::iterator f_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - return; - } - void update_classes(const PointXYZ p, vector::iterator l_begin) - { - count += 1; - point += p; - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_points(const PointXYZ p) - { - count += 1; - point += p; - return; - } -}; - - - -void grid_subsampling(vector& original_points, - vector& subsampled_points, - vector& original_features, - vector& subsampled_features, - vector& original_classes, - vector& subsampled_classes, - float sampleDl, - int verbose); - diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py index 76ca0867e..f1edd85ae 100644 --- a/research/cv/WS3/train_ascend.py +++ b/research/cv/WS3/train_ascend.py @@ -12,14 +12,15 @@ from mindspore.nn import Adam from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback from mindspore.train.loss_scale_manager import FixedLossScaleManager from mindspore import dtype as mstype -from mindspore.profiler import Profiler from src.data.S3DIS_dataset import dataloader, ms_map from src.model.base_model import get_param_groups from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg from src.utils.logger import get_logger -from src.model.model_s3dis_remove_bias import RandLANet_S3DIS, RandLA_S3DIS_WithLoss +from src.model.model_s3dis_remove_bias import RandLANetS3DIS as RandLANet_S3DIS +from src.model.model_s3dis_remove_bias import RandLAS3DISWithLoss as RandLA_S3DIS_WithLoss + use_custom_train_one_step_cell = True @@ -167,7 +168,6 @@ def train(cfg, args): else: context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) - # profiler = Profiler(output_path='./profiler_data') ## logger = get_logger(args.outputs_dir, args.rank) @@ -280,7 +280,7 @@ def train(cfg, args): if __name__ == "__main__": """Parse program arguments""" parser = argparse.ArgumentParser( - prog='RandLA-Net', + prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) expr = parser.add_argument_group('Experiment parameters') @@ -375,7 +375,4 @@ if __name__ == "__main__": if args.resume: args.outputs_dir = args.resume - - - train(cfg, args) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py index 0e2dcef64..8225290d2 100644 --- a/research/cv/WS3/train_gpu.py +++ b/research/cv/WS3/train_gpu.py @@ -17,7 +17,8 @@ from mindspore.train.loss_scale_manager import FixedLossScaleManager from src.data.S3DIS_dataset import dataloader, ms_map from src.model.base_model import get_param_groups -from src.model.model_s3dis import RandLANet_S3DIS, RandLA_S3DIS_WithLoss +from src.model.model_s3dis import RandLANetS3DIS as RandLANet_S3DIS +from src.model.model_s3dis import RandLAS3DISWithLoss as RandLA_S3DIS_WithLoss from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg from src.utils.logger import get_logger @@ -228,7 +229,7 @@ def train(cfg, args): if __name__ == "__main__": """Parse program arguments""" parser = argparse.ArgumentParser( - prog='RandLA-Net', + prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) expr = parser.add_argument_group('Experiment parameters') -- Gitee From f566f5c38c6d1acba66340a82596d6c69e8ea283 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 16:42:04 +0800 Subject: [PATCH 04/16] add WS3 cpplint --- .jenkins/check/config/filter_cpplint.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.jenkins/check/config/filter_cpplint.txt b/.jenkins/check/config/filter_cpplint.txt index 41d71bd29..262ec2fa8 100644 --- a/.jenkins/check/config/filter_cpplint.txt +++ b/.jenkins/check/config/filter_cpplint.txt @@ -184,4 +184,11 @@ "models/official/cv/YOLOX/third_party/cocoeval/*" "whitespace/braces]" "models/official/cv/YOLOX/third_party/cocoeval/*" "build/include_subdir" "models/official/cv/YOLOX/third_party/cocoeval/*" "readability/casting" -"models/official/cv/YOLOX/third_party/cocoeval/*" "build/namespaces_literals" \ No newline at end of file +"models/official/cv/YOLOX/third_party/cocoeval/*" "build/namespaces_literals" + + +"models/research/cv/WS3/third_party/*" "runtime/*" +"models/research/cv/WS3/third_party/*" "build/*" +"models/research/cv/WS3/third_party/*" "readability/*" +"models/research/cv/WS3/third_party/*" "whitespace/*" +"models/research/cv/WS3/third_party/*" "[legal/copyright]" -- Gitee From b8ecd2facde9697a36dbb4e6560ee618fc03bab3 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 16:54:31 +0800 Subject: [PATCH 05/16] update --- .jenkins/check/config/filter_cppcheck.txt | 3 +++ research/cv/WS3/src/data/S3DIS_dataset.py | 21 ++++++++++++++------- research/cv/WS3/src/utils/tools.py | 10 +++++++++- research/cv/WS3/train_gpu.py | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt index c8104e785..25a05dd04 100644 --- a/.jenkins/check/config/filter_cppcheck.txt +++ b/.jenkins/check/config/filter_cppcheck.txt @@ -29,3 +29,6 @@ "models/research/cv/WS3/third_party/nearest_neighbors/knn_.h" "models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" diff --git a/research/cv/WS3/src/data/S3DIS_dataset.py b/research/cv/WS3/src/data/S3DIS_dataset.py index 008a72cab..58ffb91b8 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset.py +++ b/research/cv/WS3/src/data/S3DIS_dataset.py @@ -227,19 +227,26 @@ class ActiveLearningSampler(ds.Sampler): # up_sampled with replacement if len(points) < self.cfg.num_points: # 虽然叫data_aug, 但与印象中的数据增强相差甚远 - queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ - DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, self.cfg.num_points) + + data_aug_dict = DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, + self.cfg.num_points) + + queried_pc_xyz = data_aug_dict['xyz_aug'] + queried_pc_colors = data_aug_dict['color_aug'] + queried_idx = data_aug_dict['idx_aug'] + queried_pc_labels = data_aug_dict['label_aug'] + # queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = data_aug_dict['xyz_aug'], \ + # data_aug_dict['color_aug'], \ + # data_aug_dict['idx_aug'], \ + # data_aug_dict['label_aug'] + # queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ + # DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, self.cfg.num_points) queried_pc_xyz = queried_pc_xyz.astype(np.float32) queried_pc_colors = queried_pc_colors.astype(np.float32) queried_pc_labels = queried_pc_labels.astype(np.int32) queried_idx = queried_idx.astype(np.int32) cloud_idx = np.array([cloud_idx], dtype=np.int32) - # print(queried_pc_xyz.shape, queried_pc_colors.shape) - # queried_pc_xyz, queried_pc_colors = data_augment(queried_pc_xyz, queried_pc_colors) - # - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[S3DIS_dataset.py] sample{i} 耗时: {time.time() - begin_time}s") yield queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cloud_idx diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index e1c8749fa..9b831a2ed 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -104,7 +104,15 @@ class DataProcessing(object): idx_dup = list(range(num_in)) + list(dup) idx_aug = idx[idx_dup] label_aug = labels[idx_dup] - return xyz_aug, color_aug, idx_aug, label_aug + + return { + "xyz_aug": xyz_aug, + "color_aug": color_aug, + "idx_aug": idx_aug, + "label_aug": label_aug + } + + # return xyz_aug, color_aug, idx_aug, label_aug @staticmethod def shuffle_idx(x): diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py index 8225290d2..8425ea5fd 100644 --- a/research/cv/WS3/train_gpu.py +++ b/research/cv/WS3/train_gpu.py @@ -251,7 +251,7 @@ if __name__ == "__main__": expr.add_argument('--scale', type=bool, help='scale or not', default=False) - # expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) + expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) misc.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') -- Gitee From f211a6cb667ce1b1fffcfcf013b39936787be85c Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 17:46:22 +0800 Subject: [PATCH 06/16] update --- .jenkins/check/config/filter_cppcheck.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt index 25a05dd04..e03902139 100644 --- a/.jenkins/check/config/filter_cppcheck.txt +++ b/.jenkins/check/config/filter_cppcheck.txt @@ -32,3 +32,13 @@ "models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" "models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" "models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" +"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" +"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" +"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" +"research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" +"research/cv/WS3/third_party/nearest_neighbors/knn_.h" +"research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" +"research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" +"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" +"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" +"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" \ No newline at end of file -- Gitee From 7b40ce529c2567e710676876958321ed18e868f9 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 17:52:46 +0800 Subject: [PATCH 07/16] update --- .jenkins/check/config/filter_cppcheck.txt | 31 ++++++++++------------- .jenkins/check/config/filter_cpplint.txt | 5 ++++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt index e03902139..7b76b923b 100644 --- a/.jenkins/check/config/filter_cppcheck.txt +++ b/.jenkins/check/config/filter_cppcheck.txt @@ -22,23 +22,20 @@ "models/official/cv/YOLOX/third_party/cocoeval/cocoeval.cpp" "models/official/cv/YOLOX/third_party/cocoeval/cocoeval.h" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" -"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" "cstyleCast" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" "variableScope" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" "moreArgsFunction" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" "useInitializationList" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" "passedByValue" +"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" "noCopyConstructor" "models/research/cv/WS3/third_party/nearest_neighbors/knn_.h" -"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" +"models/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx" "moreArgsFunction" +"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "copyCtorAndEqOperator" +"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "uninitMemberVar" +"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "noExplicitConstructor" "models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" "models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" -"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" -"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" -"research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" -"research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" -"research/cv/WS3/third_party/nearest_neighbors/knn_.h" -"research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" -"research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" -"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" -"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" -"research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" \ No newline at end of file +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" "passedByValue" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "copyCtorAndEqOperator" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "uninitMemberVar" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "noExplicitConstructor" \ No newline at end of file diff --git a/.jenkins/check/config/filter_cpplint.txt b/.jenkins/check/config/filter_cpplint.txt index 262ec2fa8..5d44a946f 100644 --- a/.jenkins/check/config/filter_cpplint.txt +++ b/.jenkins/check/config/filter_cpplint.txt @@ -192,3 +192,8 @@ "models/research/cv/WS3/third_party/*" "readability/*" "models/research/cv/WS3/third_party/*" "whitespace/*" "models/research/cv/WS3/third_party/*" "[legal/copyright]" +"research/cv/WS3/third_party/*" "runtime/*" +"research/cv/WS3/third_party/*" "build/*" +"research/cv/WS3/third_party/*" "readability/*" +"research/cv/WS3/third_party/*" "whitespace/*" +"research/cv/WS3/third_party/*" "[legal/copyright]" \ No newline at end of file -- Gitee From e4a35657b56f66d75b189958c4034fdda51bce72 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 18:43:25 +0800 Subject: [PATCH 08/16] add WS3 cpplint --- research/cv/WS3/train_gpu.py | 66 +++++++++++++++++------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py index 8425ea5fd..96b2baf32 100644 --- a/research/cv/WS3/train_gpu.py +++ b/research/cv/WS3/train_gpu.py @@ -228,52 +228,50 @@ def train(cfg, args): if __name__ == "__main__": """Parse program arguments""" - parser = argparse.ArgumentParser( - prog='WS3', - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - expr = parser.add_argument_group('Experiment parameters') - param = parser.add_argument_group('Hyperparameters') - dirs = parser.add_argument_group('Storage directories') - misc = parser.add_argument_group('Miscellaneous') + parser = argparse.ArgumentParser() + # parser = argparse.ArgumentParser( + # prog='WS3', + # description="WS3 Code Mindspore Version", + # formatter_class=argparse.ArgumentDefaultsHelpFormatter + # ) - expr.add_argument('--epochs', type=int, help='max epochs', default=100) + parser.add_argument('--epochs', type=int, help='max epochs', default=100) - expr.add_argument('--batch_size', type=int, help='batch size', default=6) + parser.add_argument('--batch_size', type=int, help='batch size', default=6) - expr.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') - expr.add_argument('--outputs_dir', type=str, help='path of output', default='./outputs') + parser.add_argument('--outputs_dir', type=str, help='path of output', default='./outputs') - expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - expr.add_argument('--resume', type=str, help='model to resume', default=None) + parser.add_argument('--resume', type=str, help='model to resume', default=None) - expr.add_argument('--scale', type=bool, help='scale or not', default=False) + parser.add_argument('--scale', type=bool, help='scale or not', default=False) - expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) + parser.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) - misc.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') + parser.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU', choices=['GPU']) - misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - misc.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) - misc.add_argument('--name', type=str, help='name of the experiment', - default=None) - misc.add_argument('--ss_pretrain', type=str, help='name of the experiment', - default=None) - misc.add_argument('--retrain_model', type=str, help='name of the experiment', - default=None) - misc.add_argument('--train_steps', type=int, default=500) - misc.add_argument('--learning_rate', type=float, default=0.01) - misc.add_argument('--lr_decays', type=float, default=0.95) - misc.add_argument('--loss_scale', type=float, default=1.0) - misc.add_argument('--topk', type=int, default=500) - misc.add_argument('--num_training_ep0', type=int, default=30) - misc.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] - misc.add_argument('--random_seed', type=int, default=888) - misc.add_argument('--graph_mode', action='store_true', default=False) + parser.add_argument('--name', type=str, help='name of the experiment', + default=None) + parser.add_argument('--ss_pretrain', type=str, help='name of the experiment', + default=None) + parser.add_argument('--retrain_model', type=str, help='name of the experiment', + default=None) + parser.add_argument('--train_steps', type=int, default=500) + parser.add_argument('--learning_rate', type=float, default=0.01) + parser.add_argument('--lr_decays', type=float, default=0.95) + parser.add_argument('--loss_scale', type=float, default=1.0) + parser.add_argument('--topk', type=int, default=500) + parser.add_argument('--num_training_ep0', type=int, default=30) + parser.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] + parser.add_argument('--random_seed', type=int, default=888) + parser.add_argument('--graph_mode', action='store_true', default=False) args = parser.parse_args() -- Gitee From 1db32f17063d0f7a631951137983e411c2ae2238 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 20:09:49 +0800 Subject: [PATCH 09/16] update --- .jenkins/check/config/filter_cppcheck.txt | 22 ++++++--------- research/cv/WS3/README.md | 12 ++++---- .../WS3/src/model/model_s3dis_remove_bias.py | 6 ++-- research/cv/WS3/train_ascend.py | 28 ++++++------------- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/.jenkins/check/config/filter_cppcheck.txt b/.jenkins/check/config/filter_cppcheck.txt index 7b76b923b..8d19d298b 100644 --- a/.jenkins/check/config/filter_cppcheck.txt +++ b/.jenkins/check/config/filter_cppcheck.txt @@ -22,20 +22,14 @@ "models/official/cv/YOLOX/third_party/cocoeval/cocoeval.cpp" "models/official/cv/YOLOX/third_party/cocoeval/cocoeval.h" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" "cstyleCast" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" "variableScope" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" "moreArgsFunction" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" "useInitializationList" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" "passedByValue" -"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" "noCopyConstructor" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h" +"models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" "models/research/cv/WS3/third_party/nearest_neighbors/knn_.h" -"models/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx" "moreArgsFunction" -"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "copyCtorAndEqOperator" -"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "uninitMemberVar" -"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "noExplicitConstructor" +"models/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx" +"models/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp" "models/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h" "models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" "passedByValue" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "copyCtorAndEqOperator" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "uninitMemberVar" -"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" "noExplicitConstructor" \ No newline at end of file +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h" +"models/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp" \ No newline at end of file diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 1b5b505a9..06346a34b 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -46,7 +46,7 @@ dataset - Mindspore = 1.7.0 - Third Package - - Pyhton==3.7 + - Python==3.7 - pandas==1.1.5 - scikit-learn==0.21.3 - numpy==1.21.6 @@ -79,8 +79,8 @@ bash scripts/eval_s3dis_ascend.sh ``` WS3 ├── scripts -│ ├── eval_s3dis_ascend.sh # Evaluting: S3DIS dataset on Ascend -│ ├── eval_s3dis_gpu.sh # Evaluting: S3DIS dataset on GPU +│ ├── eval_s3dis_ascend.sh # Evaluating: S3DIS dataset on Ascend +│ ├── eval_s3dis_gpu.sh # Evaluating: S3DIS dataset on GPU │ ├── train_s3dis_ascend.sh # Training: S3DIS dataset on Ascend │ └── train_s3dis_gpu.sh # Training: S3DIS dataset on GPU ├── src @@ -99,7 +99,7 @@ WS3 │ ├── data_prepare_s3dis.py # data processor for s3dis dataset │ ├── helper_ply.py # file utils │ ├── logger.py # logger -│ ├── metrics.py # calcualte iou and accuracy +│ ├── metrics.py # calculate iou and accuracy │ └── tools.py # DataProcessing and Config │ ├── eval_gpu.py @@ -189,8 +189,8 @@ by `{args.outputs_dir}/{args.name}/ckpt`. For example: ``` outputs ├── BatchS_6_Float16_PyNative_Ascend - ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluting: S3DIS dataset on Ascend - └── ckpt # Evaluting: S3DIS dataset on GPU + ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluating: S3DIS dataset on Ascend + └── ckpt # Evaluating: S3DIS dataset on GPU ├── randla_1_500.ckpt ├── randla_2_500.ckpt └── .... diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index ea83d695f..c70bb08b1 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -10,9 +10,9 @@ from mindspore import dtype as mstype from .base_model_remove_bias import SharedMLP, LocalFeatureAggregation -class RandLANetS3DIS(nn.Cell): +class WS3(nn.Cell): def __init__(self, d_in, num_classes): - super(RandLANetS3DIS, self).__init__() + super(WS3, self).__init__() self.fc_start = nn.Dense(d_in, 8, has_bias=False) self.bn_start = nn.SequentialCell([ @@ -128,7 +128,7 @@ class RandLANetS3DIS(nn.Cell): class RandLAS3DISWithLoss(nn.Cell): """RadnLA-net with loss""" - def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): + def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, topk): super(RandLAS3DISWithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float16) diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py index f1edd85ae..6033ecf2f 100644 --- a/research/cv/WS3/train_ascend.py +++ b/research/cv/WS3/train_ascend.py @@ -56,7 +56,6 @@ class UpdateLossEpoch(Callback): # v1.8: on_train_epoch_begin # v1.7: epoch_begin def epoch_begin(self, run_context): - # update_loss_time = time.time() cb_params = run_context.original_args() if use_custom_train_one_step_cell: @@ -72,9 +71,6 @@ class UpdateLossEpoch(Callback): f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[train_gpu.py] UpdateLossEpoch 耗时: {time.time() - update_loss_time}s") - def on_train_epoch_begin(self, run_context): self.epoch_begin(run_context) @@ -135,7 +131,6 @@ class S3DISLossMonitor(Callback): def prepare_network(weights, cfg, args): """Prepare Network""" - # from model.model_s3dis import RandLANet_S3DIS, RandLA_S3DIS_WithLoss d_in = 6 # xyzrgb network = RandLANet_S3DIS(d_in, cfg.num_classes) @@ -151,8 +146,12 @@ def prepare_network(weights, cfg, args): new_param_dict[new_key] = val load_param_into_net(network, new_param_dict, strict_load=True) - network = RandLA_S3DIS_WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, - cfg.loss3_type, cfg.topk) + network = RandLA_S3DIS_WithLoss(network, + weights, + cfg.num_classes, + cfg.ignored_label_indexs, + cfg.c_epoch, + cfg.topk) if args.retrain_model: print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") @@ -168,8 +167,6 @@ def train(cfg, args): else: context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) - # profiler = Profiler(output_path='./profiler_data') - ## logger = get_logger(args.outputs_dir, args.rank) logger.info("============ Args =================") @@ -199,7 +196,6 @@ def train(cfg, args): pickle.dump(log, f) f.close() - # resume checkpoint, cur_epoch, best_epoch, cur_step, best_step if args.resume: f = open(args.resume + '/log.pkl', 'rb') log = pickle.load(f) @@ -207,10 +203,8 @@ def train(cfg, args): f.close() param_dict = load_checkpoint(args.resume) load_param_into_net(network, param_dict) - # load_param_into_net(network, args.resume) - - # data loader + # dataloader train_loader = train_loader.batch(batch_size=cfg.batch_size, per_batch_map=ms_map, input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], @@ -219,15 +213,12 @@ def train(cfg, args): "n0", "n1", "n2", "n3", "n4", "pl0", "pl1", "pl2", "pl3", "pl4", "u0", "u1", "u2", "u3", "u4", - # 'test_dict' ], drop_remainder=True) logger.info('==========begin training===============') - # loss scale manager loss_scale = cfg.loss_scale - # loss_scale = args.scale_weight loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None print('loss_scale:', loss_scale) @@ -326,7 +317,7 @@ if __name__ == "__main__": misc.add_argument('--num_training_ep0', type=int, default=30) misc.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] misc.add_argument('--random_seed', type=int, default=888) - # misc.add_argument('--graph_mode', action='store_true', default=False) + misc.add_argument('--graph_mode', action='store_true', default=False) args = parser.parse_args() @@ -362,10 +353,7 @@ if __name__ == "__main__": np.random.seed(cfg.random_seed) set_seed(cfg.random_seed) - # https://www.mindspore.cn/docs/zh-CN/r1.7/api_python/mindspore/mindspore.set_seed.html?highlight=set_seed - # ds.config.set_auto_num_workers() - # output_dir = f"./runs/pretrain_s3dis_v13" args.outputs_dir = os.path.join(args.outputs_dir, args.name) print(f"outputs_dir:{args.outputs_dir}") -- Gitee From ebe3d14ac93cc984cd5108f77296dd845bb5bbfe Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 20:32:04 +0800 Subject: [PATCH 10/16] update --- research/cv/WS3/README.md | 52 +- research/cv/WS3/eval_ascend.py | 108 +- research/cv/WS3/eval_gpu.py | 52 +- research/cv/WS3/src/data/S3DIS_dataset.py | 51 +- .../cv/WS3/src/data/S3DIS_dataset_test.py | 71 +- research/cv/WS3/src/model/base_model.py | 16 +- .../WS3/src/model/base_model_remove_bias.py | 18 +- research/cv/WS3/src/model/model_s3dis.py | 8 +- .../WS3/src/model/model_s3dis_remove_bias.py | 6 +- research/cv/WS3/src/utils/helper_ply.py | 15 +- research/cv/WS3/src/utils/logger.py | 2 +- research/cv/WS3/src/utils/metrics.py | 5 +- research/cv/WS3/src/utils/tools.py | 140 +- research/cv/WS3/third_party/compile_op.sh | 2 + .../grid_subsampling/grid_subsampling.cpp | 176 +- .../grid_subsampling/grid_subsampling.h | 132 +- .../cpp_wrappers/cpp_subsampling/wrapper.cpp | 366 +- .../cpp_wrappers/cpp_utils/cloud/cloud.cpp | 72 +- .../cpp_wrappers/cpp_utils/cloud/cloud.h | 153 +- .../cpp_utils/nanoflann/nanoflann.hpp | 8 +- .../nearest_neighbors/KDTreeTableAdaptor.h | 126 +- .../WS3/third_party/nearest_neighbors/knn.cpp | 2 +- .../WS3/third_party/nearest_neighbors/knn.pyx | 8 +- .../third_party/nearest_neighbors/knn_.cxx | 412 +- .../WS3/third_party/nearest_neighbors/knn_.h | 24 +- .../nearest_neighbors/nanoflann.hpp | 3558 ++++++++--------- .../third_party/nearest_neighbors/setup.py | 4 +- research/cv/WS3/train_ascend.py | 167 +- research/cv/WS3/train_gpu.py | 135 +- 29 files changed, 2871 insertions(+), 3018 deletions(-) diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 06346a34b..63ee80b50 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -23,16 +23,16 @@ or [original tensorflow implementation](https://github.com/Yachao-Zhang/WS3) for ### Directory structure of dataset -``` +```html dataset └──S3DIS # S3DIS dataset ├── input_0.040 │ ├── *.ply │ ├── *_proj.pkl - │ └── *_KDTree.pkl + │ └── *_KDTree.pkl ├── original_ply - │ └── *.ply - │ + │ └── *.ply + │ └── Stanford3dDataset_v1.2_Aligned_Version ``` @@ -76,7 +76,7 @@ bash scripts/eval_s3dis_ascend.sh ### Scripts and Sample Code -``` +```html WS3 ├── scripts │ ├── eval_s3dis_ascend.sh # Evaluating: S3DIS dataset on Ascend @@ -93,13 +93,13 @@ WS3 │ │ ├── model_s3dis.py # combine loss function with network │ │ └── model_s3dis_remove_bias.py # combine loss function with network removing bias │ └── utils -│ ├── cpp_wrappers # dependency for point cloud subsampling +│ ├── cpp_wrappers # dependency for point cloud subsampling │ ├── meta # meta information for data processor │ ├── nearest_neighbors # dependency for point cloud nearest_neighbors │ ├── data_prepare_s3dis.py # data processor for s3dis dataset │ ├── helper_ply.py # file utils │ ├── logger.py # logger -│ ├── metrics.py # calculate iou and accuracy +│ ├── metrics.py # calculate iou and accuracy │ └── tools.py # DataProcessing and Config │ ├── eval_gpu.py @@ -169,13 +169,13 @@ Log information: ```shell ... -epoch: 38 step: 460, loss is 1.4644418954849243 +epoch: 38 step: 460, loss is 1.4644418954849243 epoch: 38 step: 480, loss is 1.7821598052978516 -epoch: 38 step: 500, loss is 1.6881225109100342 +epoch: 38 step: 500, loss is 1.6881225109100342 UpdateLossEpoch ==> cur_epoch_num:39, cur_training_ep:0.17547142790305748, loss_fn.c_epoch_k:1.3422978 -epoch: 39 step: 20, loss is 2.0437705516815186 -epoch: 39 step: 40, loss is 2.5041351318359375 -epoch: 39 step: 60, loss is 1.307090401649475 +epoch: 39 step: 20, loss is 2.0437705516815186 +epoch: 39 step: 40, loss is 2.5041351318359375 +epoch: 39 step: 60, loss is 1.307090401649475 ... ``` @@ -186,14 +186,14 @@ Using `bash scripts/eval_s3dis_ascend.sh` as an example: Training results will be stored in `/outputs/BatchS_6_Float16_PyNative_Ascend` , which is determined by `{args.outputs_dir}/{args.name}/ckpt`. For example: -``` +```html outputs ├── BatchS_6_Float16_PyNative_Ascend ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluating: S3DIS dataset on Ascend └── ckpt # Evaluating: S3DIS dataset on GPU ├── randla_1_500.ckpt ├── randla_2_500.ckpt - └── .... + └── .... ``` ## Evaluation @@ -229,7 +229,7 @@ Area_5_hallway_3 Acc:0.9548643367899511## Export Area_5_office_12 Acc:0.8639117068037043 Area_5_office_23 Acc:0.9049251547225916 -------------------------------------------------------------------------------------- -61.49 | 91.38 96.59 79.32 0.00 22.55 59.74 42.87 75.71 81.15 62.53 68.90 67.55 51.07 +61.49 | 91.38 96.59 79.32 0.00 22.55 59.74 42.87 75.71 81.15 62.53 68.90 67.55 51.07 -------------------------------------------------------------------------------------- ==========end test=============== ``` @@ -258,16 +258,16 @@ Area_5_office_23 Acc:0.9049251547225916 ### Inference Performance -| Parameters | Ascend | GPU | -| ------------------- | --------------------------- |--------------------------- | -| Model Version | WS3 | WS3 | -| Resource | Ascend 910; OS Euler2.8 | Nvidia GeForce RTX 3090 | -| Uploaded Date | 11/24/2022 (month/day/year) | 11/24/2022 (month/day/year) | -| MindSpore Version | 1.7.0 | 1.8.0 | -| Dataset | S3DIS | S3DIS | -| batch_size | 6 | 6 | -| outputs | feature vector + probability | feature vector + probability | -| Accuracy | See following tables | See following tables | +| Parameters | Ascend | GPU | +| ------------------- | --------------------------- |--------------------------- | +| Model Version | WS3 | WS3 | +| Resource | Ascend 910; OS Euler2.8 | Nvidia GeForce RTX 3090 | +| Uploaded Date | 11/24/2022 (month/day/year) | 11/24/2022 (month/day/year) | +| MindSpore Version | 1.7.0 | 1.8.0 | +| Dataset | S3DIS | S3DIS | +| batch_size | 6 | 6 | +| outputs | feature vector + probability | feature vector + probability | +| Accuracy | See following tables | See following tables | ### S3DIS (Setting: 1% labeled points) @@ -279,7 +279,7 @@ Area_5_office_23 Acc:0.9049251547225916 Please kindly cite the original paper references in your publications if it helps your research: -``` +```html @inproceedings{zhang2021weakly, title={Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud}, author={Zhang, Yachao and Li, Zonghao and Xie, Yuan and Qu, Yanyun and Li, Cuihua and Mei, Tao}, diff --git a/research/cv/WS3/eval_ascend.py b/research/cv/WS3/eval_ascend.py index ddabc32d9..b94a447d2 100644 --- a/research/cv/WS3/eval_ascend.py +++ b/research/cv/WS3/eval_ascend.py @@ -1,43 +1,33 @@ -import datetime, os, time, argparse -import logging +import os +import argparse from pathlib import Path - import numpy as np -from pathlib import Path from sklearn.metrics import confusion_matrix -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops +from mindspore import context, load_checkpoint, load_param_into_net, ops from mindspore import dtype as mstype from src.data.S3DIS_dataset_test import dataloader, ms_map from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg -# from src.model.model_s3dis_remove_bias import RandLANet_S3DIS -from src.model.model_s3dis_remove_bias import RandLANetS3DIS as RandLANet_S3DIS +from src.model.model_s3dis_remove_bias import WS3 from src.utils.logger import get_logger from src.utils.helper_ply import write_ply from tqdm import tqdm -def run_eval(args): - context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) - - logger = get_logger(args.outputs_dir, args.rank) - - # data loader - _, val_ds, dataset = dataloader( - dataset_dir=args.dataset_dir, - num_parallel_workers=8, - shuffle=False - ) +def run_eval(params): + context.set_context(mode=context.PYNATIVE_MODE, device_target=params.device_target, device_id=params.device_id) + logger = get_logger(params.outputs_dir, params.rank) + _, val_ds, dataset = dataloader(dataset_dir=params.dataset_dir, num_parallel_workers=8, shuffle=False) input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] output_columns = ["features", "labels", "input_inds", "cloud_inds", "p0", "p1", "p2", "p3", "p4", "n0", "n1", "n2", "n3", "n4", "pl0", "pl1", "pl2", "pl3", "pl4", "u0", "u1", "u2", "u3", "u4"] - val_loader = val_ds.batch(batch_size=args.batch_size, + val_loader = val_ds.batch(batch_size=params.batch_size, per_batch_map=ms_map, input_columns=input_columns, output_columns=output_columns, @@ -45,34 +35,28 @@ def run_eval(args): val_ds_size = val_loader.get_dataset_size() val_loader = val_loader.create_dict_iterator() - # load ckpt, iterate ckpts to find the best - d_in = 6 - network = RandLANet_S3DIS(d_in, cfg.num_classes) + network = WS3(6, cfg.num_classes) network.set_train(False) - if args.float16: + if params.float16: print("network uses float16") network.to_float(mstype.float16) - if '.ckpt' in args.model_path: - ckpts = [args.model_path] + if '.ckpt' in params.model_path: + ckpts = [params.model_path] else: - # ckpt_path = Path(args.model_path) - ckpt_path = Path(os.path.join(args.model_path, 'ckpt')) + ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) ckpts = ckpt_path.glob('*.ckpt') ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) - best_miou = 0.0 - best_ckpt = ckpts[0] + best_miou, best_ckpt = 0.0, ckpts[0] ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) logger.info('==========begin test===============') for ckpt_i, ckpt in enumerate(ckpts): - # load current ckpt logger.info('load ckpt from:{}'.format(str(ckpt))) param_dict = load_checkpoint(str(ckpt)) load_param_into_net(network, param_dict) - # Number of points per class in validation set val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) i = 0 for label_val in dataset.label_values: @@ -84,7 +68,6 @@ def run_eval(args): # Smoothing parameter for votes test_smooth = 0.95 - step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) for step_i, data in enumerate(val_loader): # begin_time = time.time() @@ -116,24 +99,14 @@ def run_eval(args): correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) acc = correct / float(np.prod(np.shape(labels))) msg = f'Step: {str(step_i)}; acc: {str(acc)}' - post_process_time = time.time() - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[train_modelarts_notebook_v2.py] post process 耗时: {post_process_time - network_time}s") step_bar.set_postfix_str(msg, refresh=False) step_bar.update() - - last_min = -0.5 - num_votes = 100 - + last_min, num_votes = -0.5, 100 while last_min < num_votes: new_min = np.min(val_ds.source.min_possibility['validation']) logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") - # if True: if last_min + 1 < new_min: - # Update last_min last_min += 1 - - # Show vote results (On subcloud so it is not the good values here) logger.info('Confusion on sub clouds') confusion_list = [] @@ -143,16 +116,12 @@ def run_eval(args): probs = test_probs[i_test] preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) labels = dataset.input_labels['validation'][i_test] - - # Confs confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] # Regroup confusions C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) - # Rescale with the right number of point per class C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) - # Compute IoUs IoUs = DP.IoU_from_confusions(C) m_IoU = np.mean(IoUs) @@ -160,38 +129,31 @@ def run_eval(args): for IoU in IoUs: s += '{:5.2f} '.format(100 * IoU) logger.info(s + '\n') - if int(np.ceil(new_min)) % 1 == 0: - # Project predictions logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) proj_probs_list = [] - for i_val in range(num_val): # Reproject probs back to the evaluations points proj_idx = dataset.val_proj[i_val] probs = test_probs[i_val][proj_idx, :] proj_probs_list += [probs] - # Show vote results logger.info('Confusion on full clouds') confusion_list = [] for i_test in range(num_val): # Get the predicted labels preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - # Confusion labels = dataset.val_labels[i_test] acc = np.sum(preds == labels) / len(labels) logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(args.outputs_dir, 'val_preds', name), [preds, labels], ['pred', 'label']) - + write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], + ['pred', 'label']) # Regroup confusions C = np.sum(np.stack(confusion_list), axis=0) - IoUs = DP.IoU_from_confusions(C) m_IoU = np.mean(IoUs) if m_IoU > best_miou: @@ -211,33 +173,21 @@ def run_eval(args): if __name__ == "__main__": - """Parse program arguments""" + # """Parse program arguments""" parser = argparse.ArgumentParser( prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - expr = parser.add_argument_group('Experiment parameters') - param = parser.add_argument_group('Hyperparameters') - dirs = parser.add_argument_group('Storage directories') - misc = parser.add_argument_group('Miscellaneous') - - expr.add_argument('--batch_size', type=int, help='val batch size', default=20) - - expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - - dirs.add_argument('--model_path', type=str, help='model saved path', default='runs') - - misc.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') - - misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) - - misc.add_argument('--rank', type=int, help='rank', default=0) - - dirs.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') - - dirs.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') - - expr.add_argument('--float16', type=bool, default=False) + parser.add_argument('--batch_size', type=int, help='val batch size', default=20) + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--model_path', type=str, help='model saved path', default='runs') + parser.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + parser.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') + + parser.add_argument('--float16', type=bool, default=False) args = parser.parse_args() diff --git a/research/cv/WS3/eval_gpu.py b/research/cv/WS3/eval_gpu.py index 022e77689..9d554a176 100644 --- a/research/cv/WS3/eval_gpu.py +++ b/research/cv/WS3/eval_gpu.py @@ -1,28 +1,29 @@ -import datetime, os, time, argparse - -import numpy as np +import os +import argparse +import datetime from pathlib import Path +import numpy as np from sklearn.metrics import confusion_matrix from tqdm import tqdm -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops +from mindspore import context, load_checkpoint, load_param_into_net, ops from src.data.S3DIS_dataset_test import dataloader, ms_map from src.utils.tools import DataProcessing as DP from src.utils.tools import ConfigS3DIS as cfg from src.utils.logger import get_logger from src.utils.helper_ply import write_ply -from src.model.model_s3dis import RandLANetS3DIS as RandLANet_S3DIS +from src.model.model_s3dis import WS3 -def run_eval(args): - context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) +def run_eval(params): + context.set_context(mode=context.PYNATIVE_MODE, device_target=params.device_target, device_id=params.device_id) - logger = get_logger(args.outputs_dir, args.rank) + logger = get_logger(params.outputs_dir, params.rank) # data loader _, val_ds, dataset = dataloader( - dataset_dir=args.dataset_dir, + dataset_dir=params.dataset_dir, num_parallel_workers=8, shuffle=False ) @@ -32,7 +33,7 @@ def run_eval(args): "n0", "n1", "n2", "n3", "n4", "pl0", "pl1", "pl2", "pl3", "pl4", "u0", "u1", "u2", "u3", "u4"] - val_loader = val_ds.batch(batch_size=args.batch_size, + val_loader = val_ds.batch(batch_size=params.batch_size, per_batch_map=ms_map, input_columns=input_columns, output_columns=output_columns, @@ -42,12 +43,12 @@ def run_eval(args): # load ckpt, iterate ckpts to find the best d_in = 6 - network = RandLANet_S3DIS(d_in, cfg.num_classes) + network = WS3(d_in, cfg.num_classes) - if '.ckpt' in args.model_path: - ckpts = [args.model_path] + if '.ckpt' in params.model_path: + ckpts = [params.model_path] else: - ckpt_path = Path(os.path.join(args.model_path, 'ckpt')) + ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) ckpts = ckpt_path.glob('*.ckpt') ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) # if len(ckpts) == 0: @@ -169,7 +170,8 @@ def run_eval(args): confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(args.outputs_dir, 'val_preds', name), [preds, labels], ['pred', 'label']) + write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], + ['pred', 'label']) # Regroup confusions C = np.sum(np.stack(confusion_list), axis=0) @@ -193,29 +195,25 @@ def run_eval(args): if __name__ == "__main__": - """Parse program arguments""" + # """Parse program arguments""" parser = argparse.ArgumentParser( prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - expr = parser.add_argument_group('Experiment parameters') - param = parser.add_argument_group('Hyperparameters') - dirs = parser.add_argument_group('Storage directories') - misc = parser.add_argument_group('Miscellaneous') - expr.add_argument('--batch_size', type=int, help='val batch size', default=20) + parser.add_argument('--batch_size', type=int, help='val batch size', default=20) - expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - dirs.add_argument('--model_path', type=str, help='model saved path', required=True, default='outputs') + parser.add_argument('--model_path', type=str, help='model saved path', required=True, default='outputs') - misc.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') + parser.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') - misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - dirs.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') - misc.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) args = parser.parse_args() diff --git a/research/cv/WS3/src/data/S3DIS_dataset.py b/research/cv/WS3/src/data/S3DIS_dataset.py index 58ffb91b8..1599481ba 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset.py +++ b/research/cv/WS3/src/data/S3DIS_dataset.py @@ -1,14 +1,13 @@ -import glob -import pickle, time, warnings -import numpy as np +import pickle +import time from pathlib import Path - -import mindspore.dataset as ds import random +import numpy as np +import mindspore.dataset as ds -from ..utils.helper_ply import read_ply -from ..utils.tools import DataProcessing as DP -from ..utils.tools import ConfigS3DIS as cfg +from src.utils.helper_ply import read_ply +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg class S3DISDatasetGenerator: @@ -53,7 +52,7 @@ class S3DISDatasetGenerator: def load_data(self): tree_path = self.dataset_path / f"input_{self.sub_grid_size:.3f}" - for i, file_path in enumerate(self.paths): + for file_path in self.paths: t0 = time.time() cloud_name = file_path.stem if self.val_split in cloud_name: @@ -71,10 +70,9 @@ class S3DISDatasetGenerator: sub_xyz = np.vstack((data['x'], data['y'], data['z'])).T all_select_label_indx = [] if cloud_split == 'training' and self.weak_label: - ''' ***************** ''' all_select_label_indx = [] - for i in range(self.num_classes): - ind_class = np.where(sub_labels == i)[0] + for class_i in range(self.num_classes): + ind_class = np.where(sub_labels == class_i)[0] num_classs = len(ind_class) if num_classs > 0: if '%' in self.labeled_point: @@ -104,7 +102,6 @@ class S3DISDatasetGenerator: ind_class_select = ind_class[select_labels_indx] all_select_label_indx.append(ind_class_select[0]) sub_labels[ind_class_noselect] = 13 - ''' ***************** ''' # Read pkl with search tree with open(kd_tree_file, 'rb') as f: @@ -122,7 +119,7 @@ class S3DISDatasetGenerator: print('\nPreparing reprojected indices for testing') # Get validation and test reprojected indices - for i, file_path in enumerate(self.paths): + for file_path in self.paths: t0 = time.time() cloud_name = file_path.stem # Validation projection and labels @@ -147,9 +144,9 @@ class S3DISDatasetGenerator: class ActiveLearningSampler(ds.Sampler): - def __init__(self, dataset, cfg, batch_size=6, split='training'): + def __init__(self, dataset, config, batch_size=6, split='training'): super(ActiveLearningSampler, self).__init__() - self.cfg = cfg + self.cfg = config self.dataset = dataset self.split = split self.batch_size = batch_size @@ -164,22 +161,24 @@ class ActiveLearningSampler(ds.Sampler): # Random initialisation for weights self.possibility[split] = [] self.min_possibility[split] = [] - for i, tree in enumerate(self.dataset.input_colors[split]): + for tree in self.dataset.input_colors[split]: self.possibility[split] += [np.random.rand(tree.data.shape[0]) * 1e-3] self.min_possibility[split] += [float(np.min(self.possibility[split][-1]))] def __iter__(self): - return self.spatially_regular_gen() def __len__(self): - len = self.n_samples * self.batch_size - print(f"Length of ActiveLearningSampler is {len}") - return len # not equal to the actual size of the dataset, but enable nice progress bars + return self.n_samples * self.batch_size # not equal to the actual size of the dataset, but enable nice progress bars + + # def __len__(self): + # len = self.n_samples * self.batch_size + # print(f"Length of ActiveLearningSampler is {len}") + # return len # not equal to the actual size of the dataset, but enable nice progress bars def spatially_regular_gen(self): # Choosing the least known point as center of a new cloud each time. - for i in range(self.n_samples * self.batch_size): # num_per_epoch + for _ in range(self.n_samples * self.batch_size): # num_per_epoch # begin_time = time.time() # Generator loop # Choose a random cloud @@ -292,17 +291,17 @@ def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_id input_up_samples[0], input_up_samples[1], input_up_samples[2], input_up_samples[3], input_up_samples[4] -def dataloader(cfg, **kwargs): - dataset = S3DISDatasetGenerator(cfg.dataset_dir, cfg.labeled_percent) +def dataloader(config, **kwargs): + dataset = S3DISDatasetGenerator(config.dataset_dir, config.labeled_percent) val_sampler = ActiveLearningSampler( dataset, - cfg, + config, batch_size=cfg.val_batch_size, split='validation' ) train_sampler = ActiveLearningSampler( dataset, - cfg, + config, batch_size=cfg.batch_size, split='training' ) diff --git a/research/cv/WS3/src/data/S3DIS_dataset_test.py b/research/cv/WS3/src/data/S3DIS_dataset_test.py index 55cf61d8c..34f31aca3 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset_test.py +++ b/research/cv/WS3/src/data/S3DIS_dataset_test.py @@ -1,15 +1,13 @@ -import pickle, time, warnings -import numpy as np +import pickle +import time from pathlib import Path +import numpy as np import mindspore.dataset as ds -from mindspore import Tensor, context -from mindspore import dtype as mstype -import mindspore.ops as ops -from utils.tools import ConfigS3DIS as cfg -from utils.tools import DataProcessing as DP -from utils.helper_ply import read_ply +from src.utils.helper_ply import read_ply +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg class S3DISDatasetGenerator: @@ -58,7 +56,7 @@ class S3DISDatasetGenerator: def load_data(self): tree_path = self.dataset_path / f'input_{self.sub_grid_size:.3f}' - for i, file_path in enumerate(self.paths): + for file_path in self.paths: t0 = time.time() cloud_name = file_path.stem if self.val_area in cloud_name: @@ -90,7 +88,7 @@ class S3DISDatasetGenerator: print('\nPreparing reprojected indices for testing') # Get validation and test reprojected indices - for i, file_path in enumerate(self.paths): + for file_path in self.paths: t0 = time.time() cloud_name = file_path.stem @@ -129,7 +127,7 @@ class ActiveLearningSampler(ds.Sampler): # Random initialisation for weights self.possibility[split] = [] self.min_possibility[split] = [] - for i, tree in enumerate(self.dataset.input_colors[split]): + for tree in self.dataset.input_colors[split]: self.possibility[split] += [np.random.rand(tree.data.shape[0]) * 1e-3] self.min_possibility[split] += [float(np.min(self.possibility[split][-1]))] @@ -141,7 +139,7 @@ class ActiveLearningSampler(ds.Sampler): def spatially_regular_gen(self): # Generator loop - for i in range(self.n_samples * self.batch_size): # num_per_epoch + for _ in range(self.n_samples * self.batch_size): # num_per_epoch # Choose the cloud with the lowest probability cloud_idx = int(np.argmin(self.min_possibility[self.split])) @@ -245,52 +243,3 @@ def dataloader(dataset_dir, **kwargs): ds.GeneratorDataset(val_sampler, ["xyz", "colors", "labels", "q_idx", "c_idx"], **kwargs), dataset - - -if __name__ == '__main__': - dir = Path('/home/ubuntu/hdd1/mqh/dataset/s3dis/train_0.040') - print('generating data loader....') - - train_ds, val_ds, dataset = dataloader(dir, val_area='Area_5', num_parallel_workers=8, shuffle=False) - - '''train_loader = train_ds.batch(batch_size=4, - per_batch_map=ms_map, - input_columns=["xyz","colors","labels","q_idx","c_idx"], - output_columns=["features","labels","input_inds","cloud_inds", - "p0","p1","p2","p3","p4", - "n0","n1","n2","n3","n4", - "pl0","pl1","pl2","pl3","pl4", - "u0","u1","u2","u3","u4"], - drop_remainder=True) - train_loader = train_loader.create_dict_iterator()''' - - val_loader = val_ds.batch(batch_size=cfg.val_batch_size, - per_batch_map=ms_map, - input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], - output_columns=["features", "labels", "input_inds", "cloud_inds", - "p0", "p1", "p2", "p3", "p4", - "n0", "n1", "n2", "n3", "n4", - "pl0", "pl1", "pl2", "pl3", "pl4", - "u0", "u1", "u2", "u3", "u4"], - drop_remainder=True) - val_loader = val_loader.create_dict_iterator() - for l in dataset.input_labels['validation']: - print(l.shape) - '''for i, data in enumerate(train_loader): - features = data['features'] - labels = data['labels'] - input_inds = data['input_inds'] - cloud_inds = data['cloud_inds'] - xyz = [data['p0'],data['p1'],data['p2'],data['p3'],data['p4']] - neigh_idx = [data['n0'],data['n1'],data['n2'],data['n3'],data['n4']] - sub_idx = [data['pl0'],data['pl1'],data['pl2'],data['pl3'],data['pl4']] - interp_idx = [data['u0'],data['u1'],data['u2'],data['u3'],data['u4']] - print("i: ",i," features.shape:", features.shape, - "\nlabels.shape:", labels.shape, - "\ninput_inds.shape:", input_inds.shape, - "\ncloud_inds.shape:", cloud_inds.shape, - "\nxyz[0].shape:", xyz[0].shape, - "\nneigh_idx[0].shape:", neigh_idx[0].shape, - "\nsub_idx[0].shape:", sub_idx[0].shape, - "\ninterp_idx[0].shape:", interp_idx[0].shape) - break''' diff --git a/research/cv/WS3/src/model/base_model.py b/research/cv/WS3/src/model/base_model.py index 5a598b27e..f61179dae 100644 --- a/research/cv/WS3/src/model/base_model.py +++ b/research/cv/WS3/src/model/base_model.py @@ -1,5 +1,4 @@ # -*-coding:utf-8-*- - import mindspore as ms import mindspore.nn as nn import mindspore.ops as P @@ -34,23 +33,24 @@ class SharedMLP(nn.Cell): has_bias=True, weight_init=TruncatedNormal(sigma=1e-3) ) - self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) if bn else None + self.has_bn = bn + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) self.activation_fn = activation_fn - def construct(self, input): + def construct(self, x): r""" construct method Parameters ---------- - input: ms.Tensor, shape (B, d_in, N, K) + x: ms.Tensor, shape (B, d_in, N, K) Returns ------- ms.Tensor, shape (B, d_out, N, K) """ - x = self.conv(input) - if self.batch_norm: + x = self.conv(x) + if self.has_bn: x = self.batch_norm(x) if self.activation_fn: x = self.activation_fn(x) @@ -122,8 +122,8 @@ class LocalSpatialEncoding(nn.Cell): if self.use_pos_encoding: return f_xyz, f_concat - else: - return f_concat + + return f_concat class AttentivePooling(nn.Cell): diff --git a/research/cv/WS3/src/model/base_model_remove_bias.py b/research/cv/WS3/src/model/base_model_remove_bias.py index f7a848506..29b2e68d7 100644 --- a/research/cv/WS3/src/model/base_model_remove_bias.py +++ b/research/cv/WS3/src/model/base_model_remove_bias.py @@ -1,6 +1,4 @@ # -*-coding:utf-8-*- - -import mindspore as ms import mindspore.nn as nn import mindspore.ops as P from mindspore.ops import composite as C @@ -9,6 +7,7 @@ from mindspore.ops import operations as op from mindspore.common.initializer import TruncatedNormal from mindspore import dtype as mstype + class SharedMLP(nn.Cell): def __init__( self, @@ -34,23 +33,24 @@ class SharedMLP(nn.Cell): has_bias=False, weight_init=TruncatedNormal(sigma=1e-3) ) - self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) if bn else None + self.has_bn = bn + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) self.activation_fn = activation_fn - def construct(self, input): + def construct(self, x): r""" construct method Parameters ---------- - input: ms.Tensor, shape (B, d_in, N, K) + x: ms.Tensor, shape (B, d_in, N, K) Returns ------- ms.Tensor, shape (B, d_out, N, K) """ - x = self.conv(input) - if self.batch_norm: + x = self.conv(x) + if self.has_bn: x = self.batch_norm(x) if self.activation_fn: x = self.activation_fn(x) @@ -122,8 +122,8 @@ class LocalSpatialEncoding(nn.Cell): if self.use_pos_encoding: return f_xyz, f_concat - else: - return f_concat + + return f_concat class AttentivePooling(nn.Cell): diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index 0318511b3..6b39a1c74 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -9,9 +9,9 @@ from mindspore import Tensor from .base_model import SharedMLP, LocalFeatureAggregation -class RandLANetS3DIS(nn.Cell): +class WS3(nn.Cell): def __init__(self, d_in, num_classes): - super(RandLANetS3DIS, self).__init__() + super(WS3, self).__init__() self.fc_start = nn.Dense(d_in, 8) self.bn_start = nn.SequentialCell([ @@ -126,11 +126,11 @@ class RandLANetS3DIS(nn.Cell): return pool_features -class RandLAS3DISWithLoss(nn.Cell): +class WS3WithLoss(nn.Cell): """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): - super(RandLAS3DISWithLoss, self).__init__() + super(WS3WithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float32) self.num_classes = num_classes diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index c70bb08b1..c65cb04ed 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -125,11 +125,11 @@ class WS3(nn.Cell): return pool_features -class RandLAS3DISWithLoss(nn.Cell): +class WS3WithLoss(nn.Cell): """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, topk): - super(RandLAS3DISWithLoss, self).__init__() + super(WS3WithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float16) self.num_classes = num_classes @@ -230,7 +230,7 @@ class RandLAS3DISWithLoss(nn.Cell): # => 求unlabelled points 与 class embedding的相似度 # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] # adj_matrix = self.double_feature(invalid_embed, mean_embed) - adj_matrix = RandLAS3DISWithLoss.double_feature(invalid_embed, mean_embed) + adj_matrix = WS3WithLoss.double_feature(invalid_embed, mean_embed) # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) neg_adj = -adj_matrix # (B*N,13) 取负 diff --git a/research/cv/WS3/src/utils/helper_ply.py b/research/cv/WS3/src/utils/helper_ply.py index d1b159539..296445dbd 100644 --- a/research/cv/WS3/src/utils/helper_ply.py +++ b/research/cv/WS3/src/utils/helper_ply.py @@ -113,7 +113,6 @@ def read_ply(filename, triangular_mesh=False): >>> data = read_ply('example.ply') >>> values = data['values'] array([0, 0, 1, 1, 0]) - >>> points = np.vstack((data['x'], data['y'], data['z'])).T array([[ 0.466 0.595 0.324] [ 0.538 0.407 0.654] @@ -127,7 +126,7 @@ def read_ply(filename, triangular_mesh=False): # Check if the file start with ply if b'ply' not in plyfile.readline(): - raise ValueError('The file does not start whith the word ply') + raise ValueError('The file does not start with the word ply') # get binary_little/big or ascii fmt = plyfile.readline().split()[1].decode() @@ -192,16 +191,16 @@ def write_ply(filename, field_list, field_names, triangular_faces=None): Parameters ---------- filename : string - the name of the file to which the data is saved. A '.ply' extension will be appended to the + the name of the file to which the data is saved. A '.ply' extension will be appended to the file name if it does no already have one. field_list : list, tuple, numpy array - the fields to be saved in the ply file. Either a numpy array, a list of numpy arrays or a - tuple of numpy arrays. Each 1D numpy array and each column of 2D numpy arrays are considered - as one field. + the fields to be saved in the ply file. Either a numpy array, a list of numpy arrays or a + tuple of numpy arrays. Each 1D numpy array and each column of 2D numpy arrays are considered + as one field. field_names : list - the name of each fields as a list of strings. Has to be the same length as the number of + the name of each fields as a list of strings. Has to be the same length as the number of fields. Examples @@ -219,7 +218,7 @@ def write_ply(filename, field_list, field_names, triangular_faces=None): """ # Format list input to the right form - field_list = list(field_list) if (isinstance(field_list, list) or isinstance(field_list, tuple)) else list( + field_list = list(field_list) if (isinstance(field_list, (list, tuple))) else list( (field_list,)) for i, field in enumerate(field_list): if field.ndim < 2: diff --git a/research/cv/WS3/src/utils/logger.py b/research/cv/WS3/src/utils/logger.py index 300a17cd9..eae0e5aec 100644 --- a/research/cv/WS3/src/utils/logger.py +++ b/research/cv/WS3/src/utils/logger.py @@ -82,4 +82,4 @@ def get_logger(path, rank): """Get Logger.""" logger = LOGGER('RandLa-net', rank) logger.setup_logging_file(path, rank) - return logger \ No newline at end of file + return logger diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py index c44159f1a..a03793464 100644 --- a/research/cv/WS3/src/utils/metrics.py +++ b/research/cv/WS3/src/utils/metrics.py @@ -61,9 +61,8 @@ def intersection_over_union(scores, labels): for label in range(num_classes): pred_mask = predictions == label labels_mask = labels == label - iou = msnp.logical_and(pred_mask, labels_mask).astype(ms.float32).sum() / msnp.logical_or(pred_mask, - labels_mask).astype( - ms.float32).sum() + iou = msnp.logical_and(pred_mask, labels_mask).astype(ms.float32).sum() / \ + msnp.logical_or(pred_mask, labels_mask).astype(ms.float32).sum() if label == 0: ious = iou else: diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index 9b831a2ed..9eb60d933 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -39,48 +39,75 @@ sys.path.append(os.path.join(BASE_DIR, 'utils')) # loss_scale = 1.0 # loss scale -class ConfigS3DIS(object): - def __init__(self): - self.k_n = 16 # KNN - self.num_layers = 5 # Number of layers - self.num_points = 40960 # Number of input points - self.num_classes = 13 # Number of valid classes - self.sub_grid_size = 0.04 # preprocess_parameter - - self.batch_size = 6 # batch_size during training - self.val_batch_size = 16 # batch_size during validation and test - self.train_steps = 500 # Number of steps per epochs - self.val_steps = 100 # Number of validation steps per epoch - - self.sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer - self.d_out = [16, 64, 128, 256, 512] # feature dimension - - self.noise_init = 3.5 # noise initial parameter - self.max_epoch = 80 # maximum epoch during training - self.learning_rate = 1e-2 # initial learning rate - # lr_decays = {i: 0.95 for i in range(0, 500)} # decay rate of learning rate - self.lr_decays = 0.95 # decay rate of learning rate - self.loss_scale = 1.0 # loss scale - - training_ep0 = {i: 0 for i in range(0, 30)} # - self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - self.training_ep.update(training_ep0) - - self.c_epoch = 0 - self.train_sum_dir = 'train_log' - self.saving = True - self.saving_path = None - - self.pretrain = True - self.checkpoint = './pretrain/snapshots/snap-11001' - self.topk = 500 - self.loss3_type = -1 - - -class DataProcessing(object): - def __init__(self): - pass - +# ConfigS3DIS = { +# 'dataset_dir': './', +# 'k_n': 16, # KNN +# 'num_layers': 5, # Number of layers +# 'num_points': 40960, # Number of input points +# 'num_classes': 13, # Number of valid classes +# 'sub_grid_size': 0.04, # preprocess_parameter +# 'batch_size': 6, # batch_size during training +# 'val_batch_size': 16, # batch_size during validation and test +# 'train_steps': 500, # Number of steps per epochs +# 'val_steps': 100, # Number of validation steps per epoch +# 'sub_sampling_ratio': [4, 4, 4, 4, 2], # sampling ratio of random sampling at each layer +# 'd_out': [16, 64, 128, 256, 512], # feature dimension +# 'noise_init': 3.5, # noise initial parameter +# 'max_epoch': 80, # maximum epoch during training +# 'learning_rate': 1e-2, # initial learning rate +# 'lr_decays': 0.95, # decay rate of learning rate +# 'loss_scale': 1.0, # loss scale +# 'training_ep0': {i: 0 for i in range(0, 30)}, +# 'training_ep': {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)}, +# 'c_epoch ': 0, +# 'train_sum_dir': 'train_log', +# 'saving ': True, +# 'saving_path': None, +# 'pretrain': True, +# 'checkpoint': './pretrain/snapshots/snap-11001', +# 'topk': 500, +# 'loss3_type': -1, +# } + + +class ConfigS3DIS: + k_n = 16 # KNN + num_layers = 5 # Number of layers + num_points = 40960 # Number of input points + num_classes = 13 # Number of valid classes + sub_grid_size = 0.04 # preprocess_parameter + + batch_size = 6 # batch_size during training + val_batch_size = 16 # batch_size during validation and test + train_steps = 500 # Number of steps per epochs + val_steps = 100 # Number of validation steps per epoch + + sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer + d_out = [16, 64, 128, 256, 512] # feature dimension + + noise_init = 3.5 # noise initial parameter + max_epoch = 80 # maximum epoch during training + learning_rate = 1e-2 # initial learning rate + decays = {i: 0.95 for i in range(0, 500)} # decay rate of learning rate + lr_decays = 0.95 # decay rate of learning rate + loss_scale = 1.0 # loss scale + + training_ep0 = {i: 0 for i in range(0, 30)} # + training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + training_ep.update(training_ep0) + + c_epoch = 0 + train_sum_dir = 'train_log' + saving = True + saving_path = None + + pretrain = True + checkpoint = './pretrain/snapshots/snap-11001' + topk = 500 + loss3_type = -1 + + +class DataProcessing: @staticmethod def knn_search(support_pts, query_pts, k): """ @@ -125,17 +152,17 @@ class DataProcessing(object): def get_class_weights(dataset_name): # pre-calculate the number of points in each category num_per_class = [] - if dataset_name is 'S3DIS': + if dataset_name == 'S3DIS': num_per_class = np.array([3370714, 2856755, 4919229, 318158, 375640, 478001, 974733, 650464, 791496, 88727, 1284130, 229758, 2272837], dtype=np.int32) - elif dataset_name is 'Semantic3D': + elif dataset_name == 'Semantic3D': num_per_class = np.array([5181602, 5012952, 6830086, 1311528, 10476365, 946982, 334860, 269353], dtype=np.int32) - elif dataset_name is 'SemanticKITTI': + elif dataset_name == 'SemanticKITTI': num_per_class = np.array([55437630, 320797, 541736, 2578735, 3274484, 552662, 184064, 78858, 240942562, 17294618, 170599734, 6369672, 230413074, 101130274, 476491114, 9833174, 129609852, 4506626, 1168181]) - elif dataset_name is 'ScanNet': + elif dataset_name == 'ScanNet': num_per_class = np.array( [13327131, 11989728, 1909991, 1291399, 3122500, 1197818, 1818311, 1942293, 1332850, 1000934, 227936, 244557, 892883, 800339, 246651, 125533, 134002, 110112, 162706, 1575880], dtype=np.int32) @@ -147,8 +174,8 @@ class DataProcessing(object): def IoU_from_confusions(confusions): """ Computes IoU from confusion matrices. - :param confusions: ([..., n_c, n_c] np.int32). Can be any dimension, the confusion matrices should be described by - the last axes. n_c = number of classes + :param confusions: ([..., n_c, n_c] np.int32). Can be any dimension, + the confusion matrices should be described by the last axes. n_c = number of classes :return: ([..., n_c] np.float32) IoU score """ @@ -182,12 +209,11 @@ class DataProcessing(object): :return: sub_sampled points, with features and/or labels depending of the input """ - if (features is None) and (labels is None): - return cpp_subsampling.compute(points, sampleDl=grid_size, verbose=verbose) - elif labels is None: - return cpp_subsampling.compute(points, features=features, sampleDl=grid_size, verbose=verbose) - elif features is None: - return cpp_subsampling.compute(points, classes=labels, sampleDl=grid_size, verbose=verbose) - else: - return cpp_subsampling.compute(points, features=features, classes=labels, sampleDl=grid_size, - verbose=verbose) + # if (features is None) and (labels is None): + # return cpp_subsampling.compute(points, sampleDl=grid_size, verbose=verbose) + # elif labels is None: + # return cpp_subsampling.compute(points, features=features, sampleDl=grid_size, verbose=verbose) + # elif features is None: + # return cpp_subsampling.compute(points, classes=labels, sampleDl=grid_size, verbose=verbose) + return cpp_subsampling.compute(points, features=features, classes=labels, sampleDl=grid_size, + verbose=verbose) diff --git a/research/cv/WS3/third_party/compile_op.sh b/research/cv/WS3/third_party/compile_op.sh index 2a7dda3b4..e66db472f 100644 --- a/research/cv/WS3/third_party/compile_op.sh +++ b/research/cv/WS3/third_party/compile_op.sh @@ -1,3 +1,5 @@ +#!/bin/sh +# shellcheck disable=SC2164 cd nearest_neighbors python setup.py install --home="." cd ../ diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp index 7c00396fe..8ea91626e 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp @@ -11,96 +11,96 @@ void grid_subsampling(vector& original_points, float sampleDl, int verbose) { - // Initiate variables - // ****************** - - // Number of points in the cloud - size_t N = original_points.size(); - - // Dimension of the features - size_t fdim = original_features.size() / N; - size_t ldim = original_classes.size() / N; - - // Limits of the cloud - PointXYZ minCorner = min_point(original_points); - PointXYZ maxCorner = max_point(original_points); - PointXYZ originCorner = floor(minCorner * (1/sampleDl)) * sampleDl; - - // Dimensions of the grid - size_t sampleNX = (size_t)floor((maxCorner.x - originCorner.x) / sampleDl) + 1; - size_t sampleNY = (size_t)floor((maxCorner.y - originCorner.y) / sampleDl) + 1; - //size_t sampleNZ = (size_t)floor((maxCorner.z - originCorner.z) / sampleDl) + 1; - - // Check if features and classes need to be processed - bool use_feature = original_features.size() > 0; - bool use_classes = original_classes.size() > 0; - - - // Create the sampled map - // ********************** - - // Verbose parameters - int i = 0; - int nDisp = N / 100; - - // Initiate variables - size_t iX, iY, iZ, mapIdx; - unordered_map data; - - for (auto& p : original_points) - { - // Position of point in sample map - iX = (size_t)floor((p.x - originCorner.x) / sampleDl); - iY = (size_t)floor((p.y - originCorner.y) / sampleDl); - iZ = (size_t)floor((p.z - originCorner.z) / sampleDl); - mapIdx = iX + sampleNX*iY + sampleNX*sampleNY*iZ; - - // If not already created, create key - if (data.count(mapIdx) < 1) - data.emplace(mapIdx, SampledData(fdim, ldim)); - - // Fill the sample map - if (use_feature && use_classes) - data[mapIdx].update_all(p, original_features.begin() + i * fdim, original_classes.begin() + i * ldim); - else if (use_feature) - data[mapIdx].update_features(p, original_features.begin() + i * fdim); - else if (use_classes) - data[mapIdx].update_classes(p, original_classes.begin() + i * ldim); - else - data[mapIdx].update_points(p); - - // Display - i++; - if (verbose > 1 && i%nDisp == 0) - std::cout << "\rSampled Map : " << std::setw(3) << i / nDisp << "%"; - - } - - // Divide for barycentre and transfer to a vector - subsampled_points.reserve(data.size()); - if (use_feature) - subsampled_features.reserve(data.size() * fdim); - if (use_classes) - subsampled_classes.reserve(data.size() * ldim); - for (auto& v : data) - { - subsampled_points.push_back(v.second.point * (1.0 / v.second.count)); - if (use_feature) - { - float count = (float)v.second.count; - transform(v.second.features.begin(), + // Initiate variables + // ****************** + + // Number of points in the cloud + size_t N = original_points.size(); + + // Dimension of the features + size_t fdim = original_features.size() / N; + size_t ldim = original_classes.size() / N; + + // Limits of the cloud + PointXYZ minCorner = min_point(original_points); + PointXYZ maxCorner = max_point(original_points); + PointXYZ originCorner = floor(minCorner * (1/sampleDl)) * sampleDl; + + // Dimensions of the grid + size_t sampleNX = (size_t)floor((maxCorner.x - originCorner.x) / sampleDl) + 1; + size_t sampleNY = (size_t)floor((maxCorner.y - originCorner.y) / sampleDl) + 1; + //size_t sampleNZ = (size_t)floor((maxCorner.z - originCorner.z) / sampleDl) + 1; + + // Check if features and classes need to be processed + bool use_feature = original_features.size() > 0; + bool use_classes = original_classes.size() > 0; + + + // Create the sampled map + // ********************** + + // Verbose parameters + int i = 0; + int nDisp = N / 100; + + // Initiate variables + size_t iX, iY, iZ, mapIdx; + unordered_map data; + + for (auto& p : original_points) + { + // Position of point in sample map + iX = (size_t)floor((p.x - originCorner.x) / sampleDl); + iY = (size_t)floor((p.y - originCorner.y) / sampleDl); + iZ = (size_t)floor((p.z - originCorner.z) / sampleDl); + mapIdx = iX + sampleNX*iY + sampleNX*sampleNY*iZ; + + // If not already created, create key + if (data.count(mapIdx) < 1) + data.emplace(mapIdx, SampledData(fdim, ldim)); + + // Fill the sample map + if (use_feature && use_classes) + data[mapIdx].update_all(p, original_features.begin() + i * fdim, original_classes.begin() + i * ldim); + else if (use_feature) + data[mapIdx].update_features(p, original_features.begin() + i * fdim); + else if (use_classes) + data[mapIdx].update_classes(p, original_classes.begin() + i * ldim); + else + data[mapIdx].update_points(p); + + // Display + i++; + if (verbose > 1 && i%nDisp == 0) + std::cout << "\rSampled Map : " << std::setw(3) << i / nDisp << "%"; + + } + + // Divide for barycentre and transfer to a vector + subsampled_points.reserve(data.size()); + if (use_feature) + subsampled_features.reserve(data.size() * fdim); + if (use_classes) + subsampled_classes.reserve(data.size() * ldim); + for (auto& v : data) + { + subsampled_points.push_back(v.second.point * (1.0 / v.second.count)); + if (use_feature) + { + float count = (float)v.second.count; + transform(v.second.features.begin(), v.second.features.end(), v.second.features.begin(), [count](float f) { return f / count;}); subsampled_features.insert(subsampled_features.end(),v.second.features.begin(),v.second.features.end()); - } - if (use_classes) - { - for (int i = 0; i < ldim; i++) - subsampled_classes.push_back(max_element(v.second.labels[i].begin(), v.second.labels[i].end(), - [](const pair&a, const pair&b){return a.second < b.second;})->first); - } - } - - return; + } + if (use_classes) + { + for (int i = 0; i < ldim; i++) + subsampled_classes.push_back(max_element(v.second.labels[i].begin(), v.second.labels[i].end(), + [](const pair&a, const pair&b){return a.second < b.second;})->first); + } + } + + return; } diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h index b1c84d1b3..7ebbba229 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h @@ -11,72 +11,72 @@ class SampledData { public: - // Elements - // ******** - - int count; - PointXYZ point; - vector features; - vector> labels; - - - // Methods - // ******* - - // Constructor - SampledData() - { - count = 0; - point = PointXYZ(); - } - - SampledData(const size_t fdim, const size_t ldim) - { - count = 0; - point = PointXYZ(); - features = vector(fdim); - labels = vector>(ldim); - } - - // Method Update - void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_features(const PointXYZ p, vector::iterator f_begin) - { - count += 1; - point += p; - transform (features.begin(), features.end(), f_begin, features.begin(), plus()); - return; - } - void update_classes(const PointXYZ p, vector::iterator l_begin) - { - count += 1; - point += p; - int i = 0; - for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) - { - labels[i][*it] += 1; - i++; - } - return; - } - void update_points(const PointXYZ p) - { - count += 1; - point += p; - return; - } + // Elements + // ******** + + int count; + PointXYZ point; + vector features; + vector> labels; + + + // Methods + // ******* + + // Constructor + SampledData() + { + count = 0; + point = PointXYZ(); + } + + SampledData(const size_t fdim, const size_t ldim) + { + count = 0; + point = PointXYZ(); + features = vector(fdim); + labels = vector>(ldim); + } + + // Method Update + void update_all(const PointXYZ p, vector::iterator f_begin, vector::iterator l_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_features(const PointXYZ p, vector::iterator f_begin) + { + count += 1; + point += p; + transform (features.begin(), features.end(), f_begin, features.begin(), plus()); + return; + } + void update_classes(const PointXYZ p, vector::iterator l_begin) + { + count += 1; + point += p; + int i = 0; + for(vector::iterator it = l_begin; it != l_begin + labels.size(); ++it) + { + labels[i][*it] += 1; + i++; + } + return; + } + void update_points(const PointXYZ p) + { + count += 1; + point += p; + return; + } }; diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp index f879059eb..fbe159e2e 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp @@ -24,7 +24,7 @@ static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObje static PyMethodDef module_methods[] = { - { "compute", (PyCFunction)grid_subsampling_compute, METH_VARARGS | METH_KEYWORDS, compute_docstring }, + { "compute", (PyCFunction)grid_subsampling_compute, METH_VARARGS | METH_KEYWORDS, compute_docstring }, {NULL, NULL, 0, NULL} }; @@ -48,7 +48,7 @@ static struct PyModuleDef moduledef = PyMODINIT_FUNC PyInit_grid_subsampling(void) { import_array(); - return PyModule_Create(&moduledef); + return PyModule_Create(&moduledef); } @@ -61,133 +61,133 @@ static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObje // Manage inputs // ************* - // Args containers - PyObject *points_obj = NULL; - PyObject *features_obj = NULL; - PyObject *classes_obj = NULL; - - // Keywords containers - static char *kwlist[] = {"points", "features", "classes", "sampleDl", "method", "verbose", NULL }; - float sampleDl = 0.1; - const char *method_buffer = "barycenters"; - int verbose = 0; - - // Parse the input - if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|$OOfsi", kwlist, &points_obj, &features_obj, &classes_obj, &sampleDl, &method_buffer, &verbose)) - { - PyErr_SetString(PyExc_RuntimeError, "Error parsing arguments"); - return NULL; - } - - // Get the method argument - string method(method_buffer); - - // Interpret method - if (method.compare("barycenters") && method.compare("voxelcenters")) - { - PyErr_SetString(PyExc_RuntimeError, "Error parsing method. Valid method names are \"barycenters\" and \"voxelcenters\" "); - return NULL; - } - - // Check if using features or classes - bool use_feature = true, use_classes = true; - if (features_obj == NULL) - use_feature = false; - if (classes_obj == NULL) - use_classes = false; + // Args containers + PyObject *points_obj = NULL; + PyObject *features_obj = NULL; + PyObject *classes_obj = NULL; + + // Keywords containers + static char *kwlist[] = {"points", "features", "classes", "sampleDl", "method", "verbose", NULL }; + float sampleDl = 0.1; + const char *method_buffer = "barycenters"; + int verbose = 0; + + // Parse the input + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|$OOfsi", kwlist, &points_obj, &features_obj, &classes_obj, &sampleDl, &method_buffer, &verbose)) + { + PyErr_SetString(PyExc_RuntimeError, "Error parsing arguments"); + return NULL; + } + + // Get the method argument + string method(method_buffer); + + // Interpret method + if (method.compare("barycenters") && method.compare("voxelcenters")) + { + PyErr_SetString(PyExc_RuntimeError, "Error parsing method. Valid method names are \"barycenters\" and \"voxelcenters\" "); + return NULL; + } + + // Check if using features or classes + bool use_feature = true, use_classes = true; + if (features_obj == NULL) + use_feature = false; + if (classes_obj == NULL) + use_classes = false; // Interpret the input objects as numpy arrays. - PyObject *points_array = PyArray_FROM_OTF(points_obj, NPY_FLOAT, NPY_IN_ARRAY); - PyObject *features_array = NULL; - PyObject *classes_array = NULL; - if (use_feature) - features_array = PyArray_FROM_OTF(features_obj, NPY_FLOAT, NPY_IN_ARRAY); - if (use_classes) - classes_array = PyArray_FROM_OTF(classes_obj, NPY_INT, NPY_IN_ARRAY); - - // Verify data was load correctly. - if (points_array == NULL) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Error converting input points to numpy arrays of type float32"); - return NULL; - } - if (use_feature && features_array == NULL) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Error converting input features to numpy arrays of type float32"); - return NULL; - } - if (use_classes && classes_array == NULL) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Error converting input classes to numpy arrays of type int32"); - return NULL; - } + PyObject *points_array = PyArray_FROM_OTF(points_obj, NPY_FLOAT, NPY_IN_ARRAY); + PyObject *features_array = NULL; + PyObject *classes_array = NULL; + if (use_feature) + features_array = PyArray_FROM_OTF(features_obj, NPY_FLOAT, NPY_IN_ARRAY); + if (use_classes) + classes_array = PyArray_FROM_OTF(classes_obj, NPY_INT, NPY_IN_ARRAY); + + // Verify data was load correctly. + if (points_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input points to numpy arrays of type float32"); + return NULL; + } + if (use_feature && features_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input features to numpy arrays of type float32"); + return NULL; + } + if (use_classes && classes_array == NULL) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Error converting input classes to numpy arrays of type int32"); + return NULL; + } // Check that the input array respect the dims - if ((int)PyArray_NDIM(points_array) != 2 || (int)PyArray_DIM(points_array, 1) != 3) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : points.shape is not (N, 3)"); - return NULL; - } - if (use_feature && ((int)PyArray_NDIM(features_array) != 2)) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); - return NULL; - } - - if (use_classes && (int)PyArray_NDIM(classes_array) > 2) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); - return NULL; - } - - // Number of points - int N = (int)PyArray_DIM(points_array, 0); - - // Dimension of the features - int fdim = 0; - if (use_feature) - fdim = (int)PyArray_DIM(features_array, 1); - - //Dimension of labels - int ldim = 1; - if (use_classes && (int)PyArray_NDIM(classes_array) == 2) - ldim = (int)PyArray_DIM(classes_array, 1); - - // Check that the input array respect the number of points - if (use_feature && (int)PyArray_DIM(features_array, 0) != N) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); - return NULL; - } - if (use_classes && (int)PyArray_DIM(classes_array, 0) != N) - { - Py_XDECREF(points_array); - Py_XDECREF(classes_array); - Py_XDECREF(features_array); - PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); - return NULL; - } + if ((int)PyArray_NDIM(points_array) != 2 || (int)PyArray_DIM(points_array, 1) != 3) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : points.shape is not (N, 3)"); + return NULL; + } + if (use_feature && ((int)PyArray_NDIM(features_array) != 2)) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); + return NULL; + } + + if (use_classes && (int)PyArray_NDIM(classes_array) > 2) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); + return NULL; + } + + // Number of points + int N = (int)PyArray_DIM(points_array, 0); + + // Dimension of the features + int fdim = 0; + if (use_feature) + fdim = (int)PyArray_DIM(features_array, 1); + + //Dimension of labels + int ldim = 1; + if (use_classes && (int)PyArray_NDIM(classes_array) == 2) + ldim = (int)PyArray_DIM(classes_array, 1); + + // Check that the input array respect the number of points + if (use_feature && (int)PyArray_DIM(features_array, 0) != N) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : features.shape is not (N, d)"); + return NULL; + } + if (use_classes && (int)PyArray_DIM(classes_array, 0) != N) + { + Py_XDECREF(points_array); + Py_XDECREF(classes_array); + Py_XDECREF(features_array); + PyErr_SetString(PyExc_RuntimeError, "Wrong dimensions : classes.shape is not (N,) or (N, d)"); + return NULL; + } // Call the C++ function @@ -199,20 +199,20 @@ static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObje // Convert PyArray to Cloud C++ class - vector original_points; - vector original_features; - vector original_classes; - original_points = vector((PointXYZ*)PyArray_DATA(points_array), (PointXYZ*)PyArray_DATA(points_array) + N); - if (use_feature) - original_features = vector((float*)PyArray_DATA(features_array), (float*)PyArray_DATA(features_array) + N*fdim); - if (use_classes) - original_classes = vector((int*)PyArray_DATA(classes_array), (int*)PyArray_DATA(classes_array) + N*ldim); + vector original_points; + vector original_features; + vector original_classes; + original_points = vector((PointXYZ*)PyArray_DATA(points_array), (PointXYZ*)PyArray_DATA(points_array) + N); + if (use_feature) + original_features = vector((float*)PyArray_DATA(features_array), (float*)PyArray_DATA(features_array) + N*fdim); + if (use_classes) + original_classes = vector((int*)PyArray_DATA(classes_array), (int*)PyArray_DATA(classes_array) + N*ldim); // Subsample - vector subsampled_points; - vector subsampled_features; - vector subsampled_classes; - grid_subsampling(original_points, + vector subsampled_points; + vector subsampled_features; + vector subsampled_classes; + grid_subsampling(original_points, subsampled_points, original_features, subsampled_features, @@ -222,7 +222,7 @@ static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObje verbose); // Check result - if (subsampled_points.size() < 1) + if (subsampled_points.size() < 1) { PyErr_SetString(PyExc_RuntimeError, "Error"); return NULL; @@ -231,56 +231,56 @@ static PyObject *grid_subsampling_compute(PyObject *self, PyObject *args, PyObje // Manage outputs // ************** - // Dimension of input containers - npy_intp* point_dims = new npy_intp[2]; - point_dims[0] = subsampled_points.size(); - point_dims[1] = 3; - npy_intp* feature_dims = new npy_intp[2]; - feature_dims[0] = subsampled_points.size(); - feature_dims[1] = fdim; - npy_intp* classes_dims = new npy_intp[2]; - classes_dims[0] = subsampled_points.size(); - classes_dims[1] = ldim; + // Dimension of input containers + npy_intp* point_dims = new npy_intp[2]; + point_dims[0] = subsampled_points.size(); + point_dims[1] = 3; + npy_intp* feature_dims = new npy_intp[2]; + feature_dims[0] = subsampled_points.size(); + feature_dims[1] = fdim; + npy_intp* classes_dims = new npy_intp[2]; + classes_dims[0] = subsampled_points.size(); + classes_dims[1] = ldim; // Create output array - PyObject *res_points_obj = PyArray_SimpleNew(2, point_dims, NPY_FLOAT); - PyObject *res_features_obj = NULL; - PyObject *res_classes_obj = NULL; - PyObject *ret = NULL; - - // Fill output array with values - size_t size_in_bytes = subsampled_points.size() * 3 * sizeof(float); - memcpy(PyArray_DATA(res_points_obj), subsampled_points.data(), size_in_bytes); - if (use_feature) - { - size_in_bytes = subsampled_points.size() * fdim * sizeof(float); - res_features_obj = PyArray_SimpleNew(2, feature_dims, NPY_FLOAT); - memcpy(PyArray_DATA(res_features_obj), subsampled_features.data(), size_in_bytes); - } - if (use_classes) - { - size_in_bytes = subsampled_points.size() * ldim * sizeof(int); - res_classes_obj = PyArray_SimpleNew(2, classes_dims, NPY_INT); - memcpy(PyArray_DATA(res_classes_obj), subsampled_classes.data(), size_in_bytes); - } - - - // Merge results - if (use_feature && use_classes) - ret = Py_BuildValue("NNN", res_points_obj, res_features_obj, res_classes_obj); - else if (use_feature) - ret = Py_BuildValue("NN", res_points_obj, res_features_obj); - else if (use_classes) - ret = Py_BuildValue("NN", res_points_obj, res_classes_obj); - else - ret = Py_BuildValue("N", res_points_obj); + PyObject *res_points_obj = PyArray_SimpleNew(2, point_dims, NPY_FLOAT); + PyObject *res_features_obj = NULL; + PyObject *res_classes_obj = NULL; + PyObject *ret = NULL; + + // Fill output array with values + size_t size_in_bytes = subsampled_points.size() * 3 * sizeof(float); + memcpy(PyArray_DATA(res_points_obj), subsampled_points.data(), size_in_bytes); + if (use_feature) + { + size_in_bytes = subsampled_points.size() * fdim * sizeof(float); + res_features_obj = PyArray_SimpleNew(2, feature_dims, NPY_FLOAT); + memcpy(PyArray_DATA(res_features_obj), subsampled_features.data(), size_in_bytes); + } + if (use_classes) + { + size_in_bytes = subsampled_points.size() * ldim * sizeof(int); + res_classes_obj = PyArray_SimpleNew(2, classes_dims, NPY_INT); + memcpy(PyArray_DATA(res_classes_obj), subsampled_classes.data(), size_in_bytes); + } + + + // Merge results + if (use_feature && use_classes) + ret = Py_BuildValue("NNN", res_points_obj, res_features_obj, res_classes_obj); + else if (use_feature) + ret = Py_BuildValue("NN", res_points_obj, res_features_obj); + else if (use_classes) + ret = Py_BuildValue("NN", res_points_obj, res_classes_obj); + else + ret = Py_BuildValue("N", res_points_obj); // Clean up // ******** - Py_DECREF(points_array); - Py_XDECREF(features_array); - Py_XDECREF(classes_array); + Py_DECREF(points_array); + Py_XDECREF(features_array); + Py_XDECREF(classes_array); - return ret; + return ret; } \ No newline at end of file diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp index bdb65679f..10265bed5 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp @@ -1,23 +1,3 @@ -// -// -// 0==========================0 -// | Local feature test | -// 0==========================0 -// -// version 1.0 : -// > -// -//--------------------------------------------------- -// -// Cloud source : -// Define usefull Functions/Methods -// -//---------------------------------------------------- -// -// Hugues THOMAS - 10/02/2017 -// - - #include "cloud.h" @@ -26,42 +6,42 @@ PointXYZ max_point(std::vector points) { - // Initiate limits - PointXYZ maxP(points[0]); + // Initiate limits + PointXYZ maxP(points[0]); - // Loop over all points - for (auto p : points) - { - if (p.x > maxP.x) - maxP.x = p.x; + // Loop over all points + for (auto p : points) + { + if (p.x > maxP.x) + maxP.x = p.x; - if (p.y > maxP.y) - maxP.y = p.y; + if (p.y > maxP.y) + maxP.y = p.y; - if (p.z > maxP.z) - maxP.z = p.z; - } + if (p.z > maxP.z) + maxP.z = p.z; + } - return maxP; + return maxP; } PointXYZ min_point(std::vector points) { - // Initiate limits - PointXYZ minP(points[0]); + // Initiate limits + PointXYZ minP(points[0]); - // Loop over all points - for (auto p : points) - { - if (p.x < minP.x) - minP.x = p.x; + // Loop over all points + for (auto p : points) + { + if (p.x < minP.x) + minP.x = p.x; - if (p.y < minP.y) - minP.y = p.y; + if (p.y < minP.y) + minP.y = p.y; - if (p.z < minP.z) - minP.z = p.z; - } + if (p.z < minP.z) + minP.z = p.z; + } - return minP; + return minP; } \ No newline at end of file diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h index 39ab05b68..d2bc13f3d 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h @@ -1,22 +1,3 @@ -// -// -// 0==========================0 -// | Local feature test | -// 0==========================0 -// -// version 1.0 : -// > -// -//--------------------------------------------------- -// -// Cloud header -// -//---------------------------------------------------- -// -// Hugues THOMAS - 10/02/2017 -// - - # pragma once #include @@ -41,66 +22,66 @@ class PointXYZ { public: - // Elements - // ******** - - float x, y, z; - - - // Methods - // ******* - - // Constructor - PointXYZ() { x = 0; y = 0; z = 0; } - PointXYZ(float x0, float y0, float z0) { x = x0; y = y0; z = z0; } - - // array type accessor - float operator [] (int i) const - { - if (i == 0) return x; - else if (i == 1) return y; - else return z; - } - - // opperations - float dot(const PointXYZ P) const - { - return x * P.x + y * P.y + z * P.z; - } - - float sq_norm() - { - return x*x + y*y + z*z; - } - - PointXYZ cross(const PointXYZ P) const - { - return PointXYZ(y*P.z - z*P.y, z*P.x - x*P.z, x*P.y - y*P.x); - } - - PointXYZ& operator+=(const PointXYZ& P) - { - x += P.x; - y += P.y; - z += P.z; - return *this; - } - - PointXYZ& operator-=(const PointXYZ& P) - { - x -= P.x; - y -= P.y; - z -= P.z; - return *this; - } - - PointXYZ& operator*=(const float& a) - { - x *= a; - y *= a; - z *= a; - return *this; - } + // Elements + // ******** + + float x, y, z; + + + // Methods + // ******* + + // Constructor + PointXYZ() { x = 0; y = 0; z = 0; } + PointXYZ(float x0, float y0, float z0) { x = x0; y = y0; z = z0; } + + // array type accessor + float operator [] (int i) const + { + if (i == 0) return x; + else if (i == 1) return y; + else return z; + } + + // opperations + float dot(const PointXYZ P) const + { + return x * P.x + y * P.y + z * P.z; + } + + float sq_norm() + { + return x*x + y*y + z*z; + } + + PointXYZ cross(const PointXYZ P) const + { + return PointXYZ(y*P.z - z*P.y, z*P.x - x*P.z, x*P.y - y*P.x); + } + + PointXYZ& operator+=(const PointXYZ& P) + { + x += P.x; + y += P.y; + z += P.z; + return *this; + } + + PointXYZ& operator-=(const PointXYZ& P) + { + x -= P.x; + y -= P.y; + z -= P.z; + return *this; + } + + PointXYZ& operator*=(const float& a) + { + x *= a; + y *= a; + z *= a; + return *this; + } }; @@ -109,37 +90,37 @@ public: inline PointXYZ operator + (const PointXYZ A, const PointXYZ B) { - return PointXYZ(A.x + B.x, A.y + B.y, A.z + B.z); + return PointXYZ(A.x + B.x, A.y + B.y, A.z + B.z); } inline PointXYZ operator - (const PointXYZ A, const PointXYZ B) { - return PointXYZ(A.x - B.x, A.y - B.y, A.z - B.z); + return PointXYZ(A.x - B.x, A.y - B.y, A.z - B.z); } inline PointXYZ operator * (const PointXYZ P, const float a) { - return PointXYZ(P.x * a, P.y * a, P.z * a); + return PointXYZ(P.x * a, P.y * a, P.z * a); } inline PointXYZ operator * (const float a, const PointXYZ P) { - return PointXYZ(P.x * a, P.y * a, P.z * a); + return PointXYZ(P.x * a, P.y * a, P.z * a); } inline std::ostream& operator << (std::ostream& os, const PointXYZ P) { - return os << "[" << P.x << ", " << P.y << ", " << P.z << "]"; + return os << "[" << P.x << ", " << P.y << ", " << P.z << "]"; } inline bool operator == (const PointXYZ A, const PointXYZ B) { - return A.x == B.x && A.y == B.y && A.z == B.z; + return A.x == B.x && A.y == B.y && A.z == B.z; } inline PointXYZ floor(const PointXYZ P) { - return PointXYZ(std::floor(P.x), std::floor(P.y), std::floor(P.z)); + return PointXYZ(std::floor(P.x), std::floor(P.y), std::floor(P.z)); } diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp index 8d2ab6cc6..dd208e3e8 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp @@ -602,7 +602,7 @@ class PooledAllocator { /* We maintain memory alignment to word boundaries by requiring that all allocations be in multiples of the machine wordsize. */ /* Size of machine word in bytes. Must be power of 2. */ - /* Minimum number of bytes requested at a time from the system. Must be + /* Minimum number of bytes requested at a time from the system. Must be * multiple of WORDSIZE. */ size_t remaining; /* Number of bytes left in current block of storage. */ @@ -1941,10 +1941,10 @@ public: * * Example of usage: * \code - * Eigen::Matrix mat; - * // Fill out "mat"... + * Eigen::Matrix mat; + * // Fill out "mat"... * - * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > + * typedef KDTreeEigenMatrixAdaptor> * my_kd_tree_t; const int max_leaf = 10; my_kd_tree_t mat_index(mat, max_leaf * ); mat_index.index->buildIndex(); mat_index.index->... \endcode * diff --git a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h index 8cdc787fb..e0b7aeed4 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h +++ b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h @@ -121,69 +121,69 @@ template struct KDTreeTableAdaptor { - typedef KDTreeTableAdaptor self_t; - typedef typename Distance::template traits::distance_t metric_t; - typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; - - index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. - size_t dim; - size_t npts; - const TableType* m_data; - - /// Constructor: takes a const ref to the vector of vectors object with the data points - KDTreeTableAdaptor(const size_t npts, const size_t dim, const TableType* mat, const int leaf_max_size = 10) : m_data(mat), dim(dim), npts(npts) - { - assert(npts != 0); - index = new index_t( static_cast(dim), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); - index->buildIndex(); - } - - ~KDTreeTableAdaptor() { - delete index; - } - - - /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). - * Note that this is a short-cut method for index->findNeighbors(). - * The user can also call index->... methods as desired. - * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. - */ - inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const - { - nanoflann::KNNResultSet resultSet(num_closest); - resultSet.init(out_indices, out_distances_sq); - index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); - } - - /** @name Interface expected by KDTreeSingleIndexAdaptor - * @{ */ - - const self_t & derived() const { - return *this; - } - self_t & derived() { - return *this; - } - - // Must return the number of data points - inline size_t kdtree_get_point_count() const { - return this->npts; - } - - // Returns the dim'th component of the idx'th point in the class: - inline num_t kdtree_get_pt(const size_t pts_id, const size_t coord_id) const { - return m_data[pts_id*this->dim + coord_id]; - } - - // Optional bounding-box computation: return false to default to a standard bbox computation loop. - // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. - // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) - template - bool kdtree_get_bbox(BBOX & /*bb*/) const { - return false; - } - - /** @} */ + typedef KDTreeTableAdaptor self_t; + typedef typename Distance::template traits::distance_t metric_t; + typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; + + index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. + size_t dim; + size_t npts; + const TableType* m_data; + + /// Constructor: takes a const ref to the vector of vectors object with the data points + KDTreeTableAdaptor(const size_t npts, const size_t dim, const TableType* mat, const int leaf_max_size = 10) : m_data(mat), dim(dim), npts(npts) + { + assert(npts != 0); + index = new index_t( static_cast(dim), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); + index->buildIndex(); + } + + ~KDTreeTableAdaptor() { + delete index; + } + + + /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). + * Note that this is a short-cut method for index->findNeighbors(). + * The user can also call index->... methods as desired. + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + */ + inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t & derived() const { + return *this; + } + self_t & derived() { + return *this; + } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return this->npts; + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const size_t pts_id, const size_t coord_id) const { + return m_data[pts_id*this->dim + coord_id]; + } + + // Optional bounding-box computation: return false to default to a standard bbox computation loop. + // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + bool kdtree_get_bbox(BBOX & /*bb*/) const { + return false; + } + + /** @} */ }; // end of KDTreeVectorOfVectorsAdaptor diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp index 7b99a91bc..db35d0ef2 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp +++ b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp @@ -1806,7 +1806,7 @@ static PyObject *__pyx_codeobj__13; /* Late includes */ /* "knn.pyx":33 - * const size_t K, long* batch_indices) + * const size_t K, long* batch_indices) * * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< * diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn.pyx b/research/cv/WS3/third_party/nearest_neighbors/knn.pyx index 8fc30980f..73259860b 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn.pyx +++ b/research/cv/WS3/third_party/nearest_neighbors/knn.pyx @@ -7,8 +7,8 @@ import cython cdef extern from "knn_.h": void cpp_knn(const float* points, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* indices) + const float* queries, const size_t nqueries, + const size_t K, long* indices) void cpp_knn_omp(const float* points, const size_t npts, const size_t dim, const float* queries, const size_t nqueries, @@ -27,8 +27,8 @@ cdef extern from "knn_.h": const size_t K, long* batch_indices) void cpp_knn_batch_distance_pick_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, - float* batch_queries, const size_t nqueries, - const size_t K, long* batch_indices) + float* batch_queries, const size_t nqueries, + const size_t K, long* batch_indices) def knn(pts, queries, K, omp=False): diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx b/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx index 58a359559..c59aff875 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx +++ b/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx @@ -20,252 +20,252 @@ using namespace std; void cpp_knn(const float* points, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* indices){ - - // create the kdtree - typedef KDTreeTableAdaptor< float, float> KDTree; - KDTree mat_index(npts, dim, points, 10); - mat_index.index->buildIndex(); - - std::vector out_dists_sqr(K); - std::vector out_ids(K); - - // iterate over the points - for(size_t i=0; i resultSet(K); - resultSet.init(&out_ids[0], &out_dists_sqr[0] ); - mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); - for(size_t j=0; j KDTree; + KDTree mat_index(npts, dim, points, 10); + mat_index.index->buildIndex(); + + std::vector out_dists_sqr(K); + std::vector out_ids(K); + + // iterate over the points + for(size_t i=0; i resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; - KDTree mat_index(npts, dim, points, 10); - mat_index.index->buildIndex(); + // create the kdtree + typedef KDTreeTableAdaptor< float, float> KDTree; + KDTree mat_index(npts, dim, points, 10); + mat_index.index->buildIndex(); - // iterate over the points + // iterate over the points # pragma omp parallel for - for(size_t i=0; i out_ids(K); - std::vector out_dists_sqr(K); - - nanoflann::KNNResultSet resultSet(K); - resultSet.init(&out_ids[0], &out_dists_sqr[0] ); - mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); - for(size_t j=0; j out_ids(K); + std::vector out_dists_sqr(K); + + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; - KDTree mat_index(npts, dim, points, 10); - - mat_index.index->buildIndex(); + // create the kdtree + typedef KDTreeTableAdaptor< float, float> KDTree; + KDTree mat_index(npts, dim, points, 10); - std::vector out_dists_sqr(K); - std::vector out_ids(K); + mat_index.index->buildIndex(); - // iterate over the points - for(size_t i=0; i resultSet(K); - resultSet.init(&out_ids[0], &out_dists_sqr[0] ); - mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); - for(size_t j=0; j out_dists_sqr(K); + std::vector out_ids(K); - } + // iterate over the points + for(size_t i=0; i resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; + KDTree mat_index(npts, dim, points, 10); - // create the kdtree - typedef KDTreeTableAdaptor< float, float> KDTree; - KDTree mat_index(npts, dim, points, 10); - - mat_index.index->buildIndex(); + mat_index.index->buildIndex(); - std::vector out_dists_sqr(K); - std::vector out_ids(K); + std::vector out_dists_sqr(K); + std::vector out_ids(K); - // iterate over the points - for(size_t i=0; i resultSet(K); - resultSet.init(&out_ids[0], &out_dists_sqr[0] ); - mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); - for(size_t j=0; j resultSet(K); + resultSet.init(&out_ids[0], &out_dists_sqr[0] ); + mat_index.index->findNeighbors(resultSet, &queries[bid*nqueries*dim + i*dim], nanoflann::SearchParams(10)); + for(size_t j=0; j KDTree; - KDTree tree(npts, dim, points, 10); - tree.index->buildIndex(); - - vector used(npts, 0); - int current_id = 0; - for(size_t ptid=0; ptid possible_ids; - while(possible_ids.size() == 0){ - for(size_t i=0; i query(3); - for(size_t i=0; i dists(K); - std::vector ids(K); - nanoflann::KNNResultSet resultSet(K); - resultSet.init(&ids[0], &dists[0] ); - tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); - - for(size_t i=0; i KDTree; + KDTree tree(npts, dim, points, 10); + tree.index->buildIndex(); + + vector used(npts, 0); + int current_id = 0; + for(size_t ptid=0; ptid possible_ids; + while(possible_ids.size() == 0){ + for(size_t i=0; i query(3); + for(size_t i=0; i dists(K); + std::vector ids(K); + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&ids[0], &dists[0] ); + tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); + + for(size_t i=0; i KDTree; - KDTree tree(npts, dim, points, 10); - tree.index->buildIndex(); - - vector used(npts, 0); - int current_id = 0; - for(size_t ptid=0; ptid possible_ids; - while(possible_ids.size() == 0){ - for(size_t i=0; i query(3); - for(size_t i=0; i dists(K); - std::vector ids(K); - nanoflann::KNNResultSet resultSet(K); - resultSet.init(&ids[0], &dists[0] ); - tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); - - for(size_t i=0; i KDTree; + KDTree tree(npts, dim, points, 10); + tree.index->buildIndex(); + + vector used(npts, 0); + int current_id = 0; + for(size_t ptid=0; ptid possible_ids; + while(possible_ids.size() == 0){ + for(size_t i=0; i query(3); + for(size_t i=0; i dists(K); + std::vector ids(K); + nanoflann::KNNResultSet resultSet(K); + resultSet.init(&ids[0], &dists[0] ); + tree.index->findNeighbors(resultSet, &query[0], nanoflann::SearchParams(10)); + + for(size_t i=0; i void cpp_knn(const float* points, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* indices); + const float* queries, const size_t nqueries, + const size_t K, long* indices); void cpp_knn_omp(const float* points, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* indices); + const float* queries, const size_t nqueries, + const size_t K, long* indices); void cpp_knn_batch(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* batch_indices); + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices); void cpp_knn_batch_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, - const float* queries, const size_t nqueries, - const size_t K, long* batch_indices); + const float* queries, const size_t nqueries, + const size_t K, long* batch_indices); void cpp_knn_batch_distance_pick(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, - float* queries, const size_t nqueries, - const size_t K, long* batch_indices); + float* queries, const size_t nqueries, + const size_t K, long* batch_indices); void cpp_knn_batch_distance_pick_omp(const float* batch_data, const size_t batch_size, const size_t npts, const size_t dim, - float* batch_queries, const size_t nqueries, - const size_t K, long* batch_indices); \ No newline at end of file + float* batch_queries, const size_t nqueries, + const size_t K, long* batch_indices); \ No newline at end of file diff --git a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp index 45c185bb4..a98586dc8 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp +++ b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp @@ -70,42 +70,42 @@ namespace nanoflann /** @addtogroup nanoflann_grp nanoflann C++ library for ANN * @{ */ - /** Library version: 0xMmP (M=Major,m=minor,P=patch) */ - #define NANOFLANN_VERSION 0x123 - - /** @addtogroup result_sets_grp Result set classes - * @{ */ - template - class KNNResultSet - { - IndexType * indices; - DistanceType* dists; - CountType capacity; - CountType count; - - public: - inline KNNResultSet(CountType capacity_) : indices(0), dists(0), capacity(capacity_), count(0) - { - } - - inline void init(IndexType* indices_, DistanceType* dists_) - { - indices = indices_; - dists = dists_; - count = 0; + /** Library version: 0xMmP (M=Major,m=minor,P=patch) */ + #define NANOFLANN_VERSION 0x123 + + /** @addtogroup result_sets_grp Result set classes + * @{ */ + template + class KNNResultSet + { + IndexType * indices; + DistanceType* dists; + CountType capacity; + CountType count; + + public: + inline KNNResultSet(CountType capacity_) : indices(0), dists(0), capacity(capacity_), count(0) + { + } + + inline void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; if (capacity) dists[capacity-1] = (std::numeric_limits::max)(); - } + } - inline CountType size() const - { - return count; - } + inline CountType size() const + { + return count; + } - inline bool full() const - { - return count == capacity; - } + inline bool full() const + { + return count == capacity; + } /** @@ -113,587 +113,587 @@ namespace nanoflann * @return true if the search should be continued, false if the results are sufficient */ inline bool addPoint(DistanceType dist, IndexType index) - { - CountType i; - for (i = count; i > 0; --i) { + { + CountType i; + for (i = count; i > 0; --i) { #ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same distance, the one with the lowest-index will be returned first. - if ( (dists[i-1] > dist) || ((dist == dists[i-1]) && (indices[i-1] > index)) ) { + if ( (dists[i-1] > dist) || ((dist == dists[i-1]) && (indices[i-1] > index)) ) { #else - if (dists[i-1] > dist) { + if (dists[i-1] > dist) { #endif - if (i < capacity) { - dists[i] = dists[i-1]; - indices[i] = indices[i-1]; - } - } - else break; - } - if (i < capacity) { - dists[i] = dist; - indices[i] = index; - } - if (count < capacity) count++; + if (i < capacity) { + dists[i] = dists[i-1]; + indices[i] = indices[i-1]; + } + } + else break; + } + if (i < capacity) { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; // tell caller that the search shall continue return true; - } - - inline DistanceType worstDist() const - { - return dists[capacity-1]; - } - }; - - /** operator "<" for std::sort() */ - struct IndexDist_Sorter - { - /** PairType will be typically: std::pair */ - template - inline bool operator()(const PairType &p1, const PairType &p2) const { - return p1.second < p2.second; - } - }; - - /** - * A result-set class used when performing a radius based search. - */ - template - class RadiusResultSet - { - public: - const DistanceType radius; - - std::vector > &m_indices_dists; - - inline RadiusResultSet(DistanceType radius_, std::vector > &indices_dists) : radius(radius_), m_indices_dists(indices_dists) - { - init(); - } - - inline void init() { clear(); } - inline void clear() { m_indices_dists.clear(); } - - inline size_t size() const { return m_indices_dists.size(); } - - inline bool full() const { return true; } + } + + inline DistanceType worstDist() const + { + return dists[capacity-1]; + } + }; + + /** operator "<" for std::sort() */ + struct IndexDist_Sorter + { + /** PairType will be typically: std::pair */ + template + inline bool operator()(const PairType &p1, const PairType &p2) const { + return p1.second < p2.second; + } + }; + + /** + * A result-set class used when performing a radius based search. + */ + template + class RadiusResultSet + { + public: + const DistanceType radius; + + std::vector > &m_indices_dists; + + inline RadiusResultSet(DistanceType radius_, std::vector > &indices_dists) : radius(radius_), m_indices_dists(indices_dists) + { + init(); + } + + inline void init() { clear(); } + inline void clear() { m_indices_dists.clear(); } + + inline size_t size() const { return m_indices_dists.size(); } + + inline bool full() const { return true; } /** * Called during search to add an element matching the criteria. * @return true if the search should be continued, false if the results are sufficient */ inline bool addPoint(DistanceType dist, IndexType index) - { - if (dist < radius) - m_indices_dists.push_back(std::make_pair(index, dist)); + { + if (dist < radius) + m_indices_dists.push_back(std::make_pair(index, dist)); return true; - } - - inline DistanceType worstDist() const { return radius; } - - /** - * Find the worst result (furtherest neighbor) without copying or sorting - * Pre-conditions: size() > 0 - */ - std::pair worst_item() const - { - if (m_indices_dists.empty()) throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on an empty list of results."); - typedef typename std::vector >::const_iterator DistIt; - DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); - return *it; - } - }; - - - /** @} */ - - - /** @addtogroup loadsave_grp Load/save auxiliary functions - * @{ */ - template - void save_value(FILE* stream, const T& value, size_t count = 1) - { - fwrite(&value, sizeof(value), count, stream); - } - - template - void save_value(FILE* stream, const std::vector& value) - { - size_t size = value.size(); - fwrite(&size, sizeof(size_t), 1, stream); - fwrite(&value[0], sizeof(T), size, stream); - } - - template - void load_value(FILE* stream, T& value, size_t count = 1) - { - size_t read_cnt = fread(&value, sizeof(value), count, stream); - if (read_cnt != count) { - throw std::runtime_error("Cannot read from file"); - } - } - - - template - void load_value(FILE* stream, std::vector& value) - { - size_t size; - size_t read_cnt = fread(&size, sizeof(size_t), 1, stream); - if (read_cnt != 1) { - throw std::runtime_error("Cannot read from file"); - } - value.resize(size); - read_cnt = fread(&value[0], sizeof(T), size, stream); - if (read_cnt != size) { - throw std::runtime_error("Cannot read from file"); - } - } - /** @} */ - - - /** @addtogroup metric_grp Metric (distance) classes - * @{ */ - - struct Metric - { - }; - - /** Manhattan distance functor (generic version, optimized for high-dimensionality data sets). - * Corresponding distance traits: nanoflann::metric_L1 - * \tparam T Type of the elements (e.g. double, float, uint8_t) - * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) - */ - template - struct L1_Adaptor - { - typedef T ElementType; - typedef _DistanceType DistanceType; - - const DataSource &data_source; - - L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } - - inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const - { - DistanceType result = DistanceType(); - const T* last = a + size; - const T* lastgroup = last - 3; - size_t d = 0; - - /* Process 4 items with each loop for efficiency. */ - while (a < lastgroup) { - const DistanceType diff0 = std::abs(a[0] - data_source.kdtree_get_pt(b_idx,d++)); - const DistanceType diff1 = std::abs(a[1] - data_source.kdtree_get_pt(b_idx,d++)); - const DistanceType diff2 = std::abs(a[2] - data_source.kdtree_get_pt(b_idx,d++)); - const DistanceType diff3 = std::abs(a[3] - data_source.kdtree_get_pt(b_idx,d++)); - result += diff0 + diff1 + diff2 + diff3; - a += 4; - if ((worst_dist > 0) && (result > worst_dist)) { - return result; - } - } - /* Process last 0-3 components. Not needed for standard vector lengths. */ - while (a < last) { - result += std::abs( *a++ - data_source.kdtree_get_pt(b_idx, d++) ); - } - return result; - } - - template - inline DistanceType accum_dist(const U a, const V b, int ) const - { - return std::abs(a-b); - } - }; - - /** Squared Euclidean distance functor (generic version, optimized for high-dimensionality data sets). - * Corresponding distance traits: nanoflann::metric_L2 - * \tparam T Type of the elements (e.g. double, float, uint8_t) - * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) - */ - template - struct L2_Adaptor - { - typedef T ElementType; - typedef _DistanceType DistanceType; - - const DataSource &data_source; - - L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } - - inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const - { - DistanceType result = DistanceType(); - const T* last = a + size; - const T* lastgroup = last - 3; - size_t d = 0; - - /* Process 4 items with each loop for efficiency. */ - while (a < lastgroup) { - const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx,d++); - const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx,d++); - const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx,d++); - const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx,d++); - result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; - a += 4; - if ((worst_dist > 0) && (result > worst_dist)) { - return result; - } - } - /* Process last 0-3 components. Not needed for standard vector lengths. */ - while (a < last) { - const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++); - result += diff0 * diff0; - } - return result; - } - - template - inline DistanceType accum_dist(const U a, const V b, int ) const - { - return (a - b) * (a - b); - } - }; - - /** Squared Euclidean (L2) distance functor (suitable for low-dimensionality datasets, like 2D or 3D point clouds) - * Corresponding distance traits: nanoflann::metric_L2_Simple - * \tparam T Type of the elements (e.g. double, float, uint8_t) - * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) - */ - template - struct L2_Simple_Adaptor - { - typedef T ElementType; - typedef _DistanceType DistanceType; - - const DataSource &data_source; - - L2_Simple_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } - - inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { - DistanceType result = DistanceType(); - for (size_t i = 0; i < size; ++i) { - const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); - result += diff * diff; - } - return result; - } - - template - inline DistanceType accum_dist(const U a, const V b, int ) const - { - return (a - b) * (a - b); - } - }; - - /** SO2 distance functor - * Corresponding distance traits: nanoflann::metric_SO2 - * \tparam T Type of the elements (e.g. double, float) - * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) - * orientation is constrained to be in [-pi, pi] - */ - template - struct SO2_Adaptor - { - typedef T ElementType; - typedef _DistanceType DistanceType; - - const DataSource &data_source; - - SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } - - inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { - return accum_dist(a[size-1], data_source.kdtree_get_pt(b_idx, size - 1) , size - 1); - } - - template - inline DistanceType accum_dist(const U a, const V b, int ) const - { - DistanceType result = DistanceType(); - result = b - a; - if (result > M_PI) - result -= 2. * M_PI; - else if (result < -M_PI) - result += 2. * M_PI; - return result; - } - }; - - /** SO3 distance functor (Uses L2_Simple) - * Corresponding distance traits: nanoflann::metric_SO3 - * \tparam T Type of the elements (e.g. double, float) - * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) - */ - template - struct SO3_Adaptor - { - typedef T ElementType; - typedef _DistanceType DistanceType; - - L2_Simple_Adaptor distance_L2_Simple; - - SO3_Adaptor(const DataSource &_data_source) : distance_L2_Simple(_data_source) { } - - inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { - return distance_L2_Simple.evalMetric(a, b_idx, size); - } - - template - inline DistanceType accum_dist(const U a, const V b, int idx) const - { - return distance_L2_Simple.accum_dist(a, b, idx); - } - }; - - /** Metaprogramming helper traits class for the L1 (Manhattan) metric */ - struct metric_L1 : public Metric - { - template - struct traits { - typedef L1_Adaptor distance_t; - }; - }; - /** Metaprogramming helper traits class for the L2 (Euclidean) metric */ - struct metric_L2 : public Metric - { - template - struct traits { - typedef L2_Adaptor distance_t; - }; - }; - /** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */ - struct metric_L2_Simple : public Metric - { - template - struct traits { - typedef L2_Simple_Adaptor distance_t; - }; - }; - /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ - struct metric_SO2 : public Metric - { - template - struct traits { - typedef SO2_Adaptor distance_t; - }; - }; - /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ - struct metric_SO3 : public Metric - { - template - struct traits { - typedef SO3_Adaptor distance_t; - }; - }; - - /** @} */ - - /** @addtogroup param_grp Parameter structs - * @{ */ - - /** Parameters (see README.md) */ - struct KDTreeSingleIndexAdaptorParams - { - KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10) : - leaf_max_size(_leaf_max_size) - {} - - size_t leaf_max_size; - }; - - /** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ - struct SearchParams - { - /** Note: The first argument (checks_IGNORED_) is ignored, but kept for compatibility with the FLANN interface */ - SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true ) : - checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {} - - int checks; //!< Ignored parameter (Kept for compatibility with the FLANN interface). - float eps; //!< search for eps-approximate neighbours (default: 0) - bool sorted; //!< only for radius search, require neighbours sorted by distance (default: true) - }; - /** @} */ - - - /** @addtogroup memalloc_grp Memory allocation - * @{ */ - - /** - * Allocates (using C's malloc) a generic type T. - * - * Params: - * count = number of instances to allocate. - * Returns: pointer (of type T*) to memory buffer - */ - template - inline T* allocate(size_t count = 1) - { - T* mem = static_cast( ::malloc(sizeof(T)*count)); - return mem; - } - - - /** - * Pooled storage allocator - * - * The following routines allow for the efficient allocation of storage in - * small chunks from a specified pool. Rather than allowing each structure - * to be freed individually, an entire pool of storage is freed at once. - * This method has two advantages over just using malloc() and free(). First, - * it is far more efficient for allocating small objects, as there is - * no overhead for remembering all the information needed to free each - * object or consolidating fragmented memory. Second, the decision about - * how long to keep an object is made at the time of allocation, and there - * is no need to track down all the objects to free them. - * - */ - - const size_t WORDSIZE = 16; - const size_t BLOCKSIZE = 8192; - - class PooledAllocator - { - /* We maintain memory alignment to word boundaries by requiring that all - allocations be in multiples of the machine wordsize. */ - /* Size of machine word in bytes. Must be power of 2. */ - /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE. */ - - - size_t remaining; /* Number of bytes left in current block of storage. */ - void* base; /* Pointer to base of current block of storage. */ - void* loc; /* Current location in block to next allocate memory. */ - - void internal_init() - { - remaining = 0; - base = NULL; - usedMemory = 0; - wastedMemory = 0; - } - - public: - size_t usedMemory; - size_t wastedMemory; - - /** - Default constructor. Initializes a new pool. - */ - PooledAllocator() { - internal_init(); - } - - /** - * Destructor. Frees all the memory allocated in this pool. - */ - ~PooledAllocator() { - free_all(); - } - - /** Frees all allocated memory chunks */ - void free_all() - { - while (base != NULL) { - void *prev = *(static_cast( base)); /* Get pointer to prev block. */ - ::free(base); - base = prev; - } - internal_init(); - } - - /** - * Returns a pointer to a piece of new memory of the given size in bytes - * allocated from the pool. - */ - void* malloc(const size_t req_size) - { - /* Round size up to a multiple of wordsize. The following expression - only works for WORDSIZE that is a power of 2, by masking last bits of - incremented size to zero. - */ - const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); - - /* Check whether a new block must be allocated. Note that the first word - of a block is reserved for a pointer to the previous block. - */ - if (size > remaining) { - - wastedMemory += remaining; - - /* Allocate new storage. */ - const size_t blocksize = (size + sizeof(void*) + (WORDSIZE - 1) > BLOCKSIZE) ? - size + sizeof(void*) + (WORDSIZE - 1) : BLOCKSIZE; - - // use the standard C malloc to allocate memory - void* m = ::malloc(blocksize); - if (!m) { - fprintf(stderr, "Failed to allocate memory.\n"); - return NULL; - } - - /* Fill first word of new block with pointer to previous block. */ - static_cast(m)[0] = base; - base = m; - - size_t shift = 0; - //int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) & (WORDSIZE-1))) & (WORDSIZE-1); - - remaining = blocksize - sizeof(void*) - shift; - loc = (static_cast(m) + sizeof(void*) + shift); - } - void* rloc = loc; - loc = static_cast(loc) + size; - remaining -= size; - - usedMemory += size; - - return rloc; - } - - /** - * Allocates (using this pool) a generic type T. - * - * Params: - * count = number of instances to allocate. - * Returns: pointer (of type T*) to memory buffer - */ - template - T* allocate(const size_t count = 1) - { - T* mem = static_cast(this->malloc(sizeof(T)*count)); - return mem; - } - - }; - /** @} */ - - /** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff - * @{ */ - - // ---------------- CArray ------------------------- - /** A STL container (as wrapper) for arrays of constant size defined at compile time (class imported from the MRPT project) - * This code is an adapted version from Boost, modifed for its integration - * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). - * See - * http://www.josuttis.com/cppcode - * for details and the latest version. - * See - * http://www.boost.org/libs/array for Documentation. - * for documentation. - * - * (C) Copyright Nicolai M. Josuttis 2001. - * Permission to copy, use, modify, sell and distribute this software - * is granted provided this copyright notice appears in all copies. - * This software is provided "as is" without express or implied - * warranty, and with no claim as to its suitability for any purpose. - * - * 29 Jan 2004 - minor fixes (Nico Josuttis) - * 04 Dec 2003 - update to synch with library TR1 (Alisdair Meredith) - * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries. - * 05 Aug 2001 - minor update (Nico Josuttis) - * 20 Jan 2001 - STLport fix (Beman Dawes) - * 29 Sep 2000 - Initial Revision (Nico Josuttis) - * - * Jan 30, 2004 - */ + } + + inline DistanceType worstDist() const { return radius; } + + /** + * Find the worst result (furtherest neighbor) without copying or sorting + * Pre-conditions: size() > 0 + */ + std::pair worst_item() const + { + if (m_indices_dists.empty()) throw std::runtime_error("Cannot invoke RadiusResultSet::worst_item() on an empty list of results."); + typedef typename std::vector >::const_iterator DistIt; + DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); + return *it; + } + }; + + + /** @} */ + + + /** @addtogroup loadsave_grp Load/save auxiliary functions + * @{ */ + template + void save_value(FILE* stream, const T& value, size_t count = 1) + { + fwrite(&value, sizeof(value), count, stream); + } + + template + void save_value(FILE* stream, const std::vector& value) + { + size_t size = value.size(); + fwrite(&size, sizeof(size_t), 1, stream); + fwrite(&value[0], sizeof(T), size, stream); + } + + template + void load_value(FILE* stream, T& value, size_t count = 1) + { + size_t read_cnt = fread(&value, sizeof(value), count, stream); + if (read_cnt != count) { + throw std::runtime_error("Cannot read from file"); + } + } + + + template + void load_value(FILE* stream, std::vector& value) + { + size_t size; + size_t read_cnt = fread(&size, sizeof(size_t), 1, stream); + if (read_cnt != 1) { + throw std::runtime_error("Cannot read from file"); + } + value.resize(size); + read_cnt = fread(&value[0], sizeof(T), size, stream); + if (read_cnt != size) { + throw std::runtime_error("Cannot read from file"); + } + } + /** @} */ + + + /** @addtogroup metric_grp Metric (distance) classes + * @{ */ + + struct Metric + { + }; + + /** Manhattan distance functor (generic version, optimized for high-dimensionality data sets). + * Corresponding distance traits: nanoflann::metric_L1 + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L1_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = std::abs(a[0] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff1 = std::abs(a[1] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff2 = std::abs(a[2] - data_source.kdtree_get_pt(b_idx,d++)); + const DistanceType diff3 = std::abs(a[3] - data_source.kdtree_get_pt(b_idx,d++)); + result += diff0 + diff1 + diff2 + diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + result += std::abs( *a++ - data_source.kdtree_get_pt(b_idx, d++) ); + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return std::abs(a-b); + } + }; + + /** Squared Euclidean distance functor (generic version, optimized for high-dimensionality data sets). + * Corresponding distance traits: nanoflann::metric_L2 + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L2_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size, DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) { + const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx,d++); + const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx,d++); + result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { + return result; + } + } + /* Process last 0-3 components. Not needed for standard vector lengths. */ + while (a < last) { + const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++); + result += diff0 * diff0; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return (a - b) * (a - b); + } + }; + + /** Squared Euclidean (L2) distance functor (suitable for low-dimensionality datasets, like 2D or 3D point clouds) + * Corresponding distance traits: nanoflann::metric_L2_Simple + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double, int64_t) + */ + template + struct L2_Simple_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + L2_Simple_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + DistanceType result = DistanceType(); + for (size_t i = 0; i < size; ++i) { + const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i); + result += diff * diff; + } + return result; + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + return (a - b) * (a - b); + } + }; + + /** SO2 distance functor + * Corresponding distance traits: nanoflann::metric_SO2 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) + * orientation is constrained to be in [-pi, pi] + */ + template + struct SO2_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + const DataSource &data_source; + + SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + return accum_dist(a[size-1], data_source.kdtree_get_pt(b_idx, size - 1) , size - 1); + } + + template + inline DistanceType accum_dist(const U a, const V b, int ) const + { + DistanceType result = DistanceType(); + result = b - a; + if (result > M_PI) + result -= 2. * M_PI; + else if (result < -M_PI) + result += 2. * M_PI; + return result; + } + }; + + /** SO3 distance functor (Uses L2_Simple) + * Corresponding distance traits: nanoflann::metric_SO3 + * \tparam T Type of the elements (e.g. double, float) + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. float, double) + */ + template + struct SO3_Adaptor + { + typedef T ElementType; + typedef _DistanceType DistanceType; + + L2_Simple_Adaptor distance_L2_Simple; + + SO3_Adaptor(const DataSource &_data_source) : distance_L2_Simple(_data_source) { } + + inline DistanceType evalMetric(const T* a, const size_t b_idx, size_t size) const { + return distance_L2_Simple.evalMetric(a, b_idx, size); + } + + template + inline DistanceType accum_dist(const U a, const V b, int idx) const + { + return distance_L2_Simple.accum_dist(a, b, idx); + } + }; + + /** Metaprogramming helper traits class for the L1 (Manhattan) metric */ + struct metric_L1 : public Metric + { + template + struct traits { + typedef L1_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the L2 (Euclidean) metric */ + struct metric_L2 : public Metric + { + template + struct traits { + typedef L2_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */ + struct metric_L2_Simple : public Metric + { + template + struct traits { + typedef L2_Simple_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ + struct metric_SO2 : public Metric + { + template + struct traits { + typedef SO2_Adaptor distance_t; + }; + }; + /** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ + struct metric_SO3 : public Metric + { + template + struct traits { + typedef SO3_Adaptor distance_t; + }; + }; + + /** @} */ + + /** @addtogroup param_grp Parameter structs + * @{ */ + + /** Parameters (see README.md) */ + struct KDTreeSingleIndexAdaptorParams + { + KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10) : + leaf_max_size(_leaf_max_size) + {} + + size_t leaf_max_size; + }; + + /** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ + struct SearchParams + { + /** Note: The first argument (checks_IGNORED_) is ignored, but kept for compatibility with the FLANN interface */ + SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true ) : + checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {} + + int checks; //!< Ignored parameter (Kept for compatibility with the FLANN interface). + float eps; //!< search for eps-approximate neighbours (default: 0) + bool sorted; //!< only for radius search, require neighbours sorted by distance (default: true) + }; + /** @} */ + + + /** @addtogroup memalloc_grp Memory allocation + * @{ */ + + /** + * Allocates (using C's malloc) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + inline T* allocate(size_t count = 1) + { + T* mem = static_cast( ::malloc(sizeof(T)*count)); + return mem; + } + + + /** + * Pooled storage allocator + * + * The following routines allow for the efficient allocation of storage in + * small chunks from a specified pool. Rather than allowing each structure + * to be freed individually, an entire pool of storage is freed at once. + * This method has two advantages over just using malloc() and free(). First, + * it is far more efficient for allocating small objects, as there is + * no overhead for remembering all the information needed to free each + * object or consolidating fragmented memory. Second, the decision about + * how long to keep an object is made at the time of allocation, and there + * is no need to track down all the objects to free them. + * + */ + + const size_t WORDSIZE = 16; + const size_t BLOCKSIZE = 8192; + + class PooledAllocator + { + /* We maintain memory alignment to word boundaries by requiring that all + allocations be in multiples of the machine wordsize. */ + /* Size of machine word in bytes. Must be power of 2. */ + /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE. */ + + + size_t remaining; /* Number of bytes left in current block of storage. */ + void* base; /* Pointer to base of current block of storage. */ + void* loc; /* Current location in block to next allocate memory. */ + + void internal_init() + { + remaining = 0; + base = NULL; + usedMemory = 0; + wastedMemory = 0; + } + + public: + size_t usedMemory; + size_t wastedMemory; + + /** + Default constructor. Initializes a new pool. + */ + PooledAllocator() { + internal_init(); + } + + /** + * Destructor. Frees all the memory allocated in this pool. + */ + ~PooledAllocator() { + free_all(); + } + + /** Frees all allocated memory chunks */ + void free_all() + { + while (base != NULL) { + void *prev = *(static_cast( base)); /* Get pointer to prev block. */ + ::free(base); + base = prev; + } + internal_init(); + } + + /** + * Returns a pointer to a piece of new memory of the given size in bytes + * allocated from the pool. + */ + void* malloc(const size_t req_size) + { + /* Round size up to a multiple of wordsize. The following expression + only works for WORDSIZE that is a power of 2, by masking last bits of + incremented size to zero. + */ + const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); + + /* Check whether a new block must be allocated. Note that the first word + of a block is reserved for a pointer to the previous block. + */ + if (size > remaining) { + + wastedMemory += remaining; + + /* Allocate new storage. */ + const size_t blocksize = (size + sizeof(void*) + (WORDSIZE - 1) > BLOCKSIZE) ? + size + sizeof(void*) + (WORDSIZE - 1) : BLOCKSIZE; + + // use the standard C malloc to allocate memory + void* m = ::malloc(blocksize); + if (!m) { + fprintf(stderr, "Failed to allocate memory.\n"); + return NULL; + } + + /* Fill first word of new block with pointer to previous block. */ + static_cast(m)[0] = base; + base = m; + + size_t shift = 0; + //int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) & (WORDSIZE-1))) & (WORDSIZE-1); + + remaining = blocksize - sizeof(void*) - shift; + loc = (static_cast(m) + sizeof(void*) + shift); + } + void* rloc = loc; + loc = static_cast(loc) + size; + remaining -= size; + + usedMemory += size; + + return rloc; + } + + /** + * Allocates (using this pool) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + T* allocate(const size_t count = 1) + { + T* mem = static_cast(this->malloc(sizeof(T)*count)); + return mem; + } + + }; + /** @} */ + + /** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff + * @{ */ + + // ---------------- CArray ------------------------- + /** A STL container (as wrapper) for arrays of constant size defined at compile time (class imported from the MRPT project) + * This code is an adapted version from Boost, modifed for its integration + * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). + * See + * http://www.josuttis.com/cppcode + * for details and the latest version. + * See + * http://www.boost.org/libs/array for Documentation. + * for documentation. + * + * (C) Copyright Nicolai M. Josuttis 2001. + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * + * 29 Jan 2004 - minor fixes (Nico Josuttis) + * 04 Dec 2003 - update to synch with library TR1 (Alisdair Meredith) + * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries. + * 05 Aug 2001 - minor update (Nico Josuttis) + * 20 Jan 2001 - STLport fix (Beman Dawes) + * 29 Sep 2000 - Initial Revision (Nico Josuttis) + * + * Jan 30, 2004 + */ template class CArray { public: @@ -751,8 +751,8 @@ namespace nanoflann static bool empty() { return false; } static size_type max_size() { return N; } enum { static_size = N }; - /** This method has no effects in this class, but raises an exception if the expected size does not match */ - inline void resize(const size_t nElements) { if (nElements!=N) throw std::logic_error("Try to change the size of a CArray."); } + /** This method has no effects in this class, but raises an exception if the expected size does not match */ + inline void resize(const size_t nElements) { if (nElements!=N) throw std::logic_error("Try to change the size of a CArray."); } // swap (note: linear complexity in N, constant for given instantiation) void swap (CArray& y) { std::swap_ranges(begin(),end(),y.begin()); } // direct access to data (read-only) @@ -773,1215 +773,1215 @@ namespace nanoflann static void rangecheck (size_type i) { if (i >= size()) { throw std::out_of_range("CArray<>: index out of range"); } } }; // end of CArray - /** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors when DIM=-1. - * Fixed size version for a generic DIM: - */ - template - struct array_or_vector_selector - { - typedef CArray container_t; - }; - /** Dynamic size version */ - template - struct array_or_vector_selector<-1, T> { - typedef std::vector container_t; - }; - - /** @} */ - - /** kd-tree base-class - * - * Contains the member functions common to the classes KDTreeSingleIndexAdaptor and KDTreeSingleIndexDynamicAdaptor_. - * - * \tparam Derived The name of the class which inherits this class. - * \tparam DatasetAdaptor The user-provided adaptor (see comments above). - * \tparam Distance The distance metric to use, these are all classes derived from nanoflann::Metric - * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) - * \tparam IndexType Will be typically size_t or int - */ - - template - class KDTreeBaseClass - { - - public: - /** Frees the previously-built index. Automatically called within buildIndex(). */ - void freeIndex(Derived &obj) - { - obj.pool.free_all(); - obj.root_node = NULL; - obj.m_size_at_index_build = 0; - } - - typedef typename Distance::ElementType ElementType; - typedef typename Distance::DistanceType DistanceType; - - /*--------------------- Internal Data Structures --------------------------*/ - struct Node - { - /** Union used because a node can be either a LEAF node or a non-leaf node, so both data fields are never used simultaneously */ - union { - struct leaf + /** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors when DIM=-1. + * Fixed size version for a generic DIM: + */ + template + struct array_or_vector_selector + { + typedef CArray container_t; + }; + /** Dynamic size version */ + template + struct array_or_vector_selector<-1, T> { + typedef std::vector container_t; + }; + + /** @} */ + + /** kd-tree base-class + * + * Contains the member functions common to the classes KDTreeSingleIndexAdaptor and KDTreeSingleIndexDynamicAdaptor_. + * + * \tparam Derived The name of the class which inherits this class. + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use, these are all classes derived from nanoflann::Metric + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + + template + class KDTreeBaseClass + { + + public: + /** Frees the previously-built index. Automatically called within buildIndex(). */ + void freeIndex(Derived &obj) + { + obj.pool.free_all(); + obj.root_node = NULL; + obj.m_size_at_index_build = 0; + } + + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + + /*--------------------- Internal Data Structures --------------------------*/ + struct Node + { + /** Union used because a node can be either a LEAF node or a non-leaf node, so both data fields are never used simultaneously */ + union { + struct leaf { - IndexType left, right; //!< Indices of points in leaf node - } lr; - struct nonleaf + IndexType left, right; //!< Indices of points in leaf node + } lr; + struct nonleaf { - int divfeat; //!< Dimension used for subdivision. - DistanceType divlow, divhigh; //!< The values used for subdivision. - } sub; - } node_type; - Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node) - }; - - typedef Node* NodePtr; - - struct Interval - { - ElementType low, high; - }; - - /** - * Array of indices to vectors in the dataset. - */ - std::vector vind; - - NodePtr root_node; - - size_t m_leaf_max_size; - - size_t m_size; //!< Number of current points in the dataset - size_t m_size_at_index_build; //!< Number of points in the dataset when the index was built - int dim; //!< Dimensionality of each data point - - /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename array_or_vector_selector::container_t BoundingBox; - - /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename array_or_vector_selector::container_t distance_vector_t; - - /** The KD-tree used to find neighbours */ - - BoundingBox root_bbox; - - /** - * Pooled memory allocator. - * - * Using a pooled memory allocator is more efficient - * than allocating memory directly when there is a large - * number small of memory allocations. - */ - PooledAllocator pool; - - /** Returns number of points in dataset */ - size_t size(const Derived &obj) const { return obj.m_size; } - - /** Returns the length of each point in the dataset */ - size_t veclen(const Derived &obj) { - return static_cast(DIM>0 ? DIM : obj.dim); - } - - /// Helper accessor to the dataset points: - inline ElementType dataset_get(const Derived &obj, size_t idx, int component) const{ - return obj.dataset.kdtree_get_pt(idx, component); - } - - /** - * Computes the inde memory usage - * Returns: memory used by the index - */ - size_t usedMemory(Derived &obj) - { - return obj.pool.usedMemory + obj.pool.wastedMemory + obj.dataset.kdtree_get_point_count() * sizeof(IndexType); // pool memory and vind array memory - } - - void computeMinMax(const Derived &obj, IndexType* ind, IndexType count, int element, ElementType& min_elem, ElementType& max_elem) - { - min_elem = dataset_get(obj, ind[0],element); - max_elem = dataset_get(obj, ind[0],element); - for (IndexType i = 1; i < count; ++i) { - ElementType val = dataset_get(obj, ind[i], element); - if (val < min_elem) min_elem = val; - if (val > max_elem) max_elem = val; - } - } - - /** - * Create a tree node that subdivides the list of vecs from vind[first] - * to vind[last]. The routine is called recursively on each sublist. - * - * @param left index of the first vector - * @param right index of the last vector - */ - NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right, BoundingBox& bbox) - { - NodePtr node = obj.pool.template allocate(); // allocate memory - - /* If too few exemplars remain, then make this a leaf node. */ - if ( (right - left) <= static_cast(obj.m_leaf_max_size) ) { - node->child1 = node->child2 = NULL; /* Mark as leaf node. */ - node->node_type.lr.left = left; - node->node_type.lr.right = right; - - // compute bounding-box of leaf points - for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { - bbox[i].low = dataset_get(obj, obj.vind[left], i); - bbox[i].high = dataset_get(obj, obj.vind[left], i); - } - for (IndexType k = left + 1; k < right; ++k) { - for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { - if (bbox[i].low > dataset_get(obj, obj.vind[k], i)) bbox[i].low = dataset_get(obj, obj.vind[k], i); - if (bbox[i].high < dataset_get(obj, obj.vind[k], i)) bbox[i].high = dataset_get(obj, obj.vind[k], i); - } - } - } - else { - IndexType idx; - int cutfeat; - DistanceType cutval; - middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval, bbox); - - node->node_type.sub.divfeat = cutfeat; - - BoundingBox left_bbox(bbox); - left_bbox[cutfeat].high = cutval; - node->child1 = divideTree(obj, left, left + idx, left_bbox); - - BoundingBox right_bbox(bbox); - right_bbox[cutfeat].low = cutval; - node->child2 = divideTree(obj, left + idx, right, right_bbox); - - node->node_type.sub.divlow = left_bbox[cutfeat].high; - node->node_type.sub.divhigh = right_bbox[cutfeat].low; - - for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { - bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); - bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); - } - } - - return node; - } - - void middleSplit_(Derived &obj, IndexType* ind, IndexType count, IndexType& index, int& cutfeat, DistanceType& cutval, const BoundingBox& bbox) - { - const DistanceType EPS = static_cast(0.00001); - ElementType max_span = bbox[0].high-bbox[0].low; - for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) { - ElementType span = bbox[i].high - bbox[i].low; - if (span > max_span) { - max_span = span; - } - } - ElementType max_spread = -1; - cutfeat = 0; - for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { - ElementType span = bbox[i].high-bbox[i].low; - if (span > (1 - EPS) * max_span) { - ElementType min_elem, max_elem; - computeMinMax(obj, ind, count, i, min_elem, max_elem); - ElementType spread = max_elem - min_elem;; - if (spread > max_spread) { - cutfeat = i; - max_spread = spread; - } - } - } - // split in the middle - DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; - ElementType min_elem, max_elem; - computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem); - - if (split_val < min_elem) cutval = min_elem; - else if (split_val > max_elem) cutval = max_elem; - else cutval = split_val; - - IndexType lim1, lim2; - planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); - - if (lim1 > count / 2) index = lim1; - else if (lim2 < count / 2) index = lim2; - else index = count/2; - } - - /** - * Subdivide the list of points by a plane perpendicular on axe corresponding - * to the 'cutfeat' dimension at 'cutval' position. - * - * On return: - * dataset[ind[0..lim1-1]][cutfeat]cutval - */ - void planeSplit(Derived &obj, IndexType* ind, const IndexType count, int cutfeat, DistanceType &cutval, IndexType& lim1, IndexType& lim2) - { - /* Move vector indices for left subtree to front of list. */ - IndexType left = 0; - IndexType right = count-1; - for (;; ) { - while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval) ++left; - while (right && left <= right && dataset_get(obj, ind[right], cutfeat) >= cutval) --right; - if (left > right || !right) break; // "!right" was added to support unsigned Index types - std::swap(ind[left], ind[right]); - ++left; - --right; - } - /* If either list is empty, it means that all remaining features - * are identical. Split in the middle to maintain a balanced tree. - */ - lim1 = left; - right = count-1; - for (;; ) { - while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval) ++left; - while (right && left <= right && dataset_get(obj, ind[right], cutfeat) > cutval) --right; - if (left > right || !right) break; // "!right" was added to support unsigned Index types - std::swap(ind[left], ind[right]); - ++left; - --right; - } - lim2 = left; - } - - DistanceType computeInitialDistances(const Derived &obj, const ElementType* vec, distance_vector_t& dists) const - { - assert(vec); - DistanceType distsq = DistanceType(); - - for (int i = 0; i < (DIM>0 ? DIM : obj.dim); ++i) { - if (vec[i] < obj.root_bbox[i].low) { - dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i); - distsq += dists[i]; - } - if (vec[i] > obj.root_bbox[i].high) { - dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i); - distsq += dists[i]; - } - } - return distsq; - } - - void save_tree(Derived &obj, FILE* stream, NodePtr tree) - { - save_value(stream, *tree); - if (tree->child1 != NULL) { - save_tree(obj, stream, tree->child1); - } - if (tree->child2 != NULL) { - save_tree(obj, stream, tree->child2); - } - } - - - void load_tree(Derived &obj, FILE* stream, NodePtr& tree) - { - tree = obj.pool.template allocate(); - load_value(stream, *tree); - if (tree->child1 != NULL) { - load_tree(obj, stream, tree->child1); - } - if (tree->child2 != NULL) { - load_tree(obj, stream, tree->child2); - } - } - - /** Stores the index in a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void saveIndex_(Derived &obj, FILE* stream) - { - save_value(stream, obj.m_size); - save_value(stream, obj.dim); - save_value(stream, obj.root_bbox); - save_value(stream, obj.m_leaf_max_size); - save_value(stream, obj.vind); - save_tree(obj, stream, obj.root_node); - } - - /** Loads a previous index from a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void loadIndex_(Derived &obj, FILE* stream) - { - load_value(stream, obj.m_size); - load_value(stream, obj.dim); - load_value(stream, obj.root_bbox); - load_value(stream, obj.m_leaf_max_size); - load_value(stream, obj.vind); - load_tree(obj, stream, obj.root_node); - } - - }; - - - /** @addtogroup kdtrees_grp KD-tree classes and adaptors - * @{ */ - - /** kd-tree static index - * - * Contains the k-d trees and other information for indexing a set of points - * for nearest-neighbor matching. - * - * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): - * - * \code - * // Must return the number of data poins - * inline size_t kdtree_get_point_count() const { ... } - * - * - * // Must return the dim'th component of the idx'th point in the class: - * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } - * - * // Optional bounding-box computation: return false to default to a standard bbox computation loop. - * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. - * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) - * template - * bool kdtree_get_bbox(BBOX &bb) const - * { - * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits - * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits - * ... - * return true; - * } - * - * \endcode - * - * \tparam DatasetAdaptor The user-provided adaptor (see comments above). - * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. - * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) - * \tparam IndexType Will be typically size_t or int - */ - template - class KDTreeSingleIndexAdaptor : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> - { - public: - /** Deleted copy constructor*/ - KDTreeSingleIndexAdaptor(const KDTreeSingleIndexAdaptor&) = delete; - - /** - * The dataset used by this index - */ - const DatasetAdaptor &dataset; //!< The source of our data - - const KDTreeSingleIndexAdaptorParams index_params; - - Distance distance; - - typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; - - typedef typename BaseClassRef::ElementType ElementType; - typedef typename BaseClassRef::DistanceType DistanceType; - - typedef typename BaseClassRef::Node Node; - typedef Node* NodePtr; - - typedef typename BaseClassRef::Interval Interval; - /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename BaseClassRef::BoundingBox BoundingBox; - - /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename BaseClassRef::distance_vector_t distance_vector_t; - - /** - * KDTree constructor - * - * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann - * - * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) - * is determined by means of: - * - The \a DIM template parameter if >0 (highest priority) - * - Otherwise, the \a dimensionality parameter of this constructor. - * - * @param inputData Dataset with the input features - * @param params Basically, the maximum leaf node size - */ - KDTreeSingleIndexAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() ) : - dataset(inputData), index_params(params), distance(inputData) - { - BaseClassRef::root_node = NULL; - BaseClassRef::m_size = dataset.kdtree_get_point_count(); - BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; - BaseClassRef::dim = dimensionality; - if (DIM>0) BaseClassRef::dim = DIM; - BaseClassRef::m_leaf_max_size = params.leaf_max_size; - - // Create a permutable array of indices to the input vectors. - init_vind(); - } - - /** - * Builds the index - */ - void buildIndex() - { - BaseClassRef::m_size = dataset.kdtree_get_point_count(); - BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; - init_vind(); - this->freeIndex(*this); - BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; - if(BaseClassRef::m_size == 0) return; - computeBoundingBox(BaseClassRef::root_bbox); - BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree - } - - /** \name Query methods - * @{ */ - - /** - * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside - * the result object. - * - * Params: - * result = the result object in which the indices of the nearest-neighbors are stored - * vec = the vector for which to search the nearest neighbors - * - * \tparam RESULTSET Should be any ResultSet + int divfeat; //!< Dimension used for subdivision. + DistanceType divlow, divhigh; //!< The values used for subdivision. + } sub; + } node_type; + Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node) + }; + + typedef Node* NodePtr; + + struct Interval + { + ElementType low, high; + }; + + /** + * Array of indices to vectors in the dataset. + */ + std::vector vind; + + NodePtr root_node; + + size_t m_leaf_max_size; + + size_t m_size; //!< Number of current points in the dataset + size_t m_size_at_index_build; //!< Number of points in the dataset when the index was built + int dim; //!< Dimensionality of each data point + + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename array_or_vector_selector::container_t BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename array_or_vector_selector::container_t distance_vector_t; + + /** The KD-tree used to find neighbours */ + + BoundingBox root_bbox; + + /** + * Pooled memory allocator. + * + * Using a pooled memory allocator is more efficient + * than allocating memory directly when there is a large + * number small of memory allocations. + */ + PooledAllocator pool; + + /** Returns number of points in dataset */ + size_t size(const Derived &obj) const { return obj.m_size; } + + /** Returns the length of each point in the dataset */ + size_t veclen(const Derived &obj) { + return static_cast(DIM>0 ? DIM : obj.dim); + } + + /// Helper accessor to the dataset points: + inline ElementType dataset_get(const Derived &obj, size_t idx, int component) const{ + return obj.dataset.kdtree_get_pt(idx, component); + } + + /** + * Computes the inde memory usage + * Returns: memory used by the index + */ + size_t usedMemory(Derived &obj) + { + return obj.pool.usedMemory + obj.pool.wastedMemory + obj.dataset.kdtree_get_point_count() * sizeof(IndexType); // pool memory and vind array memory + } + + void computeMinMax(const Derived &obj, IndexType* ind, IndexType count, int element, ElementType& min_elem, ElementType& max_elem) + { + min_elem = dataset_get(obj, ind[0],element); + max_elem = dataset_get(obj, ind[0],element); + for (IndexType i = 1; i < count; ++i) { + ElementType val = dataset_get(obj, ind[i], element); + if (val < min_elem) min_elem = val; + if (val > max_elem) max_elem = val; + } + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] + * to vind[last]. The routine is called recursively on each sublist. + * + * @param left index of the first vector + * @param right index of the last vector + */ + NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right, BoundingBox& bbox) + { + NodePtr node = obj.pool.template allocate(); // allocate memory + + /* If too few exemplars remain, then make this a leaf node. */ + if ( (right - left) <= static_cast(obj.m_leaf_max_size) ) { + node->child1 = node->child2 = NULL; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = dataset_get(obj, obj.vind[left], i); + bbox[i].high = dataset_get(obj, obj.vind[left], i); + } + for (IndexType k = left + 1; k < right; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + if (bbox[i].low > dataset_get(obj, obj.vind[k], i)) bbox[i].low = dataset_get(obj, obj.vind[k], i); + if (bbox[i].high < dataset_get(obj, obj.vind[k], i)) bbox[i].high = dataset_get(obj, obj.vind[k], i); + } + } + } + else { + IndexType idx; + int cutfeat; + DistanceType cutval; + middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = divideTree(obj, left, left + idx, left_bbox); + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + node->child2 = divideTree(obj, left + idx, right, right_bbox); + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + void middleSplit_(Derived &obj, IndexType* ind, IndexType count, IndexType& index, int& cutfeat, DistanceType& cutval, const BoundingBox& bbox) + { + const DistanceType EPS = static_cast(0.00001); + ElementType max_span = bbox[0].high-bbox[0].low; + for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high - bbox[i].low; + if (span > max_span) { + max_span = span; + } + } + ElementType max_spread = -1; + cutfeat = 0; + for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) { + ElementType span = bbox[i].high-bbox[i].low; + if (span > (1 - EPS) * max_span) { + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, i, min_elem, max_elem); + ElementType spread = max_elem - min_elem;; + if (spread > max_spread) { + cutfeat = i; + max_spread = spread; + } + } + } + // split in the middle + DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; + ElementType min_elem, max_elem; + computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem); + + if (split_val < min_elem) cutval = min_elem; + else if (split_val > max_elem) cutval = max_elem; + else cutval = split_val; + + IndexType lim1, lim2; + planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); + + if (lim1 > count / 2) index = lim1; + else if (lim2 < count / 2) index = lim2; + else index = count/2; + } + + /** + * Subdivide the list of points by a plane perpendicular on axe corresponding + * to the 'cutfeat' dimension at 'cutval' position. + * + * On return: + * dataset[ind[0..lim1-1]][cutfeat]cutval + */ + void planeSplit(Derived &obj, IndexType* ind, const IndexType count, int cutfeat, DistanceType &cutval, IndexType& lim1, IndexType& lim2) + { + /* Move vector indices for left subtree to front of list. */ + IndexType left = 0; + IndexType right = count-1; + for (;; ) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval) ++left; + while (right && left <= right && dataset_get(obj, ind[right], cutfeat) >= cutval) --right; + if (left > right || !right) break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + /* If either list is empty, it means that all remaining features + * are identical. Split in the middle to maintain a balanced tree. + */ + lim1 = left; + right = count-1; + for (;; ) { + while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval) ++left; + while (right && left <= right && dataset_get(obj, ind[right], cutfeat) > cutval) --right; + if (left > right || !right) break; // "!right" was added to support unsigned Index types + std::swap(ind[left], ind[right]); + ++left; + --right; + } + lim2 = left; + } + + DistanceType computeInitialDistances(const Derived &obj, const ElementType* vec, distance_vector_t& dists) const + { + assert(vec); + DistanceType distsq = DistanceType(); + + for (int i = 0; i < (DIM>0 ? DIM : obj.dim); ++i) { + if (vec[i] < obj.root_bbox[i].low) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i); + distsq += dists[i]; + } + if (vec[i] > obj.root_bbox[i].high) { + dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i); + distsq += dists[i]; + } + } + return distsq; + } + + void save_tree(Derived &obj, FILE* stream, NodePtr tree) + { + save_value(stream, *tree); + if (tree->child1 != NULL) { + save_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + save_tree(obj, stream, tree->child2); + } + } + + + void load_tree(Derived &obj, FILE* stream, NodePtr& tree) + { + tree = obj.pool.template allocate(); + load_value(stream, *tree); + if (tree->child1 != NULL) { + load_tree(obj, stream, tree->child1); + } + if (tree->child2 != NULL) { + load_tree(obj, stream, tree->child2); + } + } + + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex_(Derived &obj, FILE* stream) + { + save_value(stream, obj.m_size); + save_value(stream, obj.dim); + save_value(stream, obj.root_bbox); + save_value(stream, obj.m_leaf_max_size); + save_value(stream, obj.vind); + save_tree(obj, stream, obj.root_node); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex_(Derived &obj, FILE* stream) + { + load_value(stream, obj.m_size); + load_value(stream, obj.dim); + load_value(stream, obj.root_bbox); + load_value(stream, obj.m_leaf_max_size); + load_value(stream, obj.vind); + load_tree(obj, stream, obj.root_node); + } + + }; + + + /** @addtogroup kdtrees_grp KD-tree classes and adaptors + * @{ */ + + /** kd-tree static index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + * template + * bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexAdaptor : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> + { + public: + /** Deleted copy constructor*/ + KDTreeSingleIndexAdaptor(const KDTreeSingleIndexAdaptor&) = delete; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + const KDTreeSingleIndexAdaptorParams index_params; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node* NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() ) : + dataset(inputData), index_params(params), distance(inputData) + { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + BaseClassRef::dim = dimensionality; + if (DIM>0) BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + + // Create a permutable array of indices to the input vectors. + init_vind(); + } + + /** + * Builds the index + */ + void buildIndex() + { + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + init_vind(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if(BaseClassRef::m_size == 0) return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet * \return True if the requested neighbors could be found. - * \sa knnSearch, radiusSearch - */ - template - bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const - { - assert(vec); + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + assert(vec); if (this->size(*this) == 0) return false; - if (!BaseClassRef::root_node) + if (!BaseClassRef::root_node) throw std::runtime_error("[nanoflann] findNeighbors() called before building the index."); - float epsError = 1 + searchParams.eps; + float epsError = 1 + searchParams.eps; - distance_vector_t dists; // fixed or variable-sized container (depending on DIM) - dists.assign((DIM > 0 ? DIM : BaseClassRef::dim), 0); // Fill it with zeros. - DistanceType distsq = this->computeInitialDistances(*this, vec, dists); - searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. + distance_vector_t dists; // fixed or variable-sized container (depending on DIM) + dists.assign((DIM > 0 ? DIM : BaseClassRef::dim), 0); // Fill it with zeros. + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. return result.full(); - } - - /** - * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside - * the result object. - * \sa radiusSearch, findNeighbors - * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. - * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. - * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. - */ - size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const - { - nanoflann::KNNResultSet resultSet(num_closest); - resultSet.init(out_indices, out_distances_sq); - this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); - return resultSet.size(); - } - - /** - * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. - * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. - * Previous contents of \a IndicesDists are cleared. - * - * If searchParams.sorted==true, the output list is sorted by ascending distances. - * - * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. - * - * \sa knnSearch, findNeighbors, radiusSearchCustomCallback - * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) - */ - size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const - { - RadiusResultSet resultSet(radius, IndicesDists); - const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); - if (searchParams.sorted) - std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); - return nFound; - } - - /** - * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. - * See the source of RadiusResultSet<> as a start point for your own classes. - * \sa radiusSearch - */ - template - size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const - { - this->findNeighbors(resultSet, query_point, searchParams); - return resultSet.size(); - } - - /** @} */ - - public: - /** Make sure the auxiliary list \a vind has the same size than the current dataset, and re-generate if size has changed. */ - void init_vind() - { - // Create a permutable array of indices to the input vectors. - BaseClassRef::m_size = dataset.kdtree_get_point_count(); - if (BaseClassRef::vind.size() != BaseClassRef::m_size) BaseClassRef::vind.resize(BaseClassRef::m_size); - for (size_t i = 0; i < BaseClassRef::m_size; i++) BaseClassRef::vind[i] = i; - } - - void computeBoundingBox(BoundingBox& bbox) - { - bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); - if (dataset.kdtree_get_bbox(bbox)) - { - // Done! It was implemented in derived class - } - else - { - const size_t N = dataset.kdtree_get_point_count(); - if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); - for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { - bbox[i].low = - bbox[i].high = this->dataset_get(*this, 0, i); - } - for (size_t k = 1; k < N; ++k) { - for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { - if (this->dataset_get(*this, k, i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, k, i); - if (this->dataset_get(*this, k, i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, k, i); - } - } - } - } - - /** - * Performs an exact search in the tree starting from a node. - * \tparam RESULTSET Should be any ResultSet + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside + * the result object. + * \sa radiusSearch, findNeighbors + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. + * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) + */ + size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. + * See the source of RadiusResultSet<> as a start point for your own classes. + * \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const + { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + /** Make sure the auxiliary list \a vind has the same size than the current dataset, and re-generate if size has changed. */ + void init_vind() + { + // Create a permutable array of indices to the input vectors. + BaseClassRef::m_size = dataset.kdtree_get_point_count(); + if (BaseClassRef::vind.size() != BaseClassRef::m_size) BaseClassRef::vind.resize(BaseClassRef::m_size); + for (size_t i = 0; i < BaseClassRef::m_size; i++) BaseClassRef::vind[i] = i; + } + + void computeBoundingBox(BoundingBox& bbox) + { + bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); + if (dataset.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const size_t N = dataset.kdtree_get_point_count(); + if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = + bbox[i].high = this->dataset_get(*this, 0, i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, k, i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, k, i); + if (this->dataset_get(*this, k, i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, k, i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet * \return true if the search should be continued, false if the results are sufficient - */ - template + */ + template bool searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, - distance_vector_t& dists, const float epsError) const - { - /* If this is a leaf node, then do check and return. */ - if ((node->child1 == NULL) && (node->child2 == NULL)) { - //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. - DistanceType worst_dist = result_set.worstDist(); - for (IndexType i = node->node_type.lr.left; inode_type.lr.right; ++i) { - const IndexType index = BaseClassRef::vind[i];// reorder... : i; - DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); - if (dist < worst_dist) { + distance_vector_t& dists, const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; inode_type.lr.right; ++i) { + const IndexType index = BaseClassRef::vind[i];// reorder... : i; + DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (dist < worst_dist) { if(!result_set.addPoint(dist, BaseClassRef::vind[i])) { // the resultset doesn't want to receive any more points, we're done searching! return false; } - } - } + } + } return true; - } - - /* Which child branch should be taken first? */ - int idx = node->node_type.sub.divfeat; - ElementType val = vec[idx]; - DistanceType diff1 = val - node->node_type.sub.divlow; - DistanceType diff2 = val - node->node_type.sub.divhigh; - - NodePtr bestChild; - NodePtr otherChild; - DistanceType cut_dist; - if ((diff1 + diff2) < 0) { - bestChild = node->child1; - otherChild = node->child2; - cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); - } - else { - bestChild = node->child2; - otherChild = node->child1; - cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); - } - - /* Call recursively to search next level down. */ + } + + /* Which child branch should be taken first? */ + int idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ if(!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) { // the resultset doesn't want to receive any more points, we're done searching! return false; } - DistanceType dst = dists[idx]; - mindistsq = mindistsq + cut_dist - dst; - dists[idx] = cut_dist; - if (mindistsq*epsError <= result_set.worstDist()) { + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq*epsError <= result_set.worstDist()) { if(!searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError)) { // the resultset doesn't want to receive any more points, we're done searching! return false; } - } - dists[idx] = dst; + } + dists[idx] = dst; return true; - } - - public: - /** Stores the index in a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void saveIndex(FILE* stream) - { - this->saveIndex_(*this, stream); - } - - /** Loads a previous index from a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void loadIndex(FILE* stream) - { - this->loadIndex_(*this, stream); - } - - }; // class KDTree - - - /** kd-tree dynamic index - * - * Contains the k-d trees and other information for indexing a set of points - * for nearest-neighbor matching. - * - * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): - * - * \code - * // Must return the number of data poins - * inline size_t kdtree_get_point_count() const { ... } - * - * // Must return the dim'th component of the idx'th point in the class: - * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } - * - * // Optional bounding-box computation: return false to default to a standard bbox computation loop. - * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. - * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) - * template - * bool kdtree_get_bbox(BBOX &bb) const - * { - * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits - * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits - * ... - * return true; - * } - * - * \endcode - * - * \tparam DatasetAdaptor The user-provided adaptor (see comments above). - * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. - * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) - * \tparam IndexType Will be typically size_t or int - */ - template - class KDTreeSingleIndexDynamicAdaptor_ : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> - { - public: - - /** - * The dataset used by this index - */ - const DatasetAdaptor &dataset; //!< The source of our data - - KDTreeSingleIndexAdaptorParams index_params; - - std::vector &treeIndex; - - Distance distance; - - typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; - - typedef typename BaseClassRef::ElementType ElementType; - typedef typename BaseClassRef::DistanceType DistanceType; - - typedef typename BaseClassRef::Node Node; - typedef Node* NodePtr; - - typedef typename BaseClassRef::Interval Interval; - /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename BaseClassRef::BoundingBox BoundingBox; - - /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ - typedef typename BaseClassRef::distance_vector_t distance_vector_t; - - /** - * KDTree constructor - * - * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann - * - * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) - * is determined by means of: - * - The \a DIM template parameter if >0 (highest priority) - * - Otherwise, the \a dimensionality parameter of this constructor. - * - * @param inputData Dataset with the input features - * @param params Basically, the maximum leaf node size - */ - KDTreeSingleIndexDynamicAdaptor_(const int dimensionality, const DatasetAdaptor& inputData, std::vector& treeIndex_, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams()) : - dataset(inputData), index_params(params), treeIndex(treeIndex_), distance(inputData) - { - BaseClassRef::root_node = NULL; - BaseClassRef::m_size = 0; - BaseClassRef::m_size_at_index_build = 0; - BaseClassRef::dim = dimensionality; - if (DIM>0) BaseClassRef::dim = DIM; - BaseClassRef::m_leaf_max_size = params.leaf_max_size; - } - - - /** Assignment operator definiton */ - KDTreeSingleIndexDynamicAdaptor_ operator=( const KDTreeSingleIndexDynamicAdaptor_& rhs ) { - KDTreeSingleIndexDynamicAdaptor_ tmp( rhs ); - std::swap( BaseClassRef::vind, tmp.BaseClassRef::vind ); - std::swap( BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size ); - std::swap( index_params, tmp.index_params ); - std::swap( treeIndex, tmp.treeIndex ); - std::swap( BaseClassRef::m_size, tmp.BaseClassRef::m_size ); - std::swap( BaseClassRef::m_size_at_index_build, tmp.BaseClassRef::m_size_at_index_build ); - std::swap( BaseClassRef::root_node, tmp.BaseClassRef::root_node ); - std::swap( BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox ); - std::swap( BaseClassRef::pool, tmp.BaseClassRef::pool ); - return *this; - } - - /** - * Builds the index - */ - void buildIndex() - { - BaseClassRef::m_size = BaseClassRef::vind.size(); - this->freeIndex(*this); - BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; - if(BaseClassRef::m_size == 0) return; - computeBoundingBox(BaseClassRef::root_bbox); - BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree - } - - /** \name Query methods - * @{ */ - - /** - * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside - * the result object. - * - * Params: - * result = the result object in which the indices of the nearest-neighbors are stored - * vec = the vector for which to search the nearest neighbors - * - * \tparam RESULTSET Should be any ResultSet + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex(FILE* stream) + { + this->saveIndex_(*this, stream); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex(FILE* stream) + { + this->loadIndex_(*this, stream); + } + + }; // class KDTree + + + /** kd-tree dynamic index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * inline size_t kdtree_get_point_count() const { ... } + * + * // Must return the dim'th component of the idx'th point in the class: + * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + * template + * bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexDynamicAdaptor_ : public KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> + { + public: + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + KDTreeSingleIndexAdaptorParams index_params; + + std::vector &treeIndex; + + Distance distance; + + typedef typename nanoflann::KDTreeBaseClass, Distance, DatasetAdaptor, DIM, IndexType> BaseClassRef; + + typedef typename BaseClassRef::ElementType ElementType; + typedef typename BaseClassRef::DistanceType DistanceType; + + typedef typename BaseClassRef::Node Node; + typedef Node* NodePtr; + + typedef typename BaseClassRef::Interval Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::BoundingBox BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container depending on "DIM" */ + typedef typename BaseClassRef::distance_vector_t distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor_(const int dimensionality, const DatasetAdaptor& inputData, std::vector& treeIndex_, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams()) : + dataset(inputData), index_params(params), treeIndex(treeIndex_), distance(inputData) + { + BaseClassRef::root_node = NULL; + BaseClassRef::m_size = 0; + BaseClassRef::m_size_at_index_build = 0; + BaseClassRef::dim = dimensionality; + if (DIM>0) BaseClassRef::dim = DIM; + BaseClassRef::m_leaf_max_size = params.leaf_max_size; + } + + + /** Assignment operator definiton */ + KDTreeSingleIndexDynamicAdaptor_ operator=( const KDTreeSingleIndexDynamicAdaptor_& rhs ) { + KDTreeSingleIndexDynamicAdaptor_ tmp( rhs ); + std::swap( BaseClassRef::vind, tmp.BaseClassRef::vind ); + std::swap( BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size ); + std::swap( index_params, tmp.index_params ); + std::swap( treeIndex, tmp.treeIndex ); + std::swap( BaseClassRef::m_size, tmp.BaseClassRef::m_size ); + std::swap( BaseClassRef::m_size_at_index_build, tmp.BaseClassRef::m_size_at_index_build ); + std::swap( BaseClassRef::root_node, tmp.BaseClassRef::root_node ); + std::swap( BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox ); + std::swap( BaseClassRef::pool, tmp.BaseClassRef::pool ); + return *this; + } + + /** + * Builds the index + */ + void buildIndex() + { + BaseClassRef::m_size = BaseClassRef::vind.size(); + this->freeIndex(*this); + BaseClassRef::m_size_at_index_build = BaseClassRef::m_size; + if(BaseClassRef::m_size == 0) return; + computeBoundingBox(BaseClassRef::root_bbox); + BaseClassRef::root_node = this->divideTree(*this, 0, BaseClassRef::m_size, BaseClassRef::root_bbox ); // construct the tree + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet * \return True if the requested neighbors could be found. - * \sa knnSearch, radiusSearch - */ - template - bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const - { - assert(vec); + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + assert(vec); if (this->size(*this) == 0) return false; - if (!BaseClassRef::root_node) + if (!BaseClassRef::root_node) return false; - float epsError = 1 + searchParams.eps; + float epsError = 1 + searchParams.eps; - distance_vector_t dists; // fixed or variable-sized container (depending on DIM) - dists.assign((DIM > 0 ? DIM : BaseClassRef::dim) , 0); // Fill it with zeros. - DistanceType distsq = this->computeInitialDistances(*this, vec, dists); - searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. + distance_vector_t dists; // fixed or variable-sized container (depending on DIM) + dists.assign((DIM > 0 ? DIM : BaseClassRef::dim) , 0); // Fill it with zeros. + DistanceType distsq = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, BaseClassRef::root_node, distsq, dists, epsError); // "count_leaf" parameter removed since was neither used nor returned to the user. return result.full(); - } - - /** - * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside - * the result object. - * \sa radiusSearch, findNeighbors - * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. - * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. - * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. - */ - size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const - { - nanoflann::KNNResultSet resultSet(num_closest); - resultSet.init(out_indices, out_distances_sq); - this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); - return resultSet.size(); - } - - /** - * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. - * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. - * Previous contents of \a IndicesDists are cleared. - * - * If searchParams.sorted==true, the output list is sorted by ascending distances. - * - * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. - * - * \sa knnSearch, findNeighbors, radiusSearchCustomCallback - * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) - */ - size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const - { - RadiusResultSet resultSet(radius, IndicesDists); - const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); - if (searchParams.sorted) - std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); - return nFound; - } - - /** - * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. - * See the source of RadiusResultSet<> as a start point for your own classes. - * \sa radiusSearch - */ - template - size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const - { - this->findNeighbors(resultSet, query_point, searchParams); - return resultSet.size(); - } - - /** @} */ - - public: - - - void computeBoundingBox(BoundingBox& bbox) - { - bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); - if (dataset.kdtree_get_bbox(bbox)) - { - // Done! It was implemented in derived class - } - else - { - const size_t N = BaseClassRef::m_size; - if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); - for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { - bbox[i].low = - bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[0], i); - } - for (size_t k = 1; k < N; ++k) { - for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { - if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i); - if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i); - } - } - } - } - - /** - * Performs an exact search in the tree starting from a node. - * \tparam RESULTSET Should be any ResultSet - */ - template - void searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, - distance_vector_t& dists, const float epsError) const - { - /* If this is a leaf node, then do check and return. */ - if ((node->child1 == NULL) && (node->child2 == NULL)) { - //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. - DistanceType worst_dist = result_set.worstDist(); - for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) { - const IndexType index = BaseClassRef::vind[i];// reorder... : i; - if(treeIndex[index] == -1) - continue; - DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); - if (distnode_type.sub.divfeat; - ElementType val = vec[idx]; - DistanceType diff1 = val - node->node_type.sub.divlow; - DistanceType diff2 = val - node->node_type.sub.divhigh; - - NodePtr bestChild; - NodePtr otherChild; - DistanceType cut_dist; - if ((diff1 + diff2) < 0) { - bestChild = node->child1; - otherChild = node->child2; - cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); - } - else { - bestChild = node->child2; - otherChild = node->child1; - cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); - } - - /* Call recursively to search next level down. */ - searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError); - - DistanceType dst = dists[idx]; - mindistsq = mindistsq + cut_dist - dst; - dists[idx] = cut_dist; - if (mindistsq*epsError <= result_set.worstDist()) { - searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError); - } - dists[idx] = dst; - } - - public: - /** Stores the index in a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void saveIndex(FILE* stream) - { - this->saveIndex_(*this, stream); - } - - /** Loads a previous index from a binary file. - * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. - * See the example: examples/saveload_example.cpp - * \sa loadIndex */ - void loadIndex(FILE* stream) - { - this->loadIndex_(*this, stream); - } - - }; - - - /** kd-tree dynaimic index - * - * class to create multiple static index and merge their results to behave as single dynamic index as proposed in Logarithmic Approach. - * - * Example of usage: - * examples/dynamic_pointcloud_example.cpp - * - * \tparam DatasetAdaptor The user-provided adaptor (see comments above). - * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. - * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) - * \tparam IndexType Will be typically size_t or int - */ - template - class KDTreeSingleIndexDynamicAdaptor - { - public: - typedef typename Distance::ElementType ElementType; - typedef typename Distance::DistanceType DistanceType; - protected: - - size_t m_leaf_max_size; - size_t treeCount; - size_t pointCount; - - /** - * The dataset used by this index - */ - const DatasetAdaptor &dataset; //!< The source of our data - - std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which point at idx is stored. treeIndex[idx]=-1 means that point has been removed. - - KDTreeSingleIndexAdaptorParams index_params; - - int dim; //!< Dimensionality of each data point - - typedef KDTreeSingleIndexDynamicAdaptor_ index_container_t; - std::vector index; - - public: - /** Get a const ref to the internal list of indices; the number of indices is adapted dynamically as - * the dataset grows in size. */ - const std::vector & getAllIndices() const { - return index; - } - - private: - /** finds position of least significant unset bit */ - int First0Bit(IndexType num) - { - int pos = 0; - while(num&1) - { - num = num>>1; - pos++; - } - return pos; - } - - /** Creates multiple empty trees to handle dynamic support */ - void init() - { - typedef KDTreeSingleIndexDynamicAdaptor_ my_kd_tree_t; - std::vector index_(treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params)); - index=index_; - } - - public: - - Distance distance; - - /** - * KDTree constructor - * - * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann - * - * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) - * is determined by means of: - * - The \a DIM template parameter if >0 (highest priority) - * - Otherwise, the \a dimensionality parameter of this constructor. - * - * @param inputData Dataset with the input features - * @param params Basically, the maximum leaf node size - */ - KDTreeSingleIndexDynamicAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() , const size_t maximumPointCount = 1000000000U) : - dataset(inputData), index_params(params), distance(inputData) - { - treeCount = std::log2(maximumPointCount); - pointCount = 0U; - dim = dimensionality; - treeIndex.clear(); - if (DIM > 0) dim = DIM; - m_leaf_max_size = params.leaf_max_size; - init(); - int num_initial_points = dataset.kdtree_get_point_count(); - if (num_initial_points > 0) { - addPoints(0, num_initial_points - 1); - } - } - - /** Deleted copy constructor*/ - KDTreeSingleIndexDynamicAdaptor(const KDTreeSingleIndexDynamicAdaptor&) = delete; - - - /** Add points to the set, Inserts all points from [start, end] */ - void addPoints(IndexType start, IndexType end) - { - int count = end - start + 1; - treeIndex.resize(treeIndex.size() + count); - for(IndexType idx = start; idx <= end; idx++) { - int pos = First0Bit(pointCount); - index[pos].vind.clear(); - treeIndex[pointCount]=pos; - for(int i = 0; i < pos; i++) { - for(int j = 0; j < static_cast(index[i].vind.size()); j++) { - index[pos].vind.push_back(index[i].vind[j]); - treeIndex[index[i].vind[j]] = pos; - } - index[i].vind.clear(); - index[i].freeIndex(index[i]); - } - index[pos].vind.push_back(idx); - index[pos].buildIndex(); - pointCount++; - } - } - - /** Remove a point from the set (Lazy Deletion) */ - void removePoint(size_t idx) - { - if(idx >= pointCount) - return; - treeIndex[idx] = -1; - } - - /** - * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside - * the result object. - * - * Params: - * result = the result object in which the indices of the nearest-neighbors are stored - * vec = the vector for which to search the nearest neighbors - * - * \tparam RESULTSET Should be any ResultSet + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. Their indices are stored inside + * the result object. + * \sa radiusSearch, findNeighbors + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + * \return Number `N` of valid points in the result set. Only the first `N` entries in `out_indices` and `out_distances_sq` will be valid. + * Return may be less than `num_closest` only if the number of elements in the tree is less than `num_closest`. + */ + size_t knnSearch(const ElementType *query_point, const size_t num_closest, IndexType *out_indices, DistanceType *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + this->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum radius. + * The output is given as a vector of pairs, of which the first element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending distances. + * + * For a better performance, it is advisable to do a .reserve() on the vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() or dists.size() ) + */ + size_t radiusSearch(const ElementType *query_point, const DistanceType &radius, std::vector >& IndicesDists, const SearchParams& searchParams) const + { + RadiusResultSet resultSet(radius, IndicesDists); + const size_t nFound = radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter() ); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point found in the radius of the query. + * See the source of RadiusResultSet<> as a start point for your own classes. + * \sa radiusSearch + */ + template + size_t radiusSearchCustomCallback(const ElementType *query_point, SEARCH_CALLBACK &resultSet, const SearchParams& searchParams = SearchParams() ) const + { + this->findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + + + void computeBoundingBox(BoundingBox& bbox) + { + bbox.resize((DIM > 0 ? DIM : BaseClassRef::dim)); + if (dataset.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const size_t N = BaseClassRef::m_size; + if (!N) throw std::runtime_error("[nanoflann] computeBoundingBox() called but no data points found."); + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + bbox[i].low = + bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[0], i); + } + for (size_t k = 1; k < N; ++k) { + for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) { + if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low) bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i); + if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high) bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i); + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + */ + template + void searchLevel(RESULTSET& result_set, const ElementType* vec, const NodePtr node, DistanceType mindistsq, + distance_vector_t& dists, const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == NULL) && (node->child2 == NULL)) { + //count_leaf += (node->lr.right-node->lr.left); // Removed since was neither used nor returned to the user. + DistanceType worst_dist = result_set.worstDist(); + for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right; ++i) { + const IndexType index = BaseClassRef::vind[i];// reorder... : i; + if(treeIndex[index] == -1) + continue; + DistanceType dist = distance.evalMetric(vec, index, (DIM > 0 ? DIM : BaseClassRef::dim)); + if (distnode_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = distance.accum_dist( val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError); + + DistanceType dst = dists[idx]; + mindistsq = mindistsq + cut_dist - dst; + dists[idx] = cut_dist; + if (mindistsq*epsError <= result_set.worstDist()) { + searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError); + } + dists[idx] = dst; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so when loading the index object it must be constructed associated to the same source of data points used while building it. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void saveIndex(FILE* stream) + { + this->saveIndex_(*this, stream); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so the index object must be constructed associated to the same source of data points used while building the index. + * See the example: examples/saveload_example.cpp + * \sa loadIndex */ + void loadIndex(FILE* stream) + { + this->loadIndex_(*this, stream); + } + + }; + + + /** kd-tree dynaimic index + * + * class to create multiple static index and merge their results to behave as single dynamic index as proposed in Logarithmic Approach. + * + * Example of usage: + * examples/dynamic_pointcloud_example.cpp + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Will be typically size_t or int + */ + template + class KDTreeSingleIndexDynamicAdaptor + { + public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::DistanceType DistanceType; + protected: + + size_t m_leaf_max_size; + size_t treeCount; + size_t pointCount; + + /** + * The dataset used by this index + */ + const DatasetAdaptor &dataset; //!< The source of our data + + std::vector treeIndex; //!< treeIndex[idx] is the index of tree in which point at idx is stored. treeIndex[idx]=-1 means that point has been removed. + + KDTreeSingleIndexAdaptorParams index_params; + + int dim; //!< Dimensionality of each data point + + typedef KDTreeSingleIndexDynamicAdaptor_ index_container_t; + std::vector index; + + public: + /** Get a const ref to the internal list of indices; the number of indices is adapted dynamically as + * the dataset grows in size. */ + const std::vector & getAllIndices() const { + return index; + } + + private: + /** finds position of least significant unset bit */ + int First0Bit(IndexType num) + { + int pos = 0; + while(num&1) + { + num = num>>1; + pos++; + } + return pos; + } + + /** Creates multiple empty trees to handle dynamic support */ + void init() + { + typedef KDTreeSingleIndexDynamicAdaptor_ my_kd_tree_t; + std::vector index_(treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params)); + index=index_; + } + + public: + + Distance distance; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. 3 for 3D points) + * is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor(const int dimensionality, const DatasetAdaptor& inputData, const KDTreeSingleIndexAdaptorParams& params = KDTreeSingleIndexAdaptorParams() , const size_t maximumPointCount = 1000000000U) : + dataset(inputData), index_params(params), distance(inputData) + { + treeCount = std::log2(maximumPointCount); + pointCount = 0U; + dim = dimensionality; + treeIndex.clear(); + if (DIM > 0) dim = DIM; + m_leaf_max_size = params.leaf_max_size; + init(); + int num_initial_points = dataset.kdtree_get_point_count(); + if (num_initial_points > 0) { + addPoints(0, num_initial_points - 1); + } + } + + /** Deleted copy constructor*/ + KDTreeSingleIndexDynamicAdaptor(const KDTreeSingleIndexDynamicAdaptor&) = delete; + + + /** Add points to the set, Inserts all points from [start, end] */ + void addPoints(IndexType start, IndexType end) + { + int count = end - start + 1; + treeIndex.resize(treeIndex.size() + count); + for(IndexType idx = start; idx <= end; idx++) { + int pos = First0Bit(pointCount); + index[pos].vind.clear(); + treeIndex[pointCount]=pos; + for(int i = 0; i < pos; i++) { + for(int j = 0; j < static_cast(index[i].vind.size()); j++) { + index[pos].vind.push_back(index[i].vind[j]); + treeIndex[index[i].vind[j]] = pos; + } + index[i].vind.clear(); + index[i].freeIndex(index[i]); + } + index[pos].vind.push_back(idx); + index[pos].buildIndex(); + pointCount++; + } + } + + /** Remove a point from the set (Lazy Deletion) */ + void removePoint(size_t idx) + { + if(idx >= pointCount) + return; + treeIndex[idx] = -1; + } + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored inside + * the result object. + * + * Params: + * result = the result object in which the indices of the nearest-neighbors are stored + * vec = the vector for which to search the nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet * \return True if the requested neighbors could be found. - * \sa knnSearch, radiusSearch - */ - template - bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const - { - for(size_t i = 0; i < treeCount; i++) - { - index[i].findNeighbors(result, &vec[0], searchParams); - } - return result.full(); - } - - }; - - /** An L2-metric KD-tree adaptor for working with data directly stored in an Eigen Matrix, without duplicating the data storage. - * Each row in the matrix represents a point in the state space. - * - * Example of usage: - * \code - * Eigen::Matrix mat; - * // Fill out "mat"... - * - * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; - * const int max_leaf = 10; - * my_kd_tree_t mat_index(mat, max_leaf ); - * mat_index.index->buildIndex(); - * mat_index.index->... - * \endcode - * - * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. - * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. - */ - template - struct KDTreeEigenMatrixAdaptor - { - typedef KDTreeEigenMatrixAdaptor self_t; - typedef typename MatrixType::Scalar num_t; - typedef typename MatrixType::Index IndexType; - typedef typename Distance::template traits::distance_t metric_t; - typedef KDTreeSingleIndexAdaptor< metric_t,self_t, MatrixType::ColsAtCompileTime,IndexType> index_t; - - index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. - - /// Constructor: takes a const ref to the matrix object with the data points - KDTreeEigenMatrixAdaptor(const MatrixType &mat, const int leaf_max_size = 10) : m_data_matrix(mat) - { - const IndexType dims = mat.cols(); - index = new index_t( dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); - index->buildIndex(); - } - public: - /** Deleted copy constructor */ - KDTreeEigenMatrixAdaptor(const self_t&) = delete; - - ~KDTreeEigenMatrixAdaptor() { - delete index; - } - - const MatrixType &m_data_matrix; - - /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). - * Note that this is a short-cut method for index->findNeighbors(). - * The user can also call index->... methods as desired. - * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. - */ - inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const - { - nanoflann::KNNResultSet resultSet(num_closest); - resultSet.init(out_indices, out_distances_sq); - index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); - } - - /** @name Interface expected by KDTreeSingleIndexAdaptor - * @{ */ - - const self_t & derived() const { - return *this; - } - self_t & derived() { - return *this; - } - - // Must return the number of data points - inline size_t kdtree_get_point_count() const { - return m_data_matrix.rows(); - } - - // Returns the dim'th component of the idx'th point in the class: - inline num_t kdtree_get_pt(const IndexType idx, int dim) const { - return m_data_matrix.coeff(idx, IndexType(dim)); - } - - // Optional bounding-box computation: return false to default to a standard bbox computation loop. - // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. - // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) - template - bool kdtree_get_bbox(BBOX& /*bb*/) const { - return false; - } - - /** @} */ - - }; // end of KDTreeEigenMatrixAdaptor - /** @} */ + * \sa knnSearch, radiusSearch + */ + template + bool findNeighbors(RESULTSET& result, const ElementType* vec, const SearchParams& searchParams) const + { + for(size_t i = 0; i < treeCount; i++) + { + index[i].findNeighbors(result, &vec[0], searchParams); + } + return result.full(); + } + + }; + + /** An L2-metric KD-tree adaptor for working with data directly stored in an Eigen Matrix, without duplicating the data storage. + * Each row in the matrix represents a point in the state space. + * + * Example of usage: + * \code + * Eigen::Matrix mat; + * // Fill out "mat"... + * + * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; + * const int max_leaf = 10; + * my_kd_tree_t mat_index(mat, max_leaf ); + * mat_index.index->buildIndex(); + * mat_index.index->... + * \endcode + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + */ + template + struct KDTreeEigenMatrixAdaptor + { + typedef KDTreeEigenMatrixAdaptor self_t; + typedef typename MatrixType::Scalar num_t; + typedef typename MatrixType::Index IndexType; + typedef typename Distance::template traits::distance_t metric_t; + typedef KDTreeSingleIndexAdaptor< metric_t,self_t, MatrixType::ColsAtCompileTime,IndexType> index_t; + + index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. + + /// Constructor: takes a const ref to the matrix object with the data points + KDTreeEigenMatrixAdaptor(const MatrixType &mat, const int leaf_max_size = 10) : m_data_matrix(mat) + { + const IndexType dims = mat.cols(); + index = new index_t( dims, *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); + index->buildIndex(); + } + public: + /** Deleted copy constructor */ + KDTreeEigenMatrixAdaptor(const self_t&) = delete; + + ~KDTreeEigenMatrixAdaptor() { + delete index; + } + + const MatrixType &m_data_matrix; + + /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). + * Note that this is a short-cut method for index->findNeighbors(). + * The user can also call index->... methods as desired. + * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. + */ + inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int /* nChecks_IGNORED */ = 10) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances_sq); + index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t & derived() const { + return *this; + } + self_t & derived() { + return *this; + } + + // Must return the number of data points + inline size_t kdtree_get_point_count() const { + return m_data_matrix.rows(); + } + + // Returns the dim'th component of the idx'th point in the class: + inline num_t kdtree_get_pt(const IndexType idx, int dim) const { + return m_data_matrix.coeff(idx, IndexType(dim)); + } + + // Optional bounding-box computation: return false to default to a standard bbox computation loop. + // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. + // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + bool kdtree_get_bbox(BBOX& /*bb*/) const { + return false; + } + + /** @} */ + + }; // end of KDTreeEigenMatrixAdaptor + /** @} */ /** @} */ // end of grouping } // end of NS diff --git a/research/cv/WS3/third_party/nearest_neighbors/setup.py b/research/cv/WS3/third_party/nearest_neighbors/setup.py index 0bfc4ca34..78f8c4cbb 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/setup.py +++ b/research/cv/WS3/third_party/nearest_neighbors/setup.py @@ -5,10 +5,10 @@ import numpy ext_modules = [Extension( "nearest_neighbors", - sources=["knn.pyx", "knn_.cxx", ], # source file(s) + sources=["knn.pyx", "knn_.cxx"], # source file(s) include_dirs=["./", numpy.get_include()], language="c++", - extra_compile_args=["-std=c++11", "-fopenmp", ], + extra_compile_args=["-std=c++11", "-fopenmp"], extra_link_args=["-std=c++11", '-fopenmp'], )] diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py index 6033ecf2f..151c5820c 100644 --- a/research/cv/WS3/train_ascend.py +++ b/research/cv/WS3/train_ascend.py @@ -1,12 +1,11 @@ # -*-coding:utf-8-*- - -import sys - -import datetime, os, argparse, pickle, shutil +import os +import datetime +import argparse +import pickle from pathlib import Path import numpy as np -import mindspore as ms from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops, set_seed from mindspore.nn import Adam from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback @@ -16,10 +15,10 @@ from mindspore import dtype as mstype from src.data.S3DIS_dataset import dataloader, ms_map from src.model.base_model import get_param_groups from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as cfg +from src.utils.tools import ConfigS3DIS as config from src.utils.logger import get_logger -from src.model.model_s3dis_remove_bias import RandLANetS3DIS as RandLANet_S3DIS -from src.model.model_s3dis_remove_bias import RandLAS3DISWithLoss as RandLA_S3DIS_WithLoss +from src.model.model_s3dis_remove_bias import WS3 +from src.model.model_s3dis_remove_bias import WS3WithLoss use_custom_train_one_step_cell = True @@ -47,10 +46,10 @@ class CustomTrainOneStepCell(nn.Cell): class UpdateLossEpoch(Callback): - def __init__(self, num_training_ep0=30, logger=None): + def __init__(self, cur_num_training_ep0=30, logger=None): super(UpdateLossEpoch, self).__init__() self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - self.training_ep.update({i: 0 for i in range(0, num_training_ep0)}) + self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) self.logger = logger # v1.8: on_train_epoch_begin @@ -133,12 +132,11 @@ def prepare_network(weights, cfg, args): """Prepare Network""" d_in = 6 # xyzrgb - network = RandLANet_S3DIS(d_in, cfg.num_classes) + network = WS3(d_in, cfg.num_classes) if args.ss_pretrain: print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") param_dict = load_checkpoint(args.ss_pretrain) whitelist = ["encoder"] - load_all = True new_param_dict = dict() for key, val in param_dict.items(): if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: @@ -146,12 +144,12 @@ def prepare_network(weights, cfg, args): new_param_dict[new_key] = val load_param_into_net(network, new_param_dict, strict_load=True) - network = RandLA_S3DIS_WithLoss(network, - weights, - cfg.num_classes, - cfg.ignored_label_indexs, - cfg.c_epoch, - cfg.topk) + network = WS3WithLoss(network, + weights, + cfg.num_classes, + cfg.ignored_label_indexs, + cfg.c_epoch, + cfg.topk) if args.retrain_model: print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") @@ -176,7 +174,7 @@ def train(cfg, args): for c in vars(cfg): logger.info('%s: %s' % (c, getattr(cfg, c))) - train_loader, val_loader, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) + train_loader, _, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in getattr(dataset, 'ignored_labels')] cfg.ignored_label_indexs = ignored_label_indexs @@ -269,98 +267,93 @@ def train(cfg, args): if __name__ == "__main__": - """Parse program arguments""" + # """Parse program arguments""" parser = argparse.ArgumentParser( prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - expr = parser.add_argument_group('Experiment parameters') - param = parser.add_argument_group('Hyperparameters') - dirs = parser.add_argument_group('Storage directories') - misc = parser.add_argument_group('Miscellaneous') - expr.add_argument('--epochs', type=int, help='max epochs', default=100) + parser.add_argument('--epochs', type=int, help='max epochs', default=100) - expr.add_argument('--batch_size', type=int, help='batch size', default=6) + parser.add_argument('--batch_size', type=int, help='batch size', default=6) - expr.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') - expr.add_argument('--outputs_dir', type=str, help='path of output', default='outputs') + parser.add_argument('--outputs_dir', type=str, help='path of output', default='outputs') - expr.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - expr.add_argument('--resume', type=str, help='model to resume', default=None) + parser.add_argument('--resume', type=str, help='model to resume', default=None) - expr.add_argument('--scale', type=bool, help='scale or not', default=False) + parser.add_argument('--scale', type=bool, help='scale or not', default=False) - # expr.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) + parser.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') - misc.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - misc.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) - misc.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--name', type=str, help='name of the experiment', + default=None) + parser.add_argument('--ss_pretrain', type=str, help='name of the experiment', + default=None) + parser.add_argument('--retrain_model', type=str, help='name of the experiment', + default=None) + parser.add_argument('--float16', type=bool, default=False) - misc.add_argument('--name', type=str, help='name of the experiment', - default=None) - misc.add_argument('--ss_pretrain', type=str, help='name of the experiment', - default=None) - misc.add_argument('--retrain_model', type=str, help='name of the experiment', - default=None) - misc.add_argument('--float16', type=bool, default=False) + parser.add_argument('--train_steps', type=int, default=500) + parser.add_argument('--learning_rate', type=float, default=0.01) + parser.add_argument('--lr_decays', type=float, default=0.95) + parser.add_argument('--loss_scale', type=float, default=1.0) + parser.add_argument('--topk', type=int, default=500) + parser.add_argument('--num_training_ep0', type=int, default=30) + parser.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] + parser.add_argument('--random_seed', type=int, default=888) + parser.add_argument('--graph_mode', action='store_true', default=False) - misc.add_argument('--train_steps', type=int, default=500) - misc.add_argument('--learning_rate', type=float, default=0.01) - misc.add_argument('--lr_decays', type=float, default=0.95) - misc.add_argument('--loss_scale', type=float, default=1.0) - misc.add_argument('--topk', type=int, default=500) - misc.add_argument('--num_training_ep0', type=int, default=30) - misc.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] - misc.add_argument('--random_seed', type=int, default=888) - misc.add_argument('--graph_mode', action='store_true', default=False) + arguments = parser.parse_args() - args = parser.parse_args() + config.dataset_dir = arguments.dataset_dir + config.batch_size = arguments.batch_size + config.max_epoch = arguments.epochs + config.train_steps = arguments.train_steps + config.learning_rate = arguments.learning_rate + config.lr_decays = arguments.lr_decays + config.loss_scale = arguments.loss_scale + config.topk = arguments.topk + num_training_ep0 = arguments.num_training_ep0 + config.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} + config.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + config.training_ep.update(config.training_ep0) + config.labeled_percent = arguments.labeled_percent + config.random_seed = arguments.random_seed + config.graph_mode = arguments.graph_mode + config.float16 = arguments.float16 - cfg.dataset_dir = args.dataset_dir - cfg.batch_size = args.batch_size - cfg.max_epoch = args.epochs - cfg.train_steps = args.train_steps - cfg.learning_rate = args.learning_rate - cfg.lr_decays = args.lr_decays - cfg.loss_scale = args.loss_scale - cfg.topk = args.topk - num_training_ep0 = args.num_training_ep0 - cfg.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} - cfg.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - cfg.training_ep.update(cfg.training_ep0) - cfg.labeled_percent = args.labeled_percent - cfg.random_seed = args.random_seed - cfg.graph_mode = args.graph_mode - cfg.float16 = args.float16 - - if args.name is None: - if args.resume: - args.name = Path(args.resume).split('/')[-1] + if arguments.name is None: + if arguments.resume: + arguments.name = Path(arguments.resume).split('/')[-1] else: time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) - args.name = f'TSteps{cfg.train_steps}_MaxEpoch{cfg.max_epoch}_BatchS{cfg.batch_size}_lr{cfg.learning_rate}' \ - f'_lrd{cfg.lr_decays}_ls{cfg.loss_scale}_Topk{cfg.topk}_NumTrainEp0{num_training_ep0}_LP_{cfg.labeled_percent}_RS_{cfg.random_seed}' - if cfg.graph_mode: - args.name += "_GraphM" + arguments.name = f'TSteps{config.train_steps}_MaxEpoch{config.max_epoch}_BatchS{config.batch_size}' \ + f'_TopK{config.topk}_NumTrainEp0{num_training_ep0}' \ + f'_LP{config.labeled_percent}_RS{config.random_seed}' + if config.graph_mode: + arguments.name += "_GraphM" else: - args.name += "_PyNateiveM" - args.name += f'_{time_str}' + arguments.name += "_PyNateiveM" + arguments.name += f'_{time_str}' - np.random.seed(cfg.random_seed) - set_seed(cfg.random_seed) + np.random.seed(config.random_seed) + set_seed(config.random_seed) - args.outputs_dir = os.path.join(args.outputs_dir, args.name) + arguments.outputs_dir = os.path.join(arguments.outputs_dir, arguments.name) - print(f"outputs_dir:{args.outputs_dir}") - if not os.path.exists(args.outputs_dir): - os.makedirs(args.outputs_dir) + print(f"outputs_dir:{arguments.outputs_dir}") + if not os.path.exists(arguments.outputs_dir): + os.makedirs(arguments.outputs_dir) - if args.resume: - args.outputs_dir = args.resume + if arguments.resume: + arguments.outputs_dir = arguments.resume - train(cfg, args) + train(config, arguments) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py index 96b2baf32..a19986094 100644 --- a/research/cv/WS3/train_gpu.py +++ b/research/cv/WS3/train_gpu.py @@ -1,34 +1,30 @@ # -*-coding:utf-8-*- -""" - Author: chenhaomingbob - E-mail: chenhaomingbob@163.com - Time: 2022/06/23 - Description: - -""" -import datetime, os, argparse, pickle, shutil +import os +import datetime +import argparse +import pickle from pathlib import Path import numpy as np -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops, set_seed +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed from mindspore.nn import Adam from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback from mindspore.train.loss_scale_manager import FixedLossScaleManager from src.data.S3DIS_dataset import dataloader, ms_map from src.model.base_model import get_param_groups -from src.model.model_s3dis import RandLANetS3DIS as RandLANet_S3DIS -from src.model.model_s3dis import RandLAS3DISWithLoss as RandLA_S3DIS_WithLoss +from src.model.model_s3dis import WS3 +from src.model.model_s3dis import WS3WithLoss from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as cfg +from src.utils.tools import ConfigS3DIS as config from src.utils.logger import get_logger class UpdateLossEpoch(Callback): - def __init__(self, num_training_ep0=30, logger=None): + def __init__(self, cur_num_training_ep0=30, logger=None): super(UpdateLossEpoch, self).__init__() self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - self.training_ep.update({i: 0 for i in range(0, num_training_ep0)}) + self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) self.logger = logger def on_train_epoch_begin(self, run_context): @@ -85,15 +81,8 @@ class S3DISLossMonitor(Callback): self._last_print_time = cb_params.cur_step_num self.train_network_with_loss = cb_params.network - if isinstance(self.train_network_with_loss, Tensor): - msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, " \ - f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS.asnumpy()}; SP Loss:{self.train_network_with_loss.SP_LOSS.asnumpy()})" - else: - msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, " \ - f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS}; SP Loss:{self.train_network_with_loss.SP_LOSS})" - # f"loss is {loss} (CE Loss:{self.train_network_with_loss.CE_LOSS.asnumpy()}; SP Loss:{self.train_network_with_loss.SP_LOSS.asnumpy()})" - # self.train_network_with_loss.CE_LOSS.dtype == Parameters - # self.train_network_with_loss.SP_LOSS.dtype == Parameters + msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, loss is {loss}" + self.logger.info(msg) @@ -101,12 +90,11 @@ def prepare_network(weights, cfg, args): """Prepare Network""" d_in = 6 # xyzrgb - network = RandLANet_S3DIS(d_in, cfg.num_classes) + network = WS3(d_in, cfg.num_classes) if args.ss_pretrain: print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") param_dict = load_checkpoint(args.ss_pretrain) whitelist = ["encoder"] - load_all = True new_param_dict = dict() for key, val in param_dict.items(): if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: @@ -114,8 +102,8 @@ def prepare_network(weights, cfg, args): new_param_dict[new_key] = val load_param_into_net(network, new_param_dict, strict_load=True) - network = RandLA_S3DIS_WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, - cfg.loss3_type, cfg.topk) + network = WS3WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, + cfg.loss3_type, cfg.topk) if args.retrain_model: print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") @@ -140,7 +128,7 @@ def train(cfg, args): for c in vars(cfg): logger.info('%s: %s' % (c, getattr(cfg, c))) - train_loader, val_loader, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) + train_loader, _, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in getattr(dataset, 'ignored_labels')] cfg.ignored_label_indexs = ignored_label_indexs @@ -162,14 +150,10 @@ def train(cfg, args): # resume checkpoint, cur_epoch, best_epoch, cur_step, best_step if args.resume: - f = open(args.resume + '/log.pkl', 'rb') - log = pickle.load(f) - f.close() - param = load_checkpoint(args.resume) - load_param_into_net(network, args.resume) + network_param = load_checkpoint(args.resume) + load_param_into_net(network, network_param) # data loader - train_loader = train_loader.batch(batch_size=cfg.batch_size, per_batch_map=ms_map, input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], @@ -227,13 +211,8 @@ def train(cfg, args): if __name__ == "__main__": - """Parse program arguments""" + # """Parse program arguments""" parser = argparse.ArgumentParser() - # parser = argparse.ArgumentParser( - # prog='WS3', - # description="WS3 Code Mindspore Version", - # formatter_class=argparse.ArgumentDefaultsHelpFormatter - # ) parser.add_argument('--epochs', type=int, help='max epochs', default=100) @@ -273,50 +252,48 @@ if __name__ == "__main__": parser.add_argument('--random_seed', type=int, default=888) parser.add_argument('--graph_mode', action='store_true', default=False) - args = parser.parse_args() - - cfg.dataset_dir = args.dataset_dir - cfg.batch_size = args.batch_size - cfg.max_epoch = args.epochs - cfg.train_steps = args.train_steps - cfg.learning_rate = args.learning_rate - cfg.lr_decays = args.lr_decays - cfg.loss_scale = args.loss_scale - cfg.topk = args.topk - num_training_ep0 = args.num_training_ep0 - cfg.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} - cfg.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - cfg.training_ep.update(cfg.training_ep0) - cfg.labeled_percent = args.labeled_percent - cfg.random_seed = args.random_seed - cfg.graph_mode = args.graph_mode - - if args.name is None: - if args.resume: - args.name = Path(args.resume).split('/')[-1] + arguments = parser.parse_args() + + config.dataset_dir = arguments.dataset_dir + config.batch_size = arguments.batch_size + config.max_epoch = arguments.epochs + config.train_steps = arguments.train_steps + config.learning_rate = arguments.learning_rate + config.lr_decays = arguments.lr_decays + config.loss_scale = arguments.loss_scale + config.topk = arguments.topk + num_training_ep0 = arguments.num_training_ep0 + config.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} + config.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + config.training_ep.update(config.training_ep0) + config.labeled_percent = arguments.labeled_percent + config.random_seed = arguments.random_seed + config.graph_mode = arguments.graph_mode + + if arguments.name is None: + if arguments.resume: + arguments.name = Path(arguments.resume).split('/')[-1] else: time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) - args.name = f'TSteps{cfg.train_steps}_MaxEpoch{cfg.max_epoch}_BatchS{cfg.batch_size}_lr{cfg.learning_rate}' \ - f'_lrd{cfg.lr_decays}_ls{cfg.loss_scale}_Topk{cfg.topk}_NumTrainEp0{num_training_ep0}_LP_{cfg.labeled_percent}_RS_{cfg.random_seed}' - if cfg.graph_mode: - args.name += "_GraphM" + arguments.name = f'TSteps{config.train_steps}_MaxEpoch{config.max_epoch}_BatchS{config.batch_size}' \ + f'_TopK{config.topk}_NumTrainEp0{num_training_ep0}' \ + f'_LP{config.labeled_percent}_RS{config.random_seed}' + if config.graph_mode: + arguments.name += "_GraphM" else: - args.name += "_PyNateiveM" - args.name += f'_{time_str}' + arguments.name += "_PyNateiveM" + arguments.name += f'_{time_str}' - np.random.seed(cfg.random_seed) - set_seed(cfg.random_seed) - # https://www.mindspore.cn/docs/zh-CN/r1.7/api_python/mindspore/mindspore.set_seed.html?highlight=set_seed + np.random.seed(config.random_seed) + set_seed(config.random_seed) - # ds.config.set_auto_num_workers() - # output_dir = f"./runs/pretrain_s3dis_v13" - args.outputs_dir = os.path.join(args.outputs_dir, args.name) + arguments.outputs_dir = os.path.join(arguments.outputs_dir, arguments.name) - print(f"outputs_dir:{args.outputs_dir}") - if not os.path.exists(args.outputs_dir): - os.makedirs(args.outputs_dir) + print(f"outputs_dir:{arguments.outputs_dir}") + if not os.path.exists(arguments.outputs_dir): + os.makedirs(arguments.outputs_dir) - if args.resume: - args.outputs_dir = args.resume + if arguments.resume: + arguments.outputs_dir = arguments.resume # start train - train(cfg, args) + train(config, arguments) -- Gitee From 32d2ae125459afb2d487ef7c7a3be1737e9c5149 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 22:32:53 +0800 Subject: [PATCH 11/16] update --- .../nearest_neighbors/KDTreeTableAdaptor.h | 126 +++++++++--------- .../WS3/third_party/nearest_neighbors/knn.cpp | 6 +- .../nearest_neighbors/nanoflann.hpp | 18 +-- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h index e0b7aeed4..b3abd5954 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h +++ b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h @@ -49,69 +49,69 @@ // template // struct KDTreeVectorAdaptor // { -// typedef KDTreeVectorAdaptor self_t; -// typedef typename Distance::template traits::distance_t metric_t; -// typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; - -// index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. -// size_t dims; - -// /// Constructor: takes a const ref to the vector of vectors object with the data points -// KDTreeVectorAdaptor(const size_t dims /* dimensionality */, const VectorType &mat, const int leaf_max_size = 10) : m_data(mat) -// { -// assert(mat.size() != 0); -// this->dims= dims; -// index = new index_t( static_cast(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); -// index->buildIndex(); -// } - -// ~KDTreeVectorAdaptor() { -// delete index; -// } - -// const VectorType &m_data; - -// /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). -// * Note that this is a short-cut method for index->findNeighbors(). -// * The user can also call index->... methods as desired. -// * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. -// */ -// inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const -// { -// nanoflann::KNNResultSet resultSet(num_closest); -// resultSet.init(out_indices, out_distances_sq); -// index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); -// } - -// /** @name Interface expected by KDTreeSingleIndexAdaptor -// * @{ */ - -// const self_t & derived() const { -// return *this; -// } -// self_t & derived() { -// return *this; -// } - -// // Must return the number of data points -// inline size_t kdtree_get_point_count() const { -// return m_data.size()/this->dims; -// } - -// // Returns the dim'th component of the idx'th point in the class: -// inline num_t kdtree_get_pt(const size_t idx, const size_t dim) const { -// return m_data[idx*this->dims + dim]; -// } - -// // Optional bounding-box computation: return false to default to a standard bbox computation loop. -// // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. -// // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) -// template -// bool kdtree_get_bbox(BBOX & /*bb*/) const { -// return false; -// } - -// /** @} */ +// typedef KDTreeVectorAdaptor self_t; +// typedef typename Distance::template traits::distance_t metric_t; +// typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t; + +// index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index. +// size_t dims; + +// /// Constructor: takes a const ref to the vector of vectors object with the data points +// KDTreeVectorAdaptor(const size_t dims /* dimensionality */, const VectorType &mat, const int leaf_max_size = 10) : m_data(mat) +// { +// assert(mat.size() != 0); +// this->dims= dims; +// index = new index_t( static_cast(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) ); +// index->buildIndex(); +// } + +// ~KDTreeVectorAdaptor() { +// delete index; +// } + +// const VectorType &m_data; + +// /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]). +// * Note that this is a short-cut method for index->findNeighbors(). +// * The user can also call index->... methods as desired. +// * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface. +// */ +// inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const +// { +// nanoflann::KNNResultSet resultSet(num_closest); +// resultSet.init(out_indices, out_distances_sq); +// index->findNeighbors(resultSet, query_point, nanoflann::SearchParams()); +// } + +// /** @name Interface expected by KDTreeSingleIndexAdaptor +// * @{ */ + +// const self_t & derived() const { +// return *this; +// } +// self_t & derived() { +// return *this; +// } + +// // Must return the number of data points +// inline size_t kdtree_get_point_count() const { +// return m_data.size()/this->dims; +// } + +// // Returns the dim'th component of the idx'th point in the class: +// inline num_t kdtree_get_pt(const size_t idx, const size_t dim) const { +// return m_data[idx*this->dims + dim]; +// } + +// // Optional bounding-box computation: return false to default to a standard bbox computation loop. +// // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again. +// // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds) +// template +// bool kdtree_get_bbox(BBOX & /*bb*/) const { +// return false; +// } + +// /** @} */ // }; // end of KDTreeVectorOfVectorsAdaptor diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp index db35d0ef2..c722a5d42 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp +++ b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp @@ -2255,7 +2255,7 @@ static PyObject *__pyx_pf_17nearest_neighbors_knn(CYTHON_UNUSED PyObject *__pyx_ goto __pyx_L0; /* "knn.pyx":33 - * const size_t K, long* batch_indices) + * const size_t K, long* batch_indices) * * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< * @@ -6023,7 +6023,7 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__7); /* "knn.pyx":33 - * const size_t K, long* batch_indices) + * const size_t K, long* batch_indices) * * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< * @@ -6382,7 +6382,7 @@ if (!__Pyx_RefNanny) { __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; /* "knn.pyx":33 - * const size_t K, long* batch_indices) + * const size_t K, long* batch_indices) * * def knn(pts, queries, K, omp=False): # <<<<<<<<<<<<<< * diff --git a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp index a98586dc8..286ea74fe 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp +++ b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp @@ -555,7 +555,7 @@ namespace nanoflann /* We maintain memory alignment to word boundaries by requiring that all allocations be in multiples of the machine wordsize. */ /* Size of machine word in bytes. Must be power of 2. */ - /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE. */ + /* Minimum number of bytes requested at a time from the system. Must be multiple of WORDSIZE.*/ size_t remaining; /* Number of bytes left in current block of storage. */ @@ -671,7 +671,7 @@ namespace nanoflann // ---------------- CArray ------------------------- /** A STL container (as wrapper) for arrays of constant size defined at compile time (class imported from the MRPT project) * This code is an adapted version from Boost, modifed for its integration - * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). + * within MRPT (JLBC, Dec/2009) (Renamed array -> CArray to avoid possible potential conflicts). * See * http://www.josuttis.com/cppcode * for details and the latest version. @@ -1897,14 +1897,14 @@ namespace nanoflann * * Example of usage: * \code - * Eigen::Matrix mat; - * // Fill out "mat"... + * Eigen::Matrix mat; + * // Fill out "mat"... * - * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; - * const int max_leaf = 10; - * my_kd_tree_t mat_index(mat, max_leaf ); - * mat_index.index->buildIndex(); - * mat_index.index->... + * typedef KDTreeEigenMatrixAdaptor< Eigen::Matrix > my_kd_tree_t; + * const int max_leaf = 10; + * my_kd_tree_t mat_index(mat, max_leaf ); + * mat_index.index->buildIndex(); + * mat_index.index->... * \endcode * * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations. -- Gitee From c00eddcd66d94e20b1e7607ae081391b9837c222 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Fri, 25 Nov 2022 22:38:48 +0800 Subject: [PATCH 12/16] update --- research/cv/WS3/README.md | 22 ++- research/cv/WS3/eval_ascend.py | 158 +++++++++------- research/cv/WS3/eval_gpu.py | 177 ++++++++++-------- research/cv/WS3/scripts/eval_s3dis_ascend.sh | 10 +- research/cv/WS3/scripts/eval_s3dis_gpu.sh | 12 +- research/cv/WS3/scripts/train_s3dis_gpu.sh | 2 +- research/cv/WS3/src/data/S3DIS_dataset.py | 16 +- .../cv/WS3/src/data/S3DIS_dataset_test.py | 24 ++- research/cv/WS3/src/model/base_model.py | 59 +++--- .../WS3/src/model/base_model_remove_bias.py | 60 +++--- research/cv/WS3/src/model/model_s3dis.py | 69 +++---- .../WS3/src/model/model_s3dis_remove_bias.py | 104 +++------- .../cv/WS3/src/utils/data_prepare_s3dis.py | 19 +- research/cv/WS3/src/utils/helper_ply.py | 22 ++- research/cv/WS3/src/utils/logger.py | 4 +- research/cv/WS3/src/utils/metrics.py | 15 ++ research/cv/WS3/src/utils/tools.py | 86 ++------- research/cv/WS3/third_party/compile_op.sh | 2 +- research/cv/WS3/train_ascend.py | 79 +++----- research/cv/WS3/train_gpu.py | 19 +- 20 files changed, 458 insertions(+), 501 deletions(-) diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 63ee80b50..79dcf026d 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -23,7 +23,7 @@ or [original tensorflow implementation](https://github.com/Yachao-Zhang/WS3) for ### Directory structure of dataset -```html +```text dataset └──S3DIS # S3DIS dataset ├── input_0.040 @@ -76,7 +76,7 @@ bash scripts/eval_s3dis_ascend.sh ### Scripts and Sample Code -```html +```text WS3 ├── scripts │ ├── eval_s3dis_ascend.sh # Evaluating: S3DIS dataset on Ascend @@ -93,14 +93,16 @@ WS3 │ │ ├── model_s3dis.py # combine loss function with network │ │ └── model_s3dis_remove_bias.py # combine loss function with network removing bias │ └── utils -│ ├── cpp_wrappers # dependency for point cloud subsampling -│ ├── meta # meta information for data processor -│ ├── nearest_neighbors # dependency for point cloud nearest_neighbors │ ├── data_prepare_s3dis.py # data processor for s3dis dataset │ ├── helper_ply.py # file utils │ ├── logger.py # logger │ ├── metrics.py # calculate iou and accuracy │ └── tools.py # DataProcessing and Config +├── third_party +│ ├── cpp_wrappers # dependency for point cloud subsampling +│ ├── meta # meta information for data processor +│ ├── nearest_neighbors # dependency for point cloud nearest_neighbors +│ └── compile_op.sh # shell for installing dependencies, including cpp_wrappers and nearest_neighbors │ ├── eval_gpu.py ├── eval_ascend.py @@ -112,7 +114,7 @@ WS3 ### Script Parameter -we use `train_s3dis_ascend.sh` as an example +we use `train_s3dis_ascend.sh` as an example: ```shell python train_ascend.py \ @@ -186,7 +188,7 @@ Using `bash scripts/eval_s3dis_ascend.sh` as an example: Training results will be stored in `/outputs/BatchS_6_Float16_PyNative_Ascend` , which is determined by `{args.outputs_dir}/{args.name}/ckpt`. For example: -```html +```text outputs ├── BatchS_6_Float16_PyNative_Ascend ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluating: S3DIS dataset on Ascend @@ -217,7 +219,7 @@ Note: Before you start eval, please check `--model_path` in `eval_xxx.sh` and gu ### Evaluation Result 910 -```shell +```text Area_5_office_19 Acc:0.8584098992023179 Area_5_hallway_9 Acc:0.9581127867106095 Area_5_hallway_10 Acc:0.9735772827918966 @@ -251,7 +253,7 @@ Area_5_office_23 Acc:0.9049251547225916 | outputs | feature vector + probability | feature vector + probability | | Loss | 2.89 (epoch 40) | 1.85 (epoch 40) & 11.45 (epoch 100) | | Speed | 13 ms/step(8pcs) | 29 ms/step(8pcs) | -| Total time | About 4 mins | 11 minds | +| Total time | About 13 hours | 12 hours | | Parameters (M) | 4,98 | 4.99 | | Checkpoint for Fine tuning | 57.14 MB (.ckpt file) | 76.31 MB (.ckpt file) | | Scripts | [link](https://gitee.com/mindspore/models/tree/master/official/cv/WS3) | @@ -279,7 +281,7 @@ Area_5_office_23 Acc:0.9049251547225916 Please kindly cite the original paper references in your publications if it helps your research: -```html +```text @inproceedings{zhang2021weakly, title={Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud}, author={Zhang, Yachao and Li, Zonghao and Xie, Yuan and Qu, Yanyun and Li, Cuihua and Mei, Tao}, diff --git a/research/cv/WS3/eval_ascend.py b/research/cv/WS3/eval_ascend.py index b94a447d2..5b7070f8b 100644 --- a/research/cv/WS3/eval_ascend.py +++ b/research/cv/WS3/eval_ascend.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 argparse from pathlib import Path @@ -70,7 +85,6 @@ def run_eval(params): test_smooth = 0.95 step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) for step_i, data in enumerate(val_loader): - # begin_time = time.time() features = data['features'] labels = data['labels'] # (B,N) xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] @@ -81,18 +95,14 @@ def run_eval(params): cloud_idx = data['cloud_inds'].asnumpy() logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] - # logits = logits.asnumpy() logits = logits[..., :cfg.num_classes] - prob_logits = ops.Softmax(-1)(logits) # 0.0003s + prob_logits = ops.Softmax(-1)(logits) - ##### - # TODO 在Ascend环境上, prob_logits.asnumpy() 这个操作竟然要2.7s - ##### - prob_logits = prob_logits.asnumpy() # 要 2.72~2.8ls - for j in range(np.shape(prob_logits)[0]): # 遍历每一个batch + prob_logits = prob_logits.asnumpy() + for j in range(np.shape(prob_logits)[0]): probs = prob_logits[j] - p_idx = point_idx[j, :] # 第j个点云中所有的点的索引,这里一共有40960个点 + p_idx = point_idx[j, :] c_i = cloud_idx[j][0] test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs @@ -101,79 +111,85 @@ def run_eval(params): msg = f'Step: {str(step_i)}; acc: {str(acc)}' step_bar.set_postfix_str(msg, refresh=False) step_bar.update() - last_min, num_votes = -0.5, 100 - while last_min < num_votes: - new_min = np.min(val_ds.source.min_possibility['validation']) - logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") - if last_min + 1 < new_min: - last_min += 1 - logger.info('Confusion on sub clouds') - confusion_list = [] - num_val = len(dataset.input_labels['validation']) + best_ckpt, best_miou = cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, + val_ds, val_proportions) + ckpt_bar.update() + + logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) - for i_test in range(num_val): - probs = test_probs[i_test] - preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) - labels = dataset.input_labels['validation'][i_test] - confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] +def cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, val_ds, val_proportions): + last_min, num_votes = -0.5, 100 + while last_min < num_votes: + new_min = np.min(val_ds.source.min_possibility['validation']) + logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") + if last_min + 1 < new_min: + last_min += 1 + logger.info('Confusion on sub clouds') + confusion_list = [] + + num_val = len(dataset.input_labels['validation']) + + for i_test in range(num_val): + probs = test_probs[i_test] + preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) + labels = dataset.input_labels['validation'][i_test] + confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + # Rescale with the right number of point per class + C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + # Compute IoUs + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info(s + '\n') + if int(np.ceil(new_min)) % 1 == 0: + # Project predictions + logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) + proj_probs_list = [] + for i_val in range(num_val): + # Reproject probs back to the evaluations points + proj_idx = dataset.val_proj[i_val] + probs = test_probs[i_val][proj_idx, :] + proj_probs_list += [probs] + # Show vote results + logger.info('Confusion on full clouds') + confusion_list = [] + for i_test in range(num_val): + # Get the predicted labels + preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) + # Confusion + labels = dataset.val_labels[i_test] + acc = np.sum(preds == labels) / len(labels) + logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) + confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] + name = dataset.input_names['validation'][i_test] + '.ply' + write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], + ['pred', 'label']) # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) - # Rescale with the right number of point per class - C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) - # Compute IoUs + C = np.sum(np.stack(confusion_list), axis=0) IoUs = DP.IoU_from_confusions(C) m_IoU = np.mean(IoUs) + if m_IoU > best_miou: + best_miou = m_IoU + best_ckpt = ckpt s = '{:5.2f} | '.format(100 * m_IoU) for IoU in IoUs: s += '{:5.2f} '.format(100 * IoU) - logger.info(s + '\n') - if int(np.ceil(new_min)) % 1 == 0: - # Project predictions - logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) - proj_probs_list = [] - for i_val in range(num_val): - # Reproject probs back to the evaluations points - proj_idx = dataset.val_proj[i_val] - probs = test_probs[i_val][proj_idx, :] - proj_probs_list += [probs] - # Show vote results - logger.info('Confusion on full clouds') - confusion_list = [] - for i_test in range(num_val): - # Get the predicted labels - preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - # Confusion - labels = dataset.val_labels[i_test] - acc = np.sum(preds == labels) / len(labels) - logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] - name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], - ['pred', 'label']) - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0) - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - if m_IoU > best_miou: - best_miou = m_IoU - best_ckpt = ckpt - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info('-' * len(s)) - logger.info(s) - logger.info('-' * len(s) + '\n') - logger.info('==========end test===============') - break - ckpt_bar.update() - - logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) + logger.info('-' * len(s)) + logger.info(s) + logger.info('-' * len(s) + '\n') + logger.info('==========end test===============') + break + return best_ckpt, best_miou if __name__ == "__main__": - # """Parse program arguments""" parser = argparse.ArgumentParser( prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter @@ -181,7 +197,7 @@ if __name__ == "__main__": parser.add_argument('--batch_size', type=int, help='val batch size', default=20) parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') parser.add_argument('--model_path', type=str, help='model saved path', default='runs') - parser.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + parser.add_argument('--device_target', type=str, help='Ascend ', default='Ascend', choices=['Ascend']) parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) parser.add_argument('--rank', type=int, help='rank', default=0) parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') diff --git a/research/cv/WS3/eval_gpu.py b/research/cv/WS3/eval_gpu.py index 9d554a176..ad3dbbdd3 100644 --- a/research/cv/WS3/eval_gpu.py +++ b/research/cv/WS3/eval_gpu.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 argparse import datetime @@ -51,8 +66,6 @@ def run_eval(params): ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) ckpts = ckpt_path.glob('*.ckpt') ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) - # if len(ckpts) == 0: - # ckpts = ["/media/T/Codes/RnadLA_Net_mindspore/tensorflow2ms/tf2ms.ckpt"] best_miou = 0.0 best_ckpt = ckpts[0] @@ -93,9 +106,9 @@ def run_eval(params): logits = logits[..., :cfg.num_classes] prob_logits = ops.Softmax(-1)(logits).asnumpy() # (B,N,13) - for j in range(np.shape(prob_logits)[0]): # 遍历每一个batch + for j in range(np.shape(prob_logits)[0]): probs = prob_logits[j, :, :] - p_idx = point_idx[j, :] # 第j个点云中所有的点的索引,这里一共有40960个点 + p_idx = point_idx[j, :] c_i = cloud_idx[j][0] test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs @@ -105,93 +118,98 @@ def run_eval(params): step_bar.set_postfix_str(msg, refresh=False) step_bar.update() - last_min = -0.5 - num_votes = 100 + best_ckpt, best_miou = cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, + val_ds, val_proportions) + ckpt_bar.update() - while last_min < num_votes: - new_min = np.min(val_ds.source.min_possibility['validation']) - logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") - # if True: - if last_min + 1 < new_min: - # Update last_min - last_min += 1 + logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) - # Show vote results (On subcloud so it is not the good values here) - logger.info('Confusion on sub clouds') - confusion_list = [] - num_val = len(dataset.input_labels['validation']) +def cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, val_ds, val_proportions): + last_min = -0.5 + num_votes = 100 + while last_min < num_votes: + new_min = np.min(val_ds.source.min_possibility['validation']) + logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") + # if True: + if last_min + 1 < new_min: + # Update last_min + last_min += 1 + + # Show vote results (On subcloud so it is not the good values here) + logger.info('Confusion on sub clouds') + confusion_list = [] + + num_val = len(dataset.input_labels['validation']) + + for i_test in range(num_val): + probs = test_probs[i_test] + preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) + labels = dataset.input_labels['validation'][i_test] + + # Confs + confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + + # Rescale with the right number of point per class + C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + + # Compute IoUs + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info(s + '\n') + + if int(np.ceil(new_min)) % 1 == 0: + # Project predictions + logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) + proj_probs_list = [] + + for i_val in range(num_val): + # Reproject probs back to the evaluations points + proj_idx = dataset.val_proj[i_val] + probs = test_probs[i_val][proj_idx, :] + proj_probs_list += [probs] + + # Show vote results + logger.info('Confusion on full clouds') + confusion_list = [] for i_test in range(num_val): - probs = test_probs[i_test] - preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) - labels = dataset.input_labels['validation'][i_test] + # Get the predicted labels + preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - # Confs - confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + # Confusion + labels = dataset.val_labels[i_test] + acc = np.sum(preds == labels) / len(labels) + logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] + name = dataset.input_names['validation'][i_test] + '.ply' + write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], + ['pred', 'label']) - # Rescale with the right number of point per class - C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0) - # Compute IoUs IoUs = DP.IoU_from_confusions(C) m_IoU = np.mean(IoUs) + if m_IoU > best_miou: + best_miou = m_IoU + best_ckpt = ckpt s = '{:5.2f} | '.format(100 * m_IoU) for IoU in IoUs: s += '{:5.2f} '.format(100 * IoU) - logger.info(s + '\n') - - if int(np.ceil(new_min)) % 1 == 0: - - # Project predictions - logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) - proj_probs_list = [] - - for i_val in range(num_val): - # Reproject probs back to the evaluations points - proj_idx = dataset.val_proj[i_val] - probs = test_probs[i_val][proj_idx, :] - proj_probs_list += [probs] - - # Show vote results - logger.info('Confusion on full clouds') - confusion_list = [] - for i_test in range(num_val): - # Get the predicted labels - preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - - # Confusion - labels = dataset.val_labels[i_test] - acc = np.sum(preds == labels) / len(labels) - logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - - confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] - name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], - ['pred', 'label']) - - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0) - - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - if m_IoU > best_miou: - best_miou = m_IoU - best_ckpt = ckpt - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info('-' * len(s)) - logger.info(s) - logger.info('-' * len(s) + '\n') - logger.info('==========end test===============') - break - ckpt_bar.update() - - logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) + logger.info('-' * len(s)) + logger.info(s) + logger.info('-' * len(s) + '\n') + logger.info('==========end test===============') + break + return best_ckpt, best_miou if __name__ == "__main__": @@ -207,7 +225,7 @@ if __name__ == "__main__": parser.add_argument('--model_path', type=str, help='model saved path', required=True, default='outputs') - parser.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU') + parser.add_argument('--device_target', type=str, help='GPU', default='GPU', choices=['GPU']) parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) @@ -228,11 +246,10 @@ if __name__ == "__main__": args.outputs_dir = os.path.join(f"test_{t}") if not os.path.exists(args.outputs_dir): os.makedirs(args.outputs_dir) - # val output path + # val output path val_pred_path = os.path.join(args.outputs_dir, 'val_preds') if not os.path.exists(val_pred_path): os.makedirs(val_pred_path) - # start test run_eval(args) diff --git a/research/cv/WS3/scripts/eval_s3dis_ascend.sh b/research/cv/WS3/scripts/eval_s3dis_ascend.sh index 621f020f7..97f8bdea5 100644 --- a/research/cv/WS3/scripts/eval_s3dis_ascend.sh +++ b/research/cv/WS3/scripts/eval_s3dis_ascend.sh @@ -15,8 +15,8 @@ # ============================================================================ python eval_ascend.py \ ---dataset_dir ../dataset/S3DIS \ ---device_target Ascend \ ---val_area Area_5\ ---float16 True \ ---model_path ./outputs/BatchS_6_Float16_PyNative_Ascend \ No newline at end of file + --dataset_dir ../dataset/S3DIS \ + --device_target Ascend \ + --val_area Area_5 \ + --float16 True \ + --model_path ./outputs/BatchS_6_Float16_PyNative_Ascend diff --git a/research/cv/WS3/scripts/eval_s3dis_gpu.sh b/research/cv/WS3/scripts/eval_s3dis_gpu.sh index 9cf761900..128c17f30 100644 --- a/research/cv/WS3/scripts/eval_s3dis_gpu.sh +++ b/research/cv/WS3/scripts/eval_s3dis_gpu.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2021 Huawei Technologies Co., Ltd +# Copyright 2022 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ # ============================================================================ python eval_gpu.py \ ---dataset_dir ../dataset/S3DIS \ ---device_target GPU \ ---val_area Area_5\ ---float16 True \ ---model_path ./outputs/BatchS_6_Float32_PyNative_GPU \ No newline at end of file + --dataset_dir ../dataset/S3DIS \ + --device_target GPU \ + --val_area Area_5 \ + --float16 True \ + --model_path ./outputs/BatchS_6_Float32_PyNative_GPU diff --git a/research/cv/WS3/scripts/train_s3dis_gpu.sh b/research/cv/WS3/scripts/train_s3dis_gpu.sh index c27eb4c32..37e5eba08 100644 --- a/research/cv/WS3/scripts/train_s3dis_gpu.sh +++ b/research/cv/WS3/scripts/train_s3dis_gpu.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2021 Huawei Technologies Co., Ltd +# Copyright 2022 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/research/cv/WS3/src/data/S3DIS_dataset.py b/research/cv/WS3/src/data/S3DIS_dataset.py index 1599481ba..518daa23b 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset.py +++ b/research/cv/WS3/src/data/S3DIS_dataset.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 pickle import time from pathlib import Path @@ -31,7 +46,6 @@ class S3DISDatasetGenerator: self.size = len(self.paths) # self.labeled_point = f'{labeled_point_percent}%' - # self.labeled_point = '1%' self.sampling_mode = 0 # 0 random, 1:spt self.sub_grid_size = 0.04 self.weak_label = True diff --git a/research/cv/WS3/src/data/S3DIS_dataset_test.py b/research/cv/WS3/src/data/S3DIS_dataset_test.py index 34f31aca3..2dfd316a7 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset_test.py +++ b/research/cv/WS3/src/data/S3DIS_dataset_test.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 pickle import time from pathlib import Path @@ -14,7 +29,6 @@ class S3DISDatasetGenerator: def __init__(self, dataset_dir): self.name = 'S3DIS' self.dataset_path = Path(dataset_dir) - # self.dataset_path = Path("./datasets/S3DIS") print(self.dataset_path.absolute()) self.label_to_names = {0: 'ceiling', 1: 'floor', @@ -176,8 +190,12 @@ class ActiveLearningSampler(ds.Sampler): self.min_possibility[self.split][cloud_idx] = float(np.min(self.possibility[self.split][cloud_idx])) if len(points) < cfg.num_points: - queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ - DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cfg.num_points) + data_aug_dict = DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, + cfg.num_points) + queried_pc_xyz = data_aug_dict['xyz_aug'] + queried_pc_colors = data_aug_dict['color_aug'] + queried_idx = data_aug_dict['idx_aug'] + queried_pc_labels = data_aug_dict['label_aug'] queried_pc_xyz = queried_pc_xyz.astype(np.float32) queried_pc_colors = queried_pc_colors.astype(np.float32) diff --git a/research/cv/WS3/src/model/base_model.py b/research/cv/WS3/src/model/base_model.py index f61179dae..e72c01293 100644 --- a/research/cv/WS3/src/model/base_model.py +++ b/research/cv/WS3/src/model/base_model.py @@ -1,4 +1,18 @@ # -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 mindspore as ms import mindspore.nn as nn import mindspore.ops as P @@ -62,7 +76,6 @@ class LocalSpatialEncoding(nn.Cell): super(LocalSpatialEncoding, self).__init__() self.mlp = SharedMLP(in_channel, out_channel, bn=True, activation_fn=nn.LeakyReLU(0.2)) - # self.mlp = SharedMLP(10, d, bn=True, activation_fn=nn.LeakyReLU(0.2)) self.d = out_channel self.use_pos_encoding = use_pos_encoding @@ -83,42 +96,33 @@ class LocalSpatialEncoding(nn.Cell): ms.Tensor, shape (B, 2*d, N, K) """ - idx = neighbor_idx # (4,40960,16) - extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) # (4,40960,16) -> (4,1,40960,16) -> (4,3,40960,16) + idx = neighbor_idx + extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) cat = P.Concat(-3) if self.use_pos_encoding: # finding neighboring points xyz_tile = P.Tile()(coords.transpose(0, 2, 1).expand_dims(-1), - (1, 1, 1, idx.shape[-1])) # (4,3,40960) -> (4,3,40960,16) - neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(ms.int32)) # shape (4, 3, 40960, 16) + (1, 1, 1, idx.shape[-1])) + neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(ms.int32)) relative_xyz = xyz_tile - neighbor_xyz # relative_xyz relative_dist = P.Sqrt()(P.ReduceSum(keep_dims=True)(P.Square()(relative_xyz), -3)) # relative point position encoding f_xyz = cat(( - relative_dist, # (4,1,40960,16) - relative_xyz, # (4,3,40960,16) - xyz_tile, # (4,3,40960,16) - neighbor_xyz, # (4,3,40960,16) - )) # (4,10,40960,16) - - # ==========> tensorflow 源码 - # f_xyz = self.relative_pos_encoding(xyz, neigh_idx) - # def relative_pos_encoding(self, xyz, neigh_idx): - # ... - # relative_feature = tf.concat([relative_dis, relative_xyz, xyz_tile, neighbor_xyz], axis=-1) - # return relative_feature - # ==========> tensorflow 源码 + relative_dist, + relative_xyz, + xyz_tile, + neighbor_xyz, + )) else: f_xyz = coords - f_xyz = self.mlp(f_xyz) # (4,10,40960,16) -> (4,8,40960,16) + f_xyz = self.mlp(f_xyz) - f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) # (4, 8, 40960, 1) -> (4,8,40960,16) + f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) extended_idx_for_feat = P.Tile()(idx.expand_dims(1), (1, f_xyz.shape[1], 1, 1)) - # (4,8,40960,16) -> (4,8,40960,16) f_neighbours = P.GatherD()(f_tile, 2, extended_idx_for_feat.astype(ms.int32)) - f_concat = cat([f_xyz, f_neighbours]) # (4,8,40960,16) & (4,8,40960,16) -> (4,16,40960,16) + f_concat = cat([f_xyz, f_neighbours]) if self.use_pos_encoding: return f_xyz, f_concat @@ -167,9 +171,7 @@ class LocalFeatureAggregation(nn.Cell): self.shortcut = SharedMLP(d_in, 2 * d_out, bn=True) self.lse1 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2, use_pos_encoding=True) - # self.lse2 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2) self.lse2 = LocalSpatialEncoding(in_channel=d_out // 2, out_channel=d_out // 2, use_pos_encoding=False) - # self.lse2 = LocalSpatialEncoding(d_out // 2) self.pool1 = AttentivePooling(d_out, d_out // 2) self.pool2 = AttentivePooling(d_out, d_out) @@ -192,13 +194,12 @@ class LocalFeatureAggregation(nn.Cell): ------- ms.Tensor, shape (B, 2*d_out, N, 1) """ - f_pc = self.mlp1(features) # (4, 8, 40960, 1) + f_pc = self.mlp1(features) - f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) # (4, 8, 40960, 16) (4, 16, 40960, 16) - f_pc_agg = self.pool1(f_concat) # (4, 8, 40960, 1) + f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) + f_pc_agg = self.pool1(f_concat) - f_concat = self.lse2(f_xyz, f_pc_agg, - neighbor_idx) # coords: (4, 40960, 3); x: (4, 8, 40960, 1) neighbor_idx:(4, 40960, 16) + f_concat = self.lse2(f_xyz, f_pc_agg, neighbor_idx) f_pc_agg = self.pool2(f_concat) return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) diff --git a/research/cv/WS3/src/model/base_model_remove_bias.py b/research/cv/WS3/src/model/base_model_remove_bias.py index 29b2e68d7..4e934578d 100644 --- a/research/cv/WS3/src/model/base_model_remove_bias.py +++ b/research/cv/WS3/src/model/base_model_remove_bias.py @@ -1,4 +1,18 @@ # -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 mindspore.nn as nn import mindspore.ops as P from mindspore.ops import composite as C @@ -62,7 +76,6 @@ class LocalSpatialEncoding(nn.Cell): super(LocalSpatialEncoding, self).__init__() self.mlp = SharedMLP(in_channel, out_channel, bn=True, activation_fn=nn.LeakyReLU(0.2)) - # self.mlp = SharedMLP(10, d, bn=True, activation_fn=nn.LeakyReLU(0.2)) self.d = out_channel self.use_pos_encoding = use_pos_encoding @@ -83,42 +96,34 @@ class LocalSpatialEncoding(nn.Cell): ms.Tensor, shape (B, 2*d, N, K) """ - idx = neighbor_idx # (4,40960,16) - extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) # (4,40960,16) -> (4,1,40960,16) -> (4,3,40960,16) + idx = neighbor_idx + extended_idx = P.Tile()(idx.expand_dims(1), (1, 3, 1, 1)) cat = P.Concat(-3) if self.use_pos_encoding: # finding neighboring points xyz_tile = P.Tile()(coords.transpose(0, 2, 1).expand_dims(-1), - (1, 1, 1, idx.shape[-1])) # (4,3,40960) -> (4,3,40960,16) - neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(mstype.int32)) # shape (4, 3, 40960, 16) + (1, 1, 1, idx.shape[-1])) + neighbor_xyz = P.GatherD()(xyz_tile, 2, extended_idx.astype(mstype.int32)) relative_xyz = xyz_tile - neighbor_xyz # relative_xyz relative_dist = P.Sqrt()(P.ReduceSum(keep_dims=True)(P.Square()(relative_xyz), -3)) # relative point position encoding f_xyz = cat(( - relative_dist, # (4,1,40960,16) - relative_xyz, # (4,3,40960,16) - xyz_tile, # (4,3,40960,16) - neighbor_xyz, # (4,3,40960,16) - )) # (4,10,40960,16) - - # ==========> tensorflow 源码 - # f_xyz = self.relative_pos_encoding(xyz, neigh_idx) - # def relative_pos_encoding(self, xyz, neigh_idx): - # ... - # relative_feature = tf.concat([relative_dis, relative_xyz, xyz_tile, neighbor_xyz], axis=-1) - # return relative_feature - # ==========> tensorflow 源码 + relative_dist, + relative_xyz, + xyz_tile, + neighbor_xyz, + )) + else: f_xyz = coords - f_xyz = self.mlp(f_xyz) # (4,10,40960,16) -> (4,8,40960,16) + f_xyz = self.mlp(f_xyz) - f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) # (4, 8, 40960, 1) -> (4,8,40960,16) + f_tile = P.Tile()(features, (1, 1, 1, idx.shape[-1])) extended_idx_for_feat = P.Tile()(idx.expand_dims(1), (1, f_xyz.shape[1], 1, 1)) - # (4,8,40960,16) -> (4,8,40960,16) f_neighbours = P.GatherD()(f_tile, 2, extended_idx_for_feat.astype(mstype.int32)) - f_concat = cat([f_xyz, f_neighbours]) # (4,8,40960,16) & (4,8,40960,16) -> (4,16,40960,16) + f_concat = cat([f_xyz, f_neighbours]) if self.use_pos_encoding: return f_xyz, f_concat @@ -167,9 +172,7 @@ class LocalFeatureAggregation(nn.Cell): self.shortcut = SharedMLP(d_in, 2 * d_out, bn=True) self.lse1 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2, use_pos_encoding=True) - # self.lse2 = LocalSpatialEncoding(in_channel=10, out_channel=d_out // 2) self.lse2 = LocalSpatialEncoding(in_channel=d_out // 2, out_channel=d_out // 2, use_pos_encoding=False) - # self.lse2 = LocalSpatialEncoding(d_out // 2) self.pool1 = AttentivePooling(d_out, d_out // 2) self.pool2 = AttentivePooling(d_out, d_out) @@ -192,13 +195,12 @@ class LocalFeatureAggregation(nn.Cell): ------- ms.Tensor, shape (B, 2*d_out, N, 1) """ - f_pc = self.mlp1(features) # (4, 8, 40960, 1) + f_pc = self.mlp1(features) - f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) # (4, 8, 40960, 16) (4, 16, 40960, 16) - f_pc_agg = self.pool1(f_concat) # (4, 8, 40960, 1) + f_xyz, f_concat = self.lse1(coords, f_pc, neighbor_idx) + f_pc_agg = self.pool1(f_concat) - f_concat = self.lse2(f_xyz, f_pc_agg, - neighbor_idx) # coords: (4, 40960, 3); x: (4, 8, 40960, 1) neighbor_idx:(4, 40960, 16) + f_concat = self.lse2(f_xyz, f_pc_agg, neighbor_idx) f_pc_agg = self.pool2(f_concat) return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index 6b39a1c74..6ccf9b7cb 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -1,5 +1,18 @@ # -*-coding:utf-8-*- - +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 mindspore as ms import mindspore.nn as nn import mindspore.ops as P @@ -98,8 +111,6 @@ class WS3(nn.Cell): f_layer_drop = self.fc_end_drop(f_layer_fc2) f_layer_fc3 = self.fc_end_fc3(f_layer_drop) - # print(f"[model_s3dis.py] fc_end. Time :{e_time - d_time}s") - f_layer_fc2, f_layer_fc3 = f_layer_fc2.swapaxes(1, 3), f_layer_fc3.swapaxes(1, 3) f_layer_out = P.Concat(axis=-1)([f_layer_fc3, f_layer_fc2]) f_out = f_layer_out.squeeze(1) # (B,N_points,13+32) @@ -116,7 +127,6 @@ class WS3(nn.Cell): b, d = feature.shape[:2] n_ = pool_idx.shape[1] # [B, N', max_num] --> [B, d, N', max_num] - # pool_idx = P.repeat_elements(pool_idx.expand_dims(1), feature.shape[1], 1) pool_idx = P.Tile()(pool_idx.expand_dims(1), (1, feature.shape[1], 1, 1)) # [B, d, N', max_num] --> [B, d, N'*max_num] pool_idx = pool_idx.reshape((b, d, -1)) @@ -127,7 +137,6 @@ class WS3(nn.Cell): class WS3WithLoss(nn.Cell): - """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): super(WS3WithLoss, self).__init__() @@ -156,12 +165,10 @@ class WS3WithLoss(nn.Cell): xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) - # 当c_epoch_k==0时,只需要计算有GT标注点的loss值 logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) pred_embed = logits_embed[..., self.num_classes:] # (B,N,45) -> (B,N,32) logits = logits.reshape((-1, self.num_classes)) pred_embed = pred_embed.reshape((-1, 32)) - # logit = logits.reshape((-1, self.num_classes)) # (B*N,13) labels = labels.reshape((-1,)) # (b,n) -> (b*n) xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) @@ -170,74 +177,49 @@ class WS3WithLoss(nn.Cell): ignore_mask = P.zeros_like(labels).astype(mstype.bool_) for ign_label in self.ignored_label_inds: ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # - # 0为无效,1为有效 valid_mask = P.logical_not(ignore_mask) # (B*N,) # (B*N,13) one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) - # 输出Tensor各维度上的和,以达到对所有维度进行归约的目的。也可以对指定维度进行求和归约 # (B*N, 13) -> (B*N,) weights = P.ReduceSum()(weights, 1) # # (B*N,) and (B*N,13) -> unweighted_loss = self.loss_fn(logits, one_hot_labels) weighted_loss = unweighted_loss * weights weighted_loss = weighted_loss * valid_mask - CE_loss = P.ReduceSum()(weighted_loss) + ce_loss = P.ReduceSum()(weighted_loss) num_valid_points = P.ReduceSum()(valid_mask.astype(mstype.float32)) - CE_loss = CE_loss / num_valid_points + ce_loss = ce_loss / num_valid_points ### if self.c_epoch_k == 0: - loss = CE_loss + loss = ce_loss else: - SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, + sp_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, self.topk) * self.c_epoch_k - loss = CE_loss + SP_loss + loss = ce_loss + sp_loss return loss @staticmethod def get_sp_loss_by_mask(embed, logits, one_hot_label, valid_mask, topk): - """ - - Args: - embed: - logits: - one_hot_label: - valid_mask: - topk: - - Returns: - - """ invalid_mask = P.logical_not(valid_mask) # (B*N,) num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) topk += num_invalid_points - # 点类别的数量 num_class = one_hot_label.shape[1] # scalar: 13 valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) invalid_embed = embed * invalid_mask.reshape(-1, 1) # (B*N,32) - # => 将有效点的label矩阵进行转置:[M,class_num] -> [class_num,M] (13,B*N) valid_one_hot_label_T = P.transpose(valid_one_hot_label, (1, 0)) - # => 每一行那个类有那些点:[class_num,B*N] * [B*N,dim] -> [class_num,dim] sum_embed = P.matmul(valid_one_hot_label_T, valid_embed) - # => 求class的平均嵌入:[class_num,dim] - # mean_embed 为每个类别的embedding,如果这个类别没有样本,则embedding全为0 mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) - # => 求unlabelled points 与 class embedding的相似度 - # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] - adj_matrix = RandLAS3DISWithLoss.double_feature(invalid_embed, mean_embed) - # adj_matrix = RandLA_S3DIS_WithLoss.double_feature(invalid_embed, mean_embed) - - # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) - neg_adj = -adj_matrix # (B*N,13) 取负 - # 转置为了下一步 [N, M] -> [M,N] (M是有标签的点) + adj_matrix = WS3WithLoss.double_feature(invalid_embed, mean_embed) + + neg_adj = -adj_matrix # (B*N,13) neg_adj_t = P.transpose(neg_adj, (1, 0)) # (13,B*N) - # 取最不相似的 top k个点 _, nn_idx = P.TopK()(neg_adj_t, topk) - s = P.shape(neg_adj_t) # s (M,N) + s = P.shape(neg_adj_t) # (M,N) row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) ones_idx = P.Stack(axis=1)([row_idx.reshape([-1]), nn_idx.reshape([-1])]) res = P.scatter_nd(ones_idx, P.ones(s[0] * topk, neg_adj_t.dtype), s) @@ -268,11 +250,6 @@ class WS3WithLoss(nn.Cell): def double_feature(point_feature1, point_feature2): """ Compute pairwise distance of a point cloud. - Args: - [N,C],[M,C] - point_cloud: tensor (batch_size, num_points, num_dims) - Returns: - pairwise distance: (batch_size, num_points, num_points) """ point2_transpose = P.transpose(point_feature2, (1, 0)) # [C, M] point_inner = P.matmul(point_feature1, point2_transpose) # [N,M] diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index c65cb04ed..8c9129bef 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -1,9 +1,20 @@ # -*-coding:utf-8-*- - +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 mindspore.nn as nn import mindspore.ops as P -from mindspore.ops import composite as C -from mindspore.ops import operations as op from mindspore import Tensor from mindspore import dtype as mstype @@ -73,8 +84,7 @@ class WS3(nn.Cell): f_stack = [] for i in range(5): - f_encoder_i = self.encoder[i](xyz[i], feature, - neighbor_idx[i]) # (B,40960,3) (4, 8, 40960, 1) (4, 40960, 16) + f_encoder_i = self.encoder[i](xyz[i], feature, neighbor_idx[i]) f_sampled_i = self.random_sample(f_encoder_i, sub_idx[i]) feature = f_sampled_i if i == 0: @@ -115,7 +125,6 @@ class WS3(nn.Cell): b, d = feature.shape[:2] n_ = pool_idx.shape[1] # [B, N', max_num] --> [B, d, N', max_num] - # pool_idx = P.repeat_elements(pool_idx.expand_dims(1), feature.shape[1], 1) pool_idx = P.Tile()(pool_idx.expand_dims(1), (1, feature.shape[1], 1, 1)) # [B, d, N', max_num] --> [B, d, N'*max_num] pool_idx = pool_idx.reshape((b, d, -1)) @@ -126,7 +135,6 @@ class WS3(nn.Cell): class WS3WithLoss(nn.Cell): - """RadnLA-net with loss""" def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, topk): super(WS3WithLoss, self).__init__() @@ -154,89 +162,56 @@ class WS3WithLoss(nn.Cell): xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) - # 当c_epoch_k==0时,只需要计算有GT标注点的loss值 logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) pred_embed = logits_embed[..., self.num_classes:] # (B,N,45) -> (B,N,32) logits = logits.reshape((-1, self.num_classes)) pred_embed = pred_embed.reshape((-1, 32)) - # logit = logits.reshape((-1, self.num_classes)) # (B*N,13) labels = labels.reshape((-1,)) # (b,n) -> (b*n) xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) - # Boolean mask of points that should be ignored # (B*N,) ignore_mask = P.zeros_like(labels).astype(mstype.bool_) for ign_label in self.ignored_label_inds: ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # - # 0为无效,1为有效 valid_mask = P.logical_not(ignore_mask) # (B*N,) # (B*N,13) one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) - # 输出Tensor各维度上的和,以达到对所有维度进行归约的目的。也可以对指定维度进行求和归约 - # (B*N, 13) -> (B*N,) - weights = P.ReduceSum()(weights, 1) # - # (B*N,) and (B*N,13) -> + weights = P.ReduceSum()(weights, 1) unweighted_loss = self.loss_fn(logits, one_hot_labels) weighted_loss = unweighted_loss * weights weighted_loss = weighted_loss * valid_mask - CE_loss = P.ReduceSum()(weighted_loss) + ce_loss = P.ReduceSum()(weighted_loss) num_valid_points = P.ReduceSum()(valid_mask.astype(weighted_loss.dtype)) - # num_valid_points = int(P.count_nonzero(valid_mask.astype(mstype.int32))) - # CE_loss = P.ReduceSum()(weighted_loss).sum() - CE_loss = CE_loss / num_valid_points - ### + ce_loss = ce_loss / num_valid_points if self.c_epoch_k == 0: - loss = CE_loss + loss = ce_loss else: - SP_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, + sp_loss = self.get_sp_loss_by_mask(pred_embed, logits, one_hot_labels, valid_mask, self.topk) * self.c_epoch_k - loss = CE_loss + SP_loss + loss = ce_loss + sp_loss return loss @staticmethod def get_sp_loss_by_mask(embed, logits, one_hot_label, valid_mask, topk): - """ - - Args: - embed: - logits: - one_hot_label: - valid_mask: - topk: - - Returns: - - """ invalid_mask = P.logical_not(valid_mask) # (B*N,) num_invalid_points = int(P.count_nonzero(invalid_mask.astype(mstype.int32))) topk += num_invalid_points - # 点类别的数量 num_class = one_hot_label.shape[1] # scalar: 13 valid_one_hot_label = one_hot_label * valid_mask.reshape(-1, 1) # (B*N,13) valid_embed = embed * valid_mask.reshape(-1, 1) # (B*N,32) invalid_embed = embed * invalid_mask.reshape(-1, 1) # (B*N,32) - # => 将有效点的label矩阵进行转置:[M,class_num] -> [class_num,M] (13,B*N) valid_one_hot_label_T = P.transpose(valid_one_hot_label, (1, 0)) - # => 每一行那个类有那些点:[class_num,B*N] * [B*N,dim] -> [class_num,dim] sum_embed = P.matmul(valid_one_hot_label_T, valid_embed) - # => 求class的平均嵌入:[class_num,dim] - # mean_embed 为每个类别的embedding,如果这个类别没有样本,则embedding全为0 mean_embed = sum_embed / (P.reduce_sum(valid_one_hot_label_T, axis=1).reshape(-1, 1) + 0.001) - # => 求unlabelled points 与 class embedding的相似度 - # adj_matrix 欧式距离,距离越大说明越不相似 [N,M] - # adj_matrix = self.double_feature(invalid_embed, mean_embed) adj_matrix = WS3WithLoss.double_feature(invalid_embed, mean_embed) - # => 稀疏点,N个点中M分别找K和最相似的,把没有和任何M相似的去掉(说明这些点不容易分) - neg_adj = -adj_matrix # (B*N,13) 取负 - # 转置为了下一步 [N, M] -> [M,N] (M是有标签的点) + neg_adj = -adj_matrix # (B*N,13) neg_adj_t = P.transpose(neg_adj, (1, 0)) # (13,B*N) - # 取最不相似的 top k个点 _, nn_idx = P.TopK()(neg_adj_t, topk) s = P.shape(neg_adj_t) # s (M,N) row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) @@ -255,7 +230,6 @@ class WS3WithLoss(nn.Cell): new_soft_label_hot = nn.Softmax(axis=-1)(w_ij) # (B*N,13) top1 = new_soft_label_hot.argmax(axis=-1) - # soft_label_mask = self.onehot(top1) soft_label_mask = P.OneHot()(top1, num_class, Tensor(1.0, dtype=mstype.float16), Tensor(0.0, dtype=mstype.float16)) @@ -268,16 +242,10 @@ class WS3WithLoss(nn.Cell): return loss - # @ms_function() @staticmethod def double_feature(point_feature1, point_feature2): """ Compute pairwise distance of a point cloud. - Args: - [N,C],[M,C] - point_cloud: tensor (batch_size, num_points, num_dims) - Returns: - pairwise distance: (batch_size, num_points, num_points) """ point2_transpose = P.transpose(point_feature2, (1, 0)) # [C, M] point_inner = P.matmul(point_feature1, point2_transpose) # [N,M] @@ -290,31 +258,3 @@ class WS3WithLoss(nn.Cell): adj_matrix = point1_square + point_inner + point2_square_transpose return adj_matrix - - -class TrainingWrapper(nn.Cell): - """Training wrapper.""" - - def __init__(self, network, optimizer, sens=1.0): - super(TrainingWrapper, self).__init__(auto_prefix=False) - self.network = network - self.network_logits = self.network.network - self.network.set_grad() - self.opt_weights = optimizer.parameters - self.optimizer = optimizer - self.grad = C.GradOperation(get_by_list=True, sens_param=True) - self.sens = sens - - def construct(self, xyz, feature, neighbor_idx, sub_idx, interp_idx, labels): - """Build a forward graph""" - - # loss calculate - loss = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels) - logit = self.network_logits(xyz, feature, neighbor_idx, sub_idx, interp_idx) - - # opt update - opt_weights = self.opt_weights - sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) - grads = self.grad(self.network, opt_weights)(xyz, feature, neighbor_idx, sub_idx, interp_idx, labels, sens) - res = P.depend(loss, self.optimizer(grads)) - return res, logit diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py index 479f94df5..9e2c052a5 100644 --- a/research/cv/WS3/src/utils/data_prepare_s3dis.py +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 sys import os from os.path import join, exists, dirname, abspath @@ -8,14 +23,12 @@ import numpy as np import pandas as pd from src.utils.tools import DataProcessing as DP -# from tools import DataProcessing as DP - BASE_DIR = dirname(abspath(__file__)) ROOT_DIR = dirname(BASE_DIR) sys.path.append(BASE_DIR) sys.path.append(ROOT_DIR) -dataset_path = 'xxxx/xxxxx/dataset/S3DIS/Stanford3dDataset_v1.2_Aligned_Version' +dataset_path = '../dataset/S3DIS/Stanford3dDataset_v1.2_Aligned_Version' anno_paths = [line.rstrip() for line in open(join(BASE_DIR, '../../third_party/meta/anno_paths.txt'))] anno_paths = [join(dataset_path, p) for p in anno_paths] diff --git a/research/cv/WS3/src/utils/helper_ply.py b/research/cv/WS3/src/utils/helper_ply.py index 296445dbd..833ac9a2e 100644 --- a/research/cv/WS3/src/utils/helper_ply.py +++ b/research/cv/WS3/src/utils/helper_ply.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 sys import numpy as np @@ -26,13 +41,6 @@ valid_formats = {'ascii': '', 'binary_big_endian': '>', 'binary_little_endian': '<'} -# ---------------------------------------------------------------------------------------------------------------------- -# -# Functions -# \***************/ -# - - def parse_header(plyfile, ext): # Variables line = [] diff --git a/research/cv/WS3/src/utils/logger.py b/research/cv/WS3/src/utils/logger.py index eae0e5aec..9dd6d9a74 100644 --- a/research/cv/WS3/src/utils/logger.py +++ b/research/cv/WS3/src/utils/logger.py @@ -1,4 +1,5 @@ -# Copyright 2021 Huawei Technologies Co., Ltd +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""Custom Logger.""" import logging import os import sys diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py index a03793464..f0f9ec0ea 100644 --- a/research/cv/WS3/src/utils/metrics.py +++ b/research/cv/WS3/src/utils/metrics.py @@ -1,3 +1,18 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 mindspore.numpy as msnp import mindspore as ms diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index 9eb60d933..16c8e27f6 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -1,8 +1,22 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 sys import os.path import numpy as np -# import third_party.nearest_neighbors.lib.python.nearest_neighbors as nearest_neighbors -import third_party.nearest_neighbors as nearest_neighbors +import third_party.nearest_neighbors.nearest_neighbors as nearest_neighbors import third_party.cpp_wrappers.cpp_subsampling.grid_subsampling as cpp_subsampling BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -10,66 +24,6 @@ sys.path.append(BASE_DIR) sys.path.append(os.path.join(BASE_DIR, 'utils')) -# class ConfigPretrain: -# -# def __init__(self): -# pass -# -# k_n = 16 # KNN -# num_layers = 5 # Number of layers -# num_points = 40960 # Number of input points -# num_classes = 6 # a, b, \mu_{a},\mu_{a}, \sigmoid_{a}, \sigmoid_{b} -# sub_grid_size = 0.04 # preprocess_parameter -# -# batch_size = 6 # batch_size during training -# train_steps = 1000 # Number of steps per epochs -# -# sub_sampling_ratio = [4, 4, 4, 4, 2] # sampling ratio of random sampling at each layer -# d_out = [16, 64, 128, 256, 512] # feature dimension -# -# noise_init = 3.5 # 2.0 noise initial parameter -# max_epoch = 100 # maximum epoch during training -# learning_rate = 1e-2 # initial learning rate -# -# train_sum_dir = 'train_log' -# saving = True -# saving_path = None -# -# lr_decays = 0.95 # decay rate of learning rate -# loss_scale = 1.0 # loss scale - - -# ConfigS3DIS = { -# 'dataset_dir': './', -# 'k_n': 16, # KNN -# 'num_layers': 5, # Number of layers -# 'num_points': 40960, # Number of input points -# 'num_classes': 13, # Number of valid classes -# 'sub_grid_size': 0.04, # preprocess_parameter -# 'batch_size': 6, # batch_size during training -# 'val_batch_size': 16, # batch_size during validation and test -# 'train_steps': 500, # Number of steps per epochs -# 'val_steps': 100, # Number of validation steps per epoch -# 'sub_sampling_ratio': [4, 4, 4, 4, 2], # sampling ratio of random sampling at each layer -# 'd_out': [16, 64, 128, 256, 512], # feature dimension -# 'noise_init': 3.5, # noise initial parameter -# 'max_epoch': 80, # maximum epoch during training -# 'learning_rate': 1e-2, # initial learning rate -# 'lr_decays': 0.95, # decay rate of learning rate -# 'loss_scale': 1.0, # loss scale -# 'training_ep0': {i: 0 for i in range(0, 30)}, -# 'training_ep': {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)}, -# 'c_epoch ': 0, -# 'train_sum_dir': 'train_log', -# 'saving ': True, -# 'saving_path': None, -# 'pretrain': True, -# 'checkpoint': './pretrain/snapshots/snap-11001', -# 'topk': 500, -# 'loss3_type': -1, -# } - - class ConfigS3DIS: k_n = 16 # KNN num_layers = 5 # Number of layers @@ -139,8 +93,6 @@ class DataProcessing: "label_aug": label_aug } - # return xyz_aug, color_aug, idx_aug, label_aug - @staticmethod def shuffle_idx(x): # random shuffle the index @@ -209,11 +161,5 @@ class DataProcessing: :return: sub_sampled points, with features and/or labels depending of the input """ - # if (features is None) and (labels is None): - # return cpp_subsampling.compute(points, sampleDl=grid_size, verbose=verbose) - # elif labels is None: - # return cpp_subsampling.compute(points, features=features, sampleDl=grid_size, verbose=verbose) - # elif features is None: - # return cpp_subsampling.compute(points, classes=labels, sampleDl=grid_size, verbose=verbose) return cpp_subsampling.compute(points, features=features, classes=labels, sampleDl=grid_size, verbose=verbose) diff --git a/research/cv/WS3/third_party/compile_op.sh b/research/cv/WS3/third_party/compile_op.sh index e66db472f..8d9d1e15c 100644 --- a/research/cv/WS3/third_party/compile_op.sh +++ b/research/cv/WS3/third_party/compile_op.sh @@ -1,7 +1,7 @@ #!/bin/sh # shellcheck disable=SC2164 cd nearest_neighbors -python setup.py install --home="." +python setup.py build_ext --inplace cd ../ cd cpp_wrappers diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py index 151c5820c..74867cc3d 100644 --- a/research/cv/WS3/train_ascend.py +++ b/research/cv/WS3/train_ascend.py @@ -1,4 +1,18 @@ # -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 datetime import argparse @@ -6,7 +20,7 @@ import pickle from pathlib import Path import numpy as np -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, ops, set_seed +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed from mindspore.nn import Adam from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback from mindspore.train.loss_scale_manager import FixedLossScaleManager @@ -20,30 +34,6 @@ from src.utils.logger import get_logger from src.model.model_s3dis_remove_bias import WS3 from src.model.model_s3dis_remove_bias import WS3WithLoss -use_custom_train_one_step_cell = True - - -class CustomTrainOneStepCell(nn.Cell): - """自定义训练网络""" - - def __init__(self, network, optimizer, sens=1.0): - """入参有三个:训练网络,优化器和反向传播缩放比例""" - super(CustomTrainOneStepCell, self).__init__(auto_prefix=False) - self.network = network # 定义前向网络 - self.network.set_grad() # 构建反向网络 - self.optimizer = optimizer # 定义优化器 - self.weights = self.optimizer.parameters # 待更新参数 - self.grad = ops.GradOperation(get_by_list=True, sens_param=True) # 反向传播获取梯度 - - def construct(self, *inputs): - loss = self.network(*inputs) - - grads = self.grad(self.network, self.weights)(*inputs, loss) - - loss = ops.depend(loss, self.optimizer(grads)) - - return loss - class UpdateLossEpoch(Callback): def __init__(self, cur_num_training_ep0=30, logger=None): @@ -52,17 +42,13 @@ class UpdateLossEpoch(Callback): self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) self.logger = logger - # v1.8: on_train_epoch_begin - # v1.7: epoch_begin + # msv1.7: epoch_begin def epoch_begin(self, run_context): - cb_params = run_context.original_args() - if use_custom_train_one_step_cell: - train_network_with_loss = cb_params.network.network - else: - train_network_with_loss = cb_params.network - cur_epoch_num = cb_params.cur_epoch_num # 从1开始 + train_network_with_loss = cb_params.network + + cur_epoch_num = cb_params.cur_epoch_num train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] self.logger.info( @@ -70,6 +56,7 @@ class UpdateLossEpoch(Callback): f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") + # msv1.8: on_train_epoch_begin def on_train_epoch_begin(self, run_context): self.epoch_begin(run_context) @@ -81,8 +68,7 @@ class S3DISLossMonitor(Callback): self._last_print_time = 0 self.logger = logger - # v1.8: on_train_step_end - # v1.7: step_end + # msv1.7: step_end def step_end(self, run_context): """ Print training loss at the end of step. @@ -108,8 +94,6 @@ class S3DISLossMonitor(Callback): f"step: {cur_step_in_epoch}. " f"Invalid loss {loss}, terminating training.") - # In disaster recovery scenario, the cb_params.cur_step_num may be rollback to previous step - # and be less than self._last_print_time, so self._last_print_time need to be updated. if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): while cb_params.cur_step_num <= self._last_print_time: self._last_print_time -= \ @@ -117,13 +101,13 @@ class S3DISLossMonitor(Callback): if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: self._last_print_time = cb_params.cur_step_num - # self.train_network_with_loss = cb_params.network msg = f"epoch: {cb_params.cur_epoch_num} " \ f"step: {cur_step_in_epoch}, " \ f"loss is {loss} " self.logger.info(msg) + # msv1.8: on_train_step_end def on_train_step_end(self, run_context): self.step_end(run_context) @@ -220,7 +204,6 @@ def train(cfg, args): loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None print('loss_scale:', loss_scale) - # float 16 if cfg.float16: print("network uses float16") network.to_float(mstype.float16) @@ -231,14 +214,10 @@ def train(cfg, args): loss_fn=None, optimizer=opt) else: - if use_custom_train_one_step_cell: - network = CustomTrainOneStepCell(network, opt) - model = Model(network) - else: - model = Model(network, - loss_fn=None, - optimizer=opt, - ) + model = Model(network, + loss_fn=None, + optimizer=opt, + ) # callback for loss & time cost loss_cb = S3DISLossMonitor(50, logger) @@ -247,7 +226,7 @@ def train(cfg, args): # callback for saving ckpt config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) - ckpt_cb = ModelCheckpoint(prefix='randla', + ckpt_cb = ModelCheckpoint(prefix='ws3', directory=os.path.join(args.outputs_dir, 'ckpt'), config=config_ckpt) cbs += [ckpt_cb] @@ -261,13 +240,12 @@ def train(cfg, args): model.train(cfg.max_epoch, train_loader, - callbacks=cbs, # [loss_cb,time_cb,ckpt_cb,update_loss_epoch_cb] + callbacks=cbs, dataset_sink_mode=False) logger.info('==========end training===============') if __name__ == "__main__": - # """Parse program arguments""" parser = argparse.ArgumentParser( prog='WS3', formatter_class=argparse.ArgumentDefaultsHelpFormatter @@ -300,7 +278,6 @@ if __name__ == "__main__": parser.add_argument('--retrain_model', type=str, help='name of the experiment', default=None) parser.add_argument('--float16', type=bool, default=False) - parser.add_argument('--train_steps', type=int, default=500) parser.add_argument('--learning_rate', type=float, default=0.01) parser.add_argument('--lr_decays', type=float, default=0.95) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py index a19986094..12f870853 100644 --- a/research/cv/WS3/train_gpu.py +++ b/research/cv/WS3/train_gpu.py @@ -1,4 +1,18 @@ # -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 datetime import argparse @@ -30,7 +44,7 @@ class UpdateLossEpoch(Callback): def on_train_epoch_begin(self, run_context): cb_params = run_context.original_args() train_network_with_loss = cb_params.network - cur_epoch_num = cb_params.cur_epoch_num # 从1开始 + cur_epoch_num = cb_params.cur_epoch_num train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] self.logger.info( f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " @@ -70,8 +84,6 @@ class S3DISLossMonitor(Callback): cb_params.network.CE_LOSS.asnumpy(), cb_params.network.SP_LOSS.asnumpy())) - # In disaster recovery scenario, the cb_params.cur_step_num may be rollback to previous step - # and be less than self._last_print_time, so self._last_print_time need to be updated. if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): while cb_params.cur_step_num <= self._last_print_time: self._last_print_time -= \ @@ -170,7 +182,6 @@ def train(cfg, args): # loss scale manager loss_scale = cfg.loss_scale - # loss_scale = args.scale_weight loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None print('loss_scale:', loss_scale) -- Gitee From d51defe360a664ab9eb3baed557300b544436367 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Sat, 26 Nov 2022 15:17:42 +0800 Subject: [PATCH 13/16] add third_party referenced project --- research/cv/WS3/README.md | 70 ++-- research/cv/WS3/eval.py | 224 ++++++++++++ research/cv/WS3/scripts/eval_s3dis_ascend.sh | 2 +- research/cv/WS3/scripts/eval_s3dis_gpu.sh | 4 +- research/cv/WS3/scripts/train_s3dis_ascend.sh | 2 +- research/cv/WS3/scripts/train_s3dis_gpu.sh | 23 +- research/cv/WS3/src/data/S3DIS_dataset.py | 42 +-- .../cv/WS3/src/data/S3DIS_dataset_test.py | 21 +- research/cv/WS3/src/model/base_model.py | 27 +- .../WS3/src/model/base_model_remove_bias.py | 24 -- research/cv/WS3/src/model/model_s3dis.py | 20 +- .../WS3/src/model/model_s3dis_remove_bias.py | 11 +- .../cv/WS3/src/utils/data_prepare_s3dis.py | 19 +- research/cv/WS3/src/utils/helper_ply.py | 64 +++- research/cv/WS3/src/utils/logger.py | 7 +- research/cv/WS3/src/utils/metrics.py | 86 ----- research/cv/WS3/src/utils/tools.py | 23 +- .../grid_subsampling/grid_subsampling.cpp | 2 +- .../grid_subsampling/grid_subsampling.h | 2 +- .../cpp_wrappers/cpp_subsampling/setup.py | 1 + .../cpp_wrappers/cpp_subsampling/wrapper.cpp | 1 + .../cpp_wrappers/cpp_utils/cloud/cloud.cpp | 1 + .../cpp_wrappers/cpp_utils/cloud/cloud.h | 1 + .../cpp_utils/nanoflann/nanoflann.hpp | 1 + .../nearest_neighbors/KDTreeTableAdaptor.h | 2 +- .../WS3/third_party/nearest_neighbors/knn.cpp | 1 + .../WS3/third_party/nearest_neighbors/knn.pyx | 1 + .../third_party/nearest_neighbors/knn_.cxx | 2 +- .../WS3/third_party/nearest_neighbors/knn_.h | 2 +- .../nearest_neighbors/nanoflann.hpp | 2 + .../third_party/nearest_neighbors/setup.py | 1 + research/cv/WS3/train.py | 329 ++++++++++++++++++ research/cv/WS3/train_ascend.py | 2 +- 33 files changed, 713 insertions(+), 307 deletions(-) create mode 100644 research/cv/WS3/eval.py delete mode 100644 research/cv/WS3/src/utils/metrics.py create mode 100644 research/cv/WS3/train.py diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 79dcf026d..29d9917d6 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -1,17 +1,43 @@ # WS3: Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud +# Contents + +- [WS3](#WS3) + - [Model Architecture](#model-architecture) + - [Dataset](#dataset) + - [Preparation](#preparation) + - [Directory structure of dataset](#directory-structure-of-dataset) + - [Requirements](#requirements) + - [Install dependencies](#Install-dependencies) + - [Quick Start](#quick-start) + - [Script Description](#script-description) + - [Scripts and Sample Code](#scripts-and-sample-code) + - [Script Parameter](#script-parameter) + - [Training](#training) + - [Training Process](#training-process) + - [Training Result](#training-result) + - [Evaluation](#evaluation) + - [Evaluation Process 910](#evaluation-process-910) + - [Evaluation Result 910](#evaluation-result-910) + - [Performance](#performance) + - [Training Performance](#training-performance) + - [Inference Performance](#inference-performance) + - [S3DIS](#s3DIS) + - [Reference](#reference) + +# [WS3](#contents) Mindspore implementation for ***"Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud"*** Please read the [original paper](https://ojs.aaai.org/index.php/AAAI/article/view/16455) or [original tensorflow implementation](https://github.com/Yachao-Zhang/WS3) for more detailed information. -## Model Architecture +## [Model Architecture](#contents) ![WS3 Framework](./figs/WS3_framwork.png) -## Dataset +## [Dataset](#contents) -### Preparation +### [Preparation](#contents) 1. Download S3DIS dataset from this [link](https://docs.google.com/forms/d/e/1FAIpQLScDimvNMCGhy_rmBA2gHfDu3naktRm6A8BPwAWWDv-Uhm6Shw/viewform?c=0&w=1) @@ -21,7 +47,7 @@ or [original tensorflow implementation](https://github.com/Yachao-Zhang/WS3) for 4. run `data_prepare_s3dis.py` (in `src/utils/data_prepare_s3dis.py`) to process data. The processed data will be stored in `input_0.040` and `original_ply` folders. -### Directory structure of dataset +### [Directory structure of dataset](#contents) ```text dataset @@ -36,7 +62,7 @@ dataset └── Stanford3dDataset_v1.2_Aligned_Version ``` -## Requirements +## [Requirements](#contents) - Hardware - For Ascend: Ascend 910. @@ -51,12 +77,12 @@ dataset - scikit-learn==0.21.3 - numpy==1.21.6 -### Install dependencies +### [Install dependencies](#contents) 1. `pip install -r requirements.txt` 2. `cd third_party` & `bash compile_op.sh` -## Quick Start +## [Quick Start](#contents) For GPU: @@ -72,9 +98,9 @@ bash scripts/train_s3dis_ascend.sh bash scripts/eval_s3dis_ascend.sh ``` -## Script Description +## [Script Description](#contents) -### Scripts and Sample Code +### [Scripts and Sample Code](#contents) ```text WS3 @@ -112,7 +138,7 @@ WS3 └── train_ascend.py ``` -### Script Parameter +### [Script Parameter](#contents) we use `train_s3dis_ascend.sh` as an example: @@ -151,9 +177,9 @@ The following table describes the arguments. You can change freely as you want. | `--num_training_ep0` | start from X epoch to use sparse pseudo loss | | `--name` | experiment name, which will combine with outputs_dir. The output files for current experiments will be stores in `outputs_dir/name` | -## Training +## [Training](#contents) -### Training Process +### [Training Process](#contents) For GPU: @@ -181,7 +207,7 @@ epoch: 39 step: 60, loss is 1.307090401649475 ... ``` -### Training Result +### [Training Result](#contents) Using `bash scripts/eval_s3dis_ascend.sh` as an example: @@ -198,9 +224,9 @@ outputs └── .... ``` -## Evaluation +## [Evaluation](#contents) -### Evaluation Process 910 +### [Evaluation Process 910](#contents) For GPU: @@ -217,7 +243,7 @@ bash scripts/eval_s3dis_ascend.sh Note: Before you start eval, please check `--model_path` in `eval_xxx.sh` and guarantee `--model_path` is equal to `{args.outputs_dir}/{args.name}` in `train_xxx.sh`. -### Evaluation Result 910 +### [Evaluation Result 910](#contents) ```text Area_5_office_19 Acc:0.8584098992023179 @@ -236,9 +262,9 @@ Area_5_office_23 Acc:0.9049251547225916 ==========end test=============== ``` -## Performance +## [Performance](#contents) -### Training Performance +### [Training Performance](#contents) | Parameters | Ascend 910 | GPU (3090) | | -------------------------- | ------------------------------------------------------------ | ----------------------------------------------| @@ -258,7 +284,7 @@ Area_5_office_23 Acc:0.9049251547225916 | Checkpoint for Fine tuning | 57.14 MB (.ckpt file) | 76.31 MB (.ckpt file) | | Scripts | [link](https://gitee.com/mindspore/models/tree/master/official/cv/WS3) | -### Inference Performance +### [Inference Performance](#contents) | Parameters | Ascend | GPU | | ------------------- | --------------------------- |--------------------------- | @@ -271,13 +297,13 @@ Area_5_office_23 Acc:0.9049251547225916 | outputs | feature vector + probability | feature vector + probability | | Accuracy | See following tables | See following tables | -### S3DIS (Setting: 1% labeled points) - +### [S3DIS](#contents) +- Setting: 1% labeled points | Metric | Value(Tensorflow)| Value(Mindspore, Ascend) | Value(Mindspore, GPU) | | :----: | :------------: | :-------------------: | :-------------------: | | mIoU | 61.8% | 61.5% | 60.3% | -## Reference +## [Reference](#contents) Please kindly cite the original paper references in your publications if it helps your research: diff --git a/research/cv/WS3/eval.py b/research/cv/WS3/eval.py new file mode 100644 index 000000000..036a2a8c8 --- /dev/null +++ b/research/cv/WS3/eval.py @@ -0,0 +1,224 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 argparse +from pathlib import Path +import numpy as np +from sklearn.metrics import confusion_matrix + +from mindspore import context, load_checkpoint, load_param_into_net, ops +from mindspore import dtype as mstype + +from src.data.S3DIS_dataset_test import dataloader, ms_map +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as cfg + +from src.utils.logger import get_logger +from src.utils.helper_ply import write_ply + +from tqdm import tqdm + + +def run_eval(params): + context.set_context(mode=context.PYNATIVE_MODE, device_target=params.device_target, device_id=params.device_id) + logger = get_logger(params.outputs_dir, params.rank) + _, val_ds, dataset = dataloader(dataset_dir=params.dataset_dir, num_parallel_workers=8, shuffle=False) + input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] + output_columns = ["features", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4"] + val_loader = val_ds.batch(batch_size=params.batch_size, + per_batch_map=ms_map, + input_columns=input_columns, + output_columns=output_columns, + drop_remainder=True) + val_ds_size = val_loader.get_dataset_size() + val_loader = val_loader.create_dict_iterator() + + if params.device_target == 'Ascend': + from src.model.model_s3dis_remove_bias import WS3 + else: + from src.model.model_s3dis import WS3 + + network = WS3(6, cfg.num_classes) + network.set_train(False) + + if params.float16: + print("network uses float16") + network.to_float(mstype.float16) + + if '.ckpt' in params.model_path: + ckpts = [params.model_path] + else: + ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) + ckpts = ckpt_path.glob('*.ckpt') + ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) + + best_miou, best_ckpt = 0.0, ckpts[0] + ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) + logger.info('==========begin test===============') + for ckpt_i, ckpt in enumerate(ckpts): + logger.info('load ckpt from:{}'.format(str(ckpt))) + param_dict = load_checkpoint(str(ckpt)) + load_param_into_net(network, param_dict) + + val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) + i = 0 + for label_val in dataset.label_values: + if label_val not in dataset.ignored_labels: + val_proportions[i] = np.sum([np.sum(labels == label_val) for labels in dataset.val_labels]) + i += 1 + test_probs = [np.zeros(shape=[l.shape[0], cfg.num_classes], dtype=np.float32) + for l in dataset.input_labels['validation']] + + # Smoothing parameter for votes + test_smooth = 0.95 + step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) + for step_i, data in enumerate(val_loader): + features = data['features'] + labels = data['labels'] # (B,N) + xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] + neigh_idx = [data['n0'], data['n1'], data['n2'], data['n3'], data['n4']] + sub_idx = [data['pl0'], data['pl1'], data['pl2'], data['pl3'], data['pl4']] + interp_idx = [data['u0'], data['u1'], data['u2'], data['u3'], data['u4']] + point_idx = data['input_inds'].asnumpy() + cloud_idx = data['cloud_inds'].asnumpy() + + logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] + logits = logits[..., :cfg.num_classes] + + prob_logits = ops.Softmax(-1)(logits) + + prob_logits = prob_logits.asnumpy() + for j in range(np.shape(prob_logits)[0]): + probs = prob_logits[j] + p_idx = point_idx[j, :] + c_i = cloud_idx[j][0] + test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs + + correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) + acc = correct / float(np.prod(np.shape(labels))) + msg = f'Step: {str(step_i)}; acc: {str(acc)}' + step_bar.set_postfix_str(msg, refresh=False) + step_bar.update() + + best_ckpt, best_miou = cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, + val_ds, val_proportions) + ckpt_bar.update() + + logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) + + +def cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, val_ds, val_proportions): + last_min, num_votes = -0.5, 100 + while last_min < num_votes: + new_min = np.min(val_ds.source.min_possibility['validation']) + logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") + if last_min + 1 < new_min: + last_min += 1 + logger.info('Confusion on sub clouds') + confusion_list = [] + + num_val = len(dataset.input_labels['validation']) + + for i_test in range(num_val): + probs = test_probs[i_test] + preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) + labels = dataset.input_labels['validation'][i_test] + confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] + + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) + # Rescale with the right number of point per class + C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) + # Compute IoUs + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info(s + '\n') + if int(np.ceil(new_min)) % 1 == 0: + # Project predictions + logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) + proj_probs_list = [] + for i_val in range(num_val): + # Reproject probs back to the evaluations points + proj_idx = dataset.val_proj[i_val] + probs = test_probs[i_val][proj_idx, :] + proj_probs_list += [probs] + # Show vote results + logger.info('Confusion on full clouds') + confusion_list = [] + for i_test in range(num_val): + # Get the predicted labels + preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) + # Confusion + labels = dataset.val_labels[i_test] + acc = np.sum(preds == labels) / len(labels) + logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) + confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] + name = dataset.input_names['validation'][i_test] + '.ply' + write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], + ['pred', 'label']) + # Regroup confusions + C = np.sum(np.stack(confusion_list), axis=0) + IoUs = DP.IoU_from_confusions(C) + m_IoU = np.mean(IoUs) + if m_IoU > best_miou: + best_miou = m_IoU + best_ckpt = ckpt + s = '{:5.2f} | '.format(100 * m_IoU) + for IoU in IoUs: + s += '{:5.2f} '.format(100 * IoU) + logger.info('-' * len(s)) + logger.info(s) + logger.info('-' * len(s) + '\n') + logger.info('==========end test===============') + break + return best_ckpt, best_miou + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog='WS3', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument('--batch_size', type=int, help='val batch size', default=20) + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--model_path', type=str, help='model saved path', default='runs') + parser.add_argument('--device_target', type=str, help='Ascend | GPU', default='Ascend', choices=['Ascend', 'GPU']) + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + parser.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') + parser.add_argument('--float16', type=bool, default=False) + + args = parser.parse_args() + + base_dir = os.path.dirname(os.path.abspath(__file__)) + args.model_path = os.path.join(base_dir, args.model_path) + + if not os.path.exists(args.outputs_dir): + os.makedirs(args.outputs_dir) + + val_pred_path = os.path.join(args.outputs_dir, 'val_preds') + if not os.path.exists(val_pred_path): + os.makedirs(val_pred_path) + + run_eval(args) diff --git a/research/cv/WS3/scripts/eval_s3dis_ascend.sh b/research/cv/WS3/scripts/eval_s3dis_ascend.sh index 97f8bdea5..ba032fa00 100644 --- a/research/cv/WS3/scripts/eval_s3dis_ascend.sh +++ b/research/cv/WS3/scripts/eval_s3dis_ascend.sh @@ -14,7 +14,7 @@ # limitations under the License. # ============================================================================ -python eval_ascend.py \ +python eval.py \ --dataset_dir ../dataset/S3DIS \ --device_target Ascend \ --val_area Area_5 \ diff --git a/research/cv/WS3/scripts/eval_s3dis_gpu.sh b/research/cv/WS3/scripts/eval_s3dis_gpu.sh index 128c17f30..542bb058f 100644 --- a/research/cv/WS3/scripts/eval_s3dis_gpu.sh +++ b/research/cv/WS3/scripts/eval_s3dis_gpu.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2022 Huawei Technologies Co., Ltd +# Copyright 2021 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # ============================================================================ -python eval_gpu.py \ +python eval.py \ --dataset_dir ../dataset/S3DIS \ --device_target GPU \ --val_area Area_5 \ diff --git a/research/cv/WS3/scripts/train_s3dis_ascend.sh b/research/cv/WS3/scripts/train_s3dis_ascend.sh index 2f6a3a46b..23b1d647d 100644 --- a/research/cv/WS3/scripts/train_s3dis_ascend.sh +++ b/research/cv/WS3/scripts/train_s3dis_ascend.sh @@ -14,7 +14,7 @@ # limitations under the License. # ============================================================================ -python train_ascend.py \ +python train.py \ --epochs 40 \ --batch_size 6 \ --val_area Area_5 \ diff --git a/research/cv/WS3/scripts/train_s3dis_gpu.sh b/research/cv/WS3/scripts/train_s3dis_gpu.sh index 37e5eba08..2364337c1 100644 --- a/research/cv/WS3/scripts/train_s3dis_gpu.sh +++ b/research/cv/WS3/scripts/train_s3dis_gpu.sh @@ -14,13 +14,16 @@ # limitations under the License. # ============================================================================ -python train_gpu.py \ ---dataset_dir ../dataset/S3DIS \ ---epochs 100 \ ---val_area Area_5 \ ---device_target GPU \ ---device_id 0 \ ---outputs_dir ./outputs \ ---labeled_percent 1 \ ---num_training_ep0 30 \ ---name BatchS_6_Float32_PyNative_GPU \ No newline at end of file +python train.py \ + --epochs 100 \ + --batch_size 6 \ + --val_area Area_5 \ + --dataset_dir ../dataset/S3DIS \ + --device_target GPU \ + --device_id 0 \ + --train_steps 500 \ + --topk 500 \ + --outputs_dir ./outputs \ + --labeled_percent 1 \ + --num_training_ep0 30 \ + --name BatchS_6_Float32_PyNative_GPU diff --git a/research/cv/WS3/src/data/S3DIS_dataset.py b/research/cv/WS3/src/data/S3DIS_dataset.py index 518daa23b..aec095e56 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset.py +++ b/research/cv/WS3/src/data/S3DIS_dataset.py @@ -27,9 +27,7 @@ from src.utils.tools import ConfigS3DIS as cfg class S3DISDatasetGenerator: def __init__(self, dataset_dir, labeled_point_percent=1): - self.dataset_path = Path(dataset_dir) - # self.dataset_path = Path("./datasets/S3DIS") print(self.dataset_path.absolute()) self.label_to_names = {0: 'ceiling', 1: 'floor', 2: 'wall', 3: 'beam', 4: 'column', 5: 'window', 6: 'door', 7: 'table', 8: 'chair', 9: 'sofa', 10: 'bookcase', 11: 'board', 12: 'clutter', @@ -44,7 +42,7 @@ class S3DISDatasetGenerator: self.original_ply_paths = self.dataset_path / 'original_ply' self.paths = list(self.original_ply_paths.glob('*.ply')) self.size = len(self.paths) - # + self.labeled_point = f'{labeled_point_percent}%' self.sampling_mode = 0 # 0 random, 1:spt self.sub_grid_size = 0.04 @@ -183,17 +181,12 @@ class ActiveLearningSampler(ds.Sampler): return self.spatially_regular_gen() def __len__(self): - return self.n_samples * self.batch_size # not equal to the actual size of the dataset, but enable nice progress bars - - # def __len__(self): - # len = self.n_samples * self.batch_size - # print(f"Length of ActiveLearningSampler is {len}") - # return len # not equal to the actual size of the dataset, but enable nice progress bars + # not equal to the actual size of the dataset, but enable nice progress bars + return self.n_samples * self.batch_size def spatially_regular_gen(self): # Choosing the least known point as center of a new cloud each time. for _ in range(self.n_samples * self.batch_size): # num_per_epoch - # begin_time = time.time() # Generator loop # Choose a random cloud cloud_idx = int(np.argmin(self.min_possibility[self.split])) @@ -217,11 +210,11 @@ class ActiveLearningSampler(ds.Sampler): else: queried_idx = \ self.dataset.input_trees[self.split][cloud_idx].query(pick_point, k=self.cfg.num_points)[1][0] - + # training only if self.split == 'training': - s_indx = self.dataset.s_indx[self.split][cloud_idx] # training only + s_indx = self.dataset.s_indx[self.split][cloud_idx] # Shuffle index - queried_idx = np.concatenate([np.array(s_indx), queried_idx], 0)[:self.cfg.num_points] # training only + queried_idx = np.concatenate([np.array(s_indx), queried_idx], 0)[:self.cfg.num_points] # Shuffle index queried_idx = DP.shuffle_idx(queried_idx) @@ -239,21 +232,8 @@ class ActiveLearningSampler(ds.Sampler): # up_sampled with replacement if len(points) < self.cfg.num_points: - # 虽然叫data_aug, 但与印象中的数据增强相差甚远 - - data_aug_dict = DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, - self.cfg.num_points) - - queried_pc_xyz = data_aug_dict['xyz_aug'] - queried_pc_colors = data_aug_dict['color_aug'] - queried_idx = data_aug_dict['idx_aug'] - queried_pc_labels = data_aug_dict['label_aug'] - # queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = data_aug_dict['xyz_aug'], \ - # data_aug_dict['color_aug'], \ - # data_aug_dict['idx_aug'], \ - # data_aug_dict['label_aug'] - # queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ - # DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, self.cfg.num_points) + queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ + DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cfg.num_points) queried_pc_xyz = queried_pc_xyz.astype(np.float32) queried_pc_colors = queried_pc_colors.astype(np.float32) @@ -272,12 +252,9 @@ def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_id pc_idx => [B,N,] cloud_idx => [B,] """ - # begin_time = time.time() batch_xyz = np.array(batch_xyz, dtype=np.float32) batch_features = np.array(batch_features, dtype=np.float32) - # batch_features = data_augment(batch_xyz, batch_features) - # batch_xyz = batch_features[:, :3] batch_features = np.concatenate((batch_xyz, batch_features), axis=-1) input_points = [] # [num_layers, B, N, 3] input_neighbors = [] # [num_layers, B, N, 16] @@ -295,9 +272,6 @@ def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_id input_up_samples.append(up_i) batch_xyz = sub_points - # print(strftime("%Y-%m-%d %H:%M:%S", localtime()) + - # f"[S3DIS_dataset.py] ms_map 耗时: {time.time() - begin_time}s") - return batch_features, batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, \ input_points[0], input_points[1], input_points[2], input_points[3], input_points[4], \ input_neighbors[0], input_neighbors[1], input_neighbors[2], input_neighbors[3], input_neighbors[4], \ diff --git a/research/cv/WS3/src/data/S3DIS_dataset_test.py b/research/cv/WS3/src/data/S3DIS_dataset_test.py index 2dfd316a7..f37891e9f 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset_test.py +++ b/research/cv/WS3/src/data/S3DIS_dataset_test.py @@ -127,6 +127,7 @@ class S3DISDatasetGenerator: class ActiveLearningSampler(ds.Sampler): def __init__(self, dataset, batch_size=6, split='training'): + super(ActiveLearningSampler, self).__init__() self.dataset = dataset self.split = split self.batch_size = batch_size @@ -149,7 +150,8 @@ class ActiveLearningSampler(ds.Sampler): return self.spatially_regular_gen() def __len__(self): - return self.n_samples * self.batch_size # not equal to the actual size of the dataset, but enable nice progress bars + # not equal to the actual size of the dataset, but enable nice progress bars + return self.n_samples * self.batch_size def spatially_regular_gen(self): # Generator loop @@ -190,12 +192,8 @@ class ActiveLearningSampler(ds.Sampler): self.min_possibility[self.split][cloud_idx] = float(np.min(self.possibility[self.split][cloud_idx])) if len(points) < cfg.num_points: - data_aug_dict = DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, - cfg.num_points) - queried_pc_xyz = data_aug_dict['xyz_aug'] - queried_pc_colors = data_aug_dict['color_aug'] - queried_idx = data_aug_dict['idx_aug'] - queried_pc_labels = data_aug_dict['label_aug'] + queried_pc_xyz, queried_pc_colors, queried_idx, queried_pc_labels = \ + DP.data_aug(queried_pc_xyz, queried_pc_colors, queried_pc_labels, queried_idx, cfg.num_points) queried_pc_xyz = queried_pc_xyz.astype(np.float32) queried_pc_colors = queried_pc_colors.astype(np.float32) @@ -234,7 +232,6 @@ def ms_map(batch_xyz, batch_features, batch_labels, batch_pc_idx, batch_cloud_id input_up_samples.append(up_i) batch_xyz = sub_points - # b_f:[B, N, 3+d] # due to the constraints of the mapping function, only the list elements can be passed back sequentially return batch_features, batch_labels, batch_pc_idx, batch_cloud_idx, input_points[0], input_points[1], input_points[ 2], input_points[3], input_points[4], \ @@ -256,8 +253,6 @@ def dataloader(dataset_dir, **kwargs): batch_size=cfg.batch_size, split='training' ) - return ds.GeneratorDataset(train_sampler, ["xyz", "colors", "labels", "q_idx", "c_idx"], - **kwargs), \ - ds.GeneratorDataset(val_sampler, - ["xyz", "colors", "labels", "q_idx", - "c_idx"], **kwargs), dataset + return ds.GeneratorDataset(train_sampler, ["xyz", "colors", "labels", "q_idx", "c_idx"], **kwargs), \ + ds.GeneratorDataset(val_sampler, ["xyz", "colors", "labels", "q_idx", "c_idx"], **kwargs), \ + dataset diff --git a/research/cv/WS3/src/model/base_model.py b/research/cv/WS3/src/model/base_model.py index e72c01293..820569d9e 100644 --- a/research/cv/WS3/src/model/base_model.py +++ b/research/cv/WS3/src/model/base_model.py @@ -16,9 +16,6 @@ import mindspore as ms import mindspore.nn as nn import mindspore.ops as P -from mindspore.ops import composite as C -from mindspore.ops import functional as F -from mindspore.ops import operations as op from mindspore.common.initializer import TruncatedNormal @@ -48,7 +45,8 @@ class SharedMLP(nn.Cell): weight_init=TruncatedNormal(sigma=1e-3) ) self.has_bn = bn - self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) + if self.has_bn: + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) self.activation_fn = activation_fn def construct(self, x): @@ -205,27 +203,6 @@ class LocalFeatureAggregation(nn.Cell): return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) -class TrainingWrapper(nn.Cell): - """Training wrapper.""" - - def __init__(self, network, optimizer, sens=1.0): - super(TrainingWrapper, self).__init__(auto_prefix=False) - self.network = network - self.network.set_grad() - self.weights = optimizer.parameters - self.optimizer = optimizer - self.grad = C.GradOperation(get_by_list=True, sens_param=True) - self.sens = sens - - def construct(self, *args): - """Build a forward graph""" - weights = self.weights - loss, logits = self.network(*args) - sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) - grads = self.grad(self.network, weights)(*args, sens) - return F.depend(loss, self.optimizer(grads)), logits - - def get_param_groups(network): """Param groups for optimizer.""" decay_params = [] diff --git a/research/cv/WS3/src/model/base_model_remove_bias.py b/research/cv/WS3/src/model/base_model_remove_bias.py index 4e934578d..bb3ce57f7 100644 --- a/research/cv/WS3/src/model/base_model_remove_bias.py +++ b/research/cv/WS3/src/model/base_model_remove_bias.py @@ -15,9 +15,6 @@ # ============================================================================ import mindspore.nn as nn import mindspore.ops as P -from mindspore.ops import composite as C -from mindspore.ops import functional as F -from mindspore.ops import operations as op from mindspore.common.initializer import TruncatedNormal from mindspore import dtype as mstype @@ -206,27 +203,6 @@ class LocalFeatureAggregation(nn.Cell): return self.lrelu(self.mlp2(f_pc_agg) + self.shortcut(features)) -class TrainingWrapper(nn.Cell): - """Training wrapper.""" - - def __init__(self, network, optimizer, sens=1.0): - super(TrainingWrapper, self).__init__(auto_prefix=False) - self.network = network - self.network.set_grad() - self.weights = optimizer.parameters - self.optimizer = optimizer - self.grad = C.GradOperation(get_by_list=True, sens_param=True) - self.sens = sens - - def construct(self, *args): - """Build a forward graph""" - weights = self.weights - loss, logits = self.network(*args) - sens = op.Fill()(op.DType()(loss), op.Shape()(loss), self.sens) - grads = self.grad(self.network, weights)(*args, sens) - return F.depend(loss, self.optimizer(grads)), logits - - def get_param_groups(network): """Param groups for optimizer.""" decay_params = [] diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index 6ccf9b7cb..d2885c1d4 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -82,7 +82,6 @@ class WS3(nn.Cell): feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) feature = self.bn_start(feature) # shape (B, 8, N, 1) # <<<<<<<<<< ENCODER - f_stack = [] for i in range(5): f_encoder_i = self.encoder[i](xyz[i], feature, @@ -92,12 +91,9 @@ class WS3(nn.Cell): if i == 0: f_stack.append(f_encoder_i) f_stack.append(f_sampled_i) - # # >>>>>>>>>> ENCODER - + # >>>>>>>>>> ENCODER feature = self.mlp(f_stack[-1]) # [B, d, N, 1] - # <<<<<<<<<< DECODER - f_decoder_list = [] for j in range(5): f_interp_i = self.random_sample(feature, interp_idx[-j - 1]) # [B, d, n, 1] @@ -138,18 +134,15 @@ class WS3(nn.Cell): class WS3WithLoss(nn.Cell): - def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, loss3_type, topk): + def __init__(self, network, weights, num_classes, ignored_label_indexs, c_epoch, topk): super(WS3WithLoss, self).__init__() self.network = network self.weights = Tensor(weights, dtype=mstype.float32) self.num_classes = num_classes self.ignored_label_inds = ignored_label_indexs self.c_epoch = c_epoch - self.loss3_type = loss3_type self.topk = topk - self.c_epoch_k = Tensor(self.c_epoch, dtype=mstype.float32) - # self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float32) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) @@ -162,7 +155,6 @@ class WS3WithLoss(nn.Cell): interp_idx = [u0, u1, u2, u3, u4] logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) - xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) @@ -170,7 +162,6 @@ class WS3WithLoss(nn.Cell): logits = logits.reshape((-1, self.num_classes)) pred_embed = pred_embed.reshape((-1, 32)) labels = labels.reshape((-1,)) # (b,n) -> (b*n) - xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) # Boolean mask of points that should be ignored # (B*N,) @@ -179,19 +170,18 @@ class WS3WithLoss(nn.Cell): ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # valid_mask = P.logical_not(ignore_mask) # (B*N,) - # (B*N,13) one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) # (B*N, 13) -> (B*N,) - weights = P.ReduceSum()(weights, 1) # - # (B*N,) and (B*N,13) -> + weights = P.ReduceSum()(weights, 1) + # (B*N,) & (B*N,13) unweighted_loss = self.loss_fn(logits, one_hot_labels) weighted_loss = unweighted_loss * weights weighted_loss = weighted_loss * valid_mask ce_loss = P.ReduceSum()(weighted_loss) num_valid_points = P.ReduceSum()(valid_mask.astype(mstype.float32)) ce_loss = ce_loss / num_valid_points - ### + if self.c_epoch_k == 0: loss = ce_loss else: diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index 8c9129bef..bc410a569 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -81,7 +81,6 @@ class WS3(nn.Cell): feature = self.fc_start(feature).swapaxes(-2, -1).expand_dims(-1) # (B, N, 6) -> (B, 8, N, 1) feature = self.bn_start(feature) # shape (B, 8, N, 1) # <<<<<<<<<< ENCODER - f_stack = [] for i in range(5): f_encoder_i = self.encoder[i](xyz[i], feature, neighbor_idx[i]) @@ -90,12 +89,9 @@ class WS3(nn.Cell): if i == 0: f_stack.append(f_encoder_i) f_stack.append(f_sampled_i) - # # >>>>>>>>>> ENCODER - + # >>>>>>>>>> ENCODER feature = self.mlp(f_stack[-1]) # [B, d, N, 1] - # <<<<<<<<<< DECODER - f_decoder_list = [] for j in range(5): f_interp_i = self.random_sample(feature, interp_idx[-j - 1]) # [B, d, n, 1] @@ -143,23 +139,19 @@ class WS3WithLoss(nn.Cell): self.num_classes = num_classes self.ignored_label_inds = ignored_label_indexs self.topk = topk - self.c_epoch_k = Tensor(c_epoch, dtype=mstype.float16) - # self.onehot = nn.OneHot(depth=num_classes, dtype=mstype.float16) self.loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=False) def construct(self, feature, feature2, labels, input_inds, cloud_inds, p0, p1, p2, p3, p4, n0, n1, n2, n3, n4, pl0, pl1, pl2, pl3, pl4, u0, u1, u2, u3, u4): - # data_begin_time = time.time() xyz = [p0, p1, p2, p3, p4] neighbor_idx = [n0, n1, n2, n3, n4] sub_idx = [pl0, pl1, pl2, pl3, pl4] interp_idx = [u0, u1, u2, u3, u4] logits_embed = self.network(xyz, feature, neighbor_idx, sub_idx, interp_idx) - xyzrgb = feature2 # (B, N, 6) labels = labels # (B,N) logits = logits_embed[..., :self.num_classes] # (B,N,45) -> (B,N,13) @@ -167,7 +159,6 @@ class WS3WithLoss(nn.Cell): logits = logits.reshape((-1, self.num_classes)) pred_embed = pred_embed.reshape((-1, 32)) labels = labels.reshape((-1,)) # (b,n) -> (b*n) - xyzrgb = xyzrgb.reshape((-1, 6)) # (b,n,6) -> (b*n,6) # (B*N,) ignore_mask = P.zeros_like(labels).astype(mstype.bool_) diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py index 9e2c052a5..da0e6112f 100644 --- a/research/cv/WS3/src/utils/data_prepare_s3dis.py +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -1,18 +1,4 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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. -# ============================================================================ +# This file was copied from project [QingyongHu][RandLA-Net] import sys import os from os.path import join, exists, dirname, abspath @@ -43,8 +29,6 @@ if not exists(original_pc_folder): if not exists(sub_pc_folder): os.mkdir(sub_pc_folder) -# os.mkdir(original_pc_folder) if not exists(original_pc_folder) else None -# os.mkdir(sub_pc_folder) if not exists(sub_pc_folder) else None out_format = '.npy' @@ -73,7 +57,6 @@ def convert_pc2npy(anno_path, save_path): xyz = pc_label[:, :3].astype(np.float32) colors = pc_label[:, 3:6].astype(np.uint8) labels = np.expand_dims(pc_label[:, 6].astype(np.uint8), axis=1) - # print('xyz.shape:',xyz.shape,' colors.shape:',colors.shape,' labels.shape:',labels.shape) np.save(save_path, np.concatenate((xyz, colors, labels), axis=1).T) # save sub_cloud and KDTree file diff --git a/research/cv/WS3/src/utils/helper_ply.py b/research/cv/WS3/src/utils/helper_ply.py index 833ac9a2e..68770d75e 100644 --- a/research/cv/WS3/src/utils/helper_ply.py +++ b/research/cv/WS3/src/utils/helper_ply.py @@ -1,18 +1,50 @@ # -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd +# MIT License # -# 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 +# Copyright (c) 2019 HuguesTHOMAS # -# http://www.apache.org/licenses/LICENSE-2.0 +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: # -# 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. -# ============================================================================ +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file was copied from project [HuguesTHOMAS][KPConv] +# +# +# 0===============================0 +# | PLY files reader/writer | +# 0===============================0 +# +# +# ---------------------------------------------------------------------------------------------------------------------- +# +# function to read/write .ply files +# +# ---------------------------------------------------------------------------------------------------------------------- +# +# Hugues THOMAS - 10/02/2017 +# + + +# ---------------------------------------------------------------------------------------------------------------------- +# +# Imports and global variables +# \**********************************/ + + import sys import numpy as np @@ -232,19 +264,19 @@ def write_ply(filename, field_list, field_names, triangular_faces=None): if field.ndim < 2: field_list[i] = field.reshape(-1, 1) if field.ndim > 2: - # print('fields have more than 2 dimensions') + print('fields have more than 2 dimensions') return False - # check all fields have the same number of data + # check all fields have the same number of data n_points = [field.shape[0] for field in field_list] if not np.all(np.equal(n_points, n_points[0])): - # print('wrong field dimensions') + print('wrong field dimensions') return False - # Check if field_names and field_list have same nb of column + # Check if field_names and field_list have same nb of column n_fields = np.sum([field.shape[1] for field in field_list]) if n_fields != len(field_names): - # print('wrong number of field names') + print('wrong number of field names') return False # Add extension if not there diff --git a/research/cv/WS3/src/utils/logger.py b/research/cv/WS3/src/utils/logger.py index 9dd6d9a74..d6ceac35a 100644 --- a/research/cv/WS3/src/utils/logger.py +++ b/research/cv/WS3/src/utils/logger.py @@ -1,5 +1,4 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd +# Copyright 2020 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# This file was copied from project [OpenHarmony][third_party_mindspore] +"""Custom Logger.""" import logging import os import sys @@ -80,6 +81,6 @@ class LOGGER(logging.Logger): def get_logger(path, rank): """Get Logger.""" - logger = LOGGER('RandLa-net', rank) + logger = LOGGER('WS3', rank) logger.setup_logging_file(path, rank) return logger diff --git a/research/cv/WS3/src/utils/metrics.py b/research/cv/WS3/src/utils/metrics.py deleted file mode 100644 index f0f9ec0ea..000000000 --- a/research/cv/WS3/src/utils/metrics.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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 mindspore.numpy as msnp -import mindspore as ms - - -def accuracy(scores, labels): - r""" - Compute the per-class accuracies and the overall accuracy - - Parameters - ---------- - scores: ms.Tensor, dtype = float32, shape (B?, C, N) - raw scores for each class - labels: ms.Tensor, dtype = int64, shape (B?, N) - ground truth labels - - Returns - ------- - list of floats of length num_classes+1 (last item is overall accuracy) - """ - num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions - - predictions = scores.argmax(axis=-2) - - accuracies = 0 - - accuracy_mask = predictions == labels - for label in range(num_classes): - label_mask = labels == label - per_class_accuracy = msnp.logical_and(accuracy_mask, label_mask).astype(ms.float32).sum() - per_class_accuracy /= label_mask.astype(ms.float32).sum() - if label == 0: - accuracies = per_class_accuracy - else: - accuracies = msnp.append(accuracies, per_class_accuracy) - # overall accuracy - accuracies = msnp.append(accuracies, accuracy_mask.astype(ms.float32).mean()) - return accuracies - - -def intersection_over_union(scores, labels): - r""" - Compute the per-class IoU and the mean IoU - - Parameters - ---------- - scores: ms.Tensor, dtype = float32, shape (B?, C, N) - raw scores for each class - labels: ms.Tensor, dtype = int64, shape (B?, N) - ground truth labels - - Returns - ------- - list of floats of length num_classes+1 (last item is mIoU) - """ - num_classes = scores.shape[-2] # we use -2 instead of 1 to enable arbitrary batch dimensions - - predictions = scores.argmax(axis=-2) - - ious = 0 - - for label in range(num_classes): - pred_mask = predictions == label - labels_mask = labels == label - iou = msnp.logical_and(pred_mask, labels_mask).astype(ms.float32).sum() / \ - msnp.logical_or(pred_mask, labels_mask).astype(ms.float32).sum() - if label == 0: - ious = iou - else: - ious = msnp.append(ious, iou) - ious = msnp.append(ious, msnp.nanmean(ious)) - return ious diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index 16c8e27f6..04ac38579 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -1,18 +1,4 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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. -# ============================================================================ +# This file was copied from project [QingyongHu][RandLA-Net] import sys import os.path import numpy as np @@ -86,12 +72,7 @@ class DataProcessing: idx_aug = idx[idx_dup] label_aug = labels[idx_dup] - return { - "xyz_aug": xyz_aug, - "color_aug": color_aug, - "idx_aug": idx_aug, - "label_aug": label_aug - } + return xyz_aug, color_aug, idx_aug, label_aug @staticmethod def shuffle_idx(x): diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp index 8ea91626e..33ff4310f 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.cpp @@ -1,4 +1,4 @@ - +/* This file was copied from project [HuguesTHOMAS][KPConv] */ #include "grid_subsampling.h" diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h index 7ebbba229..fc474db7b 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/grid_subsampling/grid_subsampling.h @@ -1,4 +1,4 @@ - +/* This file was copied from project [HuguesTHOMAS][KPConv] */ #include "../../cpp_utils/cloud/cloud.h" diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py index 776cf7328..9b0be31a8 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/setup.py @@ -1,3 +1,4 @@ +# This file was copied from project [QingyongHu][RandLA-Net] from distutils.core import setup, Extension import numpy.distutils.misc_util diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp index fbe159e2e..ceaf1d5ed 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_subsampling/wrapper.cpp @@ -1,3 +1,4 @@ +/* This file was copied from project [QingyongHu][RandLA-Net]*/ #include #include #include "grid_subsampling/grid_subsampling.h" diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp index 10265bed5..167710674 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.cpp @@ -1,3 +1,4 @@ +/* This file was copied from project [HuguesTHOMAS][KPConv] */ #include "cloud.h" diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h index d2bc13f3d..af82560f5 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/cloud/cloud.h @@ -1,3 +1,4 @@ +/* This file was copied from project [HuguesTHOMAS][KPConv] */ # pragma once #include diff --git a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp index dd208e3e8..89ac43de7 100644 --- a/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp +++ b/research/cv/WS3/third_party/cpp_wrappers/cpp_utils/nanoflann/nanoflann.hpp @@ -29,6 +29,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *************************************************************************/ +/* This file was copied from project [jlblancoc][nanoflann] */ /** \mainpage nanoflann C++ API documentation * nanoflann is a C++ header-only library for building KD-Trees, mostly diff --git a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h index b3abd5954..07f27fcd4 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h +++ b/research/cv/WS3/third_party/nearest_neighbors/KDTreeTableAdaptor.h @@ -25,7 +25,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *************************************************************************/ - +/* This file was copied from project [jlblancoc][nanoflann] */ #pragma once #include "nanoflann.hpp" diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp index c722a5d42..dcb786dbe 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn.cpp +++ b/research/cv/WS3/third_party/nearest_neighbors/knn.cpp @@ -1,3 +1,4 @@ +/* This file was copied from project [QingyongHu][RandLA-Net]*/ /* Generated by Cython 0.29.19 */ #define PY_SSIZE_T_CLEAN diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn.pyx b/research/cv/WS3/third_party/nearest_neighbors/knn.pyx index 73259860b..c7e81cbc6 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn.pyx +++ b/research/cv/WS3/third_party/nearest_neighbors/knn.pyx @@ -1,3 +1,4 @@ +# This file was copied from project [QingyongHu][RandLA-Net] # distutils: language = c++ # distutils: sources = knn.cxx diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx b/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx index c59aff875..dacca0fe7 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx +++ b/research/cv/WS3/third_party/nearest_neighbors/knn_.cxx @@ -1,4 +1,4 @@ - +/* This file was copied from project [QingyongHu][RandLA-Net]*/ #include "knn_.h" #include "nanoflann.hpp" using namespace nanoflann; diff --git a/research/cv/WS3/third_party/nearest_neighbors/knn_.h b/research/cv/WS3/third_party/nearest_neighbors/knn_.h index d0fced47f..758f6b2ef 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/knn_.h +++ b/research/cv/WS3/third_party/nearest_neighbors/knn_.h @@ -1,4 +1,4 @@ - +/* This file was copied from project [QingyongHu][RandLA-Net]*/ #include void cpp_knn(const float* points, const size_t npts, const size_t dim, diff --git a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp index 286ea74fe..451c263be 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp +++ b/research/cv/WS3/third_party/nearest_neighbors/nanoflann.hpp @@ -29,6 +29,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *************************************************************************/ +/* This file was copied from project [jlblancoc][nanoflann] */ /** \mainpage nanoflann C++ API documentation * nanoflann is a C++ header-only library for building KD-Trees, mostly @@ -43,6 +44,7 @@ * - Doxygen documentation */ +/* This file was copied from project [QingyongHu][RandLA-Net]*/ #ifndef NANOFLANN_HPP_ #define NANOFLANN_HPP_ diff --git a/research/cv/WS3/third_party/nearest_neighbors/setup.py b/research/cv/WS3/third_party/nearest_neighbors/setup.py index 78f8c4cbb..4952e2e2a 100644 --- a/research/cv/WS3/third_party/nearest_neighbors/setup.py +++ b/research/cv/WS3/third_party/nearest_neighbors/setup.py @@ -1,3 +1,4 @@ +# This file was copied from project [QingyongHu][RandLA-Net] from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext diff --git a/research/cv/WS3/train.py b/research/cv/WS3/train.py new file mode 100644 index 000000000..9eb5612ac --- /dev/null +++ b/research/cv/WS3/train.py @@ -0,0 +1,329 @@ +# -*-coding:utf-8-*- +# Copyright 2022 Huawei Technologies Co., Ltd +# +# 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 datetime +import argparse +import pickle +from pathlib import Path +import numpy as np +import ast + +from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed +from mindspore.nn import Adam +from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback +from mindspore.train.loss_scale_manager import FixedLossScaleManager +from mindspore import dtype as mstype + +from src.data.S3DIS_dataset import dataloader, ms_map +from src.model.base_model import get_param_groups +from src.utils.tools import DataProcessing as DP +from src.utils.tools import ConfigS3DIS as config +from src.utils.logger import get_logger + + +class UpdateLossEpoch(Callback): + def __init__(self, cur_num_training_ep0=30, logger=None): + super(UpdateLossEpoch, self).__init__() + self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) + self.logger = logger + + # msv1.7: epoch_begin + def epoch_begin(self, run_context): + cb_params = run_context.original_args() + + train_network_with_loss = cb_params.network + + cur_epoch_num = cb_params.cur_epoch_num + train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] + + self.logger.info( + f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " + f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " + f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") + + # msv1.8: on_train_epoch_begin + def on_train_epoch_begin(self, run_context): + self.epoch_begin(run_context) + + +class S3DISLossMonitor(Callback): + def __init__(self, per_print_times=1, logger=None): + super(S3DISLossMonitor, self).__init__() + self._per_print_times = per_print_times + self._last_print_time = 0 + self.logger = logger + + # msv1.7: step_end + def step_end(self, run_context): + """ + Print training loss at the end of step. + + Args: + run_context (RunContext): Include some information of the model. + """ + + cb_params = run_context.original_args() + loss = cb_params.net_outputs + + if isinstance(loss, (tuple, list)): + if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray): + loss = loss[0] + + if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray): + loss = float(np.mean(loss.asnumpy())) + + cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1 + + if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)): + raise ValueError(f"epoch: {cb_params.cur_epoch_num} " + f"step: {cur_step_in_epoch}. " + f"Invalid loss {loss}, terminating training.") + + if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): + while cb_params.cur_step_num <= self._last_print_time: + self._last_print_time -= \ + max(self._per_print_times, cb_params.batch_num if cb_params.dataset_sink_mode else 1) + + if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: + self._last_print_time = cb_params.cur_step_num + + msg = f"epoch: {cb_params.cur_epoch_num} " \ + f"step: {cur_step_in_epoch}, " \ + f"loss is {loss} " + self.logger.info(msg) + + # msv1.8: on_train_step_end + def on_train_step_end(self, run_context): + self.step_end(run_context) + + +def prepare_network(weights, cfg, args): + """Prepare Network""" + + if args.device_target == 'Ascend': + from src.model.model_s3dis_remove_bias import WS3 + from src.model.model_s3dis_remove_bias import WS3WithLoss + else: + from src.model.model_s3dis import WS3 + from src.model.model_s3dis import WS3WithLoss + + d_in = 6 # xyzrgb + network = WS3(d_in, cfg.num_classes) + if args.ss_pretrain: + print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") + param_dict = load_checkpoint(args.ss_pretrain) + whitelist = ["encoder"] + new_param_dict = dict() + for key, val in param_dict.items(): + if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: + new_key = ".".join(key.split(".")[1:]) + new_param_dict[new_key] = val + load_param_into_net(network, new_param_dict, strict_load=True) + + network = WS3WithLoss(network, + weights, + cfg.num_classes, + cfg.ignored_label_indexs, + cfg.c_epoch, + cfg.topk) + + if args.retrain_model: + print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") + param_dict = load_checkpoint(args.retrain_model) + load_param_into_net(network, param_dict, strict_load=True) + + return network + + +def train(cfg, args): + if cfg.graph_mode: + context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id) + else: + context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) + + logger = get_logger(args.outputs_dir, args.rank) + + logger.info("============ Args =================") + for arg in vars(args): + logger.info('%s: %s' % (arg, getattr(args, arg))) + logger.info("============ Cfg =================") + for c in vars(cfg): + logger.info('%s: %s' % (c, getattr(cfg, c))) + + train_loader, _, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) + ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in + getattr(dataset, 'ignored_labels')] + cfg.ignored_label_indexs = ignored_label_indexs + weights = DP.get_class_weights("S3DIS") + + network = prepare_network(weights, cfg, args) + decay_lr = nn.ExponentialDecayLR(cfg.learning_rate, cfg.lr_decays, decay_steps=cfg.train_steps, is_stair=True) + opt = Adam( + params=get_param_groups(network), + learning_rate=decay_lr, + loss_scale=cfg.loss_scale + ) + + log = {'cur_epoch': 1, 'cur_step': 1, 'best_epoch': 1, 'besr_miou': 0.0} + if not os.path.exists(args.outputs_dir + '/log.pkl'): + f = open(args.outputs_dir + '/log.pkl', 'wb') + pickle.dump(log, f) + f.close() + + if args.resume: + f = open(args.resume + '/log.pkl', 'rb') + log = pickle.load(f) + print(f"log of resume file {log}") + f.close() + param_dict = load_checkpoint(args.resume) + load_param_into_net(network, param_dict) + + # dataloader + train_loader = train_loader.batch(batch_size=cfg.batch_size, + per_batch_map=ms_map, + input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], + output_columns=["features", "features2", "labels", "input_inds", "cloud_inds", + "p0", "p1", "p2", "p3", "p4", + "n0", "n1", "n2", "n3", "n4", + "pl0", "pl1", "pl2", "pl3", "pl4", + "u0", "u1", "u2", "u3", "u4", + ], + drop_remainder=True) + + logger.info('==========begin training===============') + + loss_scale = cfg.loss_scale + loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None + print('loss_scale:', loss_scale) + + if cfg.float16: + print("network uses float16") + network.to_float(mstype.float16) + + if args.scale: + model = Model(network, + loss_scale_manager=loss_scale_manager, + loss_fn=None, + optimizer=opt) + else: + model = Model(network, + loss_fn=None, + optimizer=opt, + ) + + # callback for loss & time cost + loss_cb = S3DISLossMonitor(50, logger) + time_cb = TimeMonitor(data_size=cfg.train_steps) + cbs = [loss_cb, time_cb] + + # callback for saving ckpt + config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) + ckpt_cb = ModelCheckpoint(prefix='ws3', + directory=os.path.join(args.outputs_dir, 'ckpt'), + config=config_ckpt) + cbs += [ckpt_cb] + + update_loss_epoch_cb = UpdateLossEpoch(args.num_training_ep0, logger) + cbs += [update_loss_epoch_cb] + + logger.info(f"Outputs_dir:{args.outputs_dir}") + logger.info(f"Total number of epoch: {cfg.max_epoch}; " + f"Dataset capacity: {train_loader.get_dataset_size()}") + + model.train(cfg.max_epoch, + train_loader, + callbacks=cbs, + dataset_sink_mode=False) + logger.info('==========end training===============') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog='WS3', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + + parser.add_argument('--epochs', type=int, help='max epochs', default=100) + parser.add_argument('--batch_size', type=int, help='batch size', default=6) + parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') + parser.add_argument('--outputs_dir', type=str, help='path of output', default='outputs') + parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') + parser.add_argument('--resume', type=str, help='model to resume', default=None) + parser.add_argument('--scale', type=ast.literal_eval, help='scale or not', default=False) + parser.add_argument('--device_target', type=str, help='Ascend | GPU', default='Ascend', choices=['Ascend', 'GPU']) + parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) + parser.add_argument('--rank', type=int, help='rank', default=0) + parser.add_argument('--name', type=str, help='name of the experiment', default=None) + parser.add_argument('--ss_pretrain', type=str, help='name of the experiment', default=None) + parser.add_argument('--retrain_model', type=str, help='name of the experiment', default=None) + parser.add_argument('--float16', type=ast.literal_eval, default=False) + parser.add_argument('--train_steps', type=int, default=500) + parser.add_argument('--learning_rate', type=float, default=0.01) + parser.add_argument('--lr_decays', type=float, default=0.95) + parser.add_argument('--loss_scale', type=float, default=1.0) + parser.add_argument('--topk', type=int, default=500) + parser.add_argument('--num_training_ep0', type=int, default=30) + parser.add_argument('--labeled_percent', type=int, default=1) + parser.add_argument('--random_seed', type=int, default=888) + parser.add_argument('--graph_mode', type=ast.literal_eval, default=False) + + arguments = parser.parse_args() + + config.dataset_dir = arguments.dataset_dir + config.batch_size = arguments.batch_size + config.max_epoch = arguments.epochs + config.train_steps = arguments.train_steps + config.learning_rate = arguments.learning_rate + config.lr_decays = arguments.lr_decays + config.loss_scale = arguments.loss_scale + config.topk = arguments.topk + num_training_ep0 = arguments.num_training_ep0 + config.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} + config.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} + config.training_ep.update(config.training_ep0) + config.labeled_percent = arguments.labeled_percent + config.random_seed = arguments.random_seed + config.graph_mode = arguments.graph_mode + config.float16 = arguments.float16 + + if arguments.name is None: + if arguments.resume: + arguments.name = Path(arguments.resume).split('/')[-1] + else: + time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) + arguments.name = f'TSteps{config.train_steps}_MaxEpoch{config.max_epoch}_BatchS{config.batch_size}' \ + f'_TopK{config.topk}_NumTrainEp0{num_training_ep0}' \ + f'_LP{config.labeled_percent}_RS{config.random_seed}' + if config.graph_mode: + arguments.name += "_GraphM" + else: + arguments.name += "_PyNateiveM" + arguments.name += f'_{time_str}' + + np.random.seed(config.random_seed) + set_seed(config.random_seed) + + arguments.outputs_dir = os.path.join(arguments.outputs_dir, arguments.name) + + print(f"outputs_dir:{arguments.outputs_dir}") + if not os.path.exists(arguments.outputs_dir): + os.makedirs(arguments.outputs_dir) + + if arguments.resume: + arguments.outputs_dir = arguments.resume + + train(config, arguments) diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py index 74867cc3d..c146b0abb 100644 --- a/research/cv/WS3/train_ascend.py +++ b/research/cv/WS3/train_ascend.py @@ -265,7 +265,7 @@ if __name__ == "__main__": parser.add_argument('--scale', type=bool, help='scale or not', default=False) - parser.add_argument('--device_target', type=str, help='CPU | GPU | Ascend ', default='Ascend') + parser.add_argument('--device_target', type=str, help='Ascend ', default='Ascend', choices=['Ascend']) parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) -- Gitee From a98b6a57673995836e0032b4cd36ab2a4c4a9dba Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Mon, 28 Nov 2022 18:01:06 +0800 Subject: [PATCH 14/16] fix errors --- research/cv/WS3/README.md | 9 +- research/cv/WS3/eval_ascend.py | 220 ------------ research/cv/WS3/eval_gpu.py | 255 ------------- .../cv/WS3/src/utils/data_prepare_s3dis.py | 176 +++++++++ research/cv/WS3/src/utils/tools.py | 176 +++++++++ research/cv/WS3/train.py | 2 +- research/cv/WS3/train_ascend.py | 336 ------------------ research/cv/WS3/train_gpu.py | 310 ---------------- 8 files changed, 359 insertions(+), 1125 deletions(-) delete mode 100644 research/cv/WS3/eval_ascend.py delete mode 100644 research/cv/WS3/eval_gpu.py delete mode 100644 research/cv/WS3/train_ascend.py delete mode 100644 research/cv/WS3/train_gpu.py diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 29d9917d6..cde493dd3 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -22,10 +22,11 @@ - [Performance](#performance) - [Training Performance](#training-performance) - [Inference Performance](#inference-performance) - - [S3DIS](#s3DIS) + - [Performance Table](#performance-table) - [Reference](#reference) # [WS3](#contents) + Mindspore implementation for ***"Weakly Supervised Semantic Segmentation for Large-Scale Point Cloud"*** Please read the [original paper](https://ojs.aaai.org/index.php/AAAI/article/view/16455) @@ -297,8 +298,10 @@ Area_5_office_23 Acc:0.9049251547225916 | outputs | feature vector + probability | feature vector + probability | | Accuracy | See following tables | See following tables | -### [S3DIS](#contents) -- Setting: 1% labeled points +### [Performance Table](#contents) + +- Setting: 1% labeled points on S3DIS Dataset + | Metric | Value(Tensorflow)| Value(Mindspore, Ascend) | Value(Mindspore, GPU) | | :----: | :------------: | :-------------------: | :-------------------: | | mIoU | 61.8% | 61.5% | 60.3% | diff --git a/research/cv/WS3/eval_ascend.py b/research/cv/WS3/eval_ascend.py deleted file mode 100644 index 5b7070f8b..000000000 --- a/research/cv/WS3/eval_ascend.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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 argparse -from pathlib import Path -import numpy as np -from sklearn.metrics import confusion_matrix - -from mindspore import context, load_checkpoint, load_param_into_net, ops -from mindspore import dtype as mstype - -from src.data.S3DIS_dataset_test import dataloader, ms_map -from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as cfg -from src.model.model_s3dis_remove_bias import WS3 -from src.utils.logger import get_logger -from src.utils.helper_ply import write_ply - -from tqdm import tqdm - - -def run_eval(params): - context.set_context(mode=context.PYNATIVE_MODE, device_target=params.device_target, device_id=params.device_id) - logger = get_logger(params.outputs_dir, params.rank) - _, val_ds, dataset = dataloader(dataset_dir=params.dataset_dir, num_parallel_workers=8, shuffle=False) - input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] - output_columns = ["features", "labels", "input_inds", "cloud_inds", - "p0", "p1", "p2", "p3", "p4", - "n0", "n1", "n2", "n3", "n4", - "pl0", "pl1", "pl2", "pl3", "pl4", - "u0", "u1", "u2", "u3", "u4"] - val_loader = val_ds.batch(batch_size=params.batch_size, - per_batch_map=ms_map, - input_columns=input_columns, - output_columns=output_columns, - drop_remainder=True) - val_ds_size = val_loader.get_dataset_size() - val_loader = val_loader.create_dict_iterator() - - network = WS3(6, cfg.num_classes) - network.set_train(False) - - if params.float16: - print("network uses float16") - network.to_float(mstype.float16) - - if '.ckpt' in params.model_path: - ckpts = [params.model_path] - else: - ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) - ckpts = ckpt_path.glob('*.ckpt') - ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) - - best_miou, best_ckpt = 0.0, ckpts[0] - ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) - logger.info('==========begin test===============') - for ckpt_i, ckpt in enumerate(ckpts): - logger.info('load ckpt from:{}'.format(str(ckpt))) - param_dict = load_checkpoint(str(ckpt)) - load_param_into_net(network, param_dict) - - val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) - i = 0 - for label_val in dataset.label_values: - if label_val not in dataset.ignored_labels: - val_proportions[i] = np.sum([np.sum(labels == label_val) for labels in dataset.val_labels]) - i += 1 - test_probs = [np.zeros(shape=[l.shape[0], cfg.num_classes], dtype=np.float32) - for l in dataset.input_labels['validation']] - - # Smoothing parameter for votes - test_smooth = 0.95 - step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) - for step_i, data in enumerate(val_loader): - features = data['features'] - labels = data['labels'] # (B,N) - xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] - neigh_idx = [data['n0'], data['n1'], data['n2'], data['n3'], data['n4']] - sub_idx = [data['pl0'], data['pl1'], data['pl2'], data['pl3'], data['pl4']] - interp_idx = [data['u0'], data['u1'], data['u2'], data['u3'], data['u4']] - point_idx = data['input_inds'].asnumpy() - cloud_idx = data['cloud_inds'].asnumpy() - - logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] - logits = logits[..., :cfg.num_classes] - - prob_logits = ops.Softmax(-1)(logits) - - prob_logits = prob_logits.asnumpy() - for j in range(np.shape(prob_logits)[0]): - probs = prob_logits[j] - p_idx = point_idx[j, :] - c_i = cloud_idx[j][0] - test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs - - correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) - acc = correct / float(np.prod(np.shape(labels))) - msg = f'Step: {str(step_i)}; acc: {str(acc)}' - step_bar.set_postfix_str(msg, refresh=False) - step_bar.update() - - best_ckpt, best_miou = cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, - val_ds, val_proportions) - ckpt_bar.update() - - logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) - - -def cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, val_ds, val_proportions): - last_min, num_votes = -0.5, 100 - while last_min < num_votes: - new_min = np.min(val_ds.source.min_possibility['validation']) - logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") - if last_min + 1 < new_min: - last_min += 1 - logger.info('Confusion on sub clouds') - confusion_list = [] - - num_val = len(dataset.input_labels['validation']) - - for i_test in range(num_val): - probs = test_probs[i_test] - preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) - labels = dataset.input_labels['validation'][i_test] - confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] - - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) - # Rescale with the right number of point per class - C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) - # Compute IoUs - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info(s + '\n') - if int(np.ceil(new_min)) % 1 == 0: - # Project predictions - logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) - proj_probs_list = [] - for i_val in range(num_val): - # Reproject probs back to the evaluations points - proj_idx = dataset.val_proj[i_val] - probs = test_probs[i_val][proj_idx, :] - proj_probs_list += [probs] - # Show vote results - logger.info('Confusion on full clouds') - confusion_list = [] - for i_test in range(num_val): - # Get the predicted labels - preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - # Confusion - labels = dataset.val_labels[i_test] - acc = np.sum(preds == labels) / len(labels) - logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] - name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], - ['pred', 'label']) - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0) - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - if m_IoU > best_miou: - best_miou = m_IoU - best_ckpt = ckpt - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info('-' * len(s)) - logger.info(s) - logger.info('-' * len(s) + '\n') - logger.info('==========end test===============') - break - return best_ckpt, best_miou - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - prog='WS3', - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - parser.add_argument('--batch_size', type=int, help='val batch size', default=20) - parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - parser.add_argument('--model_path', type=str, help='model saved path', default='runs') - parser.add_argument('--device_target', type=str, help='Ascend ', default='Ascend', choices=['Ascend']) - parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - parser.add_argument('--rank', type=int, help='rank', default=0) - parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') - parser.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') - - parser.add_argument('--float16', type=bool, default=False) - - args = parser.parse_args() - - base_dir = os.path.dirname(os.path.abspath(__file__)) - args.model_path = os.path.join(base_dir, args.model_path) - - if not os.path.exists(args.outputs_dir): - os.makedirs(args.outputs_dir) - - val_pred_path = os.path.join(args.outputs_dir, 'val_preds') - if not os.path.exists(val_pred_path): - os.makedirs(val_pred_path) - - run_eval(args) diff --git a/research/cv/WS3/eval_gpu.py b/research/cv/WS3/eval_gpu.py deleted file mode 100644 index ad3dbbdd3..000000000 --- a/research/cv/WS3/eval_gpu.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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 argparse -import datetime -from pathlib import Path -import numpy as np -from sklearn.metrics import confusion_matrix - -from tqdm import tqdm -from mindspore import context, load_checkpoint, load_param_into_net, ops - -from src.data.S3DIS_dataset_test import dataloader, ms_map -from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as cfg -from src.utils.logger import get_logger -from src.utils.helper_ply import write_ply -from src.model.model_s3dis import WS3 - - -def run_eval(params): - context.set_context(mode=context.PYNATIVE_MODE, device_target=params.device_target, device_id=params.device_id) - - logger = get_logger(params.outputs_dir, params.rank) - - # data loader - _, val_ds, dataset = dataloader( - dataset_dir=params.dataset_dir, - num_parallel_workers=8, - shuffle=False - ) - input_columns = ["xyz", "colors", "labels", "q_idx", "c_idx"] - output_columns = ["features", "labels", "input_inds", "cloud_inds", - "p0", "p1", "p2", "p3", "p4", - "n0", "n1", "n2", "n3", "n4", - "pl0", "pl1", "pl2", "pl3", "pl4", - "u0", "u1", "u2", "u3", "u4"] - val_loader = val_ds.batch(batch_size=params.batch_size, - per_batch_map=ms_map, - input_columns=input_columns, - output_columns=output_columns, - drop_remainder=True) - val_ds_size = val_loader.get_dataset_size() - val_loader = val_loader.create_dict_iterator() - - # load ckpt, iterate ckpts to find the best - d_in = 6 - network = WS3(d_in, cfg.num_classes) - - if '.ckpt' in params.model_path: - ckpts = [params.model_path] - else: - ckpt_path = Path(os.path.join(params.model_path, 'ckpt')) - ckpts = ckpt_path.glob('*.ckpt') - ckpts = sorted(ckpts, key=lambda ckpt: ckpt.stem.split("_")[0].split("-")[1]) - - best_miou = 0.0 - best_ckpt = ckpts[0] - ckpt_bar = tqdm(total=len(ckpts), leave=False, desc='Step', dynamic_ncols=True) - logger.info('==========begin test===============') - for ckpt_i, ckpt in enumerate(ckpts): - # load current ckpt - logger.info('load ckpt from:{}'.format(str(ckpt))) - param_dict = load_checkpoint(str(ckpt)) - load_param_into_net(network, param_dict) - - # Number of points per class in validation set - val_proportions = np.zeros(cfg.num_classes, dtype=np.float32) - i = 0 - for label_val in dataset.label_values: - if label_val not in dataset.ignored_labels: - val_proportions[i] = np.sum([np.sum(labels == label_val) for labels in dataset.val_labels]) - i += 1 - test_probs = [np.zeros(shape=[l.shape[0], cfg.num_classes], dtype=np.float32) - for l in dataset.input_labels['validation']] - - # Smoothing parameter for votes - test_smooth = 0.95 - - step_bar = tqdm(total=val_ds_size, leave=False, desc='Step', dynamic_ncols=True) - for step_i, data in enumerate(val_loader): - - features = data['features'] - labels = data['labels'] # (B,N) - xyz = [data['p0'], data['p1'], data['p2'], data['p3'], data['p4']] - neigh_idx = [data['n0'], data['n1'], data['n2'], data['n3'], data['n4']] - sub_idx = [data['pl0'], data['pl1'], data['pl2'], data['pl3'], data['pl4']] - interp_idx = [data['u0'], data['u1'], data['u2'], data['u3'], data['u4']] - point_idx = data['input_inds'].asnumpy() - cloud_idx = data['cloud_inds'].asnumpy() - - logits = network(xyz, features, neigh_idx, sub_idx, interp_idx) # [b, num_classes, N] - logits = logits[..., :cfg.num_classes] - prob_logits = ops.Softmax(-1)(logits).asnumpy() # (B,N,13) - - for j in range(np.shape(prob_logits)[0]): - probs = prob_logits[j, :, :] - p_idx = point_idx[j, :] - c_i = cloud_idx[j][0] - test_probs[c_i][p_idx] = test_smooth * test_probs[c_i][p_idx] + (1 - test_smooth) * probs - - correct = np.sum(np.argmax(prob_logits, axis=-1) == labels.asnumpy()) - acc = correct / float(np.prod(np.shape(labels))) - msg = f'Step: {str(step_i)}; acc: {str(acc)}' - step_bar.set_postfix_str(msg, refresh=False) - step_bar.update() - - best_ckpt, best_miou = cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, - val_ds, val_proportions) - ckpt_bar.update() - - logger.info('All ckpt test end. Best MIOU: {:.1f} . Best ckpt: {}'.format(100 * best_miou, str(best_ckpt))) - - -def cal_metric(best_ckpt, best_miou, ckpt, ckpt_i, dataset, logger, params, test_probs, val_ds, val_proportions): - last_min = -0.5 - num_votes = 100 - while last_min < num_votes: - new_min = np.min(val_ds.source.min_possibility['validation']) - logger.info(f"Epoch {ckpt_i}, end. Min possibility = {new_min:.1f}") - # if True: - if last_min + 1 < new_min: - # Update last_min - last_min += 1 - - # Show vote results (On subcloud so it is not the good values here) - logger.info('Confusion on sub clouds') - confusion_list = [] - - num_val = len(dataset.input_labels['validation']) - - for i_test in range(num_val): - probs = test_probs[i_test] - preds = dataset.label_values[np.argmax(probs, axis=1)].astype(np.int32) - labels = dataset.input_labels['validation'][i_test] - - # Confs - confusion_list += [confusion_matrix(labels, preds, labels=dataset.label_values)] - - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0).astype(np.float32) - - # Rescale with the right number of point per class - C *= np.expand_dims(val_proportions / (np.sum(C, axis=1) + 1e-6), 1) - - # Compute IoUs - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info(s + '\n') - - if int(np.ceil(new_min)) % 1 == 0: - - # Project predictions - logger.info('Reproject Vote #{:d}'.format(int(np.floor(new_min)))) - proj_probs_list = [] - - for i_val in range(num_val): - # Reproject probs back to the evaluations points - proj_idx = dataset.val_proj[i_val] - probs = test_probs[i_val][proj_idx, :] - proj_probs_list += [probs] - - # Show vote results - logger.info('Confusion on full clouds') - confusion_list = [] - for i_test in range(num_val): - # Get the predicted labels - preds = dataset.label_values[np.argmax(proj_probs_list[i_test], axis=1)].astype(np.uint8) - - # Confusion - labels = dataset.val_labels[i_test] - acc = np.sum(preds == labels) / len(labels) - logger.info(dataset.input_names['validation'][i_test] + ' Acc:' + str(acc)) - - confusion_list += [confusion_matrix(y_true=labels, y_pred=preds, labels=dataset.label_values)] - name = dataset.input_names['validation'][i_test] + '.ply' - write_ply(os.path.join(params.outputs_dir, 'val_preds', name), [preds, labels], - ['pred', 'label']) - - # Regroup confusions - C = np.sum(np.stack(confusion_list), axis=0) - - IoUs = DP.IoU_from_confusions(C) - m_IoU = np.mean(IoUs) - if m_IoU > best_miou: - best_miou = m_IoU - best_ckpt = ckpt - s = '{:5.2f} | '.format(100 * m_IoU) - for IoU in IoUs: - s += '{:5.2f} '.format(100 * IoU) - logger.info('-' * len(s)) - logger.info(s) - logger.info('-' * len(s) + '\n') - logger.info('==========end test===============') - break - return best_ckpt, best_miou - - -if __name__ == "__main__": - # """Parse program arguments""" - parser = argparse.ArgumentParser( - prog='WS3', - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - - parser.add_argument('--batch_size', type=int, help='val batch size', default=20) - - parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - - parser.add_argument('--model_path', type=str, help='model saved path', required=True, default='outputs') - - parser.add_argument('--device_target', type=str, help='GPU', default='GPU', choices=['GPU']) - - parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - - parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') - - parser.add_argument('--rank', type=int, help='rank', default=0) - - args = parser.parse_args() - - base_dir = os.path.dirname(os.path.abspath(__file__)) - args.model_path = os.path.join(base_dir, args.model_path) - - # test output dir - t = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) - if os.path.isdir(args.model_path): - args.outputs_dir = os.path.join(args.model_path, 'test_' + t) - else: - args.outputs_dir = os.path.join(f"test_{t}") - if not os.path.exists(args.outputs_dir): - os.makedirs(args.outputs_dir) - - # val output path - val_pred_path = os.path.join(args.outputs_dir, 'val_preds') - if not os.path.exists(val_pred_path): - os.makedirs(val_pred_path) - - run_eval(args) diff --git a/research/cv/WS3/src/utils/data_prepare_s3dis.py b/research/cv/WS3/src/utils/data_prepare_s3dis.py index da0e6112f..3c8172fa2 100644 --- a/research/cv/WS3/src/utils/data_prepare_s3dis.py +++ b/research/cv/WS3/src/utils/data_prepare_s3dis.py @@ -1,3 +1,179 @@ +# CC BY-NC-SA 4.0 license +# # Attribution-NonCommercial-ShareAlike 4.0 International +# +# Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. +# +# ### Using Creative Commons Public Licenses +# +# Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. +# +# * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). +# +# * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). +# +# ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License +# +# By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. +# +# ### Section 1 – Definitions. +# +# a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. +# +# b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. +# +# c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License. +# +# d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. +# +# e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. +# +# f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. +# +# g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. +# +# h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. +# +# i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. +# +# j. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. +# +# k. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. +# +# l. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. +# +# m. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. +# +# n. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. +# +# ### Section 2 – Scope. +# +# a. ___License grant.___ +# +# 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: +# +# A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and +# +# B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. +# +# 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. +# +# 3. __Term.__ The term of this Public License is specified in Section 6(a). +# +# 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. +# +# 5. __Downstream recipients.__ +# +# A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. +# +# B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. +# +# C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. +# +# 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). +# +# b. ___Other rights.___ +# +# 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. +# +# 2. Patent and trademark rights are not licensed under this Public License. +# +# 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. +# +# ### Section 3 – License Conditions. +# +# Your exercise of the Licensed Rights is expressly made subject to the following conditions. +# +# a. ___Attribution.___ +# +# 1. If You Share the Licensed Material (including in modified form), You must: +# +# A. retain the following if it is supplied by the Licensor with the Licensed Material: +# +# i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); +# +# ii. a copyright notice; +# +# iii. a notice that refers to this Public License; +# +# iv. a notice that refers to the disclaimer of warranties; +# +# v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; +# +# B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and +# +# C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. +# +# 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. +# +# 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. +# +# b. ___ShareAlike.___ +# +# In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. +# +# 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. +# +# 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. +# +# 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. +# +# ### Section 4 – Sui Generis Database Rights. +# +# Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: +# +# a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; +# +# b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and +# +# c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +# +# For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. +# +# ### Section 5 – Disclaimer of Warranties and Limitation of Liability. +# +# a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ +# +# b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ +# +# c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. +# +# ### Section 6 – Term and Termination. +# +# a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. +# +# b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: +# +# 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or +# +# 2. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or +# +# For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. +# +# c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. +# +# d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. +# +# ### Section 7 – Other Terms and Conditions. +# +# a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. +# +# b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. +# +# ### Section 8 – Interpretation. +# +# a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. +# +# b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. +# +# c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. +# +# d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. +# +# ``` +# Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. +# +# Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org/). +# ``` # This file was copied from project [QingyongHu][RandLA-Net] import sys import os diff --git a/research/cv/WS3/src/utils/tools.py b/research/cv/WS3/src/utils/tools.py index 04ac38579..9acfa247f 100644 --- a/research/cv/WS3/src/utils/tools.py +++ b/research/cv/WS3/src/utils/tools.py @@ -1,3 +1,179 @@ +# CC BY-NC-SA 4.0 license +# # Attribution-NonCommercial-ShareAlike 4.0 International +# +# Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. +# +# ### Using Creative Commons Public Licenses +# +# Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. +# +# * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). +# +# * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). +# +# ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License +# +# By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. +# +# ### Section 1 – Definitions. +# +# a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. +# +# b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. +# +# c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License. +# +# d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. +# +# e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. +# +# f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. +# +# g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. +# +# h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. +# +# i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. +# +# j. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. +# +# k. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. +# +# l. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. +# +# m. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. +# +# n. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. +# +# ### Section 2 – Scope. +# +# a. ___License grant.___ +# +# 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: +# +# A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and +# +# B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. +# +# 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. +# +# 3. __Term.__ The term of this Public License is specified in Section 6(a). +# +# 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. +# +# 5. __Downstream recipients.__ +# +# A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. +# +# B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. +# +# C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. +# +# 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). +# +# b. ___Other rights.___ +# +# 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. +# +# 2. Patent and trademark rights are not licensed under this Public License. +# +# 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. +# +# ### Section 3 – License Conditions. +# +# Your exercise of the Licensed Rights is expressly made subject to the following conditions. +# +# a. ___Attribution.___ +# +# 1. If You Share the Licensed Material (including in modified form), You must: +# +# A. retain the following if it is supplied by the Licensor with the Licensed Material: +# +# i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); +# +# ii. a copyright notice; +# +# iii. a notice that refers to this Public License; +# +# iv. a notice that refers to the disclaimer of warranties; +# +# v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; +# +# B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and +# +# C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. +# +# 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. +# +# 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. +# +# b. ___ShareAlike.___ +# +# In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. +# +# 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. +# +# 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. +# +# 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. +# +# ### Section 4 – Sui Generis Database Rights. +# +# Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: +# +# a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; +# +# b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and +# +# c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +# +# For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. +# +# ### Section 5 – Disclaimer of Warranties and Limitation of Liability. +# +# a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ +# +# b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ +# +# c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. +# +# ### Section 6 – Term and Termination. +# +# a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. +# +# b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: +# +# 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or +# +# 2. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or +# +# For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. +# +# c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. +# +# d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. +# +# ### Section 7 – Other Terms and Conditions. +# +# a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. +# +# b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. +# +# ### Section 8 – Interpretation. +# +# a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. +# +# b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. +# +# c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. +# +# d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. +# +# ``` +# Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. +# +# Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org/). +# ``` # This file was copied from project [QingyongHu][RandLA-Net] import sys import os.path diff --git a/research/cv/WS3/train.py b/research/cv/WS3/train.py index 9eb5612ac..86870a731 100644 --- a/research/cv/WS3/train.py +++ b/research/cv/WS3/train.py @@ -17,9 +17,9 @@ import os import datetime import argparse import pickle +import ast from pathlib import Path import numpy as np -import ast from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed from mindspore.nn import Adam diff --git a/research/cv/WS3/train_ascend.py b/research/cv/WS3/train_ascend.py deleted file mode 100644 index c146b0abb..000000000 --- a/research/cv/WS3/train_ascend.py +++ /dev/null @@ -1,336 +0,0 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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 datetime -import argparse -import pickle -from pathlib import Path -import numpy as np - -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed -from mindspore.nn import Adam -from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback -from mindspore.train.loss_scale_manager import FixedLossScaleManager -from mindspore import dtype as mstype - -from src.data.S3DIS_dataset import dataloader, ms_map -from src.model.base_model import get_param_groups -from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as config -from src.utils.logger import get_logger -from src.model.model_s3dis_remove_bias import WS3 -from src.model.model_s3dis_remove_bias import WS3WithLoss - - -class UpdateLossEpoch(Callback): - def __init__(self, cur_num_training_ep0=30, logger=None): - super(UpdateLossEpoch, self).__init__() - self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) - self.logger = logger - - # msv1.7: epoch_begin - def epoch_begin(self, run_context): - cb_params = run_context.original_args() - - train_network_with_loss = cb_params.network - - cur_epoch_num = cb_params.cur_epoch_num - train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] - - self.logger.info( - f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " - f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " - f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") - - # msv1.8: on_train_epoch_begin - def on_train_epoch_begin(self, run_context): - self.epoch_begin(run_context) - - -class S3DISLossMonitor(Callback): - def __init__(self, per_print_times=1, logger=None): - super(S3DISLossMonitor, self).__init__() - self._per_print_times = per_print_times - self._last_print_time = 0 - self.logger = logger - - # msv1.7: step_end - def step_end(self, run_context): - """ - Print training loss at the end of step. - - Args: - run_context (RunContext): Include some information of the model. - """ - - cb_params = run_context.original_args() - loss = cb_params.net_outputs - - if isinstance(loss, (tuple, list)): - if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray): - loss = loss[0] - - if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray): - loss = float(np.mean(loss.asnumpy())) - - cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1 - - if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)): - raise ValueError(f"epoch: {cb_params.cur_epoch_num} " - f"step: {cur_step_in_epoch}. " - f"Invalid loss {loss}, terminating training.") - - if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): - while cb_params.cur_step_num <= self._last_print_time: - self._last_print_time -= \ - max(self._per_print_times, cb_params.batch_num if cb_params.dataset_sink_mode else 1) - - if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: - self._last_print_time = cb_params.cur_step_num - - msg = f"epoch: {cb_params.cur_epoch_num} " \ - f"step: {cur_step_in_epoch}, " \ - f"loss is {loss} " - self.logger.info(msg) - - # msv1.8: on_train_step_end - def on_train_step_end(self, run_context): - self.step_end(run_context) - - -def prepare_network(weights, cfg, args): - """Prepare Network""" - - d_in = 6 # xyzrgb - network = WS3(d_in, cfg.num_classes) - if args.ss_pretrain: - print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") - param_dict = load_checkpoint(args.ss_pretrain) - whitelist = ["encoder"] - new_param_dict = dict() - for key, val in param_dict.items(): - if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: - new_key = ".".join(key.split(".")[1:]) - new_param_dict[new_key] = val - load_param_into_net(network, new_param_dict, strict_load=True) - - network = WS3WithLoss(network, - weights, - cfg.num_classes, - cfg.ignored_label_indexs, - cfg.c_epoch, - cfg.topk) - - if args.retrain_model: - print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") - param_dict = load_checkpoint(args.retrain_model) - load_param_into_net(network, param_dict, strict_load=True) - - return network - - -def train(cfg, args): - if cfg.graph_mode: - context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id) - else: - context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) - - logger = get_logger(args.outputs_dir, args.rank) - - logger.info("============ Args =================") - for arg in vars(args): - logger.info('%s: %s' % (arg, getattr(args, arg))) - logger.info("============ Cfg =================") - for c in vars(cfg): - logger.info('%s: %s' % (c, getattr(cfg, c))) - - train_loader, _, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) - ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in - getattr(dataset, 'ignored_labels')] - cfg.ignored_label_indexs = ignored_label_indexs - weights = DP.get_class_weights("S3DIS") - - network = prepare_network(weights, cfg, args) - decay_lr = nn.ExponentialDecayLR(cfg.learning_rate, cfg.lr_decays, decay_steps=cfg.train_steps, is_stair=True) - opt = Adam( - params=get_param_groups(network), - learning_rate=decay_lr, - loss_scale=cfg.loss_scale - ) - - log = {'cur_epoch': 1, 'cur_step': 1, 'best_epoch': 1, 'besr_miou': 0.0} - if not os.path.exists(args.outputs_dir + '/log.pkl'): - f = open(args.outputs_dir + '/log.pkl', 'wb') - pickle.dump(log, f) - f.close() - - if args.resume: - f = open(args.resume + '/log.pkl', 'rb') - log = pickle.load(f) - print(f"log of resume file {log}") - f.close() - param_dict = load_checkpoint(args.resume) - load_param_into_net(network, param_dict) - - # dataloader - train_loader = train_loader.batch(batch_size=cfg.batch_size, - per_batch_map=ms_map, - input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], - output_columns=["features", "features2", "labels", "input_inds", "cloud_inds", - "p0", "p1", "p2", "p3", "p4", - "n0", "n1", "n2", "n3", "n4", - "pl0", "pl1", "pl2", "pl3", "pl4", - "u0", "u1", "u2", "u3", "u4", - ], - drop_remainder=True) - - logger.info('==========begin training===============') - - loss_scale = cfg.loss_scale - loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None - print('loss_scale:', loss_scale) - - if cfg.float16: - print("network uses float16") - network.to_float(mstype.float16) - - if args.scale: - model = Model(network, - loss_scale_manager=loss_scale_manager, - loss_fn=None, - optimizer=opt) - else: - model = Model(network, - loss_fn=None, - optimizer=opt, - ) - - # callback for loss & time cost - loss_cb = S3DISLossMonitor(50, logger) - time_cb = TimeMonitor(data_size=cfg.train_steps) - cbs = [loss_cb, time_cb] - - # callback for saving ckpt - config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) - ckpt_cb = ModelCheckpoint(prefix='ws3', - directory=os.path.join(args.outputs_dir, 'ckpt'), - config=config_ckpt) - cbs += [ckpt_cb] - - update_loss_epoch_cb = UpdateLossEpoch(args.num_training_ep0, logger) - cbs += [update_loss_epoch_cb] - - logger.info(f"Outputs_dir:{args.outputs_dir}") - logger.info(f"Total number of epoch: {cfg.max_epoch}; " - f"Dataset capacity: {train_loader.get_dataset_size()}") - - model.train(cfg.max_epoch, - train_loader, - callbacks=cbs, - dataset_sink_mode=False) - logger.info('==========end training===============') - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - prog='WS3', - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - - parser.add_argument('--epochs', type=int, help='max epochs', default=100) - - parser.add_argument('--batch_size', type=int, help='batch size', default=6) - - parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') - - parser.add_argument('--outputs_dir', type=str, help='path of output', default='outputs') - - parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - - parser.add_argument('--resume', type=str, help='model to resume', default=None) - - parser.add_argument('--scale', type=bool, help='scale or not', default=False) - - parser.add_argument('--device_target', type=str, help='Ascend ', default='Ascend', choices=['Ascend']) - - parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - - parser.add_argument('--rank', type=int, help='rank', default=0) - - parser.add_argument('--name', type=str, help='name of the experiment', - default=None) - parser.add_argument('--ss_pretrain', type=str, help='name of the experiment', - default=None) - parser.add_argument('--retrain_model', type=str, help='name of the experiment', - default=None) - parser.add_argument('--float16', type=bool, default=False) - parser.add_argument('--train_steps', type=int, default=500) - parser.add_argument('--learning_rate', type=float, default=0.01) - parser.add_argument('--lr_decays', type=float, default=0.95) - parser.add_argument('--loss_scale', type=float, default=1.0) - parser.add_argument('--topk', type=int, default=500) - parser.add_argument('--num_training_ep0', type=int, default=30) - parser.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] - parser.add_argument('--random_seed', type=int, default=888) - parser.add_argument('--graph_mode', action='store_true', default=False) - - arguments = parser.parse_args() - - config.dataset_dir = arguments.dataset_dir - config.batch_size = arguments.batch_size - config.max_epoch = arguments.epochs - config.train_steps = arguments.train_steps - config.learning_rate = arguments.learning_rate - config.lr_decays = arguments.lr_decays - config.loss_scale = arguments.loss_scale - config.topk = arguments.topk - num_training_ep0 = arguments.num_training_ep0 - config.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} - config.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - config.training_ep.update(config.training_ep0) - config.labeled_percent = arguments.labeled_percent - config.random_seed = arguments.random_seed - config.graph_mode = arguments.graph_mode - config.float16 = arguments.float16 - - if arguments.name is None: - if arguments.resume: - arguments.name = Path(arguments.resume).split('/')[-1] - else: - time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) - arguments.name = f'TSteps{config.train_steps}_MaxEpoch{config.max_epoch}_BatchS{config.batch_size}' \ - f'_TopK{config.topk}_NumTrainEp0{num_training_ep0}' \ - f'_LP{config.labeled_percent}_RS{config.random_seed}' - if config.graph_mode: - arguments.name += "_GraphM" - else: - arguments.name += "_PyNateiveM" - arguments.name += f'_{time_str}' - - np.random.seed(config.random_seed) - set_seed(config.random_seed) - - arguments.outputs_dir = os.path.join(arguments.outputs_dir, arguments.name) - - print(f"outputs_dir:{arguments.outputs_dir}") - if not os.path.exists(arguments.outputs_dir): - os.makedirs(arguments.outputs_dir) - - if arguments.resume: - arguments.outputs_dir = arguments.resume - - train(config, arguments) diff --git a/research/cv/WS3/train_gpu.py b/research/cv/WS3/train_gpu.py deleted file mode 100644 index 12f870853..000000000 --- a/research/cv/WS3/train_gpu.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*-coding:utf-8-*- -# Copyright 2022 Huawei Technologies Co., Ltd -# -# 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 datetime -import argparse -import pickle -from pathlib import Path -import numpy as np - -from mindspore import Model, Tensor, context, load_checkpoint, load_param_into_net, nn, set_seed -from mindspore.nn import Adam -from mindspore.train.callback import TimeMonitor, ModelCheckpoint, CheckpointConfig, Callback -from mindspore.train.loss_scale_manager import FixedLossScaleManager - -from src.data.S3DIS_dataset import dataloader, ms_map -from src.model.base_model import get_param_groups -from src.model.model_s3dis import WS3 -from src.model.model_s3dis import WS3WithLoss -from src.utils.tools import DataProcessing as DP -from src.utils.tools import ConfigS3DIS as config -from src.utils.logger import get_logger - - -class UpdateLossEpoch(Callback): - def __init__(self, cur_num_training_ep0=30, logger=None): - super(UpdateLossEpoch, self).__init__() - self.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - self.training_ep.update({i: 0 for i in range(0, cur_num_training_ep0)}) - self.logger = logger - - def on_train_epoch_begin(self, run_context): - cb_params = run_context.original_args() - train_network_with_loss = cb_params.network - cur_epoch_num = cb_params.cur_epoch_num - train_network_with_loss.c_epoch_k += self.training_ep[cur_epoch_num - 1] - self.logger.info( - f"UpdateLossEpoch ==> cur_epoch_num:{cur_epoch_num}, " - f"cur_training_ep:{self.training_ep[cur_epoch_num]}, " - f"loss_fn.c_epoch_k:{train_network_with_loss.c_epoch_k}") - - -class S3DISLossMonitor(Callback): - def __init__(self, per_print_times=1, logger=None): - super(S3DISLossMonitor, self).__init__() - self._per_print_times = per_print_times - self._last_print_time = 0 - self.logger = logger - - def on_train_step_end(self, run_context): - """ - Print training loss at the end of step. - - Args: - run_context (RunContext): Include some information of the model. - """ - cb_params = run_context.original_args() - loss = cb_params.net_outputs - - if isinstance(loss, (tuple, list)): - if isinstance(loss[0], Tensor) and isinstance(loss[0].asnumpy(), np.ndarray): - loss = loss[0] - - if isinstance(loss, Tensor) and isinstance(loss.asnumpy(), np.ndarray): - loss = float(np.mean(loss.asnumpy())) - - cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num + 1 - - if isinstance(loss, float) and (np.isnan(loss) or np.isinf(loss)): - raise ValueError("epoch: {} step: {}. Invalid loss {}, terminating training." - "CE Loss {}; SP Loss {}".format(cb_params.cur_epoch_num, cur_step_in_epoch, loss, - cb_params.network.CE_LOSS.asnumpy(), - cb_params.network.SP_LOSS.asnumpy())) - - if self._per_print_times != 0 and (cb_params.cur_step_num <= self._last_print_time): - while cb_params.cur_step_num <= self._last_print_time: - self._last_print_time -= \ - max(self._per_print_times, cb_params.batch_num if cb_params.dataset_sink_mode else 1) - - if self._per_print_times != 0 and (cb_params.cur_step_num - self._last_print_time) >= self._per_print_times: - self._last_print_time = cb_params.cur_step_num - self.train_network_with_loss = cb_params.network - - msg = f"epoch: {cb_params.cur_epoch_num} step: {cur_step_in_epoch}, loss is {loss}" - - self.logger.info(msg) - - -def prepare_network(weights, cfg, args): - """Prepare Network""" - - d_in = 6 # xyzrgb - network = WS3(d_in, cfg.num_classes) - if args.ss_pretrain: - print(f"Load scannet pretrained ckpt from {args.ss_pretrain}") - param_dict = load_checkpoint(args.ss_pretrain) - whitelist = ["encoder"] - new_param_dict = dict() - for key, val in param_dict.items(): - if key.split(".")[0] == 'network' and key.split(".")[1] in whitelist: - new_key = ".".join(key.split(".")[1:]) - new_param_dict[new_key] = val - load_param_into_net(network, new_param_dict, strict_load=True) - - network = WS3WithLoss(network, weights, cfg.num_classes, cfg.ignored_label_indexs, cfg.c_epoch, - cfg.loss3_type, cfg.topk) - - if args.retrain_model: - print(f"Load S3DIS pretrained ckpt from {args.retrain_model}") - param_dict = load_checkpoint(args.retrain_model) - load_param_into_net(network, param_dict, strict_load=True) - - return network - - -def train(cfg, args): - if cfg.graph_mode: - context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target, device_id=args.device_id) - else: - context.set_context(mode=context.PYNATIVE_MODE, device_target=args.device_target, device_id=args.device_id) - - logger = get_logger(args.outputs_dir, args.rank) - - logger.info("============ Args =================") - for arg in vars(args): - logger.info('%s: %s' % (arg, getattr(args, arg))) - logger.info("============ Cfg =================") - for c in vars(cfg): - logger.info('%s: %s' % (c, getattr(cfg, c))) - - train_loader, _, dataset = dataloader(cfg, shuffle=False, num_parallel_workers=8) - ignored_label_indexs = [getattr(dataset, 'label_to_idx')[ign_label] for ign_label in - getattr(dataset, 'ignored_labels')] - cfg.ignored_label_indexs = ignored_label_indexs - weights = DP.get_class_weights("S3DIS") - network = prepare_network(weights, cfg, args) - - decay_lr = nn.ExponentialDecayLR(cfg.learning_rate, cfg.lr_decays, decay_steps=cfg.train_steps, is_stair=True) - opt = Adam( - params=get_param_groups(network), - learning_rate=decay_lr, - loss_scale=cfg.loss_scale - ) - - log = {'cur_epoch': 1, 'cur_step': 1, 'best_epoch': 1, 'besr_miou': 0.0} - if not os.path.exists(args.outputs_dir + '/log.pkl'): - f = open(args.outputs_dir + '/log.pkl', 'wb') - pickle.dump(log, f) - f.close() - - # resume checkpoint, cur_epoch, best_epoch, cur_step, best_step - if args.resume: - network_param = load_checkpoint(args.resume) - load_param_into_net(network, network_param) - - # data loader - train_loader = train_loader.batch(batch_size=cfg.batch_size, - per_batch_map=ms_map, - input_columns=["xyz", "colors", "labels", "q_idx", "c_idx"], - output_columns=["features", "features2", "labels", "input_inds", "cloud_inds", - "p0", "p1", "p2", "p3", "p4", - "n0", "n1", "n2", "n3", "n4", - "pl0", "pl1", "pl2", "pl3", "pl4", - "u0", "u1", "u2", "u3", "u4", - # 'test_dict' - ], - drop_remainder=True) - - logger.info('==========begin training===============') - - # loss scale manager - loss_scale = cfg.loss_scale - loss_scale_manager = FixedLossScaleManager(loss_scale) if args.scale or loss_scale != 1.0 else None - print('loss_scale:', loss_scale) - - if args.scale: - model = Model(network, - loss_scale_manager=loss_scale_manager, - loss_fn=None, - optimizer=opt) - else: - model = Model(network, - loss_fn=None, - optimizer=opt) - - # callback for loss & time cost - loss_cb = S3DISLossMonitor(20, logger) - time_cb = TimeMonitor(data_size=cfg.train_steps) - cbs = [loss_cb, time_cb] - - # callback for saving ckpt - config_ckpt = CheckpointConfig(save_checkpoint_steps=cfg.train_steps, keep_checkpoint_max=100) - ckpt_cb = ModelCheckpoint(prefix='randla', directory=os.path.join(args.outputs_dir, 'ckpt'), - config=config_ckpt) - cbs += [ckpt_cb] - - update_loss_epoch_cb = UpdateLossEpoch(args.num_training_ep0, logger) - cbs += [update_loss_epoch_cb] - - logger.info(f"Outputs_dir:{args.outputs_dir}") - logger.info(f"Total number of epoch: {cfg.max_epoch}; " - f"Dataset capacity: {train_loader.get_dataset_size()}") - - model.train(cfg.max_epoch, - train_loader, - callbacks=cbs, - dataset_sink_mode=False) - - logger.info('==========end training===============') - - -if __name__ == "__main__": - # """Parse program arguments""" - parser = argparse.ArgumentParser() - - parser.add_argument('--epochs', type=int, help='max epochs', default=100) - - parser.add_argument('--batch_size', type=int, help='batch size', default=6) - - parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='../dataset/S3DIS') - - parser.add_argument('--outputs_dir', type=str, help='path of output', default='./outputs') - - parser.add_argument('--val_area', type=str, help='area to validate', default='Area_5') - - parser.add_argument('--resume', type=str, help='model to resume', default=None) - - parser.add_argument('--scale', type=bool, help='scale or not', default=False) - - parser.add_argument('--scale_weight', type=float, help='scale weight', default=1.0) - - parser.add_argument('--device_target', type=str, help='CPU or GPU', default='GPU', choices=['GPU']) - - parser.add_argument('--device_id', type=int, help='GPU id to use', default=0) - - parser.add_argument('--rank', type=int, help='rank', default=0) - - parser.add_argument('--name', type=str, help='name of the experiment', - default=None) - parser.add_argument('--ss_pretrain', type=str, help='name of the experiment', - default=None) - parser.add_argument('--retrain_model', type=str, help='name of the experiment', - default=None) - parser.add_argument('--train_steps', type=int, default=500) - parser.add_argument('--learning_rate', type=float, default=0.01) - parser.add_argument('--lr_decays', type=float, default=0.95) - parser.add_argument('--loss_scale', type=float, default=1.0) - parser.add_argument('--topk', type=int, default=500) - parser.add_argument('--num_training_ep0', type=int, default=30) - parser.add_argument('--labeled_percent', type=int, default=1) # range in [1,100] - parser.add_argument('--random_seed', type=int, default=888) - parser.add_argument('--graph_mode', action='store_true', default=False) - - arguments = parser.parse_args() - - config.dataset_dir = arguments.dataset_dir - config.batch_size = arguments.batch_size - config.max_epoch = arguments.epochs - config.train_steps = arguments.train_steps - config.learning_rate = arguments.learning_rate - config.lr_decays = arguments.lr_decays - config.loss_scale = arguments.loss_scale - config.topk = arguments.topk - num_training_ep0 = arguments.num_training_ep0 - config.training_ep0 = {i: 0 for i in range(0, num_training_ep0)} - config.training_ep = {i: np.exp(i / 100 - 1.0) - np.exp(-1.0) for i in range(0, 100)} - config.training_ep.update(config.training_ep0) - config.labeled_percent = arguments.labeled_percent - config.random_seed = arguments.random_seed - config.graph_mode = arguments.graph_mode - - if arguments.name is None: - if arguments.resume: - arguments.name = Path(arguments.resume).split('/')[-1] - else: - time_str = str(datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')) - arguments.name = f'TSteps{config.train_steps}_MaxEpoch{config.max_epoch}_BatchS{config.batch_size}' \ - f'_TopK{config.topk}_NumTrainEp0{num_training_ep0}' \ - f'_LP{config.labeled_percent}_RS{config.random_seed}' - if config.graph_mode: - arguments.name += "_GraphM" - else: - arguments.name += "_PyNateiveM" - arguments.name += f'_{time_str}' - - np.random.seed(config.random_seed) - set_seed(config.random_seed) - - arguments.outputs_dir = os.path.join(arguments.outputs_dir, arguments.name) - - print(f"outputs_dir:{arguments.outputs_dir}") - if not os.path.exists(arguments.outputs_dir): - os.makedirs(arguments.outputs_dir) - - if arguments.resume: - arguments.outputs_dir = arguments.resume - # start train - train(config, arguments) -- Gitee From 6e816ac2ee070f7db10fcbb171cb683d1c23dd05 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Mon, 28 Nov 2022 18:50:23 +0800 Subject: [PATCH 15/16] update --- research/cv/WS3/README.md | 8 +++----- research/cv/WS3/src/model/base_model.py | 1 + research/cv/WS3/src/model/base_model_remove_bias.py | 4 +++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index cde493dd3..9bbcfc235 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -131,12 +131,10 @@ WS3 │ ├── nearest_neighbors # dependency for point cloud nearest_neighbors │ └── compile_op.sh # shell for installing dependencies, including cpp_wrappers and nearest_neighbors │ -├── eval_gpu.py -├── eval_ascend.py +├── eval.py ├── README.md ├── requirements.txt -├── train_gpu.py -└── train_ascend.py +└── train.py ``` ### [Script Parameter](#contents) @@ -144,7 +142,7 @@ WS3 we use `train_s3dis_ascend.sh` as an example: ```shell -python train_ascend.py \ +python train.py \ --epochs 40 \ --batch_size 6 \ --val_area Area_5 \ diff --git a/research/cv/WS3/src/model/base_model.py b/research/cv/WS3/src/model/base_model.py index 820569d9e..0030baca8 100644 --- a/research/cv/WS3/src/model/base_model.py +++ b/research/cv/WS3/src/model/base_model.py @@ -45,6 +45,7 @@ class SharedMLP(nn.Cell): weight_init=TruncatedNormal(sigma=1e-3) ) self.has_bn = bn + self.batch_norm = None if self.has_bn: self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) self.activation_fn = activation_fn diff --git a/research/cv/WS3/src/model/base_model_remove_bias.py b/research/cv/WS3/src/model/base_model_remove_bias.py index bb3ce57f7..76a23061b 100644 --- a/research/cv/WS3/src/model/base_model_remove_bias.py +++ b/research/cv/WS3/src/model/base_model_remove_bias.py @@ -45,7 +45,9 @@ class SharedMLP(nn.Cell): weight_init=TruncatedNormal(sigma=1e-3) ) self.has_bn = bn - self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) + self.batch_norm = None + if self.has_bn: + self.batch_norm = nn.BatchNorm2d(out_channels, eps=1e-6, momentum=0.99) self.activation_fn = activation_fn def construct(self, x): -- Gitee From e3f73da0507090d9790a2a45c3fde018958a5f49 Mon Sep 17 00:00:00 2001 From: Haoming Chen Date: Mon, 28 Nov 2022 19:00:39 +0800 Subject: [PATCH 16/16] update README.md --- research/cv/WS3/README.md | 12 +++++++----- research/cv/WS3/eval.py | 3 ++- research/cv/WS3/src/data/S3DIS_dataset.py | 1 - research/cv/WS3/src/data/S3DIS_dataset_test.py | 1 - research/cv/WS3/src/model/model_s3dis.py | 5 +---- research/cv/WS3/src/model/model_s3dis_remove_bias.py | 5 ++--- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/research/cv/WS3/README.md b/research/cv/WS3/README.md index 9bbcfc235..80493708f 100644 --- a/research/cv/WS3/README.md +++ b/research/cv/WS3/README.md @@ -2,7 +2,7 @@ # Contents -- [WS3](#WS3) +- [WS3](#ws3) - [Model Architecture](#model-architecture) - [Dataset](#dataset) - [Preparation](#preparation) @@ -218,8 +218,8 @@ outputs ├── BatchS_6_Float16_PyNative_Ascend ├── 2022-11-24_time_11_23_40_rank_0.log # Evaluating: S3DIS dataset on Ascend └── ckpt # Evaluating: S3DIS dataset on GPU - ├── randla_1_500.ckpt - ├── randla_2_500.ckpt + ├── ws3_1_500.ckpt + ├── ws3_2_500.ckpt └── .... ``` @@ -244,15 +244,17 @@ Note: Before you start eval, please check `--model_path` in `eval_xxx.sh` and gu ### [Evaluation Result 910](#contents) +Example: + ```text Area_5_office_19 Acc:0.8584098992023179 Area_5_hallway_9 Acc:0.9581127867106095 Area_5_hallway_10 Acc:0.9735772827918966 Area_5_office_5 Acc:0.9377652641453553 Area_5_office_39 Acc:0.8065665136231684 -Area_5_WC_2 Acc:0.8629098008590395> Provide the result of evaluation. +Area_5_WC_2 Acc:0.8629098008590395 Area_5_hallway_12 Acc:0.9826733528883095 -Area_5_hallway_3 Acc:0.9548643367899511## Export +Area_5_hallway_3 Acc:0.9548643367899511 Area_5_office_12 Acc:0.8639117068037043 Area_5_office_23 Acc:0.9049251547225916 -------------------------------------------------------------------------------------- diff --git a/research/cv/WS3/eval.py b/research/cv/WS3/eval.py index 036a2a8c8..7ce5c397b 100644 --- a/research/cv/WS3/eval.py +++ b/research/cv/WS3/eval.py @@ -15,6 +15,7 @@ # ============================================================================ import os import argparse +import ast from pathlib import Path import numpy as np from sklearn.metrics import confusion_matrix @@ -207,7 +208,7 @@ if __name__ == "__main__": parser.add_argument('--rank', type=int, help='rank', default=0) parser.add_argument('--dataset_dir', type=str, help='path of dataset', default='./datasets/S3DIS') parser.add_argument('--outputs_dir', type=str, help='path of output', default='test_outputs') - parser.add_argument('--float16', type=bool, default=False) + parser.add_argument('--float16', type=ast.literal_eval, default=False) args = parser.parse_args() diff --git a/research/cv/WS3/src/data/S3DIS_dataset.py b/research/cv/WS3/src/data/S3DIS_dataset.py index aec095e56..e4f7b90d1 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset.py +++ b/research/cv/WS3/src/data/S3DIS_dataset.py @@ -137,7 +137,6 @@ class S3DISDatasetGenerator: # Validation projection and labels if self.val_split in cloud_name: proj_file = tree_path / f"{cloud_name}_proj.pkl" - # proj_file = join(tree_path, '{:s}_proj.pkl'.format(cloud_name)) with open(proj_file, 'rb') as f: proj_idx, labels = pickle.load(f) self.val_proj += [proj_idx] diff --git a/research/cv/WS3/src/data/S3DIS_dataset_test.py b/research/cv/WS3/src/data/S3DIS_dataset_test.py index f37891e9f..f1188e785 100644 --- a/research/cv/WS3/src/data/S3DIS_dataset_test.py +++ b/research/cv/WS3/src/data/S3DIS_dataset_test.py @@ -82,7 +82,6 @@ class S3DISDatasetGenerator: kd_tree_file = tree_path / '{:s}_KDTree.pkl'.format(cloud_name) sub_ply_file = tree_path / '{:s}.ply'.format(cloud_name) - # data = np.load(sub_npy_file, mmap_mode='r').T data = read_ply(str(sub_ply_file)) sub_colors = np.vstack((data['red'], data['green'], data['blue'])).T sub_labels = data['class'] diff --git a/research/cv/WS3/src/model/model_s3dis.py b/research/cv/WS3/src/model/model_s3dis.py index d2885c1d4..2421b0feb 100644 --- a/research/cv/WS3/src/model/model_s3dis.py +++ b/research/cv/WS3/src/model/model_s3dis.py @@ -164,7 +164,6 @@ class WS3WithLoss(nn.Cell): labels = labels.reshape((-1,)) # (b,n) -> (b*n) # Boolean mask of points that should be ignored - # (B*N,) ignore_mask = P.zeros_like(labels).astype(mstype.bool_) for ign_label in self.ignored_label_inds: ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # @@ -172,9 +171,7 @@ class WS3WithLoss(nn.Cell): one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) - # (B*N, 13) -> (B*N,) weights = P.ReduceSum()(weights, 1) - # (B*N,) & (B*N,13) unweighted_loss = self.loss_fn(logits, one_hot_labels) weighted_loss = unweighted_loss * weights weighted_loss = weighted_loss * valid_mask @@ -213,7 +210,7 @@ class WS3WithLoss(nn.Cell): row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) ones_idx = P.Stack(axis=1)([row_idx.reshape([-1]), nn_idx.reshape([-1])]) res = P.scatter_nd(ones_idx, P.ones(s[0] * topk, neg_adj_t.dtype), s) - nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] multi_hot + nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] new_valid_mask = P.reduce_sum(nn_idx_multi_hot, axis=1) > 0 # (B*N,) new_valid_mask = new_valid_mask.reshape(-1, 1) # (B*N,1) diff --git a/research/cv/WS3/src/model/model_s3dis_remove_bias.py b/research/cv/WS3/src/model/model_s3dis_remove_bias.py index bc410a569..16a39b1e0 100644 --- a/research/cv/WS3/src/model/model_s3dis_remove_bias.py +++ b/research/cv/WS3/src/model/model_s3dis_remove_bias.py @@ -160,13 +160,12 @@ class WS3WithLoss(nn.Cell): pred_embed = pred_embed.reshape((-1, 32)) labels = labels.reshape((-1,)) # (b,n) -> (b*n) - # (B*N,) + # Boolean mask of points that should be ignored ignore_mask = P.zeros_like(labels).astype(mstype.bool_) for ign_label in self.ignored_label_inds: ignore_mask = P.logical_or(ignore_mask, P.equal(labels, ign_label)) # valid_mask = P.logical_not(ignore_mask) # (B*N,) - # (B*N,13) one_hot_labels = self.onehot(labels) # (B*N,) -> (B*N,13) weights = self.weights * one_hot_labels * valid_mask.reshape(-1, 1) # (B*N,13) weights = P.ReduceSum()(weights, 1) @@ -208,7 +207,7 @@ class WS3WithLoss(nn.Cell): row_idx = P.tile(P.expand_dims(P.arange(s[0]), 1), (1, topk)) ones_idx = P.Stack(axis=1)([row_idx.reshape([-1]), nn_idx.reshape([-1])]) res = P.scatter_nd(ones_idx, P.ones(s[0] * topk, neg_adj_t.dtype), s) - nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] multi_hot + nn_idx_multi_hot = P.transpose(res, (1, 0)) # [N,M] new_valid_mask = P.reduce_sum(nn_idx_multi_hot, axis=1) > 0 # (B*N,) new_valid_mask = new_valid_mask.reshape(-1, 1) # (B*N,1) -- Gitee