MODULE PZDrawCandGrids EXPORTS Main; (* Draws the dynamic programming graphs and paths for given candidate matchings *) (* Last edited on 1999-11-18 01:59:18 by hcgl *) IMPORT PSPlot; IMPORT ParseParams, FileRd, Text, Wr, Fmt; IMPORT Process, OSError, Thread, Stdio; IMPORT PZCandidate, PZSegment, PZMatch; IMPORT PZPlot; FROM Stdio IMPORT stderr; FROM PZTypes IMPORT LONG, NAT, BOOL; <* FATAL Wr.Failure, Thread.Alerted, OSError.E *> TYPE Options = RECORD input: TEXT; (* Input candidate file name. *) ideal: TEXT; (* Ideal candidate file name, or "". *) output: TEXT; (* Output candidate file (without ".???"). *) maxCands: NAT; (* Num candidates to plot. *) (* Plotting options *) separatePlots: BOOL; (* TRUE = per candidate, FALSE = per chain pair. *) segments: SegmentPair; (* Segments of interest, or empty segs. *) cellSize: LONG; (* DP grid cell size in millimeters, or 0. *) refGridLineWidth: REAL; (* Line width for reference grid (mm). *) refGridLineColor: Color; (* Line color for reference grid (0-1 RGB). *) candGridLineWidth: REAL; (* Line width for candidate grid (mm). *) candGridLineColor: Color; (* Line color for candidate grid (0-1 RGB). *) boxSize: REAL; (* Box size for fattened match pairs (cells). *) boxColor: Color; (* Box color for fattened match pairs (mm). *) matchLineWidth: REAL; (* Line width for match path (mm). *) matchLineColor: Color; (* Line color for match path (0-1 RGB). *) dotSize: REAL; (* Dot diameter for match pairs (mm). *) dotColor: Color; (* Dot color for match pairs (0-1 RGB). *) epsFormat: BOOL; (* TRUE to use Encapsulated Postscript. *) END; (* If "segments" is specified (meaning "cvx # LAST(NAT)"), only pairs belonging to the same chain pair as "segments" will be considered, and the plots will be scaled to fit those segments. (If either "tot" or "ns" is zero, the segment is understood to be the whole chain). If "segments" is not given, all candidates will be considered. The plot will be scaled to fit each individual candidate, if "separatePlots" is TRUE, or the full chains, if "separatePlots" is FALSE. In the last case, the file should be sorted by chain pair. If an "ideal" file is named, the candidates from that file are plotted too, before the "input" candidates. They are plotted with slightly different color, and with slightly larger line width and dot size. *) TYPE Color = PSPlot.Color; SegmentPair = PZSegment.Pair; PROCEDURE Main() = BEGIN WITH o = GetOptions() DO Wr.PutText(stderr, "PZDrawCandGrids " & o.input & "\n"); IF o.maxCands > 0 THEN WITH cand = ReadCandidates(o.input, o.ideal)^ DO DrawCandidates(o.output, cand, o); END; END; END; END Main; PROCEDURE ReadCandidates(input, ideal: TEXT): REF PZCandidate.List = (* Returns the concatenation of the input and ideal candidates, sorted by curve pair. The "mismatch" field is discarded and used to distinguish the ideal candidates ("mismatch > 0") from the ordinary ones ("mismatch < 0"). *) PROCEDURE DoRead(file: TEXT): REF PZCandidate.List = BEGIN IF Text.Empty(file) THEN RETURN NEW(REF PZCandidate.List, 0) ELSE WITH cd = PZCandidate.Read(FileRd.Open(file & ".can")) DO RETURN cd.c END END END DoRead; BEGIN WITH inputC = DoRead(input)^, inputN = NUMBER(inputC), idealC = DoRead(ideal)^, idealN = NUMBER(idealC), res = NEW(REF PZCandidate.List, inputN+idealN), mergeC = res^ DO SUBARRAY(mergeC, 0, inputN) := inputC; SUBARRAY(mergeC, inputN, idealN) := idealC; FOR i := 0 TO LAST(mergeC) DO WITH cand = mergeC[i] DO IF i < inputN THEN cand.mismatch := +1.0d0 ELSE cand.mismatch := -1.0d0 END END END; RETURN res END END ReadCandidates; PROCEDURE DrawCandidates( output: TEXT; VAR cand: PZCandidate.List; READONLY o: Options; )= VAR ps: PSPlot.File := NIL; nPlotted: NAT := 0; ini, fin: NAT; BEGIN Wr.PutText(stderr,"DrawCandidates...\n"); IF NOT o.epsFormat THEN (* Open output file *) WITH name = output & "-grid.ps" DO ps := NEW(PSPlot.PSFile).open(name); Wr.PutText(stderr, name & "\n"); END END; IF NOT o.separatePlots THEN (* Sort candidates by chain pair, then `ideal' before `ordinary': *) PZCandidate.Sort(cand, PZCandidate.PairMismatchBetter) END; ini := 0; WHILE ini <= LAST(cand) AND nPlotted < o.maxCands DO (* Locate the range "[ini..fin-1]" of candidates to plot: *) fin := ini + 1; WHILE fin <= LAST(cand) AND NOT (o.separatePlots OR NOT SameCurves(cand[fin].seg, cand[ini].seg)) DO INC(fin) END; WITH cvx0 = cand[ini].seg[0].cvx, cvx1 = cand[ini].seg[1].cvx DO IF (o.segments[0].cvx = LAST(NAT) OR cvx0 = o.segments[0].cvx) AND (o.segments[1].cvx = LAST(NAT) OR cvx1 = o.segments[1].cvx) THEN WITH nBatch = MIN(fin - ini, o.maxCands - nPlotted), batch = SUBARRAY(cand, ini, nBatch) DO DrawCandidateBatch(ps, ini, batch, o); INC(nPlotted, nBatch) END END END; ini := fin END; Wr.PutText(stderr,"\n"); IF NOT o.epsFormat THEN ps.close() END; END DrawCandidates; PROCEDURE SameCurves(READONLY sa, sb: PZSegment.Pair): BOOL = BEGIN RETURN sa[0].cvx = sb[0].cvx AND sa[1].cvx = sb[1].cvx END SameCurves; PROCEDURE DrawCandidateBatch( VAR ps: PSPlot.File; (* Postscript file ("NIL" if "o.epsFormat"). *) ini: NAT; (* Position of "cand[0]" in input file. *) READONLY cand: PZCandidate.List; (* The candidates (all of the same curve pair). *) READONLY o: Options; (* The command line options. *) ) = VAR segRef: PZSegment.Pair; (* Segments to plot. *) plotName, caption: TEXT; gridStep: LONG; BEGIN (* Start a new drawing. *) IF o.separatePlots THEN (* Start new drawing for a single candidate: *) <* ASSERT NUMBER(cand) = 1 *> WITH candSeg = cand[0].seg, candName = Fmt.Pad(Fmt.Int(ini), 5, '0') DO segRef := ChooseReferenceSegments(o.segments, candSeg); plotName := candName; caption := "Candidate " & candName END ELSE (* Start new drawing for a chain pair: *) WITH wholeSeg = JoinChainPairSegments(cand), chainName = ARRAY [0..1] OF TEXT{ Fmt.Pad(Fmt.Int(wholeSeg[0].cvx), 4, '0'), Fmt.Pad(Fmt.Int(wholeSeg[1].cvx), 4, '0') } DO segRef := ChooseReferenceSegments(o.segments, wholeSeg); plotName := chainName[0] & "-" & chainName[1]; caption := "Chains " & chainName[0] & " and " & chainName[1]; END; END; BeginDrawing(ps, plotName, segRef, caption, o, (*OUT*) gridStep); (* Draw global grid, if necessary: *) IF NUMBER(cand) > 1 OR segRef # cand[0].seg OR o.candGridLineWidth = 0.0 THEN DrawGrid(ps, segRef, segRef, gridStep := gridStep, lineWidth := o.refGridLineWidth, lineColor := o.refGridLineColor, drawDiags := FALSE ) END; (* Draw candidate grids: *) FOR iCand := 0 TO LAST(cand) DO WITH cd = cand[iCand] DO DrawGrid(ps, cd.seg, segRef, gridStep := gridStep, lineWidth := o.candGridLineWidth, lineColor := o.candGridLineColor, drawDiags := TRUE ) END END; (* Draw the candidate matchings: *) FOR iCand := 0 TO LAST(cand) DO WITH cd = cand[iCand] DO DrawMatch(ps, cd.pm, cd.seg, segRef, gridStep := gridStep, lineWidth := o.matchLineWidth, lineColor := o.matchLineColor, dotSize := o.dotSize, dotColor := o.dotColor, boxSize := o.boxSize, boxColor := o.boxColor, ideal := (cd.mismatch < 0.0d0) ) END END; (* Terminate the drawing: *) EndDrawing(ps, o); END DrawCandidateBatch; PROCEDURE JoinChainPairSegments(READONLY cand: PZCandidate.List): SegmentPair = (* Returns a pair of segments that describe the whole chains underlying "seg[0]" and "seg[1]", with the same orientation. The segments will be open (without the step from last sample to first sample). *) VAR segJoin: PZSegment.Pair := cand[0].seg; BEGIN FOR i := 1 TO LAST(cand) DO FOR j := 0 TO 1 DO WITH sC = cand[i].seg[j], sJ = segJoin[j] DO <* ASSERT sC.cvx = sJ.cvx *> <* ASSERT sC.tot = sJ.tot *> (* Allow segments with discordant directions: *) sJ.rev := sC.rev; sJ := PZSegment.Join(sJ, sC) END END END; FOR j := 0 TO 1 DO WITH sJ = segJoin[j] DO IF sJ.ns > sJ.tot THEN sJ.ini := 0; sJ.ns := sJ.tot END END END; RETURN segJoin END JoinChainPairSegments; PROCEDURE ChooseReferenceSegments( READONLY segUser: SegmentPair; READONLY segCand: SegmentPair; ): SegmentPair = (* Return "segUser[0..1]", completing it with "segCand[0..1]" where unspecified. *) VAR seg: SegmentPair; BEGIN FOR j := 0 TO 1 DO IF segUser[j].cvx = LAST(NAT) THEN seg[j] := segCand[j] ELSE seg[j] := segUser[j]; IF seg[j].ns = 0 OR seg[j].tot = 0 THEN seg[j].ns := segCand[j].ns; seg[j].tot := segCand[j].tot; END; END END; RETURN seg END ChooseReferenceSegments; PROCEDURE BeginDrawing( VAR ps: PSPlot.File; plotName: TEXT; READONLY segRef: SegmentPair; caption: TEXT; READONLY o: Options; VAR (*OUT*) gridStep: LONG; (* Actual grid cell size (mm). *) ) = (* Starts a new drawing for one or more candidates. For ".ps" format, starts a new page within the given file "ps". For ".eps" format, opens a new file and return it in "ps". In either case the plot is placed on the page assuming that all grids and machings will be contained in segments "segRef[0..1]". The "plotName" must be a string identifying the plot. The grid will be drawn with the given "o.cellSize" (mm), if that parameter is nonzero. Otherwise the grid step will be computed from "segRef" and the available plot area. In any case, the chosen cell size is returned in the "gridStep" parameter. *) BEGIN WITH (* These numbers include a half-cell safety border around the grid: *) nXCells = FLOAT(segRef[0].ns, LONG), nYCells = FLOAT(segRef[1].ns, LONG) DO (* Determine the actual grid step size to use: *) WITH inch = 25.4d0, (* One inch in mm *) maxXSize = PSPlot.LetterXSize - 1.0d0 * inch, maxYSize = PSPlot.LetterYSize - 2.0d0 * inch DO gridStep := MIN(maxXSize/nXCells, maxYSize/nYCells) END; IF o.cellSize # 0.0d0 AND o.cellSize < gridStep THEN gridStep := o.cellSize END; (* Set up the plotting environment: *) WITH xSize = nXCells * gridStep, ySize = nYCells * gridStep DO IF o.epsFormat THEN WITH name = o.output & "-" & plotName & "-grid.eps" DO ps := NEW(PSPlot.EPSFile).open(name, xSize := xSize, ySize := ySize); Wr.PutText(stderr, "writing " & name); END ELSE WITH ps = NARROW(ps, PSPlot.PSFile) DO ps.beginPage(); WITH plotXMargin = 0.5d0 * (PSPlot.LetterXSize - xSize), plotYMargin = PSPlot.LetterYSize - ySize - plotXMargin, xCenter = xSize * 0.5d0 + plotXMargin, yCenter = ySize * 0.5d0 + plotYMargin DO ps.beginDrawing(xSize, ySize, xCenter, yCenter) END; ps.caption("plot " & caption); END; Wr.PutText(stderr, "drawing " & caption) END; Wr.PutText(stderr, " xSize = " & FLR(xSize,4)); Wr.PutText(stderr, " ySize = " & FLR(ySize,4) & "\n"); ps.setScale(PSPlot.Axis.X, 0.0d0, xSize); ps.setScale(PSPlot.Axis.Y, 0.0d0, ySize); END; END END BeginDrawing; PROCEDURE EndDrawing(VAR ps: PSPlot.File; READONLY o: Options) = BEGIN IF o.epsFormat THEN ps.close(); ps := NIL ELSE WITH ps = NARROW(ps, PSPlot.PSFile) DO ps.endDrawing(); ps.endPage() END END END EndDrawing; PROCEDURE DrawGrid( ps: PSPlot.File; READONLY seg: SegmentPair; READONLY segRef: SegmentPair; gridStep: LONG; lineWidth: REAL; lineColor: Color; drawDiags: BOOL; ) = PROCEDURE DoDrawGrid(READONLY t, tRef: SegmentPair) = BEGIN PZPlot.SegSegGrid(ps, t, tRef, gridStep, drawDiags) END DoDrawGrid; BEGIN IF lineWidth > 0.0 THEN ps.setLineWidth(lineWidth); ps.setLineColor(lineColor); ps.setLineSolid(); PZPlot.TryAllShifts(seg, segRef, DoDrawGrid) END; END DrawGrid; PROCEDURE DrawMatch( ps: PSPlot.File; pm: REF PZMatch.PackedT; READONLY seg: SegmentPair; READONLY segRef: SegmentPair; gridStep: LONG; (* In mm *) lineWidth: REAL; (* In mm *) lineColor: Color; dotSize: REAL; (* In mm *) dotColor: Color; boxSize: REAL; (* In mm *) boxColor: Color; ideal: BOOL; ) = VAR rm: REF PZMatch.T; BEGIN IF lineWidth > 0.0 OR dotSize > 0.0 OR boxSize > 0.0 THEN IF pm # NIL THEN rm := PZMatch.Unpack(pm^) ELSE rm := PZMatch.MostPerfect(seg[0].ns, seg[1].ns) END; IF ideal THEN (* Adjust dot size/color and line width/color for ideal candidates: *) IF dotSize > 0.0 THEN dotSize := dotSize + 0.2 END; IF lineWidth > 0.0 THEN lineWidth := lineWidth + 0.2 END; dotColor := Highlight(dotColor); lineColor := Highlight(lineColor); END; WITH m = rm^ DO PROCEDURE DoDrawMatchBoxes(READONLY t, tRef: SegmentPair) = BEGIN PZPlot.MatchBoxes(ps, m, t, tRef, gridStep, boxSize*FLOAT(gridStep,REAL)) END DoDrawMatchBoxes; PROCEDURE DoDrawMatchPath(READONLY t, tRef: SegmentPair) = BEGIN PZPlot.MatchPath(ps, m, t, tRef, gridStep) END DoDrawMatchPath; PROCEDURE DoDrawMatchDots(READONLY t, tRef: SegmentPair) = BEGIN PZPlot.MatchDots(ps, m, t, tRef, gridStep, dotSize) END DoDrawMatchDots; BEGIN IF boxSize > 0.0 THEN (* Draw squares around path nodes: *) ps.setLineWidth(0.0); ps.setLineColor(PSPlot.Invisible); ps.setLineSolid(); ps.setFillColor(boxColor); PZPlot.TryAllShifts(seg, segRef, DoDrawMatchBoxes) END; IF lineWidth > 0.0 THEN (* Draw the matching "cand.pm" as a path on the DP grid: *) ps.setLineWidth(lineWidth); ps.setLineColor(lineColor); ps.setLineSolid(); ps.setFillColor(PSPlot.Invisible); PZPlot.TryAllShifts(seg, segRef, DoDrawMatchPath) END; IF dotSize > 0.0 THEN (* Draw the pairs of the matching "cand.pm" as vertices of the DP grid: *) ps.setLineWidth(dotSize); ps.setLineColor(dotColor); ps.setLineSolid(); ps.setFillColor(PSPlot.Invisible); PZPlot.TryAllShifts(seg, segRef, DoDrawMatchDots) END END END END END DrawMatch; PROCEDURE Highlight(c: Color): Color = BEGIN FOR i := 0 TO 2 DO IF c[i] > 0.5 THEN c[i] := 0.50*c[i] ELSE c[i] := 0.50 + 0.50*c[i] END END; RETURN c END Highlight; 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("-ideal") THEN o.ideal := pp.getNext() ELSE o.ideal := "" END; pp.getKeyword("-output"); o.output := pp.getNext(); IF pp.keywordPresent("-maxCands") THEN o.maxCands := pp.getNextInt(1) ELSE o.maxCands := 160 END; o.separatePlots := pp.keywordPresent("-separatePlots"); IF pp.keywordPresent("-segments") THEN o.segments[0] := ParseSegmentParam(pp); o.segments[1] := ParseSegmentParam(pp); ELSIF pp.keywordPresent("-chains") THEN WITH cvx0 = pp.getNextInt(), cvx1 = pp.getNextInt(), s = o.segments DO s[0] := PZSegment.Empty; s[0].cvx := cvx0; s[0].rev := FALSE; s[1] := PZSegment.Empty; s[1].cvx := cvx1; s[1].rev := TRUE END ELSE WITH s = o.segments DO s[0] := PZSegment.Empty; s[0].cvx := LAST(NAT); s[1] := PZSegment.Empty; s[1].cvx := LAST(NAT) END; END; IF pp.keywordPresent("-cellSize") THEN o.cellSize := pp.getNextLongReal(0.1d0, 100.0d0); ELSE o.cellSize := 0.0d0 END; IF pp.keywordPresent("-refGridLine") THEN o.refGridLineWidth := pp.getNextReal(0.0, 10.0); o.refGridLineColor := ParseColorParam(pp); ELSE o.refGridLineWidth := 0.1; o.refGridLineColor := Color{0.667, 0.667, 0.667}; END; IF pp.keywordPresent("-candGridLine") THEN o.candGridLineWidth := pp.getNextReal(0.0, 10.0); o.candGridLineColor := ParseColorParam(pp); ELSE o.candGridLineWidth := 0.1; o.candGridLineColor := Color{0.333, 0.333, 0.333}; END; IF pp.keywordPresent("-matchLine") THEN o.matchLineWidth := pp.getNextReal(0.0, 10.0); o.matchLineColor := ParseColorParam(pp); ELSE o.matchLineWidth := 0.4; o.matchLineColor := PSPlot.Black; END; IF pp.keywordPresent("-dots") THEN o.dotSize := pp.getNextReal(0.0, 10.0); o.dotColor := ParseColorParam(pp); ELSE o.dotSize := 0.8; o.dotColor := PSPlot.Black; END; IF pp.keywordPresent("-boxes") THEN o.boxSize := pp.getNextReal(0.0, 100.0); o.boxColor := ParseColorParam(pp); ELSE o.boxSize := 0.0; o.boxColor := PSPlot.Black; END; o.epsFormat := pp.keywordPresent("-epsFormat"); pp.finish(); EXCEPT | ParseParams.Error => Wr.PutText(stderr, "Usage: PZDrawCands \\\n"); Wr.PutText(stderr, " -input NAME [ -ideal NAME ] \\\n"); Wr.PutText(stderr, " -output NAME \\\n"); Wr.PutText(stderr, " [ -maxCands NUMBER ] \\\n"); Wr.PutText(stderr, " [ -separatePlots ] \\\n"); Wr.PutText(stderr, " [ -segments {CHAIN TOTSAMP START NSAMP {+|-}}^2 | \\\n"); Wr.PutText(stderr, " -chains CHAIN0 CHAIN1 \\\n"); Wr.PutText(stderr, " ] \\\n"); Wr.PutText(stderr, " [ -cellSize NUMBER ] \\\n"); Wr.PutText(stderr, " [ -refGridLine WIDTH RED GRN BLU ] \\\n"); Wr.PutText(stderr, " [ -candGridLine WIDTH RED GRN BLU ] \\\n"); Wr.PutText(stderr, " [ -matchLine WIDTH RED GRN BLU ] \\\n"); Wr.PutText(stderr, " [ -dots DIAMETER RED GRN BLU ] \\\n"); Wr.PutText(stderr, " [ -boxes SIDE RED GRN BLU ] \\\n"); Wr.PutText(stderr, " [ -epsFormat ] \n"); Process.Exit(1); END; END; RETURN o END GetOptions; PROCEDURE ParseSegmentParam( pp: ParseParams.T ): PZSegment.T RAISES {ParseParams.Error} = VAR rev: BOOL; BEGIN WITH cvx = pp.getNextInt(0, LAST(NAT)), tot = pp.getNextInt(0, 100000), ini = pp.getNextInt(0, 100000), ns = pp.getNextInt(1, 100000), s = pp.getNext() DO IF Text.Equal(s, "+") THEN rev := FALSE ELSIF Text.Equal(s, "-") THEN rev := FALSE ELSE pp.error("bad segment direction flag") END; RETURN PZSegment.T{ cvx := cvx, tot := tot, ini := ini, ns := ns, rev := rev } END END ParseSegmentParam; PROCEDURE ParseColorParam( pp: ParseParams.T ): Color RAISES {ParseParams.Error} = BEGIN WITH red = pp.getNextReal(0.0, 1.0), grn = pp.getNextReal(0.0, 1.0), blu = pp.getNextReal(0.0, 1.0) DO RETURN Color{red, grn, blu} END END ParseColorParam; PROCEDURE FLR(x: LONG; prec: NAT): TEXT = BEGIN RETURN Fmt.LongReal(x, prec := prec, style := Fmt.Style.Fix) END FLR; BEGIN Main() END PZDrawCandGrids. (* 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. *)