/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

#include "NvInfer.h"
#include "common/plugin.h"
#include <algorithm>
#include <assert.h>
#include <cub/cub.cuh>
#include <cuda.h>
#include <cuda_runtime.h>
#include <functional>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>

namespace nvinfer1
{
namespace plugin
{
#define PLUGIN_CHECK_CUDA(call)                                                                                        \
    do                                                                                                                 \
    {                                                                                                                  \
        cudaError_t status = call;                                                                                     \
        if (status != cudaSuccess)                                                                                     \
        {                                                                                                              \
            return status;                                                                                             \
        }                                                                                                              \
    } while (0)
template <typename TFloat>
struct Bbox
{
    TFloat x1, y1, x2, y2;
};

typedef nvinfer1::DataType DType_t;

typedef enum
{
    NCHW = 0,
    NC4HW = 1
} DLayout_t;

typedef pluginStatus_t frcnnStatus_t;

#define DEBUG_RPN_ENABLE 0

#define FRCNN_ASSERT_PARAM(exp)                                                         \
    do                                                                                  \
    {                                                                                   \
        if (!(exp))                                                                     \
        {                                                                               \
            DEBUG_FPRINTF(stderr, "Bad param - " #exp ", %s:%d\n", __FILE__, __LINE__); \
            return STATUS_BAD_PARAM;                                                    \
        }                                                                               \
    } while (0)

#define DEBUG_FPRINTF(...)        \
    do                            \
    {                             \
        if (DEBUG_RPN_ENABLE)     \
        {                         \
            fprintf(__VA_ARGS__); \
        }                         \
    } while (0)

#define CUDA_MEM_ALIGN 256

unsigned int hash(const void* array_, size_t size);
int8_t* alignPtr(int8_t* ptr, uintptr_t to);
__global__ void setOffset(int stride, int size, int* output);
frcnnStatus_t nms(cudaStream_t stream,
    const int N,
    const int R,
    const int preNmsTop,
    const int nmsMaxOut,
    const float iouThreshold,
    const DType_t t_fgScores,
    const DLayout_t l_fgScores,
    void* fgScores,
    const DType_t t_proposals,
    const DLayout_t l_proposals,
    const void* proposals,
    void* workspace,
    const DType_t t_rois,
    void* rois);
int8_t* nextWorkspacePtr(int8_t* ptr, uintptr_t previousWorkspaceSize);


template <typename TFloat>
__device__ __host__ inline float IoU(const Bbox<TFloat>& a, const Bbox<TFloat>& b)
{
    TFloat left = max(a.x1, b.x1), right = min(a.x2, b.x2);
    TFloat top = max(a.y1, b.y1), bottom = min(a.y2, b.y2);
    TFloat width = max((TFloat)(right - left + (TFloat) 1.0), (TFloat) 0.0);
    TFloat height = max((TFloat)(bottom - top + (TFloat) 1.0), (TFloat) 0.0);
    TFloat interS = width * height;
    TFloat Sa = (a.x2 - a.x1 + (TFloat) 1) * (a.y2 - a.y1 + (TFloat) 1);
    TFloat Sb = (b.x2 - b.x1 + (TFloat) 1) * (b.y2 - b.y1 + (TFloat) 1);
    return (float) interS / (float) (Sa + Sb - interS);
}


// NMS KERNEL FOR SMALL BATCH SIZE {{{
template <typename T_PROPOSALS, typename T_ROIS, int DIM, int TSIZE>
__global__ __launch_bounds__(DIM) void nmsKernel1(const int propSize,
        Bbox<T_PROPOSALS> const* __restrict__ preNmsProposals,
        T_ROIS* __restrict__ afterNmsProposals,
        const int preNmsTopN,
        const float nmsThres,
        const int afterNmsTopN)
{
    __shared__ bool kept_boxes[TSIZE * DIM];
    int kept = 0;
    int batch_offset = blockIdx.x * propSize;
    int max_box_idx = batch_offset + preNmsTopN;
    int batch_offset_out = blockIdx.x * afterNmsTopN;
    int flag_idx[TSIZE];
    int boxes_idx[TSIZE];
    Bbox<T_PROPOSALS> cur_boxes[TSIZE];
// initialize kept_boxes
#pragma unroll

    for (int i = 0; i < TSIZE; i++)
    {
        boxes_idx[i] = threadIdx.x + batch_offset + DIM * i;
        flag_idx[i] = threadIdx.x + DIM * i;

        if (boxes_idx[i] < max_box_idx)
        {
            cur_boxes[i] = preNmsProposals[boxes_idx[i]];
            kept_boxes[flag_idx[i]] = true;
        }
        else
        {
            kept_boxes[flag_idx[i]] = false;
            boxes_idx[i] = -1.0f;
            flag_idx[i] = -1.0f;
        }
    }

    int ref_box_idx = 0 + batch_offset;

    // remove the overlapped boxes
    while ((kept < afterNmsTopN) && (ref_box_idx < max_box_idx))
    {
        Bbox<T_PROPOSALS> ref_box;
        ref_box = preNmsProposals[ref_box_idx];
#pragma unroll

        for (int i = 0; i < TSIZE; i++)
        {
            if (boxes_idx[i] > ref_box_idx)
            {
                if (IoU(ref_box, cur_boxes[i]) > nmsThres)
                {
                    kept_boxes[flag_idx[i]] = false;
                }
            }
            else if (boxes_idx[i] == ref_box_idx)
            {
                afterNmsProposals[(batch_offset_out + kept) * 4 + 0] = ref_box.x1;
                afterNmsProposals[(batch_offset_out + kept) * 4 + 1] = ref_box.y1;
                afterNmsProposals[(batch_offset_out + kept) * 4 + 2] = ref_box.x2;
                afterNmsProposals[(batch_offset_out + kept) * 4 + 3] = ref_box.y2;
            }
        }

        __syncthreads();

        do
        {
            ref_box_idx++;
        }
        while (!kept_boxes[ref_box_idx - batch_offset] && ref_box_idx < max_box_idx);

        kept++;
    }
}
// }}}

// NMS KERNEL FOR LARGE BATCH SIZE {{{
template <typename T_PROPOSALS, typename T_ROIS, int DIM, int TSIZE>
__global__ __launch_bounds__(DIM) void nmsKernel2(const int propSize,
        Bbox<T_PROPOSALS> const* __restrict__ proposals,
        T_ROIS* __restrict__ filtered,
        const int preNmsTopN,
        const float nmsThres,
        const int afterNmsTopN)
{
    Bbox<T_PROPOSALS> const* cProposals = proposals + blockIdx.x * propSize;
    Bbox<T_PROPOSALS> t[TSIZE];
    uint64_t del = 0;

    for (int i = 0; i < TSIZE; i++)
    {
        if (i < TSIZE - 1 || i * DIM + threadIdx.x < preNmsTopN)
        {
            t[i] = cProposals[i * DIM + threadIdx.x];
        }
    }

    __shared__ Bbox<T_PROPOSALS> last;
    __shared__ bool kept;
    __shared__ int foundBatch;

    if (threadIdx.x == 0)
    {
        foundBatch = 0;
    }

    for (int i = 0; i < TSIZE; i++)
    {
        for (int j = 0; j < DIM; j++)
        {
            int offset = i * DIM;
            int index = offset + j;

            if (index >= preNmsTopN)
            {
                break;
            }
            __syncthreads();

            if (threadIdx.x == j)
            {
                kept = 0 == (del & ((uint64_t) 1 << i));
                last = t[i];

                if (kept)
                {
                    int cnt = blockIdx.x * afterNmsTopN + foundBatch;
                    filtered[cnt * 4 + 0] = t[i].x1;
                    filtered[cnt * 4 + 1] = t[i].y1;
                    filtered[cnt * 4 + 2] = t[i].x2;
                    filtered[cnt * 4 + 3] = t[i].y2;
                    foundBatch++;
                }
            }

            __syncthreads();

            if (foundBatch == afterNmsTopN)
            {
                return;
            }

            if (kept)
            {
                Bbox<T_PROPOSALS> test = last;

                for (int k = 0; k < TSIZE; k++)
                {
                    if (index < k * DIM + threadIdx.x
                            && IoU<T_PROPOSALS>(test, t[k]) > nmsThres)
                    {
                        del |= (uint64_t) 1 << k;
                    }
                }
            }
        }
    }
}
// }}}

// NMS LAUNCH {{{
template <typename T_PROPOSALS, DLayout_t L_PROPOSALS, typename T_ROIS>
frcnnStatus_t nmsLaunch(cudaStream_t stream,
                        const int batch,
                        const int propSize,
                        void* proposals,
                        void* filtered,
                        const int preNmsTopN,
                        const float nmsThres,
                        const int afterNmsTopN)
{
    const int blockSize = 1024;
#define P1(tsize) nmsKernel1<T_PROPOSALS, T_ROIS, blockSize, (tsize)>
#define P2(tsize) nmsKernel2<T_PROPOSALS, T_ROIS, blockSize, (tsize)>
    void (*kernel[64])(int, Bbox<T_PROPOSALS> const*, T_ROIS*, int, float, int) =
    {
        P1(1), P1(2), P1(3), P1(4), P1(5), P1(6), P1(7), P1(8), P1(9), P1(10), P1(11), P1(12), P2(13), P2(14), P2(15), P2(16),
        P2(17), P2(18), P2(19), P2(20), P2(21), P2(22), P2(23), P2(24), P2(25), P2(26), P2(27), P2(28), P2(29), P2(30), P2(31), P2(32),
        P2(33), P2(34), P2(35), P2(36), P2(37), P2(38), P2(39), P2(40), P2(41), P2(42), P2(43), P2(44), P2(45), P2(46), P2(47), P2(48),
        P2(49), P2(50), P2(51), P2(52), P2(53), P2(54), P2(55), P2(56), P2(57), P2(58), P2(59), P2(60), P2(61), P2(62), P2(63), P2(64)
    };
    FRCNN_ASSERT_PARAM(preNmsTopN < 64 * blockSize);
    CSC(cudaMemsetAsync(filtered, 0, batch * afterNmsTopN * 4 * sizeof(T_ROIS), stream),
        STATUS_FAILURE);
    kernel[(preNmsTopN + blockSize - 1) / blockSize - 1] <<< batch, blockSize, 0, stream>>>(propSize,
            (Bbox<T_PROPOSALS>*) proposals,
            (T_ROIS*) filtered,
            preNmsTopN,
            nmsThres,
            afterNmsTopN);
    CSC(cudaGetLastError(), STATUS_FAILURE);
    return STATUS_SUCCESS;
}
// }}}

// NMS GPU {{{
template <typename T_SCORES, typename T_ROIS>
frcnnStatus_t nmsGpu(cudaStream_t stream, const int N, const int R, const int preNmsTop, const int nmsMaxOut,
    const float iouThreshold, void* fgScores, const void* proposals, void* workspace, void* rois)
{
    // CUB's bug workaround:
    // To work properly for large batch size CUB segmented sort needs ridiculous
    // workspace alignment.
    constexpr uintptr_t kALIGNMENT = 1 << 20;

    int8_t* vworkspace = alignPtr((int8_t*) workspace, kALIGNMENT);
    DEBUG_PRINTF("&&&& [NMS] PROPOSALS %u\n", hash(proposals, N * R * 4 * sizeof(float)));
    DEBUG_PRINTF("&&&& [NMS] SCORES %u\n", hash(fgScores, N * R * sizeof(float)));
    frcnnStatus_t error;
    DEBUG_PRINTF("&&&& [NMS] DISCARD\n");
    DEBUG_PRINTF("&&&& [NMS] PROPOSALS %u\n", hash(proposals, N * R * 4 * sizeof(float)));
    DEBUG_PRINTF("&&&& [NMS] SCORES %u\n", hash(fgScores, N * R * sizeof(float)));
    // Generate offsets
    int* offsets = (int*) vworkspace;
    setOffset<<<1, 1024, 0, stream>>>(R, N + 1, offsets);
    CSC(cudaGetLastError(), STATUS_FAILURE);
    vworkspace = vworkspace + N + 1;
    vworkspace = alignPtr(vworkspace, kALIGNMENT);
    // Sort (batched)
    std::size_t tempStorageBytes = 0;
    cub::DeviceSegmentedRadixSort::SortPairsDescending(NULL, tempStorageBytes, (T_SCORES*) fgScores,
        (T_SCORES*) fgScores, (Bbox<T_ROIS>*) proposals, (Bbox<T_ROIS>*) proposals, N * R, N, offsets, offsets + 1, 0,
        8 * sizeof(T_SCORES), stream);
    CSC(cudaGetLastError(), STATUS_FAILURE);
    T_SCORES* scoresOut = (T_SCORES*) vworkspace;
    vworkspace = (int8_t*) (scoresOut + N * R);
    vworkspace = alignPtr(vworkspace, kALIGNMENT);
    Bbox<T_ROIS>* proposalsOut = (Bbox<T_ROIS>*) vworkspace;
    vworkspace = (int8_t*) (proposalsOut + N * R);
    vworkspace = alignPtr(vworkspace, kALIGNMENT);
    cub::DeviceSegmentedRadixSort::SortPairsDescending(vworkspace, tempStorageBytes, (T_SCORES*) fgScores,
        (T_SCORES*) scoresOut, (Bbox<T_ROIS>*) proposals, (Bbox<T_ROIS>*) proposalsOut, N * R, N, offsets, offsets + 1,
        0, 8 * sizeof(T_SCORES), stream);
    CSC(cudaGetLastError(), STATUS_FAILURE);
    DEBUG_PRINTF("&&&& [NMS] POST CUB\n");
    DEBUG_PRINTF("&&&& [NMS] PROPOSALS %u\n", hash(proposalsOut, N * R * 4 * sizeof(float)));
    DEBUG_PRINTF("&&&& [NMS] SCORES %u\n", hash(scoresOut, N * R * sizeof(float)));
    error = nmsLaunch<T_ROIS, NC4HW, T_ROIS>(stream,
            N,
            R,
            proposalsOut,
            rois,
            preNmsTop,
            iouThreshold,
            nmsMaxOut);
    DEBUG_PRINTF("&&&& [NMS] POST LAUNCH\n");
    DEBUG_PRINTF("&&&& [NMS] SCORES %u\n", hash(rois, N * nmsMaxOut * 4 * sizeof(float)));

    if (error != STATUS_SUCCESS)
    {
        return error;
    }

    return STATUS_SUCCESS;
}
// }}}

typedef frcnnStatus_t (*nmsFun)(cudaStream_t,
                                const int,   // N
                                const int,   // R
                                const int,   // preNmsTop
                                const int,   // nmsMaxOut
                                const float, // iouThreshold
                                void*,       // fgScores
                                const void*, // proposals,
                                void*,       // workspace,
                                void*);      // rois

struct nmsLaunchConfig
{
    DType_t t_fgScores;
    DLayout_t l_fgScores;
    DType_t t_proposals;
    DLayout_t l_proposals;
    DType_t t_rois;
    nmsFun function;

    nmsLaunchConfig(DType_t t_fgScores,
                    DLayout_t l_fgScores,
                    DType_t t_proposals,
                    DLayout_t l_proposals,
                    DType_t t_rois,
                    nmsFun function)
        : t_fgScores(t_fgScores)
        , l_fgScores(l_fgScores)
        , t_proposals(t_proposals)
        , l_proposals(l_proposals)
        , t_rois(t_rois)
        , function(function)
    {
    }

    nmsLaunchConfig(
        DType_t t_fgScores, DLayout_t l_fgScores, DType_t t_proposals, DLayout_t l_proposals, DType_t t_rois)
        : t_fgScores(t_fgScores)
        , l_fgScores(l_fgScores)
        , t_proposals(t_proposals)
        , l_proposals(l_proposals)
        , t_rois(t_rois)
        , function(nullptr)
    {
    }

    bool operator==(nmsLaunchConfig const& other) const
    {
        return (t_fgScores == other.t_fgScores) && (l_fgScores == other.l_fgScores)
               && (t_proposals == other.t_proposals) && (l_proposals == other.l_proposals)
               && (t_rois == other.t_rois);
    }
};

static std::vector<nmsLaunchConfig> nmsLCVec;
#define FLOAT32 nvinfer1::DataType::kFLOAT

__global__ void _inverse_transform_gpu(const float* RPN_prob, const float* RPN_regr, int N,
                                       int INPUT_H, int INPUT_W, int RPN_H, int RPN_W, float RPN_STD_SCALING, int RPN_STRIDE,
                                       float* ANCHOR_SIZES, int anc_size_num, float* ANCHOR_RATIOS, int anc_ratio_num, float bbox_min_size,
                                       float* fg_scores, float* proposal_out)
{
    int nthreads = N * RPN_H * RPN_W * anc_size_num * anc_ratio_num;
    int num_ancs = anc_size_num * anc_ratio_num;

    for (int out_idx = threadIdx.x + blockDim.x * blockIdx.x; out_idx < nthreads;
            out_idx += blockDim.x * gridDim.x)
    {
        //input RPN_regr: (N, A4, H, W), thread: (N, A, H, W)
        int idx = out_idx;
        int w = idx % RPN_W;
        idx /= RPN_W;
        int h = idx % RPN_H;
        idx /= RPN_H;
        int a = idx % num_ancs;
        int n = idx / num_ancs;
        // normalize by RPN_STD_SCALING
        int ptr_1 = ((((n * num_ancs) + a) * 4) * RPN_H + h) * RPN_W + w;
        int ptr_2 = ((((n * num_ancs) + a) * 4 + 1) * RPN_H + h) * RPN_W + w;
        int ptr_3 = ((((n * num_ancs) + a) * 4 + 2) * RPN_H + h) * RPN_W + w;
        int ptr_4 = ((((n * num_ancs) + a) * 4 + 3) * RPN_H + h) * RPN_W + w;
        float tx = RPN_regr[ptr_1] / RPN_STD_SCALING;
        float ty = RPN_regr[ptr_2] / RPN_STD_SCALING;
        float tw = RPN_regr[ptr_3] / RPN_STD_SCALING;
        float th = RPN_regr[ptr_4] / RPN_STD_SCALING;
        // do inverse transform
        int ar = a % anc_ratio_num;
        int as = a / anc_ratio_num;
        float anchor_w = ANCHOR_SIZES[as] * ANCHOR_RATIOS[ar];
        float anchor_h = ANCHOR_SIZES[as] / ANCHOR_RATIOS[ar];
        float anchor_cx = (w + 0.5f) * RPN_STRIDE;
        float anchor_cy = (h + 0.5f) * RPN_STRIDE;
        float cx1 = anchor_cx + anchor_w * tx;
        float cy1 = anchor_cy + anchor_h * ty;
        float w1 = __expf(tw) * anchor_w;
        float h1 = __expf(th) * anchor_h;
        tx = cx1 - w1 / 2.0f;
        ty = cy1 - h1 / 2.0f;
        tw = w1;
        th = h1;
        tw += tx;
        th += ty;
        // clip to min
        tx = (tx >= 0.0f) ? tx : 0.0f;
        ty = (ty >= 0.0f) ? ty : 0.0f;
        tw = (tw >= 0.0f) ? tw : 0.0f;
        th = (th >= 0.0f) ? th : 0.0f;
        //clip to max
        tx = (tx <= INPUT_W - 1.0f) ? tx : (INPUT_W - 1.0f);
        ty = (ty <= INPUT_H - 1.0f) ? ty : (INPUT_H - 1.0f);
        tw = (tw <= INPUT_W - 1.0f) ? tw : (INPUT_W - 1.0f);
        th = (th <= INPUT_H - 1.0f) ? th : (INPUT_H - 1.0f);
        // filter out small boxes by setting the confidence to -inf
        int ininf = 0xff800000;
        float ninf = *(float*) &ininf;

        if (tw - tx <= bbox_min_size || th - ty <= bbox_min_size)
        {
            fg_scores[out_idx] = ninf;
        }

        // copy to proposal_out, output shape: (N, A, H, W, 4)
        proposal_out[out_idx * 4] = tx;
        proposal_out[out_idx * 4 + 1] = ty;
        proposal_out[out_idx * 4 + 2] = tw;
        proposal_out[out_idx * 4 + 3] = th;
    }
}



cudaError_t _inverse_transform_wrapper(const float* RPN_prob, const float* RPN_regr, int N, int INPUT_H,
                                int INPUT_W, int RPN_H, int RPN_W, float RPN_STD_SCALING, int RPN_STRIDE,  float* ANCHOR_SIZES,
                                int anc_size_num, float* ANCHOR_RATIOS, int anc_ratio_num, float bbox_min_size, float* fg_scores,
                                float* proposal_out, cudaStream_t stream)
{
    const int block_size = 1024;
    const int grid_size = (N * anc_size_num * anc_ratio_num * RPN_H * RPN_W + block_size - 1) /
                          (block_size);
    _inverse_transform_gpu <<< grid_size, block_size, 0, stream>>> (RPN_prob, RPN_regr, N, INPUT_H,
            INPUT_W, RPN_H, RPN_W, RPN_STD_SCALING, RPN_STRIDE, ANCHOR_SIZES, anc_size_num, ANCHOR_RATIOS,
            anc_ratio_num, bbox_min_size, fg_scores, proposal_out);

    return cudaGetLastError();
}

size_t _proposalsForwardNMSWorkspaceSize(int N,
                                        int A,
                                        int H,
                                        int W,
                                        int nmsMaxOut)
{
    return N * A * H * W * 5 * 5 * sizeof(float) + (1 << 22);
}

size_t _proposalsForwardBboxWorkspaceSize(int N, int A, int H, int W)
{
    return N * A * H * W * 4 * sizeof(float);
}


size_t _proposalForwardFgScoresWorkspaceSize(int N, int A, int H, int W)
{
    return N * A * H * W * sizeof(float);
}


size_t anchors_buf_size(int anc_size_num, int anc_ratio_num)
{
    return (anc_size_num + anc_ratio_num) * sizeof(float);
}

size_t calculateTotalWorkspaceSize(size_t* workspaces, int count);

size_t _get_workspace_size(int N,
                           int anc_size_num,
                           int anc_ratio_num,
                           int H,
                           int W,
                           int nmsMaxOut)
{
    size_t wss[4];
    int A = anc_size_num * anc_ratio_num;
    wss[0] = _proposalsForwardNMSWorkspaceSize(N, A, H, W, nmsMaxOut);
    wss[1] = _proposalsForwardBboxWorkspaceSize(N, A, H, W);
    wss[2] = _proposalForwardFgScoresWorkspaceSize(N, A, H, W);
    wss[3] = anchors_buf_size(anc_size_num, anc_ratio_num);
    return calculateTotalWorkspaceSize(wss, 4);
}



template <typename T>
frcnnStatus_t extractFgScores_gpu(cudaStream_t stream,
                                  int N,
                                  int A,
                                  int H,
                                  int W,
                                  const void* scores,
                                  void* fgScores)
{
    //TODO custom kernel for this
    size_t size = A * H * W * sizeof(T);

    for (int n = 0; n < N; n++)
    {
        size_t offset_ld = n * A * H * W;
        size_t offset_st = n * A * H * W;
        CSC(cudaMemcpyAsync(((T*) fgScores) + offset_st, ((T*) scores) + offset_ld, size,
                            cudaMemcpyDeviceToDevice, stream), STATUS_FAILURE);
    }

    return STATUS_SUCCESS;
}

cudaError_t _copy_anchors_to_gpu(cudaStream_t stream, float* ANCHOR_SIZES, int anc_size_num, float* ANCHOR_RATIOS,
    int anc_ratio_num, void* anchor_size_buf)
{
    PLUGIN_CHECK_CUDA(cudaMemcpyAsync(anchor_size_buf, static_cast<void*>(ANCHOR_SIZES), sizeof(float) * anc_size_num,
        cudaMemcpyHostToDevice, stream));
    PLUGIN_CHECK_CUDA(cudaMemcpyAsync(static_cast<void*>(static_cast<float*>(anchor_size_buf) + anc_size_num),
        static_cast<void*>(ANCHOR_RATIOS), sizeof(float) * anc_ratio_num, cudaMemcpyHostToDevice, stream));

    return cudaSuccess;
}


__global__ void _normalize_rois_kernel(float* roi_after_nms, int nthreads, int width, int height)
{
    for(int i = threadIdx.x + blockDim.x * blockIdx.x; i < nthreads; i += blockDim.x * gridDim.x)
    {
        float x1 = roi_after_nms[i * 4];
        float y1 = roi_after_nms[i * 4 + 1];
        float x2 = roi_after_nms[i * 4 + 2];
        float y2 = roi_after_nms[i * 4 + 3];
        roi_after_nms[i * 4] = y1 / (height - 1.0f);
        roi_after_nms[i * 4 + 1] = x1 / (width - 1.0f);
        roi_after_nms[i * 4 + 2] = y2 / (height - 1.0f);
        roi_after_nms[i * 4 + 3] = x2 / (width - 1.0f);
    }
}



cudaError_t _normalize_rois(float* roi_after_nms, int n, int max_box_num, int input_width,
                     int input_height, cudaStream_t stream)
{
    const int block_size = 1024;
    const int grid_size = (n * max_box_num + block_size - 1) / block_size;
    _normalize_rois_kernel <<< grid_size, block_size, 0, stream>>>(roi_after_nms, n * max_box_num,
            input_width, input_height);

    return cudaGetLastError();
}


int proposalInference_gpu(
    cudaStream_t stream,
    const void* rpn_prob,
    const void* rpn_regr,
    int batch_size,
    int input_height,
    int input_width,
    int rpn_height,
    int rpn_width,
    int MAX_BOX_NUM,
    int RPN_PRE_NMS_TOP_N,
    float* ANCHOR_SIZES,
    int anc_size_num,
    float* ANCHOR_RATIOS,
    int anc_ratio_num,
    float rpn_std_scaling,
    int rpn_stride,
    float bbox_min_size,
    float nms_iou_threshold,
    void * workspace,
    void* output)
{
    size_t nmsWorkspaceSize = _proposalsForwardNMSWorkspaceSize(batch_size, anc_size_num * anc_ratio_num,
                              rpn_height, rpn_width, MAX_BOX_NUM);
    void* nmsWorkspace = workspace;
    size_t proposalsSize = _proposalsForwardBboxWorkspaceSize(batch_size, anc_size_num * anc_ratio_num,
                           rpn_height, rpn_width);
    const DType_t t_proposals = nvinfer1::DataType::kFLOAT;
    const DLayout_t l_proposals = NC4HW;
    void* proposals = nextWorkspacePtr((int8_t*) nmsWorkspace, nmsWorkspaceSize);
    void* fg_scores = nextWorkspacePtr((int8_t*) proposals, proposalsSize);
    size_t fg_scores_size = _proposalForwardFgScoresWorkspaceSize(batch_size,
                            anc_size_num * anc_ratio_num, rpn_height, rpn_width);
    void* anchor_size_buf = nextWorkspacePtr((int8_t*) fg_scores, fg_scores_size);
    void* anchor_ratio_buf = static_cast<void*>(static_cast<float*>(anchor_size_buf) + anc_size_num);
    frcnnStatus_t status;
    PLUGIN_CHECK_CUDA(
        _copy_anchors_to_gpu(stream, ANCHOR_SIZES, anc_size_num, ANCHOR_RATIOS, anc_ratio_num, anchor_size_buf));

    status = extractFgScores_gpu<float>(
        stream, batch_size, anc_size_num * anc_ratio_num, rpn_height, rpn_width, rpn_prob, fg_scores);
    PLUGIN_ASSERT(status == STATUS_SUCCESS);
    PLUGIN_CHECK_CUDA(
        _inverse_transform_wrapper(static_cast<const float*>(rpn_prob), static_cast<const float*>(rpn_regr), batch_size,
            input_height, input_width, rpn_height, rpn_width, rpn_std_scaling, rpn_stride,
            static_cast<float*>(anchor_size_buf), anc_size_num, static_cast<float*>(anchor_ratio_buf), anc_ratio_num,
            bbox_min_size, static_cast<float*>(fg_scores), static_cast<float*>(proposals), stream));

    status = nms(stream, batch_size, anc_size_num * anc_ratio_num * rpn_height * rpn_width, RPN_PRE_NMS_TOP_N,
        MAX_BOX_NUM, nms_iou_threshold, nvinfer1::DataType::kFLOAT, NCHW, fg_scores, t_proposals, l_proposals,
        proposals, workspace, nvinfer1::DataType::kFLOAT, output);

    PLUGIN_ASSERT(status == STATUS_SUCCESS);

    PLUGIN_CHECK_CUDA(
        _normalize_rois(static_cast<float*>(output), batch_size, MAX_BOX_NUM, input_width, input_height, stream));

    return STATUS_SUCCESS;
}
} // namespace plugin
} // namespace nvinfer1
