Imaging.apply_mask: trim the native arrays to the mask plus a PSF half-width, so the FFT frame follows the mask rather than the stamp #43
Unanswered
samlange04
asked this question in
Ideas & Proposals
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
While fitting JWST lenses I noticed that the size of the image a mask is cut from, not the mask, sets the cost of every PSF convolution in a fit. I would like to propose a small change to
Imaging.apply_maskthat removes that dependence, which I have implemented and run on real data, and get your view (in particular on the packaged default) before you look at the PR.Symptom.
ConvolverStatesizes its FFT frame from the native array the mask lives on (full_shape = mask_shape + kernel - 1, withmask_shapethe native shape;required_shape_for_kernelonly ever pads). So the convolution of every model image, and of every column of a pixelization's mapping matrix, runs on the frame of the stamp the mask was cut from, however small the mask. A 1.5" mask on a 12" JWST stamp at 0.03"/px fits in about 136 px but convolves on 400 x 400: 160k pixels, 16x the area the fit needs. GPU memory for a pixelized search scales as batch x source pixels x frame, which is what set the batch size on a 48 GB A6000 for one of my lenses. Cropping the PSF does nothing here (37, 31 and 21 px kernels all give a 400 px frame); only the stamp size matters, and asking users to cut their stamps by hand beforefrom_fitsis the wrong fix, since the stamp is also what the lens-light and contaminant masks are drawn on.Proposal.
Imaging.apply_mask(mask, trim_to_mask=None)cuts the native arrays, after masking, to the smallest centred frame that holds every unmasked pixel a PSF half-width plus a one-pixel buffer from the edge:mask_2d_util.trimmed_shape_for_kernel(mask_2d, kernel_shape_native, buffer=1)returns that shape. It is centred on the mask's central pixel and keeps the parity of each dimension, so the central pixel (the origin of the scaled coordinate system, theresized_array_2d_fromconvention) stays on the same pixel: scaled coordinates, positions and priors are unchanged. A mask whose unmasked pixels sit closer to the edge than the half-width makes the frame grow, as the convolver would pad it.Imaging.trimmed_to_mask_from(buffer=1)resizesdata,noise_map, both over-sampling size arrays and the mask (Array2D.resized_from, mask paddedTrue), rebuilds theConvolverState, carries thenoise_covariance_matrixover unchanged (only masked pixels are removed, so the slim ordering is the same), and returnsselfwhen the dataset is already that size. Asparse_operatoris discarded with the existing warning, asapply_maskdoes.apply_mask'strim_to_mask=Nonereads a new config key,general.yaml [dataset] trim_to_mask.Nothing a fit sees changes: the slim arrays, the blurring region and the convolution are identical (to FFT rounding), and the mask and data are unchanged so search identifiers are unchanged: a trimmed chain resumes an untrimmed one and vice versa. On a synthetic 200 px stamp with a 1.6" mask and an 11 px PSF a parametric
FitImaginglog-likelihood agrees to 1e-11 between the two.What a user does see change:
dataset.shape_native,data.native, and every native model image or residual map a fit produces are the trimmed shape, andMask2D.circular(shape_native=dataset.shape_native, ...)built afterapply_maskno longer matches a mask built before it. Project code that buildsArray2Ds or image meshes on the mask variable it passed toapply_maskhas to usedataset.maskinstead. In my projects that was two call sites (an adapt-image over-sampling map and a Hilbert image mesh), plus two places that combine a fit's arrays with the full stamp (a source-subtracted image written for a lens-light fit, and a sensitivity-mapping simulator), which pad back withresized_from, exactly, since the central pixel is shared.Numbers (
ConvolverStateframe with the real masks):Per-call GPU timings still to come; the memory saving is what let me raise the batch size.
The question for you: should the packaged default be on or off? The branch ships it on (
trim_to_mask: true), on the argument that nothing a fit sees changes and every masked fit gets a smaller frame for free, withfalsefor anyone who wants the input image's native shape kept. The case for off is that native shapes change silently for every existing user whose code builds things on the pre-mask mask (the pattern above), which is a visible behaviour change in a point release. I am happy either way; the PR has a note for the reviewer to pick, and it is a one-line config change. The unit-test config keeps it off either way so fixture shapes are untouched.PR: PyAutoLabs/PyAutoArray#675
All reactions