unit fft;

{
This file contains a Pascal language implementation of the 
Fast Hartley Transform algorithm which is covered under
United States Patent Number 4,646,256.

This code may therefore be freely used and distributed only
under the following conditions:

		1)  This header is included in every copy of the code; and
		2)  The code is used for noncommercial research purposes only.

 Firms using this code for commercial purposes will be infringing a United
 States patent and should contact the

					Office of Technology Licensing
					Stanford University
					857 Serra Street, 2nd Floor
					Stanford, CA   94305-6225
					(415) 723 0651

This implementation is based on Pascal code contibuted
by Arlo Reeves (arlo@apple.com).
}

interface
	uses
		Types, Memory, QuickDraw, QuickDrawText, Packages, Menus, Events, Fonts, 
		Scrap, ToolUtils, Resources, Errors, Palettes, StandardFile, Windows,
		Controls, TextEdit, Files, Dialogs, TextUtils, Finder, MixedMode, Processes,
		globals, Utilities, Graphics, Filters, Analysis;

	procedure doFFT(fftKind: fftType);
	procedure DisplayPowerSpectrum(x: rImagePtr);
	function   isPowerOf2 (x: LongInt): boolean;
	procedure SetFFTWindowName(doInverse: boolean);
	procedure RedisplayPowerSpectrum;
	procedure doSwapQuadrants;
	function isFFT: boolean;
	procedure ShowFFTValues (hloc, vloc, ivalue: LongInt);


implementation

	const
		UpdateTicks = 10; {Show progress 6 times/sec}
		
	var
		AbortFFT: boolean;
		C, S: rLinePtr;


	function log2 (x: LongInt): LongInt;
		var
			count: LongInt;
	begin
		count := 15;
		while not BTST(x, count) do
			count := count - 1;
		log2 := count;
	end;
	

	function BitRevX (x, bitlen: LongInt): LongInt;
		var
			i: LongInt;
			temp: longint;
	begin
		temp := 0;
		for i := 0 to bitlen do
			if BTST(x, i) then
				BSET(temp, bitlen - i - 1);
		BitRevX := LoWord(temp);
	end;
	

	procedure BitRevRArr (x: rLinePtr; bitlen, maxN: LongInt);
		var
			i: LongInt;
			tempArr: rLineType;
	begin
		for i := 0 to maxN - 1 do
			tempArr[i] := x^[BitRevX(i, bitlen)];
		BlockMove(@tempArr, x, maxN * SizeOf(real));
	end;
	

	procedure transposeR (x: rImagePtr; maxN: LongInt);
		var
			r, c: LongInt;
			rTemp: real;
	begin
		for r := 0 to maxN - 1 do
			for c := r to maxN - 1 do
				if r <> c then
					begin
						rTemp := x^[ r * maxN + c];
						x^[ r * maxN + c] := x^[c * maxN + r];
						x^[c * maxN + r] := rTemp;
					end;
	end;
	

	procedure FHTps (r, maxN: LongInt; inMat: rImagePtr;  var outArr: rLineType);
{ Power Spectrum of one row from 2D Hartley Transform }
		var
			c, base: LongInt;
	begin
		base := r * maxN;
		for c := 0 to maxN - 1 do
			outArr[c] := (sqr(inMat^[base + c]) + sqr(inMat^[((maxN - r) mod maxN) * maxN + (maxN - c) mod maxN])) / 2;
	end;


	procedure dfht3 (x: rLinePtr; inverse: boolean; maxN: LongInt);
	{ an optimized real FHT }
		var
			i, stage, gpNum, gpIndex, gpSize, numGps, Nlog2: LongInt;
			bfNum, numBfs: LongInt;
			Ad0, Ad1, Ad2, Ad3, Ad4, CSAd: LongInt;
			rt1, rt2, rt3, rt4: real;
	begin
		Nlog2 := log2(maxN);
		BitRevRArr(x, Nlog2, maxN);			{ bitReverse the input array }

		gpSize := 2;						{ first & second stages - do radix 4 butterflies once thru }
		numGps := maxN div 4;
		for gpNum := 0 to numGps - 1 do
			begin
				Ad1 := gpNum * 4;
				Ad2 := Ad1 + 1;
				Ad3 := Ad1 + gpSize;
				Ad4 := Ad2 + gpSize;
				rt1 := x^[Ad1] + x^[Ad2];			{ a + b }
				rt2 := x^[Ad1] - x^[Ad2];			{ a - b }
				rt3 := x^[Ad3] + x^[Ad4];			{ c + d }
				rt4 := x^[Ad3] - x^[Ad4];			{ c - d }
				x^[Ad1] := rt1 + rt3;				{ a + b + (c + d ) }
				x^[Ad2] := rt2 + rt4;				{ a - b + (c - d ) }
				x^[Ad3] := rt1 - rt3;				{ a + b - (c + d ) }
				x^[Ad4] := rt2 - rt4;				{ a - b - (c - d ) }
			end;

		if Nlog2 > 2 then
			begin						{ third + stages computed here }
				gpSize := 4;
				numBfs := 2;
				numGps := numGps div 2;
				for stage := 2 to Nlog2 - 1 do
					begin
						for gpNum := 0 to numGps - 1 do
							begin
								Ad0 := gpNum * gpSize * 2;
								Ad1 := Ad0;							{ 1st butterfly is different from others - no mults needed }
								Ad2 := Ad1 + gpSize;
								Ad3 := Ad1 + gpSize div 2;
								Ad4 := Ad3 + gpSize;
								rt1 := x^[Ad1];
								x^[Ad1] := x^[Ad1] + x^[Ad2];
								x^[Ad2] := rt1 - x^[Ad2];
								rt1 := x^[Ad3];
								x^[Ad3] := x^[Ad3] + x^[Ad4];
								x^[Ad4] := rt1 - x^[Ad4];
								for bfNum := 1 to numBfs - 1 do
									begin		{ subsequent BF's dealt with together }
										Ad1 := bfNum + Ad0;
										Ad2 := Ad1 + gpSize;
										Ad3 := gpSize - bfNum + Ad0;
										Ad4 := Ad3 + gpSize;

										CSAd := bfNum * numGps;
										rt1 := x^[Ad2] * C^[CSAd] + x^[Ad4] * S^[CSAd];
										rt2 := x^[Ad4] * C^[CSAd] - x^[Ad2] * S^[CSAd];

										x^[Ad2] := x^[Ad1] - rt1;
										x^[Ad1] := x^[Ad1] + rt1;
										x^[Ad4] := x^[Ad3] + rt2;
										x^[Ad3] := x^[Ad3] - rt2;

									end; 		{ for bfNum := 0 to ... }
							end;			{ for gpNum := 0 to ... }
						gpSize := gpSize * 2;
						numBfs := numBfs * 2;
						numGps := numGps div 2;
					end;
			end;		{ if }

		if inverse then
			for i := 0 to maxN - 1 do
				x^[i] := x^[i] / maxN;
	end;


	procedure rc2Dfht (x: rImagePtr; inverse: boolean; maxN: LongInt);
{ Row-column 2D FHT }
		var
			row, col, mRow, mCol, NextTicks: LongInt;
			A, B, C, D, E: real;
			theRow: rLinePtr;
	begin
		NextTicks := TickCount + UpdateTicks;
		for row := 0 to maxN - 1 do begin
			theRow := rLinePtr(ord4(x) + row * maxN * SizeOf(real));
			dfht3(theRow, inverse, maxN);
			if TickCount > nextTicks then begin
				UpdateMeter(round(row/maxN * 50.0), 'Computing FHT');
				nextTicks := TickCount + updateTicks;
				if CommandPeriod then begin
						beep;
						AbortFFT := true;
						exit(rc2Dfht)
					end;
			end;
		end;
		transposeR(x, maxN);
		for row := 0 to maxN - 1 do begin
			theRow := rLinePtr(ord4(x) + row * maxN * SizeOf(real));
			dfht3(theRow, inverse, maxN);
			if TickCount > nextTicks then begin
				UpdateMeter(round(row/maxN * 50.0) + 50, 'Computing FHT');
				nextTicks := TickCount + updateTicks;
				if CommandPeriod then begin
						beep;
						AbortFFT := true;
						exit(rc2Dfht)
					end;
			end;
		end;
		transposeR(x, maxN);
		for row := 0 to maxN div 2 do 			{ Now calculate actual Hartley transform }
			for col := 0 to maxN div 2 do
				begin
					mRow := (maxN - row) mod maxN;
					mCol := (maxN - col) mod maxN;
					A := x^[row * maxN + col];						{ see Bracewell, 'Fast 2D Hartley Transf.' IEEE Procs. 9/86 }
					B := x^[mRow * maxN + col];
					C := x^[row * maxN + mCol];
					D := x^[mRow * maxN + mCol];
					E := ((A + D) - (B + C)) / 2;
					x^[row * maxN + col] := A - E;
					x^[mRow * maxN + col] := B + E;
					x^[row * maxN + mCol] := C + E;
					x^[mRow * maxN + mCol] := D - E;
				end;
		UpdateMeter(-1, 'Hide Meter');
	end;
	
	
procedure SwapQuadrants;
{Swap quadrants 1 and 3 and quadrants 2 and 4 so the}
{power spectrum origin is at the center of the image.}
{2 1}
{3 4}
var
	row, col, halfMaxN, tmp, maxN: LongInt;
	line1, line2: LineType;
begin
	maxN := info^.PixelsPerLine;
	halfMaxN := maxN div 2;
	for row:= 0 to halfMaxN -1 do begin
		GetLine(0, row, maxN, line1);
		GetLine(0, row + halfMaxN, maxN, line2);
		for col := 0 to halfMaxN -1 do begin
			tmp := line1[col];
			line1[col] := line1[col + halfMaxN];
			line1[col + halfMaxN] := tmp;
			tmp := line2[col];
			line2[col] := line2[col + halfMaxN];
			line2[col + halfMaxN] := tmp;
		end;
		PutLine(0, row, maxN, line2);
		PutLine(0, row + halfMaxN, maxN, line1);
	end;
end;


	
	procedure DisplayRealImage(x: rImagePtr);
		var
			row, col, i, base, maxN: LongInt;
			r, min, max, scale: real;
			line: lineType;
	begin
		maxN := info^.PixelsPerLine;
		min := 1e99;
		max := -1e99;
		i := 1;
		for row := 0 to maxN - 1 do begin
			base := row * maxN;
			for col := 0 to maxN - 1 do begin
					r := x^[base + col];
					if r < min then min := r;
					if r > max then max := r;
			end;
		end;
		scale := 253.0 / (max - min);
		for row := 0 to maxN - 1 do begin
				base := row * maxN;
				for col := 0 to maxN - 1 do begin
						r := x^[base + col];
						line[col] := round((r - min) * scale) + 1;
				end;
				PutLine(0, row, maxN, line);
			end;
	end;


	procedure DisplayPSUsingBuffer(fht, ps: rImagePtr; var rLine: rLineType);
	var
		row, col, base, nextTicks, maxN: LongInt;
		r, min, max, scale: real;
		line: lineType;
		
	begin
		maxN := info^.PixelsPerLine;
		nextTicks := TickCount + updateTicks;
		min := 1e99;
		max := -1e99;
		for row := 0 to maxN - 1 do begin
			FHTps (row, maxN, fht, rLine);
			base := row * maxN;
			for col := 0 to maxN - 1 do begin
					r := rLine[col];
					if r < min then min := r;
					if r > max then max := r;
					ps^[base + col] := r;
			end;
			if TickCount > nextTicks then begin
				UpdateMeter(round(row/maxN * 80.0), 'Computing Power Spectrum');
				nextTicks := TickCount + updateTicks;
				if CommandPeriod then begin
						beep;
						AbortFFT := true;
						exit(DisplayPSUsingBuffer)
					end;
			end;
		end;
		if min < 1.0 then
			min := 0.0
		else
			min := ln(min);
		max := ln(max);
		scale := 253.0 / (max - min);
		for row := 0 to maxN - 1 do begin
				base := row * maxN;
				for col := 0 to maxN - 1 do begin
						r := ps^[base + col];
						if r < 1.0 then
							r := 0.0
						else
							r := ln(r);
						line[col] := round((r - min) * scale) + 1;
				end;
				PutLine(0, row, maxN, line);
				if TickCount > nextTicks then begin
					UpdateMeter(round(row/maxN * 20.0 ) + 80, 'Computing Power Spectrum');
					nextTicks := TickCount + updateTicks;
					if CommandPeriod then begin
							beep;
							AbortFFT := true;
							exit(DisplayPSUsingBuffer)
						end;
				end;
			end;
		SwapQuadrants;
		UpdateMeter(-1, 'Hide Meter');
	end;	
	
	
	procedure DisplayPowerSpectrum(fht: rImagePtr);
	var
		row, col, nextTicks, maxN: LongInt;
		r, min, max, scale: real;
		line: lineType;
		rLine: rLineType;
		tempH: handle;
		ps: rImagePtr;
	begin
		maxN := info^.PixelsPerLine;
		tempH := GetBigHandle(maxN * maxN * SizeOf(real));
		if tempH <> nil then begin
			hlock(tempH);
			ps := rImagePtr(tempH^);
			DisplayPSUsingBuffer(fht, ps, rLine);
			hunlock(tempH);
			DisposeHandle(tempH);
			exit(DisplayPowerSpectrum);
		end;
		min := 1e99;
		max := -1e99;
		nextTicks := TickCount + updateTicks;
		for row := 0 to maxN - 1 do begin
			FHTps (row, maxN, fht, rLine);
			for col := 0 to maxN - 1 do begin
					r := rLine[col];
					if r < min then min := r;
					if r > max then max := r;
			end;
			if TickCount > nextTicks then begin
				UpdateMeter(round(row/maxN * 35.0), 'Computing Power Spectrum');
				nextTicks := TickCount + updateTicks;
				if CommandPeriod then begin
						beep;
						AbortFFT := true;
						exit(DisplayPowerSpectrum)
					end;
			end;
		end;
		if min < 1.0 then
			min := 0.0
		else
			min := ln(min);
		max := ln(max);
		scale := 253.0 / (max - min);
		for row := 0 to maxN - 1 do begin
				FHTps (row, maxN, fht, rLine);
				for col := 0 to maxN - 1 do begin
						r := rLine[col];
						if r < 1.0 then
							r := 0.0
						else
							r := ln(r);
						line[col] := round((r - min) * scale) + 1;
				end;
				PutLine(0, row, maxN, line);
				if TickCount > nextTicks then begin
					UpdateMeter(round(row/maxN * 65.0 ) + 35, 'Computing Power Spectrum');
					nextTicks := TickCount + updateTicks;
					if CommandPeriod then begin
							beep;
							AbortFFT := true;
							exit(DisplayPowerSpectrum)
						end;
				end;
			end;
		SwapQuadrants;
		UpdateMeter(-1, 'Hide Meter');
	end;


procedure ConvertToReal;
var
	row, col, i, sum, base: LongInt;
	width, height: LongInt;
	mean, pixelCount: real;
	line: LineType;
	DataP: rImagePtr;
begin
	with info^ do begin
		width := pixelsPerLine;
		height := nLines;
		hlock(DataH);
		DataP := rImagePtr(DataH^);
	end;
	UpdateMeter(0, 'Converting to Real');
	{
	GetRectHistogram;
	sum := 0;
	pixelCount := width * height;
	for i := 0 to 255 do
		sum := sum + histogram[i] * i;
	mean := sum / pixelCount;
	}
	for row:= 0 to height - 1 do begin
		GetLine(0, row, width, line);
		base := row * width;
		for col := 0 to width - 1 do
			DataP^[base + col] := line[col] {- mean};
	end;
	hunlock(info^.DataH);
end;


function isPowerOf2 (x: LongInt): boolean;
var
	i, sum: integer;
begin
sum := 0;
x := abs(x);
for i := 0 to 15 do
	sum := sum + ord(BTST(x, i));
IsPowerOf2 := (sum <= 1);
end;


function PowerOf2Size: boolean;
var
	width, height: LongInt;

	procedure fail;
	begin
 		PutError('A square, power of two size image or selection (128x128, 256x256, etc.) required.');
 		AbortMacro;
 		PowerOf2Size := false;
 		exit(PowerOf2Size);
	end;

begin
	with info^ do begin
		if info = noInfo then
			fail;
		width := pixelsPerLine;
		height := nLines;
		if RoiShowing and (roiType = rectRoi) then with roiRect do begin
			width := right - left;
			height := bottom - top;
		end;
		if not isPowerOf2(width) or (width <> height) then
			fail;
		if width < 4 then begin
				PutMessage('Sequence Length must be >= 4.');
				PowerOf2Size := true;
				exit(PowerOf2Size);
			end;
		PowerOf2Size := true;
	end;
end;


function MakeSinCosTables(maxN: LongInt): boolean;
var
	i: LongInt;
	theta, dTheta: real;
begin
	C := rLinePtr(NewPtr(SizeOf(rLineType)));
	S := rLinePtr(NewPtr(SizeOf(rLineType)));
	if (C = nil) or (S = nil) then begin
		MakeSinCosTables := false;
		PutError('Out of Memory');
		exit(MakeSinCosTables);
	end;
	theta := 0;
	dTheta := 2 * pi / maxN;
	for i := 0 to maxN div 4 - 1 do
		begin
			C^[i] := cos(theta);
			S^[i] := sin(theta);
			theta := theta + dTheta;
		end;
		MakeSinCosTables := true;
end;


function MakeRealImage: boolean;
var
	TempH: handle;
	maxN: LongInt;
begin
	maxN := info^.PixelsPerLine;
	tempH := GetBigHandle(maxN * maxN * SizeOf(real));
	if TempH = nil then begin
		PutMemoryAlert;
		MakeRealImage := false;
		exit(MakeRealImage);
	end;
	if not Duplicate(StringOf('FFT ', nPics + 1:1), false) then begin
		DisposeHandle(TempH);
		exit(MakeRealImage);
	end;
	UpdatePicWindow;
	info^.DataH := tempH;
	ConvertToReal;
	UpdateTitleBar;
	MakeRealImage := true;
end;


procedure SetFFTWindowName(doInverse: boolean);
begin
	with info^ do begin
		if doInverse then
			title := StringOf('Inverse FFT ', picNum:1)
		else
			title := StringOf('FFT ', picNum:1);
		UpdateWindowsMenuItem;
		UpdateTitleBar;
	end;
end;


procedure ApplyFilter(rData: rImagePtr);
var
	row, col, width, height, base, i: LongInt;
	line: LineType;
	passMode: boolean;
	t:FateTable;
begin
	SwapQuadrants;
	with info^ do begin
		width := pixelsPerLine;
		height := nLines;
	end;
	for row:= 0 to height - 1 do begin
		GetLine(0, row, width, line);
		base := row * width;
		for col := 0 to width - 1 do
			rData^[base + col] := line[col]/255.0 * rData^[base + col];
	end;
end;


procedure doMasking(rData: rImagePtr);
var
	row, col, width, height, base, i: LongInt;
	line: LineType;
	passMode: boolean;
	t:FateTable;
begin
	GetRectHistogram;
	if (histogram[0] = 0) and (histogram[255] = 0) then
		exit(doMasking);
	UpdateMeter(0, 'Masking');
	passMode := histogram[255] <> 0;
	if passMode then
		ChangeValues(0,254,0)
	else
		ChangeValues(1,255,255);
	for i := 1 to 3 do
		Filter(UnweightedAvg, 0, t);
	UpdatePicWindow;
	ApplyFilter(rData);
end;


	procedure doFFT(fftKind: fftType);
	var
		startTicks, maxN: LongInt;
		trect: rect;
		RealData: rImagePtr;
		doInverse: boolean;
	begin
			doInverse := fftKind <> ForewardFFT;
			if not PowerOf2Size then
				exit(doFFT);
			startTicks := tickCount;
			if info^.DataH = nil then begin
				if doInverse then begin
					PutError('A real image is required to do an inverse transform.');
 					AbortMacro;
					exit(doFFT);
				end;
				if not MakeRealImage then begin
 					AbortMacro;
					exit(doFFT);
				end
			end else begin
				KillRoi;
				SetFFTWindowName(doInverse);
			end;
			hlock(info^.DataH);
			RealData := rImagePtr(info^.DataH^);
			ShowWatch;
			maxN := info^.PixelsPerLine;
			if not MakeSinCosTables(maxN) then
				exit(doFFT);
			AbortFFT := false;
			ShowMessage(CmdPeriodToStop);
			if doInverse then begin
				if fftKind = InverseFFTWithMask then 
					doMasking(RealData)
				else if fftKind = InverseFFTWithFilter then
					ApplyFilter(RealData);
				rc2DFHT(RealData, true, maxN);
				if not AbortFFT then
					DisplayRealImage(RealData);
			end else begin
				rc2DFHT(RealData, false, maxN);
				if not AbortFFT then
					DisplayPowerSpectrum(RealData);
			end;
			if AbortFFT then
				UpdateMeter(-1, 'Hide');
			hunlock(info^.dataH);
			UpdatePicWindow;
			SetRect(trect, 0, 0, maxN, maxN);
			ShowTime(startTicks, trect, '');
			UpdateWindowsMenuItem;
			DisposePtr(ptr(C));
			DisposePtr(ptr(S));
	end;
	

function isFFT: boolean;
begin
	isFFT := false;
	with info^ do
		if DataH <> nil then
			if pos('FFT', title) = 1 then
				isFFT := true;
end;


procedure RedisplayPowerSpectrum;
	var
		rData: rImagePtr;
	begin
			if info = noInfo then
				exit(RedisplayPowerSpectrum);
			KillRoi;
			if not PowerOf2Size then
				exit(RedisplayPowerSpectrum);
			if not isFFT then begin
					PutError('Real frequency domain image required.');
					exit(RedisplayPowerSpectrum);
				end;
			hlock(info^.DataH);
			rData := rImagePtr(info^.DataH^);
			DisplayPowerSpectrum(rData);
			hunlock(info^.dataH);
			UpdatePicWindow;
	end;


	Procedure doSwapQuadrants;
	begin
		if info = noInfo then
			exit(doSwapQuadrants);
		KillRoi;
		if not PowerOf2Size then
			exit(doSwapQuadrants);
		SetupUndo;
		WhatToUndo := UndoEdit;
		SwapQuadrants;
		UpdatePicWindow;
	end;
	
	
	function arctan2 (x, y: extended): extended;
{ returns angle in the correct quadrant }
	begin
		if x = 0 then
			x := 1E-30; { Could be improved }
		if x > 0 then
			if y >= 0 then
				arctan2 := arctan(y / x)
			else
				arctan2 := arctan(y / x) + 2 * pi
		else
			arctan2 := arctan(y / x) + pi;
	end;
	

	procedure ShowFFTValues (hloc, vloc, ivalue: LongInt);
		var
			tPort: GrafPtr;
			hstart, vstart: integer;
			r, theta, center: extended;
	begin
		with info^ do
			begin
				hstart := InfoHStart;
				vstart := InfoVStart;
				GetPort(tPort);
				SetPort(InfoWindow);
				TextSize(9);
				TextFont(Monaco);
				TextMode(SrcCopy);
				if hloc < 0 then
					hloc := -hloc;
				center := pixelsPerLine div 2;
				r := sqrt(sqr(hloc - center) + sqr(vloc - center));
				theta := arctan2(hloc - center, center - vloc);
				theta := theta * 180 / pi;
				MoveTo(xValueLoc, vstart);
				if SpatiallyCalibrated then begin
						DrawReal(pixelsPerLine / r / xScale, 6, 2);
						DrawString(xUnit);
						DrawString('/c ');
						DrawString('(');
						DrawReal(hloc - center, 4, 0);
						DrawString(')');
					end else begin
						DrawReal(pixelsPerLine / r, 6, 2);
						DrawString('p/c  ');
						DrawString('(');
						DrawReal(hloc - center, 4, 0);
						DrawString(')');
					end;
				DrawString('    ');
				vloc := PicRect.bottom - vloc - 1;
				if vloc < 0 then
					vloc := -vloc;
				MoveTo(yValueLoc, vstart + 10);
				DrawReal(theta, 6, 2);
				TextMode(srcOr);
				DrawString('¡    ');
				TextMode(srcCopy);
				DrawString('(');
				DrawReal(vloc - center + 1, 4, 0);
				DrawString(')');
				DrawString('    ');
				MoveTo(zValueLoc, vstart + 20);
				if fit <> uncalibrated then
					begin
						DrawReal(cvalue[ivalue], 6, 2);
						DrawString(' (');
						DrawLong(ivalue);
						DrawString(')');
					end
				else
					DrawLong(ivalue);
				DrawString('    ');
				SetPort(tPort);
			end;
	end;


end. {fft Unit}
