detect(inputImageT) {
		let debugExtra = null;

		// Build gaussian pyramid images, two images per octave
		/** @type {Array<Array<tf.Tensor<tf.Rank>>} */
		const pyramidImagesT = [];
		//console.log("Detector::Building pyramid Images...");
		for (let i = 0; i < this.numOctaves; i++) {
			let image1T;
			let image2T;


			if (i === 0) {
				image1T = this._applyFilter(inputImageT);
			} else {
				image1T = this._downsampleBilinear(pyramidImagesT[i - 1][pyramidImagesT[i - 1].length - 1]);
			}
			image2T = this._applyFilter(image1T);
			pyramidImagesT.push([image1T, image2T]);
		}
		//console.log("Detector::Building dog images...");
		// Build difference-of-gaussian (dog) pyramid
		/** @type {tf.Tensor<tf.Rank>[]} */
		const dogPyramidImagesT = [];
		for (let i = 0; i < this.numOctaves; i++) {
			let dogImageT = this._differenceImageBinomial(pyramidImagesT[i][0], pyramidImagesT[i][1]);
			dogPyramidImagesT.push(dogImageT);
		}

		// find local maximum/minimum
		/** @type {tf.Tensor<tf.Rank>[]} */
		const extremasResultsT = [];
		for (let i = 1; i < this.numOctaves - 1; i++) {
			const extremasResultT = this._buildExtremas(dogPyramidImagesT[i - 1], dogPyramidImagesT[i], dogPyramidImagesT[i + 1]);
			extremasResultsT.push(extremasResultT);
		}

		// divide the input into N by N buckets, and for each bucket,
		// collect the top 5 most significant extrema across extremas in all scale level
		// result would be NUM_BUCKETS x NUM_FEATURES_PER_BUCKET extremas
		const prunedExtremasList = this._applyPrune(extremasResultsT);

		const prunedExtremasT = this._computeLocalization(prunedExtremasList, dogPyramidImagesT);

		// compute the orientation angle for each pruned extremas
		const extremaHistogramsT = this._computeOrientationHistograms(prunedExtremasT, pyramidImagesT);

		const smoothedHistogramsT = this._smoothHistograms(extremaHistogramsT);
		const extremaAnglesT = this._computeExtremaAngles(smoothedHistogramsT);

		// to compute freak descriptors, we first find the pixel value of 37 freak points for each extrema 
		const extremaFreaksT = this._computeExtremaFreak(pyramidImagesT, prunedExtremasT, extremaAnglesT);

		// compute the binary descriptors
		const freakDescriptorsT = this._computeFreakDescriptors(extremaFreaksT);

		const prunedExtremasArr = prunedExtremasT.arraySync();
		const extremaAnglesArr = extremaAnglesT.arraySync();
		const freakDescriptorsArr = freakDescriptorsT.arraySync();

		if (this.debugMode) {
			debugExtra = {
				pyramidImages: pyramidImagesT.map((ts) => ts.map((t) => t.arraySync())),
				dogPyramidImages: dogPyramidImagesT.map((t) => t ? t.arraySync() : null),
				extremasResults: extremasResultsT.map((t) => t.arraySync()),
				extremaAngles: extremaAnglesT.arraySync(),
				prunedExtremas: prunedExtremasList,
				localizedExtremas: prunedExtremasT.arraySync(),
			}
		}

		pyramidImagesT.forEach((ts) => ts.forEach((t) => t.dispose()));
		dogPyramidImagesT.forEach((t) => t && t.dispose());
		extremasResultsT.forEach((t) => t.dispose());
		prunedExtremasT.dispose();
		extremaHistogramsT.dispose();
		smoothedHistogramsT.dispose();
		extremaAnglesT.dispose();
		extremaFreaksT.dispose();
		freakDescriptorsT.dispose();

		const featurePoints = [];

		for (let i = 0; i < prunedExtremasArr.length; i++) {
			if (prunedExtremasArr[i][0] == 0) continue;

			const descriptors = [];
			for (let m = 0; m < freakDescriptorsArr[i].length; m += 4) {
				const v1 = freakDescriptorsArr[i][m];
				const v2 = freakDescriptorsArr[i][m + 1];
				const v3 = freakDescriptorsArr[i][m + 2];
				const v4 = freakDescriptorsArr[i][m + 3];

				let combined = v1 * 16777216 + v2 * 65536 + v3 * 256 + v4;
				//if (m === freakDescriptorsArr[i].length-4) { // last one, legacy reason
				//  combined /= 32;
				//}
				descriptors.push(combined);
			}

			const octave = prunedExtremasArr[i][1];
			const y = prunedExtremasArr[i][2];
			const x = prunedExtremasArr[i][3];
			const originalX = x * Math.pow(2, octave) + Math.pow(2, (octave - 1)) - 0.5;
			const originalY = y * Math.pow(2, octave) + Math.pow(2, (octave - 1)) - 0.5;
			const scale = Math.pow(2, octave);

			featurePoints.push({
				maxima: prunedExtremasArr[i][0] > 0,
				x: originalX,
				y: originalY,
				scale: scale,
				angle: extremaAnglesArr[i],
				descriptors: descriptors
			});
		}
		//console.log("feature points", featurePoints);
		//console.table(tf.memory());
		return { featurePoints, debugExtra };
	}
@param {tf.Tensor<tf.Rank>} inputImageT 
@returns