INTERFACE PGMImageFilter; (* Last edited on 1999-10-30 12:56:17 by stolfi *) IMPORT PGM, IA; TYPE LONG = LONGREAL; NAT = CARDINAL; BOOL = BOOLEAN; Pixel = NAT; Interval = IA.T; (* SCANLINE I/O *) (* The follwoing procedures take as an optional parameter a `mask' image file, with the same dimensions as the main image. By convention, if a mask pixel is zero, the corresponding pixel of the main image is undefined (inexistant, unknown, invalid, etc.). *) PROCEDURE ReadPixels( imRd: PGM.Reader; (* Image reader. *) VAR im: ARRAY OF NAT; (* Pixels to read. *) mkRd: PGM.Reader := NIL; (* Optional `mask' image reader. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Reads a bunch of pixels from "imRd" into the aray "im". If "mkRd" is not NIL, it is interpreted as a `mask': pixel "im[p]" is set to "noPixel" whenever the corresponding mask pixel is zero. *) PROCEDURE WritePixels( imWr: PGM.Writer; (* Image reader. *) READONLY im: ARRAY OF Pixel; (* Pixels to write. *) mkWr: PGM.Writer := NIL; (* Optional `mask' image writer. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Writes a bunch of pixels "im" to the writer "imWr". If "mkWr" is not NIL, it is interpreted as a mask: whenever "im[p] = noPixel", it writes "0" to both "imWr" and "mkWr"; otherwise it writes "im[p]" to "imWr" and "1" to "mkWr". If "mkWr = NIL" or "im[p] # noPixel", assumes "im[p]" lies in the valid pixel range for "imWr". *) (* CYCLIC MULTI-ROW BUFFERS *) TYPE PixelBuffer = ARRAY OF ARRAY OF Pixel; LongBuffer = ARRAY OF ARRAY OF LONG; IntervalBuffer = ARRAY OF ARRAY OF Interval; (* A pixel buffer is a set of consecutive rows of some image. If the buffer has "B" rows, then row "y" of the image is row "y MOD B" of the buffer. *) PROCEDURE ReadPixelBufferRow( imRd: PGM.Reader; (* Image reader. *) yp: INTEGER; (* "Y" coordinate of scanline to read. *) VAR imBuf: PixelBuffer; (* Cyclic pixel row buffer. *) NY: NAT; (* Number of rows in image. *) mkRd: PGM.Reader := NIL; (* Optional `mask' image reader. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* If "yp" is in the range "[0..NY-1]", reads row "yp" of the image from "imRd" into the appropriate buffer row. Otherwise does nothing. The "mkRd" and "noPixel" parameters are the same as in "ReadPixels". *) (* FLOAT/PIXEL CONVERSION *) PROCEDURE PixelRange(maxPixel: NAT; noPixel: NAT): NAT; (* The effective maximum pixel value for scaling purposes, not counting "noPixel".in "[0..maxPixel]". That means "maxPixel-1" if "noPixel" lies in the range "[0..maxPixel]", and "maxPixel" otherwise. *) PROCEDURE FloatPixels( READONLY im: ARRAY OF NAT; (* Integer pixels. *) maxPixel: NAT; (* Maximum pixel value, as declared in PGM file. *) VAR fm: ARRAY OF Interval; (* Converted pixels. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Given a row "im" of image pixels, converts each pixel "im[p]" into a floating-point interval "fm[p]". If "im[p] = noPixel", the interval will be "[0 _ 1]". Otherwise the range "[0 .. maxPixel]" (skipping over "noPixel") is mapped linearly to "[0 _ 1]". *) PROCEDURE FixLongPixels( READONLY fm: ARRAY OF LONG; (* Pixel value ranges. *) maxPixel: NAT; (* Maximum pixel value, as in PGM file. *) VAR im: ARRAY OF NAT; (* Integer pixels. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Converts each pixel from a floating-point number to an integer pixel value. Maps linearly each floating-point value from the range "[0 _ 1]" to the range "[0 .. maxPixel]" (skipping over the "noPixel" value). Clips input pixels to the interval "[0 _ 1]". *) PROCEDURE FixIntervalPixels( READONLY fm: ARRAY OF Interval; (* Pixel value ranges. *) maxPixel: NAT; (* Maximum pixel value, as in PGM file. *) maxError: LONG; (* Maximum uncertainty allowed. *) VAR im: ARRAY OF NAT; (* Integer pixels. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Converts each pixel from a floating-point interval to an integer pixel value. Clips the interval's center value to the interval "[0 _ 1]", maps it linearly from the range "[0 _ 1]" to the range "[0 .. maxPixel]" (skipping over the "noPixel" value), and rounds the result to the nearest pixel. However, if the interval "fm[x]" is empty, or wider than "2*maxError", sets "im[x]" to "noPixel". TO DO: should take quantization error into account as well, when deciding whether to use "noPixel". *) PROCEDURE FixIntervalPixelErrors( READONLY fm: ARRAY OF Interval; (* Pixel value ranges. *) maxPixel: NAT; (* Maximum pixel value, as in PGM file. *) VAR im: ARRAY OF NAT; (* Integer pixels. *) noPixel: NAT := LAST(NAT); (* `Null' pixel value. *) ); (* Converts the uncertainty in each interval-valued pixel to an integer pixel value. Clips the interval's width to the interval "[0 _ 1]", maps it linearly from the range "[0 _ 1]" to the range "[0 .. maxPixel]" (skipping over the "noPixel" value), and rounds the result to the nearest pixel. However, if the interval "fm[x]" is empty, sets the result to "noPixel". *) (* DISCRETE IMAGE FILTERING *) PROCEDURE ReplacePixelValue(a, b: NAT; VAR im: ARRAY OF NAT); (* Replaces all occurrences of "a" by "b" in the pixel array "im". *) (* NEIGHBORHOODS *) (* PIXEL VALUE HISTOGRAMS *) TYPE Histogram = ARRAY OF LONG; PROCEDURE WeightedHistogram( xp, yp: NAT; READONLY imBuf: PixelBuffer; NX, NY: NAT; READONLY w: ARRAY OF ARRAY OF LONG; VAR hist: Histogram; ); (* Computes a distance-weighted histogram of the pixels around pixel "(xp,yp)". The distance weight matrix "w" should have an odd number of columns "2*HX+1" and of rows "2*HY+1". The histogram entry "hist[v]" is the sum of all weights "w[yw+HY, xw+HX]" for the pixels "(xp+xw,yp+yw)" whose value is "v". Assumes that the input scanline "y", for "y" in "[yp-HY..yp+HX]" intersection "[0..NY-1]", is stored in the cyclic scanline buffer "imBuf". Pixels outside the image bounds or with values greater than "LAST(hist)" are ignored. *) PROCEDURE RankFromHistogram( p: NAT; READONLY hist: Histogram; fuzz: LONG := 0.0d0; ): Interval; (* Computes the rank of pixel "p" from the given histogram. The rank is computed as the sum of "hist[q]" times the `score' of "p" when compared against "q". The score is 0 if "p-q < -2*fuzz", 0.5 if "p = q", 1 if "p-q > +2*fuzz"; and increases gradually as "p-q" varies between "-2*fuzz" and "+2*fuzz". The histogram should be normalized to have sum "S" at most 1; the balance "1-S" is assumed to consist of "unknown" pixels, whose value can be anything between 0 and LAST(hist). In that case, the output will be an interval of nonzero width. In any case, the resulting interval is contained between 0 (p is somewhat below the histogram's minimum) and 1 (p is somewhat above the maximum).*) PROCEDURE MeanFromHistogram(READONLY hist: Histogram): LONG; (* The mean pixel value of the given histogram. *) PROCEDURE DeviationFromHistogram(READONLY hist: Histogram; mean: LONG): LONG; (* The root mean square value of "q - mean", where "q" are the pixel values, weighted by "hist[q]". Includes the estimated effect of the quantization error (nominal variance = 1/12 of a pixel). *) PROCEDURE GroundFromHistogram(READONLY hist: Histogram; alpha: LONG): LONG; (* Extracts from the histogram of a mostly bi-level image the probable background or foreground pixel value. The result is actually the weighted average of the pixel values recorded in the histogram, where the weight of value "q" is "alpha**q*hist[q]". Thus, if "alpha > 1" the result is close to the low end of the pixel range; if "alpha < 1" it is close to the high end; if "alpha = 1" it is simply the mean value. Warning: may bomb out if "alpha**N" exceeds 2**2000, where "N = NUMBER(hist)". *) PROCEDURE FractileFromHistogram(READONLY hist: Histogram; fraction: LONG): LONG; (* Returns the pixel value "y" such that the mass of all entries of "hist" that are less than "y" is "fraction" times the total mass. *) (* CONTINUOUS IMAGE FILTERING *) PROCEDURE FilterRow( READONLY im: ARRAY OF Interval; READONLY w: ARRAY OF LONG; VAR om: ARRAY OF Interval; base: NAT := 0; step: NAT := 1; noPixel: Interval := Interval{0.0d0, 1.0d0}; ); (* Filters and samples a row of pixels. The input pixels are given by the intervals "im[x]", the output is returned in "om[x]". Each output pixel "om[xf]" is the "w"-weighted linear combination of pixels surrounding input pixel "xp = base + step*xf". The weight vector "w" should have an odd number of elements, the middle one being taken as the weight of pixel "xp". Assumes that the non-existent pixels have range "noPixel". *) PROCEDURE MixRows( yp: NAT; READONLY imBuf: IntervalBuffer; NX, NY: NAT; READONLY w: ARRAY OF LONG; VAR om: ARRAY OF Interval; noPixel: Interval := Interval{0.0d0, 1.0d0}; ); (* Given a set of consecutive scanlines, combines them with specified weights. Returns the result in "om". The weight vector "w" should have an odd number "2*HW+1" of elements, with the center one being used for scanline "yp". Assumes that the input scanlines "[yp-HW..yp+HW]", if they exist in the image, are stored in the cyclic buffer "imBuf" (see "ReadBufferRow"). Assumes that the non-existent pixels have range "noPixel". *) PROCEDURE WeightedSum( xp, yp: NAT; READONLY imBuf: IntervalBuffer; NX, NY: NAT; READONLY w: ARRAY OF ARRAY OF LONG; noPixel: Interval := Interval{0.0d0, 1.0d0}; ): Interval; (* Computes a distance-weighted sum of the pixels surrounding pixel "(xp,yp)". The distance weight matrix "w" should have an odd number of columns "2*HX+1" and of rows "2*HY+1". The histogram entry "hist[v]" is the sum of all weights "w[yw+HY, xw+HX]" for the pixels "(xp+xw,yp+yw)" whose value is "v". Assumes that the input scanline "y", for "y" in "[yp-HY..yp+HX]" intersection "[0..NY-1]", is stored in the cyclic scanline buffer "imBuf". Pixels outside the image bounds are assumed to have range "noPixel". *) END PGMImageFilter.