MODULE PZDrawCands EXPORTS Main; (* Draws a list of candidates, flagging true ones. *) (* Last edited on 2001-12-18 08:38:43 by stolfi *) (* Output file names are "XXX-NNNNN.eps" if "-epsFormat" is given, or "XXX.ps" otherwise; where XXX is the "-output" parameter, and "NNNNN" is the candidate's index in the file. *) IMPORT ParseParams, FileRd, Wr, Fmt, Text; IMPORT Process, OSError, Thread, Stdio; IMPORT PSPlot, LR2; IMPORT PZLR3Chain, PZCandidate, PZPose; IMPORT PZPlot; FROM Stdio IMPORT stderr; FROM PZTypes IMPORT LONG, NAT, BOOL, BOOLS; <* FATAL Wr.Failure, Thread.Alerted, OSError.E *> TYPE Options = RECORD input: TEXT; (* Input candidate file name. *) firstCand: NAT; (* Index of first candidate to plot. *) lastCand: NAT; (* Index of last candidate to plot. *) chainDir: TEXT; (* Directory where chains can be found. *) chainPrefix: TEXT; (* Chain file name prefix. *) band: NAT; (* Filtering scale ID for file names. *) step: LONG; (* Nominal input sampling step (pixels). *) extension: TEXT; (* Extension for geometric chains (".flc" usually) *) output: TEXT; (* Output plot file (without ".ps"/".eps") *) (* The reference candidate set and comparison parameters: *) refInput: TEXT; (* Reference candidate set name ("" if none). *) omitTrue: BOOL; (* TRUE to skip candidates that are in ref set. *) omitFalse: BOOL; (* TRUE to skip candidates not in ref. set. *) minRefLength: LONG; (* Min length (pixels) for ref cands (at lambda->0). *) blurFactor: LONG; (* Corner broadening factor. *) minCompOverlap: LONG; (* Minimum overlap (pixels). *) maxCompShift: LONG; (* Maximum displacement on each curve (pixels). *) (* Plotting options *) shift: LR2Pair; (* Displacements to apply to each curve. *) tilt: LongPair; (* CCW rotation (degrees) to apply to each curve. *) segsOnly: BOOL; (* TRUE does not draw curve outside segment. *) drawAllSamples: BOOL; (* TRUE plot all samples, FALSE only every 0.1mm. *) sampleDots: BOOL; (* Plot a dot at each sample position. *) noPointers: BOOL; (* TRUE omits the arrowheads at the segment ends. *) noFrame: BOOL; (* TRUE omits the frame around each candidate. *) noColors: BOOL; (* TRUE uses only black, FALSE uses red/blue for segs. *) noThicker: BOOL; (* TRUE draws segments like rest of curve, FALSE thicker. *) labelSize: REAL; (* Curve label font size in pt (0 = no labels). *) mismatchSize: REAL; (* Font size in pt for mismatch value (0 = omit). *) grid: LONG; (* Coordinate grid spacing (0 = no grid). *) drawMatchEvery: NAT; (* Draw sample pairs every this many (0 = none). *) (* Options for output file format and scale: *) drawFmt: PZPlot.DrawFmtOptions; (* Options that control the scale bar and/or icon at corner: *) scaleIconOptions: PZPlot.ScaleIconOptions; (* Icon style options. *) scaleIconLambda: LONG; (* Basic length for scale/icon *) END; LongPair = ARRAY [0..1] OF LONG; LR2Pair = ARRAY [0..1] OF LR2.T; PROCEDURE Main() = BEGIN WITH o = GetOptions() DO IF o.lastCand >= o.firstCand THEN WITH inpData = ReadCands(o.input), inpCand = inpData.c^, refData = ReadRefCands(o.refInput, minLength := o.minRefLength, blurFactor := o.blurFactor, lambda := inpData.lambda, step := o.step ), flag = CompareCandSets( inpData, refData, minCompOverlap := o.minCompOverlap, maxCompShift := o.maxCompShift, step := o.step )^, chainUsed = ChainsReallyUsed(inpCand, o)^, chAllData = PZLR3Chain.ReadAll( o.chainPrefix, o.band, o.extension, sel := chainUsed, dir := o.chainDir, headerOnly := FALSE, centralize := FALSE ) DO DrawCandidates(inpCand, flag, chAllData.chData^, o) END; END; END; END Main; PROCEDURE ReadCands(name: TEXT): PZCandidate.ReadData = BEGIN WITH fileName = name & ".can" DO Wr.PutText(stderr, "reading " & fileName & " ...\n"); RETURN PZCandidate.Read(FileRd.Open(fileName)) END END ReadCands; PROCEDURE ReadRefCands( name: TEXT; minLength, blurFactor: LONG; lambda, step: LONG; ): PZCandidate.ReadData = (* Reads a candidate list, and removes entries shorter than "minLength" (minus corner blurring) on either segment. *) VAR nCands: NAT; cData: PZCandidate.ReadData; BEGIN (* Hack to avoid overflow when "step" is zero. *) IF step = 0.0d0 THEN minLength := 0.0d0; step := 1.0d0 END; IF NOT Text.Empty(name) THEN WITH fileName = name & ".can" DO Wr.PutText(stderr, "reading " & fileName & " ...\n"); cData := PZCandidate.Read(FileRd.Open(fileName)); END; <* ASSERT cData.lambda = lambda *> WITH c = cData.c^, minCorrLength = MAX(0.0d0, minLength - 2.0d0*blurFactor*cData.lambda), minSteps = FLOOR(minCorrLength/step) DO nCands := NUMBER(c); PZCandidate.RemoveShort(c, nCands, minSteps); WITH diff = NUMBER(c) - nCands, txt = " Read " & Fmt.Int(NUMBER(c)) & " candidates.\n" & " Removed " & Fmt.Int(diff) & " candidates with less than " & Fmt.Int(minSteps) & " steps.\n" & " Left " & Fmt.Int(nCands) & " candidates." DO Wr.PutText(stderr, txt & "\n"); cData.cmt := cData.cmt & "\n" & "PZDrawCands:\n" & txt END END ELSE nCands := 0; cData.cmt := "No reference candidates"; cData.c := NEW(REF PZCandidate.List, 0); cData.lambda := lambda; END; IF nCands < NUMBER(cData.c^) THEN WITH rd = NEW(REF PZCandidate.List, nCands), d = rd^ DO d := SUBARRAY(cData.c^, 0, nCands); cData.c := rd END END; RETURN cData END ReadRefCands; PROCEDURE ChainsReallyUsed( READONLY cand: PZCandidate.List; READONLY o: Options; ): REF BOOLS = BEGIN WITH nCands = MIN(o.lastCand, LAST(cand)) - o.firstCand + 1 DO RETURN PZCandidate.ChainsUsed(SUBARRAY(cand, o.firstCand, nCands)) END END ChainsReallyUsed; PROCEDURE DrawCandidates( READONLY cand: PZCandidate.List; READONLY flag: BOOLS; READONLY flc: ARRAY OF PZLR3Chain.ReadData; READONLY o: Options; )= VAR f: PSPlot.File := NIL; nInFile: NAT := 0; BEGIN <* ASSERT NUMBER(flag) = NUMBER(cand) *> WITH firstCand = o.firstCand, lastCand = MIN(o.lastCand, LAST(cand)) DO Wr.PutText(stderr, "Drawing candidates from " & Fmt.Int(firstCand) & " to " & Fmt.Int(lastCand) & "\n" ); FOR iCand := firstCand TO lastCand DO Wr.PutText(stderr, "Considering candidate " & Fmt.Int(iCand) & "\n"); IF ((flag[iCand]) AND NOT o.omitTrue) OR ((NOT flag[iCand]) AND NOT o.omitFalse) THEN WITH id = Fmt.Pad(Fmt.Int(iCand), 5, '0') DO PZPlot.BeginNewDrawing(f, o.output, id, o.drawFmt, nInFile) END; DrawCandidate(f, cand[iCand], flag[iCand], flc, o); END END END; PZPlot.NoMoreDrawings(f, o.drawFmt, nInFile); Wr.PutText(stderr, "Drawings finished.\n") END DrawCandidates; PROCEDURE DrawCandidate( f: PSPlot.File; READONLY cand: PZCandidate.T; flag: BOOL; READONLY flc: ARRAY OF PZLR3Chain.ReadData; READONLY o: Options; ) = VAR mappedChain: ARRAY [0..1] OF REF PZLR3Chain.T; BEGIN IF o.grid > 0.0d0 THEN PZPlot.Grid(f, o.grid) END; IF NOT o.noFrame THEN PZPlot.Frame(f) END; FOR j := 0 TO 1 DO WITH seg = cand.seg[j], chainNum = seg.cvx, chain = flc[chainNum].c^, m = PZPose.PlaceSegment( seg, chain, tilt := o.tilt[j], shift := o.shift[j], flip := FALSE (* Hope that "seg.rev = (j = 1). *) ) DO mappedChain[j] := PZLR3Chain.Map(chain, m) END END; PZPlot.Candidate( f, cand, mappedChain[0]^, mappedChain[1]^, whole := NOT o.segsOnly, closed := TRUE, colors := NOT o.noColors, thicker := NOT o.noThicker, dots := o.sampleDots, pointers := NOT o.noPointers, labelSize := o.labelSize, drawEvery := PZPlot.ComputeDrawEvery(f, 0.2, o.step, all := o.drawAllSamples), drawMatchEvery := o.drawMatchEvery ); IF flag THEN IF o.noColors THEN f.setLineColor(PSPlot.Black) ELSE f.setLineColor(PSPlot.Color{0.0, 0.7, 0.0}) END; PZPlot.Star(f, radius := MAX(1.0, o.labelSize/10.0)) END; IF o.mismatchSize > 0.0 THEN WITH xr = f.getRange(PSPlot.Axis.X), x = xr.min + 0.05d0*(xr.max - xr.min), yr = f.getRange(PSPlot.Axis.Y), y = yr.min + 0.05d0*(yr.max - yr.min), mstr = Fmt.LongReal(cand.mismatch, prec := 2, style := Fmt.Style.Fix) DO f.label(mstr, x, y, size := o.mismatchSize) END; END; IF o.scaleIconLambda > 0.0d0 OR o.scaleIconOptions.barCount > 0 THEN PZPlot.ScaleIcon(f, lambda := o.scaleIconLambda, o := o.scaleIconOptions) END; END DrawCandidate; PROCEDURE CompareCandSets( READONLY inpData: PZCandidate.ReadData; READONLY refData: PZCandidate.ReadData; minCompOverlap: LONG; maxCompShift: LONG; step: LONG; ): REF BOOLS = (* Compares the input candidates "inpData" with the reference candidates "refData", and returns a vector "ok" where "ok[i] = TRUE" iff "inpData.c[i]" is fairly similar to some reference candidate "refData.c[j]". *) BEGIN IF NUMBER(refData.c^) > 0 THEN <* ASSERT inpData.lambda = refData.lambda *> END; (* Hack to avoid overflow when "step" is zero. *) IF step = 0.0d0 THEN step := minCompOverlap; IF step = 0.0d0 THEN step := 1.0d0 END; END; WITH inpCand = inpData.c^, refCand = refData.c^, inpOK = NEW(REF BOOLS, NUMBER(inpCand)) DO IF NUMBER(refCand) = 0 THEN FOR iCand := 0 TO LAST(inpCand) DO inpOK[iCand] := FALSE END ELSE WITH refOK = NEW(REF BOOLS, NUMBER(refCand)), minSteps = FLOOR(minCompOverlap / step + 0.0001d0), maxAdjust = CEILING(maxCompShift / step - 0.0001d0) DO Wr.PutText(stderr, "Candidate comparison parameters:\n"); Wr.PutText(stderr, " overlap minSteps = " & Fmt.Int(minSteps) & "\n"); Wr.PutText(stderr, " alignment maxAdjust = " & Fmt.Int(maxAdjust) & "\n"); PZCandidate.FindSimilarCands( aCand := inpCand, bCand := refCand, minSteps := minSteps, maxAdjust := maxAdjust, aOK := inpOK^, bOK := refOK^ ); PZCandidate.PrintSimilarityStatistics( stderr, inpOK^, "input", refOK^, "reference" ); END END; RETURN inpOK END; END CompareCandSets; PROCEDURE GetOptions(): Options = VAR o: Options; BEGIN WITH pp = NEW(ParseParams.T).init(stderr) DO TRY pp.getKeyword("-input"); o.input := pp.getNext(); IF pp.keywordPresent("-chainDir") THEN o.chainDir := pp.getNext() ELSE o.chainDir := "." END; pp.getKeyword("-chainPrefix"); o.chainPrefix := pp.getNext(); pp.getKeyword("-band"); o.band := pp.getNextInt(); IF pp.keywordPresent("-extension") THEN o.extension:= pp.getNext(); ELSE o.extension := ".flc"; END; pp.getKeyword("-step"); o.step := pp.getNextLongReal(0.0d0, 1024.0d0); pp.getKeyword("-output"); o.output := pp.getNext(); IF pp.keywordPresent("-maxCands") THEN o.firstCand := 0; o.lastCand := pp.getNextInt(1) - 1 ELSIF pp.keywordPresent("-candsRange") THEN o.firstCand := pp.getNextInt(0); o.lastCand := pp.getNextInt(o.firstCand) ELSE o.firstCand := 0; o.lastCand := 239 END; (* Reference set options: *) IF pp.keywordPresent("-refInput") THEN o.refInput := pp.getNext(); IF pp.keywordPresent("-minRefLength") THEN o.minRefLength := pp.getNextLongReal(0.0d0) ELSE o.minRefLength := 0.0d0 END; o.omitTrue := pp.keywordPresent("-omitTrue"); o.omitFalse := pp.keywordPresent("-omitFalse"); pp.getKeyword("-blurFactor"); o.blurFactor := pp.getNextLongReal(0.01d0, 100.0d0); pp.getKeyword("-minCompOverlap"); o.minCompOverlap := pp.getNextLongReal(0.0d0); pp.getKeyword("-maxCompShift"); o.maxCompShift := pp.getNextLongReal(0.0d0); ELSE o.refInput := ""; o.omitTrue := FALSE; o.omitFalse := FALSE; o.minRefLength := 0.0d0; o.blurFactor := 0.0d0; o.minCompOverlap := 0.0d0; o.maxCompShift := 0.0d0 END; o.drawAllSamples := pp.keywordPresent("-drawAllSamples"); IF pp.keywordPresent("-drawMatch") THEN o.drawMatchEvery := 1 ELSIF pp.keywordPresent("-drawMatchEvery") THEN o.drawMatchEvery:= pp.getNextInt(1); ELSE o.drawMatchEvery := 0; END; IF pp.keywordPresent("-displace") THEN WITH d = pp.getNextLongReal() DO o.shift[0] := LR2.T{0.0d0, -d/2.0d0}; o.shift[1] := LR2.T{0.0d0, +d/2.0d0} END ELSIF pp.keywordPresent("-shift") THEN FOR j := 0 TO 1 DO FOR k := 0 TO 1 DO o.shift[j][k] := pp.getNextLongReal(); END END ELSE FOR j := 0 TO 1 DO o.shift[j] := LR2.T{0.0d0, 0.0d0} END END; IF pp.keywordPresent("-tilt") THEN o.tilt[0] := pp.getNextLongReal(); o.tilt[1] := pp.getNextLongReal() ELSE o.tilt[0] := 0.0d0; o.tilt[1] := 0.0d0 END; o.segsOnly := pp.keywordPresent("-segsOnly"); o.sampleDots := pp.keywordPresent("-sampleDots"); o.noPointers := pp.keywordPresent("-noPointers"); o.noFrame := pp.keywordPresent("-noFrame"); o.noColors := pp.keywordPresent("-noColors"); o.noThicker := pp.keywordPresent("-noThicker"); IF pp.keywordPresent("-labelSize") THEN o.labelSize := pp.getNextReal(0.0, 100.0) ELSE o.labelSize := 12.0 END; IF pp.keywordPresent("-mismatchSize") THEN o.mismatchSize := pp.getNextReal(0.0, 100.0) ELSE o.mismatchSize := 12.0 END; IF pp.keywordPresent("-grid") THEN o.grid := pp.getNextLongReal(1.0d-10, 1.0d+10) ELSE o.grid := 0.0d0 END; o.drawFmt := PZPlot.ParseDrawFmtOptions( pp, single := FALSE, defaultWidth := 40.0d0, defaultHeight := 40.0d0 ); o.scaleIconOptions := PZPlot.ParseScaleIconOptions(pp); IF pp.keywordPresent("-scaleBarStep") OR pp.keywordPresent("-scaleIconLambda") THEN o.scaleIconLambda := pp.getNextLongReal(0.0d0); ELSE IF o.scaleIconOptions.style # PZPlot.ScaleIconStyle.None OR o.scaleIconOptions.barCount > 0 THEN pp.error("scale icon/bar requires \"-scaleBarStep\" or \"-scaleIconLambda\"") END; o.scaleIconLambda := 0.0d0 END; pp.finish(); EXCEPT | ParseParams.Error => Wr.PutText(stderr, "Usage: PZDrawCands \\\n"); Wr.PutText(stderr, " -input NAME \\\n"); Wr.PutText(stderr, " [ -chainDir DIR ] -chainPrefix NAME \\\n"); Wr.PutText(stderr, " -band NUMBER [ -extension EXT ] -step NUM \\\n"); Wr.PutText(stderr, " -output NAME \\\n"); Wr.PutText(stderr, " [ -maxCands NUMBER | -candsRange FIRSTNUM LASTNUM ] \\\n"); Wr.PutText(stderr, " [ -drawAllSamples ] \\\n"); Wr.PutText(stderr, " [ -refInput NAME \\\n"); Wr.PutText(stderr, " [ -minRefLength PIXELS ] \\\n"); Wr.PutText(stderr, " -blurFactor NUM \\\n"); Wr.PutText(stderr, " -minCompOverlap PIXELS -maxCompShift PIXELS \\\n"); Wr.PutText(stderr, " -blurFactor PIXELS \\\n"); Wr.PutText(stderr, " ] \\\n"); Wr.PutText(stderr, " [ -drawMatch | -drawMatchEvery NUMBER ] \\\n"); Wr.PutText(stderr, " [ -displace NUMBER | -shift DXA DYA DXB DYB ] \\\n"); Wr.PutText(stderr, " [ -tilt NUMBER NUMBER ] \\\n"); Wr.PutText(stderr, " [ -segsOnly ] [ -sampleDots ] [ -noColors ] \\\n"); Wr.PutText(stderr, " [ -noPointers ] [ -noFrame ] [ -noThicker ] \\\n"); Wr.PutText(stderr, " [ -labelSize POINTS ] [ -mismatchSize POINTS ] \\\n"); Wr.PutText(stderr, " [ -grid SPACING ] \\\n"); Wr.PutText(stderr, PZPlot.ScaleIconOptionsHelp & " \\\n"); Wr.PutText(stderr, " [ -scaleBarStep NUM | -scaleIconLambda NUM ] \\\n"); Wr.PutText(stderr, PZPlot.DrawFmtOptionsHelp & " \n"); Process.Exit(1); END; END; RETURN o END GetOptions; BEGIN Main() END PZDrawCands. (* Copyright © 2001 Universidade Estadual de Campinas (UNICAMP). Authors: Helena C. G. Leitão and Jorge Stolfi. This file can be freely distributed, used, and modified, provided that this copyright and authorship notice is preserved, and that any modified versions are clearly marked as such. This software has NO WARRANTY of correctness or applicability for any purpose. Neither the authors nor their employers chall be held responsible for any losses or damages that may result from its use. *)