IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Changeset 2783


Ignore:
Timestamp:
Dec 21, 2004, 4:07:04 PM (22 years ago)
Author:
Paul Price
Message:

Running fast now. Only optimisation tweaks to be made now.

Location:
trunk/stac/src
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/stac/src/stac.c

    r2764 r2783  
    3636    psTraceSetLevel("stac.rejection",10);
    3737    psTraceSetLevel("stac.time",10);
    38    
     38    psTraceSetLevel("stac.area",10);
    3939
    4040    // Set logging level
     
    5050    psArray *maps = stacReadMaps(config);
    5151
     52    psTrace("stac.time", 1, "I/O completed at %f seconds\n", getTime() - startTime);
     53
    5254    // Invert maps
    5355    psArray *inverseMaps = stacInvertMaps(maps);
     
    5759
    5860    // Transform inputs and errors
     61    psArray *transformed = NULL;
    5962    psArray *transformedErrors = NULL;
    60     psArray *transformed = stacTransform(inputs, inverseMaps, errors, &transformedErrors, NULL, config);
     63    (void)stacTransform(&transformed, &transformedErrors, inputs, inverseMaps, errors, NULL, NULL, config);
    6164
    6265    psTrace("stac.time",1,"Transformation completed at %f seconds\n", getTime() - startTime);
     
    6467    // Combine with rejection
    6568    psArray *rejected = NULL;
    66     psImage *combined = stacCombine(transformed, transformedErrors, 2,  &rejected, config);
     69    psImage *combined = NULL;
     70    (void)stacCombine(&combined, &rejected, transformed, transformedErrors, config->nReject, NULL, config);
     71
     72    psTrace("stac.time",1,"First combination completed at %f seconds\n", getTime() - startTime);
    6773
    6874#ifdef TESTING
     
    7581    psArray *regions = psArrayAlloc(inputs->n); // Array of images denoting regions of interest
    7682    for (int i = 0; i < inputs->n; i++) {
    77         fprintf(stderr, "Finding area of interest for image %d\n", i);
    7883        regions->data[i] = stacAreaOfInterest(rejected->data[i], inverseMaps->data[i],
    7984                                              ((psImage*)(inputs->data[i]))->numCols,
     
    8994    psArray *rejectedSource = stacRejection(inputs, rejected, regions, maps, inverseMaps, config);
    9095
    91     // Redo transformation with the mask
    92     psFree(transformed);
    93     psFree(transformedErrors);
    94     psArray *transformedErrorsNew = NULL;
    95     psArray *transformedNew = stacTransform(inputs, inverseMaps, errors, &transformedErrorsNew,
    96                                             rejectedSource, config);
     96    // Get regions of interest in the output frame
     97    psImage *combineRegion = psImageAlloc(config->outnx, config->outny, PS_TYPE_U8);
     98    for (int y = 0; y < config->outny; y++) {
     99        for (int x = 0; x < config->outnx; x++) {
     100            combineRegion->data.U8[y][x] = 0;
     101        }
     102    }
     103    for (int i = 0; i < inputs->n; i++) {
     104        psImage *region = stacAreaOfInterest(rejectedSource->data[i], maps->data[i], config->outnx,
     105                                             config->outny);
     106        psBinaryOp(combineRegion, combineRegion, "+", region);
     107        psFree(region);
     108    }
     109
     110    // Redo transformation with the masks
     111    (void)stacTransform(&transformed, &transformedErrors, inputs, inverseMaps, errors, rejectedSource,
     112                        combineRegion, config);
    97113
    98114    // Combine the newly-transformed CR-free images, no rejection
    99     psFree(combined);
    100115    psFree(rejected);
    101116    rejected = NULL;
    102     combined = stacCombine(transformedNew, transformedErrorsNew, 0,  &rejected, config);
     117    (void)stacCombine(&combined, &rejected, transformed, transformedErrors, 0, combineRegion, config);
    103118
    104119    // Write output image
     
    107122    // Free everything I've used
    108123    stacConfigFree(config);
     124    psFree(combineRegion);
    109125    psFree(regions);
    110126    psFree(inputs);
     
    113129    psFree(errors);
    114130    psFree(transformedErrors);
    115     psFree(transformedNew);
     131    psFree(transformed);
    116132    psFree(rejectedSource);
    117133    psFree(combined);
    118134
    119     psTrace("stac.time",1,"Combination completed at %f seconds\n", getTime() - startTime);
     135    psTrace("stac.time",1,"Final combination completed at %f seconds\n", getTime() - startTime);
    120136
    121137   // Check memory for leaks, corruption
  • trunk/stac/src/stac.h

    r2764 r2783  
    2828                                        // considered bad
    2929    float grad;                         // Multiplier of the gradient
     30    int nReject;                        // Number of rejection iterations
    3031} stacConfig;
    3132
     
    6768// stacTransform.c
    6869
    69 // Transform input images
    70 psArray *stacTransform(const psArray *images, // Array of images to be transformed
    71                        const psArray *maps, // Array of polynomials that do the transformation
    72                        const psArray *errors, // Array of error images
    73                        psArray **outErrors, // Array of output errors
    74                        const psArray *masks, // Masks of input images
    75                        const stacConfig *config // Configuration
     70// Transform input images, return success
     71bool stacTransform(psArray **outputs,   // Transformed images for output
     72                   psArray **outErrors, // Transformed error images for output
     73                   const psArray *images, // Array of images to be transformed
     74                   const psArray *maps, // Array of polynomials that do the transformation
     75                   const psArray *errors, // Array of error images to be transformed
     76                   const psArray *masks, // Masks of input images
     77                   const psImage *region, // Region of interest for transformation
     78                   const stacConfig *config // Configuration
    7679    );
    7780
     
    110113
    111114// Combine the transformed images
    112 psImage *stacCombine(psArray *images,   // Array of transformed images
    113                      psArray *errors,   // Array of transformed error images
    114                      int nReject,       // Number of rejection iterations
    115                      psArray **rejected, // Array of rejection masks
    116                      stacConfig *config // Configuration
     115bool stacCombine(psImage **combined,    // The combined image for output
     116                 psArray **rejected,    // Array of rejection masks
     117                 psArray *images,       // Array of transformed images
     118                 psArray *errors,       // Array of transformed error images
     119                 int nReject,           // Number of rejection iterations
     120                 psImage *region,       // Region to combine
     121                 stacConfig *config     // Configuration
    117122    );
    118123
  • trunk/stac/src/stacAreaOfInterest.c

    r2764 r2783  
    8989    }
    9090
     91    psTrace("stac.area", 3, "Finding area of interest....\n");
     92
     93
    9194    // Coordinates for the transformations
    9295    psPlane *inFrame = psAlloc(sizeof(psPlane));
  • trunk/stac/src/stacCombine.c

    r2764 r2783  
    2626    for (int i = 0; i < num; i++) {
    2727        if (! masks->data.U8[i]) {
    28             sum += values->data.F32[i] / SQUARE(errors->data.F32[i]);
    29             weights += 1.0 / SQUARE(errors->data.F32[i]);
     28            // "error" here is the variance
     29            sum += values->data.F32[i] / errors->data.F32[i];
     30            weights += 1.0 / errors->data.F32[i];
    3031        }
    3132    }
     
    5354
    5455
    55 psImage *stacCombine(psArray *images,   // Array of transformed images
    56                      psArray *errors,   // Array of transformed error images
    57                      int nReject,       // Number of rejection iterations
    58                      psArray **rejected, // Array of rejection masks
    59                      stacConfig *config // Configuration
     56
     57bool stacCombine(psImage **combined,    // The combined image for output
     58                 psArray **rejected,    // Array of rejection masks
     59                 psArray *images,       // Array of transformed images
     60                 psArray *errors,       // Array of transformed error images
     61                 int nReject,           // Number of rejection iterations
     62                 psImage *region,       // Region to combine
     63                 stacConfig *config     // Configuration
    6064    )
    6165{
     
    7680                    "Image size mismatch on error image %d\nExpected %dx%d, got %dx%d\n",
    7781                    i, numCols, numRows, image->numCols, image->numRows);
    78             return NULL;
     82            return false;
    7983        }
    8084        if ((error->numCols != numCols) || (error->numRows != numRows)) {
     
    8286                    "Image size mismatch on error image %d\nExpected %dx%d, got %dx%d\n",
    8387                    i, numCols, numRows, error->numCols, error->numRows);
    84             return NULL;
    85         }
     88            return false;
     89        }
     90    }
     91
     92    // Check combined image
     93    if (*combined == NULL) {
     94        *combined = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Combined image
     95    } else if (((*combined)->numRows != numRows) || ((*combined)->numCols != numCols)) {
     96        psError("stac.combine", "Size of combined image (%dx%d) does not match inputs (%dx%d)\n",
     97                (*combined)->numCols, (*combined)->numRows, numCols, numRows);
     98        return false;
     99    }
     100
     101    // Check area of interest
     102    if (region && ((region->numRows != numRows) || (region->numCols != numCols))) {
     103        psError("stac.combine", "Size of area of interest (%dx%d) does not match inputs (%dx%d)\n",
     104                region->numCols, region->numRows, numCols, numRows);
     105        return false;
    86106    }
    87107
    88108    psTrace("stac.combine", 1, "Combining images....\n");
    89     psTrace("stac.combine", 3, "Saturation: %f Bad: %f\n", saturated, bad);
    90109
    91110    psVector *pixels = psVectorAlloc(nImages, PS_TYPE_F32); // Will hold the pixels in the statistics step
    92111    psVector *deltas = psVectorAlloc(nImages, PS_TYPE_F32); // Will hold the errors in the statistics step
    93     psVector *mask = psVectorAlloc(nImages, PS_TYPE_U8); // Mask bad pixels
    94     psImage *combined = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Combined image
    95 
     112    psVector *mask = psVectorAlloc(nImages, PS_TYPE_U8); // Will hold the mask in the statistics step
    96113
    97114    // Set up rejection masks
     
    105122            return NULL;
    106123        }
     124
     125        // Create and initialise rejection masks
    107126        for (int i = 0; i < nImages; i++) {
    108             (*rejected)->data[i] = psImageAlloc(numCols, numRows, PS_TYPE_U8); // Create rejection mask
    109             // Zero out the mask
     127            (*rejected)->data[i] = psImageAlloc(numCols, numRows, PS_TYPE_U8);
    110128            for (int r = 0; r < numRows; r++) {
    111129                for (int c = 0; c < numCols; c++) {
     
    124142    for (int y = 0; y < numRows; y++) {
    125143        for (int x = 0; x < numCols; x++) {
     144
     145            // Only combine those pixels requested
     146            if (!region || (region && region->data.U8[y][x])) {
    126147           
    127             // Export pixels into the vector and get stats
    128             for (int i = 0; i < nImages; i++) {
    129                 float pixel = ((psImage*)images->data[i])->data.F32[y][x];
    130                 float delta = ((psImage*)errors->data[i])->data.F32[y][x];
    131                 pixels->data.F32[i] = pixel;
    132                 deltas->data.F32[i] = delta;
    133                 if ((pixel >= saturated) || (pixel <= bad) || (! isfinite(pixel)) || (! isfinite(delta))) {
    134                     mask->data.U8[i] = (psU8)1; // Don't use!
    135                 } else {
    136                     mask->data.U8[i] = (psU8)0; // Use.
    137                 }
    138             }
     148                // Export pixels into the vector and get stats
     149                for (int i = 0; i < nImages; i++) {
     150                    float pixel = ((psImage*)images->data[i])->data.F32[y][x];
     151                    float delta = ((psImage*)errors->data[i])->data.F32[y][x]; // This is the variance!
     152                    pixels->data.F32[i] = pixel;
     153                    deltas->data.F32[i] = delta;
     154                    if ((pixel >= saturated) || (pixel <= bad) || (! isfinite(pixel)) || (! isfinite(delta))) {
     155                        mask->data.U8[i] = (psU8)1; // Don't use!
     156                    } else {
     157                        mask->data.U8[i] = (psU8)0; // Use.
     158                    }
     159                }
    139160           
    140             float average = stacCombineMean(pixels, deltas, mask); // Combined value
     161                float average = stacCombineMean(pixels, deltas, mask); // Combined value
    141162
    142163#ifdef TESTING
    143             // Calculate chi^2
    144             chi2->data.F32[y][x] = 0.0;
    145             int numGoodPix = 0;
    146             for (int i = 0; i < nImages; i++) {
    147                 if (! mask->data.U8[i]) {
    148                     chi2->data.F32[y][x] += SQUARE((pixels->data.F32[i] - average) / deltas->data.F32[i]);
    149                     numGoodPix++;
    150                 }
    151             }
    152             chi2->data.F32[y][x] /= (float)numGoodPix;
    153 #endif
     164                // Calculate chi^2
     165                chi2->data.F32[y][x] = 0.0;
     166                int numGoodPix = 0;
     167                for (int i = 0; i < nImages; i++) {
     168                    if (! mask->data.U8[i]) {
     169                        chi2->data.F32[y][x] += SQUARE(pixels->data.F32[i] - average) / deltas->data.F32[i];
     170                        numGoodPix++;
     171                    }
     172                }
     173                chi2->data.F32[y][x] /= (float)numGoodPix;
     174#endif
     175               
     176                // Rejection iterations
     177                bool keepGoing = true;  // Keep going with rejection?
     178                for (int rejNum = 0; (rejNum < nReject) && keepGoing; rejNum++) {
     179                    float max = 0.0;    // Maximum deviation
     180                    int maxIndex = -1;  // Index of the maximum deviation
     181                    for (int i = 0; i < nImages; i++) {
     182                        if (!mask->data.U8[i] &&
     183                            ((pixels->data.F32[i] - average) / sqrtf(deltas->data.F32[i]) > max)) {
     184                            max = (pixels->data.F32[i] - average) / sqrtf(deltas->data.F32[i]);
     185                            maxIndex = i;
     186                        }
     187                    }
     188                    // Reject the pixel with the maximum deviation
     189                    if (max > reject) {
     190                        mask->data.U8[maxIndex] = 1;
     191                        ((psImage*)((*rejected)->data[maxIndex]))->data.U8[y][x] += 1;
     192                        // Re-do combination following rejection
     193                        average = stacCombineMean(pixels, deltas, mask);
     194                    } else {
     195                        keepGoing = false;
     196                    }
     197                }
    154198           
    155             // Rejection iterations
    156             bool keepGoing = true;      // Keep going with rejection?
    157             for (int rejNum = 0; (rejNum < nReject) && keepGoing; rejNum++) {
    158                 float max = 0.0;        // Maximum deviation
    159                 int maxIndex = -1;      // Index of the maximum deviation
    160                 for (int i = 0; i < nImages; i++) {
    161                     if (!mask->data.U8[i] && ((pixels->data.F32[i] - average) / deltas->data.F32[i] > max)) {
    162                         max = (pixels->data.F32[i] - average) / deltas->data.F32[i];
    163                         maxIndex = i;
    164                     }
    165                 }
    166                 // Reject the pixel with the maximum deviation
    167                 if (max > reject) {
    168                     mask->data.U8[maxIndex] = 1;
    169                     ((psImage*)((*rejected)->data[maxIndex]))->data.U8[y][x] += 1.0;
    170                     // Re-do combination following rejection
    171                     average = stacCombineMean(pixels, deltas, mask);
    172                 } else {
    173                     keepGoing = false;
    174                 }
    175             }
    176            
    177             combined->data.F32[y][x] = average;
     199                (*combined)->data.F32[y][x] = average;
     200
     201            } // Pixels of interest
     202
    178203        }
    179204    } // Iterating over output pixels
  • trunk/stac/src/stacConfig.c

    r2711 r2783  
    99{
    1010    fprintf (stderr, "STAC: Simultaneous Telescope Array Combination\n"
    11              "Usage: %s [-h] [-v] [-g GAIN] [-r RN] [-o NX NY] [-s SAT] [-b BAD] [-k REJ] [-k FRAC] [-G GRAD] OUT IN1 IN2...\n"
     11             "Usage: %s [-h] [-v] [-g GAIN] [-r RN] [-o NX NY] [-s SAT] [-b BAD] [-k REJ] [-n NREJECT] [-k FRAC] [-G GRAD] OUT IN1 IN2...\n"
    1212             "where\n"
    1313             "\t-h           Help (this info)\n"
     
    1919             "\t-b BAD       Bad level (0)\n"
    2020             "\t-k REJ       Rejection level (k-sigma; 3.5)\n"
     21             "\t-n NREJECT   Number of rejection iterations (2)\n"
    2122             "\t-f FRAC      Fraction of pixel to be marked before considered bad (0.5)\n"
    2223             "\t-d GRAD      Gradient threshold for pixel to be marked (0.0)\n"
     
    4546    config->frac = 0.5;
    4647    config->grad = 0.4;
     48    config->nReject = 2;
    4749
    4850    return config;
     
    7476
    7577    /* Parse command-line arguments using getopt */
    76     while ((opt = getopt(argc, argv, "hvg:r:o:s:b:k:f:G:")) != -1) {
     78    while ((opt = getopt(argc, argv, "hvg:r:o:s:b:k:n:f:G:")) != -1) {
    7779        switch (opt) {
    7880          case 'h':
     
    115117          case 'k':
    116118            if (sscanf(optarg, "%f", &config->reject) != 1) {
     119                help(programName);
     120            }
     121            break;
     122          case 'n':
     123            if (sscanf(optarg, "%d", &config->nReject) != 1) {
    117124                help(programName);
    118125            }
  • trunk/stac/src/stacTransform.c

    r2751 r2783  
    105105
    106106
    107 psArray *stacTransform(const psArray *images, // Array of images to be transformed
    108                        const psArray *maps, // Array of polynomials that do the transformation
    109                        const psArray *errors, // Array of error images to be transformed
    110                        psArray **outErrors, // Transformed error images for output
    111                        const psArray *masks, // Masks of input images
    112                        const stacConfig *config // Configuration
     107bool stacTransform(psArray **outputs,   // Transformed images for output
     108                   psArray **outErrors, // Transformed error images for output
     109                   const psArray *images, // Array of images to be transformed
     110                   const psArray *maps, // Array of polynomials that do the transformation
     111                   const psArray *errors, // Array of error images to be transformed
     112                   const psArray *masks, // Masks of input images
     113                   const psImage *region, // Region of interest for transformation
     114                   const stacConfig *config // Configuration
    113115    )
    114116{
     
    121123        psError("stac.transform", "Number of maps (%d) does not match number of images (%d).\n",
    122124                maps->n, images->n);
    123         return NULL;
     125        return false;
    124126    }
    125127    if (errors && (images->n != errors->n)) {
    126128        psError("stac.transform", "Number of error images (%d) does not match number of images (%d).\n",
    127129                errors->n, images->n);
    128         return NULL;
    129     }
    130 
     130        return false;
     131    }
     132
     133    // Allocate the output images if required, otherwise check the number
     134    if (*outputs == NULL) {
     135        *outputs = psArrayAlloc(nImages);
     136        psTrace("stac.transform", 5, "Allocating space for transformed images, %dx%d\n", nx, ny);
     137        for (int i = 0; i < nImages; i++) {
     138            (*outputs)->data[i] = psImageAlloc(nx, ny, PS_TYPE_F32);
     139        }
     140    } else if ((*outputs)->n != nImages) {
     141        psError("stac.transform", "Number of output images (%d) does not match number of input images "
     142                "(%d).\n", (*outputs)->n, nImages);
     143        return false;
     144    }
     145
     146    // Allocate the output error images, if required, otherwise check the number
    131147    if (errors && (*outErrors == NULL)) {
    132         // Allocate the output error images, if required
    133148        *outErrors = psArrayAlloc(errors->n);
     149        psTrace("stac.transform", 5, "Allocating space for transformed error images, %dx%d\n", nx, ny);
     150        for (int i = 0; i < nImages; i++) {
     151            (*outErrors)->data[i] = psImageAlloc(nx, ny, PS_TYPE_F32);
     152        }
    134153    } else if (errors->n != (*outErrors)->n) {
    135154        psError("stac.transform", "Number of error output images (%d) does not match number of input"
    136155                "images (%d).\n", (*outErrors)->n, errors->n);
    137         return NULL;
    138     }
    139 
     156        return false;
     157    }
     158
     159    // Check the masks, if specified
    140160    if (masks != NULL) {
    141161        if (masks->n != nImages) {
    142162            psError("stac.transform", "Number of masks (%d) does not match number of input images (%d).\n",
    143163                    masks->n, nImages);
    144             return NULL;
     164            return false;
    145165        }
    146166        for (int i = 0; i < nImages; i++) {
     
    151171                         "Size of input mask (%dx%d) does not match that of input image (%dx%d).\n",
    152172                         mask->numCols, mask->numRows, image->numCols, image->numRows);
    153                 return NULL;
     173                return false;
    154174            }
    155175        }
    156176    }
    157177
    158     // Arrays of images for return
    159     psArray *outImages = psArrayAlloc(nImages); // Output images
    160178
    161179    // Stuff for the transformations
    162180    psPlane *detector = psAlloc(sizeof(psPlane)); // Coordinates on the detector
    163181    psPlane *sky = psAlloc(sizeof(psPlane)); // Coordinates on the sky
     182
    164183
    165184    // Iterate over the images
     
    171190        psPlaneTransform *map = maps->data[n]; // The map
    172191        psImage *error = errors->data[n]; // The error image
    173 
    174         // Initialise the output images
    175         psImage *outImage = psImageAlloc(nx, ny, PS_TYPE_F32);
    176         psImage *outError = psImageAlloc(nx, ny, PS_TYPE_F32);
    177         psTrace("stac.transform", 5, "Allocating space for transformed image, %dx%d\n", nx, ny);
     192        psImage *outImage = (*outputs)->data[n]; // The output image
     193        psImage *outError = (*outErrors)->data[n]; // The output error image
    178194
    179195#if 0
     
    193209        }
    194210
    195 #if 0
    196         // Calculate scales; could be adapted for higher order than linear by moving this into the
    197         // pixel-dependent section and calculating derivatives
    198         double xscale = 0.5*sqrt(SQUARE(map->x->coeff[0][1]) + SQUARE(map->x->coeff[1][0]));
    199         double yscale = 0.5*sqrt(SQUARE(map->y->coeff[0][1]) + SQUARE(map->y->coeff[1][0]));
    200         double areascale = 0.25 / xscale / yscale;
    201 #endif
    202 
    203211        // Iterate over the output image pixels
    204212        for (int y = 0; y < ny; y++) {
    205213            for (int x = 0; x < nx; x++) {
    206                 // Transform!
    207                 sky->x = (double)x + 0.5;
    208                 sky->y = (double)y + 0.5;
    209                 (void)psPlaneTransformApply(detector, map, sky);
    210 
    211                 // Change PS_INTERPOLATE_BILINEAR to best available technique.
    212                 outImage->data.F32[y][x] = (psF32)psImagePixelInterpolate(image, detector->x + 0.5,
    213                                                                           detector->y + 0.5, mask, 1, 0.0,
    214                                                                           PS_INTERPOLATE_BILINEAR);
    215                 outError->data.F32[y][x] = (psF32)p_psImageErrorInterpolateBILINEAR_F32(error,
    216                                                                                         detector->x + 0.5,
    217                                                                                         detector->y + 0.5,
    218                                                                                         mask, 1, 0.0);
    219                 outError->data.F32[y][x] = sqrtf(outError->data.F32[y][x]);
     214
     215                // Only transform those pixels requested
     216                if (!region || (region && region->data.U8[y][x])) {
     217                    // Transform!
     218                    sky->x = (double)x + 0.5;
     219                    sky->y = (double)y + 0.5;
     220                    (void)psPlaneTransformApply(detector, map, sky);
     221                   
     222                    // Change PS_INTERPOLATE_BILINEAR to best available technique.
     223                    outImage->data.F32[y][x] = (psF32)psImagePixelInterpolate(image, detector->x + 0.5,
     224                                                                              detector->y + 0.5, mask, 1, 0.0,
     225                                                                              PS_INTERPOLATE_BILINEAR);
     226                    // Error is actually the variance
     227                    outError->data.F32[y][x] = (psF32)p_psImageErrorInterpolateBILINEAR_F32(error,
     228                                                                                            detector->x + 0.5,
     229                                                                                            detector->y + 0.5,
     230                                                                                            mask, 1, 0.0);
     231                } // Pixels of interest
     232
    220233            }
    221234        } // Iterating over output pixels
     
    233246#endif
    234247
    235         // Plug the new images into arrays
    236         outImages->data[n] = outImage;
    237         (*outErrors)->data[n] = outError;
    238 
    239248    } // Iterating over images
    240249
     
    243252    psFree(sky);
    244253
    245     return outImages;
     254    return true;
    246255}
    247256
Note: See TracChangeset for help on using the changeset viewer.