func (pg *Pigo) ClusterDetections(detections []Detection, iouThreshold float64) []Detection {
	sort.Sort(det(detections))
	calcIoU := func(det1, det2 Detection) float64 {
		r1, c1, s1 := float64(det1.Row), float64(det1.Col), float64(det1.Scale)
		r2, c2, s2 := float64(det2.Row), float64(det2.Col), float64(det2.Scale)
		overRow := math.Max(0, math.Min(r1+s1/2, r2+s2/2)-math.Max(r1-s1/2, r2-s2/2))
		overCol := math.Max(0, math.Min(c1+s1/2, c2+s2/2)-math.Max(c1-s1/2, c2-s2/2))
		return overRow * overCol / (s1*s1 + s2*s2 - overRow*overCol)
	}
	assignments := make([]bool, len(detections))
	clusters := []Detection{}
	for i := 0; i < len(detections); i++ {
		if !assignments[i] {
			var (
				r, c, s, n int
				q          float32
			)
			for j := 0; j < len(detections); j++ {
				if calcIoU(detections[i], detections[j]) > iouThreshold {
					assignments[j] = true
					r += detections[j].Row
					c += detections[j].Col
					s += detections[j].Scale
					q += detections[j].Q
					n++
				}
			}
			if n > 0 {
				clusters = append(clusters, Detection{r / n, c / n, s / n, q})
			}
		}
	}
	return clusters
}
ClusterDetections returns the intersection over union of multiple clusters.
We need to make this comparision to filter out multiple face detection regions.
package pigo
import (
	"bytes"
	"encoding/binary"
	"math"
	"sort"
	"unsafe"
)
type CascadeParams struct {
	MinSize     int
	MaxSize     int
	ShiftFactor float64
	ScaleFactor float64
	ImageParams
}
type ImageParams struct {
	Pixels []uint8
	Rows   int
	Cols   int
	Dim    int
}
type Pigo struct {
	treeDepth     uint32
	treeNum       uint32
	treeCodes     []int8
	treePred      []float32
	treeThreshold []float32
}
func NewPigo() *Pigo {
	return &Pigo{}
}
func (pg *Pigo) Unpack(packet []byte) (*Pigo, error) {
	var (
		treeDepth     uint32
		treeNum       uint32
		treeCodes     []int8
		treePred      []float32
		treeThreshold []float32
	)
	pos := 8
	buff := make([]byte, 4)
	dataView := bytes.NewBuffer(buff)
	_, err := dataView.Write([]byte{packet[pos+0], packet[pos+1], packet[pos+2], packet[pos+3]})
	if err != nil {
		return nil, err
	}
	if dataView.Len() > 0 {
		treeDepth = binary.LittleEndian.Uint32(packet[pos:])
		pos += 4
		_, err := dataView.Write([]byte{packet[pos+0], packet[pos+1], packet[pos+2], packet[pos+3]})
		if err != nil {
			return nil, err
		}
		treeNum = binary.LittleEndian.Uint32(packet[pos:])
		pos += 4
		for t := 0; t < int(treeNum); t++ {
			treeCodes = append(treeCodes, []int8{0, 0, 0, 0}...)
			code := packet[pos : pos+int(4*math.Pow(2, float64(treeDepth))-4)]
			signedCode := *(*[]int8)(unsafe.Pointer(&code))
			treeCodes = append(treeCodes, signedCode...)
			pos = pos + int(4*math.Pow(2, float64(treeDepth))-4)
			for i := 0; i < int(math.Pow(2, float64(treeDepth))); i++ {
				_, err := dataView.Write([]byte{packet[pos+0], packet[pos+1], packet[pos+2], packet[pos+3]})
				if err != nil {
					return nil, err
				}
				u32pred := binary.LittleEndian.Uint32(packet[pos:])
				f32pred := *(*float32)(unsafe.Pointer(&u32pred))
				treePred = append(treePred, f32pred)
				pos += 4
			}
			_, err := dataView.Write([]byte{packet[pos+0], packet[pos+1], packet[pos+2], packet[pos+3]})
			if err != nil {
				return nil, err
			}
			u32thr := binary.LittleEndian.Uint32(packet[pos:])
			f32thr := *(*float32)(unsafe.Pointer(&u32thr))
			treeThreshold = append(treeThreshold, f32thr)
			pos += 4
		}
	}
	return &Pigo{
		treeDepth,
		treeNum,
		treeCodes,
		treePred,
		treeThreshold,
	}, nil
}
func (pg *Pigo) classifyRegion(r, c, s int, pixels []uint8, dim int) float32 {
	var (
		root  int = 0
		out   float32
		pTree = int(math.Pow(2, float64(pg.treeDepth)))
	)
	r = r * 256
	c = c * 256
	for i := 0; i < int(pg.treeNum); i++ {
		var idx = 1
		for j := 0; j < int(pg.treeDepth); j++ {
			var pix = 0
			x1 := ((r+int(pg.treeCodes[root+4*idx+0])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+1])*s) >> 8)
			x2 := ((r+int(pg.treeCodes[root+4*idx+2])*s)>>8)*dim + ((c + int(pg.treeCodes[root+4*idx+3])*s) >> 8)
			px1 := pixels[x1]
			px2 := pixels[x2]
			if px1 <= px2 {
				pix = 1
			} else {
				pix = 0
			}
			idx = 2*idx + pix
		}
		out += pg.treePred[pTree*i+idx-pTree]
		if out <= pg.treeThreshold[i] {
			return -1.0
		} else {
			root += 4 * pTree
		}
	}
	return out - pg.treeThreshold[pg.treeNum-1]
}
func (pg *Pigo) classifyRotatedRegion(r, c, s int, a float64, nrows, ncols int, pixels []uint8, dim int) float32 {
	var (
		root  int = 0
		out   float32
		pTree = int(math.Pow(2, float64(pg.treeDepth)))
	)
	r = r * 65536
	c = c * 65536
	qCosTable := []int{256, 251, 236, 212, 181, 142, 97, 49, 0, -49, -97, -142, -181, -212, -236, -251, -256, -251, -236, -212, -181, -142, -97, -49, 0, 49, 97, 142, 181, 212, 236, 251, 256}
	qSinTable := []int{0, 49, 97, 142, 181, 212, 236, 251, 256, 251, 236, 212, 181, 142, 97, 49, 0, -49, -97, -142, -181, -212, -236, -251, -256, -251, -236, -212, -181, -142, -97, -49, 0}
	qsin := s * qSinTable[int(32.0*a)]  
	qcos := s * qCosTable[int(32.0*a)]  
	if (r+46341*s)/65536 >= nrows || (r-46341*s)/65536 < 0 || (c+46341*s)/65536 >= ncols || (c-46341*s)/65536 < 0 {
		return -1
	}
	for i := 0; i < int(pg.treeNum); i++ {
		var idx = 1
		for j := 0; j < int(pg.treeDepth); j++ {
			var pix = 0
			r1 := abs(r+qcos*int(pg.treeCodes[root+4*idx+0])-qsin*int(pg.treeCodes[root+4*idx+1])) >> 16
			c1 := abs(c+qsin*int(pg.treeCodes[root+4*idx+0])+qcos*int(pg.treeCodes[root+4*idx+1])) >> 16
			r2 := abs(r+qcos*int(pg.treeCodes[root+4*idx+2])-qsin*int(pg.treeCodes[root+4*idx+3])) >> 16
			c2 := abs(c+qsin*int(pg.treeCodes[root+4*idx+2])+qcos*int(pg.treeCodes[root+4*idx+3])) >> 16
			px1 := pixels[r1*dim+c1]
			px2 := pixels[r2*dim+c2]
			if px1 <= px2 {
				pix = 1
			} else {
				pix = 0
			}
			idx = 2*idx + pix
		}
		out += pg.treePred[pTree*i+idx-pTree]
		if out <= pg.treeThreshold[i] {
			return -1.0
		} else {
			root += 4 * pTree
		}
	}
	return out - pg.treeThreshold[pg.treeNum-1]
}
type Detection struct {
	Row   int
	Col   int
	Scale int
	Q     float32
}
func (pg *Pigo) RunCascade(cp CascadeParams, angle float64) []Detection {
	var detections []Detection
	var pixels = cp.Pixels
	var q float32
	scale := cp.MinSize
	for scale <= cp.MaxSize {
		step := int(math.Max(cp.ShiftFactor*float64(scale), 1))
		offset := (scale/2 + 1)
		for row := offset; row <= cp.Rows-offset; row += step {
			for col := offset; col <= cp.Cols-offset; col += step {
				if angle > 0.0 {
					if angle > 1.0 {
						angle = 1.0
					}
					q = pg.classifyRotatedRegion(row, col, scale, angle, cp.Rows, cp.Cols, pixels, cp.Dim)
				} else {
					q = pg.classifyRegion(row, col, scale, pixels, cp.Dim)
				}
				if q > 0.0 {
					detections = append(detections, Detection{row, col, scale, q})
				}
			}
		}
		scale = int(float64(scale) * cp.ScaleFactor)
	}
	return detections
}