Skip to content

Commit 3cb2c7c

Browse files
authored
Added MorphSqueeze and MorphFuncy tutorials (#226)
* tutorials: adding tutorials for MorphSqueeze and MorphFuncy, also corrected docstring tab * tutorials: adding tutorials for MorphSqueeze and MorphFuncy, updated docstring tab * tutorials: fogot news * cleanup: remove tutorial additionalData folder and update news entry
1 parent 79e9c32 commit 3cb2c7c

File tree

6 files changed

+138
-48
lines changed

6 files changed

+138
-48
lines changed

doc/source/morphpy.rst

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ through Python scripting.
189189

190190
.. code-block:: python
191191
192-
from diffpy.morph.morph_api import morph, morph_default_config
192+
from diffpy.morph.morphpy import morph_arrays
193193
import numpy as np
194194
195195
2. Define a custom Python function to apply a transformation to the data.
@@ -215,39 +215,28 @@ through Python scripting.
215215
x_target = x_morph.copy()
216216
y_target = np.sin(x_target) * 20 * x_target + 0.8
217217
218-
4. Set up the morph configuration dictionary. This includes both the
219-
transformation parameters (our initial guess) and the transformation
220-
function itself:
218+
4. Setup and run the morph using the ``morph_arrays(...)``.
219+
``morph_arrays`` expects the morph and target data as **2D arrays** in
220+
*two-column* format ``[[x0, y0], [x1, y1], ...]``. This will apply
221+
the user-defined function and refine the parameters to best align the
222+
morph data with the target data. This includes both the transformation
223+
parameters (our initial guess) and the transformation function itself:
221224

222225
.. code-block:: python
223226
224-
morph_config = morph_default_config(funcy={"scale": 1.2, "offset": 0.1})
225-
morph_config["function"] = linear_function
227+
morph_params, morph_table = morph_arrays(np.array([x_morph, y_morph]).T,np.array([x_target, y_target]).T,
228+
funcy=(linear_function,{'scale': 1.2, 'offset': 0.1}))
226229
227-
# morph_config now contains:
228-
# {'funcy': {'scale': 1.2, 'offset': 0.1}, 'function': linear_function}
229-
230-
5. Run the morph using the ``morph(...)``. This will apply the user-defined
231-
function and refine the parameters to best align the morph data
232-
with the target data:
233-
234-
.. code-block:: python
235-
236-
morph_result = morph(x_morph, y_morph, x_target, y_target, **morph_config)
237-
238-
6. Extract the morphed output and the fitted parameters from the result:
230+
5. Extract the fitted parameters from the result:
239231

240232
.. code-block:: python
241233
242-
fitted_config = morph_result["morphed_config"]
243-
x_morph_out, y_morph_out, x_target_out, y_target_out = morph_result["morph_chain"].xyallout
244-
245-
fitted_params = fitted_config["funcy"]
234+
fitted_params = morph_params["funcy"]
246235
print(f"Fitted scale: {fitted_params['scale']}")
247236
print(f"Fitted offset: {fitted_params['offset']}")
248237
249238
As you can see, the fitted scale and offset values match the ones used
250239
to generate the target (scale=20 & offset=0.8). This example shows how
251240
``MorphFuncy`` can be used to fit and apply custom transformations. Now
252241
it's your turn to experiment with other custom functions that may be useful
253-
for analyzing your data.
242+
for analyzing your data.

doc/source/tutorials.rst

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,84 @@ selected directory and plot resulting :math:`R_w` values from each morph.
106106
PDFs. See the ``--save-names-file`` option to see how you can set
107107
the names for these saved morphs!
108108

109+
Polynomial Squeeze Morph
110+
=========================
111+
112+
Another advanced feature in ``diffpy.morph`` is the ``MorphSqueeze`` morph,
113+
which applies a user-defined polynomial to squeeze the morph function along the
114+
x-axis. This provides a flexible way to correct for higher-order distortions
115+
that simple shift or stretch morphs cannot fully address.
116+
Such distortions can arise from geometric artifacts in X-ray detector modules,
117+
including tilts, curved detection planes, or angle-dependent offsets, as well
118+
as from intrinsic structural effects in the sample.
119+
120+
A first-order squeeze polynomial recovers the behavior of simple shift or stretch,
121+
while higher-order terms enable non-linear corrections. The squeeze transformation
122+
is defined as:
123+
124+
.. math::
125+
126+
\Delta r(r) = a_0 + a_1 r + a_2 r^2 + \dots + a_n r^n
127+
128+
where :math:`a_0, a_1, ..., a_n` are the polynomial coefficients defined by the user.
129+
130+
In this example, we show how to apply a squeeze morph in combination
131+
with a scale morph to match a morph function to its target. The required
132+
files can be found in ``additionalData/morphsqueeze/``.
133+
134+
1. ``cd`` into the ``morphsqueeze`` directory::
135+
136+
cd additionalData/morphsqueeze
137+
138+
Here you will find:
139+
140+
- ``squeeze_morph.cgr`` — the morph function with a small built-in polynomial distortion.
141+
- ``squeeze_target.cgr`` — the target function.
142+
143+
2. Suppose we know that the morph needs a quadratic and cubic squeeze,
144+
plus a scale factor to best match the target. As an initial guess,
145+
we can use:
146+
147+
- ``squeeze = 0,-0.001,-0.0001,0.0001``
148+
(for a polynomial: :math:`a_0 + a_1 x + a_2 x^2 + a_3 x^3`)
149+
- ``scale = 1.1``
150+
151+
The squeeze polynomial is provided as a comma-separated list (no spaces)::
152+
153+
diffpy.morph --scale=1.1 --squeeze=0,-0.001,-0.0001,0.0001 -a squeeze_morph.cgr squeeze_target.cgr
154+
155+
3. ``diffpy.morph`` will apply the polynomial squeeze and scale,
156+
display the initial and refined coefficients, and show the final
157+
difference ``Rw``.
158+
159+
To refine the squeeze polynomial and scale automatically, remove
160+
the ``-a`` tag if you used it. For example::
161+
162+
diffpy.morph --scale=1.1 --squeeze=0,-0.001,-0.0001,0.0001 squeeze_morph.cgr squeeze_target.cgr
163+
164+
4. Check the output for the final squeeze polynomial coefficients and scale.
165+
They should match the true values used to generate the test data:
166+
167+
- ``squeeze = 0, 0.01, 0.0001, 0.001``
168+
- ``scale = 0.5``
169+
170+
``diffpy.morph`` refines the coefficients to minimize the residual
171+
between the squeezed, scaled morph function and the target.
172+
173+
.. warning::
174+
175+
**Extrapolation risk:**
176+
A polynomial squeeze can shift morph data outside the target’s ``r``-range,
177+
so parts of the output may be extrapolated.
178+
This is generally fine if the polynomial coefficients are small and
179+
the distortion is therefore small. If your coefficients are large, check the
180+
plots carefully — strong extrapolation can produce unrealistic features at
181+
the edges. If needed, adjust the coefficients to keep the morph physically
182+
meaningful.
183+
184+
Experiment with your own squeeze polynomials to fine-tune your morphs — even
185+
small higher-order corrections can make a big difference!
186+
109187
Nanoparticle Shape Effects
110188
==========================
111189

news/tutorial_squeeze_funcy.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* Added tutorial for MorphSqueeze and MorphFuncy
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

src/diffpy/morph/morphs/morphfuncy.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,33 @@ class MorphFuncy(Morph):
3030
-------
3131
Import the funcy morph function:
3232
33-
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy
33+
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy
3434
3535
Define or import the user-supplied transformation function:
3636
37-
>>> def sine_function(x, y, amplitude, frequency):
38-
>>> return amplitude * np.sin(frequency * x) * y
37+
>>> def sine_function(x, y, amplitude, frequency):
38+
>>> return amplitude * np.sin(frequency * x) * y
3939
4040
Provide initial guess for parameters:
4141
42-
>>> parameters = {'amplitude': 2, 'frequency': 2}
42+
>>> parameters = {'amplitude': 2, 'frequency': 2}
4343
4444
Run the funcy morph given input morph array (x_morph, y_morph)and target
4545
array (x_target, y_target):
4646
47-
>>> morph = MorphFuncy()
48-
>>> morph.function = sine_function
49-
>>> morph.funcy = parameters
50-
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
51-
... morph.morph(x_morph, y_morph, x_target, y_target)
47+
>>> morph = MorphFuncy()
48+
>>> morph.function = sine_function
49+
>>> morph.funcy = parameters
50+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
51+
... morph.morph(x_morph, y_morph, x_target, y_target)
5252
5353
To access parameters from the morph instance:
5454
55-
>>> x_morph_in = morph.x_morph_in
56-
>>> y_morph_in = morph.y_morph_in
57-
>>> x_target_in = morph.x_target_in
58-
>>> y_target_in = morph.y_target_in
59-
>>> parameters_out = morph.funcy
55+
>>> x_morph_in = morph.x_morph_in
56+
>>> y_morph_in = morph.y_morph_in
57+
>>> x_target_in = morph.x_target_in
58+
>>> y_target_in = morph.y_target_in
59+
>>> parameters_out = morph.funcy
6060
"""
6161

6262
# Define input output types

src/diffpy/morph/morphs/morphsqueeze.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,27 @@ class MorphSqueeze(Morph):
3232
-------
3333
Import the squeeze morph function:
3434
35-
>>> from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
35+
>>> from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
3636
3737
Provide initial guess for squeezing coefficients:
3838
39-
>>> squeeze_coeff = {"a0":0.1, "a1":-0.01, "a2":0.005}
39+
>>> squeeze_coeff = {"a0":0.1, "a1":-0.01, "a2":0.005}
4040
4141
Run the squeeze morph given input morph array (x_morph, y_morph) and target
4242
array (x_target, y_target):
4343
44-
>>> morph = MorphSqueeze()
45-
>>> morph.squeeze = squeeze_coeff
46-
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
47-
... morph(x_morph, y_morph, x_target, y_target)
44+
>>> morph = MorphSqueeze()
45+
>>> morph.squeeze = squeeze_coeff
46+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
47+
... morph(x_morph, y_morph, x_target, y_target)
4848
4949
To access parameters from the morph instance:
5050
51-
>>> x_morph_in = morph.x_morph_in
52-
>>> y_morph_in = morph.y_morph_in
53-
>>> x_target_in = morph.x_target_in
54-
>>> y_target_in = morph.y_target_in
55-
>>> squeeze_coeff_out = morph.squeeze
51+
>>> x_morph_in = morph.x_morph_in
52+
>>> y_morph_in = morph.y_morph_in
53+
>>> x_target_in = morph.x_target_in
54+
>>> y_target_in = morph.y_target_in
55+
>>> squeeze_coeff_out = morph.squeeze
5656
"""
5757

5858
# Define input output types

tutorial/additionalData.zip

3.97 KB
Binary file not shown.

0 commit comments

Comments
 (0)