usnistgov / PyHyperScattering

Tools for hyperspectral x-ray and neutron scattering data loading, reduction, slicing, and visualization.
Other
6 stars 8 forks source link

UserWarning: Error while time-integrating monitors onto images. Usually, this indicates a problem with the monitors, this is a critical error if doing normalization otherwise fine to ignore. #112

Open cbishop4 opened 5 months ago

cbishop4 commented 5 months ago

When using loadRun, this error is thrown.

cbishop4 commented 5 months ago

It's possible that this doesn't throw an error anymore, but it has always been an issue for me (currently using scan 42237). Ended up being an issue with the terms "drop_index" vs. "drop_vars" within loadMonitors. Added a try except for this in case "drop_index" does work for some other generation of data. (Partially adding this comment because I am not sure how good my commit message will be)

pbeaucage commented 5 months ago

So, for context, this is almost always related to the streaming-monitors mode of operation on SST1. There are good arguments that this basically is useless for quantitative/correct normalization (bc there is no guarantee that the streaming counter data time resolution matches your image time resolution, or that the streaming data is truly an integral of the monitor during that time vs an average). It's messy. And made messier by changing names for monitors as the instrument evolved, etc. There will be terrible edge cases for different data collection times, e.g at 0.1s you functionally do not have monitor data.

I would try to manually invoke the loadMonitors() function for suspect data and ensure that the monitor values physically make sense. It is very easy to make this error go away but still get incorrect data; chalk that up to creative instrument design.

cbishop4 commented 5 months ago

Personally, I'm inclined to neglect normalizing during data collection/initial loading. What I've been doing in the past is just ignoring all these errors, then going back and getting the streams post-load, taking in the diode streams, and been doing intensity correction there. Do you think maybe it would be good to move toward some default workflow where this is what we encourage? Maybe so that these loaders give you the raw data in the truest sense of the word, to avoid any confusion.

pbeaucage commented 3 months ago

I'm reusing this issue for a recent idea I had that has dramatically improved monitor loading on SST1 RSoXS and ought to greatly improve matters for the original error, but to your final point, which I must have missed (apologies, my Jan/Feb was a mess!) this is a pretty core PyHyper philosophy. After loading, the data should be free of instrument artifacts, and ready to interoperate with any other instrument. Fundamentally, in other words, PyHyper loaders should never give you truly raw data unless you really ask for it. The reason for this is that during the loading step, you have maximum access to accompanying metadata - after that everything is squished down and normalized and generally less than you have at the beginning. And having to manually correlate separately provided spectral data with the loaded scans is clunky and not philosophically really consistent with a smooth data experience. So the loader, the beginning, is the time to get this done - "if not now, then when?". That isn't to say loading should be an interaction-free black box, but the loader should provide an artifact that has all corrections applied. These corrections also should generally be pretty on-rails for most instruments - the streaming monitor mode of SST1RSoXS is a non-ideality that doesn't represent reasonable best practice.

Soapbox aside, it's worth digging into HOW SST1 loads monitor data and what goes wrong that leads to this error. This is mainly documentation and to set up the story.

SST1 monitor data is sparse, where each data point represents a new value provided by an EPICS IOC (server) over the network. Fundamentally, these events only get sent when a value changes. Each individual counter has its own set of network events, and so each data point sits on its own point in time. The array we load contains all these time points in the coordinate array, a number when the event corresponds to that time, and a NaN otherwise.

The current approach is:

  1. forward-fill in NaN's, i.e, assume the value did not change until another data point is received
  2. back-fill any remaining NaN's so that every timepoints - even unphysical ones - has a value
  3. take the RSoXS shutter open/close event stream, so extended, and binary thin it - remove 5 points from each side
  4. drop all counter data where that RSoXS shutter signal is 0
  5. correlate the times of the remaining data to the image times, and assign matching timepoints in the images to the counter values.

This works reasonably well for continuous streams that are not interrupted by the shutter (i.e, the signal is a continuous wave). Here, taking an average value is fine. It generally speaking fails for streams like the diode and TEY that are interrupted by the shutter (where the signal looks like a square wave or series of pulses), for several reasons: it relies on perfect clock sync between the streams, it has directional sensitivity (if the shutter signal was delayed and came later than the diode increasing, we never catch that early part), and the arbitrary loss of 5 points means a lot of useful data is lost. The diode runs between 3 and 5 Hz depending on network conditions, so with 1 s exposures losing 5 points could be all the data. When this happens, some images just don't have an accompanying counter point, which causes the above referenced error.

An approach I'm prototyping at this point is, for "interrupted" counters, just directly thresholding the square wave-like signal and thinning it in that space. Initial results are promising with decent NEXAFS for exposures of 1 s or so. Updated loading function for testing is coming soon.