New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM-41648: Write a Plugin for computing higher-order moments #44
Conversation
5e35391
to
1295bf0
Compare
COPYRIGHT
Outdated
Copyright 2017-2018 University of Washington | ||
Copyright 2015, 2017 The Regents of the University of California | ||
Copyright 2016 University of Illinois Board of Trustees | ||
Copyright 2011-2014 LSST Corporation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pinging @timj to weigh in on the COPYRIGHT file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LSST Corp copyrights were handed to AURA so these should be combined with AURA entry.
d319a23
to
bc63b2a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not done yet, but wanted you to see comments so far since I was also starring at _calculate_higher_order_moments...
@@ -45,7 +45,7 @@ class HsmShapeConfig(measBase.SingleFramePluginConfig): | |||
|
|||
shearType = pexConfig.ChoiceField[str]( | |||
doc="The desired method of PSF correction using GalSim. The first three options return an e-type " | |||
"distortion, whereas the last option returns a g-type shear.", | |||
"distortion, whereas the last option returns a g-type shear.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is just a blackening, but personally, I find the following (black-acceptable) format nicer to read:
shearType = pexConfig.ChoiceField[str](
doc=(
"The desired method of PSF correction using GalSim. The first three options return an e-type "
"distortion, whereas the last option returns a g-type shear."
),
)
Your call though.
Parameters | ||
---------- | ||
image : `~lsst.afw.image.Image` | ||
Image of the PSF |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just Image of source
?
the object. For any well-sampled image, the zeroth order moment is 1, | ||
the first order moments are 0, and the second order moments are 0.5 for xx | ||
and yy and 0 for xy. For a symmetric profile, the odd order moments are | ||
zeros. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this implies that the normalized x coordinate lies either on the unnormalized major or minor axis. Can you clarify which (assuming I got that first part right)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, and I don't think it matters whether it lies along the major or minor axis due to the normalization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you swapped x from major -> minor, doesn't that mean that you also swap Mpq to Mqp ? That's what I'm trying to clarify, that the moments are in some sense M_major_minor. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh hmm, good point. I think x is along the major axis, but this makes me question if I should think of these numbers are coordinate-independent or not. I had naively assumed it was. If we have to transform them from pixel-coordinates to sky-coordinates, we need to remember to transform these accordingly then.
if bbox.getArea() == 0: | ||
raise measBase.MeasurementError(self.NO_PIXELS.doc, self.NO_PIXELS.number) | ||
|
||
# # Ensure that the centroid is within the bounding box. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going to leave commented-out code, then I think you need to attach an explanation as to why it's commented out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, these should be removed actually.
image_weight = weight * image_array | ||
normalization = np.sum(image_weight) | ||
|
||
results = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saw the use_linear_algebra
bit appear, so seems you're staring at this too :).
Aside from the 2x2 specialization (which I think should be the only code path BTW), I got a tad bit more performance by caching the power arrays. Something like:
std_x_pow = {}
std_y_pow = {}
pmin = min(p for p, q in pqlist)
qmin = min(q for p, q in pqlist)
if pmin == 0:
std_x_pow[0] = 1.0
elif pmin == 1:
std_x_pow[1] = std_x
else:
std_x_pow[pmin] = std_x**pmin
if qmin == 0:
std_y_pow[0] = 1.0
elif qmin == 1:
std_y_pow[1] = std_y
else:
std_y_pow[qmin] = std_y**qmin
for p in range(pmin, max(p for p, q in pqlist)+1):
std_x_pow[p+1] = std_x_pow[p] * std_x
for q in range(qmin, max(q for p, q in pqlist)+1):
std_y_pow[q+1] = std_y_pow[q] * std_y
results = {}
for p, q in pqlist:
tmp = std_x_pow[p] * std_y_pow[q]
tmp *= image_weight
results[(p, q)] = np.sum(tmp) / normalization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, and I hope this does well even on objects with large footprints.
pmin
and qmin
are always going to be 0 given how we generate those tuples. So I am going to remove the if
-elif
-else
conditions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it occurs to me that np.vander
may be your friend here (but I didn't try it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like to have the option of having the more expensive code path, given that that was what TQ originally used in the previous HSC data, so I consider that validated. I'd like to have an option to resort to the validated code if something doesn't look right. I could add another unit test to check that both code paths result in consistent answers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like np.vander
is only for 1-d arrays. So I went with your code suggestion, minus the condition checking.
1a67504
to
eee0916
Compare
Summary of changes since you last reviewed:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I found the bug
# Measure all the moments together to save time | ||
try: | ||
hm_measurement = self._calculate_higher_order_moments( | ||
exposure.image[bbox], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be a copy of the sub-image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be clearer if the copy is inside _calculate_higher_order_moments
, but yeah, good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copying inside _calculate_higher_order_moments
would make unnecessary copies for psfImage
. I fixed it to make a deep copy on a need-to basis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, I could make the modification to the weight image and not have to make a deep copy ever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I just meant we don't want anyone to have to remember that _calculate_higher_order_moments
modifies the image. Modifying the weight is a great solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commit I just pushed looks to me like the cleanest solution - multiply the weight and image with no regards for the badpix
and mask this product as configured. It avoids making expensive deep copies, and avoids any division of pixel values (as I currently have) which may become unstable if the pixel values are close to 0.
93e1726
to
c1f1960
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks much better!
yield f"{p}{q}" | ||
|
||
def _generate_powers_of_standard_positions(self, std_x, std_y): | ||
std_x_powers, std_y_powers = {}, {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess these could be lists now. Probably doesn't matter in practice...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Looking forward to seeing some higher order moments analysis in the near future!
This package is now almost completely rewritten in the past few tickets. The COPYRIGHT is cleared and beginning from scratch.
Co-authored-by: Tianqing Zhang <ztq1996@live.com>
This recognizes Tianqing Zhang's code contribution to the higher order moments plugins.
979ef84
to
962aa21
Compare
Setting up a base branch (+PR) to receive contributions from Tianqing Zhang