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-18610: Add fields, limited mutability, and trim/assembly-state tracking to cameraGeom #487
Conversation
czwa
commented
Sep 18, 2019
- Implement cameraGeom Builder() methods
- Remove AmpInfoRecord/Table/Catalog
b1e447a
to
216eb90
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.
Sorry for the delay, it took me a long time to understand the changes (particularly 87cfb2d). I have two big-picture concerns:
- The code misuses inheritance a lot. While I'll grant that quick conversion of client code requires that
X::Builder
be queriable just like theX
it will create, it's dangerous to make this fundamental to the builder interface and even more dangerous to do it by inheritance. With inheritance (or duck-typing), in many places you can replace an object with its builder (no need to callfinish
), which defeats the goal of minimizing mutability.
Please look into architectures based on composition instead, or simply provide duplicate methods. These methods can be removed later as the responsibility for altering existing objects becomes factored into the builders (e.g., it sounds like amplifiers only need to be transformed in very specific ways). Detector
is a fairly ill-behaved class because aDetector
object depends on theCamera
that contains it, including correlated state changes in either direction. This leads to very messy code whereCamera
's andDetector
's builders need to transfer responsibility back and forth. While you've clearly gotten it to work, I worry about maintainability; it might be a lesser evil to roll back the changes toDetector
, at least until it can be better decoupled fromCamera
.
* Copy the Amplifier's fields into the given record. | ||
* | ||
* @param[in,out] record Record to modify. | ||
* `record.getSchema().contains(this->getRecordSchema())` must be true. |
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.
Do you mean @param[out]
? If not, please clarify how record
is used as input.
//@} | ||
|
||
//@{ | ||
/// # TODO! NEVER DOCUMENTED! |
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.
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.
@czwa , could you take care of this one? I'm hoping you might actually know enough about how this field is used in ISR to be able to document it now.
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've fixed these to be more useful and represent what they currently are.
pitch = lsst.geom.Angle(detectorConfig.pitchDeg, lsst.geom.degrees) | ||
roll = lsst.geom.Angle(detectorConfig.rollDeg, lsst.geom.degrees) | ||
return Orientation(offset, refPos, yaw, pitch, roll) | ||
return detectorConfig.getOrientation() |
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.
At this point you might as well just get rid of the function and call getOrientation
directly. It's not in __all__
, so all uses should™ be confined to this 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.
I apparently already removed the reference to makeOrientation
in favor of getOrientation
, so I've removed this method.
@@ -219,18 +220,18 @@ def makeImageFromAmp(amp, imValue=None, imageFactory=afwImage.ImageU, markSize=1 | |||
img.set(imValue) | |||
# Set the first pixel read to a different value | |||
markbbox = lsst.geom.Box2I() | |||
if amp.getReadoutCorner() == 0: | |||
if amp.getReadoutCorner() == afwCameraGeom.ReadoutCorner.LL: |
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.
😱 👍
* classes. A Detector must be created initially as part of a Camera | ||
* (see Camera::Builder::add), but can then be modified either individually | ||
* (see Detector::rebuild and Detector::PartialRebuilder) or as part of | ||
* modifying the full Camera (see Detector::InCameraBuilder). |
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 name InCameraBuilder
is a bit confusing, because it suggests that it modifies the camera as a side effect (instead, it appears to mostly be used by Camera::Builder
to make a new camera). Consider renaming.
Is PartialRebuilder
used for anything other than tests?
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 like "InCameraBuilder", either, but I haven't been able to come up with anything I like better.
I've been wondering since I saw your overall review comments whether I can just drop PartialRebuilder
(which would free up Detector::Builder
as a replacement for Detector::InCameraBuilder
- though of course we're discussing renaming all of them on another thread).
@czwa , have you found any need for PartialRebuilder
in any downstream packages yet? My original use case is (vaguely) about how obs_lsst updates the Detector
attached to raw Exposures
on read, so if you've managed to make that code work without PartialRebuilder
, it's probably safe to get rid of 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.
obs_lsst
does use a rebuild without an attached camera (in assembly.py
), so I believe this is a PartialRebuilder
. I don't explicitly choose, so if this code changes, I can check that continues to work and fix it if it doesn't.
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.
WithTransformBuilder
? Kind of unwieldy...
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.
FixedTransformsBuilder
? DetachedBuilder
?
@czwa's statement that he doesn't know actually makes me less worried about this - while I'm sure he is using a PartialRebuilder
, it sounds like the APIs don't really involve the name (at least in Python, or I imagine in C++ if making heavy use of auto
).
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've completed my first pass at addressing review comments for all but the last commit (which I'll leave to @czwa). Changes are on new commits I intend to rebase and squash before merge.
On @kfindeisen 's big-picture points:
The code misuses inheritance a lot. While I'll grant that quick conversion of client code requires that X::Builder be queriable just like the X it will create, it's dangerous to make this fundamental to the builder interface and even more dangerous to do it by inheritance. With inheritance (or duck-typing), in many places you can replace an object with its builder (no need to call finish), which defeats the goal of minimizing mutability.
Please look into architectures based on composition instead, or simply provide duplicate methods. These methods can be removed later as the responsibility for altering existing objects becomes factored into the builders (e.g., it sounds like amplifiers only need to be transformed in very specific ways).
There are use cases for the builders behaving like the things they build in at least some respects, so as long as they're not actually violating Liskov substitutability, I'd like to keep the inheritance. If we change anything about this, I think they best way to go would be to not call them builders, but to keep discussions threaded let's take that to this line comment.
Detector is a fairly ill-behaved class because a Detector object depends on the Camera that contains it, including correlated state changes in either direction. This leads to very messy code where Camera's and Detector's builders need to transfer responsibility back and forth. While you've clearly gotten it to work, I worry about maintainability; it might be a lesser evil to roll back the changes to Detector, at least until it can be better decoupled from Camera.
The relationship between Detector
and Camera
is preexisting and not something we can change easily; the details may be down to history, but we're pretty boxed in by use cases and established APIs (DM-14980 has a bit of that history, if you're curious). And I don't think backing out the Detector
changes here is at all viable - they're too tied to everything else, and frankly it's been quite difficult to find a solution that closes the immutability loophole we have right now without being even more complex, so I'm not optimistic we'll be able to come back later and do substantially better.
What might be possible is removing Detector::PartialRebuilder
, which would let us merge and simplify Detector::Builder
and Detector::InCameraBuilder
, but this depends on whether we've actually ended up using Detector::PartialRebuilder
downstream. Again, let's take further discussion to a relevant line comment for better threading.
Note that I've redirected a few other line comments to the threads linked above as well.
//@} | ||
|
||
//@{ | ||
/// # TODO! NEVER DOCUMENTED! |
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.
@czwa , could you take care of this one? I'm hoping you might actually know enough about how this field is used in ISR to be able to document it now.
} | ||
// Make a single big TransformMap. | ||
auto transformMap = TransformMap::make(getNativeCameraSys(), connections); | ||
// Make actual Detector objects, giving each the full TransformMap. |
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 have a definite use case for a detector wanting a transform to another detector, but it's not completely implausible. More importantly, given that the Detector::Builder
s only have the connections that tie to their own detector and not the non-detector ones, I think it'd be at least as much work to construct a distinct TransformMap
for each detector that has both its transforms and the non-detector transforms but not any of the other detector-specific transforms.
src/cameraGeom/Detector.cc
Outdated
// Given that the number of amplifiers is generally quite small (even LSST | ||
// only has 16), we just do a linear search in the vector, as adding a map | ||
// of some kind is definitely more storage and code complexity, without | ||
// necessarily being any faster. |
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.
Added doc comment, and moved this comment (with some tweaks) to the Detector::_amplifiers
data member.
namespace { | ||
|
||
template <typename Iter> | ||
Iter findConnection(Iter first, Iter last, CameraSys const & toSys) { |
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, but not enough to be worth doing anything about, I think, when the alternatives involve either adding a new header, some more friending, or a public method.
// Deserialized connections should already be standardized, but be | ||
// defensive anyway. | ||
auto const referenceSys = getReferenceSys(connections); | ||
connections = standardizeConnections(referenceSys, std::move(connections)); |
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.
All the std::move
are a bit unfortunate, I guess I imagined it would all fit on one line. 😞
Do we need to worry about what the referenceSys
is? I think I remember you saying something about how PIXELS
is the only right answer...
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.
All the std::move are a bit unfortunate
My turn to say, "that's C++[11] for you," I guess. After playing around in Rust a bit (where move is actually the default, and that feels surprisingly natural), it's a real shame C++ didn't bite the bullet and add an operator for it - it's too fundamental to involve that many characters.
Do we need to worry about what the referenceSys is? I think I remember you saying something about how PIXELS is the only right answer...
PIXELS is the only right answer for the nativeSys
option in the Python configuration for Detector
. It's also true that the TransformMap
s created by cameraGeom actually always use FOCAL_PLANE
as the referenceSys
, but TransformMap
itself is better behaved than the classes that currently use it, and does not rely on 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.
Thanks for another careful review, @kfindeisen, especially considering that both this and the geom changes were enormous and structured in a way that made them difficult to look at.
Unfortunately, unlike with the geom changes, I feel like we may have brought you in too late here - the issues you brought up really make me wish I had the time to step back and re-do some of this, or that I'd thought more about trying to make inheritance relationships more natural earlier in the process. I simply don't have time to do that now, and I think we do really need (soon) many of the changes here that are now impossible to decouple from the more problematic ones. As I've argued, I think the problems that remain are likely to remain hypothetical rather than practical, and we've at least traded public/Python mutability loopholes for private C++ mutability loopholes. I think that makes it worthwhile to proceed without major additional changes on this ticket. But I want to make it clear that this is about expediency and the branch being "good enough", rather than objecting fundamentally to your arguments.
This may just be rationalization, but I also think it may make sense for the work consolidating the Python construction logic to proceed before taking another big-picture look at these classes. That may well remove or replace some boundary conditions, and I am confident it will at least shrink the code surface that depends on the Builder classes, making further changes easier. That's not a guarantee that we'll actually be able to return to these - C++ developer time is always in short supply - but I hope we will.
e3be8ef
to
ef434b8
Compare
29da6ae
to
53fe196
Compare
AmpInfoRecord et al remain, and will be removed in subsequent tickets. API changes (especially in Python) have been minimized, but could not be avoided entirely.
We've apparently always ignored the documentation that said these should only be the usable parts of those regions, and it's better to bring that documentation in line with reality than change current usage.
* Add AssemblyState enum for amplifiers to increase flexibility of hasRawInfo(). * Add getDataBBox() accessor to amplifiers to provide consistent BBox access, regardless of AssemblyState. * Add helpers to CameraConfig class to generate correctly typed values from input. * Simplify CameraFactory to use fromConfig() and new CameraConfig methods to construct detectors. * Add method to show transformMap connections (as there was previously no way to inspect this easily from python).
* Defer AssemblyState to DM-21277. * Remove unneeded methods that duplicate functionality. * Be consistent with TransformMap.getConnections naming.
* Add AssemblyState enum for amplifiers to increase flexibility of hasRawInfo(). * Add getDataBBox() accessor to amplifiers to provide consistent BBox access, regardless of AssemblyState. * Add helpers to CameraConfig class to generate correctly typed values from input. * Simplify CameraFactory to use fromConfig() and new CameraConfig methods to construct detectors. * Add method to show transformMap connections (as there was previously no way to inspect this easily from python).
53fe196
to
82f031a
Compare