- Timestamp:
- May 3, 2010, 8:50:52 AM (16 years ago)
- Location:
- branches/simtest_nebulous_branches
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/simtest_nebulous_branches
- Property svn:mergeinfo changed
-
branches/simtest_nebulous_branches/psModules
-
Property svn:mergeinfo
set to (toggle deleted branches)
/trunk/psModules merged eligible /branches/eam_branches/stackphot.20100406/psModules 27623-27653 /branches/pap_delete/psModules 27530-27595
-
Property svn:mergeinfo
set to (toggle deleted branches)
-
branches/simtest_nebulous_branches/psModules/src/imcombine/pmSubtraction.c
r24298 r27840 17 17 #include <pslib.h> 18 18 19 #include "pmErrorCodes.h" 19 20 #include "pmHDU.h" // Required for pmFPA.h 20 21 #include "pmFPA.h" 21 22 #include "pmSubtractionStamps.h" 22 23 #include "pmSubtractionEquation.h" 24 #include "pmSubtractionVisual.h" 23 25 #include "pmSubtractionThreads.h" 24 26 … … 29 31 #define PIXEL_LIST_BUFFER 100 // Number of entries to add to pixel list at a time 30 32 #define MIN_SAMPLE_STATS 7 // Minimum number to use sample statistics; otherwise use quartiles 33 #define USE_KERNEL_ERR // Use kernel error image? 31 34 32 35 ////////////////////////////////////////////////////////////////////////////////////////////////////////////// … … 36 39 // Generate the kernel to apply to the variance from the normal kernel 37 40 static psKernel *varianceKernel(psKernel *out, // Output kernel 38 psKernel *normalKernel // Normal kernel41 const psKernel *normalKernel // Normal kernel 39 42 ) 40 43 { … … 48 51 49 52 // Take the square of the normal kernel 50 double sum Normal = 0.0, sumVariance = 0.0; // Sum of the normal andvariance kernels53 double sumVariance = 0.0; // Sum of the variance kernels 51 54 for (int v = yMin; v <= yMax; v++) { 52 55 for (int u = xMin; u <= xMax; u++) { 53 float value = normalKernel->kernel[v][u]; // Value of interest 54 float value2 = PS_SQR(value); // Value squared 55 sumNormal += value; 56 sumVariance += value2; 57 out->kernel[v][u] = value2; 56 sumVariance += out->kernel[v][u] = PS_SQR(normalKernel->kernel[v][u]); 58 57 } 59 58 } … … 65 64 return out; 66 65 } 66 67 // Contribute to an image of the solved kernel component using the preCalculated image 68 static void solvedKernelPreCalc(psKernel *kernel, // Kernel, updated 69 const pmSubtractionKernels *kernels, // Kernel basis functions 70 float value, // Normalisation value for basis function 71 int index // Index of basis function of interest 72 ) 73 { 74 int size = kernels->size; // Kernel half-size 75 pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated values 76 #if 0 77 // Iterating over the kernel 78 for (int y = 0, v = -size; v <= size; y++, v++) { 79 float yValue = value * preCalc->yKernel->data.F32[y]; 80 for (int x = 0, u = -size; u <= size; x++, u++) { 81 kernel->kernel[v][u] += yValue * preCalc->xKernel->data.F32[x]; 82 } 83 } 84 // Photometric scaling for even kernels only 85 if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) { 86 kernel->kernel[0][0] -= value; 87 } 88 #else 89 for (int v = -size; v <= size; v++) { 90 for (int u = -size; u <= size; u++) { 91 kernel->kernel[v][u] += value * preCalc->kernel->kernel[v][u]; 92 } 93 } 94 #endif 95 96 return; 97 } 98 67 99 68 100 // Generate an image of the solved kernel … … 70 102 const pmSubtractionKernels *kernels, // Kernel basis functions 71 103 const psImage *polyValues, // Spatial polynomial values 104 bool normalise, // Add normalisation? 72 105 bool wantDual // Want the dual (second) kernel? 73 106 ) … … 85 118 for (int i = 0; i < numKernels; i++) { 86 119 double value = p_pmSubtractionSolutionCoeff(kernels, polyValues, i, wantDual); // Polynomial value 120 if (wantDual) { 121 // The model is built with the dual convolution terms added, so to produce zero residual the 122 // equation results in negative coefficients which we must undo. 123 value *= -1.0; 124 } 87 125 88 126 switch (kernels->type) { … … 115 153 case PM_SUBTRACTION_KERNEL_GUNK: { 116 154 if (i < kernels->inner) { 117 // Using pre-calculated function 118 psKernel *preCalc = kernels->preCalc->data[i]; // Precalculated values 119 // Iterating over the kernel 120 for (int v = -size; v <= size; v++) { 121 for (int u = -size; u <= size; u++) { 122 kernel->kernel[v][u] += preCalc->kernel[v][u] * value; 123 // Photometric scaling is built into the preCalc kernel --- no subtraction! 124 } 125 } 155 solvedKernelPreCalc(kernel, kernels, value, i); 126 156 } else { 127 157 // Using delta function … … 133 163 break; 134 164 } 135 case PM_SUBTRACTION_KERNEL_ISIS: { 136 psArray *preCalc = kernels->preCalc->data[i]; // Precalculated values 137 psVector *xKernel = preCalc->data[0]; // Kernel in x 138 psVector *yKernel = preCalc->data[1]; // Kernel in y 139 // Iterating over the kernel 140 for (int y = 0, v = -size; v <= size; y++, v++) { 141 for (int x = 0, u = -size; u <= size; x++, u++) { 142 kernel->kernel[v][u] += value * xKernel->data.F32[x] * yKernel->data.F32[y]; 143 } 144 } 145 // Photometric scaling for even kernels only 146 if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) { 147 kernel->kernel[0][0] -= value; 148 } 165 case PM_SUBTRACTION_KERNEL_ISIS: 166 case PM_SUBTRACTION_KERNEL_ISIS_RADIAL: 167 case PM_SUBTRACTION_KERNEL_HERM: 168 case PM_SUBTRACTION_KERNEL_DECONV_HERM: { 169 solvedKernelPreCalc(kernel, kernels, value, i); 149 170 break; 150 171 } 151 172 case PM_SUBTRACTION_KERNEL_RINGS: { 152 psArray *preCalc = kernels->preCalc->data[i]; // Precalculated data 153 psVector *uCoords = preCalc->data[0]; // u coordinates 154 psVector *vCoords = preCalc->data[1]; // v coordinates 155 psVector *poly = preCalc->data[2]; // Polynomial values 156 int num = uCoords->n; // Number of pixels 173 pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[i]; // Precalculated kernels 174 int num = preCalc->uCoords->n; // Number of pixels 157 175 158 176 for (int j = 0; j < num; j++) { 159 int u = uCoords->data.S32[j], v = vCoords->data.S32[j]; // Kernel coordinates 160 kernel->kernel[v][u] += poly->data.F32[j] * value; 177 int u = preCalc->uCoords->data.S32[j]; 178 int v = preCalc->vCoords->data.S32[j]; // Kernel coordinates 179 kernel->kernel[v][u] += preCalc->poly->data.F32[j] * value; 161 180 } 162 181 // Photometric scaling is built into the kernel --- no subtraction! … … 168 187 } 169 188 170 // Put in the normalisation component 171 kernel->kernel[0][0] += (wantDual ? 1.0 : p_pmSubtractionSolutionNorm(kernels)); 189 if (normalise) { 190 // Put in the normalisation component 191 kernel->kernel[0][0] += (wantDual ? 1.0 : p_pmSubtractionSolutionNorm(kernels)); 192 } 172 193 173 194 return kernel; … … 250 271 static void convolveVarianceFFT(psImage *target,// Place the result in here 251 272 psImage *variance, // Variance map to convolve 252 psImage * sys, // Systematicerror image273 psImage *kernelErr, // Kernel error image 253 274 psImage *mask, // Mask image 254 275 psImageMaskType maskVal, // Value to mask … … 262 283 263 284 psImage *subVariance = variance ? psImageSubset(variance, border) : NULL; // Variance map 264 psImage *sub Sys = sys ? psImageSubset(sys, border) : NULL; // Systematicerror image285 psImage *subKE = kernelErr ? psImageSubset(kernelErr, border) : NULL; // Kernel error image 265 286 psImage *subMask = mask ? psImageSubset(mask, border) : NULL; // Mask 266 287 267 288 // XXX Can trim this a little by combining the convolution: only have to take the FFT of the kernel once 268 289 psImage *convVariance = psImageConvolveFFT(NULL, subVariance, subMask, maskVal, kernel); // Convolved variance 269 psImage *conv Sys = subSys ? psImageConvolveFFT(NULL, subSys, subMask, maskVal, kernel) : NULL; // Conv sys290 psImage *convKE = subKE ? psImageConvolveFFT(NULL, subKE, subMask, maskVal, kernel) : NULL; // Conv KE 270 291 271 292 psFree(subVariance); 272 psFree(sub Sys);293 psFree(subKE); 273 294 psFree(subMask); 274 295 275 296 // Now, we have to stick it in where it belongs 276 297 int xMin = region.x0, xMax = region.x1, yMin = region.y0, yMax = region.y1; // Bounds of region 277 if (conv Sys) {298 if (convKE) { 278 299 for (int yTarget = yMin, ySource = size; yTarget < yMax; yTarget++, ySource++) { 279 300 for (int xTarget = xMin, xSource = size; xTarget < xMax; xTarget++, xSource++) { 280 301 target->data.F32[yTarget][xTarget] = convVariance->data.F32[ySource][xSource] + 281 conv Sys->data.F32[ySource][xSource];302 convKE->data.F32[ySource][xSource]; 282 303 } 283 304 } … … 290 311 291 312 psFree(convVariance); 292 psFree(conv Sys);313 psFree(convKE); 293 314 294 315 return; … … 326 347 psImage *image, // Image to convolve 327 348 psImage *variance, // Variance map to convolve, or NULL 328 psImage * sys, // Systematicerror image, or NULL349 psImage *kernelErr, // Kernel error image, or NULL 329 350 psImage *subMask, // Subtraction mask 330 351 const pmSubtractionKernels *kernels, // Kernels … … 339 360 ) 340 361 { 341 *kernelImage = solvedKernel(*kernelImage, kernels, polyValues, wantDual);362 *kernelImage = solvedKernel(*kernelImage, kernels, polyValues, true, wantDual); 342 363 if (variance || subMask) { 343 364 *kernelVariance = varianceKernel(*kernelVariance, *kernelImage); … … 363 384 convolveFFT(convImage, image, subMask, subBad, *kernelImage, region, background, kernels->size); 364 385 if (variance) { 365 convolveVarianceFFT(convVariance, variance, sys, subMask, subBad, *kernelVariance, region, kernels->size); 386 convolveVarianceFFT(convVariance, variance, kernelErr, subMask, subBad, *kernelVariance, 387 region, kernels->size); 366 388 } 367 389 } else { … … 373 395 } 374 396 375 // Convolve the mask for bad pixels397 // Convolve the mask for bad/poor pixels 376 398 if (subMask && convMask) { 377 399 int box = p_pmSubtractionBadRadius(*kernelImage, kernels, polyValues, 378 400 wantDual, poorFrac); // Size of bad box 401 psAssert(box >= 0, "Bad radius must be >= 0"); 402 403 int colMin = region.x0, colMax = region.x1, rowMin = region.y0, rowMax = region.y1; // Bounds 404 psImage *convolved = NULL; // Convolved subtraction mask 379 405 if (box > 0) { 380 int colMin = region.x0, colMax = region.x1, rowMin = region.y0, rowMax = region.y1; // Bounds 381 psRegion region = psRegionSet(colMin - box, colMax + box, 382 rowMin - box, rowMax + box); // Region to convolve 383 384 psImage *image = subMask ? psImageSubset(subMask, region) : NULL; // Mask to convolve 385 386 psImage *convolved = psImageConvolveMask(NULL, image, subBad, subConvBad, 387 -box, box, -box, box); // Convolved subtraction mask 388 406 psRegion maskRegion = psRegionSet(colMin - box, colMax + box, 407 rowMin - box, rowMax + box); // Region to convolve 408 psImage *image = subMask ? psImageSubset(subMask, maskRegion) : NULL; // Mask to convolve 409 convolved = psImageConvolveMask(NULL, image, subBad, subConvBad, -box, box, -box, box); 389 410 psFree(image); 390 391 psAssert(convolved->numCols - 2 * box == colMax - colMin, "Bad number of columns"); 392 psAssert(convolved->numRows - 2 * box == rowMax - rowMin, "Bad number of rows"); 393 394 for (int yTarget = rowMin, ySource = box; yTarget < rowMax; yTarget++, ySource++) { 395 // Dereference images 396 psImageMaskType *target = &convMask->data.PS_TYPE_IMAGE_MASK_DATA[yTarget][colMin]; // Target values 397 psImageMaskType *source = &convolved->data.PS_TYPE_IMAGE_MASK_DATA[ySource][box]; // Source values 398 for (int xTarget = colMin; xTarget < colMax; xTarget++, target++, source++) { 399 if (*source & subConvBad) { 400 *target |= maskBad; 401 } else if (*source & subConvPoor) { 402 *target |= maskPoor; 403 } 411 } else { 412 convolved = psImageSubset(subMask, region); 413 } 414 415 psAssert(convolved->numCols - 2 * box == colMax - colMin, "Bad number of columns"); 416 psAssert(convolved->numRows - 2 * box == rowMax - rowMin, "Bad number of rows"); 417 418 for (int yTarget = rowMin, ySource = box; yTarget < rowMax; yTarget++, ySource++) { 419 // Dereference images 420 psImageMaskType *target = &convMask->data.PS_TYPE_IMAGE_MASK_DATA[yTarget][colMin]; // Target values 421 psImageMaskType *source = &convolved->data.PS_TYPE_IMAGE_MASK_DATA[ySource][box]; // Source values 422 for (int xTarget = colMin; xTarget < colMax; xTarget++, target++, source++) { 423 if (*source & subConvBad) { 424 *target |= maskBad; 425 } else if (*source & subConvPoor) { 426 *target &= ~maskBad; 427 *target |= maskPoor; 428 } else { 429 *target &= ~maskBad & ~maskPoor; 404 430 } 405 431 } 406 407 // No need to lock: we own this 408 psFree(convolved); 409 } 432 } 433 434 psFree(convolved); 410 435 } 411 436 … … 413 438 } 414 439 415 // Generate an image that can be used to track systematic errors 416 static psImage *subtractionSysErrImage(const psImage *image, // Image from which to make sys err image 417 float sysError // Relative systematic error 418 ) 419 { 420 if (!isfinite(sysError) || sysError == 0.0) { 440 #ifdef USE_KERNEL_ERR 441 // Generate an image that can be used to track systematic errors in the kernel 442 static psImage *subtractionKernelErrImage(const psImage *image, // Image from which to make kernel error image 443 float kernelError // Relative systematic error in kernel 444 ) 445 { 446 if (!isfinite(kernelError) || kernelError == 0.0) { 421 447 return NULL; 422 448 } 423 449 424 450 int numCols = image->numCols, numRows = image->numRows; // Size of image 425 psImage * sys = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Systematicerror image426 427 float sysError2 = PS_SQR(sysError); // Square of the systematicerror451 psImage *kernelErr = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Kernel error image 452 453 float kernelError2 = PS_SQR(kernelError); // Square of the kernel error 428 454 for (int y = 0; y < numRows; y++) { 429 455 for (int x = 0; x < numCols; x++) { 430 sys->data.F32[y][x] = PS_SQR(image->data.F32[y][x]) * sysError2; 431 } 432 } 433 434 return sys; 456 kernelErr->data.F32[y][x] = PS_SQR(image->data.F32[y][x]) * kernelError2; 457 } 458 } 459 460 return kernelErr; 461 } 462 #endif 463 464 // Convolve a stamp using a pre-calculated kernel basis function 465 static psKernel *convolveStampPreCalc(const psKernel *image, // Image to convolve 466 const pmSubtractionKernels *kernels, // Kernel basis functions 467 int index, // Index of basis function of interest 468 int footprint // Half-size of stamp 469 ) 470 { 471 pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated data 472 #if 0 473 // Convolving using separable convolution 474 int size = kernels->size; // Size of kernel 475 476 // Convolve in x 477 // Need to convolve a bit more than the footprint, for the y convolution 478 int yMin = -size - footprint, yMax = size + footprint; // Range for y 479 psKernel *temp = psKernelAlloc(yMin, yMax, 480 -footprint, footprint); // Temporary convolution; NOTE: wrong way! 481 for (int y = yMin; y <= yMax; y++) { 482 for (int x = -footprint; x <= footprint; x++) { 483 float value = 0.0; // Value of convolved pixel 484 int uMin = x - size, uMax = x + size; // Range for u 485 psF32 *xKernelData = &preCalc->xKernel->data.F32[xKernel->n - 1]; // Kernel values 486 psF32 *imageData = &image->kernel[y][uMin]; // Image values 487 for (int u = uMin; u <= uMax; u++, xKernelData--, imageData++) { 488 value += *xKernelData * *imageData; 489 } 490 temp->kernel[x][y] = value; // NOTE: putting in wrong way! 491 } 492 } 493 494 // Convolve in y 495 psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint);// Convolved image 496 for (int x = -footprint; x <= footprint; x++) { 497 for (int y = -footprint; y <= footprint; y++) { 498 float value = 0.0; // Value of convolved pixel 499 int vMin = y - size, vMax = y + size; // Range for v 500 psF32 *yKernelData = &preCalc->yKernel->data.F32[yKernel->n - 1]; // Kernel values 501 psF32 *imageData = &temp->kernel[x][vMin]; // Image values; NOTE: wrong way! 502 for (int v = vMin; v <= vMax; v++, yKernelData--, imageData++) { 503 value += *yKernelData * *imageData; 504 } 505 convolved->kernel[y][x] = value; 506 } 507 } 508 psFree(temp); 509 510 // Photometric scaling for even kernels only 511 if (kernels->u->data.S32[index] % 2 == 0 && kernels->v->data.S32[index] % 2 == 0) { 512 convolveSub(convolved, image, footprint); 513 } 514 return convolved; 515 #else 516 // Convolving using precalculated kernel 517 return p_pmSubtractionConvolveStampPrecalc(image, preCalc->kernel); 518 #endif 435 519 } 436 520 … … 447 531 int x0 = - image->xMin, y0 = - image->yMin; // Position of centre of convolved image 448 532 psKernel *convolved = psKernelAllocFromImage(conv, x0, y0); // Kernel version 533 534 // pmSubtractionVisualShowSubtraction(image->image, kernel->image, conv); 535 449 536 psFree(conv); 450 537 return convolved; … … 456 543 psKernel *kernel; // Kernel to use 457 544 if (!preKernel) { 458 kernel = solvedKernel(NULL, kernels, polyValues, wantDual);545 kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); 459 546 } else { 460 547 kernel = psMemIncrRefCounter(preKernel); … … 491 578 } 492 579 580 void p_pmSubtractionPolynomialNormCoords(float *xOut, float *yOut, float xIn, float yIn, 581 int xMin, int xMax, int yMin, int yMax) 582 { 583 float xNormSize = xMax - xMin, yNormSize = yMax - yMin; // Size to use for normalisation 584 *xOut = 2.0 * (float)(xIn - xMin - xNormSize/2.0) / xNormSize; 585 *yOut = 2.0 * (float)(yIn - yMin - yNormSize/2.0) / yNormSize; 586 return; 587 } 588 493 589 psImage *p_pmSubtractionPolynomialFromCoords(psImage *output, const pmSubtractionKernels *kernels, 494 int numCols, int numRows, intx, int y)590 int x, int y) 495 591 { 496 592 assert(kernels); 497 assert(numCols > 0 && numRows > 0); 498 499 // Size to use when calculating normalised coordinates (different from actual size when convolving 500 // subimage) 501 int xNormSize = (kernels->numCols > 0 ? kernels->numCols : numCols); 502 int yNormSize = (kernels->numRows > 0 ? kernels->numRows : numRows); 503 504 // Normalised coordinates 505 float yNorm = 2.0 * (float)(y - yNormSize/2.0) / (float)yNormSize; 506 float xNorm = 2.0 * (float)(x - xNormSize/2.0) / (float)xNormSize; 507 593 594 float xNorm, yNorm; // Normalised coordinates 595 p_pmSubtractionPolynomialNormCoords(&xNorm, &yNorm, x, y, 596 kernels->xMin, kernels->xMax, kernels->yMin, kernels->yMax); 508 597 return p_pmSubtractionPolynomial(output, kernels->spatialOrder, xNorm, yNorm); 509 598 } … … 586 675 if (index < kernels->inner) { 587 676 // Photometric scaling is already built in to the precalculated kernel 588 return p_pmSubtractionConvolveStampPrecalc(image, kernels->preCalc->data[index]);677 return convolveStampPreCalc(image, kernels, index, footprint); 589 678 } 590 679 // Using delta function … … 595 684 return convolved; 596 685 } 597 case PM_SUBTRACTION_KERNEL_ISIS: { 598 psArray *preCalc = kernels->preCalc->data[index]; // Precalculated values 599 psVector *xKernel = preCalc->data[0]; // Kernel in x 600 psVector *yKernel = preCalc->data[1]; // Kernel in y 601 int size = kernels->size; // Size of kernel 602 603 // Convolve in x 604 // Need to convolve a bit more than the footprint, for the y convolution 605 int yMin = -size - footprint, yMax = size + footprint; // Range for y 606 psKernel *temp = psKernelAlloc(yMin, yMax, 607 -footprint, footprint); // Temporary convolution; NOTE: wrong way! 608 for (int y = yMin; y <= yMax; y++) { 609 for (int x = -footprint; x <= footprint; x++) { 610 float value = 0.0; // Value of convolved pixel 611 int uMin = x - size, uMax = x + size; // Range for u 612 psF32 *xKernelData = &xKernel->data.F32[xKernel->n - 1]; // Kernel values 613 psF32 *imageData = &image->kernel[y][uMin]; // Image values 614 for (int u = uMin; u <= uMax; u++, xKernelData--, imageData++) { 615 value += *xKernelData * *imageData; 616 } 617 temp->kernel[x][y] = value; // NOTE: putting in wrong way! 618 } 619 } 620 621 // Convolve in y 622 psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint);// Convolved image 623 for (int x = -footprint; x <= footprint; x++) { 624 for (int y = -footprint; y <= footprint; y++) { 625 float value = 0.0; // Value of convolved pixel 626 int vMin = y - size, vMax = y + size; // Range for v 627 psF32 *yKernelData = &yKernel->data.F32[yKernel->n - 1]; // Kernel values 628 psF32 *imageData = &temp->kernel[x][vMin]; // Image values; NOTE: wrong way! 629 for (int v = vMin; v <= vMax; v++, yKernelData--, imageData++) { 630 value += *yKernelData * *imageData; 631 } 632 convolved->kernel[y][x] = value; 633 } 634 } 635 psFree(temp); 636 637 // Photometric scaling for even kernels only 638 if (kernels->u->data.S32[index] % 2 == 0 && kernels->v->data.S32[index] % 2 == 0) { 639 convolveSub(convolved, image, footprint); 640 } 641 return convolved; 642 } 686 case PM_SUBTRACTION_KERNEL_ISIS: 687 case PM_SUBTRACTION_KERNEL_ISIS_RADIAL: 688 case PM_SUBTRACTION_KERNEL_HERM: 689 case PM_SUBTRACTION_KERNEL_DECONV_HERM: { 690 return convolveStampPreCalc(image, kernels, index, footprint); 691 } 643 692 case PM_SUBTRACTION_KERNEL_RINGS: { 644 psKernel *convolved = psKernelAlloc(-footprint, footprint, 645 -footprint, footprint); // Convolved image 646 psArray *preCalc = kernels->preCalc->data[index]; // Precalculated data 647 psVector *uCoords = preCalc->data[0]; // u coordinates 648 psVector *vCoords = preCalc->data[1]; // v coordinates 649 psVector *poly = preCalc->data[2]; // Polynomial values 650 int num = uCoords->n; // Number of pixels 651 psS32 *uData = uCoords->data.S32, *vData = vCoords->data.S32; // Dereference u,v coordinates 652 psF32 *polyData = poly->data.F32; // Dereference polynomial values 693 psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Convolved image 694 pmSubtractionKernelPreCalc *preCalc = kernels->preCalc->data[index]; // Precalculated data 695 696 int num = preCalc->uCoords->n; // Number of pixels 697 psS32 *uData = preCalc->uCoords->data.S32; // Dereference v coordinate 698 psS32 *vData = preCalc->vCoords->data.S32; // Dereference u coordinate 699 psF32 *polyData = preCalc->poly->data.F32; // Dereference polynomial values 653 700 psF32 **imageData = image->kernel; // Dereference image 654 701 psF32 **convData = convolved->kernel; // Dereference convolved image … … 706 753 707 754 if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE) { 708 psError(P S_ERR_BAD_PARAMETER_VALUE, true, "Stamp not marked for calculation.");755 psError(PM_ERR_PROG, true, "Stamp not marked for calculation."); 709 756 return false; 710 757 } … … 730 777 731 778 732 733 734 779 int pmSubtractionRejectStamps(pmSubtractionKernels *kernels, pmSubtractionStampList *stamps, 735 const psVector *deviations, psImage *subMask, float sigmaRej , int footprint)780 const psVector *deviations, psImage *subMask, float sigmaRej) 736 781 { 737 782 PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false); … … 764 809 765 810 if (numStamps == 0) { 766 psError(P S_ERR_UNKNOWN, true, "No good stamps found.");811 psError(PM_ERR_STAMPS, true, "No good stamps found."); 767 812 psFree(mask); 768 813 return -1; … … 772 817 PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_QUARTILE); // Statistics for deviatns 773 818 if (!psVectorStats(stats, deviations, NULL, mask, 0xff)) { 774 psError(P S_ERR_UNKNOWN, false, "Unable to measure statistics for deviations.");819 psError(PM_ERR_DATA, false, "Unable to measure statistics for deviations."); 775 820 psFree(stats); 776 821 psFree(mask); … … 821 866 ds9num++; 822 867 868 int footprint = stamps->footprint; // Half-size of stamp region of interest 823 869 int numRejected = 0; // Number of stamps rejected 824 870 int numGood = 0; // Number of good stamps 825 871 double newMean = 0.0; // New mean 872 psString log = NULL; // Log message 873 psStringAppend(&log, "Rejecting stamps, mean = %f, threshold = %f\n", mean, limit); 826 874 for (int i = 0; i < stamps->num; i++) { 827 875 pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest … … 836 884 // Mask out the stamp in the image so you it's not found again 837 885 psTrace("psModules.imcombine", 3, "Rejecting stamp %d (%d,%d)\n", i, 838 (int)(stamp->x + 0.5), (int)(stamp->y + 0.5)); 886 (int)(stamp->x - 0.5), (int)(stamp->y - 0.5)); 887 psStringAppend(&log, "Stamp %d (%d,%d): %f\n", i, 888 (int)(stamp->x - 0.5), (int)(stamp->y - 0.5), 889 fabsf(deviations->data.F32[i] - mean)); 839 890 numRejected++; 840 891 for (int y = stamp->y - footprint; y <= stamp->y + footprint; y++) { … … 857 908 psFree(stamp->image1); 858 909 psFree(stamp->image2); 859 psFree(stamp->variance); 860 stamp->image1 = stamp->image2 = stamp->variance = NULL; 861 psFree(stamp->matrix1); 862 psFree(stamp->matrix2); 863 psFree(stamp->matrixX); 864 stamp->matrix1 = stamp->matrix2 = stamp->matrixX = NULL; 865 psFree(stamp->vector1); 866 psFree(stamp->vector2); 867 stamp->vector1 = stamp->vector2 = NULL; 910 psFree(stamp->weight); 911 stamp->image1 = stamp->image2 = stamp->weight = NULL; 912 psFree(stamp->matrix); 913 stamp->matrix = NULL; 914 psFree(stamp->vector); 915 stamp->vector = NULL; 868 916 } else { 869 917 numGood++; … … 874 922 } 875 923 newMean /= numGood; 924 925 if (numRejected == 0) { 926 psStringAppend(&log, "<none>\n"); 927 } 928 psLogMsg("psModules.imcombine", PS_LOG_DETAIL, "%s", log); 929 psFree(log); 876 930 877 931 if (ds9) { … … 900 954 901 955 psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); // Solved polynomial 902 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual); // The appropriate kernel956 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel 903 957 psFree(polyValues); 904 958 … … 931 985 psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); 932 986 933 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual); // The appropriate kernel987 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel 934 988 psFree(polyValues); 935 989 … … 948 1002 } 949 1003 950 #if 01004 #if 1 951 1005 psArray *pmSubtractionKernelSolutions(const pmSubtractionKernels *kernels, float x, float y, bool wantDual) 952 1006 { … … 956 1010 PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NULL); 957 1011 958 psArray *images = psArrayAlloc(solution->n - 1); // Images of each kernel to return 959 psVector *fakeSolution = psVectorAlloc(solution->n, PS_TYPE_F64); // Fake solution vector 960 psVectorInit(fakeSolution, 0.0); 961 962 for (int i = 0; i < solution->n - 1; i++) { 963 fakeSolution->data.F64[i] = solution->data.F64[i]; 964 images->data[i] = pmSubtractionKernelImage(kernels, x, y, wantDual); 965 fakeSolution->data.F64[i] = 0.0; 966 } 967 968 psFree(fakeSolution); 1012 psVector *solution = wantDual ? kernels->solution2 : kernels->solution1; // Solution of interest 1013 psVector *backup = psVectorCopy(NULL, solution, PS_TYPE_F64); // Backup version 1014 1015 int num = kernels->num; // Number of kernel basis functions 1016 1017 psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y); // Solved polynomial 1018 psArray *images = psArrayAlloc(num + 1); // Images of each kernel to return 1019 1020 // The whole kernel 1021 { 1022 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, true, wantDual); // The appropriate kernel 1023 images->data[0] = psMemIncrRefCounter(kernel->image); 1024 psFree(kernel); 1025 } 1026 1027 // The parts 1028 psVectorInit(solution, 0.0); 1029 for (int i = 0; i < num; i++) { 1030 solution->data.F64[i] = backup->data.F64[i]; 1031 psKernel *kernel = solvedKernel(NULL, kernels, polyValues, false, wantDual); // The appropriate kernel 1032 #if 0 1033 int size = kernels->size; 1034 double sum = 0.0; 1035 for (int v = -size; v <= size; v++) { 1036 for (int u = -size; u <= size; u++) { 1037 sum += kernel->kernel[v][u]; 1038 } 1039 } 1040 fprintf(stderr, "Kernel %d: %lf\n", i, sum); 1041 #endif 1042 images->data[i + 1] = psMemIncrRefCounter(kernel->image); 1043 psFree(kernel); 1044 solution->data.F64[i] = 0.0; 1045 } 1046 psFree(polyValues); 1047 psVectorCopy(solution, backup, PS_TYPE_F64); 1048 psFree(backup); 969 1049 970 1050 return images; … … 979 1059 psImage *convMask, // Output convolved mask 980 1060 const pmReadout *ro1, const pmReadout *ro2, // Input readouts 981 psImage * sys1, psImage *sys2, // Systematicerror images1061 psImage *kernelErr1, psImage *kernelErr2, // Kernel error images 982 1062 psImage *subMask, // Input subtraction mask 983 1063 psImageMaskType maskBad, // Mask value to give bad pixels … … 998 1078 // Only generate polynomial values every kernel footprint, since we have already assumed 999 1079 // (with the stamps) that it does not vary rapidly on this scale. 1000 psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, numCols, numRows, 1001 xMin + x0 + size + 1, 1002 yMin + y0 + size + 1); 1080 psImage *polyValues = p_pmSubtractionPolynomialFromCoords(NULL, kernels, xMin + x0 + size + 1, 1081 yMin + y0 + size + 1); // Polynomial 1003 1082 float background = doBG ? p_pmSubtractionSolutionBackground(kernels, polyValues) : 0.0; // Background term 1004 1083 1005 1084 if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1006 convolveRegion(out1->image, out1->variance, convMask, &kernelImage, &kernelVariance,1007 ro1->image, ro1->variance, sys1, subMask, kernels, polyValues, background, *region,1008 maskBad, maskPoor, poorFrac, useFFT, false);1085 convolveRegion(out1->image, out1->variance, out1->mask, &kernelImage, &kernelVariance, 1086 ro1->image, ro1->variance, kernelErr1, subMask, kernels, polyValues, background, 1087 *region, maskBad, maskPoor, poorFrac, useFFT, false); 1009 1088 } 1010 1089 if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1011 convolveRegion(out2->image, out2->variance, convMask, &kernelImage, &kernelVariance, 1012 ro2->image, ro2->variance, sys2, subMask, kernels, polyValues, background, *region, 1013 maskBad, maskPoor, poorFrac, useFFT, kernels->mode == PM_SUBTRACTION_MODE_DUAL); 1090 convolveRegion(out2->image, out2->variance, out2->mask, &kernelImage, &kernelVariance, 1091 ro2->image, ro2->variance, kernelErr2, subMask, kernels, polyValues, background, 1092 *region, maskBad, maskPoor, poorFrac, useFFT, 1093 kernels->mode == PM_SUBTRACTION_MODE_DUAL); 1014 1094 } 1015 1095 … … 1019 1099 1020 1100 if ((kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) && ro1->mask) { 1021 psImageMaskType **target = convMask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask1101 psImageMaskType **target = out1->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask 1022 1102 psImageMaskType **source = ro1->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Source mask 1023 1103 … … 1029 1109 } 1030 1110 if ((kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) && ro2->mask) { 1031 psImageMaskType **target = convMask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask1111 psImageMaskType **target = out2->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Target mask 1032 1112 psImageMaskType **source = ro2->mask->data.PS_TYPE_IMAGE_MASK_DATA; // Source mask 1033 1113 … … 1056 1136 const pmReadout *ro1 = args->data[7]; // Input readout 1 1057 1137 const pmReadout *ro2 = args->data[8]; // Input readout 2 1058 psImage * sys1 = args->data[9]; // Systematicerror image 11059 psImage * sys2 = args->data[10]; // Systematicerror image 21138 psImage *kernelErr1 = args->data[9]; // Kernel error image 1 1139 psImage *kernelErr2 = args->data[10]; // Kernel error image 2 1060 1140 psImage *subMask = args->data[11]; // Subtraction mask 1061 1141 psImageMaskType maskBad = PS_SCALAR_VALUE(args->data[12], PS_TYPE_IMAGE_MASK_DATA); // Output mask value for bad pixels … … 1067 1147 bool useFFT = PS_SCALAR_VALUE(args->data[18], U8); // Use FFT for convolution? 1068 1148 1069 return subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, sys1, sys2, 1070 subMask, maskBad, maskPoor, poorFrac, region, kernels, doBG, useFFT); 1149 return subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, kernelErr1, 1150 kernelErr2, subMask, maskBad, maskPoor, poorFrac, region, kernels, 1151 doBG, useFFT); 1071 1152 } 1072 1153 1073 1154 bool pmSubtractionConvolve(pmReadout *out1, pmReadout *out2, const pmReadout *ro1, const pmReadout *ro2, 1074 1155 psImage *subMask, int stride, psImageMaskType maskBad, psImageMaskType maskPoor, 1075 float poorFrac, float sysError, const psRegion *region,1156 float poorFrac, float kernelError, float covarFrac, const psRegion *region, 1076 1157 const pmSubtractionKernels *kernels, bool doBG, bool useFFT) 1077 1158 { … … 1082 1163 PM_ASSERT_READOUT_NON_NULL(ro1, false); 1083 1164 PM_ASSERT_READOUT_IMAGE(ro1, false); 1165 PM_ASSERT_READOUT_IMAGE(out1, false); 1084 1166 numCols = ro1->image->numCols; 1085 1167 numRows = ro1->image->numRows; … … 1091 1173 PM_ASSERT_READOUT_NON_NULL(ro2, false); 1092 1174 PM_ASSERT_READOUT_IMAGE(ro2, false); 1175 PM_ASSERT_READOUT_IMAGE(out2, false); 1093 1176 if (numCols == 0 && numRows == 0) { 1094 1177 numCols = ro2->image->numCols; … … 1111 1194 PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(poorFrac, 0.0, false); 1112 1195 PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(poorFrac, 1.0, false); 1113 PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(sysError, 0.0, false); 1114 PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(sysError, 1.0, false); 1196 PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(kernelError, 0.0, false); 1197 PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(kernelError, 1.0, false); 1198 PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(covarFrac, 0.0, false); 1199 PS_ASSERT_FLOAT_LESS_THAN(covarFrac, 1.0, false); 1115 1200 if (region && psRegionIsNaN(*region)) { 1116 1201 psString string = psRegionToString(*region); 1117 psError(P S_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) contains NAN values", string);1202 psError(PM_ERR_PROG, true, "Input region (%s) contains NAN values", string); 1118 1203 psFree(string); 1119 1204 return false; … … 1124 1209 bool threaded = pmSubtractionThreaded(); // Running threaded? 1125 1210 1126 // Outputs1127 if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {1128 if (!out1->image) {1129 out1->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);1130 // XXX if (threaded) {1131 // XXX psMutexInit(out1->image);1132 // XXX }1133 }1134 if (ro1->variance) {1135 if (!out1->variance) {1136 out1->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);1137 // XXX if (threaded) {1138 // XXX psMutexInit(out1->variance);1139 // XXX }1140 }1141 psImageInit(out1->variance, 0.0);1142 }1143 }1144 if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {1145 if (!out2->image) {1146 out2->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);1147 // XXX if (threaded) {1148 // XXX psMutexInit(out2->image);1149 // XXX }1150 }1151 if (ro2->variance) {1152 if (!out2->variance) {1153 out2->variance = psImageAlloc(numCols, numRows, PS_TYPE_F32);1154 // XXX if (threaded) {1155 // XXX psMutexInit(out2->variance);1156 // XXX }1157 }1158 psImageInit(out2->variance, 0.0);1159 }1160 }1161 1211 psImage *convMask = NULL; // Convolved mask image (common to inputs 1 and 2) 1162 1212 if (subMask) { 1163 // XXX if (threaded) {1164 // XXX psMutexInit(subMask);1165 // XXX }1166 1213 if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1167 if (!out1->mask) {1168 out1->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK);1169 }1170 1214 convMask = out1->mask; 1171 1215 } 1172 1216 if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1173 if (convMask) { 1174 if (out2->mask) { 1175 psFree(out2->mask); 1176 } 1177 out2->mask = psMemIncrRefCounter(convMask); 1178 } else { 1179 if (!out2->mask) { 1180 out2->mask = psImageAlloc(numCols, numRows, PS_TYPE_IMAGE_MASK); 1181 } 1217 if (!convMask) { 1182 1218 convMask = out2->mask; 1183 1219 } 1184 1220 } 1185 psImageInit(convMask, 0);1186 } 1187 1188 psImage *sys1 = NULL, *sys2 = NULL; // Systematic error images 1221 } 1222 1223 psImage *kernelErr1 = NULL, *kernelErr2 = NULL; // Kernel error images 1224 #ifdef USE_KERNEL_ERR 1189 1225 if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1190 sys1 = subtractionSysErrImage(ro1->image, sysError); 1191 // XXX if (threaded && sys1) { 1192 // XXX psMutexInit(sys1); 1193 // XXX } 1226 kernelErr1 = subtractionKernelErrImage(ro1->image, kernelError); 1194 1227 } 1195 1228 if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1196 sys2 = subtractionSysErrImage(ro2->image, sysError); 1197 // XXX if (threaded && sys2) { 1198 // XXX psMutexInit(sys2); 1199 // XXX } 1200 } 1229 kernelErr2 = subtractionKernelErrImage(ro2->image, kernelError); 1230 } 1231 #endif 1201 1232 1202 1233 int size = kernels->size; // Half-size of kernel 1203 1234 1204 1235 // Get region for convolution: [xMin:xMax,yMin:yMax] 1205 int xMin = size, xMax = numCols- size;1206 int yMin = size, yMax = numRows- size;1236 int xMin = kernels->xMin + size, xMax = kernels->xMax - size; 1237 int yMin = kernels->yMin + size, yMax = kernels->yMax - size; 1207 1238 if (region) { 1208 1239 xMin = PS_MAX(region->x0, xMin); … … 1247 1278 psArrayAdd(args, 1, (pmReadout*)ro1); // Casting away const 1248 1279 psArrayAdd(args, 1, (pmReadout*)ro2); // Casting away const 1249 // Since adding to the array can impact the reference count, we need to lock 1250 // XXX if (sys1) { 1251 // XXX psMutexLock(sys1); 1252 // XXX } 1253 psArrayAdd(args, 1, sys1); 1254 // XXX if (sys1) { 1255 // XXX psMutexUnlock(sys1); 1256 // XXX } 1257 // XXX if (sys2) { 1258 // XXX psMutexLock(sys2); 1259 // XXX } 1260 psArrayAdd(args, 1, sys2); 1261 // XXX if (sys2) { 1262 // XXX psMutexUnlock(sys2); 1263 // XXX } 1264 // XXX if (subMask) { 1265 // XXX psMutexLock(subMask); 1266 // XXX } 1280 psArrayAdd(args, 1, kernelErr1); 1281 psArrayAdd(args, 1, kernelErr2); 1267 1282 psArrayAdd(args, 1, subMask); 1268 // XXX if (subMask) {1269 // XXX psMutexUnlock(subMask);1270 // XXX }1271 1283 PS_ARRAY_ADD_SCALAR(args, maskBad, PS_TYPE_IMAGE_MASK); 1272 1284 PS_ARRAY_ADD_SCALAR(args, maskPoor, PS_TYPE_IMAGE_MASK); … … 1283 1295 psFree(job); 1284 1296 } else { 1285 subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, sys1, sys2,1286 subMask, maskBad, maskPoor, poorFrac, subRegion, kernels, doBG,1287 useFFT);1297 subtractionConvolvePatch(numCols, numRows, x0, y0, out1, out2, convMask, ro1, ro2, 1298 kernelErr1, kernelErr2, subMask, maskBad, maskPoor, poorFrac, 1299 subRegion, kernels, doBG, useFFT); 1288 1300 } 1289 1301 psFree(subRegion); … … 1292 1304 1293 1305 if (!psThreadPoolWait(false)) { 1294 psError( PS_ERR_UNKNOWN, false, "Error waiting for threads.");1306 psError(psErrorCodeLast(), false, "Error waiting for threads."); 1295 1307 return false; 1296 1308 } … … 1307 1319 psFree(job); 1308 1320 } 1309 1310 // XXX if (subMask) {1311 // XXX psMutexDestroy(subMask);1312 // XXX }1313 // XXX if (sys1) {1314 // XXX psMutexDestroy(sys1);1315 // XXX }1316 // XXX if (sys2) {1317 // XXX psMutexDestroy(sys2);1318 // XXX }1319 1321 } 1320 1322 psImageConvolveSetThreads(oldThreads); 1321 1323 1322 psFree( sys1);1323 psFree( sys2);1324 psFree(kernelErr1); 1325 psFree(kernelErr2); 1324 1326 1325 1327 // Calculate covariances 1326 // This can take a while, so we only do it for a single instance 1327 // XXX psImageCovarianceCalculate could be multithreaded 1328 // This can be fairly involved, so we only do it for a single instance 1329 // Enable threads for covariance calculation, since we're not threading on top of it. 1330 oldThreads = psImageCovarianceSetThreads(true); 1328 1331 if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) { 1329 1332 psKernel *kernel = pmSubtractionKernel(kernels, 0.0, 0.0, false); // Convolution kernel 1333 psKernelTruncate(kernel, covarFrac); 1330 1334 out1->covariance = psImageCovarianceCalculate(kernel, ro1->covariance); 1331 1335 psFree(kernel); … … 1334 1338 psKernel *kernel = pmSubtractionKernel(kernels, 0.0, 0.0, 1335 1339 kernels->mode == PM_SUBTRACTION_MODE_DUAL); // Conv. kernel 1340 psKernelTruncate(kernel, covarFrac); 1336 1341 out2->covariance = psImageCovarianceCalculate(kernel, ro2->covariance); 1337 1342 psFree(kernel); 1338 1343 } 1344 psImageCovarianceSetThreads(oldThreads); 1339 1345 1340 1346 // Copy anything that wasn't convolved … … 1376 1382 } 1377 1383 1378 1379 1384 psLogMsg("psModules.imcombine", PS_LOG_INFO, "Convolve image: %f sec", 1380 1385 psTimerClear("pmSubtractionConvolve"));
Note:
See TracChangeset
for help on using the changeset viewer.
