@@ -52,7 +52,10 @@ def set_wavelength(self, wavelength: float | str | None):
5252
5353 @classmethod
5454 def load_pattern (
55- cls , location : str | Path , wavelength : float | None = None
55+ cls ,
56+ location : str | Path ,
57+ wavelength : float | None = None ,
58+ target_resolution : float | None = 0.01 ,
5659 ) -> tuple [pd .DataFrame , list [str ], dict ]:
5760 """Load the XRD pattern at the given file location, returning
5861 a DataFrame with the pattern data, a list of y-axis options for plotting
@@ -61,13 +64,18 @@ def load_pattern(
6164 Parameters:
6265 location: The file location of the XRD pattern.
6366 wavelength: The wavelength of the X-ray source. Defaults to CuKa.
67+ target_resolution: The target resolution for rebinning the data,
68+ set to `None` to disable rebinning, otherwise, the data will
69+ be rebinned to a uniform 2θ grid with this resolution if the
70+ average resolution is lower than this value.
6471
6572 """
6673
6774 if not isinstance (location , str ):
6875 location = str (location )
6976
7077 ext = os .path .splitext (location .split ("/" )[- 1 ])[- 1 ].lower ()
78+ LOGGER .debug ("Loading XRD pattern from %s as %s" , location , ext )
7179
7280 theoretical = False
7381 peak_data : dict = {}
@@ -137,7 +145,39 @@ def _try_read_csv(sep: str, skiprows: int) -> pd.DataFrame | None:
137145 if len (df ) == 0 :
138146 raise RuntimeError (f"No compatible data found in { location } " )
139147
140- df = df .rename (columns = {"twotheta" : "2θ (°)" })
148+ df = df .rename (columns = {"twotheta" : "2θ (°)" , "intensity" : "counts" })
149+
150+ # Always retain the raw measurement as "counts". "intensity" is the signal
151+ # used downstream: the rebinned data where the native sampling is finer than
152+ # the target resolution, otherwise the raw counts.
153+ df ["intensity" ] = df ["counts" ]
154+ if not theoretical and target_resolution is not None :
155+ if target_resolution <= 0 :
156+ raise ValueError ("Target resolution must be a positive number" )
157+
158+ average_two_theta_resolution = np .mean (np .diff (df ["2θ (°)" ]))
159+ if average_two_theta_resolution < target_resolution :
160+ warnings .warn (
161+ f"Native 2θ sampling ({ average_two_theta_resolution :.4f} °) is finer than the "
162+ f"target resolution; rebinning onto a uniform { target_resolution :.4f} ° grid."
163+ )
164+ two_theta = df ["2θ (°)" ].to_numpy ()
165+ # Bin edges span the data range at the target resolution
166+ edges = np .arange (
167+ two_theta .min (), two_theta .max () + target_resolution , target_resolution
168+ )
169+
170+ # Weighted histogram rebinning: each bin holds the mean counts
171+ # of the input points falling within it
172+ bin_counts , _ = np .histogram (two_theta , bins = edges )
173+ weighted , _ = np .histogram (two_theta , bins = edges , weights = df ["counts" ].to_numpy ())
174+ with np .errstate (invalid = "ignore" ):
175+ binned_intensity = weighted / bin_counts
176+
177+ # Map each bin's value back onto the original grid (a point's own
178+ # bin is never empty, so no NaNs are introduced here)
179+ bin_indices = np .clip (np .digitize (two_theta , edges ) - 1 , 0 , len (bin_counts ) - 1 )
180+ df ["intensity" ] = binned_intensity [bin_indices ]
141181
142182 # if no wavelength (or invalid wavelength) is passed, don't convert to Q and d
143183 if wavelength :
@@ -161,11 +201,15 @@ def _try_read_csv(sep: str, skiprows: int) -> pd.DataFrame | None:
161201 for warning_type , message in warnings_to_ignore :
162202 warnings .filterwarnings ("ignore" , category = warning_type , message = message )
163203
204+ # Baselines/normalisations are derived from "intensity", which is the
205+ # rebinned signal when rebinning occurred (smoother, on a uniform grid)
164206 y_option_df = cls ._calc_baselines_and_normalize (
165207 df ["2θ (°)" ], df ["intensity" ], theoretical = theoretical
166208 )
167209
168- y_options = ["intensity" ] + list (y_option_df .columns )
210+ # Expose the raw "counts" alongside "intensity" only when they differ
211+ intensity_options = ["counts" , "intensity" ]
212+ y_options = intensity_options + list (y_option_df .columns )
169213
170214 df = pd .concat ([df , y_option_df ], axis = 1 )
171215 df .index .name = location .split ("/" )[- 1 ] + (" (theoretical)" if theoretical else "" )
0 commit comments