-
Notifications
You must be signed in to change notification settings - Fork 3
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
Cylinder absorp corr #532
Merged
Merged
Cylinder absorp corr #532
Changes from 41 commits
Commits
Show all changes
84 commits
Select commit
Hold shift + click to select a range
df1bab0
feat: absorption correction
jokasimr 3a35667
fix: translate quadrature
jokasimr 7455339
fix: deal with case when quadrature and physical cylinder are already…
jokasimr f53d083
fix: use standard spherical coordinates
jokasimr 39135ea
tests: add cylinder intersection and quadrature tests
jokasimr 520eedb
quadrature tests
jokasimr 19107c1
more quadrature tests
jokasimr cc7b9ed
remove unused
jokasimr 13bea5a
better names for quadratures
jokasimr 1a85b8e
docs: add usage example
jokasimr 7dceab7
fix: better handling of input dims
jokasimr 00de9f8
fix: don't adapt quadrature with R/H ratio, it doesn't seem to make a…
jokasimr 0b21888
feat: allow computing transmission map at any point (not only infinit…
jokasimr 4b2587e
fix: better name
jokasimr 5844be2
docs: add to index
jokasimr 131859a
test: more cylinder intersection tests
jokasimr a2ae114
add material example
jokasimr a23032c
fix: adapt quadrature to cylinder shape
jokasimr 21c5329
absorption coefficient -> attenuation coefficient
jokasimr 00e6738
fix docs
jokasimr a1d7079
fix: handle units
jokasimr 2e3a599
docs: better docstrings
jokasimr da87e1d
fix: handle units
jokasimr 86f25ed
typo
jokasimr db12bdd
feat: reduce memory usage to avoid oom
jokasimr 3abdb60
make interface explicit
jokasimr 8ae2693
test: mc method
jokasimr ce57d0a
test: transmission correction
jokasimr 44bf714
feat: choose mc samples
jokasimr 7b74c61
refactor test and some docs
jokasimr f9ea772
type annotations
jokasimr 792dcb3
fix: reduce size before broadcasting
jokasimr 7bf4e98
Update src/scippneutron/absorption/cylinder.py
jokasimr e8480cc
docs: add references to source of quadratures
jokasimr 7a08bda
Merge branch 'cylinder-absorp-corr' of github.com:scipp/scippneutron …
jokasimr 36c3c74
docs: some more context on the quadratures
jokasimr 8b588c4
fixup
jokasimr c5d6c8d
fix: set default values to avoid having to specify lots of None value…
jokasimr 06bbabb
fix: use the atoms module to get scattering params
jokasimr 880ff8f
fix: update the example
jokasimr 2cfb05b
Update src/scippneutron/absorption/quadratures.py
jokasimr ab2dacd
feat: absorption correction
jokasimr fe24d09
fix: translate quadrature
jokasimr 85ea46b
fix: deal with case when quadrature and physical cylinder are already…
jokasimr 4469f9f
fix: use standard spherical coordinates
jokasimr 43b8095
tests: add cylinder intersection and quadrature tests
jokasimr 78df37d
quadrature tests
jokasimr 6463766
more quadrature tests
jokasimr 249bbe2
remove unused
jokasimr 9833957
better names for quadratures
jokasimr 2e58017
docs: add usage example
jokasimr 3d0dbcb
fix: better handling of input dims
jokasimr af5f2b1
fix: don't adapt quadrature with R/H ratio, it doesn't seem to make a…
jokasimr 07dfd37
feat: allow computing transmission map at any point (not only infinit…
jokasimr eb56e6c
fix: better name
jokasimr 181447c
docs: add to index
jokasimr 1612bcb
test: more cylinder intersection tests
jokasimr 646f885
add material example
jokasimr 0ee8132
fix: adapt quadrature to cylinder shape
jokasimr 20b3e45
absorption coefficient -> attenuation coefficient
jokasimr 4feec1b
fix docs
jokasimr 64fedc9
fix: handle units
jokasimr 1cfc598
docs: better docstrings
jokasimr 3b00f4f
fix: handle units
jokasimr 8fb23bd
typo
jokasimr f5de322
feat: reduce memory usage to avoid oom
jokasimr 7ecb742
make interface explicit
jokasimr 6dd2f36
test: mc method
jokasimr 56eb7fe
test: transmission correction
jokasimr 339257c
feat: choose mc samples
jokasimr 727c2b8
refactor test and some docs
jokasimr 1be8b86
type annotations
jokasimr 256c15d
fix: reduce size before broadcasting
jokasimr 030e439
docs: add references to source of quadratures
jokasimr fc8417b
Update src/scippneutron/absorption/cylinder.py
jokasimr bb8ebc9
docs: some more context on the quadratures
jokasimr fb4a70b
fixup
jokasimr ef32b4b
fix: set default values to avoid having to specify lots of None value…
jokasimr c08166f
fix: use the atoms module to get scattering params
jokasimr 220aebf
fix: update the example
jokasimr ac63431
Update src/scippneutron/absorption/quadratures.py
jokasimr 990bcfe
docs: improve example
jokasimr 51f2f41
Merge branch 'cylinder-absorp-corr' of github.com:scipp/scippneutron …
jokasimr dd2451c
Merge remote-tracking branch 'origin/main' into cylinder-absorp-corr
jokasimr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a6de8431-db39-49a5-b5dc-1afee82bc7d2", | ||
"metadata": {}, | ||
"source": [ | ||
"# Absorption correction for cylindrical samples\n", | ||
"\n", | ||
"The intensity per unit solid angle at a detector element located at $\\mathbf{x}$ is modeled as\n", | ||
"$$\n", | ||
"I(\\lambda, \\mathbf{x}) = \\int_{sample} S(\\lambda, \\theta(\\hat{\\mathbf{b}}, \\mathbf{x}-\\mathbf{x}')) T(\\lambda, \\mathbf{x}, \\mathbf{x}') \\ d\\mathbf{x}'\n", | ||
"$$\n", | ||
"where $S(\\lambda, \\theta)$ is the probability that a neutron with wavelength $\\lambda$ scatters with the scattering angle $\\theta$, $T(\\lambda, \\mathbf{x}, \\mathbf{x}')$ is the transmission rate for neutrons scattering at $\\mathbf{x}'$ towards $\\mathbf{x}$, and $\\hat{\\mathbf{b}}$ is the direction of the incoming beam.\n", | ||
"\n", | ||
"If the detector is far away from the sample relative to the size of the sample then $\\theta$ is close to constant as a function of $\\mathbf{x}'$ and the $S$ term can be moved outside of the integtral:\n", | ||
"$$\n", | ||
"I(\\lambda, \\mathbf{x}) \\approx S(\\lambda, \\theta(\\hat{\\mathbf{b}}, \\mathbf{x} -\\bar{\\mathbf{x}'})) \\int_{sample} T(\\lambda, \\mathbf{x}, \\mathbf{x}') \\ d\\mathbf{x}'.\n", | ||
"$$\n", | ||
"To compute the scattering probabiltiy $S$ from the intensity $I$ we need to estimate the term\n", | ||
"$$\n", | ||
"C(\\lambda, \\mathbf{x}) = \\int_{sample} T(\\lambda, \\mathbf{x}, \\mathbf{x}') \\ d\\mathbf{x}'.\n", | ||
"$$\n", | ||
"This is the \"absorption correction\" term.\n", | ||
"\n", | ||
"The transmission fraction is a function of path length $L$ of the neutron going through the sample\n", | ||
"$$\n", | ||
"T(\\lambda, \\mathbf{x}, \\mathbf{x}') = \\exp{(-\\mu(\\lambda) L(\\hat{\\mathbf{b}}, \\mathbf{x}, \\mathbf{x}'))}\n", | ||
"$$\n", | ||
"where $\\mu$ is material dependent.\n", | ||
"\n", | ||
"The path length through the sample depends on the shape of the sample, the scattering point $\\mathbf{x}'$ and the detection position $\\mathbf{x}$.\n", | ||
"\n", | ||
"To compute $C(\\lambda, \\mathbf{x})$ you can use\n", | ||
"```python\n", | ||
"scippneutron.absorption.base.compute_transmission_map(\n", | ||
" shape,\n", | ||
" material,\n", | ||
" beam_direction,\n", | ||
" wavelength,\n", | ||
" detector_position\n", | ||
")\n", | ||
"```\n", | ||
"where `shape` and `material` are sample properties, `shape` defines `L` and `material` defines $\\mu$, `beam_direction` is $\\mathbf{b}$, `wavelength` is $\\lambda$ and `detector_position` is $\\mathbf{x}$. " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "07bd8cdb-c18a-48e4-a60a-041a0d4cd07c", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import scipp as sc\n", | ||
"import numpy as np\n", | ||
"\n", | ||
"from scippneutron.absorption import compute_transmission_map, Cylinder, Material\n", | ||
"from scippneutron.atoms import ScatteringParams\n", | ||
"\n", | ||
"\n", | ||
"sample_material = Material(scattering_params=ScatteringParams.for_isotope('V'), effective_sample_number_density=sc.scalar(1, unit='1/angstrom**3'))\n", | ||
"\n", | ||
"sample_shape = Cylinder(\n", | ||
" symmetry_line=sc.vector([0, 1, 0]),\n", | ||
" center_of_base=sc.vector([0, -.5, 0], unit='cm'),\n", | ||
" radius=sc.scalar(1., unit='cm'),\n", | ||
" height=sc.scalar(0.3, unit='cm')\n", | ||
")\n", | ||
"\n", | ||
"# Create some dummy detector positions.\n", | ||
"theta = sc.linspace('theta', 0, np.pi, 60, endpoint=True, unit='rad')\n", | ||
"phi = sc.linspace('phi', 0, 2 * np.pi, 120, endpoint=False, unit='rad')\n", | ||
"r = sc.array(dims='r', values=[5.], unit='m')\n", | ||
"dims = (*r.dims, *theta.dims, *phi.dims)\n", | ||
"\n", | ||
"# Detector positions in grid on a sphere around the origin\n", | ||
"detector_positions = sc.vectors(\n", | ||
" dims=dims,\n", | ||
" values=sc.concat(\n", | ||
" [\n", | ||
" r * sc.sin(theta) * sc.cos(phi),\n", | ||
" r * sc.sin(theta) * sc.sin(phi),\n", | ||
" sc.broadcast(r * sc.cos(theta), sizes={**r.sizes, **theta.sizes, **phi.sizes}),\n", | ||
" ],\n", | ||
" dim='row',\n", | ||
" )\n", | ||
" .transpose([*dims, 'row'])\n", | ||
" .values,\n", | ||
" unit=r.unit,\n", | ||
")\n", | ||
"\n", | ||
"def transmission(quadrature_kind):\n", | ||
" 'Evaluates the transmission map for different kinds of quadrature'\n", | ||
" da = compute_transmission_map(\n", | ||
" sample_shape,\n", | ||
" sample_material,\n", | ||
" beam_direction=sc.vector([0, 0, 1]),\n", | ||
" wavelength=sc.linspace('wavelength', 0.5, 2.5, 10, unit='angstrom'),\n", | ||
" detector_position=detector_positions,\n", | ||
" quadrature_kind=quadrature_kind,\n", | ||
" )\n", | ||
" da.coords['phi'] = phi\n", | ||
" da.coords['theta'] = theta\n", | ||
" return da\n", | ||
"\n", | ||
"\n", | ||
"def show_correction_map(da):\n", | ||
" 'Plotting utility'\n", | ||
" return (\n", | ||
" da['wavelength', 0]['r', 0].plot() /\n", | ||
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2].plot() /\n", | ||
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2]['phi', da.sizes['phi']//4 - da.sizes['phi']//6:da.sizes['phi']//4 + da.sizes['phi']//6].plot() /\n", | ||
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2]['phi', da.sizes['phi']//2 - da.sizes['phi']//6:da.sizes['phi']//2 + da.sizes['phi']//6].plot() /\n", | ||
" da['wavelength', 0]['r', 0]['phi', da.sizes['phi']//2].plot()\n", | ||
" )" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "6f03111b-8d1c-4c82-a9e9-b2fcde5c1003", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"transmission('cheap')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "abb0d783-9a57-4daa-b4dc-7295044beb78", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"show_correction_map(transmission('cheap'))" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "1512c244-118e-40a0-ac88-1510504603da", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"show_correction_map(transmission('medium'))" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "d34de43c-0341-4656-b739-656cb3aff4a1", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"show_correction_map(transmission('expensive'))" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "93a0af6e-dd16-4ee4-838d-df2ef014ff4a", | ||
"metadata": {}, | ||
"source": [ | ||
"## Error estimate" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "dbbdbf77-07b0-491e-b898-6daccc03ccd8", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import plopp as pp\n", | ||
"cheap = transmission('cheap')['wavelength', 0]['r', 0]\n", | ||
"medium = transmission('medium')['wavelength', 0]['r', 0]\n", | ||
"expensive = transmission('expensive')['wavelength', 0]['r', 0]\n", | ||
"\n", | ||
"t = cheap.sizes['theta'] // 2\n", | ||
"pmin = cheap.sizes['phi'] // 2 - cheap.sizes['phi'] // 8\n", | ||
"pmax = cheap.sizes['phi'] // 2 + cheap.sizes['phi'] // 8\n", | ||
"pp.plot(\n", | ||
" {\n", | ||
" 'cheap': cheap['theta', t]['phi', pmin:pmax],\n", | ||
" 'medium': medium['theta', t]['phi', pmin:pmax],\n", | ||
" 'expensive': expensive['theta', t]['phi', pmin:pmax],\n", | ||
" }\n", | ||
")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "180ab825-0df5-4422-91cf-474523c63fe6", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Relative difference between cheap and expensive quadrature\n", | ||
"(sc.abs(cheap - expensive)/sc.abs(expensive)).mean().value" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "5a3fb34e-449b-4a4c-8ea6-849b0c5166cc", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Relative difference between medium and expensive quadrature\n", | ||
"(sc.abs(medium - expensive)/sc.abs(expensive)).mean().value" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3 (ipykernel)", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.10.13" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,5 @@ instrument-view | |
recipes | ||
masking-tool | ||
algorithms-background/index | ||
absorption-correction | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from .base import compute_transmission_map | ||
from .cylinder import Cylinder | ||
from .material import Material | ||
|
||
|
||
__all__ = ('compute_transmission_map', 'Cylinder', 'Material') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
from functools import partial | ||
from typing import Any | ||
|
||
import scipp as sc | ||
|
||
from .material import Material | ||
from .types import SampleShape | ||
|
||
|
||
def compute_transmission_map( | ||
sample_shape: SampleShape, | ||
sample_material: Material, | ||
beam_direction: sc.Variable, | ||
wavelength: sc.Variable, | ||
detector_position: sc.Variable, | ||
quadrature_kind: Any = 'medium', | ||
) -> sc.DataArray: | ||
points, weights = sample_shape.quadrature(quadrature_kind) | ||
transmission = _integrate_transmission_fraction( | ||
partial( | ||
_single_scatter_distance_through_sample, | ||
sample_shape, | ||
points, | ||
beam_direction, | ||
), | ||
partial(_transmission_fraction, sample_material), | ||
points, | ||
weights, | ||
detector_position, | ||
wavelength, | ||
) | ||
return sc.DataArray( | ||
data=transmission / sample_shape.volume, | ||
coords={'detector_position': detector_position, 'wavelength': wavelength}, | ||
) | ||
|
||
|
||
def _single_scatter_distance_through_sample( | ||
sample_shape, scatter_point, initial_direction, scatter_direction | ||
): | ||
L1 = sample_shape.beam_intersection(scatter_point, -initial_direction) | ||
L2 = sample_shape.beam_intersection(scatter_point, scatter_direction) | ||
return L1 + L2 | ||
|
||
|
||
def _transmission_fraction(material, distance_through_sample, wavelength): | ||
return sc.exp( | ||
-(material.attenuation_coefficient(wavelength) * distance_through_sample).to( | ||
unit='dimensionless', copy=False | ||
) | ||
) | ||
|
||
|
||
def _integrate_transmission_fraction( | ||
distance_through_sample, | ||
transmission, | ||
points, | ||
weights, | ||
detector_position, | ||
wavelengths, | ||
): | ||
# If size after broadcast is too large | ||
# then don't vectorize the operation to avoid OOM | ||
if points.size * detector_position.size > 20_000_000: | ||
out = [] | ||
dim = detector_position.dims[0] | ||
for i in range(detector_position.sizes[dim]): | ||
out.append( # noqa: PERF401 | ||
_integrate_transmission_fraction( | ||
distance_through_sample, | ||
transmission, | ||
points, | ||
weights, | ||
detector_position[dim, i], | ||
wavelengths, | ||
) | ||
) | ||
|
||
return sc.concat( | ||
out, | ||
dim=dim, | ||
) | ||
|
||
scatter_direction = detector_position - points.to(unit=detector_position.unit) | ||
scatter_direction /= sc.norm(scatter_direction) | ||
Ltot = distance_through_sample(scatter_direction) | ||
|
||
# The Ltot array is already large, to avoid OOM, don't vectorize this operation | ||
return sc.concat( | ||
[ | ||
# Instead of broadcast multiply and sum, use matvec for efficiency | ||
# this becomes relevant when the number of wavelength points grows | ||
sc.array( | ||
dims=(tf := transmission(Ltot, w)).dims[:-1], | ||
values=tf.values @ weights.values, | ||
unit=tf.unit * weights.unit, | ||
) | ||
for w in wavelengths | ||
], | ||
dim=wavelengths.dim, | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 add y-axis labels.