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
Tickets/dm 9229 #54
Tickets/dm 9229 #54
Conversation
lenPsfScience = lenMax if self.config.padPsf else lenMin | ||
dimenS = afwGeom.Extent2I(lenPsfScience, lenPsfScience) | ||
if self.config.doAutoPadPsf: | ||
minPsfSize = nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo) |
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 may not be necessary, as this function is only called a small number of times, but it is possible to avoid all the overhead of calling a function, and the steps in the function by saying:
minPsfSize = int(np.ceil(self.KConfig.kernelSize*self.config.autoPadPsfTo))|1
This will return the number if it is odd, or the next integer if it is even
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 acknowledge two suggestions here: (1) to take the minPsfSize calculation out of a function and (2) to switch the last bit to 1 to get the next odd integer.
1: This function is not in a loop and only called once. My rationale for putting it in a function was to reduce code duplication with the unit test. If you argue that unit tests should calculate it differently, then I'll take it out of the function.
2: Flipping the last bit to 1 smells clever to me, because it doesn't read like english.
The function plus english-reading % 2 version also appears to be (counterintuitively) faster, but a microsecond (or even 100 milliseconds) won't make a difference in a long task like ModelPsfMatch:
import timeit
setupBitFlip = """
from random import random
import numpy as np
def nextOddInt(x):
return int(np.ceil(x))|1
def genKRand():
for i in range(1000):
yield 10*random()
"""
setupIfMod = """
from random import random
import numpy as np
def genKRand():
for i in range(1000):
yield 10*random()
def nextOddInt(x):
return x if x % 2 == 0 else x + 1
"""
print('%.02f us'% (1000*timeit.timeit('[nextOddInt(y) for y in genKRand()]', setup=setupIfMod, number=10)/10.))
# 0.63 us
print('%.02f us'% (1000*timeit.timeit('[nextOddInt(y) for y in genKRand()]', setup=setupBitFlip, number=10)/10.))
# 1.26 us
print('%.02f us'% (1000*timeit.timeit('[int(np.ceil(y))|1 for y in genKRand()]', setup=setupBitFlip, number=10)/10.))
# 1.07 us
# Single value
print('%.02f us'%(timeit.timeit('nextOddInt(3.4)', setup=setupIfMod, number=1000000)))
# 0.35 us
print('%.02f us'%(timeit.timeit('nextOddInt(3.4)', setup=setupBitFlip, number=1000000)))
# 0.99 us
print('%.02f us'%(timeit.timeit('int(np.ceil(3.4))|1', setup=setupBitFlip, number=1000000)))
# 0.82 us
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 speed difference here comes from setupBitFlip using int(np.ceil( and setupIfMod not, it seems to just be doing the mod on whatever type that x is. Over all I don't have a strong feeling on this and reuse in the unit test is a good enough argument for me.
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.
Yeah, whoops. Let me update those last times for posterity:
# # Single value UPDATED
print('%.02f us'%(timeit.timeit('nextOddInt(3.4)', setup=setupIfMod, number=1000000)))
# 1.23 us
print('%.02f us'%(timeit.timeit('nextOddInt(3.4)', setup=setupBitFlip, number=1000000)))
# 1.02 us
print('%.02f us'%(timeit.timeit('int(np.ceil(3.4))|1', setup=setupBitFlip, number=1000000)))
# 0.87 us
sigma2fwhm = 2. * num.sqrt(2. * num.log(2.)) | ||
|
||
|
||
def nextOddInteger(x): | ||
nextInt = int(num.ceil(x)) |
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.
please change the import to import numpy as np
, as it is what the community has settled on, and it will make this file more consistent with the rest of the stack which uses that import.
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.
👍 (but on a separate commit because it is independent of this ticket)
doAutoPadPsf = pexConfig.Field( | ||
dtype=bool, | ||
doc=("Automatically pad science PSF to minimum dimensions appropriate for the kernel. " | ||
"If the Science Psf is smaller than a the matching kernel + a minimum fraction. " |
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.
remove either "a" or "the" and change + to "add" or "pad by" the sentence is a bit confusing on the first read. I read it as "If the Science Psf is smaller than (the matching kernel + a minimum fraction)"
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.
👍
paddingPix = max(0, minPsfSize - psfSize) | ||
else: | ||
if self.config.padPsfBy % 2 != 0: | ||
raise ValueError("Config padPsfBy (%i pixels) must be even number." % |
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.
Is it worth just moving to the next even number rather than throwing an error? Or maybe being explicit is good, like you have it. If the algorithm must have an even to work, perhaps move to the next even number and put out a warning? What you have is fine, these are just some things for you to consider.
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 considered that. My reasoning for raising an error is that when setting doAutoPadPsf=False, I presume the user has reasons for turning off the automatic tuning, and thus wants exact manual control over the configs and wants ModelPsfMatchTask to just do exactly what they ask of it or quit. The behavior is consistent: Kernel too big? Raise. Padding inappropriately odd? Raise. I suspect this config option will only be used when users want precise control such as for testing config parameters like you did for the HSC image
@@ -7,6 +7,7 @@ | |||
import lsst.ip.diffim as ipDiffim | |||
import lsst.log.utils as logUtils | |||
import lsst.meas.algorithms as measAlg | |||
from lsst.ip.diffim.modelPsfMatch import nextOddInteger |
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 changed to the binary operator in the other file, remember to change where the function is used here as well
In modelPsfMatchTask, padPsf controlled whether users want to either clip or pad the Warped Psf to a standard size. We should always pad.
5a8797d
to
63a9b50
Compare
Add three config parameters to ModelPsfMatchTask that enable automatic or manual padding of the science PSF before matching to a model PSF. By default, doAutoPadPsf is set to True and will automatically pad the PSF to the minimum size appropriate, as defined by the config autoPadPsfTo (in units of the kernel size). If doAutoPadPsf is set to False, science PSFs will be padded by the padPsfBy number of pixels.
63a9b50
to
edbf328
Compare
No description provided.