-
Couldn't load subscription status.
- Fork 3
Beam centre finder #169
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
Beam centre finder #169
Conversation
…. We need to recompute wavelength after moving pixels
|
@wpotrzebowski the final $I(Q)$results have changed, because we are masking both numerator and denominator now (this was an oversight in the old code). |
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 comments for now, I did not look at everything in detail yet.
src/ess/sans/beam_center.py
Outdated
| from .normalization import normalize | ||
|
|
||
|
|
||
| def _center_of_mass(data): |
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.
Type hints missing everywhere
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.
fixed
src/ess/sans/beam_center.py
Outdated
| summed = data.sum(list(set(data.dims) - set(data.meta['position'].dims))) | ||
| v = sc.values(summed.data) | ||
| com = sc.sum(summed.meta['position'] * v) / v.sum() | ||
| return com.fields.x, com.fields.y |
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 assumes that the beam direction is z. Should this be checked somewhere?
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 tried to make it general in terms of normal to the direction of the beam. please check
src/ess/sans/beam_center.py
Outdated
| c = ((data['top'] - ref)**2 + (data['left'] - ref)**2 + | ||
| (data['bottom'] - ref)**2) / ref**2 | ||
| out = c.sum().value | ||
| print(xy, 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.
| print(xy, 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.
This should go to the logger 👍
src/ess/sans/beam_center.py
Outdated
| pi = sc.constants.pi.value | ||
| phi_offset = sc.scalar(pi / 4, unit='rad') | ||
| phi = (sc.atan2(y=data.coords['position'].fields.y, | ||
| x=data.coords['position'].fields.x) + | ||
| phi_offset) % (2 * (pi * sc.units.rad)) | ||
| phi_bins = sc.linspace('phi', 0, pi * 2, 5, unit='rad') | ||
| quadrants = ['right', 'top', 'left', 'bottom'] |
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 sure I follow entirely, but given the pi/4 and naming (right, top, ...), it looks like you make an X cut into quadrants? I imagine this is fine for square detector banks, but what if the bank is rectangular? Wouldn't you get very different pixel counts in the (left/right) vs (top/bottom) quadrants?
Can you make a + cut instead to avoid this?
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.
Nice observations. I tested a + cut instead and I get nice results, so I will go with that. Thanks.
src/ess/sans/beam_center.py
Outdated
| # Make a copy of the original data | ||
| data = sc.DataArray(data=sample.data) | ||
| coord_list = ['position', 'sample_position', 'source_position'] | ||
| for c in coord_list: | ||
| data.coords[c] = sample.meta[c].copy(deep=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.
- Why are you not using
data=data.copy()? Is it do avoid copying wrong wavelength/Q coords from previous iterations? data = sc.DataArray(data=sample.data)will not make a copy of the data values, is that intentional? Note that if you have event data then this will also affect event coords.- Are there no prior masks (such as TOF, or dead pixels) that need to be copied?
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 do avoid copying wrong wavelength/Q coords from previous iterations?
Yes
will not make a copy of the data values, is that intentional?
Yes, I don't think we need a deep copy.
Note that if you have event data then this will also affect event coords.
Not sure what you mean by this will also affect event coords.
Are there no prior masks (such as TOF, or dead pixels) that need to be copied?
hmm yes, masks are missing! Thanks. I would like to change to use da.copy() and then da.coords.clear() once I can use the latest version of Scipp, but we first need to deal with the variances broadcast issue before we can do this.
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 sure what you mean by this will also affect event coords
I mean, not copying the data implies not copying the event coords (since they are "within" the data values).
src/ess/sans/conversions.py
Outdated
|
|
||
|
|
||
| def sans_elastic(gravity: bool = False) -> dict: | ||
| def phi(position: sc.Variable) -> sc.Variable: |
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.
Should at the very least document that is for a beam direction along z. I do not know how precisely this is fulfilled 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.
hmm nice catch, we are not using this anymore. I forgot to remove 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.
There are also other places where the same assumption is made in this code.
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 left this in the end and generalized it so that we don't assume the beam is along z.
src/ess/sans/i_of_q.py
Outdated
| lu = sc.lookup(mask, mask.dim) | ||
| if da.coords.is_edges(mask.dim): | ||
| sampling = sc.midpoints(da.coords[mask.dim]) | ||
| else: | ||
| sampling = da.coords[mask.dim] | ||
| da.masks[name] = lu[sampling] |
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.
Hmm, is considering the midpoints the correct solution? Shouldn't we mask all bins that have overlap with a masked region instead? Something like
dim = mask.dim
edges = sc.lookup(mask, dim)[da.coords[dim]]
da.masks[name] = edges[dim, 1:] | edges[dim, :-1]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 made a function mask_range which basically implements what you suggest, and works for both binned and dense data. Not sure if that should go somewhere more general than in the SANS submodule, as it's fully generic.
Yes
Correct (and we will use
Yes
They are the same python object, so it should not matter. But I need to check about maybe also masking the background run... |
src/ess/sans/beam_center.py
Outdated
| Cost function for determining how close the I(Q) curves are in all four quadrants. | ||
| """ | ||
| ref = data['north-east'] | ||
| c = ((data['north-west'] - ref)**2 + (data['south-west'] - ref)**2 + | ||
| (data['south-east'] - ref)**2) / ref**2 |
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 it biased and not symmetric. One would expect a result like
AB
AA
to have the same cost as others such as
BA
AA
but this is not the case in this implementation.
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.
What would you suggest? Compute the mean of all 4 and use that as ref?
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 could work, yes.
src/ess/sans/beam_center.py
Outdated
| q_bins: | ||
| The binning in the Q dimension to be used. | ||
| masking_radius: | ||
| The radius of the circular mask to apply to the data while iterating. |
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.
Which circular mask? To mask the beam stop?
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 (dim in da.coords) and (da.coords[dim].ndim > 1): | ||
| raise sc.DimensionError( | ||
| 'Cannot mask range on data with multi-dimensional coordinate. ' | ||
| f'Found dimensions {da.coords[dim].dims} for coordinate {dim}.') |
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 checking if there is an existing mask of the same name? Or else we risk dropping it silently.
| "# Add X, Y coordinates\n", | ||
| "sample.coords['x'] = sample.coords['position'].fields.x\n", | ||
| "sample.coords['y'] = sample.coords['position'].fields.y\n", |
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.
Why is this necessary?
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.
Convenience. We hist in x and y to make images of the detector panel, instead of using the instrument view which increases size bloat of the docs pages.
| "id": "afbf9a8c-4fc7-4eb3-a477-111baa047b18", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "## Making 4 quadrants\n", |
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 all of this repeating the code from the Python module?
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, it was. I was never happy with the duplication.
I now tried to do what I did in the other notebooks, which is to break up the BCF code into smaller functions, and use those inside the notebook.
src/ess/sans/beam_center.py
Outdated
| """ | ||
| Find the beam center of a SANS scattering pattern. | ||
| Description of the procedure: | ||
| #. obtain an initial guess by computing the center-of-mass of the pixels, | ||
| weighted by the counts on each pixel | ||
| #. from that initial guess, divide the panel into 4 quadrants | ||
| #. compute :math:`I(Q)` inside each quadrant and compute the residual difference | ||
| between all 4 quadrants | ||
| #. iteratively move the centre position and repeat 2. and 3. until all 4 | ||
| :math:`I(Q)` curves lie on top of each other | ||
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 had talked about documenting all of the things that may be done wrong, i.e., all the things you learning during implementation. Shouldn't this be documented here somewhere?
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 discussed, I added some notes in the docstring of the beam_center function.
src/ess/sans/beam_center_finder.py
Outdated
| iofq = iofq_in_quadrants(xy, *args) | ||
| all_q = sc.concat(list(iofq.values()), dim='quadrant') | ||
| ref = sc.values(all_q.mean('quadrant')) | ||
| c = sc.abs(all_q - ref) / ref |
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.
Didn't you have squares in the old implementation?
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 did, I am not sure it matters?
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.
Does it not affect convergence speed, maybe?
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 doesn't seem to, the number of iterations is the same as before.
| Notes | ||
| ----- |
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 place under the parameter section, since users will often want to read the params, but not the notes?
… number of Q bins. remove circular mask in beam center finder
| all_q = sc.concat([sc.values(da) for da in iofq.values()], dim='quadrant') | ||
| ref = all_q.mean('quadrant') | ||
| c = (all_q - ref)**2 | ||
| out = (sc.sum(ref * c) / sc.sum(ref)).value |
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.
You mention above that you now use a weighted mean, but I cannot really see this here. I expected that the variances would be used as weights?
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 thought you meant that the signal intensity should be used as weights, to give less importance to the noisy parts with low signal?
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'm slightly reluctant to use variances as weights, as we are not computing them accurately?
This PR adds the
beam_centerfunction to find the beam center in a SANS experiment.The prodecure to determine the precise location of the beam center is the following:
This is described in detail in a new notebook, and is used in the existing SANS notebooks.
I do not know how to add good unit tests for the beam center finder... Suggestions welcome.