11from pathlib import Path
2- from typing import Optional
32
43import matplotlib .pyplot as plt
54import numpy as np
6- from rich .progress import Progress , SpinnerColumn , TimeElapsedColumn , MofNCompleteColumn
75import typer
86
97from xtl .cli .cliio import Console , epilog
108from xtl .cli .utils import Timer
11- from xtl .cli .diffraction .cli_utils import (IntegrationRadialUnits , get_geometry_from_header ,
12- get_radial_units_from_header , units_repr )
13- from xtl .diffraction .images .correlators import AzimuthalCrossCorrelatorQQ_1
9+ from xtl .cli .diffraction .cli_utils import get_geometry_from_header , get_radial_units_from_header
1410from xtl .exceptions .utils import Catcher
1511from xtl .files .npx import NpxFile
16-
17- import warnings
12+ from xtl .units .crystallography .radial import RadialUnitType , RadialValue
1813
1914
2015app = typer .Typer ()
@@ -25,13 +20,18 @@ def cli_diffraction_correlate_fft(
2520 ccf_file : Path = typer .Argument (..., metavar = 'ccf.npx' , help = 'CCF file to analyze' ),
2621 ai2_file : Path = typer .Argument (None , metavar = 'ai2.npx' , help = 'Azimuthal integration file' ),
2722 # Selection parameters
28- selection_2theta : float = typer .Option (None , '-tt ' , '--2theta' , help = '2\u03b8 angle to inspect (in \u00b0 )' ,
23+ selection_2theta : float = typer .Option (None , '-t ' , '--2theta' , help = '2\u03b8 angle to inspect (in \u00b0 )' ,
2924 rich_help_panel = 'Selection parameters' ),
3025 selection_q : float = typer .Option (None , '-q' , '--q' , help = 'Q value to inspect (in nm\u207B \u00B9 )' ,
3126 rich_help_panel = 'Selection parameters' ),
3227 # FFT parameters
3328 no_coeffs : int = typer .Option (24 , '-n' , '--no-coeffs' , help = 'Number of Fourier components to display' ,
3429 rich_help_panel = 'FFT parameters' ),
30+ fill_value : float = typer .Option (None , '--fill' , help = 'Fill NaN values in CCF with a given value' ,
31+ rich_help_panel = 'FFT parameters' ),
32+ # Plotting parameters
33+ # polar_plots: bool = typer.Option(False, '--polar', help='Plot CCF and azimuthal integration in polar coordinates',
34+ # rich_help_panel='Plotting parameters'),
3535 # Debugging
3636 verbose : int = typer .Option (0 , '-v' , '--verbose' , count = True , help = 'Print additional information' ,
3737 rich_help_panel = 'Debugging' ),
@@ -47,8 +47,11 @@ def cli_diffraction_correlate_fft(
4747 elif selection_2theta is not None and selection_q is not None :
4848 cli .print ('Select only one parameter to calculate FFT (--2theta, --q)' , style = 'red' )
4949 raise typer .Abort ()
50- selection_units = IntegrationRadialUnits .Q_NM if selection_q is not None else IntegrationRadialUnits .TWOTHETA_DEG
51- selection = selection_q if selection_q is not None else selection_2theta
50+
51+ if selection_2theta is not None :
52+ selection = RadialValue (value = selection_2theta , type = RadialUnitType .TWOTHETA_DEG )
53+ else :
54+ selection = RadialValue (value = selection_q , type = RadialUnitType .Q_NM )
5255
5356 # Load CCF data
5457 with Catcher (echo_func = cli .print , traceback_func = cli .print_traceback ) as catcher :
@@ -86,89 +89,104 @@ def cli_diffraction_correlate_fft(
8689 raise typer .Abort ()
8790
8891 # Get the radial units of the CCF
89- radial_units = get_radial_units_from_header (acc .header )
90- if radial_units is None :
92+ r = get_radial_units_from_header (acc .header )
93+ if r is None :
9194 cli .print ('Error: Failed to get radial units from CCF file header' , style = 'red' )
9295 raise typer .Abort ()
93- u = units_repr (radial_units )
94- cli .print (f'Radial units in CCF file: { u [0 ]} ({ u [1 ]} )' )
96+ cli .print (f'Radial units in CCF file: { r .name .latex } ({ r .unit .latex } )' )
9597
9698 # Convert selection units to the units of CCF if necessary
97- if radial_units != selection_units :
98- previous_selection = selection
99- if radial_units == IntegrationRadialUnits .Q_NM : # Convert 2theta to q
100- selection = 4 * np .pi / (geometry .wavelength / 1e-10 ) * np .sin (np .radians (selection / 2 ))
101- selection *= 10 # Convert 1/A to nm^-1
102- elif radial_units == IntegrationRadialUnits .TWOTHETA_DEG : # Convert q to 2theta
103- selection /= 10 # Convert nm^-1 to 1/A
104- selection = 2 * np .degrees (np .arcsin (selection * (geometry .wavelength / 1e-10 ) / (4 * np .pi )))
105- else :
106- cli .print (f'Error: Invalid radial units { radial_units !r} ' , style = 'red' )
107- raise typer .Abort ()
108- u0 = units_repr (selection_units )
109- u1 = units_repr (radial_units )
110- cli .print (f'Converted selection from { u0 [0 ]} ={ previous_selection :.4f} ({ u0 [1 ]} ) '
111- f'to { u1 [0 ]} ={ selection :.4f} ({ u1 [1 ]} ) using \u03bb ={ geometry .wavelength / 1e-10 :.4f} (\u212b )' )
99+ wavelength = geometry .wavelength / 1e-10
100+ if r != selection .units :
101+ with Catcher (echo_func = cli .print , traceback_func = cli .print_traceback ) as catcher :
102+ n0 , v0 , u0 = selection .name .latex , selection .value , selection .units .latex
103+ selection = selection .to (units = r , wavelength = wavelength )
104+ n1 , v1 , u1 = selection .name .latex , selection .value , selection .units .latex
105+
106+ u0 = u0 if u0 == '\u00b0 ' else f' { u0 } '
107+ u1 = u1 if u1 == '\u00b0 ' else f' { u1 } '
108+ cli .print (f'Converted selection from { n0 } ={ v0 :.4f} { u0 } '
109+ f'to { n1 } ={ v1 :.4f} { u1 } using \u03bb ={ wavelength :.4f} \u212b ' )
112110
113111 # Check if selection is within the radial range of the CCF
114112 radial_min , radial_max = acc .data ['radial' ].min (), acc .data ['radial' ].max ()
115- if selection < radial_min or selection > radial_max :
113+ if selection . value < radial_min or selection . value > radial_max :
116114 cli .print (f'Error: Selection is outside the radial range of the CCF: { [radial_min , radial_max ]} ' , style = 'red' )
117115 raise typer .Abort ()
118116
119117 # Get the radial index of the selection
120- ccf_i = np .argmin (np .abs (acc .data ['radial' ] - selection ))
118+ ccf_i = np .argmin (np .abs (acc .data ['radial' ] - selection . value ))
121119
122120 # Calculate the FFT of the CCF
123121 if verbose :
124- cli .print (f'Calculating FFT(CCF) ... ' )
125- with Timer (silent = verbose <= 1 , echo_func = cli .print ):
122+ cli .print (f'Calculating FFT... ' )
123+ with Timer (silent = verbose < 1 , echo_func = cli .print ):
126124 ccf = acc .data ['ccf' ]
127125 ccf_delta = ccf [:, ccf_i ]
126+ if np .any (np .isnan (ccf_delta )):
127+ if fill_value is not None :
128+ cli .print (f'Replacing missing values in CCF with { fill_value } ' , style = 'yellow' )
129+ ccf_delta = np .nan_to_num (ccf_delta , nan = fill_value )
130+ else :
131+ cli .print ('Warning: NaN values found in CCF data. FFT will be empty. '
132+ 'Use --fill to replace the NaNs.' , style = 'yellow' )
128133 fc = np .fft .fft (ccf_delta ) / len (ccf_delta )
129134 fc = np .abs (fc )
130135
136+ # Calculate selection in 2theta, q and d
137+ tth = selection .to (RadialUnitType .TWOTHETA_DEG , wavelength = wavelength )
138+ q = selection .to (RadialUnitType .Q_NM , wavelength = wavelength )
139+ d = selection .to (RadialUnitType .D_A , wavelength = wavelength )
140+ subtitle = (f'{ tth .name .latex } ={ tth .value :.4f} { tth .units .latex } | '
141+ f'{ q .name .latex } ={ q .value :.4f} { q .units .latex } | '
142+ f'{ d .name .latex } ={ d .value :.2f} { d .units .latex } ' )
143+
131144 # Prepare plots
132145 fig = plt .figure ('XCCA overview' , figsize = (16 / 1.2 , 9 / 1.2 ))
133- fig .suptitle (f'{ ccf_file .name } { u [ 0 ] } = { selection :.4f } { u [ 1 ] } ' )
146+ fig .suptitle (f'{ ccf_file .name } \n { subtitle } ' )
134147 gs = fig .add_gridspec (2 , 3 , wspace = 0.2 ,)
135148 ax0 = fig .add_subplot (gs [0 , 0 ]) # CCF 2D
136149 ax1 = fig .add_subplot (gs [1 , 0 ]) # Azimuthal integration 2D
137150 ax2 = fig .add_subplot (gs [0 , 1 ]) # CCF 1D
151+ # ax2 = fig.add_subplot(gs[0, 1], projection='polar') # CCF 1D
138152 ax3 = fig .add_subplot (gs [1 , 1 ]) # Azimuthal integration 1D
153+ # ax3 = fig.add_subplot(gs[1, 1], projection='polar') # Azimuthal integration 1D
139154 ax4 = fig .add_subplot (gs [:, 2 ]) # FFT
140155
141156 ccf , radial , delta = acc .data ['ccf' ], acc .data ['radial' ], acc .data ['delta' ]
142157 ax0 .imshow (ccf , origin = 'lower' , aspect = 'auto' , interpolation = 'nearest' , cmap = 'Spectral' ,
143158 #norm=norm(vmin=zmin, vmax=zmax),
144159 extent = (radial .min (), radial .max (), delta .min (), delta .max ()))
145- ax0 .vlines (selection , delta .min (), delta .max (), 'r' , '--' )
160+ ax0 .vlines (selection . value , delta .min (), delta .max (), 'r' , '--' )
146161 ax0 .set_title ('2D Cross-correlation function' )
147- ax0 .set_ylabel (f'Angular offset / \u0394 ({ u [ 1 ] } )' )
148- ax0 .set_xlabel (f'Radial angle / { u [ 0 ] } ({ u [ 1 ] } )' )
162+ ax0 .set_ylabel (f'\u0394 (\u00b0 )' )
163+ ax0 .set_xlabel (f'{ selection . name . latex } ({ selection . units . latex } )' )
149164
150- ax2 .plot (delta , ccf [:, ccf_i ])
165+ ax2 .plot (delta , ccf [:, ccf_i ], color = 'xkcd:light brown' )
166+ # ax2.plot(delta * np.pi / 180., ccf[:, ccf_i], color='xkcd:light brown')
151167 ax2 .set_title ('1D Cross-correlation function' )
152168 ax2 .set_ylabel ('Cross-correlation function' )
153- ax2 .set_xlabel (f'Angular offset / \u0394 ( { u [ 1 ] } )' )
169+ ax2 .set_xlabel (' \u03c7 ( \u00b0 )' )
154170
155171 if has_ai2 :
156172 intensities , radial , azimuthal = ai2 .data ['intensities' ], ai2 .data ['radial' ], ai2 .data ['azimuthal' ]
157173 ax1 .imshow (intensities , origin = 'lower' , aspect = 'auto' , interpolation = 'nearest' , cmap = 'magma' ,
158174 #norm=norm(vmin=zmin, vmax=zmax),
159175 extent = (radial .min (), radial .max (), azimuthal .min (), azimuthal .max ()))
160- ax1 .vlines (selection , azimuthal .min (), azimuthal .max (), 'r' , '--' )
176+ ax1 .vlines (selection . value , azimuthal .min (), azimuthal .max (), 'r' , '--' )
161177 ax1 .set_title ('2D Azimuthal integration' )
162- ax1 .set_ylabel (f'Azimuthal angle / \u03c7 { u [ 1 ] } ' )
163- ax1 .set_xlabel (f'Radial angle / { u [ 0 ] } ({ u [ 1 ] } )' )
178+ ax1 .set_ylabel (' \u03c7 ( \u00b0 ) ' )
179+ ax1 .set_xlabel (f'{ selection . name . latex } ({ selection . units . latex } )' )
164180
165- ax3 .plot (azimuthal , intensities [:, ccf_i ])
181+ ax3 .plot (azimuthal , intensities [:, ccf_i ], color = 'xkcd:crimson' )
182+ # ax3.plot(azimuthal * np.pi / 180., intensities[:, ccf_i], color='xkcd:crimson')
183+ # ax3.set_ylim(intensities[:, ccf_i].min(), intensities[:, ccf_i].max())
166184 ax3 .set_title ('1D Azimuthal integration' )
167185 ax3 .set_ylabel ('Intensity' )
168- ax3 .set_xlabel (f'Azimuthal angle / \u03c7 ({ u [ 1 ] } )' )
186+ ax3 .set_xlabel (' \u03c7 (\u00b0 )' )
169187
170188 ax4 .bar (range (1 , no_coeffs + 1 ), fc [1 :no_coeffs + 1 ])
171- ax4 .set_title ('FFT( CCF) ' )
189+ ax4 .set_title ('$ \u2131 \{\mathrm{ CCF}( \u0394 )\}$ ' )
172190 ax4 .set_ylabel ('Distribution' )
173191 ax4 .set_xlabel ('Fourier coefficient' )
174192
0 commit comments