gx
chenyc
2025-06-12 7b72ac13a83764a662159d4a49b7fffb90476ecb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"use strict";
/**
 * @license
 * Copyright 2018 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =============================================================================
 */
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * Implementation of the NonMaxSuppression kernel shared between webgl and cpu.
 */
var tensor_ops_1 = require("../ops/tensor_ops");
var array_util_1 = require("./array_util");
function nonMaxSuppressionV3(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold) {
    var dummySoftNmsSigma = 0.0;
    return nonMaxSuppressionImpl_(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, dummySoftNmsSigma)
        .selectedIndices;
}
exports.nonMaxSuppressionV3 = nonMaxSuppressionV3;
function nonMaxSuppressionV5(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, softNmsSigma) {
    // For NonMaxSuppressionV5Op, we always return a second output holding
    // corresponding scores.
    var returnScoresTensor = true;
    var result = nonMaxSuppressionImpl_(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, softNmsSigma, returnScoresTensor);
    result.numValidOutputs.dispose();
    return {
        selectedIndices: result.selectedIndices,
        selectedScores: result.selectedScores
    };
}
exports.nonMaxSuppressionV5 = nonMaxSuppressionV5;
function nonMaxSuppressionImpl_(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold, softNmsSigma, returnScoresTensor, padToMaxOutputSize) {
    if (returnScoresTensor === void 0) { returnScoresTensor = false; }
    if (padToMaxOutputSize === void 0) { padToMaxOutputSize = false; }
    // The list is sorted in ascending order, so that we can always pop the
    // candidate with the largest score in O(1) time.
    var candidates = Array.from(scores)
        .map(function (score, boxIndex) { return ({ score: score, boxIndex: boxIndex, suppressBeginIndex: 0 }); })
        .filter(function (c) { return c.score > scoreThreshold; })
        .sort(ascendingComparator);
    // If softNmsSigma is 0, the outcome of this algorithm is exactly same as
    // before.
    var scale = softNmsSigma > 0 ? (-0.5 / softNmsSigma) : 0.0;
    var selectedIndices = [];
    var selectedScores = [];
    while (selectedIndices.length < maxOutputSize && candidates.length > 0) {
        var candidate = candidates.pop();
        var originalScore = candidate.score, boxIndex = candidate.boxIndex, suppressBeginIndex = candidate.suppressBeginIndex;
        if (originalScore < scoreThreshold) {
            break;
        }
        // Overlapping boxes are likely to have similar scores, therefore we
        // iterate through the previously selected boxes backwards in order to
        // see if candidate's score should be suppressed. We use
        // suppressBeginIndex to track and ensure a candidate can be suppressed
        // by a selected box no more than once. Also, if the overlap exceeds
        // iouThreshold, we simply ignore the candidate.
        var ignoreCandidate = false;
        for (var j = selectedIndices.length - 1; j >= suppressBeginIndex; --j) {
            var iou = intersectionOverUnion(boxes, boxIndex, selectedIndices[j]);
            if (iou >= iouThreshold) {
                ignoreCandidate = true;
                break;
            }
            candidate.score =
                candidate.score * suppressWeight(iouThreshold, scale, iou);
            if (candidate.score <= scoreThreshold) {
                break;
            }
        }
        // At this point, if `candidate.score` has not dropped below
        // `scoreThreshold`, then we know that we went through all of the
        // previous selections and can safely update `suppressBeginIndex` to the
        // end of the selected array. Then we can re-insert the candidate with
        // the updated score and suppressBeginIndex back in the candidate list.
        // If on the other hand, `candidate.score` has dropped below the score
        // threshold, we will not add it back to the candidates list.
        candidate.suppressBeginIndex = selectedIndices.length;
        if (!ignoreCandidate) {
            // Candidate has passed all the tests, and is not suppressed, so
            // select the candidate.
            if (candidate.score === originalScore) {
                selectedIndices.push(boxIndex);
                selectedScores.push(candidate.score);
            }
            else if (candidate.score > scoreThreshold) {
                // Candidate's score is suppressed but is still high enough to be
                // considered, so add back to the candidates list.
                array_util_1.binaryInsert(candidates, candidate, ascendingComparator);
            }
        }
    }
    // NonMaxSuppressionV4 feature: padding output to maxOutputSize.
    var numValidOutputs = selectedIndices.length;
    if (padToMaxOutputSize) {
        selectedIndices.fill(0, numValidOutputs);
        selectedScores.fill(0.0, numValidOutputs);
    }
    return {
        selectedIndices: tensor_ops_1.tensor1d(selectedIndices, 'int32'),
        selectedScores: tensor_ops_1.tensor1d(selectedScores, 'float32'),
        numValidOutputs: tensor_ops_1.scalar(numValidOutputs, 'int32')
    };
}
function intersectionOverUnion(boxes, i, j) {
    var iCoord = boxes.subarray(i * 4, i * 4 + 4);
    var jCoord = boxes.subarray(j * 4, j * 4 + 4);
    var yminI = Math.min(iCoord[0], iCoord[2]);
    var xminI = Math.min(iCoord[1], iCoord[3]);
    var ymaxI = Math.max(iCoord[0], iCoord[2]);
    var xmaxI = Math.max(iCoord[1], iCoord[3]);
    var yminJ = Math.min(jCoord[0], jCoord[2]);
    var xminJ = Math.min(jCoord[1], jCoord[3]);
    var ymaxJ = Math.max(jCoord[0], jCoord[2]);
    var xmaxJ = Math.max(jCoord[1], jCoord[3]);
    var areaI = (ymaxI - yminI) * (xmaxI - xminI);
    var areaJ = (ymaxJ - yminJ) * (xmaxJ - xminJ);
    if (areaI <= 0 || areaJ <= 0) {
        return 0.0;
    }
    var intersectionYmin = Math.max(yminI, yminJ);
    var intersectionXmin = Math.max(xminI, xminJ);
    var intersectionYmax = Math.min(ymaxI, ymaxJ);
    var intersectionXmax = Math.min(xmaxI, xmaxJ);
    var intersectionArea = Math.max(intersectionYmax - intersectionYmin, 0.0) *
        Math.max(intersectionXmax - intersectionXmin, 0.0);
    return intersectionArea / (areaI + areaJ - intersectionArea);
}
// A Gaussian penalty function, this method always returns values in [0, 1].
// The weight is a function of similarity, the more overlap two boxes are, the
// smaller the weight is, meaning highly overlapping boxe will be significantly
// penalized. On the other hand, a non-overlapping box will not be penalized.
function suppressWeight(iouThreshold, scale, iou) {
    var weight = Math.exp(scale * iou * iou);
    return iou <= iouThreshold ? weight : 0.0;
}
function ascendingComparator(c1, c2) {
    // For objects with same scores, we make the object with the larger index go
    // first. In an array that pops from the end, this means that the object with
    // the smaller index will be popped first. This ensures the same output as
    // the TensorFlow python version.
    return (c1.score - c2.score) ||
        ((c1.score === c2.score) && (c2.boxIndex - c1.boxIndex));
}
//# sourceMappingURL=non_max_suppression_impl.js.map