IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Ignore:
Timestamp:
Mar 5, 2012, 5:19:48 PM (14 years ago)
Author:
mhuber
Message:

merging latest r33407 trunk changes to meh_branches/ppstack_test

Location:
branches/meh_branches/ppstack_test
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • branches/meh_branches/ppstack_test

  • branches/meh_branches/ppstack_test/psphot

  • branches/meh_branches/ppstack_test/psphot/src

  • branches/meh_branches/ppstack_test/psphot/src/psphotRadialApertures.c

    r31452 r33415  
    33bool psphotRadialAperturesSortFlux (psVector *radius, psVector *pixFlux, psVector *pixVar);
    44
     5// this function measures the radial aperture fluxes for the set of readouts.  this function
     6// may be called multiple times, presumably for different versions of PSF-matched or unmatched images. 
     7
    58// for now, let's store the detections on the readout->analysis for each readout
    6 bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule)
     9bool psphotRadialApertures (pmConfig *config, const pmFPAview *view, const char *filerule, int entry)
    710{
    811    bool status = true;
     12
     13    fprintf (stdout, "\n");
     14    psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Radial Apertures ---");
    915
    1016    // select the appropriate recipe information
     
    2026    int num = psphotFileruleCount(config, filerule);
    2127
     28    // skip the chisq image (optionally?)
     29    int chisqNum = psMetadataLookupS32 (&status, config->arguments, "PSPHOT.CHISQ.NUM");
     30    if (!status) chisqNum = -1;
     31
    2232    // loop over the available readouts
    2333    for (int i = 0; i < num; i++) {
    24         if (!psphotRadialAperturesReadout (config, view, filerule, i, recipe)) {
     34        if (i == chisqNum) continue; // skip chisq image
     35
     36        if (!psphotRadialAperturesReadout (config, view, filerule, i, recipe, entry)) {
    2537            psError (PSPHOT_ERR_CONFIG, false, "failed on measure extended source aperture-like parameters for %s entry %d", filerule, i);
    2638            return false;
     
    3042}
    3143
     44// these values are used by all threads repeatedly (and are not modified)
     45static psVector *aperRadii = NULL;
     46static psVector *aperRadii2 = NULL;
     47static float outerRadius = NAN;
     48static float SN_LIM = NAN;
     49static psImageMaskType maskVal = 0;
     50
    3251// aperture-like measurements for extended sources
    3352// flux in simple, circular apertures
    34 bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe) {
     53// 'entry' tells us which of the matched-PSF images we are working on (0 == unmatched image, also non-stack psphot)
     54bool psphotRadialAperturesReadout (pmConfig *config, const pmFPAview *view, const char *filerule, int index, psMetadata *recipe, int entry) {
    3555
    3656    bool status;
     
    6383    }
    6484
    65     // radMax stores the upper bounds of the annuli
     85    // determine the number of allowed threads
     86    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads
     87    if (!status) {
     88        nThreads = 0;
     89    }
     90
     91    // aperRadii stores the upper bounds of the annuli
    6692    // XXX keep the same name here as for the petrosian / elliptical apertures?
    67     psVector *radMax = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
    68     psAssert (radMax, "annular bins (RADIAL.ANNULAR.BINS.UPPER) are not defined in the recipe");
    69     psAssert (radMax->n, "no valid annular bins (RADIAL.ANNULAR.BINS.UPPER) are define");
    70     float outerRadius = radMax->data.F32[radMax->n - 1];
     93    aperRadii = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
     94    psAssert (aperRadii, "annular bins (RADIAL.ANNULAR.BINS.UPPER) are not defined in the recipe");
     95    psAssert (aperRadii->n, "no valid annular bins (RADIAL.ANNULAR.BINS.UPPER) are define");
     96
     97    outerRadius = aperRadii->data.F32[aperRadii->n - 1];
     98
     99    // save the R^2 values as well for quicker comparison
     100    aperRadii2 = psVectorAlloc(aperRadii->n, PS_TYPE_F32);
     101    for (int i = 0; i < aperRadii->n; i++) {
     102        aperRadii2->data.F32[i] = PS_SQR(aperRadii->data.F32[i]);
     103    }
    71104
    72105    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
    73     psImageMaskType maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
     106    maskVal = psMetadataLookupImageMask(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
    74107    assert (maskVal);
    75108
    76109    // S/N limit to perform full non-linear fits
    77     float SN_LIM = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
     110    SN_LIM = psMetadataLookupF32 (&status, recipe, "RADIAL_APERTURES_SN_LIM");
    78111
    79112    // source analysis is done in S/N order (brightest first)
     
    81114    sources = psArraySort (sources, pmSourceSortByFlux);
    82115
     116    // XXX make this consistent with entry 0 == unmatched
     117    int nEntry = 1;
     118    psVector *fwhmValues = psMetadataLookupVector(&status, readout->analysis, "STACK.PSF.FWHM.VALUES");
     119    if (fwhmValues) {
     120        psAssert (entry < fwhmValues->n, "inconsistent matched-PSF entry");
     121        nEntry = fwhmValues->n;
     122    }
     123    if (entry > 0) {
     124        psLogMsg ("psphot", PS_LOG_DETAIL, "Radial Apertures for matched image %s : PSF FWHM = %f pixels\n", file->name, fwhmValues->data.F32[entry]);
     125    } else {
     126        psLogMsg ("psphot", PS_LOG_DETAIL, "Radial Apertures for unmatched image %s\n", file->name);
     127    }
     128
    83129    // option to limit analysis to a specific region
    84130    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
    85     psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
    86     if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
     131    psRegion *AnalysisRegion = psRegionAlloc(0,0,0,0);
     132    *AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
     133    if (psRegionIsNaN (*AnalysisRegion)) psAbort("analysis region mis-defined");
     134
     135    // choose Cx, Cy (see psphotThreadTools.c for overview of the concepts)
     136    int Cx = 1, Cy = 1;
     137    psphotChooseCellSizes (&Cx, &Cy, readout, nThreads);
     138
     139    psArray *cellGroups = psphotAssignSources (Cx, Cy, sources);
     140
     141    for (int i = 0; i < cellGroups->n; i++) {
     142
     143        psArray *cells = cellGroups->data[i];
     144
     145        for (int j = 0; j < cells->n; j++) {
     146
     147            // allocate a job -- if threads are not defined, this just runs the job
     148            psThreadJob *job = psThreadJobAlloc ("PSPHOT_RADIAL_APERTURES");
     149
     150            psArrayAdd(job->args, 1, readout);
     151            psArrayAdd(job->args, 1, cells->data[j]); // sources
     152            psArrayAdd(job->args, 1, AnalysisRegion);
     153            PS_ARRAY_ADD_SCALAR(job->args, entry,  PS_TYPE_S32);
     154            PS_ARRAY_ADD_SCALAR(job->args, nEntry, PS_TYPE_S32);
     155            PS_ARRAY_ADD_SCALAR(job->args, 0, PS_TYPE_S32); // this is used as a return value for Nradial
     156
     157            // set this to 0 to run without threading
     158# if (1)           
     159            if (!psThreadJobAddPending(job)) {
     160                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
     161                psFree(AnalysisRegion);
     162                return false;
     163            }
     164# else
     165            if (!psphotRadialApertures_Threaded(job)) {
     166                psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
     167                psFree(AnalysisRegion);
     168                return false;
     169            }
     170            psScalar *scalar = NULL;
     171            scalar = job->args->data[5];
     172            Nradial += scalar->data.S32;
     173            psFree(job);
     174# endif
     175        }
     176
     177        // wait for the threads to finish and manage results
     178        if (!psThreadPoolWait (false, true)) {
     179            psError(PS_ERR_UNKNOWN, false, "Unable to guess model.");
     180            psFree(AnalysisRegion);
     181            return false;
     182        }
     183
     184        // we have only supplied one type of job, so we can assume the types here
     185        psThreadJob *job = NULL;
     186        while ((job = psThreadJobGetDone()) != NULL) {
     187            if (job->args->n < 1) {
     188                fprintf (stderr, "error with job\n");
     189            } else {
     190                psScalar *scalar = NULL;
     191                scalar = job->args->data[5];
     192                Nradial += scalar->data.S32;
     193            }
     194            psFree(job);
     195        }
     196    }
     197    psFree (cellGroups);
     198    psFree(AnalysisRegion);
     199    psFree (aperRadii2);
     200
     201    psLogMsg ("psphot", PS_LOG_WARN, "radial source apertures: %f sec for %d objects\n", psTimerMark ("psphot.radial"), Nradial);
     202    return true;
     203}
     204 
     205bool psphotRadialApertures_Threaded (psThreadJob *job) {
     206
     207    int Nradial = 0;
     208
     209    // arguments: readout, sources, models, region, psfSize, maskVal, markVal
     210    pmReadout *readout      = job->args->data[0];
     211    psArray *sources        = job->args->data[1];
     212    psRegion *region        = job->args->data[2];
     213    int entry               = PS_SCALAR_VALUE(job->args->data[3],S32); // which psf-matched image are we working on? (0 == unmatched)
     214    int nEntry              = PS_SCALAR_VALUE(job->args->data[4],S32); // total number of psf-matched images + 1 unmatched
     215
     216    // storage for the derived pixel values (these are passed into psphotRadialApertureSource)
     217    psVector *pixRadius2 = psVectorAllocEmpty(100, PS_TYPE_F32);
     218    psVector *pixFlux    = psVectorAllocEmpty(100, PS_TYPE_F32);
     219    psVector *pixVar     = psVectorAllocEmpty(100, PS_TYPE_F32);
    87220
    88221    // choose the sources of interest
     
    90223
    91224        pmSource *source = sources->data[i];
     225
     226        // if we have checked the source validity on the basis of the object set, then
     227        // we either skip these tests below or we skip the source completely
     228        if (source->tmpFlags & PM_SOURCE_TMPF_RADIAL_SKIP) continue;
     229        if (source->tmpFlags & PM_SOURCE_TMPF_RADIAL_KEEP) goto keepSource;
    92230
    93231        // skip PSF-like and non-astronomical objects
     
    104242
    105243        // limit selection by analysis region
    106         if (source->peak->x < AnalysisRegion.x0) continue;
    107         if (source->peak->y < AnalysisRegion.y0) continue;
    108         if (source->peak->x > AnalysisRegion.x1) continue;
    109         if (source->peak->y > AnalysisRegion.y1) continue;
     244        if (source->peak->x < region->x0) continue;
     245        if (source->peak->y < region->y0) continue;
     246        if (source->peak->x > region->x1) continue;
     247        if (source->peak->y > region->y1) continue;
     248
     249    keepSource:
    110250
    111251        // allocate pmSourceExtendedParameters, if not already defined
    112         if (!source->radialAper) {
    113             source->radialAper = psArrayAlloc(1);
     252        // XXX check that nPSFsizes is consistent with targets
     253        if (source->parent) {
     254            if (!source->parent->radialAper) {
     255                source->parent->radialAper = psArrayAlloc(nEntry);
     256            }
     257        } else {
     258            if (!source->radialAper) {
     259                source->radialAper = psArrayAlloc(nEntry);
     260            }
    114261        }
    115262
     
    119266        }
    120267
    121         // we need to change the view for the radial aperture analysis, but we want to recover exactly
    122         // the original view; the following elements get destroyed by pmSourceRedefinePixels so save them:
    123         psImage *oldMaskObj   = psMemIncrRefCounter(source->maskObj);
    124         psImage *oldModelFlux = psMemIncrRefCounter(source->modelFlux);
    125         psImage *oldPSFimage  = psMemIncrRefCounter(source->psfImage);
    126         psRegion oldRegion    = source->region;
    127 
    128268        Nradial ++;
    129269
    130         // force source image to be a bit larger...
    131         pmSourceRedefinePixels (source, readout, source->peak->xf, source->peak->yf, outerRadius + 2);
    132 
    133         if (!psphotRadialApertureSource (source, recipe, maskVal, radMax, 0)) {
     270        if (!psphotRadialApertureSource (source, readout, entry, pixRadius2, pixFlux, pixVar)) {
    134271            psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->Mx, source->moments->My);
    135272        } else {
     
    137274        }
    138275
    139         pmSourceRedefinePixelsByRegion (source, readout, oldRegion);
    140         psFree(source->maskObj);   source->maskObj   = oldMaskObj;
    141         psFree(source->modelFlux); source->modelFlux = oldModelFlux;
    142         psFree(source->psfImage);  source->psfImage  = oldPSFimage;
    143        
    144276        // re-subtract the object, leave local sky
    145277        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
    146278    }
    147 
    148     psLogMsg ("psphot", PS_LOG_INFO, "radial source apertures: %f sec for %d objects\n", psTimerMark ("psphot.radial"), Nradial);
     279    psScalar *scalar = job->args->data[5];
     280    scalar->data.S32 = Nradial;
     281
     282    psFree (pixRadius2);
     283    psFree (pixFlux);
     284    psFree (pixVar);
     285
    149286    return true;
    150287}
    151288
    152 bool psphotRadialApertureSource (pmSource *source, psMetadata *recipe, psImageMaskType maskVal, const psVector *aperRadii, int entry) {
    153 
     289bool psphotRadialApertureSource (pmSource *source, pmReadout *readout, int entry, psVector *pixRadius2, psVector *pixFlux, psVector *pixVar) {
     290                                           
    154291    // if we are a child source, save the results to the parent source radial aperture array
    155292    psArray *radialAperSet = source->radialAper;
     
    163300    radialAperSet->data[entry] = radialAper;
    164301
    165     // storage for the derived pixel values
    166     psVector *pixRadius2 = psVectorAllocEmpty(100, PS_TYPE_F32);
    167     psVector *pixFlux    = psVectorAllocEmpty(100, PS_TYPE_F32);
    168     psVector *pixVar     = psVectorAllocEmpty(100, PS_TYPE_F32);
     302    // find the largest aperture of interest (use only apertures with inner radii <=
     303    // source->skyRadius)
     304    int lastAp = aperRadii->n;
     305    for (int i = 0; i < aperRadii->n; i++) {
     306        if (aperRadii->data.F32[i] < source->skyRadius) continue;
     307        lastAp = i + 1;
     308        break;
     309    }
    169310
    170311    // outer-most radius for initial truncation
    171     float Rmax  = aperRadii->data.F32[aperRadii->n - 1];
     312    float Rmax  = aperRadii->data.F32[lastAp - 1];
    172313    float Rmax2 = PS_SQR(Rmax);
    173314
    174     // store the R^2 values for the apertures
    175     psVector *aperRadii2 = psVectorAlloc(aperRadii->n, PS_TYPE_F32);
    176     for (int i = 0; i < aperRadii->n; i++) {
    177         aperRadii2->data.F32[i] = PS_SQR(aperRadii->data.F32[i]);
    178     }
     315    // in this function, the operatins are relative to the full image (readout->image, etc)
    179316
    180317    float xCM = NAN, yCM = NAN;
    181318    if (pmSourcePositionUseMoments(source)) {
    182         xCM = source->moments->Mx - 0.5 - source->pixels->col0; // coord of peak in subimage
    183         yCM = source->moments->My - 0.5 - source->pixels->row0; // coord of peak in subimage
     319        xCM = source->moments->Mx; // index coord of peak in readout
     320        yCM = source->moments->My; // index coord of peak in readout
    184321    } else {
    185         xCM = source->peak->xf - 0.5 - source->pixels->col0; // coord of peak in subimage
    186         yCM = source->peak->yf - 0.5 - source->pixels->row0; // coord of peak in subimage
    187     }
     322        xCM = source->peak->xf; // index coord of peak in readout
     323        yCM = source->peak->yf; // index coord of peak in readout
     324    }
     325
     326    int Nx = readout->image->numCols;
     327    int Ny = readout->image->numRows;
     328
     329    pixRadius2->n = 0;
     330    pixFlux->n = 0;
     331    pixVar->n = 0;
    188332
    189333    // one pass through the pixels to select the valid pixels and calculate R^2
    190     for (int iy = 0; iy < source->pixels->numRows; iy++) {
    191 
    192         float yDiff = iy - yCM;
    193         if (fabs(yDiff) > Rmax) continue;
    194 
    195         float *vPix = source->pixels->data.F32[iy];
    196         float *vWgt = source->variance->data.F32[iy];
    197         psImageMaskType  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.PS_TYPE_IMAGE_MASK_DATA[iy];
    198 
    199         for (int ix = 0; ix < source->pixels->numCols; ix++, vPix++, vWgt++) {
    200 
    201             if (vMsk) {
    202                 if (*vMsk & maskVal) {
    203                     vMsk++;
    204                     continue;
    205                 }
    206                 vMsk++;
    207             }
    208             if (isnan(*vPix)) continue;
    209 
    210             float xDiff = ix - xCM;
    211             if (fabs(xDiff) > Rmax) continue;
     334    for (int iy = -Rmax; iy < Rmax + 1; iy++) {
     335
     336        float yDiff = iy + 0.5 + yCM;  // y-coordinate at this offse
     337        int yPix = (int) yDiff;
     338
     339        if (yPix < 0) continue;
     340        if (yPix > Ny - 1) continue;
     341        if (fabs(iy) > Rmax) continue;
     342
     343        float *vPix = readout->image->data.F32[yPix];
     344        float *vWgt = readout->variance->data.F32[yPix];
     345        psImageMaskType  *vMsk = readout->mask->data.PS_TYPE_IMAGE_MASK_DATA[yPix];
     346
     347        for (int ix = -Rmax; ix < Rmax + 1; ix++) {
     348
     349            float xDiff = ix + 0.5 + xCM;  // x-coordinate at this offse
     350            int xPix = (int) xDiff;
     351           
     352            if (xPix < 0) continue;
     353            if (xPix > Nx - 1) continue;
     354            if (fabs(ix) > Rmax) continue;
     355           
     356            if (vMsk[xPix] & maskVal) continue;
     357            if (isnan(vPix[xPix])) continue;
    212358
    213359            // radius is just a function of (xDiff, yDiff)
    214             float r2  = PS_SQR(xDiff) + PS_SQR(yDiff);
     360            float r2  = PS_SQR(ix) + PS_SQR(iy);
    215361            if (r2 > Rmax2) continue;
    216362
    217363            psVectorAppend(pixRadius2, r2);
    218             psVectorAppend(pixFlux, *vPix);
    219             psVectorAppend(pixVar, *vWgt);
     364            psVectorAppend(pixFlux, vPix[xPix]);
     365            psVectorAppend(pixVar, vWgt[xPix]);
    220366        }
    221367    }
     
    226372    psVector *fill    = psVectorAlloc(aperRadii->n, PS_TYPE_F32); // surface brightness of radial bin
    227373
     374    // init the apertures of interest to 0.0, the rest go to NAN
    228375    psVectorInit (flux,    0.0);
    229376    psVectorInit (fluxStd, 0.0);
    230377    psVectorInit (fluxErr, 0.0);
    231378    psVectorInit (fill,    0.0);
     379    for (int i = lastAp; i < flux->n; i++) {
     380        flux->data.F32[i] = NAN;
     381        fluxStd->data.F32[i] = NAN;
     382        fluxErr->data.F32[i] = NAN;
     383        fill->data.F32[i] = NAN;
     384    }
    232385
    233386    float *rPix2 = pixRadius2->data.F32;
     
    236389        int j = 0;
    237390        float *aRad2 = aperRadii2->data.F32;
    238         for (; (*aRad2 < *rPix2) && (j < aperRadii2->n); j++, aRad2++);
    239         for (; j < aperRadii2->n; j++, aRad2++) {
     391        for (; (*aRad2 < *rPix2) && (j < lastAp); j++, aRad2++);
     392
     393        // XXX I can speed this up by only saving this single aperture
     394        for (; j < lastAp; j++, aRad2++) {
    240395            flux->data.F32[j]    += pixFlux->data.F32[i];
    241396            fluxStd->data.F32[j] += PS_SQR(pixFlux->data.F32[i]);
     
    249404       2) the fractional fill factor (count of valid pixels / effective area of the aperture
    250405       3) the error on the flux within that aperture
    251      */
    252 
    253     for (int i = 0; i < flux->n; i++) {
     406    */
     407
     408    for (int i = 0; i < lastAp; i++) {
    254409        // calculate the total flux for bin 'nOut'
    255410        float Area = M_PI*aperRadii2->data.F32[i];
     
    261416        // XXX report the total flux or the mask-corrected flux?
    262417        // flux->data.F32[i]    = SBmean * Area;
    263         // fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]) * Area / nPix;
     418        // fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]) * Area / Pinx;
    264419
    265420        fluxErr->data.F32[i] = sqrt(fluxErr->data.F32[i]);
     
    271426    }
    272427   
     428# if (1)
    273429    radialAper->flux = flux;
    274430    radialAper->fluxStdev = fluxStd;
    275431    radialAper->fluxErr = fluxErr;
    276432    radialAper->fill = fill;
    277 
    278     psFree (aperRadii2);
    279     psFree (pixRadius2);
    280     psFree (pixFlux);
    281     psFree (pixVar);
     433# else
     434    // XXX TEST
     435    psFree(flux);
     436    psFree(fluxStd);
     437    psFree(fluxErr);
     438    psFree(fill);
     439# endif
    282440
    283441    return true;
    284442}
     443
     444/*** below is a test to use a sort to speed this up, not very successfully ***/
    285445
    286446static int nCalls = 0;
     
    373533// *** pmSourceRadialProfileSortPair is a utility function for sorting a pair of vectors
    374534# define COMPARE_VECT(A,B) (radius->data.F32[A] < radius->data.F32[B])
    375 # define SWAP_VECT(TYPE,A,B) { \
    376   float tmp; \
    377   if (A != B) { \
    378     tmp = radius->data.F32[A]; \
    379     radius->data.F32[A] = radius->data.F32[B];  \
    380     radius->data.F32[B] = tmp; \
    381     tmp = pixFlux->data.F32[A]; \
    382     pixFlux->data.F32[A] = pixFlux->data.F32[B]; \
    383     pixFlux->data.F32[B] = tmp; \
    384     tmp = pixVar->data.F32[A]; \
    385     pixVar->data.F32[A] = pixVar->data.F32[B]; \
    386     pixVar->data.F32[B] = tmp; \
    387   } \
    388 }
     535# define SWAP_VECT(TYPE,A,B) {                                  \
     536        float tmp;                                              \
     537        if (A != B) {                                           \
     538            tmp = radius->data.F32[A];                          \
     539            radius->data.F32[A] = radius->data.F32[B];          \
     540            radius->data.F32[B] = tmp;                          \
     541            tmp = pixFlux->data.F32[A];                         \
     542            pixFlux->data.F32[A] = pixFlux->data.F32[B];        \
     543            pixFlux->data.F32[B] = tmp;                         \
     544            tmp = pixVar->data.F32[A];                          \
     545            pixVar->data.F32[A] = pixVar->data.F32[B];          \
     546            pixVar->data.F32[B] = tmp;                          \
     547        }                                                       \
     548    }
    389549
    390550bool psphotRadialAperturesSortFlux (psVector *radius, psVector *pixFlux, psVector *pixVar) {
Note: See TracChangeset for help on using the changeset viewer.