# How it works ## Scaling up the Non Maximum Suppression process ### Complexity * In the best case scenario, NMS is a **linear-complex** process ($O(n)$): if all boxes are perfectly overlapping, then one pass of the algorithm discards all the boxes except the highest scoring one. * In worst case scenario, NMS is a **quadratic-complex** operation (one needs to perform $n \times (n - 1) / 2$ IoU comparisons): if all boxes are perfectly disconnected, each NMS step will discard only one box (the highest scoring one, by decreasing order of score). Hence, one needs to perform $(n-1) + (n-2) + \dots + 1 = n \times (n - 1) / 2$ IoU computations. ### Working with huge images When working with high-dimensional images (such as satellital or histology images), one often runs object detection inference by patching (with overlap) the input image and applying NMS to independent patches. Because patches do overlap, a final NMS needs to be re-applied afterward. In that final case, one is close to being in the worst case scenario since each NMS step will discard only a very low amount of candidate instances (actually, pretty much the amount of overlapping passes over each instance, usually $\le 10$). Hence, depending on the size of the input image, computation time can reach several minutes on CPU. If the number of detections is in the hundreds of thousands, it can OOM consumer-grade GPUs. A more natural way to speed up NMS could be through parallelization, like it is done for GPU-based implementations, but: 1. Efficiently parallelizing NMS is not a straightforward process. 2. If too many instances are predicted, GPU VRAM will often not be sufficient, preventing one from using GPU accelerators. 3. The process remains quadratic, and does not scale well. ### LSNMS This project offers a way to overcome the aforementioned issues elegantly: 1. Like vanilla NMS, confidence scores are argsorted in decreasing order. 2. A rectangular tree (R-Tree) is initiated (empty). 3. Boxes are reviewed in decreasing order of confidence scores. Every time, the R-Tree is used to check whether this box is redundant with any already-seen bounding box: 1. If not, the index of this bbox is added to the list of boxes to return, and the box is inserted in the R-Tree. 2. If redundant, the box is discarded and the algorithm proceeds to the next box. Insertion and query time complexity both are $O(\log(k))$ with $k$ being the amount of boxes indexed in the R-Tree at insertion/query time. Thus $k$ grows over time (as boxes are eventually inserted in the tree). > **Note:** An older implementation (< v0.5.0) of `lsnms.nms` used another version of the algorithm, that is still used by the `"numba"` backend. This algorithm works by building the R-Tree on all the bounding boxes at once, then checking whether each bbox is redundant with any other. This older version is approximately 2 to 4 times slower. An introduction to R-Tree can be found here: [OpenGenus R-Tree](https://iq.opengenus.org/r-tree/).
Run times (on a virtual image of 10kx10k pixels)
Illustration of the bbox offsetting process for multiclass support