Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
2e505bc
Modifications to the visualization code to optionally use a cutoff in…
May 4, 2025
f70d827
Notebook now uses the dark cutoff feature from the addition to visual…
May 4, 2025
0d562db
Merge pull request #23 from lg345/master
lg345 May 4, 2025
88c0dd4
Merge pull request #24 from lg345/21-xspectvisualizationplot_2d_diffe…
lg345 May 4, 2025
026e82f
README update so link to docs is easy to find
May 4, 2025
962ad9d
adding gitignore to stop syncing jupyter temp files
May 4, 2025
7e15706
Difference in magnitude for the timing was causing issue with binning…
May 4, 2025
60e1c78
JB analysis notebook from today. Includes extra diagnostic functions
May 5, 2025
a6afd38
Adding a power titration alignment block into the notebook. Works out…
May 5, 2025
7c78ed6
Merge remote-tracking branch 'refs/remotes/origin/mfx10089224' into m…
May 5, 2025
6818bd3
Post beamtime push. Mainly changes to notebooks (XES examples, dehe_o…
May 6, 2025
ad9f27b
Fixing README.md
May 6, 2025
65fd736
Added hitfinding and statistics. Have not fine tuned the threshold yet.
May 7, 2025
f473f59
Reorganized to put all of the default attributes to the top of the in…
May 7, 2025
da6b6d0
Remvoed redundancy in new default attributes.
May 7, 2025
9bbdfea
Showing hitfinding and statistics in the notebook now.
May 7, 2025
a75331e
added power titration analysis
May 7, 2025
0de602b
Merge remote-tracking branch 'refs/remotes/origin/mfx10089224' into m…
May 7, 2025
d4c3d92
adding ignore file
May 13, 2025
2e3e394
Merge pull request #29 from lg345/master
lg345 May 18, 2025
66cb836
Merge pull request #30 from lg345/mfx10089224
lg345 May 18, 2025
998b6d4
Clearning up notebook locations and storing them in the examples folder
May 18, 2025
0ac873f
Changed purge_all_keys() to set keys at the beginning to ensure the k…
May 18, 2025
f9b8ba2
feat: Add laser-specific analysis method and refactor analysis
May 18, 2025
6260529
Cleaning up notebooks. getting close to mfx101080524 notebook working.
May 18, 2025
52bc4d9
Forgot to git track the newly moved files which became unctracked.
May 18, 2025
4399050
modified XSpect for side-facing epix detector at MFX with static spec…
May 18, 2025
36c877c
Took out the absolute threshold for Hitfinding really should handle t…
May 18, 2025
1cf7a47
Working notebook
May 18, 2025
640122e
Some reason the energies were coming back backwards. Also for normali…
May 18, 2025
2885e84
more working notebook.
May 18, 2025
df69efa
Updates to the diagnostics:
May 23, 2025
1281d9d
Renamed files
May 23, 2025
430510a
To diagnostics module added a method to open/load h5 file since it fr…
May 23, 2025
d9b72e9
deleting/renaming files
May 23, 2025
ac2f70e
I seemingly deleted an extra line
May 29, 2025
ed6b99d
Added option to use a y-array for normalization. PRobably should neve…
May 29, 2025
7d60bfb
Sort of working notebook. hopefully the last working.
May 29, 2025
6acc6bd
Merge pull request #42 from lg345/mfx101080524_Follmer
follmerlab May 30, 2025
8a41376
Add files via upload
Jul 18, 2025
5d3833f
Fixed link to readthedocs in README.md
lg345 Jul 18, 2025
35b4130
Update README.md
lg345 Jul 18, 2025
09360e7
XSpect_Analysis.py amended to include a droplet_reconstruction functi…
Aug 30, 2025
83c52fb
Deleting .ipynb_checkpoints dir because it is in .gitignore and also …
Sep 2, 2025
4c3e99f
Deleting .ipynb_checkpoints dir because it is in .gitignore and also …
Sep 2, 2025
b47f9e1
Removed diagnostics.ipynb notebook from master because it is causing …
Sep 2, 2025
18fed81
Merge pull request #49 from lg345/dev
jtbabicz Sep 2, 2025
a961692
Some adjustments to the XSpect_Controller correcting the summation of…
Sep 2, 2025
d70d010
Changes to time_binning (1) Fixed by by removing the if/else statemen…
Sep 4, 2025
2758fa7
Minor tweak in diagnostics ROI viewer. Changed plt.imshow aspect to a…
Sep 4, 2025
b88ed07
New features in visualization: In added option to sum all (all runs/…
Sep 15, 2025
5e7c4dd
(1) Added notebook demonstrating new analysis features for averaging …
Sep 15, 2025
80724eb
Simplified/revised the changes made yesterday for the averaging all l…
Sep 16, 2025
40d2b09
Add option to SVD post processing to change size of plot
Sep 16, 2025
83cca4d
New feature: diff_slice in visualization. Slice/plot the various ener…
Sep 17, 2025
4e1c2dc
Tweaked the plotting offset for the svdplot function. Multiplied the …
Sep 18, 2025
59bdebe
Fixed rotation for diagnostics xes_ROI which was broken. Added a thir…
Sep 18, 2025
fd6b27c
Rename new feature notebook
Sep 18, 2025
e5c6784
Updated new features notebook to show xes ROI improved features
Sep 18, 2025
d43d837
New feature: Added method to XASBatchAnalysis to pickle/save analysis…
Sep 19, 2025
a3325f6
Adjusted how standard deviation gets calculated - fisrt in reduce_det…
Sep 25, 2025
47b291c
Removed .ipynb_checkpoints folder
Sep 25, 2025
fc51b09
Merge branch 'master' into Droplet_Analysis_Working
rribson Sep 25, 2025
6c602ad
Merge pull request #51 from lg345/Droplet_Analysis_Working
rribson Sep 25, 2025
33e082b
For XES batch analysis rotation primary analysis: added option to giv…
Sep 25, 2025
f7c741b
testing diagnostics commit
Sep 25, 2025
453a575
Retesting commit
Sep 25, 2025
3cee321
Merge pull request #52 from lg345/Droplet_Analysis_Working
rribson Sep 25, 2025
b4dcc3a
Added check for self.crystal_dict in controller. If it exists it will…
Oct 6, 2025
681b4f7
Fixed pixels_to_patch and patch_pixels. The behavior before was to se…
Oct 6, 2025
4fcfa30
Reorganized some of the status messages to make more sense in the con…
Oct 7, 2025
b14b584
1. Updates to the controller primary_analysis_parallel_range method. …
Oct 10, 2025
2891d44
Updated README link to docs page
Oct 10, 2025
e1a4f6e
Adding XSpect_XES_examples.ipynb notebook bc I suspect its causing an…
Oct 10, 2025
e3ca65c
Merge branch 'jtb' into master
jtbabicz Oct 10, 2025
5d97d9d
Revert "Merge branch 'jtb' into master"
Oct 10, 2025
68f23cb
Removed vim tmp file
Oct 15, 2025
41f1649
(1) Moved code in controller primary_analysis_parallel_range combinin…
Oct 15, 2025
2b3ac6e
Added averaging all laser off across time bins option
Oct 16, 2025
65643a4
Propogated error for averaging all laser off
Oct 16, 2025
010b712
Removing empty file
Oct 16, 2025
0387bcd
Updated diff_slice to include error bars.
Oct 16, 2025
fc1a84c
Bug in update status string formating during the handling of ROI/angl…
Oct 17, 2025
72826ac
Same bug as previous commit with the type error with string formatting.
Oct 17, 2025
976875b
Fixed bugs Leland outlined in https://github.com/lg345/XSpect/issues/…
Oct 30, 2025
d465bd3
To post processing svdplot: (1) added xlims optional parameter (2) re…
Oct 31, 2025
6afe6d0
Analysis and controller currently working for XAS analysis, represent…
Feb 25, 2026
345ed7a
re staging files to push
Apr 8, 2026
f3d490f
Currently ScanAnalysis_1D_XES can be run, it's not compatible with pa…
May 5, 2026
522767f
Added class in Analysis module for spectrum derivative analyzer for f…
May 22, 2026
042d513
Merge pull request #90 from lg345/xcs101237825
rribson May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
46 changes: 0 additions & 46 deletions .github/workflows/build_versioned_mkdocs.yml

This file was deleted.

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# ignore jupyter temp files
.ipynb_checkpoints/
Controller_Plans.ipynb
./examples/.ipynb_checkpoints/
XSpect_XES_examples.ipynb
*.swp
XSpect/*.swp
31 changes: 31 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "19"
# rust: "1.64"
# golang: "1.19"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Clone the repository:

```bash
git clone https://github.com/lg345/XSpecT.git
git clone https://github.com/lg345/XSpect.git
```

Ensure that you have the necessary dependencies installed on your system.
Expand Down
163 changes: 81 additions & 82 deletions XSpect/.ipynb_checkpoints/XSpect_Analysis-checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ def purge_all_keys(self,keys_to_keep):
List of keys to retain.
"""

keys_to_keep = set(keys_to_keep) # Remove duplicates by converting to a set
new_dict = {attr: value for attr, value in self.__dict__.items() if attr in keys_to_keep}
self.__dict__ = new_dict

Expand Down Expand Up @@ -397,11 +398,15 @@ def purge_keys(self,run,keys):
setattr(run, detector_key, None)
run.update_status(f"Purged key to save room: {detector_key}")

def reduce_detector_shots(self, run, detector_key,reduction_function=np.sum, purge=True):
def reduce_detector_shots(self, run, detector_key,reduction_function=np.sum, purge=True,new_key=False):
detector = getattr(run, detector_key)
reduced_data=reduction_function(detector,axis=0)
run.update_status(f"Reduced detector by shots: {detector_key} with number of shots: {np.shape(detector)}")
setattr(run, f"{detector_key}_summed", reduced_data)
if new_key:
target_key=f"{detector_key}_summed"
else:
target_key=detector_key
setattr(run, target_key, reduced_data)
if purge:
setattr(run, detector_key,None)
run.update_status(f"Purged key to save room: {detector_key}")
Expand Down Expand Up @@ -431,11 +436,16 @@ def reduce_detector_spatial(self, run, detector_key, shot_range=[0, None], rois=
if combine:

roi_combined = [rois[0][0], rois[-1][1]] # Combined ROI spanning the first and last ROI
mask = np.zeros(detector.shape[2], dtype=bool)
mask = np.zeros(detector.shape[-1], dtype=bool)
for roi in rois:
mask[roi[0]:roi[1]] = True
masked_data = detector[shot_range[0]:shot_range[1], :, :][:, :, mask]
reduced_data = reduction_function(masked_data, axis=2)
if detector.ndim==3:
masked_data = detector[shot_range[0]:shot_range[1], :, :][:, :, mask]
elif detector.ndim==2:
masked_data = detector[:, mask]
elif detector.ndim==1:
masked_data = detector[mask]
reduced_data = reduction_function(masked_data, axis=-1)
roi_indices = ', '.join([f"{roi[0]}-{roi[1]}" for roi in rois])
run.update_status(f"Spatially reduced detector: {detector_key} with combined ROI indices: {roi_indices}")
setattr(run, f"{detector_key}_ROI_1", reduced_data)
Expand All @@ -452,7 +462,7 @@ def reduce_detector_spatial(self, run, detector_key, shot_range=[0, None], rois=
setattr(run, detector_key,None)
#delattr(run, detector_key)
#del run.detector_key
run.update_status(f"Purged key to save room: {detector_key}")
run.update_status(f"Purged key after spatial reduction to save room: {detector_key}")

def time_binning(self,run,bins,lxt_key='lxt_ttc',fast_delay_key='encoder',tt_correction_key='time_tool_correction'):
"""
Expand All @@ -471,10 +481,16 @@ def time_binning(self,run,bins,lxt_key='lxt_ttc',fast_delay_key='encoder',tt_cor
tt_correction_key : str, optional
The key for the time tool correction data (default is 'time_tool_correction').
"""
#print( str(getattr(run,fast_delay_key)))
#print( str(getattr(run,tt_correction_key)))
#print( str(getattr(run,fast_delay_key)+getattr(run,tt_correction_key)))
if lxt_key==None:
run.delays = 0+ getattr(run,fast_delay_key) + getattr(run,tt_correction_key)
delays = np.array(getattr(run,fast_delay_key)).flatten() + np.array(getattr(run,tt_correction_key)).flatten()
else:
run.delays = getattr(run,lxt_key)*1.0e12 + getattr(run,fast_delay_key) + getattr(run,tt_correction_key)
delays = np.array(getattr(run,lxt_key)).flatten()*1.0e12 + np.array((getattr(run,fast_delay_key))).flatten() + np.array(getattr(run,tt_correction_key)).flatten()
delays=np.array(getattr(run,fast_delay_key)).flatten()*1.0e12+np.array(getattr(run,tt_correction_key)).flatten()
#print(str(delays))
run.delays=delays
run.time_bins=bins
run.timing_bin_indices=np.digitize(run.delays, bins)[:]
run.update_status('Generated timing bins from %f to %f in %d steps.' % (np.min(bins),np.max(bins),len(bins)))
Expand Down Expand Up @@ -550,7 +566,6 @@ def reduce_detector_temporal(self, run, detector_key, timing_bin_key_indices,ave
"""
detector = getattr(run, detector_key)
indices = getattr(run, timing_bin_key_indices)
#print(len(detector.shape))
expected_length = len(run.time_bins)+1
if len(detector.shape) < 2:
reduced_array = np.zeros((expected_length))
Expand Down Expand Up @@ -592,7 +607,7 @@ def patch_pixels(self,run,detector_key, mode='average', patch_range=4, deg=1, p
self.patch_pixel(run,detector_key,pixel,mode,patch_range,deg,poly_range,axis=axis)


def patch_pixel(self, run, detector_key, pixel, mode='average', patch_range=4, deg=1, poly_range=6,axis=1):
def patch_pixel(self, run, detector_key, pixel, mode='average', patch_range=4, deg=1, poly_range=6, axis=1):
"""
EPIX detector pixel patching.
TODO: extend to patch regions instead of per pixel.
Expand All @@ -603,9 +618,9 @@ def patch_pixel(self, run, detector_key, pixel, mode='average', patch_range=4, d
pixel : integer
Pixel point to be patched
mode : string
Determined which mode to use for patching the pixel. Averaging works well.
Determines which mode to use for patching the pixel. Averaging works well.
patch_range : integer
pixels away from the pixel to be patched to be used for patching. Needed if multiple pixels in a row are an issue.
Pixels away from the pixel to be patched to be used for patching. Needed if multiple pixels in a row are an issue.
deg : integer
Degree of polynomial if polynomial patching is used.
poly_range : integer
Expand All @@ -616,28 +631,62 @@ def patch_pixel(self, run, detector_key, pixel, mode='average', patch_range=4, d
The original data with the new patch values.
"""
data = getattr(run, detector_key)
if mode == 'average':
neighbor_values = data[:, pixel - patch_range:pixel + patch_range + 1, :]
new_val=np.sum(neighbor_values, axis=1) / neighbor_values.shape[1]

if axis==1:
data[:, pixel, :] = new_val
elif axis==2:
data[:,:,pixel]=new_val
elif mode == 'polynomial':
patch_x = np.arange(pixel - patch_range - poly_range, pixel + patch_range + poly_range + 1, 1)
def get_neighbor_values(data, pixel, patch_range, axis):
axis_slice = [slice(None)] * data.ndim
start_index = max(pixel - patch_range, 0)
end_index = min(pixel + patch_range + 1, data.shape[axis])
axis_slice[axis] = slice(start_index, end_index)
return data[tuple(axis_slice)]

def patch_value_average(data, pixel, patch_range, axis):
neighbor_values = get_neighbor_values(data, pixel, patch_range, axis)
neighbor_values = np.moveaxis(neighbor_values, axis, 0)
new_val = np.mean(neighbor_values, axis=0)
return new_val

def patch_value_polynomial(data, pixel, patch_range, poly_range, deg, axis):
patch_x = np.arange(pixel - patch_range - poly_range, pixel + patch_range + poly_range + 1)
patch_range_weights = np.ones(len(patch_x))
patch_range_weights[pixel - patch_range - poly_range:pixel + patch_range + poly_range] = 0.001
coeffs = np.polyfit(patch_x, data[pixel - patch_range - poly_range:pixel + patch_range + poly_range + 1, :], deg,
w=patch_range_weights)
data[pixel, :] = np.polyval(coeffs, pixel)
patch_range_weights[patch_range:-patch_range] = 0.001

neighbor_values = get_neighbor_values(data, pixel, patch_range + poly_range, axis)
neighbor_values = np.moveaxis(neighbor_values, axis, 0)

new_vals = []
for idx in range(neighbor_values.shape[1]):
ys = neighbor_values[:, idx]
coeffs = np.polyfit(patch_x, ys, deg, w=patch_range_weights)
new_vals.append(np.polyval(coeffs, pixel))
return np.array(new_vals)

def patch_value_interpolate(data, pixel, patch_range, poly_range, axis):
patch_x = np.arange(pixel - patch_range - poly_range, pixel + patch_range + poly_range + 1)
neighbor_values = get_neighbor_values(data, pixel, patch_range + poly_range, axis)
neighbor_values = np.moveaxis(neighbor_values, axis, 0)

new_vals = []
for idx in range(neighbor_values.shape[1]):
ys = neighbor_values[:, idx]
interp_func = interp1d(patch_x, ys, kind='quadratic')
new_vals.append(interp_func(pixel))
return np.array(new_vals)

if mode == 'average':
new_val = patch_value_average(data, pixel, patch_range, axis)
elif mode == 'polynomial':
new_val = patch_value_polynomial(data, pixel, patch_range, poly_range, deg, axis)
elif mode == 'interpolate':
patch_x = np.arange(pixel - patch_range - poly_range, pixel + patch_range + poly_range + 1, 1)
interp = interp1d(patch_x, data[pixel - patch_range - poly_range:pixel + patch_range + poly_range + 1, :],
kind='quadratic')
data[pixel, :] = interp(pixel)
setattr(run,detector_key,data)
run.update_status('Detector %s pixel %d patched. Old value.'%(detector_key, pixel ))
new_val = patch_value_interpolate(data, pixel, patch_range, poly_range, axis)
else:
raise ValueError(f"Unsupported mode: {mode}")

patch_slice = [slice(None)] * data.ndim
patch_slice[axis] = pixel
data[tuple(patch_slice)] = new_val

setattr(run, detector_key, data)
run.update_status(f"Detector {detector_key} pixel {pixel} patched. Old value.")

def patch_pixels_1d(self,run,detector_key, mode='average', patch_range=4, deg=1, poly_range=6):
"""
Expand Down Expand Up @@ -700,7 +749,7 @@ def patch_pixel_1d(self, run, detector_key, pixel, mode='average', patch_range=4
kind='quadratic')
data[pixel, :] = interp(pixel)
setattr(run,detector_key,data)
run.update_status('Detector %s pixel %d patched.'%(detector_key, pixel ))
run.update_status('Detector %s pixel %d patched in mode %s'%(detector_key, pixel,mode ))



Expand Down Expand Up @@ -750,57 +799,7 @@ def make_energy_axis(self, run,energy_axis_length, A, R, mm_per_pixel=0.05, d=0

setattr(run,self.xes_line+'_energy',xaxis[::-1])
run.update_status('XES energy axis generated for %s'%(self.xes_line))
def reduce_detector_spatial(self, run, detector_key, shot_range=[0, None], rois=[[0, None]], reduction_function=np.sum, purge=True, combine=True,adu_cutoff=3.0):
"""
Reduce spatial dimensions of detector data by combining or applying reduction functions over regions of interest (ROIs).

Parameters
----------
run : object
The spectroscopy run instance.
detector_key : str
The key corresponding to the detector data.
shot_range : list of int, optional
The range of shots to consider for reduction (default is [0, None]).
rois : list of lists of int, optional
The regions of interest for spatial reduction (default is [[0, None]]).
reduction_function : function, optional
The function to use for reduction (default is np.sum).
purge : bool, optional
Whether to purge the original detector data after reduction (default is True).
combine : bool, optional
Whether to combine ROIs into a single reduced dataset (default is True).
adu_cutoff : float, optional
The ADU cutoff value for filtering (default is 3.0).
"""
detector = getattr(run, detector_key)
if combine:

roi_combined = [rois[0][0], rois[-1][1]] # Combined ROI spanning the first and last ROI
mask = np.zeros(detector.shape[2], dtype=bool)
for roi in rois:
mask[roi[0]:roi[1]] = True
masked_data = detector[shot_range[0]:shot_range[1], :, :][:, :, mask]
#masked_data = masked_data * (masked_data > adu_cutoff)
#Note the adu filtering should be handled at the controller level
reduced_data = reduction_function(masked_data, axis=2)
roi_indices = ', '.join([f"{roi[0]}-{roi[1]}" for roi in rois])
run.update_status(f"Spatially reduced detector: {detector_key} with combined ROI indices: {roi_indices}")
setattr(run, f"{detector_key}_ROI_1", reduced_data)
else:
for idx, roi in enumerate(rois):
data_chunk = detector[shot_range[0]:shot_range[1], roi[0]:roi[1]]
reduced_data = reduction_function(data_chunk, **kwargs)
if roi[1] is None:
roi[1] = detector.shape[1] - 1
run.update_status(f"Spatially reduced detector: {detector_key} with ROI: {roi[0]}, {roi[1]}")
setattr(run, f"{detector_key}_ROI_{idx+1}", reduced_data)
if purge:
#pass
setattr(run, detector_key,None)
#delattr(run, detector_key)
#del run.detector_key
run.update_status(f"Purged key to save room: {detector_key}")
def reduce_det_scanvar(self, run, detector_key, scanvar_key, scanvar_bins_key):
"""
Reduce detector data by binning according to an arbitrary scan variable.
Expand Down
Loading