- Timestamp:
- Jun 19, 2012, 5:24:19 PM (14 years ago)
- Location:
- branches/meh_branches/ppstack_test
- Files:
-
- 4 edited
-
. (modified) (1 prop)
-
psphot (modified) (1 prop)
-
psphot/src (modified) (1 prop)
-
psphot/src/psphotKronIterate.c (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/meh_branches/ppstack_test
- Property svn:mergeinfo changed
-
branches/meh_branches/ppstack_test/psphot
- Property svn:mergeinfo changed
/branches/eam_branches/ipp-20111122/psphot merged: 33070-33071,33086,33088,33094-33095,33612,33640 /branches/eam_branches/ipp-20120405/psphot (added) merged: 33946,33953
- Property svn:mergeinfo changed
-
branches/meh_branches/ppstack_test/psphot/src
- Property svn:mergeinfo changed
-
branches/meh_branches/ppstack_test/psphot/src/psphotKronIterate.c
r33415 r34041 1 1 # include "psphotInternal.h" 2 # ifndef ROUND 3 # define ROUND(X) ((int) ((X) + 0.5*SIGN(X))) 4 # endif 5 6 bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert); 7 bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal); 2 3 bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert, bool oldWindow); 4 bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal, bool applyWeight, psImage *smoothedPixels); 5 8 6 9 7 bool psphotKronIterate (pmConfig *config, const pmFPAview *view, const char *filerule) … … 11 9 bool status = true; 12 10 13 // return true;14 15 11 fprintf (stdout, "\n"); 16 12 psLogMsg ("psphot", PS_LOG_INFO, "--- psphot Kron Iterate ---"); … … 46 42 // psAssert (psf, "missing psf?"); 47 43 48 if (!psphotKronIterateReadout (config, recipe, view, readout, sources, psf)) {44 if (!psphotKronIterateReadout (config, recipe, view, filerule, readout, sources, psf, i)) { 49 45 psError (PSPHOT_ERR_CONFIG, false, "failed to measure magnitudes for %s entry %d", filerule, i); 50 46 return false; … … 58 54 bool psphotVisualRangeImage (int kapaFD, psImage *inImage, const char *name, int channel, float min, float max); 59 55 60 bool psphotKronIterateReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, pmReadout *readout, psArray *sources, pmPSF *psf) {56 bool psphotKronIterateReadout(pmConfig *config, psMetadata *recipe, const pmFPAview *view, const char * filerule, pmReadout *readout, psArray *sources, pmPSF *psf, int index) { 61 57 62 58 bool status = false; … … 69 65 psTimerStart ("psphot.kron"); 70 66 67 71 68 // determine the number of allowed threads 72 69 int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS"); // Number of threads … … 84 81 MIN_KRON_RADIUS = 0.25*RADIUS; 85 82 } 83 84 int KRON_ITERATIONS = psMetadataLookupS32 (&status, recipe, "KRON_ITERATIONS"); 85 if (!status) { 86 KRON_ITERATIONS = 1; 87 } 88 89 bool KRON_APPLY_WEIGHT = psMetadataLookupBool (&status, recipe, "KRON_APPLY_WEIGHT"); 90 if (!status) { 91 KRON_APPLY_WEIGHT = true; 92 } 93 94 bool KRON_APPLY_WINDOW = psMetadataLookupBool (&status, recipe, "KRON_APPLY_WINDOW"); 95 if (!status) { 96 KRON_APPLY_WINDOW = false; 97 } 98 bool KRON_SMOOTH = psMetadataLookupBool (&status, recipe, "KRON_SMOOTH"); 99 if (!status) { 100 KRON_SMOOTH = false; 101 } 102 float KRON_SMOOTH_SIGMA = psMetadataLookupF32 (&status, recipe, "KRON_SMOOTH_SIGMA"); 103 if (!status) { 104 KRON_SMOOTH_SIGMA = 1.7; 105 } 106 float KRON_SMOOTH_NSIGMA = psMetadataLookupS32 (&status, recipe, "KRON_SMOOTH_NSIGMA"); 107 if (!status) { 108 KRON_SMOOTH_NSIGMA = 2; 109 } 110 /* 111 * Parameter for calculating maximum integration radius based on source's surface 112 * brightness 113 * Given minimum surface brightness SBmin and a flux the maximum radius is found from 114 * 115 * SBmin = source->flux / (pi * Rmax**2) 116 * Rmax = sqrt (source->flux ) / sqrt (SBmin * pi) 117 * 118 * Now what do we use for SBmin? 119 * 120 * SBmin = ( some flux ) / (some area) 121 * some flux ~ flux of Ns sigma PSF source 122 * some area ~ K times area of a PSF 123 * 124 * flux of Ns sigma source ~ Ns * SKY_STDEV * PSF_EFFECTIVE_AREA 125 * PSF_EFFECTIVE_AREA = 4 pi sigma_PSF^2 126 * (the 4 accounts for the fact that 1 sigma is not the total area, it is 127 * actually a larger region). 128 * 129 * SBmin = Ns * SKY_STDEV * 4 * pi * sigma_PSF^2 / (K * pi * sigma_PSF^2) 130 * = Ns * SKY_STDEV * 4 / K 131 * 132 * We combine the two parameters Ns and K, and the constant 4 into a single recipe value 133 * KRON_SB_MIN_FACTOR with Ns = 5 and K = 2 the corresponding value 134 * for KRON_SB_MIN_FACTOR is 5 * 4 / 2 = 10 135 */ 136 float KRON_SB_MIN_FACTOR = psMetadataLookupF32 (&status, recipe, "KRON_SB_MIN_FACTOR"); 137 if (!status) { 138 KRON_SB_MIN_FACTOR = 10; 139 } 140 float SKY_STDEV = psMetadataLookupF32 (&status, readout->analysis, "MSKY_DEV"); 141 float KRON_SB_MIN_DIVISOR = sqrt ( M_PI * KRON_SB_MIN_FACTOR * SKY_STDEV ); 86 142 87 143 // bit-masks to test for good/bad pixels … … 114 170 // set a window function for each source based on the moments 115 171 // (this skips really bad sources (no peak, no moments, DEFECT) 116 psphotKronWindowSetSource (source, kronWindow, false, true); 172 psphotKronWindowSetSource (source, kronWindow, false, true, KRON_APPLY_WINDOW); 173 } 174 175 // We measure the Kron Radius on a smoothed copy of the readout image 176 psImage *smoothedImage = NULL; 177 if (KRON_SMOOTH) { 178 // Build the smoothed source image 179 // Replace the subtracted sources 180 psphotReplaceAllSourcesReadout(config, view, filerule, index, recipe, false); 181 // Copy the image and smooth 182 psTimerStart ("psphot.kron.smooth"); 183 smoothedImage = psImageCopy(NULL, readout->image, PS_TYPE_F32); 184 psImageSmooth(smoothedImage, KRON_SMOOTH_SIGMA, KRON_SMOOTH_NSIGMA); 185 psLogMsg ("psphot.kron", PS_LOG_INFO, "smoothed image %f sec\n", psTimerMark ("psphot.kron.smooth")); 186 187 // remove the sources 188 psphotRemoveAllSourcesReadout( config, view, filerule, index, recipe, false ); 189 // Now subtract smooth versions of the sources from the smoothed image 190 psTimerStart ("psphot.kron.smooth.sources"); 191 for (int i=0; i< sources->n; i++) { 192 pmSource *source = sources->data[i]; 193 // If source has been subtracted from the readout image subtract a "smoothed" version from the smoothedImage 194 if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) { 195 // cache copy of smoothedPixels in the source 196 // tmpPtr is for use by a single "module" and must be null otherwise 197 psAssert(source->tmpPtr == NULL, "source->tmpPtr is not null!"); 198 199 psImage *smoothedPixels = psImageSubset(smoothedImage, source->region); 200 source->tmpPtr = (psPtr) smoothedPixels; 201 pmSourceSmoothOp(source, PM_MODEL_OP_FUNC, smoothedPixels, KRON_SMOOTH_SIGMA, false, maskVal, 0, 0); 202 } 203 } 204 psLogMsg ("psphot.kron", PS_LOG_INFO, "removed %ld smoothed sources %f sec\n", sources->n, psTimerMark ("psphot.kron.smooth.sources")); 205 117 206 } 118 207 … … 136 225 psArrayAdd(job->args, 1, kronWindow); 137 226 psArrayAdd(job->args, 1, cells->data[j]); // sources 227 psArrayAdd(job->args, 1, smoothedImage); 138 228 PS_ARRAY_ADD_SCALAR(job->args, markVal, PS_TYPE_IMAGE_MASK); 139 229 PS_ARRAY_ADD_SCALAR(job->args, maskVal, PS_TYPE_IMAGE_MASK); 140 230 PS_ARRAY_ADD_SCALAR(job->args, RADIUS, PS_TYPE_F32); 141 231 PS_ARRAY_ADD_SCALAR(job->args, MIN_KRON_RADIUS, PS_TYPE_F32); 232 PS_ARRAY_ADD_SCALAR(job->args, KRON_ITERATIONS, PS_TYPE_S32); 233 PS_ARRAY_ADD_SCALAR(job->args, (psS32) KRON_APPLY_WEIGHT, PS_TYPE_S32); 234 PS_ARRAY_ADD_SCALAR(job->args, (psS32) KRON_APPLY_WINDOW, PS_TYPE_S32); 235 PS_ARRAY_ADD_SCALAR(job->args, KRON_SMOOTH_SIGMA, PS_TYPE_F32); 236 PS_ARRAY_ADD_SCALAR(job->args, KRON_SB_MIN_DIVISOR,PS_TYPE_F32); 142 237 143 238 // set this to 0 to run without threading … … 150 245 if (!psphotKronIterate_Threaded(job)) { 151 246 psError(PS_ERR_UNKNOWN, false, "Unable to guess model."); 152 psFree(AnalysisRegion);247 // psFree(AnalysisRegion); 153 248 return false; 154 249 } … … 174 269 psFree (cellGroups); 175 270 psFree (kronWindow); 271 if (KRON_SMOOTH) { 272 for (int i = 0; i < sources->n; i++) { 273 pmSource *source = sources->data[i]; 274 psFree(source->tmpPtr); 275 source->tmpPtr = NULL; 276 } 277 } 278 psFree (smoothedImage); 176 279 177 280 psLogMsg ("psphot.kron", PS_LOG_WARN, "measure masked kron magnitudes : %f sec for %ld objects\n", psTimerMark ("psphot.kron"), sources->n); … … 184 287 psImage *kronWindow = job->args->data[1]; 185 288 psArray *sources = job->args->data[2]; 186 psImageMaskType markVal = PS_SCALAR_VALUE(job->args->data[3],PS_TYPE_IMAGE_MASK_DATA); 187 psImageMaskType maskVal = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA); 188 float RADIUS = PS_SCALAR_VALUE(job->args->data[5],F32); 189 float MIN_KRON_RADIUS = PS_SCALAR_VALUE(job->args->data[6],F32); 190 191 // XXX TEST : set iteration to 1 192 for (int j = 0; j < 1; j++) { 289 psImage *smoothedImage = job->args->data[3]; 290 psImageMaskType markVal = PS_SCALAR_VALUE(job->args->data[4],PS_TYPE_IMAGE_MASK_DATA); 291 psImageMaskType maskVal = PS_SCALAR_VALUE(job->args->data[5],PS_TYPE_IMAGE_MASK_DATA); 292 float RADIUS = PS_SCALAR_VALUE(job->args->data[6],F32); 293 float MIN_KRON_RADIUS = PS_SCALAR_VALUE(job->args->data[7],F32); 294 int KRON_ITERATIONS = PS_SCALAR_VALUE(job->args->data[8],S32); 295 bool KRON_APPLY_WEIGHT = PS_SCALAR_VALUE(job->args->data[9],S32); 296 bool KRON_APPLY_WINDOW = PS_SCALAR_VALUE(job->args->data[10],S32); 297 float KRON_SMOOTH_SIGMA = PS_SCALAR_VALUE(job->args->data[11],F32); 298 float KRON_SB_MIN_DIVISOR = PS_SCALAR_VALUE(job->args->data[12],F32); 299 300 for (int j = 0; j < KRON_ITERATIONS; j++) { 193 301 for (int i = 0; i < sources->n; i++) { 194 302 … … 196 304 if (!source->peak) continue; // XXX how can we have a peak-less source? 197 305 198 // allocate space formoments306 // check status of this source's moments 199 307 if (!source->moments) continue; 308 if (!(source->tmpFlags & PM_SOURCE_TMPF_MOMENTS_MEASURED)) continue; 309 if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) continue; 200 310 201 311 // replace object in image 202 312 bool reSubtract = false; 313 psImage *smoothedPixels = NULL; 203 314 if (source->tmpFlags & PM_SOURCE_TMPF_SUBTRACTED) { 204 pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal); 315 pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal); 316 smoothedPixels = (psImage *) source->tmpPtr; 317 if (smoothedPixels) { 318 pmSourceSmoothOp(source, PM_MODEL_OP_FUNC, smoothedPixels, KRON_SMOOTH_SIGMA, true, maskVal, 0, 0); 319 } 205 320 reSubtract = true; 206 321 } 207 322 208 // use S/N to control max window size 209 // float kronSN = source->moments->KronFlux / source->moments->KronFluxErr; 210 211 // maxWindow -> 1.5*RADIUS for kronSN = 5.0, keeping S.B. constant (kronSN ~ flux) 212 // (kronSN / maxWindow^2) = (5.0 / (1.5 RADIUS)^2) 213 // maxWindow = 1.5 * RADIUS * sqrt(kronSN / 5.0) 214 // XXX float maxWindow = (isfinite(kronSN) && (kronSN > 5.0)) ? 1.5 * RADIUS * sqrt(kronSN / 5.0) : 1.5*RADIUS; 215 216 // iterate to the window radius 217 // XXX float windowRadius = PS_MIN(PS_MAX(RADIUS, 4.0*source->moments->Mrf), maxWindow); 218 219 // XXX TEST : use a window based on the radial profile numbers: max is skyRadius, min is RADIUS 220 // if we lack the skyRadius (eg MATCHED sources), go to the default value 221 float maxWindow = isfinite(source->skyRadius) ? source->skyRadius : RADIUS; 323 // On first iteration set window radius to sky radius (if valid) on second iteration 324 // use a factor times the previous radial moment value up to a maximum value that 325 // depends on the surface brightness of the source 326 float maxWindow; 327 if (j == 0) { 328 maxWindow = isfinite(source->skyRadius) ? source->skyRadius : RADIUS; 329 } else { 330 if (KRON_SB_MIN_DIVISOR) { 331 if (isfinite(source->moments->KronFlux) && (source->moments->KronFlux > 0)) { 332 // Limit window radius based on surface brightness 333 float Rmax = sqrt(source->moments->KronFlux) / KRON_SB_MIN_DIVISOR; 334 335 if (source->moments->Mrf > 0) { 336 maxWindow = PS_MIN(6.0*source->moments->Mrf, Rmax); 337 } else { 338 maxWindow = Rmax; 339 } 340 } else { 341 maxWindow = RADIUS; 342 } 343 } else { 344 // old code 345 maxWindow = isfinite(source->moments->Mrf) ? 6.0*source->moments->Mrf : RADIUS; 346 } 347 } 222 348 float windowRadius = PS_MAX(RADIUS, maxWindow); 223 349 224 350 // re-allocate image, weight, mask arrays for each peak with box big enough to fit BIG_RADIUS 225 pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2);351 bool extend = pmSourceRedefinePixels (source, readout, source->peak->x, source->peak->y, windowRadius + 2); 226 352 psAssert (source->pixels, "WTF?"); 353 if (extend && smoothedPixels) { 354 psFree(source->tmpPtr); 355 smoothedPixels = psImageSubset(smoothedImage, source->region); 356 psAssert (smoothedPixels, "WTF?"); 357 source->tmpPtr = (psPtr) smoothedPixels ; 358 } 359 227 360 228 361 // clear the window function for this source based on the moments 229 psphotKronWindowSetSource (source, kronWindow, (j > 0), false); 230 231 // this function populates moments->Mrf,KronFlux,KronFluxErr 232 psphotKronWindowMag (source, kronWindow, windowRadius, MIN_KRON_RADIUS, maskVal); 233 psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 234 235 // set a window function for each source based on the moments 236 psphotKronWindowSetSource (source, kronWindow, true, true); 362 // Note: this function also applies cuts on the source and returns false if it 363 // does not meet the requirements for measuring the Kron Radius or Magnitude. 364 // Note: that it performs the cuts even if KRON_APPLY_WINDOW is false 365 if (psphotKronWindowSetSource (source, kronWindow, (j > 0), false, KRON_APPLY_WINDOW)) { 366 367 // this function populates moments->Mrf,KronFlux,KronFluxErr 368 psphotKronWindowMag (source, kronWindow, windowRadius, MIN_KRON_RADIUS, maskVal, KRON_APPLY_WEIGHT, smoothedPixels); 369 psImageMaskPixels (source->maskObj, "AND", PS_NOT_IMAGE_MASK(markVal)); 370 371 // set a window function for each source based on the moments 372 psphotKronWindowSetSource (source, kronWindow, true, true, KRON_APPLY_WINDOW); 373 } 237 374 238 375 // if we subtracted it above, re-subtract the object, leave local sky 239 376 if (reSubtract) { 240 pmSourceSub (source, PM_MODEL_OP_FULL, maskVal); 377 pmSourceSub (source, PM_MODEL_OP_FULL, maskVal); 378 if (smoothedPixels) { 379 pmSourceSmoothOp(source, PM_MODEL_OP_FUNC, smoothedPixels, KRON_SMOOTH_SIGMA, false, maskVal, 0, 0); 380 } 241 381 } 242 382 } 243 383 } 384 244 385 return true; 245 386 } 246 387 247 bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal) { 388 bool psphotKronWindowMag (pmSource *source, psImage *kronWindow, float radius, float minKronRadius, psImageMaskType maskVal, 389 bool applyWeight, psImage *smoothedPixels) { 248 390 249 391 PS_ASSERT_PTR_NON_NULL(source, false); … … 253 395 254 396 psF32 R2 = PS_SQR(radius); 255 float rsigma2 = 0.5 / PS_SQR(radius/2.0);397 float rsigma2 = applyWeight ? 0.5 / R2 : 0; 256 398 257 399 // a note about coordinates: coordinates of objects throughout psphot refer to the primary … … 266 408 // Xn = SUM (x - xc)^n * (z - sky) 267 409 410 268 411 psF32 RF = 0.0; 269 412 psF32 RS = 0.0; … … 284 427 int Ywo = source->pixels->row0; 285 428 286 psF32 **vPix = source->pixels->data.F32; 429 psF32 **vPix; 430 if (smoothedPixels) { 431 vPix = smoothedPixels->data.F32; 432 } else { 433 vPix = source->pixels->data.F32; 434 } 287 435 psF32 **vWin = kronWindow->data.F32; 288 436 psF32 **vWgt = source->variance->data.F32; … … 357 505 float Var = 0.0; 358 506 float Win = 0.0; 507 508 // set vPix to the source pixels (it may have been set to the 509 // smoothed image above) 510 vPix = source->pixels->data.F32; 511 359 512 360 513 for (psS32 row = 0; row < source->pixels->numRows ; row++) { … … 395 548 } 396 549 397 bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert ) {550 bool psphotKronWindowSetSource(pmSource *source, psImage *kronWindow, bool useKronRadius, bool insert, bool applyWindow) { 398 551 399 552 if (!source) return false; … … 402 555 if (source->type == PM_SOURCE_TYPE_DEFECT) return false; 403 556 if (source->type == PM_SOURCE_TYPE_SATURATED) return false; 557 if (!(source->tmpFlags & PM_SOURCE_TMPF_MOMENTS_MEASURED)) return false; 558 if (source->mode & PM_SOURCE_MODE_MOMENTS_FAILURE) return false; 404 559 psAssert(kronWindow, "need a window"); 560 561 if (!isfinite(source->moments->Mrf) || source->moments->Mrf < 0 ) return false; 562 563 if (!applyWindow) { 564 return true; 565 } 405 566 406 567 // we have a source with moments Mx, My, Mxx, Mxy, Myy. we just need to define a Gaussian that has … … 413 574 float Yo = source->moments->My; 414 575 415 float Mxx = source->moments->Mxx; 416 float Mxy = source->moments->Mxy; 417 float Myy = source->moments->Myy; 418 419 float Mmajor = 0.5*(Mxx + Myy) + 0.5*sqrt(PS_SQR(Mxx - Myy) + 4.0*PS_SQR(Mxy)); 420 float Mminor = 0.5*(Mxx + Myy) - 0.5*sqrt(PS_SQR(Mxx - Myy) + 4.0*PS_SQR(Mxy)); 421 422 // Mxx, Mxy, Myy define the elliptical shape, but Mrf defines the width 423 float scale = PS_SQR(0.5 * source->moments->Mrf) / Mmajor; 424 425 float Sxx = scale * Mmajor * Mminor / Myy; // sigma_x^2 426 float Sxy = Mxy / (scale * Mmajor * Mminor); 427 float Syy = scale * Mmajor * Mminor / Mxx; // sigma_y^2 428 429 float Smajor = sqrt(Mmajor); 576 psEllipseMoments moments; 577 moments.x2 = source->moments->Mxx; 578 moments.y2 = source->moments->Myy; 579 moments.xy = source->moments->Mxy; 580 581 psEllipseAxes axes = psEllipseMomentsToAxes(moments, 20.0); 582 if (! (isfinite(axes.major) && isfinite(axes.minor) && isfinite(axes.theta)) ) { 583 // Shall we log a proper warning? This happens often with matched sources (forced photometry) 584 // fprintf(dump, "invalid axes found id: %4d major: %f minor: %f theta: %f\n", source->id, axes.major, axes.minor, axes.theta); 585 return false; 586 } 587 588 // Why this factor of 0.5 ? 589 float scale = 0.5 * source->moments->Mrf / axes.major; 590 axes.major *= scale; 591 axes.minor *= scale; 592 593 psEllipseShape shape = psEllipseAxesToShape(axes); 594 if (! (isfinite(shape.sx) && isfinite(shape.sy) && isfinite(shape.sxy)) ) { 595 // Shall we log a proper warning? This happens often with matched sources (forced photometry) 596 // fprintf(dump, "invalid shape found id: %d sx: %f sy %f sxy: %f\n", source->id, shape.sx, shape.sy, shape.sxy); 597 return false; 598 } 599 600 float Smajor = axes.major; 430 601 431 602 int minX = PS_MIN(PS_MAX(Xo - 5*Smajor, 0), Nx - 1); … … 434 605 int maxY = PS_MIN(PS_MAX(Yo + 5*Smajor, 0), Ny - 1); 435 606 436 float rMxx = 0.5 / Sxx; 437 float rMyy = 0.5 / Syy; 438 607 float rMxx = 0.5 / PS_SQR(shape.sx); 608 float rMyy = 0.5 / PS_SQR(shape.sy); 609 float Sxy = -1. * shape.sxy; // factor of -1 is included to match the previous window function 610 // implementation. XXX: Is this correct? 611 439 612 for (int iy = minY; iy < maxY; iy++) { 440 613 for (int ix = minX; ix < maxX; ix++) { … … 447 620 float f = insert ? 1.001 - exp(-z) : 1.0 / (1.001 - exp(-z)); 448 621 449 kronWindow->data.F32[iy][ix] *= f;622 kronWindow->data.F32[iy][ix] *= f; 450 623 } 451 624 } 625 452 626 return true; 453 627 }
Note:
See TracChangeset
for help on using the changeset viewer.
