-
Notifications
You must be signed in to change notification settings - Fork 11
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
82 spot analysis for peak intensity correction #87
82 spot analysis for peak intensity correction #87
Conversation
a0512b8
to
8da78ac
Compare
@e10harvey @braden6521 I added both of you as reviewers in case one of you really wants to do this review, not because I think you both need to do it. I suggest adding a comment that you're claiming the review. |
Thank you, @bbean23. This is a very large diff. It is impossible for me to review this. Can you split this into multiple PRs in a logical manner. Perhaps on a per-class or per-related tests basis. It would also be helpful to the reviewers to provide a summary of the changes in the PR description. The longer this PR sits, the harder it is going to be merge this in. Please to to rebase on-top of develop on a weekly basis to avoid high cognitive load when resolving conflicts. |
Sorry about this PR being so big. As you can see by the description, I actually started this PR because I realized that it was becoming so big in the first place.
|
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.
@bbean23, some of this was over my head to be honest. But the code looks well written and well documented. I have some simple suggestions, but feel free to take or leave them.
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | ||
Conversion function to get the physical point in space for the given x/y position information. Used in the | ||
default self.scale implementation. Defaults to 1 meter per pixel. |
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.
Some questions, although this is not necessarily wrong:
- Does this assume that the camera is normal to the fiducials?
- Is distortion taken into account?
- Will different pixels ever have different scales?
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.
These are really good questions, and I'm glad that you brought them up. I've updated the docstring to try and answer them:
"""
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional
Conversion function to get the physical point in space for the given x/y position information. Used in the
default self.scale implementation. A good implementation of this function will correct for many factors such
as relative camera position and camera distortion. For extreme accuracy, this will also account for
non-uniformity in the target surface. Defaults to a simple 1 meter per pixel model.
"""
contrib/app/SpotAnalysis/PeakFlux.py
Outdated
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 don't think I have the background to fully understand how this works. But generally, this code looks good.
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.
Thanks! I don't think you're missing a background, you're just not in my head. :)
In reality, understanding this class requires understanding the SpotAnalysis class, which will hopefully get easier to do with some examples.
class AbstractFiducials(ABC): | ||
def __init__(self, style=None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): | ||
""" | ||
A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects | ||
in the scene. It is suggested that each implementing class be paired with a complementary locator method or | ||
SpotAnalysisImageProcessor. | ||
|
||
Parameters | ||
---------- | ||
style : RenderControlPointSeq, optional | ||
How to render this fiducial when using the defaul render_to_plot() method. By default rcps.default(). | ||
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | ||
Conversion function to get the physical point in space for the given x/y position information. Used in the | ||
default self.scale implementation. Defaults to 1 meter per pixel. | ||
""" |
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.
We should probably get in the habit of adding docstrings to all our classes just so it's easier to add these new classes to the docstring test later. Feel free to do this at a later date too. I noticed a few classes with no docstrings here but didn't point them all out.
class AbstractFiducials(ABC): | |
def __init__(self, style=None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): | |
""" | |
A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects | |
in the scene. It is suggested that each implementing class be paired with a complementary locator method or | |
SpotAnalysisImageProcessor. | |
Parameters | |
---------- | |
style : RenderControlPointSeq, optional | |
How to render this fiducial when using the defaul render_to_plot() method. By default rcps.default(). | |
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | |
Conversion function to get the physical point in space for the given x/y position information. Used in the | |
default self.scale implementation. Defaults to 1 meter per pixel. | |
""" | |
class AbstractFiducials(ABC): | |
""" | |
A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects | |
in the scene. It is suggested that each implementing class be paired with a complementary locator method or | |
SpotAnalysisImageProcessor. | |
Parameters | |
---------- | |
style : RenderControlPointSeq, optional | |
How to render this fiducial when using the defaul render_to_plot() method. By default rcps.default(). | |
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | |
Conversion function to get the physical point in space for the given x/y position information. Used in the | |
default self.scale implementation. Defaults to 1 meter per pixel. | |
""" | |
def __init__(self, style=None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): |
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 wasn't sure where to put these docstrings, since they seem like they belong both on the class description and on the init. It looks like realpython suggests splitting it, putting the description on the class and the parameters on the method. I've updated the code to follow that approach. What do you think?
https://realpython.com/documenting-python-code/#class-docstrings
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.
Yes, that sounds like a good idea! I like that standard.
@property | ||
def orientation(self) -> v3.Vxyz: | ||
return v3.Vxyz([0, 0, 0]) |
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 this "orientation" supposed to have a rotation component too, or is this a pointing direction?
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's intended to be an absolute orientation in relation to the camera. Do we also need a pointing direction for that?
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 want a full six degree of freedom orientation, then you would need a rotation and translation, but I'm not sure that's what you're trying to do here.
def orientation(self) -> v3.Vxyz: | ||
"""The orientation(s) of this instance, in radians. This is relative to | ||
the source image, where x is positive to the right, y is positive down, | ||
and z is positive in (away from the camera).""" |
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 this supposed to be "radians?" it looks like it outputs a 3d vector? If it is radians, did you consider using a scipy.spatial.transform.Rotation object?
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.
That is a brilliant idea! Thanks! 🧠
If you don't mind, please take a look at the updated description and let me know what you think 2568e2d:
"""
The orientation of the normal vector(s) of this instance.
This is relative to the orthorectified source image, where x is positive
to the right, y is positive down, and z is positive in (away from the
camera).
This can be used to describe the forward transformation from the
camera's perspective. For example, an aruco marker whose origin is in
the center of the image and is facing towards the camera could have the
orientation::
Rotation.from_euler('y', np.pi)
If that same aruco marker was also placed upside down, then it's
orientation could be::
Rotation.from_euler(
'yz',
[ [np.pi, 0],
[0, np.pi] ]
)
"""
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 that description is overall good! I'm not sure I understand what is meant by:
relative to the orthorectified source image
Is this the image on the camera sensor? If so, why is this orthorectified? If it is the camera or the target coordinates, I would just expect "camera" or "target."
Also I would add that this is just a rotation, not translation. When I think of an orientation, I usually think of rotation and translation (six degree of freedom (or six DOF)). Maybe consider renaming this rotation?
opencsp/common/lib/cv/spot_analysis/image_processor/SupportingImagesCollectorImageProcessor.py
Show resolved
Hide resolved
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 is nice
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.
Thanks! It's really handy for this sort of situation where:
- you're going to be using lots of random classes
- they all have the same name so it's easy to tell which package they came from
1a35c4a
to
2568e2d
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.
Thank you for your thoughtful and considerate review, as always! I've made some commits to try and improve the code based on your comments. Please let me know what you think of these.
contrib/app/SpotAnalysis/PeakFlux.py
Outdated
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.
Thanks! I don't think you're missing a background, you're just not in my head. :)
In reality, understanding this class requires understanding the SpotAnalysis class, which will hopefully get easier to do with some examples.
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | ||
Conversion function to get the physical point in space for the given x/y position information. Used in the | ||
default self.scale implementation. Defaults to 1 meter per pixel. |
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.
These are really good questions, and I'm glad that you brought them up. I've updated the docstring to try and answer them:
"""
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional
Conversion function to get the physical point in space for the given x/y position information. Used in the
default self.scale implementation. A good implementation of this function will correct for many factors such
as relative camera position and camera distortion. For extreme accuracy, this will also account for
non-uniformity in the target surface. Defaults to a simple 1 meter per pixel model.
"""
class AbstractFiducials(ABC): | ||
def __init__(self, style=None, pixels_to_meters: Callable[[p2.Pxy], v3.Vxyz] = None): | ||
""" | ||
A collection of markers (such as an ArUco board) that is used to orient the camera relative to observed objects | ||
in the scene. It is suggested that each implementing class be paired with a complementary locator method or | ||
SpotAnalysisImageProcessor. | ||
|
||
Parameters | ||
---------- | ||
style : RenderControlPointSeq, optional | ||
How to render this fiducial when using the defaul render_to_plot() method. By default rcps.default(). | ||
pixels_to_meters : Callable[[p2.Pxy], v3.Vxyz], optional | ||
Conversion function to get the physical point in space for the given x/y position information. Used in the | ||
default self.scale implementation. Defaults to 1 meter per pixel. | ||
""" |
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 wasn't sure where to put these docstrings, since they seem like they belong both on the class description and on the init. It looks like realpython suggests splitting it, putting the description on the class and the parameters on the method. I've updated the code to follow that approach. What do you think?
https://realpython.com/documenting-python-code/#class-docstrings
|
||
class NullImageSubtractionImageProcessor(AbstractSpotAnalysisImagesProcessor): | ||
""" | ||
Subtracts the NULL supporting image from the primary image, if there is an associated NULL 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.
It's an image of a target without a beam on it. (Thanks for prompting me to add descriptions 62e77ae)
I wasn't sure what to call this type of image. Do you have a good idea?
opencsp/common/lib/cv/spot_analysis/image_processor/AnnotationImageProcessor.py
Show resolved
Hide resolved
@property | ||
def orientation(self) -> v3.Vxyz: | ||
# TODO untested | ||
return np.zeros((3, self.points.x.size)) |
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 do like that more. Done!
@property | ||
def orientation(self) -> v3.Vxyz: | ||
return v3.Vxyz([0, 0, 0]) |
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's intended to be an absolute orientation in relation to the camera. Do we also need a pointing direction for that?
def orientation(self) -> v3.Vxyz: | ||
"""The orientation(s) of this instance, in radians. This is relative to | ||
the source image, where x is positive to the right, y is positive down, | ||
and z is positive in (away from the camera).""" |
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.
That is a brilliant idea! Thanks! 🧠
If you don't mind, please take a look at the updated description and let me know what you think 2568e2d:
"""
The orientation of the normal vector(s) of this instance.
This is relative to the orthorectified source image, where x is positive
to the right, y is positive down, and z is positive in (away from the
camera).
This can be used to describe the forward transformation from the
camera's perspective. For example, an aruco marker whose origin is in
the center of the image and is facing towards the camera could have the
orientation::
Rotation.from_euler('y', np.pi)
If that same aruco marker was also placed upside down, then it's
orientation could be::
Rotation.from_euler(
'yz',
[ [np.pi, 0],
[0, np.pi] ]
)
"""
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.
@bbean23, thanks for answering my questions. Feel free to take or leave my new comments. I still need some more exposure to all this to fully wrap my head around it. I tried to do my best with this large PR, but in general the code looks good.
0e193f2
to
4341257
Compare
@bbean23: I restarted the windows2022 CI. |
4341257
to
e2d5055
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.
Approved!
…" example, expand SpotAnalysis desired features list
…tAnalysisOperable
… add initial_min and initial_max to PopulationStatisticsImageProcessor
…ure of the Pxy and Vxyz classes
e2d5055
to
918a09b
Compare
Note: this ticket got unwieldly big. Splitting between here and #91
Add spot analysis code to analyze peak intensities of a heliostat. The necessary features include:
Additional features that might be helpful include: