"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