-
Notifications
You must be signed in to change notification settings - Fork 46
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
Remove unneeded slicing and imageset creation in spotfinding, indexing, and integration #1705
Conversation
…g, and integration Fixes #1704
if isinstance(experiment.imageset, ImageSequence): | ||
experiment.imageset = ImageSet( | ||
experiment.imageset.data(), experiment.imageset.indices() | ||
) |
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 isn't clear to me why indexing is even creating imagesets in the first place... it appears to only be doing this in the stills indexer, so presumably you might have a better idea why this is the case?
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, so in dials.stills_process
, imageset_from_anyset is called on all images right at the start. So by the time the indexing is ran, there's no way to have an ImageSequence.
However, if dials.index
is ran, with indexing.stills.indexer=stills
, then it's possible an ImageSequence makes it this far, even though the user wishes it to be treated as a still. Therefore, the ImageSequence is reset as an ImageSet, and things like the StillsReflectionPredictor are properly picked.
In the dials.stills_process
case, before this changeset, ImageSets were always recreated, which invalidated the cache and caused a data read later on. With this changeset, they are never recreated, because of the imageset_from_anyset call.
A data read wouldn't be triggered in the dials.index
case because the data is never read in that program, so the cache doesn't matter.
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 see. It seems a bit odd that refinement should care at all about the imageset - it should only depend on the models and input parameters. Perhaps a better (at least longer term) solution would be for refinement not to depend on the imageset type when choosing which predictor to use, then constructing a new imageset in indexing shouldn't be necessary at all.
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.
Totally agree. My googling indicates that picking the StillsReflectionPredictor is the only place where ImageSequence is tested for explicitly, and I think it should instead be choosing the predictor based on if the scan is a set of stills:
Instead of
if isinstance(experiment.imageset, ImageSequence):
It should be
if experiment.scan and not experiment.scan.is_still():
Like it is elsewhere.
But that seemed like a bigger change than this fix warranted? Maybe not? If folks like that better I can add it to this PR.
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.
TBH I think this is not a question of "like or dislike" - I think very shortly this will become necessary for sane processing of grid scan data beyond simply spot finding. As such, I'd probably suggest we put together a second PR where we actively eliminate
if isinstance(experiment.imageset, ImageSequence):
type checks from the code base - this would be work which was started back in the load before heat death days but never (yet) completed.
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.
On a practical note @phyy-nx if you could put a comment in there as to why this check is in place would probably help for the medium term.
@@ -499,7 +499,8 @@ def __call__(self): | |||
|
|||
# I am 99% sure this is implied by all the code above | |||
assert (frame1 - frame0) <= len(imageset) | |||
imageset = imageset[frame0:frame1] | |||
if len(imageset) > 1: | |||
imageset = imageset[frame0:frame1] |
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 seems slightly concerning that this apparently results in an extra image read - is this the case for rotation data, or just still imagesets?
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 didn't profile dials.integrate
, but I don't think it has the same problem. I believe profile fitting is done from the strong shoeboxes, and data reading then only happens during integration itself.
In dials.stills_process
, the same imageset is handed from program to program and so it cannot be reset. In the dials.integrate
it is invoked fresh and used once. I believe :)
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.
Profile fitting is not just done from the strong shoeboxes - for rotation data they are only used to determine the profile parameters - all reflections are then gathered to generate the reference profiles.
We should probably check whether the rest is called for dials.integrate
on rotation data as a separate exercise and raise an issue if so.
if len(imageset) == 1: | ||
r, h = extract_spots(imageset) | ||
else: | ||
r, h = extract_spots(imageset[j0:j1]) |
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.
As above - it would probably be instructive to understand why this results in an extra image read.
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.
See above, but in this case it's because of the extra radial_average step we do in the XFEL GUI. That causes a data read, so when spotfinding happens and the imageset is sliced, the cache is lost. In this changeset, the same imageset is used at each step, preventing the cache loss.
Thanks for the great questions @rjgildea. Happy to clarify further or even provide the cProfile plots as needed. |
Questions raised above suggest we should probably have a better collective insight into what is going on 🙂 |
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 are questions raised in here which are bigger in scope than the PR so seems unfair to ask them to be addressed in here.
Overall, I think the changes are limited in their scope & hopefully we will be revisiting the slightly questionable bits (checking if image sets are sequences) so I am not too worried.
if isinstance(experiment.imageset, ImageSequence): | ||
experiment.imageset = ImageSet( | ||
experiment.imageset.data(), experiment.imageset.indices() | ||
) |
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.
On a practical note @phyy-nx if you could put a comment in there as to why this check is in place would probably help for the medium term.
newsfragments/1705.bugfix
Outdated
@@ -0,0 +1 @@ | |||
Speed up processing still images by reducing the number of file reads per 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.
dials.stills_process
: improve processing performance by preventing re-reading of image data
?
Any other comments? I'll do the other ImageSequence PR after this goes through. |
Updates:
If you run without nproc=1, parallel map isn't profiled in such a way that you can see what's going on with get_raw_data. However, with nproc=1, it's clear that get_raw_data is called 1080 times, twice per image. Folks interested in a second issue on this? |
Find spots is fine, 540 calls to get_raw_data:
|
More on dials.integrate. As I mentioned, get_raw_data is called twice per image, but that's because the reflection profile modeler reads each image in turn, and then the integrator reads each image in turn. Since only one image at a time is cached, there's no way to avoid 2 data reads as currently implemented. Unless the profile modeler is creating shoeboxes that are then discarded, and then recreated. In which case that does seem like a waste. The second read shouldn't be needed. (for stills it doesn't matter since we don't use the profile modeler) (moving on from this, FYI :) |
yup:
too much memory - already this is very heavy on memory just storing the boxes for the "live" block
yup, fair |
Fixes #1704