aboutsummaryrefslogtreecommitdiff
path: root/pkg/images/tv/display/zscale.x
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/images/tv/display/zscale.x')
-rw-r--r--pkg/images/tv/display/zscale.x623
1 files changed, 623 insertions, 0 deletions
diff --git a/pkg/images/tv/display/zscale.x b/pkg/images/tv/display/zscale.x
new file mode 100644
index 00000000..abbf2ecb
--- /dev/null
+++ b/pkg/images/tv/display/zscale.x
@@ -0,0 +1,623 @@
+# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+
+include <ctype.h>
+include <imhdr.h>
+include <imset.h>
+include <pmset.h>
+include <imio.h>
+
+# User callable routines.
+# ZSCALE -- Sample an image and compute greyscale limits.
+# MZSCALE -- Sample an image with pixel masks and compute greyscale limits.
+# ZSC_PMSECTION -- Create a pixel mask from an image section.
+# ZSC_ZLIMITS -- Compute Z transform limits from a sample of pixels.
+
+
+# ZSCALE -- Sample an image and compute greyscale limits.
+# A sample mask is created based on the input parameters and then
+# MZSCALE is called.
+
+procedure zscale (im, z1, z2, contrast, optimal_sample_size, len_stdline)
+
+pointer im # image to be sampled
+real z1, z2 # output min and max greyscale values
+real contrast # adj. to slope of transfer function
+int optimal_sample_size # desired number of pixels in sample
+int len_stdline # optimal number of pixels per line
+
+int nc, nl
+pointer sp, section, zpm, zsc_pmsection()
+errchk zsc_pmsection, mzscale
+
+begin
+ call smark (sp)
+ call salloc (section, SZ_FNAME, TY_CHAR)
+
+ # Make the sample image section.
+ switch (IM_NDIM(im)) {
+ case 1:
+ call sprintf (Memc[section], SZ_FNAME, "[*]")
+ default:
+ nc = max (1, min (IM_LEN(im,1), len_stdline))
+ nl = max (1, min (IM_LEN(im,2), optimal_sample_size / nc))
+ call sprintf (Memc[section], SZ_FNAME, "[*:%d,*:%d]")
+ call pargi (IM_LEN(im,1) / nc)
+ call pargi (IM_LEN(im,2) / nl)
+ }
+
+ # Make a mask and compute the greyscale limits.
+ zpm = zsc_pmsection (Memc[section], im)
+ call mzscale (im, zpm, NULL, contrast, optimal_sample_size, z1, z2)
+ call imunmap (zpm)
+ call sfree (sp)
+end
+
+
+# MZSCALE -- Sample an image with pixel masks and compute greyscale limits.
+# The image is sampled through a pixel mask. If no pixel mask is given
+# a uniform sample mask is generated. If a bad pixel mask is given
+# bad pixels in the sample are eliminated. Once the sample is obtained
+# the greyscale limits are obtained using the ZSC_ZLIMITS algorithm.
+
+procedure mzscale (im, zpm, bpm, contrast, maxpix, z1, z2)
+
+pointer im #I image to be sampled
+pointer zpm #I pixel mask for sampling
+pointer bpm #I bad pixel mask
+real contrast #I contrast parameter
+int maxpix #I maximum number of pixels in sample
+real z1, z2 #O output min and max greyscale values
+
+int i, ndim, nc, nl, npix, nbp, imstati()
+pointer sp, section, v, sample, zmask, bp, zim, pmz, pmb, buf
+pointer zsc_pmsection(), imgnlr()
+bool pm_linenotempty()
+errchk zsc_pmsection, zsc_zlimits
+
+begin
+ call smark (sp)
+ call salloc (section, SZ_FNAME, TY_CHAR)
+ call salloc (v, IM_MAXDIM, TY_LONG)
+ call salloc (sample, maxpix, TY_REAL)
+ zmask = NULL
+ bp = NULL
+
+ ndim = min (2, IM_NDIM(im))
+ nc = IM_LEN(im,1)
+ nl = IM_LEN(im,2)
+
+ # Generate a uniform sample mask if none is given.
+ if (zpm == NULL) {
+ switch (IM_NDIM(im)) {
+ case 1:
+ call sprintf (Memc[section], SZ_FNAME, "[*]")
+ default:
+ i = max (1., sqrt ((nc-1)*(nl-1) / real (maxpix)))
+ call sprintf (Memc[section], SZ_FNAME, "[*:%d,*:%d]")
+ call pargi (i)
+ call pargi (i)
+ }
+ zim = zsc_pmsection (Memc[section], im)
+ pmz = imstati (zim, IM_PMDES)
+ } else
+ pmz = imstati (zpm, IM_PMDES)
+
+ # Set bad pixel mask.
+ if (bpm != NULL)
+ pmb = imstati (bpm, IM_PMDES)
+ else
+ pmb = NULL
+
+ # Get the sample up to maxpix pixels.
+ npix = 0
+ nbp = 0
+ call amovkl (long(1), Memi[v], IM_MAXDIM)
+ repeat {
+ if (pm_linenotempty (pmz, Meml[v])) {
+ if (zmask == NULL)
+ call salloc (zmask, nc, TY_INT)
+ call pmglpi (pmz, Meml[v], Memi[zmask], 0, nc, 0)
+ if (pmb != NULL) {
+ if (pm_linenotempty (pmb, Meml[v])) {
+ if (bp == NULL)
+ call salloc (bp, nc, TY_INT)
+ call pmglpi (pmb, Meml[v], Memi[bp], 0, nc, 0)
+ nbp = nc
+ } else
+ nbp = 0
+
+ }
+ if (imgnlr (im, buf, Meml[v]) == EOF)
+ break
+ do i = 0, nc-1 {
+ if (Memi[zmask+i] == 0)
+ next
+ if (nbp > 0)
+ if (Memi[bp+i] != 0)
+ next
+ Memr[sample+npix] = Memr[buf+i]
+ npix = npix + 1
+ if (npix == maxpix)
+ break
+ }
+ if (npix == maxpix)
+ break
+ } else {
+ do i = 2, ndim {
+ Meml[v+i-1] = Meml[v+i-1] + 1
+ if (Meml[v+i-1] <= IM_LEN(im,i))
+ break
+ else if (i < ndim)
+ Meml[v+i-1] = 1
+ }
+ }
+ } until (Meml[v+ndim-1] > IM_LEN(im,ndim))
+
+ if (zpm == NULL)
+ call imunmap (zim)
+
+ # Compute greyscale limits.
+ call zsc_zlimits (Memr[sample], npix, contrast, z1, z2)
+
+ call sfree (sp)
+end
+
+
+# ZSC_PMSECTION -- Create a pixel mask from an image section.
+# This only applies the mask to the first plane of the image.
+
+pointer procedure zsc_pmsection (section, refim)
+
+char section[ARB] #I Image section
+pointer refim #I Reference image pointer
+
+int i, j, ip, ndim, temp, a[2], b[2], c[2], rop, ctoi()
+pointer pm, im, mw, dummy, pm_newmask(), im_pmmapo(), imgl1i(), mw_openim()
+define error_ 99
+
+begin
+ # Decode the section string.
+ call amovki (1, a, 2)
+ call amovki (1, b, 2)
+ call amovki (1, c, 2)
+ ndim = min (2, IM_NDIM(refim))
+ do i = 1, ndim
+ b[i] = IM_LEN(refim,i)
+
+ ip = 1
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (section[ip] == '[') {
+ ip = ip + 1
+
+ do i = 1, ndim {
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+
+ # Get a:b:c. Allow notation such as "-*:c"
+ # (or even "-:c") where the step is obviously negative.
+
+ if (ctoi (section, ip, temp) > 0) { # a
+ a[i] = temp
+ if (section[ip] == ':') {
+ ip = ip + 1
+ if (ctoi (section, ip, b[i]) == 0) # a:b
+ goto error_
+ } else
+ b[i] = a[i]
+ } else if (section[ip] == '-') { # -*
+ temp = a[i]
+ a[i] = b[i]
+ b[i] = temp
+ ip = ip + 1
+ if (section[ip] == '*')
+ ip = ip + 1
+ } else if (section[ip] == '*') # *
+ ip = ip + 1
+ if (section[ip] == ':') { # ..:step
+ ip = ip + 1
+ if (ctoi (section, ip, c[i]) == 0)
+ goto error_
+ else if (c[i] == 0)
+ goto error_
+ }
+ if (a[i] > b[i] && c[i] > 0)
+ c[i] = -c[i]
+
+ while (IS_WHITE(section[ip]))
+ ip = ip + 1
+ if (i < ndim) {
+ if (section[ip] != ',')
+ goto error_
+ } else {
+ if (section[ip] != ']')
+ goto error_
+ }
+ ip = ip + 1
+ }
+ }
+
+ # In this case make the values be increasing only.
+ do i = 1, ndim
+ if (c[i] < 0) {
+ temp = a[i]
+ a[i] = b[i]
+ b[i] = temp
+ c[i] = -c[i]
+ }
+
+ # Make the mask.
+ pm = pm_newmask (refim, 16)
+
+ rop = PIX_SET+PIX_VALUE(1)
+ if (c[1] == 1 && c[2] == 1)
+ call pm_box (pm, a[1], a[2], b[1], b[2], rop)
+
+ else if (c[1] == 1)
+ for (i=a[2]; i<=b[2]; i=i+c[2])
+ call pm_box (pm, a[1], i, b[1], i, rop)
+
+ else
+ for (i=a[2]; i<=b[2]; i=i+c[2])
+ for (j=a[1]; j<=b[1]; j=j+c[1])
+ call pm_point (pm, j, i, rop)
+
+ i = IM_NPHYSDIM(refim)
+ IM_NPHYSDIM(refim) = ndim
+ im = im_pmmapo (pm, refim)
+ IM_NPHYSDIM(refim) = i
+ dummy = imgl1i (im) # Force I/O to set header
+ ifnoerr (mw = mw_openim (refim)) { # Set WCS
+ call mw_saveim (mw, im)
+ call mw_close (mw)
+ }
+
+ return (im)
+
+error_
+ call error (1, "Error in image section specification")
+end
+
+
+.help zsc_zlimits
+.nf ___________________________________________________________________________
+ZSC_ZLIMITS -- Compute limits for a linear transform that best samples the
+the histogram about the median value. This is often called to compute
+greyscale limits from a sample of pixel values.
+
+If the number of pixels is too small an error condition is returned. If
+the contrast parameter value is zero the limits of the sample are
+returned. Otherwise the sample is sorted and the median is found from the
+central value(s). A straight line is fitted to the sorted sample with
+interative rejection. If more than half the pixels are rejected the full
+range is returned. The contrast parameter is used to adjust the transfer
+slope about the median. The final limits are the extension of the fitted
+line to the first and last array index.
+.endhelp ______________________________________________________________________
+
+define MIN_NPIXELS 5 # smallest permissible sample
+define MAX_REJECT 0.5 # max frac. of pixels to be rejected
+define GOOD_PIXEL 0 # use pixel in fit
+define BAD_PIXEL 1 # ignore pixel in all computations
+define REJECT_PIXEL 2 # reject pixel after a bit
+define KREJ 2.5 # k-sigma pixel rejection factor
+define MAX_ITERATIONS 5 # maximum number of fitline iterations
+
+
+# ZSC_ZLIMITS -- Compute Z transform limits from a sample of pixels.
+
+procedure zsc_zlimits (sample, npix, contrast, z1, z2)
+
+real sample[ARB] #I Sample of pixel values (possibly resorted)
+int npix #I Number of pixels
+real contrast #I Contrast algorithm parameter
+real z1, z2 #O Z transform limits
+
+int center_pixel, minpix, ngoodpix, ngrow, zsc_fit_line()
+real zmin, zmax, median
+real zstart, zslope
+
+begin
+ # Check for a sufficient sample.
+ if (npix < MIN_NPIXELS)
+ call error (1, "Insufficient sample pixels found")
+
+ # If contrast is zero return the range.
+ if (contrast == 0.) {
+ call alimr (sample, npix, z1, z2)
+ return
+ }
+
+ # Sort the sample, compute the range, and median pixel values.
+ # The median value is the average of the two central values if there
+ # are an even number of pixels in the sample.
+
+ call asrtr (sample, sample, npix)
+ zmin = sample[1]
+ zmax = sample[npix]
+
+ center_pixel = (npix + 1) / 2
+ if (mod (npix, 2) == 1)
+ median = sample[center_pixel]
+ else
+ median = (sample[center_pixel] + sample[center_pixel+1]) / 2
+
+ # Fit a line to the sorted sample vector. If more than half of the
+ # pixels in the sample are rejected give up and return the full range.
+ # If the user-supplied contrast factor is not 1.0 adjust the scale
+ # accordingly and compute Z1 and Z2, the y intercepts at indices 1 and
+ # npix.
+
+ minpix = max (MIN_NPIXELS, int (npix * MAX_REJECT))
+ ngrow = max (1, nint (npix * .01))
+ ngoodpix = zsc_fit_line (sample, npix, zstart, zslope,
+ KREJ, ngrow, MAX_ITERATIONS)
+
+ if (ngoodpix < minpix) {
+ z1 = zmin
+ z2 = zmax
+ } else {
+ if (contrast > 0)
+ zslope = zslope / contrast
+ z1 = max (zmin, median - (center_pixel - 1) * zslope)
+ z2 = min (zmax, median + (npix - center_pixel) * zslope)
+ }
+end
+
+
+# ZSC_FIT_LINE -- Fit a straight line to a data array of type real. This is
+# an iterative fitting algorithm, wherein points further than ksigma from the
+# current fit are excluded from the next fit. Convergence occurs when the
+# next iteration does not decrease the number of pixels in the fit, or when
+# there are no pixels left. The number of pixels left after pixel rejection
+# is returned as the function value.
+
+int procedure zsc_fit_line (data, npix, zstart, zslope, krej, ngrow, maxiter)
+
+real data[npix] # data to be fitted
+int npix # number of pixels before rejection
+real zstart # Z-value of pixel data[1] (output)
+real zslope # dz/pixel (output)
+real krej # k-sigma pixel rejection factor
+int ngrow # number of pixels of growing
+int maxiter # max iterations
+
+int i, ngoodpix, last_ngoodpix, minpix, niter
+real xscale, z0, dz, x, z, mean, sigma, threshold
+double sumxsqr, sumxz, sumz, sumx, rowrat
+pointer sp, flat, badpix, normx
+int zsc_reject_pixels(), zsc_compute_sigma()
+
+begin
+ call smark (sp)
+
+ if (npix <= 0)
+ return (0)
+ else if (npix == 1) {
+ zstart = data[1]
+ zslope = 0.0
+ return (1)
+ } else
+ xscale = 2.0 / (npix - 1)
+
+ # Allocate a buffer for data minus fitted curve, another for the
+ # normalized X values, and another to flag rejected pixels.
+
+ call salloc (flat, npix, TY_REAL)
+ call salloc (normx, npix, TY_REAL)
+ call salloc (badpix, npix, TY_SHORT)
+ call aclrs (Mems[badpix], npix)
+
+ # Compute normalized X vector. The data X values [1:npix] are
+ # normalized to the range [-1:1]. This diagonalizes the lsq matrix
+ # and reduces its condition number.
+
+ do i = 0, npix - 1
+ Memr[normx+i] = i * xscale - 1.0
+
+ # Fit a line with no pixel rejection. Accumulate the elements of the
+ # matrix and data vector. The matrix M is diagonal with
+ # M[1,1] = sum x**2 and M[2,2] = ngoodpix. The data vector is
+ # DV[1] = sum (data[i] * x[i]) and DV[2] = sum (data[i]).
+
+ sumxsqr = 0
+ sumxz = 0
+ sumx = 0
+ sumz = 0
+
+ do i = 1, npix {
+ x = Memr[normx+i-1]
+ z = data[i]
+ sumxsqr = sumxsqr + (x ** 2)
+ sumxz = sumxz + z * x
+ sumz = sumz + z
+ }
+
+ # Solve for the coefficients of the fitted line.
+ z0 = sumz / npix
+ dz = sumxz / sumxsqr
+
+ # Iterate, fitting a new line in each iteration. Compute the flattened
+ # data vector and the sigma of the flat vector. Compute the lower and
+ # upper k-sigma pixel rejection thresholds. Run down the flat array
+ # and detect pixels to be rejected from the fit. Reject pixels from
+ # the fit by subtracting their contributions from the matrix sums and
+ # marking the pixel as rejected.
+
+ ngoodpix = npix
+ minpix = max (MIN_NPIXELS, int (npix * MAX_REJECT))
+
+ for (niter=1; niter <= maxiter; niter=niter+1) {
+ last_ngoodpix = ngoodpix
+
+ # Subtract the fitted line from the data array.
+ call zsc_flatten_data (data, Memr[flat], Memr[normx], npix, z0, dz)
+
+ # Compute the k-sigma rejection threshold. In principle this
+ # could be more efficiently computed using the matrix sums
+ # accumulated when the line was fitted, but there are problems with
+ # numerical stability with that approach.
+
+ ngoodpix = zsc_compute_sigma (Memr[flat], Mems[badpix], npix,
+ mean, sigma)
+ threshold = sigma * krej
+
+ # Detect and reject pixels further than ksigma from the fitted
+ # line.
+ ngoodpix = zsc_reject_pixels (data, Memr[flat], Memr[normx],
+ Mems[badpix], npix, sumxsqr, sumxz, sumx, sumz, threshold,
+ ngrow)
+
+ # Solve for the coefficients of the fitted line. Note that after
+ # pixel rejection the sum of the X values need no longer be zero.
+
+ if (ngoodpix > 0) {
+ rowrat = sumx / sumxsqr
+ z0 = (sumz - rowrat * sumxz) / (ngoodpix - rowrat * sumx)
+ dz = (sumxz - z0 * sumx) / sumxsqr
+ }
+
+ if (ngoodpix >= last_ngoodpix || ngoodpix < minpix)
+ break
+ }
+
+ # Transform the line coefficients back to the X range [1:npix].
+ zstart = z0 - dz
+ zslope = dz * xscale
+
+ call sfree (sp)
+ return (ngoodpix)
+end
+
+
+# ZSC_FLATTEN_DATA -- Compute and subtract the fitted line from the data array,
+# returned the flattened data in FLAT.
+
+procedure zsc_flatten_data (data, flat, x, npix, z0, dz)
+
+real data[npix] # raw data array
+real flat[npix] # flattened data (output)
+real x[npix] # x value of each pixel
+int npix # number of pixels
+real z0, dz # z-intercept, dz/dx of fitted line
+int i
+
+begin
+ do i = 1, npix
+ flat[i] = data[i] - (x[i] * dz + z0)
+end
+
+
+# ZSC_COMPUTE_SIGMA -- Compute the root mean square deviation from the
+# mean of a flattened array. Ignore rejected pixels.
+
+int procedure zsc_compute_sigma (a, badpix, npix, mean, sigma)
+
+real a[npix] # flattened data array
+short badpix[npix] # bad pixel flags (!= 0 if bad pixel)
+int npix
+real mean, sigma # (output)
+
+real pixval
+int i, ngoodpix
+double sum, sumsq, temp
+
+begin
+ sum = 0
+ sumsq = 0
+ ngoodpix = 0
+
+ # Accumulate sum and sum of squares.
+ do i = 1, npix
+ if (badpix[i] == GOOD_PIXEL) {
+ pixval = a[i]
+ ngoodpix = ngoodpix + 1
+ sum = sum + pixval
+ sumsq = sumsq + pixval ** 2
+ }
+
+ # Compute mean and sigma.
+ switch (ngoodpix) {
+ case 0:
+ mean = INDEF
+ sigma = INDEF
+ case 1:
+ mean = sum
+ sigma = INDEF
+ default:
+ mean = sum / ngoodpix
+ temp = sumsq / (ngoodpix - 1) - sum**2 / (ngoodpix * (ngoodpix - 1))
+ if (temp < 0) # possible with roundoff error
+ sigma = 0.0
+ else
+ sigma = sqrt (temp)
+ }
+
+ return (ngoodpix)
+end
+
+
+# ZSC_REJECT_PIXELS -- Detect and reject pixels more than "threshold" greyscale
+# units from the fitted line. The residuals about the fitted line are given
+# by the "flat" array, while the raw data is in "data". Each time a pixel
+# is rejected subtract its contributions from the matrix sums and flag the
+# pixel as rejected. When a pixel is rejected reject its neighbors out to
+# a specified radius as well. This speeds up convergence considerably and
+# produces a more stringent rejection criteria which takes advantage of the
+# fact that bad pixels tend to be clumped. The number of pixels left in the
+# fit is returned as the function value.
+
+int procedure zsc_reject_pixels (data, flat, normx, badpix, npix,
+ sumxsqr, sumxz, sumx, sumz, threshold, ngrow)
+
+real data[npix] # raw data array
+real flat[npix] # flattened data array
+real normx[npix] # normalized x values of pixels
+short badpix[npix] # bad pixel flags (!= 0 if bad pixel)
+int npix
+double sumxsqr,sumxz,sumx,sumz # matrix sums
+real threshold # threshold for pixel rejection
+int ngrow # number of pixels of growing
+
+int ngoodpix, i, j
+real residual, lcut, hcut
+double x, z
+
+begin
+ ngoodpix = npix
+ lcut = -threshold
+ hcut = threshold
+
+ do i = 1, npix
+ if (badpix[i] == BAD_PIXEL)
+ ngoodpix = ngoodpix - 1
+ else {
+ residual = flat[i]
+ if (residual < lcut || residual > hcut) {
+ # Reject the pixel and its neighbors out to the growing
+ # radius. We must be careful how we do this to avoid
+ # directional effects. Do not turn off thresholding on
+ # pixels in the forward direction; mark them for rejection
+ # but do not reject until they have been thresholded.
+ # If this is not done growing will not be symmetric.
+
+ do j = max(1,i-ngrow), min(npix,i+ngrow) {
+ if (badpix[j] != BAD_PIXEL) {
+ if (j <= i) {
+ x = normx[j]
+ z = data[j]
+ sumxsqr = sumxsqr - (x ** 2)
+ sumxz = sumxz - z * x
+ sumx = sumx - x
+ sumz = sumz - z
+ badpix[j] = BAD_PIXEL
+ ngoodpix = ngoodpix - 1
+ } else
+ badpix[j] = REJECT_PIXEL
+ }
+ }
+ }
+ }
+
+ return (ngoodpix)
+end