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.STREAM ← IO.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:
BOOLEAN ←
TRUE, 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: INT ← MAX[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.
STREAM ←
NIL]
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.
STREAM ←
NIL, 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: ROPE ← IF 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:
ROPE ←
NIL] = {
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: REAL ← ABS[z.x];
y: REAL ← ABS[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 REAL ← ALL[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 REAL ← ALL[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: REAL ← IF 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.STREAM ← IO.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.STREAM ← IO.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.STREAM ← IO.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.STREAM ← IO.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.STREAM ← IO.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 CARDINAL ← IO.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.STREAM ← IO.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 REAL ← NEW[ARRAY [0..32000) OF REAL];
position: REF ARRAY [0..32000) OF REAL ← NEW[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 CARDINAL ← IO.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.STREAM ← IO.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.