ImageFFTCommandsImpl.mesa
Copyright (c) 1984, Xerox Corporation. All right reserved.
Michael Plass, July 19, 1984 9:33:16 pm PDT
DIRECTORY Basics, BasicTime, FS, Convert, Commander, ImageFFT, AIS, ImagerPixelMaps, Rope, IO, ImagerAISUtil, Process, ImagerFrameBuffer, Complex, RealFns, ImagerPixelRow, Real, Seq, Imager, ImagerBasic;
ImageFFTCommandsImpl: CEDAR PROGRAM
IMPORTS Basics, FS, Convert, Commander, ImageFFT, AIS, ImagerPixelMaps, Rope, IO, ImagerAISUtil, Process, ImagerFrameBuffer, RealFns, ImagerPixelRow, Real, Complex, Imager ~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
CachedFFTName: PROC [aisFileName: ROPE] RETURNS [fftName: ROPE] ~ TRUSTED {
cp: FS.ComponentPositions;
fullFName: ROPE;
created: BasicTime.GMT;
stream: IO.STREAMIO.ROS[];
[fullFName: fullFName, created: created] ← FS.FileInfo[aisFileName];
cp ← FS.ExpandName[fullFName].cp;
stream.PutF["%g%g.FFT", IO.rope[fullFName.Substr[cp.base.start, cp.base.length]], IO.int[LOOPHOLE[created]]];
fftName ← stream.RopeFromROS[close: TRUE];
};
StorePixelMap: PROC [aisFileName: ROPE, source: ImagerPixelMaps.PixelMap, bitmap: BOOLEANTRUE, comment: ROPE ← NIL] ~ TRUSTED {
output: AIS.FRef ← AIS.CreateFile[name: aisFileName, raster: NEW[AIS.RasterPart ← [
scanCount: source.sSize,
scanLength: source.fSize,
scanMode: rd,
bitsPerPixel: IF source.refRep.lgBitsPerPixel = 0 AND bitmap THEN 0 ELSE Basics.BITSHIFT[1, source.refRep.lgBitsPerPixel],
linesPerBlock: -1,
paddingPerBlock: 65535
]]];
outputWindow: AIS.WRef ← AIS.OpenWindow[output];
lineMap: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[source.refRep.lgBitsPerPixel, [source.sOrigin+source.sMin, source.fOrigin+source.fMin, 1, source.fSize]];
lineBufferDesc: AIS.Buffer ← [length: lineMap.refRep.words, addr: lineMap.refRep.pointer];
AIS.WriteComment[output, comment];
FOR i: NAT IN [0..source.sSize) DO
lineMap.Clear;
lineMap.Transfer[source];
lineMap.sOrigin ← lineMap.sOrigin + 1;
AIS.UnsafeWriteLine[outputWindow, lineBufferDesc, i];
ENDLOOP;
AIS.CloseFile[output];
};
GetAISComment: PROC [aisFileName: ROPE] RETURNS [comment: ROPE] ~ {
ais: AIS.FRef ← AIS.OpenFile[name: aisFileName];
comment ← AIS.ReadComment[ais];
AIS.CloseFile[ais];
};
Padded: PROC [pixelMap: ImagerPixelMaps.PixelMap] RETURNS [padded: ImagerPixelMaps.PixelMap] ~ {
d: INTMAX[pixelMap.fSize, pixelMap.sSize];
n: INT ← 1;
WHILE n < d DO n ← n+n ENDLOOP;
padded ← ImagerPixelMaps.Create[pixelMap.refRep.lgBitsPerPixel, [0, 0, n, n]];
padded.Clear;
padded.Transfer[pixelMap];
IF pixelMap.refRep.lgBitsPerPixel = 0 THEN padded.Fill[[0, 0, n, n], 1, [xor, null]];
};
GetTransform: PROC [inputName: ROPE, msg: IO.STREAMNIL] RETURNS [ImageFFT.Image] ~ {
fftName: ROPE ← CachedFFTName[inputName];
original: ImagerPixelMaps.PixelMap ← ImagerAISUtil.PixelMapFromAIS[inputName].pixelMap;
padded: ImagerPixelMaps.PixelMap ← Padded[original];
result: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[3, original.Window];
image: ImageFFT.Image ← NIL;
image ← ImageFFT.Load[fftName ! FS.Error => CONTINUE];
IF image = NIL THEN {
image ← ImageFFT.FromPixelMap[padded];
IF msg # NIL THEN msg.PutF["Generating FFT of %g . . . ", IO.rope[inputName]];
ImageFFT.Transform[image, FALSE];
ImageFFT.Store[image, fftName];
IF msg # NIL THEN msg.PutF["%g written.\n", IO.rope[fftName]];
};
RETURN [image]
};
FilterAIS: PROC [outputName, inputName: ROPE, filters: LIST OF FilterRep, msg: IO.STREAMNIL, comment: ROPE] ~ {
fftName: ROPE ← CachedFFTName[inputName];
original: ImagerPixelMaps.PixelMap ← ImagerAISUtil.PixelMapFromAIS[inputName].pixelMap;
padded: ImagerPixelMaps.PixelMap ← Padded[original];
result: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[3, original.Window];
image: ImageFFT.Image ← NIL;
aisloc: INT ← Rope.Find[outputName, ".ais", 0, FALSE];
imageName: ROPEIF aisloc = -1 THEN outputName.Concat[".image"] ELSE outputName.Replace[aisloc, 4, ".image"];
comment ← GetAISComment[inputName].Cat["; ", comment];
image ← ImageFFT.Load[fftName ! FS.Error => CONTINUE];
IF image = NIL THEN {
image ← ImageFFT.FromPixelMap[padded];
IF msg # NIL THEN msg.PutF["Generating FFT of %g . . . ", IO.rope[inputName]];
ImageFFT.Transform[image, FALSE];
ImageFFT.Store[image, fftName];
IF msg # NIL THEN msg.PutF["%g written.\n", IO.rope[fftName]];
};
IF msg # NIL THEN msg.PutRope["Filtering . . . "];
ApplyFilters[image, filters];
IF msg # NIL THEN msg.PutRope["Applying inverse transform . . . "];
ImageFFT.Transform[image, TRUE];
ImageFFT.TransferToPixels[result, image, 255];
IF msg # NIL THEN msg.PutF["Writing %g . . . ", IO.rope[imageName]];
ImageFFT.Store[image, imageName];
IF msg # NIL THEN msg.PutF["Writing %g . . . ", IO.rope[outputName]];
image ← ImageFFT.Destroy[image];
StorePixelMap[outputName, result, FALSE, comment];
};
Break: PROC [char: CHAR] RETURNS [IO.CharClass] ~ {
IF char = '← OR char = '; THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
GetToken: PROC [stream: IO.STREAM] RETURNS [rope: ROPENIL] = {
rope ← stream.GetTokenRope[Break ! IO.EndOfStream => CONTINUE].token;
};
GetInt: PROC [stream: IO.STREAM] RETURNS [int: INT] = {
int ← INT.FIRST;
int ← stream.GetInt[! IO.EndOfStream, IO.Error => CONTINUE];
};
ColorDisplay: PROC [sSize, fSize: NAT] RETURNS [colorDisplay: ImagerPixelMaps.PixelMap] ~ TRUSTED {
colorDisplay ← ImagerFrameBuffer.GrayScaleDisplay8[];
colorDisplay ← colorDisplay.ShiftMap[(sSize-colorDisplay.sSize)/2, (fSize-colorDisplay.fSize)/2].Clip[[0, 0, sSize+1, fSize+1]];
};
AbsBound: PROC [z: Complex.Vec] RETURNS [REAL] ~ INLINE {
x: REALABS[z.x];
y: REALABS[z.y];
RETURN [MIN[MAX[x, y], 1.414214*(x+y)]]
};
dispScale: REAL ← 40;
dispOffset: REAL ← 40;
Show: PROC [a: ImageFFT.Image] ~ {
height: NAT ← ImageFFT.Height[a];
width: NAT ← ImageFFT.Width[a];
cd: ImagerPixelMaps.PixelMap ← ColorDisplay[height, width];
row: ImagerPixelRow.PixelRow ← ImagerPixelRow.CreatePixelRow[height/2, width/2, width];
FOR p: ImageFFT.Image ← a, p.rest UNTIL p=NIL DO
FOR j: NAT IN [0..width) DO
mag: REAL ← AbsBound[p.first[j]];
ln: REAL ← RealFns.Ln[mag];
pixel: INT ← Real.RoundLI[ln*dispScale+dispOffset];
row[j] ← MIN[MAX[pixel,0],255];
ENDLOOP;
row.sOrigin ← row.sOrigin - height;
row.fOrigin ← row.fOrigin - width;
ImagerPixelRow.StorePixelRow[row, cd];
row.fOrigin ← row.fOrigin + width;
ImagerPixelRow.StorePixelRow[row, cd];
row.sOrigin ← row.sOrigin + height;
row.fOrigin ← row.fOrigin - width;
ImagerPixelRow.StorePixelRow[row, cd];
row.fOrigin ← row.fOrigin + width;
ImagerPixelRow.StorePixelRow[row, cd];
row.sOrigin ← row.sOrigin + 1;
ENDLOOP;
};
SquareLowPassFilter: FilterProc ~ {
RETURN [IF ABS[z.x] > param[0] OR ABS[z.y] > param[0] THEN 0.0 ELSE 1.0]
};
SpotFilter: FilterProc ~ {
radius: REAL ~ param[2];
m: REAL ~ param[3];
diam: REAL ~ 2*radius;
z ← [ABS[z.x-param[0]], ABS[z.y-param[1]]];
IF z.x > diam OR z.y > diam THEN RETURN [1.0]
ELSE {
d: REAL ← Complex.Abs[z] / radius;
IF d > 2 THEN RETURN [1.0];
d ← (d-2)*(d+2);
d ← d*d;
d ← d/16*(m-1) + 1;
RETURN [d]
};
};
Sqr: PROC [r: REAL] RETURNS [REAL] ~ INLINE {RETURN [r*r]};
BandBoostFilter: FilterProc ~ {
fuzz: REAL ~ param[2]/2;
m: REAL ~ param[3];
d: REAL ← Complex.SqrAbs[z];
IF d <= Sqr[param[0]-fuzz] OR d >= Sqr[param[1]+fuzz] THEN RETURN [1.0];
IF d >= Sqr[param[0]+fuzz] AND d <= Sqr[param[1]-fuzz] THEN RETURN [m];
d ← Real.SqRt[d];
IF d < param[0] + fuzz THEN d ← (d - param[0])/fuzz
ELSE d ← (param[1] - d)/fuzz;
d ← (d+1)/2;
RETURN [1 + d*(m-1)];
};
FilterProc: TYPE ~ PROC [param: ARRAY [0..maxParam) OF REAL, z: Complex.Vec] RETURNS [REAL];
FilterRep: TYPE ~ RECORD [
filterProc: FilterProc,
param: ARRAY [0..maxParam) OF REALALL[0]
];
FilterEntry: TYPE ~ RECORD [
filterName: ROPE,
nParam: NAT ← 0,
filterProc: FilterProc,
doc: ROPE
];
FindFilterEntry: PROC [name: ROPE] RETURNS [filterEntry: FilterEntry] ~ {
FOR p: LIST OF FilterEntry ← filterList, p.rest UNTIL p = NIL DO
IF name.Equal[p.first.filterName, FALSE] THEN RETURN [p.first]
ENDLOOP;
RETURN [[NIL, 0, NIL, NIL]]
};
ParseFilterSpec: PROC [stream: IO.STREAM, msg: IO.STREAM] RETURNS [filters: LIST OF FilterRep] ~ {
p: LIST OF FilterRep ← NIL;
token: ROPE ← GetToken[stream];
param: ARRAY [0..maxParam) OF REALALL[0];
nParam: NAT ← 0;
WHILE token.Length > 0 DO
r: REAL ← -99999999;
r ← Convert.RealFromRope[token ! Convert.Error => CONTINUE];
IF r = -99999999 THEN r ← Convert.IntFromRope[token ! Convert.Error => CONTINUE];
IF r = -99999999 THEN {
filterEntry: FilterEntry ← FindFilterEntry[token];
IF filterEntry.filterName = NIL THEN {
msg.PutF["%g: Unknown filter", IO.rope[token]];
ERROR ABORTED;
};
IF nParam # filterEntry.nParam THEN {
msg.PutF["%g: Wrong number of parameters", IO.rope[token]];
ERROR ABORTED;
};
p ← CONS[[filterEntry.filterProc, param], p];
nParam ← 0;
param ← ALL[0];
}
ELSE {
param[nParam] ← r;
nParam ← nParam + 1;
IF nParam > maxParam THEN {
msg.PutF["%g: Too many parameters", IO.rope[token]];
ERROR ABORTED;
};
};
token ← GetToken[stream];
ENDLOOP;
WHILE p # NIL DO
t: LIST OF FilterRep ← p;
p ← p.rest;
t.rest ← filters;
filters ← t;
ENDLOOP;
};
ApplyFilters: PROC [image: ImageFFT.Image, filters: LIST OF FilterRep] ~ {
n: NAT ← ImageFFT.Height[image];
ninv2: REAL ← 2.0/n;
s: REAL ← 0;
FOR p: ImageFFT.Image ← image, p.rest UNTIL p = NIL DO
row: Seq.ComplexSequence ← p.first;
FOR f: NAT IN [0..n) DO
fr: REALIF 2*f > n THEN f-n ELSE f;
z: Complex.Vec ← [ABS[fr]*ninv2, ABS[s]*ninv2];
aij: Complex.Vec ← row[f];
m: REAL ← 1.0;
FOR f: LIST OF FilterRep ← filters, f.rest UNTIL f = NIL DO
m ← m*f.first.filterProc[f.first.param, z];
ENDLOOP;
row[f] ← [aij.x*m, aij.y*m];
ENDLOOP;
s ← s + 1.0;
IF s > n/2 THEN s ← s - n;
ENDLOOP;
};
FFTShowCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
inputName: ROPE ← GetToken[stream];
filters: LIST OF FilterRep ← ParseFilterSpec[stream, cmd.out];
image: ImageFFT.Image ← GetTransform[inputName, cmd.out];
ApplyFilters[image, filters];
Show[image];
image ← image.Destroy;
};
FFTFilterCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
oldPriority: Process.Priority ← Process.GetPriority[];
filters: LIST OF FilterRep ← ParseFilterSpec[stream, cmd.out];
IF NOT gets.Equal["←"] THEN {
cmd.out.PutRope["Specify output ← input <filterspec>, please.\n"];
RETURN;
};
Process.SetPriority[Process.priorityBackground];
FilterAIS[outputName, inputName, filters, cmd.out, cmd.commandLine ! UNWIND => {Process.SetPriority[oldPriority]}];
Process.SetPriority[oldPriority];
cmd.out.PutRope["Done.\n"];
};
ShowAISCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
inputName: ROPE ← GetToken[stream];
param: ROPE ← GetToken[stream];
context: Imager.Context ← Imager.Create[$Gray8bpp];
color: ImagerBasic.SampledColor ← ImagerAISUtil.AISToColor[inputName];
horizontalSlotNumber: INT ← 0;
horizontalSlotNumber ← Convert.IntFromRope[param ! Convert.Error => CONTINUE];
context.state.T ← Imager.Scale[1.0];
context.SetColor[color];
context.MaskRectangle[horizontalSlotNumber*color.pa.yPixels, 0, color.pa.yPixels, color.pa.xPixels];
cmd.out.PutRope["AIS comment: "];
cmd.out.PutRope[GetAISComment[inputName]];
cmd.out.PutRope["\n"];
cmd.out.PutRope["Done.\n"];
};
ClipAISCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
skipLines: INT ← GetInt[stream];
skipDots: INT ← GetInt[stream];
lineCount: INT ← GetInt[stream];
dotCount: INT ← GetInt[stream];
pixelMap: ImagerPixelMaps.PixelMap;
raster: AIS.Raster;
IF NOT (gets.Equal["←"] AND skipLines >= 0 AND skipDots >= 0 AND lineCount > 0 AND dotCount > 0) THEN {
cmd.out.PutRope["Specify output ← input <skipLines> <skipDots> <lineCount> <dotCount>, please.\n"];
RETURN;
};
[pixelMap, raster] ← ImagerAISUtil.PixelMapFromAIS[inputName];
StorePixelMap[outputName, pixelMap.Clip[[skipLines, skipDots, lineCount, dotCount]], raster.bitsPerPixel = 0, GetAISComment[inputName].Cat["; ", cmd.commandLine]];
cmd.out.PutRope["Done.\n"];
};
FFTPeekCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
inputName: ROPE ← GetToken[stream];
kind: ROPE ← GetToken[stream];
skipLines: NAT ← GetInt[stream];
skipDots: NAT ← GetInt[stream];
lineCount: NAT ← GetInt[stream];
dotCount: NAT ← GetInt[stream];
realPart: BOOLEAN ← kind.Length = 0 OR kind.Substr[0, 1].Equal["R", FALSE];
elementSize: NAT ~ Basics.bytesPerWord*SIZE[Complex.Vec];
stream ← FS.StreamOpen[fileName: inputName, accessOptions: $read, streamOptions: [tiogaRead: FALSE, commitAndReopenTransOnFlush: TRUE, truncatePagesOnClose: TRUE, finishTransOnClose: TRUE, closeFSOpenFileOnClose: TRUE]];
BEGIN ENABLE UNWIND => {IO.Close[stream]};
Lg: PROC [n: LONG CARDINAL] RETURNS [k: NAT ← 0] ~ {
WHILE n # 1 DO
IF n = 0 OR n MOD 2 = 1 THEN ERROR;
n ← n / 2;
k ← k + 1;
ENDLOOP;
};
lgnsrq: NAT;
n: NAT;
bytes: LONG CARDINALIO.GetLength[stream];
IF bytes MOD elementSize # 0 THEN ERROR;
lgnsrq ← Lg[bytes/elementSize];
IF lgnsrq MOD 2 # 0 THEN ERROR;
n ← Basics.BITSHIFT[1, lgnsrq/2];
FOR i: NAT IN [0..skipDots+dotCount) DO
inrange: BOOLEAN ← i IN [skipLines..skipLines+lineCount);
IF inrange THEN cmd.out.PutRope["["];
FOR j: NAT IN [0..n) DO
complex: Complex.Vec;
TRUSTED {
dataPointer: LONG POINTER ← @complex;
IF elementSize # IO.UnsafeGetBlock[stream, [base: dataPointer, count: elementSize]] THEN ERROR;
};
IF inrange AND j IN [skipDots..skipDots+dotCount) THEN cmd.out.PutF["\t%g", IO.real[IF realPart THEN complex.x ELSE complex.y]];
IF inrange AND j IN [skipDots..skipDots+dotCount-1) THEN cmd.out.PutRope[","];
ENDLOOP;
IF inrange THEN cmd.out.PutRope["]"];
IF i IN [skipLines..skipLines+lineCount-1) THEN cmd.out.PutRope[",\n"];
ENDLOOP;
END;
IO.Close[stream];
};
FFTRankPixelsCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
inputName: ROPE ← GetToken[stream];
skipLines: NAT ← GetInt[stream];
skipDots: NAT ← GetInt[stream];
lineCount: NAT ← GetInt[stream];
dotCount: NAT ← GetInt[stream];
elementSize: NAT ~ Basics.bytesPerWord*SIZE[Complex.Vec];
element: REF ARRAY [0..32000) OF REALNEW[ARRAY [0..32000) OF REAL];
position: REF ARRAY [0..32000) OF REALNEW[ARRAY [0..32000) OF REAL];
k: NAT ← 0;
stream ← FS.StreamOpen[fileName: inputName, accessOptions: $read, streamOptions: [tiogaRead: FALSE, commitAndReopenTransOnFlush: TRUE, truncatePagesOnClose: TRUE, finishTransOnClose: TRUE, closeFSOpenFileOnClose: TRUE]];
BEGIN ENABLE UNWIND => {IO.Close[stream]};
Lg: PROC [n: LONG CARDINAL] RETURNS [k: NAT ← 0] ~ {
WHILE n # 1 DO
IF n = 0 OR n MOD 2 = 1 THEN ERROR;
n ← n / 2;
k ← k + 1;
ENDLOOP;
};
lgnsrq: NAT;
n: NAT;
bytes: LONG CARDINALIO.GetLength[stream];
IF bytes MOD elementSize # 0 THEN ERROR;
lgnsrq ← Lg[bytes/elementSize];
IF lgnsrq MOD 2 # 0 THEN ERROR;
n ← Basics.BITSHIFT[1, lgnsrq/2];
FOR i: NAT IN [0..skipDots+dotCount) DO
inrange: BOOLEAN ← i IN [skipLines..skipLines+lineCount);
FOR j: NAT IN [0..n) DO
complex: Complex.Vec;
TRUSTED {
dataPointer: LONG POINTER ← @complex;
IF elementSize # IO.UnsafeGetBlock[stream, [base: dataPointer, count: elementSize]] THEN ERROR;
};
IF inrange AND j IN [skipDots..skipDots+dotCount) THEN {
position[k] ← k;
element[k] ← complex.x;
k ← k + 1;
};
ENDLOOP;
ENDLOOP;
END;
IO.Close[stream];
ShellSort[element, position, k];
FOR i: NAT IN [0..k) DO
element[i] ← REAL[i]/k;
ENDLOOP;
ShellSort[position, element, k];
FOR i: NAT IN [0..k) DO
cmd.out.PutF["\t%g", IO.real[element[i]]];
IF (i+1) MOD dotCount = 0 THEN cmd.out.PutRope["\n"];
ENDLOOP;
};
shellD: ARRAY [1..11] OF CARDINAL = [1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 65535];
ShellSort: PROCEDURE [key, data: REF ARRAY [0..32000) OF REAL, length: NAT] = {
passes: NAT ← 1;
UNTIL shellD[passes+2] >= length DO passes ← passes+1 ENDLOOP;
FOR pass: NAT DECREASING IN [1..passes] DO
h: NAT ← shellD[pass];
FOR i: NAT IN [0..length) DO
aKey: REAL ← key[i];
aData: REAL ← data[i];
j: NAT ← i;
WHILE j>=h AND aKey < key[j-h] DO
key[j] ← key[j-h];
data[j] ← data[j-h];
j ← j-h;
ENDLOOP;
key[j] ← aKey;
data[j] ← aData;
ENDLOOP;
ENDLOOP;
};
FFTLocalScaleCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
neighborRadius: INT ← GetInt[stream];
radiusSqr: INT ← neighborRadius*neighborRadius;
image: ImageFFT.Image ← ImageFFT.Load[inputName];
size: INT ← image.Height;
pixelMap: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[3, [0, 0, size, size]];
a: ARRAY [0..1024) OF Seq.ComplexSequence;
n: NAT ← 0;
Sqr: PROC [a: NAT] RETURNS [INT] ~ INLINE {RETURN [Basics.LongMult[a, a]]};
FOR p: ImageFFT.Image ← image, p.rest UNTIL p=NIL DO
a[n] ← p.first;
n ← n + 1;
ENDLOOP;
FOR i: INT IN [0..n) DO
FOR j: INT IN [0..n) DO
aij: REAL ← a[i][j].x;
max: REAL ← aij;
min: REAL ← aij;
FOR di: INT IN [-neighborRadius..neighborRadius] DO
FOR dj: INT IN [-neighborRadius..neighborRadius] DO
IF Sqr[ABS[di]] + Sqr[ABS[dj]] <= radiusSqr THEN {
aiijj: REAL;
ii: INT ← i + di;
jj: INT ← j + dj;
WHILE ii >= n DO ii ← ii - n ENDLOOP;
WHILE jj >= n DO jj ← jj - n ENDLOOP;
WHILE ii < 0 DO ii ← ii + n ENDLOOP;
WHILE jj < 0 DO jj ← jj + n ENDLOOP;
aiijj ← a[ii][jj].x;
min ← MIN[min, aiijj];
max ← MAX[max, aiijj];
};
ENDLOOP;
ENDLOOP;
IF min < max THEN aij ← (aij-min)/(max-min);
pixelMap.Fill[[i, j, 1, 1], MIN[MAX[Real.RoundLI[255*aij], 0], 255]];
ENDLOOP;
ENDLOOP;
StorePixelMap[outputName, pixelMap, FALSE, Rope.Concat["FFTLocalScale ", cmd.commandLine]];
FOR i: NAT IN [0..n) DO
a[i] ← NIL;
ENDLOOP;
image ← image.Destroy;
};
maxParam: NAT ~ 4;
filterList: LIST OF FilterEntry ~ LIST [
["SquareLowPass", 1, SquareLowPassFilter, "Square-shaped low-pass filter"],
["Spot", 4, SpotFilter, "[x, y, radius, m] scales fuzzy spot around [x, y] by m"],
["BandBoost", 4, BandBoostFilter, "[innerRadius, outerRadius, edgeFuzz, m] scales annulus centered on the origin by m"]
];
Commander.Register["ShowAIS", ShowAISCommand, "Show a sampled image on the color display (input <horizontalSlotNumber: INTEGER>)"];
Commander.Register["ClipAIS", ClipAISCommand, "Clip a sampled image (output ← input <skipLines> <skipDots> <lineCount> <dotCount>)"];
Commander.Register["FFTHelp", FFTHelpCommand, "List the names of the available spectral filters"];
Commander.Register["FFTFilter", FFTFilterCommand, "Apply spectral filters to (output ← input <params1> filter1 <params2> filter2 . . . )"];
Commander.Register["FFTShow", FFTShowCommand, "Show the filtered FFT of an image on the color display (input <params1> filter1 <params2> filter2 . . . ) (log intensity is displayed)"];
Commander.Register["FFTPeek", FFTPeekCommand, "Type the pixel values of a portion of a .image file (input <REAL | IMAGINARY> <firstScan> <firstPixel> <scanCount> <pixelCount>)"];
Commander.Register["FFTRankPixels", FFTRankPixelsCommand, "Type the ranks of the pixel values of a portion of a .image file (input <firstScan> <firstPixel> <scanCount> <pixelCount>)"];
Commander.Register["FFTLocalScale", FFTLocalScaleCommand, "Rescale each pixel according to the min and max of its local neighborhood (output.ais ← input.image <neighborRadius>)"];
END.