From 639bfd6115cc7ba709bb60dce263352540dd3311 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 1 Apr 2025 15:26:31 +0200 Subject: [PATCH 01/57] PSX_RestoreGuiState: Prefer GUI control setter over PGC We already call PSX_UpdateAllEventGraph with all flags set in PSX_PostPlot. So using PGC for each control and doing a partial update is just a waste of CPU cycles. This also fixes a bug where the Fit accepted average checkbox was checked and the restore triggered an assertion as the single event waves were not yet up-to-date. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 9b2a72f274..9b216870af 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -3590,18 +3590,15 @@ static Function PSX_RestoreGuiState(string win) SetCheckBoxState(specialEventPanel, ctrl, JSON_GetVariable(jsonID, "/specialEventPanel/" + ctrl)) endfor - // first block size, as that recalculates the number of blocks - PGC_SetAndActivateControl(specialEventPanel, "setvar_event_block_size", val = JSON_GetVariable(jsonID, "/specialEventPanel/setvar_event_block_size")) + SetSetVariable(specialEventPanel, "setvar_event_block_size", JSON_GetVariable(jsonID, "/specialEventPanel/setvar_event_block_size")) + SetSetVariable(specialEventPanel, "setvar_fit_start_amplitude", JSON_GetVariable(jsonID, "/specialEventPanel/setvar_fit_start_amplitude")) - selectedBlock = JSON_GetVariable(jsonID, "/specialEventPanel/popup_block") - allBlocks = PSX_GetAllEventBlockNumbers(specialEventPanel) - lastBlock = NumberFromList(ItemsInList(allBlocks) - 1, allBlocks) - PGC_SetAndActivateControl(specialEventPanel, "popup_block", val = limit(selectedBlock, 0, lastBlock)) + SetPopupMenuIndex(specialEventPanel, "popup_block", JSON_GetVariable(jsonID, "/specialEventPanel/popup_block")) WAVE/T popups = PSX_GetSpecialEventPanelPopups(specialEventPanel) for(popMenu : popups) - PGC_SetAndActivateControl(specialEventPanel, popMenu, val = JSON_GetVariable(jsonID, "/specialEventPanel/" + popMenu)) + SetPopupMenuIndex(specialEventPanel, popMenu, JSON_GetVariable(jsonID, "/specialEventPanel/" + popMenu)) endfor mainWindow = GetMainWindow(win) From 92c02c04323bd4d0910a022372f004891e7b312d Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 4 Apr 2025 18:11:21 +0200 Subject: [PATCH 02/57] PSX_GetEventIndexAndComboIndex: Handle vs in keyboard navigation When having a SF formula like psxStats vs [0, 1] the xWave has not the same size anymore. So we need to refetch the x-wave in this case. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 27 +++++++++--- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 9b216870af..68acbb5286 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -4064,7 +4064,7 @@ End static Function [variable eventIndex, variable waveIndex, variable comboIndex] PSX_GetEventIndexAndComboIndex(string win, [variable direction]) string psxGraph, info, trace, postProc - variable idx, yPointNumber, numEntries + variable idx, yPointNumber, yNumRows, xNumRows psxGraph = PSX_GetPSXGraph(win) @@ -4119,16 +4119,28 @@ static Function [variable eventIndex, variable waveIndex, variable comboIndex] P WAVE/T comboKeys = JWN_GetTextWaveFromWaveNote(yWave, SF_META_USER_GROUP + PSX_JWN_COMBO_KEYS_NAME) - numEntries = DimSize(yWave, ROWS) - ASSERT(numEntries == DimSize(xWave, ROWS), "Unmatching wave sizes") + yNumRows = DimSize(yWave, ROWS) + xNumRows = DimSize(xWave, ROWS) - yPointNumber = limit(yPointNumber + direction, 0, numEntries - 1) + if(xNumRows == yNumRows) + // x wave is from the psx yWave + WAVE refxWave = xWave + else + // yWave vs something else + WAVE refxWave = JWN_GetNumericWaveFromWaveNote(yWave, SF_META_XVALUES) + xNumRows = DimSize(refxWave, ROWS) + endif + WaveClear xWave + + ASSERT(xNumRows == yNumRows, "Unmatching wave sizes") + + yPointNumber = limit(yPointNumber + direction, 0, yNumRows - 1) comboIndex = PSX_GetComboIndexForComboKey(win, comboKeys[yPointNumber]) strswitch(postProc) case "nothing": // fallthrough case "log10": - eventIndex = xWave[yPointNumber] + eventIndex = refxWave[yPointNumber] break case "nonfinite": eventIndex = yWave[yPointNumber] @@ -4228,7 +4240,10 @@ Function PSX_PlotInteractionHook(STRUCT WMWinHookStruct &s) direction = PSX_GetDirectionFromKeyCode(psxGraph, s.keyCode) [eventIndex, waveIndex, comboIndex] = PSX_GetEventIndexAndComboIndex(win, direction = direction) - ASSERT(IsFinite(eventIndex) && IsFinite(waveIndex) && IsFinite(comboIndex), "Invalid event index") + if(IsNaN(eventIndex) || IsNaN(waveIndex) || IsNaN(comboIndex)) + BUG("Could not get new eventIndex after direction application") + break + endif if(PSX_GetCurrentComboIndex(win) != comboIndex) mainWindow = GetMainWindow(win) diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 874446900d..42b3ab14f3 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -1697,6 +1697,50 @@ static Function MouseSelectionStatsPostProcNonFinite() CheckPSXEventField({psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0}, PSX_ACCEPT) End +static Function KeyboardSelectionPSXStatsVS() + + string win, browser, code, psxGraph, psxStatsGraph, postProc, comboKey, tracenames, trace + variable numEvents, logMode + + Make/FREE/T/N=2 combos + sprintf comboKey, "Range[50, 150], Sweep [0], Channel [AD6], Device [ITC16_Dev_0], Experiment [%s]", GetExperimentName() + combos[0] = comboKey + sprintf comboKey, "Range[50, 150], Sweep [2], Channel [AD6], Device [ITC16_Dev_0], Experiment [%s]", GetExperimentName() + combos[1] = comboKey + WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) + + overrideResults[][][%$"Fit Result"] = 1 + overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"KernelAmpSignQC"] = 1 + + browser = SetupDatabrowserWithSomeData() + + code = GetTestCode("nothing") + "\n vs [1, 2]" + + // combo0 is the current one + + win = ExecuteSweepFormulaCode(browser, code) + + psxGraph = MIES_PSX#PSX_GetPSXGraph(win) + psxStatsGraph = MIES_PSX#PSX_GetPSXStatsGraph(psxGraph) + + REQUIRE(WindowExists(psxStatsGraph)) + + SetActiveSubwindow $psxStatsGraph + + tracenames = TraceNameList(psxStatsGraph, ";", 1) + trace = StringFromList(0, tracenames) + CHECK_PROPER_STR(trace) + + Cursor/W=$psxStatsGraph/P A, $trace, 0 + + CheckCurrentEvent(psxStatsGraph, 0, 0, 0) + + SendKey(psxGraph, LEFT_KEY) + SendKey(psxGraph, LEFT_KEY) + CHECK_NO_RTE() +End + static Function/WAVE GetTracesHelper(string win, variable options) return ListToTextWave(SortList(TraceNameList(win, ";", options)), ";") From f2fdcede1b15b75c88e90503ecb26603bfe008af Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Tue, 18 Mar 2025 12:53:52 -0700 Subject: [PATCH 03/57] PSX: Tweak detection algorithm --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 68acbb5286..e994034c75 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -367,7 +367,7 @@ static Function/WAVE PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFF // no window function on purpose WAVE/C outputFFT = DoFFT(sweepData, padSize = numPoints) - Multithread outputFFT[] = outputFFT[p] / psxKernelFFT[p] + Multithread outputFFT[] = outputFFT[p] / (psxKernelFFT[p] + 1e-5) IFFT/DEST=Deconv/FREE outputFFT @@ -476,7 +476,7 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FindPeaks(WAVE sweepDataOffFilt Make/FREE/D/N=(numPeaksMax) peakX, peakY for(i = 0; i < numPeaksMax; i += 1) - FindPeak/B=10/M=(threshold)/Q/R=(start, stop) sweepDataOffFiltDeconv + FindPeak/B=100/M=(threshold)/Q/R=(start, stop) sweepDataOffFiltDeconv if(V_Flag != 0) break From 1691e34ac548dbf146e8589a3d3dad74e93aa327 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 19 Mar 2025 14:09:18 +0100 Subject: [PATCH 04/57] PSX: Calculate the starting point for the average fit better Taking the start of the single event (0) does not work that well. So let's take 10% of the mean peak time instead. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index e994034c75..308e5b341f 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2226,7 +2226,7 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, numEvents = DimSize(eventIndexFromTraces, ROWS) Make/WAVE/FREE/N=(numEvents) contAverageAll, contAverageAccept, contAverageReject, contAverageUndet - Make/FREE/D/N=(numEvents) eventStopTime + Make/FREE/D/N=(numEvents) eventStopTime, eventPeakTime for(i = 0; i < numEvents; i += 1) idx = str2num(eventIndexFromTraces[i]) @@ -2249,6 +2249,7 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, // single event waves are zeroed in x-direction to extractStartAbs [extractStartAbs, extractStopAbs] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, idx) eventStopTime[acceptIndex] = extractStopAbs - extractStartAbs + eventPeakTime[acceptIndex] = psxEvent[idx][%peak_t] - extractStartAbs acceptIndex += 1 break @@ -2275,7 +2276,7 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, PSX_UpdateAverageWave(contAverageAll, numEvents, averageDFR, PSX_ALL) Redimension/N=(acceptIndex) eventStopTime - PSX_FitAcceptAverage(win, averageDFR, eventStopTime) + PSX_FitAcceptAverage(win, averageDFR, eventPeakTime, eventStopTime) End /// @brief Helper function to update the average waves for the all event graph @@ -2299,10 +2300,10 @@ static Function/DF PSX_GetAverageFolder(string win) return PSX_GetWorkingFolder(win) End -static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventStopTime) +static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventPeakTime, WAVE eventStopTime) string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc - variable err, numAveragePoints, start, stop, meanStopTime + variable err, numAveragePoints, start, stop, meanStopTime, meanPeakTime WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) @@ -2330,12 +2331,19 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventSto WAVE/Z eventStopTimeClean = ZapNaNs(eventStopTime) if(WaveExists(eventStopTimeClean)) - meanStopTime = mean(eventStopTime) + meanStopTime = mean(eventStopTimeClean) else meanStopTime = Inf endif - start = 0 + WAVE/Z eventPeakTimeClean = ZapNaNs(eventPeakTime) + if(WaveExists(eventPeakTimeClean)) + meanPeakTime = mean(eventPeakTime) + else + FATAL_ERROR("Could not find any events with finite peak_t") + endif + + start = 0.1 * meanPeakTime stop = min(IndexToScale(average, DimSize(average, ROWS) - 1, ROWS), meanStopTime) AssertOnAndClearRTError() From e53a9231e005f9a25581ea2596e226f3c3214334 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 19 Mar 2025 14:40:22 +0100 Subject: [PATCH 05/57] PSX_GetEventFitRange: Change starting position Requested by Tim Jarsky. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 308e5b341f..8baa87c8bc 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -764,17 +764,17 @@ static Function [variable start, variable stop] PSX_GetEventFitRange(WAVE sweepD variable calcLength, maxLength - start = psxEvent[eventIndex][%deconvPeak_t] + start = psxEvent[eventIndex][%peak_t] maxLength = PSX_FIT_RANGE_FACTOR * JWN_GetNumberFromWaveNote(psxEvent, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") if(eventIndex == (DimSize(psxEvent, ROWS) - 1)) calcLength = maxLength else - calcLength = min((psxEvent[eventIndex + 1][%deconvPeak_t] - start) * PSX_FIT_RANGE_PERC, maxLength) + calcLength = min((psxEvent[eventIndex + 1][%peak_t] - start) * PSX_FIT_RANGE_PERC, maxLength) endif - if(calcLength == 0) + if(calcLength <= 0) calcLength = maxLength endif From d8727e5680cfb3e830a34e43cb1d97ec50665928 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 19 Mar 2025 14:56:06 +0100 Subject: [PATCH 06/57] PSX_CalculateEventProperties: Tweak baseline calculation The current algorithm for determining the left boundary for the baseline search did not take into account events which are close-by. Fix that by using the peak time of the previous event as maximum boundary. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 21 ++++++++++++-------- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 8baa87c8bc..03f4572b36 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -505,6 +505,7 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ variable peak, peak_t, baseline, baseline_t, amplitude variable overrideSignQC = NaN string comboKey + variable peak_t_prev = -Inf if(!WaveExists(peakXUnfiltered) || !WaveExists(peakYUnfiltered)) return [$"", $""] @@ -516,7 +517,7 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ for(i = 0; i < numCrossings; i += 1) - [peak, peak_t, baseline, baseline_t, amplitude] = PSX_CalculateEventProperties(peakXUnfiltered, peakYUnfiltered, sweepDataOffFilt, kernelAmp, kernelRiseTau, kernelDecayTau, i) + [peak, peak_t, baseline, baseline_t, amplitude] = PSX_CalculateEventProperties(peakXUnfiltered, peakYUnfiltered, sweepDataOffFilt, peak_t_prev, kernelAmp, kernelRiseTau, kernelDecayTau, i) #ifdef AUTOMATED_TESTING WAVE/Z overrideResults = GetOverrideResults() @@ -539,7 +540,8 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ peakX[idx] = peakXUnfiltered[i] peakY[idx] = peakYUnfiltered[i] - idx += 1 + idx += 1 + peak_t_prev = peak_t endfor if(idx == 0) @@ -593,11 +595,11 @@ static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE pea return [peak_t, peak] End -static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t, variable kernelAmp, variable kernelRiseTau) +static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t_prev, variable peak_t, variable kernelAmp, variable kernelRiseTau) variable range - WaveStats/M=1/Q/R=(peak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, peak_t) sweepDataOffFilt + WaveStats/M=1/Q/R=(max(peak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, peak_t_prev), peak_t) sweepDataOffFilt if(kernelAmp > 0) baseline_t = V_minloc @@ -614,10 +616,10 @@ static Function [variable baseline_t, variable baseline] PSX_CalculateEventBasel return [baseline_t, baseline] End -static Function [variable peak, variable peak_t, variable baseline, variable baseline_t, variable amplitude] PSX_CalculateEventProperties(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) +static Function [variable peak, variable peak_t, variable baseline, variable baseline_t, variable amplitude] PSX_CalculateEventProperties(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable peak_t_prev, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) [peak_t, peak] = PSX_CalculateEventPeak(peakX, peakY, sweepDataOffFilt, kernelAmp, kernelRiseTau, kernelDecayTau, index) - [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t, kernelAmp, kernelRiseTau) + [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t_prev, peak_t, kernelAmp, kernelRiseTau) amplitude = peak - baseline @@ -628,6 +630,7 @@ End static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffFiltDeconv, WAVE sweepDataOffFilt, WAVE sweepData, WAVE/Z peakXUnfiltered, WAVE/Z peakYUnfiltered, variable maxTauFactor, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, WAVE riseTimeParams, WAVE psxEvent, WAVE eventFit) variable i, numCrossings, deconvPeak, deconvPeak_t, peak, peak_t, baseline, baseline_t, amplitude, iei + variable peak_t_prev = -Inf // we need to first throw away events with invalid amplitude so that // we can then calculate the distance to the neighbour in peakX[i + 1] below @@ -649,8 +652,8 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffF deconvPeak_t = peakX[i] deconvPeak = peakY[i] - [peak, peak_t, baseline, baseline_t, amplitude] = PSX_CalculateEventProperties(peakX, peakY, sweepDataOffFilt, \ - kernelAmp, kernelRiseTau, kernelDecayTau, i) + [peak, peak_t, baseline, baseline_t, amplitude] = PSX_CalculateEventProperties(peakX, peakY, sweepDataOffFilt, \ + peak_t_prev, kernelAmp, kernelRiseTau, kernelDecayTau, i) if(i == 0) iei = NaN @@ -667,6 +670,8 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffF psxEvent[i][%baseline_t] = baseline_t psxEvent[i][%amplitude] = amplitude psxEvent[i][%iei] = iei + + peak_t_prev = peak_t endfor // safe defaults diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index ec9685c2b9..2e21ccd31f 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8478,7 +8478,8 @@ End /// in the filtered sweep wave /// - 4/peak_t: peak time /// - 5/baseline: Maximum (negative kernel amp sign) or minimum (positive kernel amp sign) in the range of -/// [peak_t – 10 * kernelRiseTau, peak_t], averaged over +/- 5 points, in the filtered sweep wave +/// [peak_t – 10 * kernelRiseTau or peak_t of previous event (whichever comes later), +/// peak_t], averaged over +/- 5 points, in the filtered sweep wave /// - 6/baseline_t: baseline time /// - 7/amplitude: Relative amplitude: [3] - [5] /// - 8/iei: Time difference to previous event (inter event interval) [ms] From ad50bcaee54c29c96af3bb26d32ed7437de0a495 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 19 Mar 2025 15:40:15 +0100 Subject: [PATCH 07/57] UTF_SweepFormula_PSX.ipf: Fix tests We do override the box size for the find peak search as that is a quick fix but not that dirty. Some minor test adjustments were also required. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 15 +++++++++++++-- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 19 ++++++++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 03f4572b36..42ea6405ad 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -160,6 +160,15 @@ Menu "GraphMarquee" "PSX: Jump to Events", /Q, PSX_JumpToEvents() End +static Function PSX_GetFindPeakBoxSize() + +#ifdef AUTOMATED_TESTING + return 10 +#endif // AUTOMATED_TESTING + + return 100 +End + static Function/S PSX_GetUserDataForWorkingFolder() return PSX_USER_DATA_WORKING_FOLDER @@ -459,7 +468,7 @@ End /// @retval peakY y-coordinates of peaks static Function [WAVE/D peakX, WAVE/D peakY] PSX_FindPeaks(WAVE sweepDataOffFiltDeconv, variable threshold, [variable numPeaksMax, variable start, variable stop]) - variable i + variable i, boxSize if(ParamIsDefault(numPeaksMax)) numPeaksMax = PSX_NUM_PEAKS_MAX @@ -473,10 +482,12 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FindPeaks(WAVE sweepDataOffFilt stop = rightx(sweepDataOffFiltDeconv) endif + boxSize = PSX_GetFindPeakBoxSize() + Make/FREE/D/N=(numPeaksMax) peakX, peakY for(i = 0; i < numPeaksMax; i += 1) - FindPeak/B=100/M=(threshold)/Q/R=(start, stop) sweepDataOffFiltDeconv + FindPeak/B=(boxSize)/M=(threshold)/Q/R=(start, stop) sweepDataOffFiltDeconv if(V_Flag != 0) break diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 42b3ab14f3..46564756a7 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -1353,9 +1353,9 @@ static Function CheckEventDataHelper(WAVE/Z/WAVE dataWref, variable index, varia INFO("index = %d, V_numNaNs = %d, kernelAmpSign = %d", n0 = index, n1 = V_numNans, n2 = kernelAmpSign) - // 1 NaN for the first event only, the rest is onset Time + // 5 NaNs for the first event only, the rest is onset Time if(kernelAmpSign == 1) - CHECK_EQUAL_VAR(V_numNaNs, 1) + CHECK_EQUAL_VAR(V_numNaNs, 5) elseif(kernelAmpSign == -1) CHECK_EQUAL_VAR(V_numNaNs, 9) else @@ -2860,6 +2860,9 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() overrideResults[1][%$combos[0]][%$"Fit Result"] = 1 overrideResults[1][%$combos[0]][%$"Tau"] = -Inf + overrideResults[3][%$combos[0]][%$"Fit Result"] = 1 + overrideResults[3][%$combos[0]][%$"Tau"] = -Inf + overrideResults[0][%$combos[0]][%$"Fit Result"] = 0 overrideResults[0][%$combos[0]][%$"Tau"] = NaN @@ -2892,11 +2895,10 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() [WAVE psxEvent_0, WAVE psxEvent_1] = GetPSXEventWavesHelper(psxStatsGraph) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0, 3}, PSX_REJECT) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {1, 2}, PSX_UNDET) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0}, PSX_REJECT) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {1, 2, 3}, PSX_UNDET) CheckPSXEventField({psxEvent_0}, {"Event manual QC call"}, {0, 1, 2, 3}, PSX_UNDET) - CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {0, 1}, PSX_UNDET) - CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {2}, PSX_REJECT) + CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {0, 1, 2}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Event manual QC call"}, {0, 1, 2}, PSX_UNDET) SendKey(psxStatsGraph, UP_KEY) @@ -2904,7 +2906,7 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() SendKey(psxStatsGraph, DOWN_KEY) SendKey(psxStatsGraph, UP_KEY) - CheckCurrentEvent(psxStatsGraph, 1, 2, 4) + CheckCurrentEvent(psxStatsGraph, 1, 0, 3) CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0, 3}, PSX_REJECT) CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {2}, PSX_UNDET) @@ -2916,8 +2918,7 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() CheckPSXEventField({psxEvent_0}, {"Event manual QC call"}, {3}, PSX_REJECT) CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {0}, PSX_ACCEPT) - CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {1}, PSX_UNDET) - CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {2}, PSX_REJECT) + CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {1, 2}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Event manual QC call"}, {0}, PSX_ACCEPT) CheckPSXEventField({psxEvent_1}, {"Event manual QC call"}, {1}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Event manual QC call"}, {2}, PSX_UNDET) From d5d8d9f887a01d6c18686d964f92ef7c4e3ef911 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 25 Mar 2025 21:53:12 +0100 Subject: [PATCH 08/57] PSX_GetGoodTau: Handle edge case When the calculated average standard deviation is NaN we can still get a tau of NaN. Use the fallback value in this case. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 42ea6405ad..59bca1e6c3 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -728,20 +728,26 @@ End // @brief Returns a good tau which does capture a lot of the tau events static Function PSX_GetGoodTauImpl(WAVE psxEvent) - variable numEvents, err, xVal, idx + variable numEvents, err, xVal, idx, tau idx = FindDimLabel(psxEvent, COLS, "tau") Duplicate/FREE/RMD=[][idx] psxEvent, tauWithNaN - WAVE/Z tau = ZapNaNs(tauWithNaN) + WAVE/Z taus = ZapNaNs(tauWithNaN) - if(!WaveExists(tau)) + if(!WaveExists(taus)) return PSX_DEFAULT_X_START_OFFSET endif - WaveStats/Q tau + WaveStats/Q taus - return V_avg + PSX_TAU_CALC_FACTOR * V_sdev + tau = V_avg + PSX_TAU_CALC_FACTOR * V_sdev + + if(IsFinite(tau)) + return tau + endif + + return PSX_DEFAULT_X_START_OFFSET End /// @brief Return the x-axis range useful for displaying and extracting a single event From f61bab03f665169d86c258e5a1e1b7b908aa7d17 Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Wed, 19 Mar 2025 14:28:29 -0700 Subject: [PATCH 09/57] MIES_SweepFormula_PSX.ipf: Tweak PSX_TAU_CALC_FACTOR --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 59bca1e6c3..98b1a90c3c 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -59,7 +59,7 @@ static Constant PSX_KEYBOARD_DIR_LR = 1 static Constant PSX_NUMBER_OF_SDS_DEFAULT = 2.5 -static Constant PSX_TAU_CALC_FACTOR = 2.5 +static Constant PSX_TAU_CALC_FACTOR = 1 static Constant PSX_BASELINE_RANGE_FACTOR = 10 static Constant PSX_FIT_RANGE_FACTOR = 10 static Constant PSX_FIT_RANGE_PERC = 0.9 From ce40b0ff75061267225c8479a734c40ec89169b3 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 7 Apr 2025 16:55:40 +0200 Subject: [PATCH 10/57] MIES_SweepFormula_PSX.ipf: Tweak peak and baseline calculation --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 31 ++++++++++++-------- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 8 ++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 98b1a90c3c..e87613e47c 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -564,15 +564,13 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ return [peakX, peakY] End -static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) +static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable baseline_t, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) - variable numCrossings, deconvPeak_t, prevDeconvPeak_t, nextDeconvPeak_t + variable numCrossings, prevDeconvPeak_t, nextDeconvPeak_t variable peak_start_search, peak_end_search numCrossings = DimSize(peakX, ROWS) - deconvPeak_t = peakX[index] - // lower bound if(index > 0) prevDeconvPeak_t = peakX[index - 1] @@ -580,7 +578,7 @@ static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE pea prevDeconvPeak_t = -Inf endif - peak_start_search = max(deconvPeak_t - PSX_PEAK_RANGE_FACTOR_LEFT * kernelRiseTau, prevDeconvPeak_t) + peak_start_search = max(baseline_t - PSX_PEAK_RANGE_FACTOR_LEFT * kernelRiseTau, prevDeconvPeak_t) // upper bound if(index < (numCrossings - 1)) @@ -589,7 +587,7 @@ static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE pea nextDeconvPeak_t = Inf endif - peak_end_search = min(deconvPeak_t + PSX_PEAK_RANGE_FACTOR_RIGHT * kernelDecayTau, nextDeconvPeak_t) + peak_end_search = min(baseline_t + PSX_PEAK_RANGE_FACTOR_RIGHT * kernelDecayTau, nextDeconvPeak_t) WaveStats/M=1/Q/R=(peak_start_search, peak_end_search) sweepDataOffFilt @@ -606,18 +604,20 @@ static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE pea return [peak_t, peak] End -static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t_prev, variable peak_t, variable kernelAmp, variable kernelRiseTau) +static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t_prev, variable deconvPeak_t, variable kernelAmp, variable kernelRiseTau) variable range + string str - WaveStats/M=1/Q/R=(max(peak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, peak_t_prev), peak_t) sweepDataOffFilt + WaveStats/M=1/Q/R=(max(deconvPeak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, peak_t_prev), deconvPeak_t) sweepDataOffFilt if(kernelAmp > 0) baseline_t = V_minloc elseif(kernelAmp < 0) baseline_t = V_maxloc else - FATAL_ERROR("Can't handle kernelAmp of zero") + sprintf str, "Can't handle kernelAmp of: %g\r", kernelAmp // could be Nan, Inf, or zero + FATAL_ERROR(str) endif range = PSX_BASELINE_NUM_POINTS_AVERAGE * DimDelta(sweepDataOffFilt, ROWS) @@ -629,8 +629,12 @@ End static Function [variable peak, variable peak_t, variable baseline, variable baseline_t, variable amplitude] PSX_CalculateEventProperties(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable peak_t_prev, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) - [peak_t, peak] = PSX_CalculateEventPeak(peakX, peakY, sweepDataOffFilt, kernelAmp, kernelRiseTau, kernelDecayTau, index) - [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t_prev, peak_t, kernelAmp, kernelRiseTau) + variable deconvPeak_t + + deconvPeak_t = peakX[index] + + [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t_prev, deconvPeak_t, kernelAmp, kernelRiseTau) + [peak_t, peak] = PSX_CalculateEventPeak(peakX, peakY, sweepDataOffFilt, baseline_t, kernelAmp, kernelRiseTau, kernelDecayTau, index) amplitude = peak - baseline @@ -776,6 +780,8 @@ static Function [variable first, variable last] PSX_GetSingleEventRange(WAVE psx last = min(first + offset, psxEvent[index + 1][%baseline_t]) endif + ASSERT(first <= last, "range must have first < last") + return [first, last] End @@ -789,6 +795,7 @@ static Function [variable start, variable stop] PSX_GetEventFitRange(WAVE sweepD start = psxEvent[eventIndex][%peak_t] maxLength = PSX_FIT_RANGE_FACTOR * JWN_GetNumberFromWaveNote(psxEvent, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") + ASSERT(isFinite(maxLength), "Failed to retrieve finite decay tau") if(eventIndex == (DimSize(psxEvent, ROWS) - 1)) calcLength = maxLength @@ -802,7 +809,7 @@ static Function [variable start, variable stop] PSX_GetEventFitRange(WAVE sweepD stop = min(start + calcLength, IndexToScale(sweepDataOffFilt, DimSize(sweepDataOffFilt, ROWS), ROWS)) - ASSERT(start < stop, "Invalid fit range calculation") + ASSERT(start <= stop, "Invalid fit range calculation") return [start, stop] End diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index 2e21ccd31f..fd405fd485 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8473,13 +8473,13 @@ End /// - 1/deconvPeak: event amplitude in deconvoluted data [y unit of data] /// - 2/deconvPeak_t: deconvolved peak time [ms] /// - 3/peak: Maximum (positive kernel amp sign) or minimum (negative kernel amp sign) in the range of -/// [deconvPeak_t – kernelRiseTau or devonvPeak_t of the previous event (whichever comes later), -/// deconvPeak_t + 0.33 * kernelDecayTau or deconvPeak_t of the next event (which ever comes first)] +/// [baseline_t – 5 * kernelRiseTau or devonvPeak_t of the previous event (whichever comes later), +/// baseline_t + 0.33 * kernelDecayTau or deconvPeak_t of the next event (which ever comes first)] /// in the filtered sweep wave /// - 4/peak_t: peak time /// - 5/baseline: Maximum (negative kernel amp sign) or minimum (positive kernel amp sign) in the range of -/// [peak_t – 10 * kernelRiseTau or peak_t of previous event (whichever comes later), -/// peak_t], averaged over +/- 5 points, in the filtered sweep wave +/// [deconvPeak_t – 10 * kernelRiseTau or peak_t of previous event (whichever comes later), +/// deconvPeak_t], averaged over +/- 5 points, in the filtered sweep wave /// - 6/baseline_t: baseline time /// - 7/amplitude: Relative amplitude: [3] - [5] /// - 8/iei: Time difference to previous event (inter event interval) [ms] From 58b8dc814f89aab8eff3b3fd986959955238145f Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 25 Mar 2025 21:57:24 +0100 Subject: [PATCH 11/57] PSX_CalculateRiseTime: Factor it out And rename the original function to PSX_CalculateRiseTimeWrapper. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 59 ++++++++++++++----------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index e87613e47c..2fd4d00048 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -702,10 +702,10 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffF riseTimeParams[%$"Differentiate Threshold"], \ p) - Multithread psxEvent[][%$"Rise Time"] = PSX_CalculateRiseTime(sweepDataOffFilt, psxEvent, kernelAmp, \ - riseTimeParams[%$"Lower Threshold"], \ - riseTimeParams[%$"Upper Threshold"], \ - p) + Multithread psxEvent[][%$"Rise Time"] = PSX_CalculateRiseTimeWrapper(sweepDataOffFilt, psxEvent, kernelAmp, \ + riseTimeParams[%$"Lower Threshold"], \ + riseTimeParams[%$"Upper Threshold"], \ + p) psxEvent[][%tau] = PSX_FitEventDecay(sweepDataOffFilt, psxEvent, maxTauFactor, eventFit, p) @@ -1739,24 +1739,39 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE s return output End -threadsafe static Function PSX_CalculateRiseTime(WAVE sweepDataOffFilt, WAVE psxEvent, variable kernelAmp, variable lowerThreshold, variable upperThreshold, variable index) +threadsafe static Function PSX_CalculateRiseTimeWrapper(WAVE sweepDataOffFilt, WAVE psxEvent, variable kernelAmp, variable lowerThreshold, variable upperThreshold, variable index) - variable dY, xStart, xEnd, yStart, yEnd, xlt, xupt, lowerLevel, upperLevel, riseTime - variable printDebug - string comboKey + variable error, xStart, xEnd, yStart, yEnd, riseTime + string comboKey // deconvPeak is defined in the deconvoluted wave, // so we can't use %deconvPeak as y-value xStart = psxEvent[index][%$"Onset Time"] + yStart = IsNaN(xStart) ? NaN : sweepDataOffFilt(xStart) - if(IsNaN(xStart)) - return NaN + xEnd = psxEvent[index][%peak_t] + yEnd = psxEvent[index][%peak] + + [riseTime, error] = PSX_CalculateRiseTime(sweepDataOffFilt, xStart, xEnd, yStart, yEnd, lowerThreshold, upperThreshold, kernelAmp) + +#ifdef DEBUGGING_ENABLED + if(error) + comboKey = JWN_GetStringFromWaveNote(psxEvent, PSX_EVENTS_COMBO_KEY_WAVE_NOTE) + + printf "comboKey: %s, x: [%g, %g], y: [%g, %g], index: %d, thresholds: [%g, %g], risetime: %g\r", comboKey, xStart, xEnd, yStart, yEnd, index, lowerThreshold, upperThreshold, risetime endif +#endif // DEBUGGING_ENABLED + + return riseTime +End - yStart = sweepDataOffFilt(xStart) +threadsafe static Function [variable riseTime, variable error] PSX_CalculateRiseTime(WAVE data, variable xStart, variable xEnd, variable yStart, variable yEnd, variable lowerThreshold, variable upperThreshold, variable kernelAmp) - xEnd = psxEvent[index][%peak_t] - yEnd = psxEvent[index][%peak] + variable dY, xlt, xupt, lowerLevel, upperLevel + + if(IsNaN(xStart)) + return [NaN, 0] + endif dY = abs(yStart - yEnd) @@ -1767,34 +1782,26 @@ threadsafe static Function PSX_CalculateRiseTime(WAVE sweepDataOffFilt, WAVE psx xlt = NaN xupt = NaN - FindLevel/R=(xStart, xEnd)/Q sweepDataOffFilt, lowerLevel + FindLevel/R=(xStart, xEnd)/Q data, lowerLevel if(!V_flag) xlt = V_levelX else - printDebug = 1 + error = 1 endif - FindLevel/R=(xStart, xEnd)/Q sweepDataOffFilt, upperLevel + FindLevel/R=(xStart, xEnd)/Q data, upperLevel if(!V_flag) xupt = V_levelX else - printDebug = 1 + error = 1 endif ASSERT_TS(kernelAmp != 0 && IsFinite(kernelAmp), "kernelAmp must be finite and not zero") riseTime = (xlt - xupt) * sign(kernelAmp) * (-1) -#ifdef DEBUGGING_ENABLED - if(printDebug) - comboKey = JWN_GetStringFromWaveNote(psxEvent, PSX_EVENTS_COMBO_KEY_WAVE_NOTE) - - printf "comboKey: %s, x: [%g, %g], y: [%g, %g], index: %d, dY: %g, thresholds: [%g, %g], levels: [%g, %g], risetime: %g, xlt: %g, xupt: %g\r", comboKey, xStart, xEnd, yStart, yEnd, index, dY, lowerThreshold, upperThreshold, lowerLevel, upperLevel, risetime, xlt, xupt - endif -#endif // DEBUGGING_ENABLED - - return riseTime + return [riseTime, error] End threadsafe static Function PSX_CalculateOnsetTime(WAVE sweepDataDiff, WAVE psxEvent, variable kernelAmp, variable diffThreshPerc, variable index) From 2ec4962ed1e6faec0dcb826bcb88fb4719419e01 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 26 Mar 2025 18:10:57 +0100 Subject: [PATCH 12/57] PSX_CalculateRiseTime: Return error when xStart is NaN --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 2fd4d00048..082923f77d 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -1770,7 +1770,7 @@ threadsafe static Function [variable riseTime, variable error] PSX_CalculateRise variable dY, xlt, xupt, lowerLevel, upperLevel if(IsNaN(xStart)) - return [NaN, 0] + return [NaN, 1] endif dY = abs(yStart - yEnd) From 96085d15c16facdb5e220ba76c827c4abd904fe0 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 7 Apr 2025 18:53:56 +0200 Subject: [PATCH 13/57] PSX_UpdateDisplayedFit: Remove yOffset This is always zero. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 082923f77d..7f0255a475 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2025,7 +2025,7 @@ End static Function PSX_UpdateOffsetInAllEventGraph(string win) string extAllGraph, specialEventPanel - variable i, numEvents, offsetMode, first, last, xOffset, yOffset + variable i, numEvents, offsetMode, first, last, xOffset extAllGraph = PSX_GetAllEventGraph(win) @@ -2062,26 +2062,22 @@ static Function PSX_UpdateOffsetInAllEventGraph(string win) [first, last] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, i) - Duplicate/FREE/R=(first, last) sweepDataOffFilt, singleEventRaw + Duplicate/O/R=(first, last) sweepDataOffFilt, singleEvent switch(offsetMode) case PSX_HORIZ_OFFSET_ONSET: xOffset = IsFinite(psxEvent[i][%$"Onset Time"]) ? (first - psxEvent[i][%$"Onset Time"]) : 0 - yOffset = 0 break case PSX_HORIZ_OFFSET_PEAK: xOffset = first - psxEvent[i][%peak_t] - yOffset = 0 break case PSX_HORIZ_OFFSET_SLEW: xOffset = first - psxEvent[i][%$"Slew Rate Time"] - yOffset = 0 break default: FATAL_ERROR("Invalid offset mode") endswitch - MultiThread singleEvent[] = singleEventRaw[p] - yOffset SetScale/P x, xOffset, DimDelta(singleEvent, ROWS), singleEvent endfor endfor From 6f46e5b919d2242c2d84e28f15555ece604b9df1 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 25 Mar 2025 21:39:04 +0100 Subject: [PATCH 14/57] PSX_FitAcceptAverage: Use different method for calculating start position for accepted average fit --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 69 ++++++++++++++++--- Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf | 16 +++-- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 7f0255a475..84377778a3 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -123,7 +123,7 @@ static StrConstant PSX_TUD_COMBO_KEY = "comboKey" static StrConstant PSX_TUD_COMBO_INDEX = "comboIndex" static StrConstant PSX_TUD_BLOCK_INDEX = "blockIndex" -static Constant PSX_GUI_SETTINGS_VERSION = 1 +static Constant PSX_GUI_SETTINGS_VERSION = 2 static StrConstant PSX_GUI_SETTINGS_PSX = "GuiSettingsPSX" @@ -2251,14 +2251,14 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, variable i, idx, numEvents, eventState, start, stop variable acceptIndex, rejectIndex, undetIndex, extractStartAbs, extractStopAbs, fitStartAbs - string extAllGraph, name + string extAllGraph, name, path extAllGraph = PSX_GetAllEventGraph(win) numEvents = DimSize(eventIndexFromTraces, ROWS) Make/WAVE/FREE/N=(numEvents) contAverageAll, contAverageAccept, contAverageReject, contAverageUndet - Make/FREE/D/N=(numEvents) eventStopTime, eventPeakTime + Make/FREE/D/N=(numEvents) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp for(i = 0; i < numEvents; i += 1) idx = str2num(eventIndexFromTraces[i]) @@ -2281,8 +2281,12 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, // single event waves are zeroed in x-direction to extractStartAbs [extractStartAbs, extractStopAbs] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, idx) eventStopTime[acceptIndex] = extractStopAbs - extractStartAbs + eventOnsetTime[acceptIndex] = psxEvent[idx][%$"Onset Time"] - extractStartAbs eventPeakTime[acceptIndex] = psxEvent[idx][%peak_t] - extractStartAbs + path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL + eventKernelAmp[acceptIndex] = JWN_GetNumberFromWaveNote(psxEvent, path + "/amp") + acceptIndex += 1 break case PSX_REJECT: @@ -2307,8 +2311,8 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, PSX_UpdateAverageWave(contAverageUndet, undetIndex, averageDFR, PSX_UNDET) PSX_UpdateAverageWave(contAverageAll, numEvents, averageDFR, PSX_ALL) - Redimension/N=(acceptIndex) eventStopTime - PSX_FitAcceptAverage(win, averageDFR, eventPeakTime, eventStopTime) + Redimension/N=(acceptIndex) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp + PSX_FitAcceptAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp) End /// @brief Helper function to update the average waves for the all event graph @@ -2332,10 +2336,12 @@ static Function/DF PSX_GetAverageFolder(string win) return PSX_GetWorkingFolder(win) End -static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventPeakTime, WAVE eventStopTime) +static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOnsetTime, WAVE eventPeakTime, WAVE eventStopTime, WAVE eventKernelAmp) string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc - variable err, numAveragePoints, start, stop, meanStopTime, meanPeakTime + variable err, numAveragePoints, start, stop, meanStopTime, meanOnsetTime, meanPeakTime, meanKernelAmp + variable xStart, xEnd, yStart, yEnd, lowerThreshold, upperThreshold + variable extrema, extrema_t, edge WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) @@ -2361,6 +2367,13 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventPea FastOp acceptedAverageFit = (NaN) CopyScales average, acceptedAverageFit + WAVE/Z eventOnsetTimeClean = ZapNaNs(eventOnsetTime) + if(WaveExists(eventOnsetTimeClean)) + meanOnsetTime = mean(eventOnsetTimeClean) + else + meanOnsetTime = Inf + endif + WAVE/Z eventStopTimeClean = ZapNaNs(eventStopTime) if(WaveExists(eventStopTimeClean)) meanStopTime = mean(eventStopTimeClean) @@ -2368,14 +2381,33 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventPea meanStopTime = Inf endif + WAVE/Z eventKernelAmpClean = ZapNaNs(eventKernelAmp) + ASSERT(WaveExists(eventKernelAmpClean), "Could not find any events with finite kernelAmp") + meanKernelAmp = mean(eventKernelAmpClean) + WAVE/Z eventPeakTimeClean = ZapNaNs(eventPeakTime) - if(WaveExists(eventPeakTimeClean)) - meanPeakTime = mean(eventPeakTime) + ASSERT(WaveExists(eventPeakTimeClean), "Could not find any events with finite peak_t") + meanPeakTime = mean(eventPeakTime) + + WaveStats/M=1/Q average + + if(meanKernelAmp > 0) + extrema = V_max + extrema_t = V_maxLoc + edge = FINDLEVEL_EDGE_INCREASING + elseif(meanKernelAmp < 0) + extrema = V_min + extrema_t = V_minLoc + edge = FINDLEVEL_EDGE_DECREASING else - FATAL_ERROR("Could not find any events with finite peak_t") + FATAL_ERROR("Invalid kernel amp") endif - start = 0.1 * meanPeakTime + lowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE + + FindLevel/EDGE=(edge)/Q average, (lowerThreshold * extrema) + + start = V_LevelX stop = min(IndexToScale(average, DimSize(average, ROWS) - 1, ROWS), meanStopTime) AssertOnAndClearRTError() @@ -3586,6 +3618,7 @@ static Function PSX_StoreGuiState(string win, string browser) JSON_SetVariable(jsonID, "/specialEventPanel/popup_block", GetPopupMenuIndex(specialEventPanel, "popup_block")) JSON_SetVariable(jsonID, "/specialEventPanel/setvar_event_block_size", GetSetVariable(specialEventPanel, "setvar_event_block_size")) + JSON_SetVariable(jsonID, "/specialEventPanel/setvar_fit_start_amplitude", GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude")) mainWindow = GetMainWindow(win) JSON_SetVariable(jsonID, "/mainPanel/checkbox_suppress_update", GetCheckBoxState(mainWindow, "checkbox_suppress_update")) @@ -5608,6 +5641,19 @@ Function PSX_CheckboxProcFitAcceptAverage(STRUCT WMCheckboxAction &cba) : Checkb endswitch End +Function PSX_FitStartAmplitude(STRUCT WMSetVariableAction &sva) : SetVariableControl + + switch(sva.eventCode) + case 1: // fallthrough, mouse up + case 2: // fallthrough, Enter key + case 3: // Live update + PSX_UpdateAllEventGraph(sva.win, forceSingleEventUpdate = 1, forceAverageUpdate = 1) + break + default: + break + endswitch +End + Function PSX_PopupMenuBlockNumber(STRUCT WMPopupAction &cba) : PopupMenuControl switch(cba.eventCode) @@ -5719,6 +5765,7 @@ Function PSX_PlotStartupSettings() PopupMenu popupmenu_accept_fit_function, mode=1, win=$specialEventPanel SetVariable setvar_event_block_size, value=_NUM:100, win=$specialEventPanel PopupMenu popup_block, mode=1, value="", win=$specialEventPanel, userdata($PSX_UD_NUM_BLOCKS)="1" + SetVariable setvar_fit_start_amplitude, value=_NUM:20, win=$specialEventPanel StoreCurrentPanelsResizeInfo(win) diff --git a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf index 6cc0127eb3..fb0da2d404 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf @@ -8,7 +8,7 @@ Window PSXPanel() : Panel PauseUpdate; Silent 1 // building window... - NewPanel/K=1/W=(1251, 700, 2391, 1532) as "SweepFormula plot from " + NewPanel/K=1/W=(100, 633, 1354, 1113) as "SweepFormula plot from " SetDrawLayer UserBack SetDrawEnv pop DrawText 47, 475, "UI" @@ -56,16 +56,16 @@ Window PSXPanel() : Panel ListBox listbox_select_combo, userdata(ResizeControlsInfo)=A"!!,B9!!#@ Date: Mon, 7 Apr 2025 19:09:44 +0200 Subject: [PATCH 15/57] MIES_SweepFormula_PSX.ipf: Revise event peak and baseline calculation --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 86 +++++++++++++------- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 4 +- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 84377778a3..19925add23 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -60,7 +60,7 @@ static Constant PSX_KEYBOARD_DIR_LR = 1 static Constant PSX_NUMBER_OF_SDS_DEFAULT = 2.5 static Constant PSX_TAU_CALC_FACTOR = 1 -static Constant PSX_BASELINE_RANGE_FACTOR = 10 +static Constant PSX_BASELINE_RANGE_FACTOR = 5 static Constant PSX_FIT_RANGE_FACTOR = 10 static Constant PSX_FIT_RANGE_PERC = 0.9 static Constant PSX_BASELINE_NUM_POINTS_AVERAGE = 5 @@ -166,7 +166,7 @@ static Function PSX_GetFindPeakBoxSize() return 10 #endif // AUTOMATED_TESTING - return 100 + return 10 End static Function/S PSX_GetUserDataForWorkingFolder() @@ -564,21 +564,13 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ return [peakX, peakY] End -static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable baseline_t, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) - - variable numCrossings, prevDeconvPeak_t, nextDeconvPeak_t - variable peak_start_search, peak_end_search +static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable baseline_t, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index, variable peak_t_prev, variable prevDeconvPeak_t) - numCrossings = DimSize(peakX, ROWS) - - // lower bound - if(index > 0) - prevDeconvPeak_t = peakX[index - 1] - else - prevDeconvPeak_t = -Inf - endif + variable numCrossings, nextDeconvPeak_t + variable peak_start_search, peak_end_search, threshold, range - peak_start_search = max(baseline_t - PSX_PEAK_RANGE_FACTOR_LEFT * kernelRiseTau, prevDeconvPeak_t) + numCrossings = DimSize(peakX, ROWS) + peak_end_search = max(peakX[index] - PSX_PEAK_RANGE_FACTOR_LEFT * kernelRiseTau, baseline_t, prevDeconvPeak_t, peak_t_prev) // upper bound if(index < (numCrossings - 1)) @@ -587,36 +579,63 @@ static Function [variable peak_t, variable peak] PSX_CalculateEventPeak(WAVE pea nextDeconvPeak_t = Inf endif - peak_end_search = min(baseline_t + PSX_PEAK_RANGE_FACTOR_RIGHT * kernelDecayTau, nextDeconvPeak_t) + peak_start_search = min(peakX[index] + PSX_PEAK_RANGE_FACTOR_RIGHT * kernelDecayTau, nextDeconvPeak_t) WaveStats/M=1/Q/R=(peak_start_search, peak_end_search) sweepDataOffFilt - + variable boxSize = 10 + variable ampFactor = 0.25 if(kernelAmp > 0) - peak = V_max - peak_t = V_maxloc + threshold = sweepDataOffFilt(baseline_t) + ampFactor * (V_max - sweepDataOffFilt(baseline_t)) + FindPeak/B=(boxSize)/M=(threshold)/Q/R=(peak_start_search, peak_end_search) sweepDataOffFilt + + if(!V_flag) + peak_t = V_PeakLoc + peak = V_PeakVal + else + range = 2 * DimDelta(sweepDataOffFilt, ROWS) + WaveStats/M=1/Q/R=(peakX[index] - range, peakX[index] + range) sweepDataOffFilt + peak_t = peakX[index] + peak = V_avg + endif elseif(kernelAmp < 0) - peak = V_min - peak_t = V_minloc + threshold = sweepDataOffFilt(baseline_t) + ampFactor * (V_min - sweepDataOffFilt(baseline_t)) + FindPeak/B=(boxSize)/N/M=(threshold)/Q/R=(peak_start_search, peak_end_search) sweepDataOffFilt + + if(!V_flag) + peak_t = V_PeakLoc + peak = V_PeakVal + else + range = 2 * DimDelta(sweepDataOffFilt, ROWS) + WaveStats/M=1/Q/R=(peakX[index] - range, peakX[index] + range) sweepDataOffFilt + peak_t = peakX[index] + peak = V_avg + endif else FATAL_ERROR("Can't handle kernelAmp of zero") endif + ASSERT(IsFinite(peak_t), "peak_t is not finite") + ASSERT(IsFinite(peak), "peak is not finite") + return [peak_t, peak] End -static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t_prev, variable deconvPeak_t, variable kernelAmp, variable kernelRiseTau) +static Function [variable baseline_t, variable baseline] PSX_CalculateEventBaseline(WAVE sweepDataOffFilt, variable peak_t_prev, variable deconvPeak_t, variable kernelAmp, variable kernelRiseTau, variable prevDeconvPeak_t) + + variable range, start + string str - variable range - string str + start = max(deconvPeak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, min(peak_t_prev, prevDeconvPeak_t)) - WaveStats/M=1/Q/R=(max(deconvPeak_t - PSX_BASELINE_RANGE_FACTOR * kernelRiseTau, peak_t_prev), deconvPeak_t) sweepDataOffFilt + // the end of the search window for the baseline time is the deconvolved peak time of the current event + WaveStats/M=1/Q/R=(start, deconvPeak_t) sweepDataOffFilt if(kernelAmp > 0) baseline_t = V_minloc elseif(kernelAmp < 0) baseline_t = V_maxloc else - sprintf str, "Can't handle kernelAmp of: %g\r", kernelAmp // could be Nan, Inf, or zero + sprintf str, "Can't handle kernelAmp of: %g\r", kernelAmp FATAL_ERROR(str) endif @@ -624,17 +643,24 @@ static Function [variable baseline_t, variable baseline] PSX_CalculateEventBasel WaveStats/M=1/Q/R=(baseline_t - range, baseline_t + range) sweepDataOffFilt baseline = V_avg + ASSERT(isFinite(baseline), "basline is not finite") + return [baseline_t, baseline] End static Function [variable peak, variable peak_t, variable baseline, variable baseline_t, variable amplitude] PSX_CalculateEventProperties(WAVE peakX, WAVE peakY, WAVE sweepDataOffFilt, variable peak_t_prev, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, variable index) - variable deconvPeak_t + variable deconvPeak_t, prevdeconvPeak_t deconvPeak_t = peakX[index] + if(index == 0) + prevdeconvPeak_t = 0 + else + prevdeconvPeak_t = peakX[index - 1] + endif - [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t_prev, deconvPeak_t, kernelAmp, kernelRiseTau) - [peak_t, peak] = PSX_CalculateEventPeak(peakX, peakY, sweepDataOffFilt, baseline_t, kernelAmp, kernelRiseTau, kernelDecayTau, index) + [baseline_t, baseline] = PSX_CalculateEventBaseline(sweepDataOffFilt, peak_t_prev, deconvPeak_t, kernelAmp, kernelRiseTau, prevdeconvPeak_t) + [peak_t, peak] = PSX_CalculateEventPeak(peakX, peakY, sweepDataOffFilt, baseline_t, kernelAmp, kernelRiseTau, kernelDecayTau, index, peak_t_prev, prevdeconvPeak_t) amplitude = peak - baseline @@ -645,7 +671,7 @@ End static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffFiltDeconv, WAVE sweepDataOffFilt, WAVE sweepData, WAVE/Z peakXUnfiltered, WAVE/Z peakYUnfiltered, variable maxTauFactor, variable kernelAmp, variable kernelRiseTau, variable kernelDecayTau, WAVE riseTimeParams, WAVE psxEvent, WAVE eventFit) variable i, numCrossings, deconvPeak, deconvPeak_t, peak, peak_t, baseline, baseline_t, amplitude, iei - variable peak_t_prev = -Inf + variable peak_t_prev = -Inf // sets the previous peak time to the start of the range // we need to first throw away events with invalid amplitude so that // we can then calculate the distance to the neighbour in peakX[i + 1] below diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index fd405fd485..268f665df5 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8473,12 +8473,12 @@ End /// - 1/deconvPeak: event amplitude in deconvoluted data [y unit of data] /// - 2/deconvPeak_t: deconvolved peak time [ms] /// - 3/peak: Maximum (positive kernel amp sign) or minimum (negative kernel amp sign) in the range of -/// [baseline_t – 5 * kernelRiseTau or devonvPeak_t of the previous event (whichever comes later), +/// [baseline_t – 5 * kernelRiseTau or baseline_t or devonvPeak_t/peak_t of the previous event (whichever comes later), /// baseline_t + 0.33 * kernelDecayTau or deconvPeak_t of the next event (which ever comes first)] /// in the filtered sweep wave /// - 4/peak_t: peak time /// - 5/baseline: Maximum (negative kernel amp sign) or minimum (positive kernel amp sign) in the range of -/// [deconvPeak_t – 10 * kernelRiseTau or peak_t of previous event (whichever comes later), +/// [deconvPeak_t – 10 * kernelRiseTau or the minimum of peak_t and deconvPeak_t of previous event (whichever comes later), /// deconvPeak_t], averaged over +/- 5 points, in the filtered sweep wave /// - 6/baseline_t: baseline time /// - 7/amplitude: Relative amplitude: [3] - [5] From 5ecaa09c6d625b11dc1a1ed44d909a8b82142e78 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 7 Apr 2025 19:10:42 +0200 Subject: [PATCH 16/57] PSX_GetEventFitRange/PSX_FitEventDecay: Revise them --- Packages/MIES/MIES_Constants.ipf | 4 +++- Packages/MIES/MIES_SweepFormula_PSX.ipf | 27 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 4b8990d79b..597013054d 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2409,7 +2409,9 @@ Constant PSX_MARKER_UNDET = 18 /// @name Custom error codes for PSX_FitEventDecay() /// @anchor FitEventDecayCustomErrors ///@{ -Constant PSX_DECAY_FIT_ERROR = -10000 +Constant PSX_DECAY_FIT_ERROR = -10000 +Constant PSX_DECAY_FIT_INVALID_RANGE_ERROR = -10001 + ///@} StrConstant PSX_STATS_LABELS = "Average;Median;Average Deviation;Standard deviation;Skewness;Kurtosis" diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 19925add23..1e38c1414f 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -816,26 +816,25 @@ End /// x-zero is taken from sweepData static Function [variable start, variable stop] PSX_GetEventFitRange(WAVE sweepDataOffFilt, WAVE psxEvent, variable eventIndex) - variable calcLength, maxLength + variable calcLength, maxLength, decayTau start = psxEvent[eventIndex][%peak_t] - maxLength = PSX_FIT_RANGE_FACTOR * JWN_GetNumberFromWaveNote(psxEvent, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") + decayTau = JWN_GetNumberFromWaveNote(psxEvent, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") + maxLength = PSX_FIT_RANGE_FACTOR * decayTau ASSERT(isFinite(maxLength), "Failed to retrieve finite decay tau") if(eventIndex == (DimSize(psxEvent, ROWS) - 1)) - calcLength = maxLength + stop = start + maxLength else - calcLength = min((psxEvent[eventIndex + 1][%peak_t] - start) * PSX_FIT_RANGE_PERC, maxLength) + stop = min(start + 5 * decayTau, psxEvent[eventIndex + 1][%baseline_t]) // min(psxEvent[eventIndex + 1][%peak_t] * PSX_FIT_RANGE_PERC, psxEvent[eventIndex + 1][%baseline_t]) endif - if(calcLength <= 0) - calcLength = maxLength - endif - - stop = min(start + calcLength, IndexToScale(sweepDataOffFilt, DimSize(sweepDataOffFilt, ROWS), ROWS)) + stop = min(stop, IndexToScale(sweepDataOffFilt, DimSize(sweepDataOffFilt, ROWS), ROWS)) - ASSERT(start <= stop, "Invalid fit range calculation") + if(start > stop) + return [NaN, NaN] + endif return [start, stop] End @@ -855,6 +854,12 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable [startTime, endTime] = PSX_GetEventFitRange(sweepDataOffFilt, psxEvent, eventIndex) + if(IsNaN(startTime) && IsNaN(endTime)) + psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_REJECT + psxEvent[eventIndex][%$"Fit result"] = PSX_DECAY_FIT_INVALID_RANGE_ERROR + return NaN + endif + DFREF currDFR = GetDataFolderDFR() SetDataFolder NewFreeDataFolder() @@ -1936,6 +1941,8 @@ Function/S PSX_FitResultToString(variable fitResult) return UpperCaseFirstChar(GetErrMessage(abs(fitResult))) elseif(fitResult == PSX_DECAY_FIT_ERROR) return "Too large tau" + elseif(fitResult == PSX_DECAY_FIT_INVALID_RANGE_ERROR) + return "Invalid fit range" endif BUG("Unknown fitResult") From 9e11faf45012634079ca771afa2ca1c3fd556f5a Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Wed, 2 Apr 2025 08:38:51 -0700 Subject: [PATCH 17/57] PSX_FitEventDecay: Switch from single exponential to double exponential fit For the individual event decay fit. Weighted tau is computed for each event. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 1e38c1414f..0ba9f1a3e1 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -866,16 +866,16 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable // require a converging exponential Make/FREE/T constraints = {"K2 > 0"} - Make/FREE/D/N=3 coefWave + Make/FREE/D/N=5 coefWave AssertOnAndClearRTError() - CurveFit/Q/N=1/NTHR=1/M=0/W=2 exp_XOffset, kwCWave=coefWave, sweepDataOffFilt(startTime, endTime)/D/C=constraints; err = GetRTError(1) + CurveFit/Q/N=1/NTHR=1/M=0/W=2 dblexp_XOffset, kwCWave=coefWave, sweepDataOffFilt(startTime, endTime)/D/C=constraints; err = GetRTError(1) WAVE fit = MakeWaveFree($"fit__free_") SetDataFolder currDFR - - decayTau = coefWave[2] + // weighted tau computed from double exponential fit + decayTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) #ifdef AUTOMATED_TESTING WAVE/Z overrideResults = GetOverrideResults() From c5daab80f9d562f9324000fe8ab648895da325ca Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Wed, 2 Apr 2025 14:36:36 -0700 Subject: [PATCH 18/57] PSX_FitAcceptAverage: Multiple simultaneous fits to the accepted all events average wave --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 143 ++++++++++++------ Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf | 3 - Packages/MIES/MIES_WaveDataFolderGetters.ipf | 26 ++++ 3 files changed, 123 insertions(+), 49 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 0ba9f1a3e1..c79aedacfe 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2373,10 +2373,12 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc variable err, numAveragePoints, start, stop, meanStopTime, meanOnsetTime, meanPeakTime, meanKernelAmp - variable xStart, xEnd, yStart, yEnd, lowerThreshold, upperThreshold - variable extrema, extrema_t, edge + variable xStart, xEnd, yStart, yEnd, riselowerThreshold, riseAndDecayUpperThreshold + variable extrema, extrema_t, edge, riseStart, riseStop, decayStart, decayStop, wTau, backwardEdge - WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) + WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) + WAVE acceptedRiseAverageFit = GetPSXAcceptedRiseAverageFitWaveFromDFR(averageDFR) + WAVE acceptedDecayAverageFit = GetPSXAcceptedDecayAverageFitWaveFromDFR(averageDFR) specialEventPanel = PSX_GetSpecialPanel(win) @@ -2385,6 +2387,8 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns if(!GetCheckBoxState(specialEventPanel, "checkbox_average_events_fit")) FastOp acceptedAverageFit = (NaN) + FastOp acceptedRiseAverageFit = (NaN) + FastOp acceptedDecayAverageFit = (NaN) return NaN endif @@ -2393,12 +2397,16 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns if(numAveragePoints == 0 || !HasOneValidEntry(average)) FastOp acceptedAverageFit = (NaN) + FastOp acceptedRiseAverageFit = (NaN) + FastOp acceptedDecayAverageFit = (NaN) return NaN endif - Redimension/N=(numAveragePoints) acceptedAverageFit + Redimension/N=(numAveragePoints) acceptedAverageFit, acceptedRiseAverageFit, acceptedDecayAverageFit FastOp acceptedAverageFit = (NaN) - CopyScales average, acceptedAverageFit + FastOp acceptedRiseAverageFit = (NaN) + FastOp acceptedDecayAverageFit = (NaN) + CopyScales average, acceptedAverageFit, acceptedRiseAverageFit, acceptedDecayAverageFit WAVE/Z eventOnsetTimeClean = ZapNaNs(eventOnsetTime) if(WaveExists(eventOnsetTimeClean)) @@ -2425,56 +2433,83 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns WaveStats/M=1/Q average if(meanKernelAmp > 0) - extrema = V_max - extrema_t = V_maxLoc - edge = FINDLEVEL_EDGE_INCREASING + extrema = V_max + extrema_t = V_maxLoc + edge = FINDLEVEL_EDGE_INCREASING + backwardEdge = FINDLEVEL_EDGE_DECREASING elseif(meanKernelAmp < 0) - extrema = V_min - extrema_t = V_minLoc - edge = FINDLEVEL_EDGE_DECREASING + extrema = V_min + extrema_t = V_minLoc + edge = FINDLEVEL_EDGE_DECREASING + backwardEdge = FINDLEVEL_EDGE_INCREASING else FATAL_ERROR("Invalid kernel amp") endif - lowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE + riselowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE + riseAndDecayUpperThreshold = 0.9 + FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) - FindLevel/EDGE=(edge)/Q average, (lowerThreshold * extrema) + riseStart = V_LevelX + FindLevel/EDGE=(edge)/Q average, (riseAndDecayUpperThreshold * extrema) + riseStop = V_levelX - start = V_LevelX - stop = min(IndexToScale(average, DimSize(average, ROWS) - 1, ROWS), meanStopTime) + FindLevel/EDGE=(backwardEdge)/R=(Inf, 0)/Q average, (riseAndDecayUpperThreshold * extrema) + decayStart = V_levelX + + decayStop = min(IndexToScale(average, DimSize(average, ROWS) - 1, ROWS), meanStopTime) AssertOnAndClearRTError() - fitFunc = GetPopupMenuString(specialEventPanel, "popupmenu_accept_fit_function") - strswitch(fitFunc) - case "dblexp_peak": - Make/FREE/D/N=5 coefWave - Make/FREE/T coeffNames = {"y0", "A", "tau1", "tau2", "X0"} - CurveFit/M=0/Q/N=2 dblexp_peak, kwCWave=coefWave, average(start, stop)/D=acceptedAverageFit; err = GetRTError(1) - break - case "dblexp_XOffset": - Make/FREE/D/N=5 coefWave - Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} - CurveFit/M=0/Q/N=2 dblexp_XOffset, kwCWave=coefWave, average(start, stop)/D=acceptedAverageFit; err = GetRTError(1) - break - default: - FATAL_ERROR("Unknown fit function") - endswitch - sprintf msg, "Fit in the range [%g, %g] finished with %d (%s)\r", start, stop, err, GetErrMessage(err) + Make/FREE/T/N=(10, 2) InputAvg, InputRise, InputDecay + + Make/FREE/D/N=5 coefWave + Make/FREE/T coeffNames = {"y0", "A", "tau1", "tau2", "X0"} + CurveFit/M=0/Q/N=2 dblexp_peak, kwCWave=coefWave, average(riseStart, decayStop)/D=acceptedAverageFit; err = GetRTError(1) + + InputAvg[0][0, 1] = {{"Function"}, {"dblexp_peak"}} + InputAvg[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} + InputAvg[2, 6][0] = coeffNames[p - 2] + InputAvg[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputAvg[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} + InputAvg[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + + Make/FREE/D/N=5 coefWave + Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} + CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(riseStart, riseStop)/D=acceptedRiseAverageFit; err = GetRTError(1) + + wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) + + InputRise[0][0, 1] = {{"Function"}, {"dblexp_XOffset rise"}} + InputRise[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} + InputRise[2, 6][0] = coeffNames[p - 2] + InputRise[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputRise[7][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} + InputRise[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} + InputRise[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + + Make/FREE/D/N=5 coefWave + Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} + CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(decayStart, decayStop)/D=acceptedDecayAverageFit; err = GetRTError(1) + + wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) + + InputDecay[0][0, 1] = {{"Function"}, {"dblexp_XOffset decay"}} + InputDecay[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} + InputDecay[2, 6][0] = coeffNames[p - 2] + InputDecay[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputDecay[7][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} + InputDecay[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} + InputDecay[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + + sprintf msg, "Fit in the range [%g, %g] finished with %d (%s)\r", riseStart, decayStop, err, GetErrMessage(err) DEBUGPRINT(msg) if(err) return NaN endif - Make/FREE/T/N=(9, 2) input - - input[0][0, 1] = {{"Function"}, {fitFunc}} - input[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} - input[2, 6][0] = coeffNames[p - 2] - input[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - input[7][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} - input[8][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + Concatenate/NP/FREE {InputAvg, InputRise, InputDecay}, input UpdateInfoButtonHelp(specialEventPanel, "button_fit_results", input) @@ -2852,7 +2887,7 @@ End static Function PSX_AppendAverageTraces(string extAllGraph, DFREF averageDFR, string traceSuffix, variable idx, string comboKey, variable comboIndex, WAVE traceUserDataKeys, WAVE states, WAVE acceptColors, WAVE rejectColors, WAVE undetColors) variable state - string trace + string trace, traceAvgFit, traceRiseAvgFit, traceDecayAvgFit for(state : states) @@ -2874,13 +2909,30 @@ static Function PSX_AppendAverageTraces(string extAllGraph, DFREF averageDFR, st idx += 1 endfor - WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) + WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) + WAVE acceptedRiseAverageFit = GetPSXAcceptedRiseAverageFitWaveFromDFR(averageDFR) + WAVE acceptedDecayAverageFit = GetPSXAcceptedDecayAverageFitWaveFromDFR(averageDFR) + + traceAvgFit = PSX_GetAverageTraceName(idx, "acceptAverageFit", comboIndex, traceSuffix) + idx += 1 + + traceRiseAvgFit = PSX_GetAverageTraceName(idx, "acceptRiseAverageFit", comboIndex, traceSuffix) + idx += 1 + + traceDecayAvgFit = PSX_GetAverageTraceName(idx, "acceptDecayAverageFit", comboIndex, traceSuffix) + idx += 1 - trace = PSX_GetAverageTraceName(idx, "acceptAverageFit", comboIndex, traceSuffix) - idx += 1 + AppendToGraph/W=$extAllGraph acceptedAverageFit/TN=$traceAvgFit, acceptedRiseAverageFit/TN=$traceRiseAvgFit, acceptedDecayAverageFit/TN=$traceDecayAvgFit + + TUD_SetUserDataFromWaves(extAllGraph, traceAvgFit, \ + traceUserDataKeys, \ + {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + + TUD_SetUserDataFromWaves(extAllGraph, traceRiseAvgFit, \ + traceUserDataKeys, \ + {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) - AppendToGraph/W=$extAllGraph acceptedAverageFit/TN=$trace - TUD_SetUserDataFromWaves(extAllGraph, trace, \ + TUD_SetUserDataFromWaves(extAllGraph, traceDecayAvgFit, \ traceUserDataKeys, \ {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) @@ -5795,7 +5847,6 @@ Function PSX_PlotStartupSettings() CheckBox checkbox_average_events_fit, value=0, win=$specialEventPanel CheckBox checkbox_restrict_events_to_current_combination, value=0, win=$specialEventPanel - PopupMenu popupmenu_accept_fit_function, mode=1, win=$specialEventPanel SetVariable setvar_event_block_size, value=_NUM:100, win=$specialEventPanel PopupMenu popup_block, mode=1, value="", win=$specialEventPanel, userdata($PSX_UD_NUM_BLOCKS)="1" SetVariable setvar_fit_start_amplitude, value=_NUM:20, win=$specialEventPanel diff --git a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf index fb0da2d404..30996f1ed6 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf @@ -104,9 +104,6 @@ Window PSXPanel() : Panel PopupMenu popupmenu_state_type, pos={113.00, 143.00}, size={80.00, 19.00}, proc=PSX_PopupMenuState PopupMenu popupmenu_state_type, help={"Select which state is used for plotting. Can be either \"event\" or \"fit\" state."} PopupMenu popupmenu_state_type, mode=1, popvalue="Event State", value=#"PSX_GetEventStateNames()" - PopupMenu popupmenu_accept_fit_function, pos={12.00, 167.00}, size={115.00, 19.00}, bodyWidth=115, proc=PSX_PopupFitAcceptAverageFunc - PopupMenu popupmenu_accept_fit_function, help={"Select which fit function to use for accepted average fitting. Can be one of \"dblexp_peak\" or \"dblexp_XOffset\"."} - PopupMenu popupmenu_accept_fit_function, mode=1, popvalue="dblexp_peak", value=#"PSX_GetAverageFitAcceptNames()" CheckBox checkbox_average_events_fit, pos={104.00, 111.00}, size={29.00, 15.00}, proc=PSX_CheckboxProcFitAcceptAverage CheckBox checkbox_average_events_fit, title="Fit" CheckBox checkbox_average_events_fit, help={"Fit the accept average with a double exponential and store the outcome in the results wave"} diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index 268f665df5..e574f35d41 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8692,6 +8692,32 @@ Function/WAVE GetPSXAverageWave(DFREF dfr, variable state) return wv End +Function/WAVE GetPSXAcceptedRiseAverageFitWaveFromDFR(DFREF dfr) + + WAVE/Z/D/SDFR=dfr wv = acceptedRiseAverageFit + + if(WaveExists(wv)) + return wv + endif + + Make/D/N=(0) dfr:acceptedRiseAverageFit/WAVE=wv + + return wv +End + +Function/WAVE GetPSXAcceptedDecayAverageFitWaveFromDFR(DFREF dfr) + + WAVE/Z/D/SDFR=dfr wv = acceptedDecayAverageFit + + if(WaveExists(wv)) + return wv + endif + + Make/D/N=(0) dfr:acceptedDecayAverageFit/WAVE=wv + + return wv +End + Function/WAVE GetPSXAcceptedAverageFitWaveFromDFR(DFREF dfr) WAVE/Z/D/SDFR=dfr wv = acceptedAverageFit From be9d0574391654866b25e947e2a26f5d1aad7b6e Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 7 Apr 2025 19:31:16 +0200 Subject: [PATCH 19/57] PSX_FilterEventsKernelAmpSign: Ignore events with non-finite deconv values --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index c79aedacfe..048dc96a9b 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -548,11 +548,13 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_FilterEventsKernelAmpSign(WAVE/ continue endif - peakX[idx] = peakXUnfiltered[i] - peakY[idx] = peakYUnfiltered[i] + if(isFinite(peakXUnfiltered[i]) && isFinite(peakYUnfiltered[i])) + peakX[idx] = peakXUnfiltered[i] + peakY[idx] = peakYUnfiltered[i] - idx += 1 - peak_t_prev = peak_t + idx += 1 + peak_t_prev = peak_t + endif endfor if(idx == 0) From 5739a7e7faf390d1ce318069dc8720ee7708a88b Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 7 Apr 2025 19:24:08 +0200 Subject: [PATCH 20/57] PSX: Add more vertical lines for peak_t and baseline_t --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 178 +++++++++++++++--- Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf | 43 +++-- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 35 +++- 3 files changed, 211 insertions(+), 45 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 048dc96a9b..e73baf571e 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -123,7 +123,7 @@ static StrConstant PSX_TUD_COMBO_KEY = "comboKey" static StrConstant PSX_TUD_COMBO_INDEX = "comboIndex" static StrConstant PSX_TUD_BLOCK_INDEX = "blockIndex" -static Constant PSX_GUI_SETTINGS_VERSION = 2 +static Constant PSX_GUI_SETTINGS_VERSION = 3 static StrConstant PSX_GUI_SETTINGS_PSX = "GuiSettingsPSX" @@ -152,6 +152,15 @@ static Constant PSX_CACHE_KEY_ANALYZE_PEAKS = 0x3 static Constant EVENT_INDEX_HORIZONTAL = 0x1 static Constant EVENT_INDEX_VERTICAL = 0x2 +static StrConstant PSX_FREE_AXIS_DECONV = "freeDeconvAxis" +static StrConstant PSX_COLORS_DECONV = "65535,16385,36045" + +static StrConstant PSX_FREE_AXIS_PEAK = "freePeakAxis" +static StrConstant PSX_COLORS_PEAK = "65535,43690,0" + +static StrConstant PSX_FREE_AXIS_BASELINE = "freeBaselineAxis" +static StrConstant PSX_COLORS_BASELINE = "0,0,0" + Menu "GraphMarquee" "PSX: Accept Event && Fit", /Q, PSX_MouseEventSelection(PSX_ACCEPT, PSX_STATE_EVENT | PSX_STATE_FIT) "PSX: Reject Event && Fit", /Q, PSX_MouseEventSelection(PSX_REJECT, PSX_STATE_EVENT | PSX_STATE_FIT) @@ -3708,7 +3717,13 @@ static Function PSX_StoreGuiState(string win, string browser) JSON_SetVariable(jsonID, "/specialEventPanel/setvar_fit_start_amplitude", GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude")) mainWindow = GetMainWindow(win) - JSON_SetVariable(jsonID, "/mainPanel/checkbox_suppress_update", GetCheckBoxState(mainWindow, "checkbox_suppress_update")) + + WAVE/T checkboxes = PSX_GetSpecialEventPanelCheckboxes(mainWindow) + + for(ctrl : checkboxes) + JSON_SetVariable(jsonID, "/mainPanel/" + ctrl, GetCheckBoxState(mainWindow, ctrl)) + endfor + JSON_SetVariable(jsonID, "/mainPanel/listbox_select_combo", GetListBoxSelRow(mainWindow, "listbox_select_combo")) WAVE axesProps = GetAxesProperties(extAllGraph) @@ -3762,7 +3777,11 @@ static Function PSX_RestoreGuiState(string win) endfor mainWindow = GetMainWindow(win) - SetCheckBoxState(mainWindow, "checkbox_suppress_update", JSON_GetVariable(jsonID, "/mainPanel/checkbox_suppress_update")) + WAVE/T controls = PSX_GetSpecialEventPanelCheckboxes(mainWindow) + + for(ctrl : controls) + SetCheckBoxState(mainWindow, ctrl, JSON_GetVariable(jsonID, "/mainPanel/" + ctrl)) + endfor lastActiveCombo = JSON_GetVariable(jsonID, "/mainPanel/listbox_select_combo") @@ -3914,12 +3933,26 @@ static Function PSX_MoveWavesToDataFolders(DFREF workDFR, WAVE/Z/WAVE results, v Duplicate peakY, dfr:peakYAtFilt/WAVE=peakYAtFilt peakYAtFilt[] = sweepDataOffFilt(peakX[p]) - Make/T/N=(numEvents, 2) dfr:eventLocationLabels/WAVE=eventLocationLabels - SetDimLabel COLS, 1, $"Tick Type", eventLocationLabels - eventLocationLabels[][1] = "Major" + WAVE eventLabelsDeconv = GetPSXEventLabelsAsFree(numEvents, PSX_FREE_AXIS_DECONV) + MoveWave eventLabelsDeconv, dfr + + WAVE eventTicksDeconv = GetPSXEventTicksAsFree(numEvents, PSX_FREE_AXIS_DECONV) + MoveWave eventTicksDeconv, dfr + eventTicksDeconv[] = psxEvent[p][%deconvPeak_t] + + WAVE eventLabelsPeak = GetPSXEventLabelsAsFree(numEvents, PSX_FREE_AXIS_PEAK) + MoveWave eventLabelsPeak, dfr - Make/D/N=(numEvents) dfr:eventLocationTicks/WAVE=eventLocationTicks - eventLocationTicks[] = peakX[p] + WAVE eventTicksPeak = GetPSXEventTicksAsFree(numEvents, PSX_FREE_AXIS_PEAK) + MoveWave eventTicksPeak, dfr + eventTicksPeak[] = psxEvent[p][%peak_t] + + WAVE eventLabelsBaseline = GetPSXEventLabelsAsFree(numEvents, PSX_FREE_AXIS_BASELINE) + MoveWave eventLabelsBaseline, dfr + + WAVE eventTicksBaseline = GetPSXEventTicksAsFree(numEvents, PSX_FREE_AXIS_BASELINE) + MoveWave eventTicksBaseline, dfr + eventTicksBaseline[] = psxEvent[p][%baseline_t] PSX_CreateSingleEventWaves(dfr, psxEvent, sweepDataOffFilt) @@ -4066,19 +4099,12 @@ static Function PSX_CreatePSXGraphAndSubwindows(string win, string graph, WAVE/T PSX_MarkGraphForPSX(win) - WAVE eventLocationLabels = GetPSXEventLocationLabels(comboDFR) - WAVE eventLocationTicks = GetPSXEventLocationTicks(comboDFR) - WAVE eventColors = GetPSXEventColorsWaveFromDFR(comboDFR) - WAVE eventMarker = GetPSXEventMarkerWaveFromDFR(comboDFR) + WAVE eventColors = GetPSXEventColorsWaveFromDFR(comboDFR) + WAVE eventMarker = GetPSXEventMarkerWaveFromDFR(comboDFR) - NewFreeAxis/W=$win/O/T eventLocAxis - ModifyFreeAxis/W=$win/Z eventLocAxis, master=bottom - ModifyGraph/W=$win grid(eventLocAxis)=1 - ModifyGraph/W=$win tick(eventLocAxis)=3 - ModifyGraph/W=$win lblPos(eventLocAxis)=43 - ModifyGraph/W=$win noLabel(eventLocAxis)=2 - ModifyGraph/W=$win freePos(eventLocAxis)={0, kwFraction} - Label/W=$win eventLocAxis, "\\u#2" + PSX_AppendVisualizationAxis(win, PSX_FREE_AXIS_DECONV) + PSX_AppendVisualizationAxis(win, PSX_FREE_AXIS_PEAK) + PSX_AppendVisualizationAxis(win, PSX_FREE_AXIS_BASELINE) ModifyGraph/W=$win zColor(peakYAtFilt)={eventColors, *, *, directRGB, 0} ModifyGraph/W=$win mode(peakYAtFilt)=3 @@ -4109,6 +4135,47 @@ static Function PSX_CreatePSXGraphAndSubwindows(string win, string graph, WAVE/T PS_InitCoordinates(JSONid, mainWin, recursive = 1) End +static Function/WAVE PSX_GetColorsForFreeAxis(string axis) + + string list + + strswitch(axis) + case PSX_FREE_AXIS_BASELINE: + list = PSX_COLORS_BASELINE + break + case PSX_FREE_AXIS_DECONV: + list = PSX_COLORS_DECONV + break + case PSX_FREE_AXIS_PEAK: + list = PSX_COLORS_PEAK + break + default: + FATAL_ERROR("Invalid axis type") + endswitch + + WAVE wv = ListToNumericWave(list, ",") + + return wv +End + +/// @brief Appends a free axis overlayed with the bottom axis name +static Function PSX_AppendVisualizationAxis(string win, string axis) + + NewFreeAxis/W=$win/O/T $axis + ModifyFreeAxis/W=$win/Z $axis, master=bottom + ModifyGraph/W=$win grid($axis)=1 + ModifyGraph/W=$win tick($axis)=3 + ModifyGraph/W=$win lblPos($axis)=43 + ModifyGraph/W=$win noLabel($axis)=2 + ModifyGraph/W=$win freePos($axis)={0, kwFraction} + ModifyGraph/W=$win nticks($axis)=0 + + WAVE RGBcolors = PSX_GetColorsForFreeAxis(axis) + ModifyGraph/W=$win gridRGB($axis)=(RGBcolors[0], RGBcolors[1], RGBcolors[2]) + + Label/W=$win $axis, "\\u#2" +End + /// @brief Mark `win` as being an psx graph static Function PSX_MarkGraphForPSX(string win) @@ -4116,9 +4183,33 @@ static Function PSX_MarkGraphForPSX(string win) End /// @brief Apply plot properties which have to be reapplied on every combo index change -static Function PSX_ApplySpecialPlotProperties(string win, WAVE eventLocationTicks, WAVE eventLocationLabels) +static Function PSX_ApplySpecialPlotProperties(string win) + + DFREF comboDFR = PSX_GetCurrentComboFolder(win) - ModifyGraph/W=$win userticks(eventLocAxis)={eventLocationTicks, eventLocationLabels} + PSX_ApplySpecialPlotPropertiesImpl(win, comboDFR, PSX_FREE_AXIS_DECONV, "checkbox_show_deconv_lines") + PSX_ApplySpecialPlotPropertiesImpl(win, comboDFR, PSX_FREE_AXIS_BASELINE, "checkbox_show_baseline_lines") + PSX_ApplySpecialPlotPropertiesImpl(win, comboDFR, PSX_FREE_AXIS_PEAK, "checkbox_show_peak_lines") +End + +static Function PSX_ApplySpecialPlotPropertiesImpl(string win, DFREF comboDFR, string name, string checkBoxControl) + + variable enabled + string panel, psxGraph + + panel = GetMainWindow(win) + psxGraph = PSX_GetPSXGraph(win) + + enabled = GetCheckboxState(panel, checkBoxControl) + + WAVE eventLabels = GetPSXEventLabelsFromDFR(comboDFR, name) + WAVE eventTicks = GetPSXEventTicksFromDFR(comboDFR, name) + + if(enabled) + ModifyGraph/W=$psxGraph userticks($name)={eventTicks, eventLabels} + else + ModifyGraph/W=$psxGraph userticks($name)=0, nticks($name)=0 + endif End /// @brief Read the user JWN from results and create a legend from all operation parameters @@ -4729,11 +4820,25 @@ Function PSX_PostPlot(string win) PSX_UpdateAllEventGraph(win, forceAverageUpdate = 1, forceSingleEventUpdate = 1, forceBlockIndexUpdate = 1, forceSingleEventOffsetUpdate = 1) - DFREF comboDFR = PSX_GetCurrentComboFolder(win) - WAVE eventLocationLabels = GetPSXEventLocationLabels(comboDFR) - WAVE eventLocationTicks = GetPSXEventLocationTicks(comboDFR) + PSX_SetCheckboxTitleColors(win) - PSX_ApplySpecialPlotProperties(win, eventLocationTicks, eventLocationLabels) + PSX_ApplySpecialPlotProperties(win) +End + +static Function PSX_SetCheckboxTitleColors(string win) + + string str, mainWindow + + mainWindow = GetMainWindow(win) + + sprintf str, "Show Deconv \\[0\\K(%s)lines\\]0", PSX_COLORS_DECONV + SetControlTitle(mainWindow, "checkbox_show_deconv_lines", str) + + sprintf str, "Show Peak \\[0\\K(%s)lines\\]0", PSX_COLORS_PEAK + SetControlTitle(mainWindow, "checkbox_show_peak_lines", str) + + sprintf str, "Show Baseline \\[0\\K(%s)lines\\]0", PSX_COLORS_BASELINE + SetControlTitle(mainWindow, "checkbox_show_baseline_lines", str) End static Function PSX_OperationSetDimensionLabels(WAVE/WAVE output, variable numCombos, WAVE/T labels, WAVE/T labelsTemplate) @@ -5497,10 +5602,7 @@ static Function PSX_SetCombo(string win, variable comboIndex) ReplaceWave/W=$extSingleGraph allinCDF SetDataFolder currentDFR - WAVE eventLocationTicks = GetPSXEventLocationTicks(comboDFR) - WAVE eventLocationLabels = GetPSXEventLocationLabels(comboDFR) - - PSX_ApplySpecialPlotProperties(psxGraph, eventLocationTicks, eventLocationLabels) + PSX_ApplySpecialPlotProperties(psxGraph) if(PSX_GetRestrictEventsToCurrentCombo(win)) PSX_UpdateAllEventGraph(win, forceAverageUpdate = 1, forceBlockIndexUpdate = 1) @@ -5832,6 +5934,9 @@ Function PSX_PlotStartupSettings() endfor // default GUI values + CheckBox checkbox_show_deconv_lines, value=1, win=$win + CheckBox checkbox_show_peak_lines, value=0, win=$win + CheckBox checkbox_show_baseline_lines, value=0, win=$win CheckBox checkbox_suppress_update, value=0, win=$win ListBox listbox_select_combo, win=$win, listWave=$"", selWave=$"", helpWave=$"", selRow=0 @@ -5911,3 +6016,16 @@ static Function PSX_ApplyMacroToExistingPanel(string win, string mac) KillVariables/Z V_flag KillStrings/Z S_name End + +Function PSX_UpdateVisualizationHelpers(STRUCT WMCheckboxAction &cba) : CheckBoxControl + + switch(cba.eventCode) + case 2: // mouse up + PSX_ApplySpecialPlotProperties(cba.win) + break + default: + break + endswitch + + return 0 +End diff --git a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf index 30996f1ed6..b1d27dc620 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf @@ -8,7 +8,7 @@ Window PSXPanel() : Panel PauseUpdate; Silent 1 // building window... - NewPanel/K=1/W=(100, 633, 1354, 1113) as "SweepFormula plot from " + NewPanel/K=1/W=(196, 1011, 1450, 1491) as "SweepFormula plot from " SetDrawLayer UserBack SetDrawEnv pop DrawText 47, 475, "UI" @@ -32,13 +32,13 @@ Window PSXPanel() : Panel Button button_jump_first_undet, userdata(ResizeControlsInfo)=A"!!,EN!!#=c!!#>V!!#- none -"} Button button_psx_info, userdata="- none -" @@ -51,12 +51,33 @@ Window PSXPanel() : Panel GroupBox group_UI, userdata(ResizeControlsInfo)+=A"zzzzzzzzzzzz!!#u:Du]k!!#BMJ,fQL!!#](Aon\"Qzzzzzzzzzzzzzz!!#](Aon\"Qzz" ListBox listbox_select_combo, userdata(ResizeControlsInfo)+=A"zzzzzzzzzzzz!!#u:Du]k Date: Wed, 2 Apr 2025 23:15:19 +0200 Subject: [PATCH 21/57] PSX_FitEventDecay: Support three tau instead of one --- Packages/MIES/MIES_Cache.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 79 ++++++++++++++------ Packages/MIES/MIES_WaveDataFolderGetters.ipf | 42 ++++++----- 3 files changed, 79 insertions(+), 44 deletions(-) diff --git a/Packages/MIES/MIES_Cache.ipf b/Packages/MIES/MIES_Cache.ipf index bda268526f..5d6447b840 100644 --- a/Packages/MIES/MIES_Cache.ipf +++ b/Packages/MIES/MIES_Cache.ipf @@ -499,7 +499,7 @@ End /// @param psxParameters JSON dump of the psx/psxKernel operation parameters Function/S CA_PSXEventsKey(string comboKey, string psxParameters) - return CA_PSXBaseKey(comboKey, psxParameters) + " Events " + ":Version 2" + return CA_PSXBaseKey(comboKey, psxParameters) + " Events " + ":Version 3" End Function/S CA_PSXOperationKey(string comboKey, string psxParameters) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index e73baf571e..41401e6b23 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -744,7 +744,8 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffF riseTimeParams[%$"Upper Threshold"], \ p) - psxEvent[][%tau] = PSX_FitEventDecay(sweepDataOffFilt, psxEvent, maxTauFactor, eventFit, p) + Make/FREE/N=(DimSize(psxEvent, ROWS)) indexHelper + indexHelper[] = PSX_FitEventDecay(sweepDataOffFilt, psxEvent, maxTauFactor, eventFit, p) return [peakX, peakY] End @@ -855,12 +856,12 @@ End /// /// \rst /// -/// exp_XOffset: :math:`y = K0 + K1 \cdot exp(-(x - x0)/K2)` +/// dblexp_XOffset: :math:`y = K0 + K1 \cdot exp(-(x - x0) / K2) + K3 * exp(-(x - x0) / K4)` /// /// \endrst static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable maxTauFactor, WAVE/WAVE eventFit, variable eventIndex) - variable startTime, endTime, err, decayTau, fitRange, overrideTau + variable startTime, endTime, err, weightedTau, slowTau, fastTau, fitRange, overrideTau string comboKey [startTime, endTime] = PSX_GetEventFitRange(sweepDataOffFilt, psxEvent, eventIndex) @@ -868,6 +869,10 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable if(IsNaN(startTime) && IsNaN(endTime)) psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_REJECT psxEvent[eventIndex][%$"Fit result"] = PSX_DECAY_FIT_INVALID_RANGE_ERROR + psxEvent[eventIndex][%weightedTau] = NaN + psxEvent[eventIndex][%slowTau] = NaN + psxEvent[eventIndex][%fastTau] = NaN + return NaN endif @@ -886,7 +891,8 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable SetDataFolder currDFR // weighted tau computed from double exponential fit - decayTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) + [fastTau, slowTau] = MinMax(coefWave[2], coefWave[4]) + weightedTau = ((coefWave[1] * slowTau + coefWave[3] * fastTau) / (coefWave[1] + coefWave[3])) #ifdef AUTOMATED_TESTING WAVE/Z overrideResults = GetOverrideResults() @@ -900,7 +906,7 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable overrideTau = overrideResults[eventIndex][%$comboKey][%Tau] if(!IsNaN(overrideTau)) - decayTau = overrideTau + weightedTau = overrideTau endif endif #endif // AUTOMATED_TESTING @@ -908,14 +914,22 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable if(err) psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_REJECT psxEvent[eventIndex][%$"Fit result"] = -err + psxEvent[eventIndex][%weightedTau] = NaN + psxEvent[eventIndex][%slowTau] = NaN + psxEvent[eventIndex][%fastTau] = NaN + return NaN endif fitRange = endTime - startTime - if(IsFinite(decayTau) && decayTau > (maxTauFactor * fitRange)) + if((IsFinite(weightedTau) && weightedTau > (maxTauFactor * fitRange)) || fastTau <= 0 || SlowTau <= 0 || weightedTau <= 0) psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_REJECT psxEvent[eventIndex][%$"Fit result"] = PSX_DECAY_FIT_ERROR + psxEvent[eventIndex][%weightedTau] = NaN + psxEvent[eventIndex][%slowTau] = NaN + psxEvent[eventIndex][%fastTau] = NaN + return NaN endif @@ -925,8 +939,9 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable eventFit[eventIndex] = fit psxEvent[eventIndex][%$"Fit result"] = 1 psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_UNDET - - return decayTau + psxEvent[eventIndex][%weightedTau] = weightedTau + psxEvent[eventIndex][%slowTau] = slowTau + psxEvent[eventIndex][%fastTau] = fastTau End /// @brief Create the override results 2D wave @@ -1216,8 +1231,14 @@ static Function [WAVE/D results, WAVE eventIndex, WAVE marker, WAVE/T comboKeys] case "xinterval": propLabel = "iei" break - case "tau": - propLabel = "tau" + case "slowtau": + propLabel = "slowTau" + break + case "fasttau": + propLabel = "fastTau" + break + case "weightedtau": + propLabel = "weightedTau" break case "estate": propLabel = "Event manual QC call" @@ -1262,7 +1283,9 @@ static Function [WAVE/D results, WAVE eventIndex, WAVE marker, WAVE/T comboKeys] stateType = "Event manual QC call" break case "Fit result": // fallthrough - case "tau": // fallthrough + case "slowtau": // fallthrough + case "fasttau": // fallthrough + case "weightedtau": // fallthrough case "Fit manual QC call": stateType = "Fit manual QC call" break @@ -1556,8 +1579,14 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE s case "xinterval": propLabelAxis = "Event interval" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" break - case "tau": - propLabelAxis = "Decay tau" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" + case "slowtau": + propLabelAxis = "Slow tau" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" + break + case "fasttau": + propLabelAxis = "Fast tau" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" + break + case "weightedtau": + propLabelAxis = "Weighted tau" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" break case "estate": propLabelAxis = "Event manual QC" + " (enum)" @@ -3001,9 +3030,9 @@ static Function PSX_UpdateSingleEventTextbox(string win, [variable eventIndex]) DFREF comboDFR = PSX_GetCurrentComboFolder(win) WAVE psxEvent = GetPSXEventWaveFromDFR(comboDFR) - Make/FREE/T/N=(8, 2) input + Make/FREE/T/N=(10, 2) input - input[0][0] = {"Event State:", "Fit State:", "Fit Result:", "Event:", "Deconv Peak:", "Peak:", "Baseline:", "IeI:", "Amp (rel.):", "Tau:", "Onset time:", "Rise time:"} + input[0][0] = {"Event State:", "Fit State:", "Fit Result:", "Event:", "Deconv Peak:", "Peak:", "Baseline:", "IeI:", "Amp (rel.):", "Slow tau:", "Fast tau:", "Weighted tau:", "Onset time:", "Rise time:"} input[0][1] = {PSX_StateToString(psxEvent[eventIndex][%$"Event manual QC call"]), \ PSX_StateToString(psxEvent[eventIndex][%$"Fit manual QC call"]), \ PSX_FitResultToString(psxEvent[eventIndex][%$"Fit Result"]), \ @@ -3013,7 +3042,9 @@ static Function PSX_UpdateSingleEventTextbox(string win, [variable eventIndex]) num2str(psxEvent[eventIndex][%baseline_t], "%8.02f") + " [ms]", \ num2str(psxEvent[eventIndex][%iei], "%8.02f") + " [ms]", \ num2str(psxEvent[eventIndex][%amplitude], "%8.02f") + " [" + yUnit + "]", \ - num2str(psxEvent[eventIndex][%tau], "%8.02f") + " [ms]", \ + num2str(psxEvent[eventIndex][%slowTau], "%8.02f") + " [ms]", \ + num2str(psxEvent[eventIndex][%fastTau], "%8.02f") + " [ms]", \ + num2str(psxEvent[eventIndex][%weightedTau], "%8.02f") + " [ms]", \ num2str(psxEvent[eventIndex][%$"Onset Time"], "%8.02f") + " [ms]", \ num2str(psxEvent[eventIndex][%$"Rise Time"], "%8.02f") + " [ms]"} @@ -5165,14 +5196,14 @@ End static Function/WAVE PSX_GetAllStatsProperties() - Make/FREE/T allProps = {"amp", \ - "peak", "peaktime", \ - "deconvpeak", "deconvpeaktime", \ - "baseline", "baselinetime", \ - "xinterval", \ - "tau", \ - "estate", "fstate", "fitresult", \ - "slewrate", "slewratetime", \ + Make/FREE/T allProps = {"amp", \ + "peak", "peaktime", \ + "deconvpeak", "deconvpeaktime", \ + "baseline", "baselinetime", \ + "xinterval", \ + "slowtau", "fasttau", "weightedtau", \ + "estate", "fstate", "fitresult", \ + "slewrate", "slewratetime", \ "risetime", "onsettime"} return allProps diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index f3414bc8cf..2b7d2955ae 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8437,15 +8437,15 @@ End /// @name SweepFormula PSX ///@{ -static Constant PSX_WAVE_VERSION = 3 -static Constant PSX_EVENT_WAVE_COLUMNS = 17 +static Constant PSX_WAVE_VERSION = 4 +static Constant PSX_EVENT_WAVE_COLUMNS = 19 /// @brief Return the upgraded psxEvent wave Function/WAVE UpgradePSXEventWave(WAVE psxEvent) if(WaveVersionIsAtLeast(psxEvent, PSX_WAVE_VERSION)) return psxEvent - elseif(WaveVersionIsAtLeast(psxEvent, 2)) + elseif(WaveVersionIsAtLeast(psxEvent, 2)) // Version 2 and 3 if(!AlreadyCalledOnce(CO_PSX_UPGRADE_EVENT)) print "The algorithm for psp/psc event detection was heavily overhauled, therefore we are very sorry " \ @@ -8483,16 +8483,18 @@ End /// - 6/baseline_t: baseline time /// - 7/amplitude: Relative amplitude: [3] - [5] /// - 8/iei: Time difference to previous event (inter event interval) [ms] -/// - 9/tau: Decay constant tau of exponential fit -/// - 10/Fit manual QC call: One of @ref PSXStates -/// - 11/Fit result: 1 for success, everything smaller than 0 is failure: +/// - 9/weightedTau: Weighted tau of the double exponential fit +/// - 11/slowTau: Slow tau of the double exponential fit +/// - 10/fastTau: Fast tau of the double exponential fit +/// - 12/Fit manual QC call: One of @ref PSXStates +/// - 13/Fit result: 1 for success, everything smaller than 0 is failure: /// - `]-10000, 0[`: CurveFit error codes /// - `]-inf, -10000]`: Custom error codes, one of @ref FitEventDecayCustomErrors -/// - 12/Event manual QC call: One of @ref PSXStates -/// - 13/Onset time as calculated by PSX_CalculateOnsetTime -/// - 14/Rise Time as calculated by PSX_CalculateRiseTime -/// - 15/Slew Rate -/// - 16/Slew Rate Time +/// - 14/Event manual QC call: One of @ref PSXStates +/// - 15/Onset time as calculated by PSX_CalculateOnsetTime +/// - 16/Rise Time as calculated by PSX_CalculateRiseTime +/// - 17/Slew Rate +/// - 18/Slew Rate Time Function/WAVE GetPSXEventWaveAsFree() variable versionOfWave = PSX_WAVE_VERSION @@ -8517,14 +8519,16 @@ static Function SetPSXEventDimensionLabels(WAVE wv) SetDimLabel COLS, 6, baseline_t, wv SetDimLabel COLS, 7, amplitude, wv SetDimLabel COLS, 8, iei, wv - SetDimLabel COLS, 9, tau, wv - SetDimLabel COLS, 10, $"Fit manual QC call", wv - SetDimLabel COLS, 11, $"Fit result", wv - SetDimLabel COLS, 12, $"Event manual QC call", wv - SetDimLabel COLS, 13, $"Onset Time", wv - SetDimLabel COLS, 14, $"Rise Time", wv - SetDimLabel COLS, 15, $"Slew Rate", wv - SetDimLabel COLS, 16, $"Slew Rate Time", wv + SetDimLabel COLS, 9, weightedTau, wv + SetDimLabel COLS, 10, slowTau, wv + SetDimLabel COLS, 11, fastTau, wv + SetDimLabel COLS, 12, $"Fit manual QC call", wv + SetDimLabel COLS, 13, $"Fit result", wv + SetDimLabel COLS, 14, $"Event manual QC call", wv + SetDimLabel COLS, 15, $"Onset Time", wv + SetDimLabel COLS, 16, $"Rise Time", wv + SetDimLabel COLS, 17, $"Slew Rate", wv + SetDimLabel COLS, 18, $"Slew Rate Time", wv End Function/WAVE GetPSXSingleEventFitWaveFromDFR(DFREF dfr) From 3e6dda9551facd38cd70ae7df89a6b8ecf829f63 Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Tue, 22 Apr 2025 18:27:11 -0700 Subject: [PATCH 22/57] BandPassWithRingingDetection: Add it New MiES algorithmic utility for band pass filtering with Nan, Inf, and ringing rejection and automatic order reduction --- .../MIES/MIES_MiesUtilities_Algorithm.ipf | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf index 8ad2263139..24ff3a62f2 100644 --- a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf +++ b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf @@ -641,3 +641,86 @@ threadsafe Function/WAVE FindIndizes(WAVE numericOrTextWave, [variable col, stri return result End + +// @brief Band‑pass filters a wave while automatically reducing IIR filter order until the output contains no NaNs/Infs and its SEM is not larger than the original (simple ringing detection). + +// ----------------------------------------------------------------------------- +// BandPassWithRingingDetection(src, fHigh, fLow, maxOrder) +// ----------------------------------------------------------------------------- +// @param src – input wave (is **not** modified; a filtered copy called _BPF is produced) +// @param fHigh/fLow – pass‑band edge frequencies in Hz (Igor’s band‑pass requires fLow > fHigh; the routine swaps them if needed) +// @param maxOrder – starting (maximum) IIR filter order to try (>0) +// +// Logic: iteratively lowers the filter order until three conditions are met: +// 1. FilterIIR executes without error. +// 2. WaveStats reports V_numNaNs = 0 and V_numInfs = 0. +// 3. SEM(filtered) ≤ SEM(original). +// +// Return value: filter order that finally succeeded (0 if every order failed). +// ----------------------------------------------------------------------------- +Function [variable curOrder, WAVE filtered] bandpass_with_RingingDetection(WAVE src, variable fHigh, variable fLow, variable maxOrder) + + // ---- parameter sanity --------------------------------------------------- + ASSERT(maxOrder > 0, "maxOrder must be positive") + if(fLow <= fHigh) // Igor band‑pass expects fLow > fHigh + variable tmp = fLow + fLow = fHigh + fHigh = tmp + endif + + // Sampling rate (Hz) – assumes X scaling is in milliseconds + variable samp = 1 / (DeltaX(src) * MILLI_TO_ONE) + + // Pre‑compute SEM(original) once + WaveStats/Q src + variable semOrig = V_sem + variable offset = v_avg + + // Prepare destination wave (same name every call → convenient overwrite) + duplicate/FREE src, src_BPF + WAVE/Z filtered = src_BPF + + // remove offset from src copy + filtered -= offset + + curOrder = maxOrder + variable err + do + // -------- copy fresh data into filtered ------------------------------ + filtered = src // avoids repeated duplicate/O allocations + + // -------- attempt current order -------------------------------------- + FilterIIR/LO=(fLow / samp)/HI=(fHigh / samp)/DIM=(ROWS)/ORD=(curOrder) filtered + err = GetRTError(1) + if(err) + Print "FilterIIR failed (order=" + num2str(curOrder) + "): " + GetErrMessage(err) + curOrder -= 1 + continue + endif + + // -------- WaveStats: NaN/Inf + SEM in one call ------------------------ + WaveStats/Q filtered + if(V_numNaNs > 0 || V_numInfs > 0) + curOrder -= 1 + continue // bad numerical output → lower order + endif + + if(V_sem > semOrig) // noisier than original → ringing + curOrder -= 1 + continue + endif + + // -------- success ----------------------------------------------------- + + break + while(curOrder > 0) + + if(curOrder <= 0) + Print "bandpass_with_RingingDetection(): all orders down to 1 produced NaNs/Infs or increased SEM." + endif + + // add offset back to filtered wave + filtered += offset + + return [curOrder, filtered] +End From 9cc0fed2dfc01fb1723b45ed466647ab601d2145 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 9 May 2025 20:11:14 +0200 Subject: [PATCH 23/57] psxDeconvFilter: Rework it Rename it to psxDeconvBPFilter to make it clear that this is a bandpass filter, see also PSX_DeconvoluteSweepData. And also reorder the filter frequencies for the user to avoid an assertion. --- Packages/MIES/MIES_Constants.ipf | 12 +++---- Packages/MIES/MIES_SweepFormula.ipf | 2 +- Packages/MIES/MIES_SweepFormula_Executor.ipf | 4 +-- Packages/MIES/MIES_SweepFormula_PSX.ipf | 31 ++++++++++++------- Packages/doc/SweepFormula.rst | 18 +++++------ Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 26 +++++++++------- 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 597013054d..376a88fd64 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2375,12 +2375,12 @@ Constant SECONDS_PER_DAY = 86400 StrConstant DB_AXIS_PART_EPOCHS = "_EP" ///@} -StrConstant SF_OP_PSX = "psx" -StrConstant SF_OP_PSX_KERNEL = "psxKernel" -StrConstant SF_OP_PSX_STATS = "psxStats" -StrConstant SF_OP_PSX_RISETIME = "psxRiseTime" -StrConstant SF_OP_PSX_PREP = "psxPrep" -StrConstant SF_OP_PSX_DECONV_FILTER = "psxDeconvFilter" +StrConstant SF_OP_PSX = "psx" +StrConstant SF_OP_PSX_KERNEL = "psxKernel" +StrConstant SF_OP_PSX_STATS = "psxStats" +StrConstant SF_OP_PSX_RISETIME = "psxRiseTime" +StrConstant SF_OP_PSX_PREP = "psxPrep" +StrConstant SF_OP_PSX_DECONV_BP_FILTER = "psxDeconvBPFilter" /// @name Available PSX states /// @anchor PSXStates diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index f03c706930..64995533f4 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -92,7 +92,7 @@ Function/WAVE SF_GetNamedOperations() SF_OP_LOG10, SF_OP_APFREQUENCY, SF_OP_CURSORS, SF_OP_SELECTSWEEPS, SF_OP_AREA, SF_OP_SETSCALE, SF_OP_BUTTERWORTH, \ SF_OP_SELECTCHANNELS, SF_OP_DATA, SF_OP_LABNOTEBOOK, SF_OP_WAVE, SF_OP_FINDLEVEL, SF_OP_EPOCHS, SF_OP_TP, \ SF_OP_STORE, SF_OP_SELECT, SF_OP_POWERSPECTRUM, SF_OP_TPSS, SF_OP_TPBASE, SF_OP_TPINST, SF_OP_TPFIT, \ - SF_OP_PSX, SF_OP_PSX_KERNEL, SF_OP_PSX_STATS, SF_OP_PSX_RISETIME, SF_OP_PSX_PREP, SF_OP_PSX_DECONV_FILTER, \ + SF_OP_PSX, SF_OP_PSX_KERNEL, SF_OP_PSX_STATS, SF_OP_PSX_RISETIME, SF_OP_PSX_PREP, SF_OP_PSX_DECONV_BP_FILTER, \ SF_OP_MERGE, SF_OP_FIT, SF_OP_FITLINE, SF_OP_DATASET, SF_OP_SELECTVIS, SF_OP_SELECTCM, SF_OP_SELECTSTIMSET, \ SF_OP_SELECTIVSCCSWEEPQC, SF_OP_SELECTIVSCCSETQC, SF_OP_SELECTRANGE, SF_OP_SELECTEXP, SF_OP_SELECTDEV, \ SF_OP_SELECTEXPANDSCI, SF_OP_SELECTEXPANDRAC, SF_OP_SELECTSETCYCLECOUNT, SF_OP_SELECTSETSWEEPCOUNT, \ diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index e6ba507602..9fc3a45652 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -454,8 +454,8 @@ Function/WAVE SFE_FormulaExecutor(STRUCT SF_ExecutionData &exd, [variable srcLoc case SF_OP_PSX_PREP: WAVE out = PSX_OperationPrep(exdop) break - case SF_OP_PSX_DECONV_FILTER: - WAVE out = PSX_OperationDeconvFilter(exdop) + case SF_OP_PSX_DECONV_BP_FILTER: + WAVE out = PSX_OperationDeconvBPFilter(exdop) break case SF_OP_MERGE: WAVE out = SFO_OperationMerge(exdop) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 41401e6b23..c4a2d0224e 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -4949,7 +4949,7 @@ End // Output[1] = sweepDataOffFilt(1) // ... // -// psx(id, [psxKernel(...), numSDs, sweepFilterLow, sweepFilterHigh, maxTauFactor, psxRiseTime(...), psxDeconvFilter(...)]) +// psx(id, [psxKernel(...), numSDs, sweepFilterLow, sweepFilterHigh, maxTauFactor, psxRiseTime(...), psxDeconvBPFilter(...)]) Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) variable numberOfSDs, sweepFilterLow, sweepFilterHigh, parameterJsonID, numCombos, i, addedData, kernelAmp @@ -4969,7 +4969,7 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) maxTauFactor = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 5, defValue = PSX_DEFAULT_MAX_TAU_FACTOR, checkFunc = IsStrictlyPositiveAndFinite) WAVE riseTime = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxRiseTime()", expectedMinorType = IGOR_TYPE_64BIT_FLOAT, singleResult = 1) ASSERT(IsNumericWave(riseTime), "Invalid return from psxRiseTime") - WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 7, defOp = "psxDeconvFilter()", singleResult = 1) + WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 7, defOp = "psxDeconvBPFilter()", singleResult = 1) parameterJsonID = JWN_GetWaveNoteAsJSON(psxKernelDataset) parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX @@ -4984,7 +4984,7 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) JSON_AddVariable(parameterJsonID, parameterPath + "/upperThreshold", riseTime[%$"Upper Threshold"]) JSON_AddVariable(parameterJsonID, parameterPath + "/lowerThreshold", riseTime[%$"Lower Threshold"]) JSON_AddVariable(parameterJsonID, parameterPath + "/differentiateThreshold", riseTime[%$"Differentiate Threshold"]) - parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_FILTER + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_BP_FILTER JSON_AddTreeObject(parameterJsonID, parameterPath) JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", deconvFilter[%$"Filter Low"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", deconvFilter[%$"Filter High"]) @@ -5173,25 +5173,32 @@ Function/WAVE PSX_OperationRiseTime(STRUCT SF_ExecutionData &exd) return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX_RISETIME) End -// psxDeconvFilter([low, high, order]) -Function/WAVE PSX_OperationDeconvFilter(STRUCT SF_ExecutionData &exd) +// psxDeconvBPFilter([low, high, order]) +Function/WAVE PSX_OperationDeconvBPFilter(STRUCT SF_ExecutionData &exd) - variable low, high, order + variable low, high, first, second, order - SFH_CheckArgumentCount(exd, SF_OP_PSX_DECONV_FILTER, 0, maxArgs = 3) + SFH_CheckArgumentCount(exd, SF_OP_PSX_DECONV_BP_FILTER, 0, maxArgs = 3) - low = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_FILTER, 0, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) - high = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_FILTER, 1, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) - order = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_FILTER, 2, defValue = NaN, checkFunc = IsStrictlyPositiveAndFinite, checkDefault = 0) + first = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_BP_FILTER, 0, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) + second = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_BP_FILTER, 1, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) + order = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_DECONV_BP_FILTER, 2, defValue = NaN, checkFunc = IsStrictlyPositiveAndFinite, checkDefault = 0) + + if(!IsNaN(first) && !IsNaN(second)) + [high, low] = MinMax(first, second) + else + low = first + high = second + endif Make/D/FREE params = {low, high, order} SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) - WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX_DECONV_FILTER, 1) + WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX_DECONV_BP_FILTER, 1) output[0] = params - return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX_DECONV_FILTER) + return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX_DECONV_BP_FILTER) End static Function/WAVE PSX_GetAllStatsProperties() diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index a8bea1f6f8..07bc74b429 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -1299,7 +1299,7 @@ The `psx` operation allows to classify miniature PSC/PSP's interactively. .. code-block:: bash - psx(id, [psxKernel(), numSDs, filterLow, filterHigh, maxTauFactor, psxRiseTime(), psxDeconvFilter()]) + psx(id, [psxKernel(), numSDs, filterLow, filterHigh, maxTauFactor, psxRiseTime(), psxDeconvBPFilter()]) The function accepts one to seven arguments. @@ -1326,8 +1326,8 @@ maxTauFactor psxRiseTime results from the `psxRiseTime` operation -psxDeconvFilter - results from the `psxDeconvFilter` operation +psxDeconvBPFilter + results from the `psxDeconvBPFilter` operation The plotting is implemented in a custom way. Due to that multiple `psx` operations can only be separated by `with` and not `and`. @@ -1414,13 +1414,13 @@ diffThreshold psxRiseTime(0.5, 0.9) psxRiseTime(0.5, 0.9, 0.15) -psxDeconvFilter -""""""""""""""" +psxDeconvBPFilter +""""""""""""""""" -The `psxDeconvFilter` operation is a helper operation for `psx` to manage the deconvolution filter settings. +The `psxDeconvBPFilter` operation is a helper operation for `psx` to manage the deconvolution filter settings. This filter is a bandpass filter. - psxDeconvFilter([lowFreq, highFreq, order]) + psxDeconvBPFilter([lowFreq, highFreq, order]) The function accepts zero to three arguments. @@ -1440,8 +1440,8 @@ passband, see also the description of `/LO` and `/HI` from `FilterIIR`. .. code-block:: bash - psxDeconvFilter(800, 100) - psxDeconvFilter(400, 50, 11) + psxDeconvBPFilter(800, 100) + psxDeconvBPFilter(400, 50, 11) psxstats """""""" diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 46564756a7..d07bb0affa 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -3441,44 +3441,46 @@ static Function [variable filterLow, variable filterHigh, variable filterOrder] return [filterLow, filterHigh, filterOrder] End -static Function TestOperationDeconvFilter() +static Function TestOperationDeconvBPFilter() string win, str variable filterLow, filterHigh, filterOrder win = SetupDatabrowserWithSomeData() - str = "psxDeconvFilter()" + str = "psxDeconvBPFilter()" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) CHECK_EQUAL_VAR(filterLow, NaN) CHECK_EQUAL_VAR(filterHigh, NaN) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvFilter(40)" + str = "psxDeconvBPFilter(40)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) CHECK_EQUAL_VAR(filterLow, 40) CHECK_EQUAL_VAR(filterHigh, NaN) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvFilter(40, 50)" + str = "psxDeconvBPFilter(40, 50)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) - CHECK_EQUAL_VAR(filterLow, 40) - CHECK_EQUAL_VAR(filterHigh, 50) + // we automatically fixup the order for the user + CHECK_EQUAL_VAR(filterLow, 50) + CHECK_EQUAL_VAR(filterHigh, 40) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvFilter(40, 50, 11)" + str = "psxDeconvBPFilter(40, 50, 11)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) - CHECK_EQUAL_VAR(filterLow, 40) - CHECK_EQUAL_VAR(filterHigh, 50) + // we automatically fixup the order for the user + CHECK_EQUAL_VAR(filterLow, 50) + CHECK_EQUAL_VAR(filterHigh, 40) CHECK_EQUAL_VAR(filterOrder, 11) // check parameters try - str = "psxDeconvFilter(-1)" + str = "psxDeconvBPFilter(-1)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch @@ -3486,7 +3488,7 @@ static Function TestOperationDeconvFilter() endtry try - str = "psxDeconvFilter(1, -1)" + str = "psxDeconvBPFilter(1, -1)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch @@ -3494,7 +3496,7 @@ static Function TestOperationDeconvFilter() endtry try - str = "psxDeconvFilter(1, 1, -1)" + str = "psxDeconvBPFilter(1, 1, -1)" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch From bd91bdd140eb82f823b57df62e3457ed4806298f Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 9 May 2025 23:15:44 +0200 Subject: [PATCH 24/57] psxSweepBPFilter: Add it --- Packages/MIES/MIES_Constants.ipf | 2 + Packages/MIES/MIES_SweepFormula.ipf | 1 + Packages/MIES/MIES_SweepFormula_Executor.ipf | 3 + Packages/MIES/MIES_SweepFormula_PSX.ipf | 80 ++++++++++++++------ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 376a88fd64..b20caa0a58 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2381,6 +2381,7 @@ StrConstant SF_OP_PSX_STATS = "psxStats" StrConstant SF_OP_PSX_RISETIME = "psxRiseTime" StrConstant SF_OP_PSX_PREP = "psxPrep" StrConstant SF_OP_PSX_DECONV_BP_FILTER = "psxDeconvBPFilter" +StrConstant SF_OP_PSX_SWEEP_BP_FILTER = "psxSweepBPFilter" /// @name Available PSX states /// @anchor PSXStates @@ -2429,6 +2430,7 @@ Constant PSX_HORIZ_OFFSET_SLEW = 2 Constant PSX_DECONV_FILTER_DEF_LOW = 500 Constant PSX_DECONV_FILTER_DEF_HIGH = 50 +Constant PSX_SWEEP_FILTER_DEF_ORDER = 6 Constant PSX_DECONV_FILTER_DEF_ORDER = 7 StrConstant PSX_JWN_COMBO_KEYS_NAME = "ComboKeys" diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 64995533f4..96f55136fe 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -93,6 +93,7 @@ Function/WAVE SF_GetNamedOperations() SF_OP_SELECTCHANNELS, SF_OP_DATA, SF_OP_LABNOTEBOOK, SF_OP_WAVE, SF_OP_FINDLEVEL, SF_OP_EPOCHS, SF_OP_TP, \ SF_OP_STORE, SF_OP_SELECT, SF_OP_POWERSPECTRUM, SF_OP_TPSS, SF_OP_TPBASE, SF_OP_TPINST, SF_OP_TPFIT, \ SF_OP_PSX, SF_OP_PSX_KERNEL, SF_OP_PSX_STATS, SF_OP_PSX_RISETIME, SF_OP_PSX_PREP, SF_OP_PSX_DECONV_BP_FILTER, \ + SF_OP_PSX_SWEEP_BP_FILTER, \ SF_OP_MERGE, SF_OP_FIT, SF_OP_FITLINE, SF_OP_DATASET, SF_OP_SELECTVIS, SF_OP_SELECTCM, SF_OP_SELECTSTIMSET, \ SF_OP_SELECTIVSCCSWEEPQC, SF_OP_SELECTIVSCCSETQC, SF_OP_SELECTRANGE, SF_OP_SELECTEXP, SF_OP_SELECTDEV, \ SF_OP_SELECTEXPANDSCI, SF_OP_SELECTEXPANDRAC, SF_OP_SELECTSETCYCLECOUNT, SF_OP_SELECTSETSWEEPCOUNT, \ diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index 9fc3a45652..33f9b04ef9 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -457,6 +457,9 @@ Function/WAVE SFE_FormulaExecutor(STRUCT SF_ExecutionData &exd, [variable srcLoc case SF_OP_PSX_DECONV_BP_FILTER: WAVE out = PSX_OperationDeconvBPFilter(exdop) break + case SF_OP_PSX_SWEEP_BP_FILTER: + WAVE out = PSX_OperationSweepBPFilter(exdop) + break case SF_OP_MERGE: WAVE out = SFO_OperationMerge(exdop) break diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index c4a2d0224e..b4c9de3b3b 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -266,23 +266,29 @@ End /// @brief Filter the sweep data /// /// @param sweepDataOff data from a single sweep and channel *without* inserted TP already offsetted -/// @param high high cutoff [Hz] -/// @param low low cutoff [Hz] -static Function/WAVE PSX_FilterSweepData(WAVE/Z sweepDataOff, variable low, variable high) +/// @param sweepFilter low/high cutoff [Hz] and order +static Function/WAVE PSX_FilterSweepData(WAVE/Z sweepDataOff, WAVE sweepFilter) - variable samp, err + variable samp, err, low, high, order if(!WaveExists(sweepDataOff)) return $"" endif - samp = 1 / (deltax(sweepDataOff) * MILLI_TO_ONE) + samp = 1 / (deltax(sweepDataOff) * MILLI_TO_ONE) + low = sweepFilter[%$"Filter Low"] + high = sweepFilter[%$"Filter High"] + order = sweepFilter[%$"Filter Order"] ASSERT(low > high, "Expected a band pass filter with low > high") + if(IsNaN(order)) + order = PSX_SWEEP_FILTER_DEF_ORDER + endif + Duplicate/FREE sweepDataOff, filtered - FilterIIR/LO=(low / samp)/HI=(high / samp)/DIM=(ROWS)/ORD=6 filtered; err = GetRTError(1) + FilterIIR/LO=(low / samp)/HI=(high / samp)/DIM=(ROWS)/ORD=(order) filtered; err = GetRTError(1) SFH_ASSERT(!err, "Error filtering the data, msg: " + GetErrMessage(err)) return filtered @@ -448,13 +454,13 @@ End /// - deconvolution /// - histogram of deconvolution /// - gaussian fit of histogram -static Function [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] PSX_Analysis(WAVE sweepData, WAVE psxKernelFFT, variable sweepFilterLow, variable sweepFilterHigh, WAVE deconvFilter) +static Function [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] PSX_Analysis(WAVE sweepData, WAVE psxKernelFFT, WAVE sweepFilter, WAVE deconvFilter) variable offset [WAVE sweepDataOff, offset] = PSX_OffsetSweepData(sweepData) - WAVE sweepDataOffFilt = PSX_FilterSweepData(sweepDataOff, sweepFilterLow, sweepFilterHigh) + WAVE sweepDataOffFilt = PSX_FilterSweepData(sweepDataOff, sweepFilter) if(!WaveExists(sweepDataOffFilt)) return [$"", $""] @@ -974,7 +980,7 @@ static Function/WAVE PSX_CreateOverrideResults(variable numEvents, WAVE/T combos End /// @return 0 on success, 1 otherwise -static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDataset, variable parameterJsonID, variable sweepFilterLow, variable sweepFilterHigh, WAVE deconvFilter, variable index, WAVE/WAVE output) +static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDataset, variable parameterJsonID, WAVE sweepFilter, WAVE deconvFilter, variable index, WAVE/WAVE output) string key, comboKey, psxParametersAnalyzePeaks, cacheKey @@ -1001,7 +1007,7 @@ static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDat WAVE sweepDataOffFilt = psxAnalyzePeaksFromCache[%sweepDataOffFilt] WAVE sweepDataOffFiltDeconv = psxAnalyzePeaksFromCache[%sweepDataOffFiltDeconv] else - [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] = PSX_Analysis(sweepData, psxKernelFFT, sweepFilterLow, sweepFilterHigh, deconvFilter) + [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] = PSX_Analysis(sweepData, psxKernelFFT, sweepFilter, deconvFilter) if(!WaveExists(sweepDataOffFilt) || !WaveExists(sweepDataOffFiltDeconv)) Make/FREE/WAVE/N=(0) psxAnalyzePeaks @@ -4949,35 +4955,32 @@ End // Output[1] = sweepDataOffFilt(1) // ... // -// psx(id, [psxKernel(...), numSDs, sweepFilterLow, sweepFilterHigh, maxTauFactor, psxRiseTime(...), psxDeconvBPFilter(...)]) +// psx(id, [psxKernel(...), numSDs, psxSweepBPFilter(...), maxTauFactor, psxRiseTime(...), psxDeconvBPFilter(...)]) Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) - variable numberOfSDs, sweepFilterLow, sweepFilterHigh, parameterJsonID, numCombos, i, addedData, kernelAmp + variable numberOfSDs, parameterJsonID, numCombos, i, addedData, kernelAmp variable maxTauFactor, peakThresh, idx, success, kernelRiseTau, kernelDecayTau string parameterPath, id, psxParameters, dataUnit, path - SFH_CheckArgumentCount(exd, SF_OP_PSX, 1, maxArgs = 8) + SFH_CheckArgumentCount(exd, SF_OP_PSX, 1, maxArgs = 7) id = SFH_GetArgumentAsText(exd, SF_OP_PSX, 0, checkFunc = IsValidObjectName) WAVE/WAVE psxKernelDataset = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 1, defOp = "psxKernel()") try - numberOfSDs = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 2, defValue = PSX_NUMBER_OF_SDS_DEFAULT, checkFunc = IsStrictlyPositiveAndFinite) - sweepFilterLow = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 3, defValue = PSX_DEFAULT_FILTER_LOW, checkFunc = IsNullOrPositiveAndFinite) - sweepFilterHigh = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 4, defValue = PSX_DEFAULT_FILTER_HIGH, checkFunc = IsNullOrPositiveAndFinite) - maxTauFactor = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 5, defValue = PSX_DEFAULT_MAX_TAU_FACTOR, checkFunc = IsStrictlyPositiveAndFinite) - WAVE riseTime = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxRiseTime()", expectedMinorType = IGOR_TYPE_64BIT_FLOAT, singleResult = 1) + numberOfSDs = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 2, defValue = PSX_NUMBER_OF_SDS_DEFAULT, checkFunc = IsStrictlyPositiveAndFinite) + WAVE sweepFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 3, defOp = "psxSweepBPFilter()", singleResult = 1) + maxTauFactor = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 4, defValue = PSX_DEFAULT_MAX_TAU_FACTOR, checkFunc = IsStrictlyPositiveAndFinite) + WAVE riseTime = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 5, defOp = "psxRiseTime()", expectedMinorType = IGOR_TYPE_64BIT_FLOAT, singleResult = 1) ASSERT(IsNumericWave(riseTime), "Invalid return from psxRiseTime") - WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 7, defOp = "psxDeconvBPFilter()", singleResult = 1) + WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxDeconvBPFilter()", singleResult = 1) parameterJsonID = JWN_GetWaveNoteAsJSON(psxKernelDataset) parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX JSON_AddTreeObject(parameterJsonID, parameterPath) JSON_AddString(parameterJsonID, parameterPath + "/id", id) JSON_AddVariable(parameterJsonID, parameterPath + "/numberOfSDs", numberOfSDs) - JSON_AddVariable(parameterJsonID, parameterPath + "/sweepFilterLow", sweepFilterLow) - JSON_AddVariable(parameterJsonID, parameterPath + "/sweepFilterHigh", sweepFilterHigh) JSON_AddVariable(parameterJsonID, parameterPath + "/maxTauFactor", maxTauFactor) parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_RISETIME JSON_AddTreeObject(parameterJsonID, parameterPath) @@ -4989,6 +4992,11 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", deconvFilter[%$"Filter Low"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", deconvFilter[%$"Filter High"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", deconvFilter[%$"Filter Order"]) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_SWEEP_BP_FILTER + JSON_AddTreeObject(parameterJsonID, parameterPath) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", sweepFilter[%$"Filter Low"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", sweepFilter[%$"Filter High"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", sweepFilter[%$"Filter Order"]) numCombos = DimSize(psxKernelDataset, ROWS) / PSX_KERNEL_OUTPUTWAVES_PER_ENTRY ASSERT(IsInteger(numCombos) && numCombos > 0, "Invalid number of input sets from psxKernel()") @@ -5010,7 +5018,7 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) PSX_OperationSetDimensionLabels(output, numCombos, labels, labelsTemplate) for(i = 0; i < numCombos; i += 1) - success = !PSX_OperationSweepGathering(exd.graph, psxKernelDataset, parameterJsonID, sweepFilterLow, sweepFilterHigh, deconvFilter, idx, output) + success = !PSX_OperationSweepGathering(exd.graph, psxKernelDataset, parameterJsonID, sweepFilter, deconvFilter, idx, output) idx += success endfor @@ -5201,6 +5209,34 @@ Function/WAVE PSX_OperationDeconvBPFilter(STRUCT SF_ExecutionData &exd) return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX_DECONV_BP_FILTER) End +// psxSweepBPFilter([low, high, order]) +Function/WAVE PSX_OperationSweepBPFilter(STRUCT SF_ExecutionData &exd) + + variable low, high, first, second, order + + SFH_CheckArgumentCount(exd, SF_OP_PSX_SWEEP_BP_FILTER, 0, maxArgs = 3) + + first = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_SWEEP_BP_FILTER, 0, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) + second = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_SWEEP_BP_FILTER, 1, defValue = NaN, checkFunc = IsNullOrPositiveAndFinite, checkDefault = 0) + order = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX_SWEEP_BP_FILTER, 2, defValue = NaN, checkFunc = IsStrictlyPositiveAndFinite, checkDefault = 0) + + if(!IsNaN(first) && !IsNaN(second)) + [high, low] = MinMax(first, second) + else + low = first + high = second + endif + + Make/D/FREE params = {low, high, order} + SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) + + WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX_SWEEP_BP_FILTER, 1) + + output[0] = params + + return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX_SWEEP_BP_FILTER) +End + static Function/WAVE PSX_GetAllStatsProperties() Make/FREE/T allProps = {"amp", \ From e97641f97b3a7594ffcbfbd4febdfd4aaeeddb4f Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 12 May 2025 18:47:15 +0200 Subject: [PATCH 25/57] PSX_FilterSweepData/PSX_DeconvoluteSweepData: Always make order even Due to implementation details of how FilterIIR implements band pass filters the used order is always even. If required, odd values are increased by one so that they are even. Document that and also make them even already when we get them. We can as well adap the default value to be even already. --- Packages/MIES/MIES_Constants.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index b20caa0a58..285f91d74e 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2431,7 +2431,7 @@ Constant PSX_HORIZ_OFFSET_SLEW = 2 Constant PSX_DECONV_FILTER_DEF_LOW = 500 Constant PSX_DECONV_FILTER_DEF_HIGH = 50 Constant PSX_SWEEP_FILTER_DEF_ORDER = 6 -Constant PSX_DECONV_FILTER_DEF_ORDER = 7 +Constant PSX_DECONV_FILTER_DEF_ORDER = 8 StrConstant PSX_JWN_COMBO_KEYS_NAME = "ComboKeys" diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index b4c9de3b3b..6ee942732a 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -288,6 +288,7 @@ static Function/WAVE PSX_FilterSweepData(WAVE/Z sweepDataOff, WAVE sweepFilter) Duplicate/FREE sweepDataOff, filtered + // order needs to be even, as odd values are increased to the next even number FilterIIR/LO=(low / samp)/HI=(high / samp)/DIM=(ROWS)/ORD=(order) filtered; err = GetRTError(1) SFH_ASSERT(!err, "Error filtering the data, msg: " + GetErrMessage(err)) @@ -399,6 +400,7 @@ static Function/WAVE PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFF ASSERT(V_Value == -1, "Can not handle NaN in the deconvoluted wave") CopyScales sweepData, Deconv + // order needs to be even, as odd values are increased to the next even number FilterIIR/LO=(lowFrac)/HI=(highFrac)/ORD=(order) Deconv; err = GetRTError(0) if(err) @@ -5199,6 +5201,10 @@ Function/WAVE PSX_OperationDeconvBPFilter(STRUCT SF_ExecutionData &exd) high = second endif + if(IsOdd(order)) + order += 1 + endif + Make/D/FREE params = {low, high, order} SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) @@ -5227,6 +5233,10 @@ Function/WAVE PSX_OperationSweepBPFilter(STRUCT SF_ExecutionData &exd) high = second endif + if(IsOdd(order)) + order += 1 + endif + Make/D/FREE params = {low, high, order} SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) From ea82d0300c97adb8e724127d503ed94fe016864c Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 12 May 2025 19:26:19 +0200 Subject: [PATCH 26/57] BandPassWithRingingDetection: Minor cleanup Define all variables at the top, reword the documentation. --- .../MIES/MIES_MiesUtilities_Algorithm.ipf | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf index 24ff3a62f2..78a0a75dbc 100644 --- a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf +++ b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf @@ -642,49 +642,45 @@ threadsafe Function/WAVE FindIndizes(WAVE numericOrTextWave, [variable col, stri return result End -// @brief Band‑pass filters a wave while automatically reducing IIR filter order until the output contains no NaNs/Infs and its SEM is not larger than the original (simple ringing detection). - -// ----------------------------------------------------------------------------- -// BandPassWithRingingDetection(src, fHigh, fLow, maxOrder) -// ----------------------------------------------------------------------------- -// @param src – input wave (is **not** modified; a filtered copy called _BPF is produced) -// @param fHigh/fLow – pass‑band edge frequencies in Hz (Igor’s band‑pass requires fLow > fHigh; the routine swaps them if needed) -// @param maxOrder – starting (maximum) IIR filter order to try (>0) -// -// Logic: iteratively lowers the filter order until three conditions are met: -// 1. FilterIIR executes without error. -// 2. WaveStats reports V_numNaNs = 0 and V_numInfs = 0. -// 3. SEM(filtered) ≤ SEM(original). -// -// Return value: filter order that finally succeeded (0 if every order failed). -// ----------------------------------------------------------------------------- -Function [variable curOrder, WAVE filtered] bandpass_with_RingingDetection(WAVE src, variable fHigh, variable fLow, variable maxOrder) - - // ---- parameter sanity --------------------------------------------------- +/// @brief Band‑pass filters a wave while automatically reducing IIR filter +/// order until the output contains no NaNs/Infs and its SEM is not larger than +/// the original (simple ringing detection). +/// +/// @param src – input wave +/// @param fHigh – pass‑band edge frequencies in Hz (Igor’s band‑pass requires fLow > fHigh; the routine swaps them if needed) +/// @param fLow – low part +/// @param maxOrder – starting (maximum) IIR filter order to try (>0) +/// +/// Logic: iteratively lowers the filter order until three conditions are met: +/// 1. FilterIIR executes without error. +/// 2. WaveStats reports V_numNaNs = 0 and V_numInfs = 0. +/// 3. SEM(filtered) ≤ SEM(original). +/// +/// @retval curOrder filter order that finally succeeded (0 if every order failed) +/// @retval filtered filtered data +Function [variable curOrder, WAVE filtered] BandPassWithRingingDetection(WAVE src, variable fHigh, variable fLow, variable maxOrder) + + variable err, samp, semOrig, offset + ASSERT(maxOrder > 0, "maxOrder must be positive") - if(fLow <= fHigh) // Igor band‑pass expects fLow > fHigh - variable tmp = fLow - fLow = fHigh - fHigh = tmp - endif + // Igor band‑pass expects fLow > fHigh + [fHigh, fLow] = MinMax(fLow, fHigh) // Sampling rate (Hz) – assumes X scaling is in milliseconds - variable samp = 1 / (DeltaX(src) * MILLI_TO_ONE) + samp = 1 / (DeltaX(src) * MILLI_TO_ONE) - // Pre‑compute SEM(original) once + // Pre-compute SEM(original) once WaveStats/Q src - variable semOrig = V_sem - variable offset = v_avg + semOrig = V_sem + offset = v_avg - // Prepare destination wave (same name every call → convenient overwrite) - duplicate/FREE src, src_BPF - WAVE/Z filtered = src_BPF + // Prepare destination wave + duplicate/FREE src, filtered // remove offset from src copy filtered -= offset curOrder = maxOrder - variable err do // -------- copy fresh data into filtered ------------------------------ filtered = src // avoids repeated duplicate/O allocations From 90e5578f71bb2d44e31d61d61011d8ea74a4a318 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 12 May 2025 19:28:17 +0200 Subject: [PATCH 27/57] BandPassWithRingingDetection: Remove the offset for every iteration --- Packages/MIES/MIES_MiesUtilities_Algorithm.ipf | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf index 78a0a75dbc..3c7ecf0d2e 100644 --- a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf +++ b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf @@ -677,13 +677,10 @@ Function [variable curOrder, WAVE filtered] BandPassWithRingingDetection(WAVE sr // Prepare destination wave duplicate/FREE src, filtered - // remove offset from src copy - filtered -= offset - curOrder = maxOrder do // -------- copy fresh data into filtered ------------------------------ - filtered = src // avoids repeated duplicate/O allocations + filtered = src - offset // -------- attempt current order -------------------------------------- FilterIIR/LO=(fLow / samp)/HI=(fHigh / samp)/DIM=(ROWS)/ORD=(curOrder) filtered From 7b6998a852a0d7d823682f38a50aa0a6a52588bb Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 12 May 2025 19:33:48 +0200 Subject: [PATCH 28/57] BandPassWithRingingDetection: Reorganize the code --- .../MIES/MIES_MiesUtilities_Algorithm.ipf | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf index 3c7ecf0d2e..93acd628f9 100644 --- a/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf +++ b/Packages/MIES/MIES_MiesUtilities_Algorithm.ipf @@ -656,14 +656,16 @@ End /// 2. WaveStats reports V_numNaNs = 0 and V_numInfs = 0. /// 3. SEM(filtered) ≤ SEM(original). /// -/// @retval curOrder filter order that finally succeeded (0 if every order failed) +/// @retval realOrder filter order that finally succeeded (NaN if every order failed) /// @retval filtered filtered data -Function [variable curOrder, WAVE filtered] BandPassWithRingingDetection(WAVE src, variable fHigh, variable fLow, variable maxOrder) +Function [variable realOrder, WAVE filtered] BandPassWithRingingDetection(WAVE src, variable fHigh, variable fLow, variable maxOrder) - variable err, samp, semOrig, offset + variable err, samp, semOrig, offset, i + string msg + + ASSERT(IsInteger(maxOrder) && maxOrder > 0 && isEven(maxOrder), "maxOrder must be a positive and even integer") - ASSERT(maxOrder > 0, "maxOrder must be positive") - // Igor band‑pass expects fLow > fHigh + // Igor band-pass expects fLow > fHigh [fHigh, fLow] = MinMax(fLow, fHigh) // Sampling rate (Hz) – assumes X scaling is in milliseconds @@ -673,47 +675,46 @@ Function [variable curOrder, WAVE filtered] BandPassWithRingingDetection(WAVE sr WaveStats/Q src semOrig = V_sem offset = v_avg + ASSERT(V_numNaNs == 0 && V_numInfs == 0, "Expected only finite value in input data") // Prepare destination wave - duplicate/FREE src, filtered + Duplicate/FREE src, filtered - curOrder = maxOrder - do - // -------- copy fresh data into filtered ------------------------------ - filtered = src - offset + for(i = maxOrder; i > 0; i -= 2) + Multithread filtered = src - offset - // -------- attempt current order -------------------------------------- - FilterIIR/LO=(fLow / samp)/HI=(fHigh / samp)/DIM=(ROWS)/ORD=(curOrder) filtered - err = GetRTError(1) + FilterIIR/LO=(fLow / samp)/HI=(fHigh / samp)/DIM=(ROWS)/ORD=(i) filtered; err = GetRTError(1) if(err) - Print "FilterIIR failed (order=" + num2str(curOrder) + "): " + GetErrMessage(err) - curOrder -= 1 continue endif - // -------- WaveStats: NaN/Inf + SEM in one call ------------------------ WaveStats/Q filtered if(V_numNaNs > 0 || V_numInfs > 0) - curOrder -= 1 - continue // bad numerical output → lower order + sprintf msg, "V_numNaNs: %g, V_numInfs: %g", V_numNaNs, V_numInfs + DEBUGPRINT(msg) + + // bad numerical output → lower order + continue endif - if(V_sem > semOrig) // noisier than original → ringing - curOrder -= 1 + if(V_sem > semOrig) + sprintf msg, "V_sem: %g,semOrig: %g", V_sem, semOrig + DEBUGPRINT(msg) + + // noisier than original → ringing continue endif - // -------- success ----------------------------------------------------- + Multithread filtered += offset - break - while(curOrder > 0) + sprintf msg, "maxOrder: %g, realOrder: %g", maxOrder, i + DEBUGPRINT(msg) - if(curOrder <= 0) - Print "bandpass_with_RingingDetection(): all orders down to 1 produced NaNs/Infs or increased SEM." - endif + return [i, filtered] + endfor - // add offset back to filtered wave - filtered += offset + sprintf msg, "maxOrder: %g, realOrder: NaN", maxOrder + DEBUGPRINT(msg) - return [curOrder, filtered] + return [NaN, $""] End From d04391a36388c41d1a224cf0949f9509971ddfbc Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 13 May 2025 16:30:06 +0200 Subject: [PATCH 29/57] PSX_Operation: Fix data gathering after failure When PSX_OperationSweepGathering returns 1 as error code, we wrote the code so that idx is not incremented in this case. The idea in d359a7a64a (PSX_Operation: Handle partial results better, 2024-02-27) was that we don't want holes in the output wave. But the code was buggy as it, after an error, would reuse the same data again, and thus error out again, and therefore ignoring all data behind the first combination with an error. Fix that by introducing separate indizes for reading and writing. --- Packages/MIES/MIES_Cache.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 285 +++++++++++++++--------- 2 files changed, 175 insertions(+), 112 deletions(-) diff --git a/Packages/MIES/MIES_Cache.ipf b/Packages/MIES/MIES_Cache.ipf index 5d6447b840..7dfdac6550 100644 --- a/Packages/MIES/MIES_Cache.ipf +++ b/Packages/MIES/MIES_Cache.ipf @@ -509,7 +509,7 @@ End Function/S CA_PSXAnalyzePeaks(string comboKey, string psxParameters) - return CA_PSXBaseKey(comboKey, psxParameters) + " Analyze Peaks " + ":Version 2" + return CA_PSXBaseKey(comboKey, psxParameters) + " Analyze Peaks " + ":Version 3" End /// @brief Return the key for the igor info entries diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 6ee942732a..ed839ff75b 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -161,6 +161,9 @@ static StrConstant PSX_COLORS_PEAK = "65535,43690,0" static StrConstant PSX_FREE_AXIS_BASELINE = "freeBaselineAxis" static StrConstant PSX_COLORS_BASELINE = "0,0,0" +static Constant PSX_FILTER_SWEEP = 0x1 +static Constant PSX_FILTER_DECONV = 0x2 + Menu "GraphMarquee" "PSX: Accept Event && Fit", /Q, PSX_MouseEventSelection(PSX_ACCEPT, PSX_STATE_EVENT | PSX_STATE_FIT) "PSX: Reject Event && Fit", /Q, PSX_MouseEventSelection(PSX_REJECT, PSX_STATE_EVENT | PSX_STATE_FIT) @@ -267,32 +270,25 @@ End /// /// @param sweepDataOff data from a single sweep and channel *without* inserted TP already offsetted /// @param sweepFilter low/high cutoff [Hz] and order -static Function/WAVE PSX_FilterSweepData(WAVE/Z sweepDataOff, WAVE sweepFilter) +static Function [variable realOrder, WAVE filtered] PSX_FilterSweepData(WAVE/Z sweepDataOff, WAVE sweepFilter) - variable samp, err, low, high, order + variable low, high, maxOrder if(!WaveExists(sweepDataOff)) - return $"" + return [NaN, $""] endif - samp = 1 / (deltax(sweepDataOff) * MILLI_TO_ONE) - low = sweepFilter[%$"Filter Low"] - high = sweepFilter[%$"Filter High"] - order = sweepFilter[%$"Filter Order"] + low = sweepFilter[%$"Filter Low"] + high = sweepFilter[%$"Filter High"] + maxOrder = sweepFilter[%$"Filter Order"] - ASSERT(low > high, "Expected a band pass filter with low > high") - - if(IsNaN(order)) - order = PSX_SWEEP_FILTER_DEF_ORDER + if(IsNaN(maxOrder)) + maxOrder = PSX_SWEEP_FILTER_DEF_ORDER endif - Duplicate/FREE sweepDataOff, filtered - - // order needs to be even, as odd values are increased to the next even number - FilterIIR/LO=(low / samp)/HI=(high / samp)/DIM=(ROWS)/ORD=(order) filtered; err = GetRTError(1) - SFH_ASSERT(!err, "Error filtering the data, msg: " + GetErrMessage(err)) + [realOrder, WAVE filtered] = BandPassWithRingingDetection(sweepDataOff, high, low, maxOrder) - return filtered + return [realOrder, filtered] End /// @brief Create a histogram of the sweep data @@ -359,33 +355,26 @@ End /// @param sweepData data from a single sweep and channel *without* inserted TP /// @param psxKernelFFT FFT'ed kernel from PSX_CreatePSXKernel() /// @param deconvFilter deconvolution filter settings -static Function/WAVE PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFFT, WAVE deconvFilter) +static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFFT, WAVE deconvFilter) - variable numPoints, fftSize, samp, low, high, order, lowFrac, highFrac, err + variable numPoints, fftSize, samp, low, high, maxOrder - samp = 1 / (deltax(sweepData) * MILLI_TO_ONE) - low = deconvFilter[%$"Filter Low"] - high = deconvFilter[%$"Filter High"] - order = deconvFilter[%$"Filter Order"] + low = deconvFilter[%$"Filter Low"] + high = deconvFilter[%$"Filter High"] + maxOrder = deconvFilter[%$"Filter Order"] if(IsNaN(low)) - lowFrac = PSX_DECONV_FILTER_DEF_LOW / samp - else - lowFrac = low / samp + low = PSX_DECONV_FILTER_DEF_LOW endif if(IsNaN(high)) - highFrac = PSX_DECONV_FILTER_DEF_HIGH / samp - else - highFrac = high / samp + high = PSX_DECONV_FILTER_DEF_HIGH endif - if(IsNaN(order)) - order = PSX_DECONV_FILTER_DEF_ORDER + if(IsNaN(maxOrder)) + maxOrder = PSX_DECONV_FILTER_DEF_ORDER endif - ASSERT(lowFrac > highFrac, "Expected a band pass filter with low > high") - numPoints = DimSize(sweepData, ROWS) fftSize = DimSize(psxKernelFFT, ROWS) @@ -400,15 +389,9 @@ static Function/WAVE PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFF ASSERT(V_Value == -1, "Can not handle NaN in the deconvoluted wave") CopyScales sweepData, Deconv - // order needs to be even, as odd values are increased to the next even number - FilterIIR/LO=(lowFrac)/HI=(highFrac)/ORD=(order) Deconv; err = GetRTError(0) - - if(err) - printf "Error applying deconvolution filter: %s\r", GetRTErrMessage() - ClearRTError() - endif + [realOrder, filtered] = BandPassWithRingingDetection(Deconv, high, low, maxOrder) - return Deconv + return [realOrder, filtered] End /// @brief Creates a histogram of the deconvoluted sweep data @@ -456,21 +439,21 @@ End /// - deconvolution /// - histogram of deconvolution /// - gaussian fit of histogram -static Function [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] PSX_Analysis(WAVE sweepData, WAVE psxKernelFFT, WAVE sweepFilter, WAVE deconvFilter) +static Function [variable realOrderSweep, variable realOrderDeconv, WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] PSX_Analysis(WAVE sweepData, WAVE psxKernelFFT, WAVE sweepFilter, WAVE deconvFilter) variable offset [WAVE sweepDataOff, offset] = PSX_OffsetSweepData(sweepData) - WAVE sweepDataOffFilt = PSX_FilterSweepData(sweepDataOff, sweepFilter) + [realOrderSweep, WAVE sweepDataOffFilt] = PSX_FilterSweepData(sweepDataOff, sweepFilter) if(!WaveExists(sweepDataOffFilt)) - return [$"", $""] + return [NaN, NaN, $"", $""] endif - WAVE sweepDataOffFiltDeconv = PSX_DeconvoluteSweepData(sweepDataOffFilt, psxKernelFFT, deconvFilter) + [realOrderDeconv, WAVE sweepDataOffFiltDeconv] = PSX_DeconvoluteSweepData(sweepDataOffFilt, psxKernelFFT, deconvFilter) - return [sweepDataOffFilt, sweepDataOffFiltDeconv] + return [realOrderSweep, realOrderDeconv, sweepDataOffFilt, sweepDataOffFiltDeconv] End /// Searches for peaks in sweepData @@ -982,14 +965,15 @@ static Function/WAVE PSX_CreateOverrideResults(variable numEvents, WAVE/T combos End /// @return 0 on success, 1 otherwise -static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDataset, variable parameterJsonID, WAVE sweepFilter, WAVE deconvFilter, variable index, WAVE/WAVE output) +static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDataset, variable parameterJsonID, WAVE sweepFilter, WAVE deconvFilter, variable readIndex, variable writeIndex, WAVE/WAVE output) string key, comboKey, psxParametersAnalyzePeaks, cacheKey + variable realOrderSweep, realOrderDeconv - key = PSX_GenerateKey("psxKernelFFT", index) + key = PSX_GenerateKey("psxKernelFFT", readIndex) WAVE psxKernelFFT = psxKernelDataset[%$key] - key = PSX_GenerateKey("sweepData", index) + key = PSX_GenerateKey("sweepData", readIndex) WAVE sweepData = psxKernelDataset[%$key] [WAVE selectData, WAVE range] = SFH_ParseToSelectDataWaveAndRange(sweepData) @@ -1008,16 +992,24 @@ static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDat WAVE sweepDataOffFilt = psxAnalyzePeaksFromCache[%sweepDataOffFilt] WAVE sweepDataOffFiltDeconv = psxAnalyzePeaksFromCache[%sweepDataOffFiltDeconv] + realOrderSweep = WaveRef(psxAnalyzePeaksFromCache[%realOrderSweep])[0] + realOrderDeconv = WaveRef(psxAnalyzePeaksFromCache[%realOrderDeconv])[0] else - [WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] = PSX_Analysis(sweepData, psxKernelFFT, sweepFilter, deconvFilter) + [realOrderSweep, realOrderDeconv, WAVE sweepDataOffFilt, WAVE sweepDataOffFiltDeconv] = PSX_Analysis(sweepData, psxKernelFFT, sweepFilter, deconvFilter) if(!WaveExists(sweepDataOffFilt) || !WaveExists(sweepDataOffFiltDeconv)) Make/FREE/WAVE/N=(0) psxAnalyzePeaks else - Make/FREE/WAVE/N=(2) psxAnalyzePeaks - SetDimensionLabels(psxAnalyzePeaks, "sweepDataOffFilt;sweepDataOffFiltDeconv", ROWS) + Make/FREE/WAVE/N=(4) psxAnalyzePeaks + SetDimensionLabels(psxAnalyzePeaks, "sweepDataOffFilt;sweepDataOffFiltDeconv;realOrderSweep;realOrderDeconv", ROWS) psxAnalyzePeaks[%sweepDataOffFilt] = sweepDataOffFilt psxAnalyzePeaks[%sweepDataOffFiltDeconv] = sweepDataOffFiltDeconv + + Make/FREE pkg = {realOrderSweep} + psxAnalyzePeaks[%realOrderSweep] = pkg + + Make/FREE pkg = {realOrderDeconv} + psxAnalyzePeaks[%realOrderDeconv] = pkg endif CA_StoreEntryIntoCache(cacheKey, psxAnalyzePeaks) @@ -1027,13 +1019,17 @@ static Function PSX_OperationSweepGathering(string graph, WAVE/WAVE psxKernelDat endif endif - key = PSX_GenerateKey("sweepData", index) + key = PSX_GenerateKey("sweepData", writeIndex) output[%$key] = sweepData - key = PSX_GenerateKey("sweepDataOffFilt", index) + JWN_SetNumberInWaveNote(sweepDataOffFilt, "maxOrder", sweepFilter[%$"Filter Order"]) + JWN_SetNumberInWaveNote(sweepDataOffFilt, "realOrder", realOrderSweep) + key = PSX_GenerateKey("sweepDataOffFilt", writeIndex) output[%$key] = sweepDataOffFilt - key = PSX_GenerateKey("sweepDataOffFiltDeconv", index) + JWN_SetNumberInWaveNote(sweepDataOffFiltDeconv, "maxOrder", deconvFilter[%$"Filter Order"]) + JWN_SetNumberInWaveNote(sweepDataOffFiltDeconv, "realOrder", realOrderDeconv) + key = PSX_GenerateKey("sweepDataOffFiltDeconv", writeIndex) output[%$key] = sweepDataOffFiltDeconv return 0 @@ -4960,9 +4956,10 @@ End // psx(id, [psxKernel(...), numSDs, psxSweepBPFilter(...), maxTauFactor, psxRiseTime(...), psxDeconvBPFilter(...)]) Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) - variable numberOfSDs, parameterJsonID, numCombos, i, addedData, kernelAmp - variable maxTauFactor, peakThresh, idx, success, kernelRiseTau, kernelDecayTau - string parameterPath, id, psxParameters, dataUnit, path + variable numberOfSDs, parameterJsonID, numCombos, i, readIndex, writeIndex, addedData, kernelAmp + variable maxTauFactor, peakThresh, success, kernelRiseTau, kernelDecayTau, constFilterOrderSweep, constFilterOrderDeconv + variable minFilterOrderSweep, minFilterOrderDeconv + string parameterPath, id, psxParameters, dataUnit, path, msg SFH_CheckArgumentCount(exd, SF_OP_PSX, 1, maxArgs = 7) @@ -4970,71 +4967,103 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) WAVE/WAVE psxKernelDataset = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 1, defOp = "psxKernel()") + numberOfSDs = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 2, defValue = PSX_NUMBER_OF_SDS_DEFAULT, checkFunc = IsStrictlyPositiveAndFinite) + WAVE sweepFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 3, defOp = "psxSweepBPFilter()", singleResult = 1) + maxTauFactor = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 4, defValue = PSX_DEFAULT_MAX_TAU_FACTOR, checkFunc = IsStrictlyPositiveAndFinite) + WAVE riseTime = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 5, defOp = "psxRiseTime()", expectedMinorType = IGOR_TYPE_64BIT_FLOAT, singleResult = 1) + ASSERT(IsNumericWave(riseTime), "Invalid return from psxRiseTime") + WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxDeconvBPFilter()", singleResult = 1) + + parameterJsonID = JWN_GetWaveNoteAsJSON(psxKernelDataset) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX + JSON_AddTreeObject(parameterJsonID, parameterPath) + JSON_AddString(parameterJsonID, parameterPath + "/id", id) + JSON_AddVariable(parameterJsonID, parameterPath + "/numberOfSDs", numberOfSDs) + JSON_AddVariable(parameterJsonID, parameterPath + "/maxTauFactor", maxTauFactor) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_RISETIME + JSON_AddTreeObject(parameterJsonID, parameterPath) + JSON_AddVariable(parameterJsonID, parameterPath + "/upperThreshold", riseTime[%$"Upper Threshold"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/lowerThreshold", riseTime[%$"Lower Threshold"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/differentiateThreshold", riseTime[%$"Differentiate Threshold"]) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_BP_FILTER + JSON_AddTreeObject(parameterJsonID, parameterPath) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", deconvFilter[%$"Filter Low"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", deconvFilter[%$"Filter High"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", deconvFilter[%$"Filter Order"]) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_SWEEP_BP_FILTER + JSON_AddTreeObject(parameterJsonID, parameterPath) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", sweepFilter[%$"Filter Low"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", sweepFilter[%$"Filter High"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", sweepFilter[%$"Filter Order"]) + + numCombos = DimSize(psxKernelDataset, ROWS) / PSX_KERNEL_OUTPUTWAVES_PER_ENTRY + ASSERT(IsInteger(numCombos) && numCombos > 0, "Invalid number of input sets from psxKernel()") + + WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX, numCombos * PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY) + + WAVE/T labelsTemplate = ListToTextWave(PSX_EVENT_DIMENSION_LABELS, ";") + ASSERT(DimSize(labelsTemplate, ROWS) == PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY, "Mismatched label wave") + Duplicate/FREE/T labelsTemplate, labels + + PSX_OperationSetDimensionLabels(output, numCombos, labels, labelsTemplate) + try - numberOfSDs = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 2, defValue = PSX_NUMBER_OF_SDS_DEFAULT, checkFunc = IsStrictlyPositiveAndFinite) - WAVE sweepFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 3, defOp = "psxSweepBPFilter()", singleResult = 1) - maxTauFactor = SFH_GetArgumentAsNumeric(exd, SF_OP_PSX, 4, defValue = PSX_DEFAULT_MAX_TAU_FACTOR, checkFunc = IsStrictlyPositiveAndFinite) - WAVE riseTime = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 5, defOp = "psxRiseTime()", expectedMinorType = IGOR_TYPE_64BIT_FLOAT, singleResult = 1) - ASSERT(IsNumericWave(riseTime), "Invalid return from psxRiseTime") - WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxDeconvBPFilter()", singleResult = 1) - - parameterJsonID = JWN_GetWaveNoteAsJSON(psxKernelDataset) - parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX - JSON_AddTreeObject(parameterJsonID, parameterPath) - JSON_AddString(parameterJsonID, parameterPath + "/id", id) - JSON_AddVariable(parameterJsonID, parameterPath + "/numberOfSDs", numberOfSDs) - JSON_AddVariable(parameterJsonID, parameterPath + "/maxTauFactor", maxTauFactor) - parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_RISETIME - JSON_AddTreeObject(parameterJsonID, parameterPath) - JSON_AddVariable(parameterJsonID, parameterPath + "/upperThreshold", riseTime[%$"Upper Threshold"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/lowerThreshold", riseTime[%$"Lower Threshold"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/differentiateThreshold", riseTime[%$"Differentiate Threshold"]) - parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_BP_FILTER - JSON_AddTreeObject(parameterJsonID, parameterPath) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", deconvFilter[%$"Filter Low"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", deconvFilter[%$"Filter High"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", deconvFilter[%$"Filter Order"]) - parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_SWEEP_BP_FILTER - JSON_AddTreeObject(parameterJsonID, parameterPath) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", sweepFilter[%$"Filter Low"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", sweepFilter[%$"Filter High"]) - JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", sweepFilter[%$"Filter Order"]) - - numCombos = DimSize(psxKernelDataset, ROWS) / PSX_KERNEL_OUTPUTWAVES_PER_ENTRY - ASSERT(IsInteger(numCombos) && numCombos > 0, "Invalid number of input sets from psxKernel()") - - WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX, numCombos * PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY) + for(;;) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_BP_FILTER + JSON_SetVariable(parameterJsonID, parameterPath + "/filterOrder", deconvFilter[%$"Filter Order"]) - path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL - kernelAmp = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/amp") - ASSERT(IsFinite(kernelAmp), "psxKernel amplitude must be finite") - kernelRiseTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/riseTau") - ASSERT(IsFinite(kernelRiseTau), "riseTau must be finite") - kernelDecayTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/decayTau") - ASSERT(IsFinite(kernelDecayTau), "decayTau must be finite") + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_SWEEP_BP_FILTER + JSON_SetVariable(parameterJsonID, parameterPath + "/filterOrder", sweepFilter[%$"Filter Order"]) - WAVE/T labelsTemplate = ListToTextWave(PSX_EVENT_DIMENSION_LABELS, ";") - ASSERT(DimSize(labelsTemplate, ROWS) == PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY, "Mismatched label wave") - Duplicate/FREE/T labelsTemplate, labels + sprintf msg, "sweepFilter: %s, deconvFilter: %s", NumericWaveToList(sweepFilter, ";", trailSep = 0), NumericWaveToList(deconvFilter, ";", trailSep = 0) + DEBUGPRINT(msg) - PSX_OperationSetDimensionLabels(output, numCombos, labels, labelsTemplate) + writeIndex = 0 + for(i = 0; i < numCombos; i += 1) + readIndex = i + success = !PSX_OperationSweepGathering(exd.graph, psxKernelDataset, parameterJsonID, sweepFilter, deconvFilter, readIndex, writeIndex, output) + writeIndex += success + endfor - for(i = 0; i < numCombos; i += 1) - success = !PSX_OperationSweepGathering(exd.graph, psxKernelDataset, parameterJsonID, sweepFilter, deconvFilter, idx, output) - idx += success - endfor + numCombos = writeIndex - numCombos = idx + if(numCombos == 0) + Abort + endif - if(numCombos == 0) - Abort - endif + [constFilterOrderSweep, minFilterOrderSweep] = PSX_HasConstantFilterOrder(output, numCombos, PSX_FILTER_SWEEP) + sprintf msg, "constFilterOrderSweep=%g, minFilterOrderSweep=%g", constFilterOrderSweep, minFilterOrderSweep + DEBUGPRINT(msg) - Redimension/N=(numCombos * PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY) output + [constFilterOrderDeconv, minFilterOrderDeconv] = PSX_HasConstantFilterOrder(output, numCombos, PSX_FILTER_DECONV) + sprintf msg, "constFilterOrderDeconv=%g, minFilterOrderDeconv=%g", constFilterOrderDeconv, minFilterOrderDeconv + DEBUGPRINT(msg) + + if(constFilterOrderSweep && constFilterOrderDeconv) + Redimension/N=(numCombos * PSX_OPERATION_OUTPUT_WAVES_PER_ENTRY) output + break + endif + + if(!constFilterOrderSweep) + sweepFilter[%$"Filter Order"] = minFilterOrderSweep + endif + + if(!constFilterOrderDeconv) + deconvFilter[%$"Filter Order"] = minFilterOrderDeconv + endif + endfor [WAVE hist, WAVE fit, peakThresh, dataUnit] = PSX_CalculatePeakThreshold(output, numCombos, numberOfSDs) WaveClear hist, fit + path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL + kernelAmp = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/amp") + ASSERT(IsFinite(kernelAmp), "psxKernel amplitude must be finite") + kernelRiseTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/riseTau") + ASSERT(IsFinite(kernelRiseTau), "riseTau must be finite") + kernelDecayTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/decayTau") + ASSERT(IsFinite(kernelDecayTau), "decayTau must be finite") + for(i = 0; i < numCombos; i += 1) PSX_OperationImpl(exd.graph, parameterJsonID, id, peakThresh, maxTauFactor, riseTime, kernelAmp, kernelRiseTau, kernelDecayTau, i, output) endfor @@ -5059,6 +5088,40 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) return SFH_GetOutputForExecutor(output, exd.graph, SF_OP_PSX) End +Function [variable constFilterOrder, variable minFilterOrder] PSX_HasConstantFilterOrder(WAVE/WAVE output, variable numCombos, variable type) + + variable i, realOrder + string lbl, key, msg + + switch(type) + case PSX_FILTER_SWEEP: + lbl = "sweepDataOffFilt" + break + case PSX_FILTER_DECONV: + lbl = "sweepDataOffFiltDeconv" + break + default: + FATAL_ERROR("Unexpected type") + endswitch + + Make/FREE/N=(numCombos) filterOrder + + for(i = 0; i < numCombos; i += 1) + key = PSX_GenerateKey(lbl, i) + WAVE/Z wv = output[%$key] + ASSERT(WaveExists(wv), "Expected data wave") + + realOrder = JWN_GetNumberFromWaveNote(wv, "realOrder") + ASSERT(IsInteger(realOrder), "Expected an integer") + filterOrder[i] = realOrder + endfor + + sprintf msg, "filterOrder: %s", NumericWaveToList(filterOrder, ";", trailSep = 0) + DEBUGPRINT(msg) + + return [IsConstant(filterOrder, filterOrder[0]), WaveMin(filterOrder)] +End + /// @brief Implementation of the `psxKernel` operation /// // Returns a SweepFormula dataset with n * PSX_KERNEL_OUTPUTWAVES_PER_ENTRY From a7893711041c65f1fa9eb50908451cd2f311ac09 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 30 May 2025 15:41:08 +0200 Subject: [PATCH 30/57] PSX: Use 2^x padding for FFT input The FFT algorithm used by Igor Pro requires the prime factors of the data points to be small, the documentation even says < 5. A real world dataset with size 2308332 was way too slow and all time was spent in FFT. This number factors into {2,2,3,13,14797} so this is really not small. We now fix that by padding the data to the next power of two, this should also not influence the result. --- Packages/MIES/MIES_Cache.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Packages/MIES/MIES_Cache.ipf b/Packages/MIES/MIES_Cache.ipf index 7dfdac6550..54e2cb9f65 100644 --- a/Packages/MIES/MIES_Cache.ipf +++ b/Packages/MIES/MIES_Cache.ipf @@ -481,7 +481,7 @@ Function/S CA_PSXKernelOperationKey(variable riseTau, variable decayTau, variabl crc = StringCRC(crc, num2strHighPrec(dt, precision = MAX_DOUBLE_PRECISION)) crc = WaveCRC(crc, range) - return num2istr(crc) + "PSX Kernel Version 2" + return num2istr(crc) + "PSX Kernel Version 3" End static Function/S CA_PSXBaseKey(string comboKey, string psxParameters) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index ed839ff75b..4bca6bcfeb 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -357,7 +357,7 @@ End /// @param deconvFilter deconvolution filter settings static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFFT, WAVE deconvFilter) - variable numPoints, fftSize, samp, low, high, maxOrder + variable samp, low, high, maxOrder low = deconvFilter[%$"Filter Low"] high = deconvFilter[%$"Filter High"] @@ -375,12 +375,10 @@ static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAV maxOrder = PSX_DECONV_FILTER_DEF_ORDER endif - numPoints = DimSize(sweepData, ROWS) - fftSize = DimSize(psxKernelFFT, ROWS) - // no window function on purpose - WAVE/C outputFFT = DoFFT(sweepData, padSize = numPoints) + WAVE/C outputFFT = DoFFT(sweepData) + ASSERT(DimSize(outputFFT, ROWS) == DimSize(psxKernelFFT, ROWS), "Unmatched wave sizes") Multithread outputFFT[] = outputFFT[p] / (psxKernelFFT[p] + 1e-5) IFFT/DEST=Deconv/FREE outputFFT @@ -1199,7 +1197,7 @@ static Function [WAVE kernel, WAVE kernelFFT] PSX_CreatePSXKernel(variable riseT SetScale/P x, 0, dt, kernel // no window function on purpose - WAVE kernelFFT = DoFFT(kernel, padSize = numPoints) + WAVE kernelFFT = DoFFT(kernel, padSize = 2^FindNextPower(numPoints, 2)) return [kernel, kernelFFT] End From 8adb123fcd435818b7537bd5cbdc62ec648e0d1a Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Fri, 30 May 2025 11:45:20 -0700 Subject: [PATCH 31/57] PSX: Prefer odd number of historgram bins --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 4bca6bcfeb..4518365af4 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -66,7 +66,7 @@ static Constant PSX_FIT_RANGE_PERC = 0.9 static Constant PSX_BASELINE_NUM_POINTS_AVERAGE = 5 static Constant PSX_PEAK_RANGE_FACTOR_LEFT = 5 static Constant PSX_PEAK_RANGE_FACTOR_RIGHT = 0.33 -static Constant PSX_PEAK_NUM_HIST_BINS = 20 +static Constant PSX_PEAK_NUM_HIST_BINS = 21 static Constant PSX_NUM_PEAKS_MAX = 2000 From 80a81063c2b4d39b04e168cc04e05b2954297cf5 Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Fri, 30 May 2025 11:46:25 -0700 Subject: [PATCH 32/57] Revert "PSX: Use 2^x padding for FFT input" This reverts commit 2180a893da81e98dc0395fa18280e3cd8fbdc0a8. --- Packages/MIES/MIES_Cache.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Packages/MIES/MIES_Cache.ipf b/Packages/MIES/MIES_Cache.ipf index 54e2cb9f65..7dfdac6550 100644 --- a/Packages/MIES/MIES_Cache.ipf +++ b/Packages/MIES/MIES_Cache.ipf @@ -481,7 +481,7 @@ Function/S CA_PSXKernelOperationKey(variable riseTau, variable decayTau, variabl crc = StringCRC(crc, num2strHighPrec(dt, precision = MAX_DOUBLE_PRECISION)) crc = WaveCRC(crc, range) - return num2istr(crc) + "PSX Kernel Version 3" + return num2istr(crc) + "PSX Kernel Version 2" End static Function/S CA_PSXBaseKey(string comboKey, string psxParameters) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 4518365af4..1d016abd5b 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -357,7 +357,7 @@ End /// @param deconvFilter deconvolution filter settings static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFFT, WAVE deconvFilter) - variable samp, low, high, maxOrder + variable numPoints, fftSize, samp, low, high, maxOrder low = deconvFilter[%$"Filter Low"] high = deconvFilter[%$"Filter High"] @@ -375,10 +375,12 @@ static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAV maxOrder = PSX_DECONV_FILTER_DEF_ORDER endif + numPoints = DimSize(sweepData, ROWS) + fftSize = DimSize(psxKernelFFT, ROWS) + // no window function on purpose - WAVE/C outputFFT = DoFFT(sweepData) + WAVE/C outputFFT = DoFFT(sweepData, padSize = numPoints) - ASSERT(DimSize(outputFFT, ROWS) == DimSize(psxKernelFFT, ROWS), "Unmatched wave sizes") Multithread outputFFT[] = outputFFT[p] / (psxKernelFFT[p] + 1e-5) IFFT/DEST=Deconv/FREE outputFFT @@ -1197,7 +1199,7 @@ static Function [WAVE kernel, WAVE kernelFFT] PSX_CreatePSXKernel(variable riseT SetScale/P x, 0, dt, kernel // no window function on purpose - WAVE kernelFFT = DoFFT(kernel, padSize = 2^FindNextPower(numPoints, 2)) + WAVE kernelFFT = DoFFT(kernel, padSize = numPoints) return [kernel, kernelFFT] End From cb7d8e6ee436ced0294a5816ee9778b66df8cb65 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 4 Jun 2025 16:05:47 +0200 Subject: [PATCH 33/57] PSX_FitAcceptAverage: Calculate maximum of it as well --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 1d016abd5b..c0c3f7bfb4 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2505,7 +2505,7 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns AssertOnAndClearRTError() - Make/FREE/T/N=(10, 2) InputAvg, InputRise, InputDecay + Make/FREE/T/N=(11, 2) InputAvg, InputRise, InputDecay Make/FREE/D/N=5 coefWave Make/FREE/T coeffNames = {"y0", "A", "tau1", "tau2", "X0"} @@ -2515,6 +2515,7 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns InputAvg[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} InputAvg[2, 6][0] = coeffNames[p - 2] InputAvg[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputAvg[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedAverageFit))}} InputAvg[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} InputAvg[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} @@ -2524,13 +2525,14 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) - InputRise[0][0, 1] = {{"Function"}, {"dblexp_XOffset rise"}} - InputRise[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} - InputRise[2, 6][0] = coeffNames[p - 2] - InputRise[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - InputRise[7][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} - InputRise[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} - InputRise[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + InputRise[0][0, 1] = {{"Function"}, {"dblexp_XOffset rise"}} + InputRise[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} + InputRise[2, 6][0] = coeffNames[p - 2] + InputRise[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputRise[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedRiseAverageFit))}} + InputRise[8][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} + InputRise[9][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} + InputRise[10][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} Make/FREE/D/N=5 coefWave Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} @@ -2538,13 +2540,14 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) - InputDecay[0][0, 1] = {{"Function"}, {"dblexp_XOffset decay"}} - InputDecay[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} - InputDecay[2, 6][0] = coeffNames[p - 2] - InputDecay[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - InputDecay[7][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} - InputDecay[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} - InputDecay[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} + InputDecay[0][0, 1] = {{"Function"}, {"dblexp_XOffset decay"}} + InputDecay[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} + InputDecay[2, 6][0] = coeffNames[p - 2] + InputDecay[2, 6][1] = num2strHighPrec(coefWave[p - 2]) + InputDecay[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedDecayAverageFit))}} + InputDecay[8][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} + InputDecay[9][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} + InputDecay[10][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} sprintf msg, "Fit in the range [%g, %g] finished with %d (%s)\r", riseStart, decayStop, err, GetErrMessage(err) DEBUGPRINT(msg) From c113d326bf0d1961a8981f196656e779a1b1c636 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 4 Jun 2025 16:52:30 +0200 Subject: [PATCH 34/57] PSX: Add average fit for all states We now have average fits for all states (accept, reject, undet, all) and not only accept. The GUI was also redesigned to accomodate the new checkboxes and buttons. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 225 +++++++++++------- Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf | 76 +++--- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 24 +- 3 files changed, 200 insertions(+), 125 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index c0c3f7bfb4..e65d73f458 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -123,7 +123,7 @@ static StrConstant PSX_TUD_COMBO_KEY = "comboKey" static StrConstant PSX_TUD_COMBO_INDEX = "comboIndex" static StrConstant PSX_TUD_BLOCK_INDEX = "blockIndex" -static Constant PSX_GUI_SETTINGS_VERSION = 3 +static Constant PSX_GUI_SETTINGS_VERSION = 4 static StrConstant PSX_GUI_SETTINGS_PSX = "GuiSettingsPSX" @@ -2335,7 +2335,7 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, numEvents = DimSize(eventIndexFromTraces, ROWS) Make/WAVE/FREE/N=(numEvents) contAverageAll, contAverageAccept, contAverageReject, contAverageUndet - Make/FREE/D/N=(numEvents) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp + Make/FREE=1/D/N=(numEvents) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp for(i = 0; i < numEvents; i += 1) idx = str2num(eventIndexFromTraces[i]) @@ -2348,23 +2348,22 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, WAVE/SDFR=singleEventDFR singleEvent = $name - switch(stateNew[i]) - case PSX_ACCEPT: - contAverageAccept[acceptIndex] = singleEvent - - WAVE sweepDataOffFilt = GetPSXSweepDataOffFiltWaveFromDFR(comboDFR) - WAVE psxEvent = GetPSXEventWaveFromDFR(comboDFR) + WAVE sweepDataOffFilt = GetPSXSweepDataOffFiltWaveFromDFR(comboDFR) + WAVE psxEvent = GetPSXEventWaveFromDFR(comboDFR) - // single event waves are zeroed in x-direction to extractStartAbs - [extractStartAbs, extractStopAbs] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, idx) - eventStopTime[acceptIndex] = extractStopAbs - extractStartAbs - eventOnsetTime[acceptIndex] = psxEvent[idx][%$"Onset Time"] - extractStartAbs - eventPeakTime[acceptIndex] = psxEvent[idx][%peak_t] - extractStartAbs + // single event waves are zeroed in x-direction to extractStartAbs + [extractStartAbs, extractStopAbs] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, idx) + eventStopTime[idx] = extractStopAbs - extractStartAbs + eventOnsetTime[idx] = psxEvent[idx][%$"Onset Time"] - extractStartAbs + eventPeakTime[idx] = psxEvent[idx][%peak_t] - extractStartAbs - path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL - eventKernelAmp[acceptIndex] = JWN_GetNumberFromWaveNote(psxEvent, path + "/amp") + path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL + eventKernelAmp[idx] = JWN_GetNumberFromWaveNote(psxEvent, path + "/amp") - acceptIndex += 1 + switch(stateNew[i]) + case PSX_ACCEPT: + contAverageAccept[acceptIndex] = singleEvent + acceptIndex += 1 break case PSX_REJECT: contAverageReject[rejectIndex] = singleEvent @@ -2384,12 +2383,16 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, DFREF averageDFR = PSX_GetAverageFolder(win) PSX_UpdateAverageWave(contAverageAccept, acceptIndex, averageDFR, PSX_ACCEPT) + PSX_FitAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp, stateNew, PSX_ACCEPT) + PSX_UpdateAverageWave(contAverageReject, rejectIndex, averageDFR, PSX_REJECT) + PSX_FitAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp, stateNew, PSX_REJECT) + PSX_UpdateAverageWave(contAverageUndet, undetIndex, averageDFR, PSX_UNDET) - PSX_UpdateAverageWave(contAverageAll, numEvents, averageDFR, PSX_ALL) + PSX_FitAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp, stateNew, PSX_UNDET) - Redimension/N=(acceptIndex) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp - PSX_FitAcceptAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp) + PSX_UpdateAverageWave(contAverageAll, numEvents, averageDFR, PSX_ALL) + PSX_FitAverage(win, averageDFR, eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp, stateNew, PSX_ALL) End /// @brief Helper function to update the average waves for the all event graph @@ -2413,66 +2416,89 @@ static Function/DF PSX_GetAverageFolder(string win) return PSX_GetWorkingFolder(win) End -static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOnsetTime, WAVE eventPeakTime, WAVE eventStopTime, WAVE eventKernelAmp) +Function PSX_UpdateInfoButtonHelp(string win, WAVE/T help, variable state) + + string ctrl + + ctrl = "button_fit_results_" + LowerStr(PSX_StateToString(state)) + + UpdateInfoButtonHelp(win, ctrl, help) +End + +Function PSX_IsFitEnabled(string win, variable state) + + string ctrl + + ctrl = "checkbox_events_fit_" + LowerStr(PSX_StateToString(state)) + + return GetCheckBoxState(win, ctrl) +End + +Function PSX_FilterFitAverageParameters(WAVE input, WAVE newState, variable state, [variable requireFiniteResult]) + + variable numEvents = DimSize(newState, ROWS) + + if(ParamIsDefault(requireFiniteResult)) + requireFiniteResult = 0 + else + requireFiniteResult = !!requireFiniteResult + endif + + Make/FREE/N=(numEvents) result = (newState[p] & state) ? input[p] : NaN + + WAVE/Z resultClean = ZapNaNs(result) + if(WaveExists(resultClean)) + return mean(resultClean) + endif + + ASSERT(!requireFiniteResult, "Could not find any events with finite " + NameOfWave(input)) + + return Inf +End + +static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime, WAVE eventPeakTime, WAVE eventStopTime, WAVE eventKernelAmp, WAVE newState, variable state) string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc variable err, numAveragePoints, start, stop, meanStopTime, meanOnsetTime, meanPeakTime, meanKernelAmp variable xStart, xEnd, yStart, yEnd, riselowerThreshold, riseAndDecayUpperThreshold variable extrema, extrema_t, edge, riseStart, riseStop, decayStart, decayStop, wTau, backwardEdge - WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) - WAVE acceptedRiseAverageFit = GetPSXAcceptedRiseAverageFitWaveFromDFR(averageDFR) - WAVE acceptedDecayAverageFit = GetPSXAcceptedDecayAverageFitWaveFromDFR(averageDFR) + WAVE AverageFit = GetPSXAverageFitWaveFromDFR(averageDFR, state) + WAVE RiseAverageFit = GetPSXRiseAverageFitWaveFromDFR(averageDFR, state) + WAVE DecayAverageFit = GetPSXDecayAverageFitWaveFromDFR(averageDFR, state) specialEventPanel = PSX_GetSpecialPanel(win) Make/T/FREE help = {PSX_AVERAGE_FIT_RESULT_DEFAULT_HELP} - UpdateInfoButtonHelp(specialEventPanel, "button_fit_results", help) + PSX_UpdateInfoButtonHelp(specialEventPanel, help, state) - if(!GetCheckBoxState(specialEventPanel, "checkbox_average_events_fit")) - FastOp acceptedAverageFit = (NaN) - FastOp acceptedRiseAverageFit = (NaN) - FastOp acceptedDecayAverageFit = (NaN) + if(!PSX_IsFitEnabled(specialEventPanel, state)) + FastOp AverageFit = (NaN) + FastOp RiseAverageFit = (NaN) + FastOp DecayAverageFit = (NaN) return NaN endif - WAVE average = GetPSXAverageWave(averageDFR, PSX_ACCEPT) + WAVE average = GetPSXAverageWave(averageDFR, state) numAveragePoints = DimSize(average, ROWS) if(numAveragePoints == 0 || !HasOneValidEntry(average)) - FastOp acceptedAverageFit = (NaN) - FastOp acceptedRiseAverageFit = (NaN) - FastOp acceptedDecayAverageFit = (NaN) + FastOp AverageFit = (NaN) + FastOp RiseAverageFit = (NaN) + FastOp DecayAverageFit = (NaN) return NaN endif - Redimension/N=(numAveragePoints) acceptedAverageFit, acceptedRiseAverageFit, acceptedDecayAverageFit - FastOp acceptedAverageFit = (NaN) - FastOp acceptedRiseAverageFit = (NaN) - FastOp acceptedDecayAverageFit = (NaN) - CopyScales average, acceptedAverageFit, acceptedRiseAverageFit, acceptedDecayAverageFit - - WAVE/Z eventOnsetTimeClean = ZapNaNs(eventOnsetTime) - if(WaveExists(eventOnsetTimeClean)) - meanOnsetTime = mean(eventOnsetTimeClean) - else - meanOnsetTime = Inf - endif - - WAVE/Z eventStopTimeClean = ZapNaNs(eventStopTime) - if(WaveExists(eventStopTimeClean)) - meanStopTime = mean(eventStopTimeClean) - else - meanStopTime = Inf - endif - - WAVE/Z eventKernelAmpClean = ZapNaNs(eventKernelAmp) - ASSERT(WaveExists(eventKernelAmpClean), "Could not find any events with finite kernelAmp") - meanKernelAmp = mean(eventKernelAmpClean) + Redimension/N=(numAveragePoints) AverageFit, RiseAverageFit, DecayAverageFit + FastOp AverageFit = (NaN) + FastOp RiseAverageFit = (NaN) + FastOp DecayAverageFit = (NaN) + CopyScales average, AverageFit, RiseAverageFit, DecayAverageFit - WAVE/Z eventPeakTimeClean = ZapNaNs(eventPeakTime) - ASSERT(WaveExists(eventPeakTimeClean), "Could not find any events with finite peak_t") - meanPeakTime = mean(eventPeakTime) + meanOnsetTime = PSX_FilterFitAverageParameters(eventOnsetTime, newState, state) + meanStopTime = PSX_FilterFitAverageParameters(eventStopTime, newState, state) + meanKernelAmp = PSX_FilterFitAverageParameters(eventKernelAmp, newState, state, requireFiniteResult = 1) + meanPeakTime = PSX_FilterFitAverageParameters(eventPeakTime, newState, state, requireFiniteResult = 1) WaveStats/M=1/Q average @@ -2509,19 +2535,19 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns Make/FREE/D/N=5 coefWave Make/FREE/T coeffNames = {"y0", "A", "tau1", "tau2", "X0"} - CurveFit/M=0/Q/N=2 dblexp_peak, kwCWave=coefWave, average(riseStart, decayStop)/D=acceptedAverageFit; err = GetRTError(1) + CurveFit/M=0/Q/N=2 dblexp_peak, kwCWave=coefWave, average(riseStart, decayStop)/D=AverageFit; err = GetRTError(1) InputAvg[0][0, 1] = {{"Function"}, {"dblexp_peak"}} InputAvg[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} InputAvg[2, 6][0] = coeffNames[p - 2] InputAvg[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - InputAvg[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedAverageFit))}} + InputAvg[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(AverageFit))}} InputAvg[8][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} InputAvg[9][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} Make/FREE/D/N=5 coefWave Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} - CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(riseStart, riseStop)/D=acceptedRiseAverageFit; err = GetRTError(1) + CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(riseStart, riseStop)/D=RiseAverageFit; err = GetRTError(1) wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) @@ -2529,14 +2555,14 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns InputRise[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} InputRise[2, 6][0] = coeffNames[p - 2] InputRise[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - InputRise[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedRiseAverageFit))}} + InputRise[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(RiseAverageFit))}} InputRise[8][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} InputRise[9][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} InputRise[10][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} Make/FREE/D/N=5 coefWave Make/FREE/T coeffNames = {"y0", "A1", "tau1", "A2", "tau2"} - CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(decayStart, decayStop)/D=acceptedDecayAverageFit; err = GetRTError(1) + CurveFit/M=0/Q/N=1 dblexp_XOffset, kwCWave=coefWave, average(decayStart, decayStop)/D=DecayAverageFit; err = GetRTError(1) wTau = ((coefWave[1] * coefWave[2] + coefWave[3] * coefWave[4]) / (coefWave[1] + coefWave[3])) @@ -2544,7 +2570,7 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns InputDecay[1][0, 1] = {{"ChiSq"}, {num2strHighPrec(V_chiSq)}} InputDecay[2, 6][0] = coeffNames[p - 2] InputDecay[2, 6][1] = num2strHighPrec(coefWave[p - 2]) - InputDecay[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(acceptedDecayAverageFit))}} + InputDecay[7][0, 1] = {{"Maximum"}, {num2strHighPrec(WaveMax(DecayAverageFit))}} InputDecay[8][0, 1] = {{"weighted tau"}, {num2strHighPrec(wTau)}} InputDecay[9][0, 1] = {{"State source"}, {PSX_GetStateTypeFromSpecialPanel(win)}} InputDecay[10][0, 1] = {{"Current combo"}, {ToTrueFalse(PSX_GetrestrictEventsToCurrentCombo(win))}} @@ -2558,7 +2584,7 @@ static Function PSX_FitAcceptAverage(string win, DFREF averageDFR, WAVE eventOns Concatenate/NP/FREE {InputAvg, InputRise, InputDecay}, input - UpdateInfoButtonHelp(specialEventPanel, "button_fit_results", input) + PSX_UpdateInfoButtonHelp(specialEventPanel, input, state) browser = SFH_GetBrowserForFormulaGraph(win) PSX_StoreIntoResultsWave(browser, SFH_RESULT_TYPE_PSX_MISC, input, "accepted average fit results") @@ -2934,7 +2960,7 @@ End static Function PSX_AppendAverageTraces(string extAllGraph, DFREF averageDFR, string traceSuffix, variable idx, string comboKey, variable comboIndex, WAVE traceUserDataKeys, WAVE states, WAVE acceptColors, WAVE rejectColors, WAVE undetColors) variable state - string trace, traceAvgFit, traceRiseAvgFit, traceDecayAvgFit + string trace, stateAsString, nameTemplate for(state : states) @@ -2953,35 +2979,53 @@ static Function PSX_AppendAverageTraces(string extAllGraph, DFREF averageDFR, st TUD_SetUserDataFromWaves(extAllGraph, trace, \ traceUserDataKeys, \ {"NaN", num2str(state), num2str(state), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + idx += 1 - endfor - WAVE acceptedAverageFit = GetPSXAcceptedAverageFitWaveFromDFR(averageDFR) - WAVE acceptedRiseAverageFit = GetPSXAcceptedRiseAverageFitWaveFromDFR(averageDFR) - WAVE acceptedDecayAverageFit = GetPSXAcceptedDecayAverageFitWaveFromDFR(averageDFR) + stateAsString = PSX_StateToString(state) + + // averageFit + WAVE averageFit = GetPSXAverageFitWaveFromDFR(averageDFR, state) + + nameTemplate = stateAsString + "_AverageFit" + trace = PSX_GetAverageTraceName(idx, nameTemplate, comboIndex, traceSuffix) + + AppendToGraph/W=$extAllGraph averageFit/TN=$trace + + TUD_SetUserDataFromWaves(extAllGraph, trace, \ + traceUserDataKeys, \ + {"NaN", num2str(state), num2str(state), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + + idx += 1 + + // riseAverageFit + WAVE riseAverageFit = GetPSXRiseAverageFitWaveFromDFR(averageDFR, state) + + nameTemplate = stateAsString + "_RiseAverageFit" + trace = PSX_GetAverageTraceName(idx, nameTemplate, comboIndex, traceSuffix) - traceAvgFit = PSX_GetAverageTraceName(idx, "acceptAverageFit", comboIndex, traceSuffix) - idx += 1 + AppendToGraph/W=$extAllGraph riseAverageFit/TN=$trace + + TUD_SetUserDataFromWaves(extAllGraph, trace, \ + traceUserDataKeys, \ + {"NaN", num2str(state), num2str(state), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) - traceRiseAvgFit = PSX_GetAverageTraceName(idx, "acceptRiseAverageFit", comboIndex, traceSuffix) - idx += 1 + idx += 1 - traceDecayAvgFit = PSX_GetAverageTraceName(idx, "acceptDecayAverageFit", comboIndex, traceSuffix) - idx += 1 + // decayAverageFit + WAVE decayAverageFit = GetPSXDecayAverageFitWaveFromDFR(averageDFR, state) - AppendToGraph/W=$extAllGraph acceptedAverageFit/TN=$traceAvgFit, acceptedRiseAverageFit/TN=$traceRiseAvgFit, acceptedDecayAverageFit/TN=$traceDecayAvgFit + nameTemplate = stateAsString + "_DecayAverageFit" + trace = PSX_GetAverageTraceName(idx, nameTemplate, comboIndex, traceSuffix) - TUD_SetUserDataFromWaves(extAllGraph, traceAvgFit, \ - traceUserDataKeys, \ - {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + AppendToGraph/W=$extAllGraph decayAverageFit/TN=$trace - TUD_SetUserDataFromWaves(extAllGraph, traceRiseAvgFit, \ - traceUserDataKeys, \ - {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + TUD_SetUserDataFromWaves(extAllGraph, trace, \ + traceUserDataKeys, \ + {"NaN", num2str(state), num2str(state), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) - TUD_SetUserDataFromWaves(extAllGraph, traceDecayAvgFit, \ - traceUserDataKeys, \ - {"NaN", num2str(PSX_ACCEPT), num2str(PSX_ACCEPT), "0", PSX_TUD_TYPE_AVERAGE, comboKey, "NaN", num2str(comboIndex)}) + idx += 1 + endfor return idx End @@ -5969,7 +6013,7 @@ Function PSX_CheckboxProcChangeRestrictCurrentCombo(STRUCT WMCheckboxAction &cba endswitch End -Function PSX_CheckboxProcFitAcceptAverage(STRUCT WMCheckboxAction &cba) : CheckboxControl +Function PSX_CheckboxProcFitAverage(STRUCT WMCheckboxAction &cba) : CheckboxControl switch(cba.eventCode) case 2: // mouse up @@ -6048,7 +6092,7 @@ Function PSX_PlotStartupSettings() // propagate changes as ResizeControls queries the guides as well DoUpdate/W=$win - Make/T/FREE infoButtons = {"button_psx_info", "button_fit_results"} + Make/T/FREE infoButtons = {"button_psx_info", "button_accept_fit_results", "button_reject_fit_results", "button_undet_fit_results", "button_all_fit_results"} WAVE/T subwindows = ListToTextWave(GetAllWindows(win), ";") for(subwin : subwindows) @@ -6101,7 +6145,10 @@ Function PSX_PlotStartupSettings() CheckBox checkbox_average_events_reject, value=0, win=$specialEventPanel CheckBox checkbox_average_events_undetermined, value=0, win=$specialEventPanel CheckBox checkbox_average_events_all, value=0, win=$specialEventPanel - CheckBox checkbox_average_events_fit, value=0, win=$specialEventPanel + CheckBox checkbox_events_fit_accept, value=0, win=$specialEventPanel + CheckBox checkbox_events_fit_reject, value=0, win=$specialEventPanel + CheckBox checkbox_events_fit_undetermined, value=0, win=$specialEventPanel + CheckBox checkbox_events_fit_all, value=0, win=$specialEventPanel CheckBox checkbox_restrict_events_to_current_combination, value=0, win=$specialEventPanel SetVariable setvar_event_block_size, value=_NUM:100, win=$specialEventPanel diff --git a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf index b1d27dc620..eede49c38c 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX_Macro.ipf @@ -8,7 +8,7 @@ Window PSXPanel() : Panel PauseUpdate; Silent 1 // building window... - NewPanel/K=1/W=(196, 1011, 1450, 1491) as "SweepFormula plot from " + NewPanel/K=1/W=(27, 1010, 1281, 1490) as "SweepFormula plot from " SetDrawLayer UserBack SetDrawEnv pop DrawText 47, 475, "UI" @@ -40,8 +40,7 @@ Window PSXPanel() : Panel CheckBox checkbox_show_deconv_lines, userdata(ResizeControlsInfo)+=A"zzz!!#u:Du]k- none -"} - Button button_psx_info, userdata="- none -" + Button button_psx_info, title="i", help={"
"}, userdata="- none -\r\n"
 	Button button_psx_info, userdata(ResizeControlsInfo)=A"!!,BI!!#CJ!!#- none -"}
-	Button button_fit_results, userdata="- none -"
-	GroupBox group_event, pos={14.00, 13.00}, size={69.00, 122.00}, title="Event"
+	CheckBox checkbox_events_fit_accept, pos={181.00, 33.00}, size={14.00, 14.00}, proc=PSX_CheckboxProcFitAverage
+	CheckBox checkbox_events_fit_accept, title=""
+	CheckBox checkbox_events_fit_accept, help={"Fit the accept average and store the outcome in the results wave"}
+	CheckBox checkbox_events_fit_accept, value=0
+	Button button_fit_results_accept, pos={199.00, 32.00}, size={18.00, 16.00}, proc=PSX_CopyHelpToClipboard
+	Button button_fit_results_accept, title="i", help={"
- none -
"} + Button button_fit_results_accept, userdata="- none -" + GroupBox group_event, pos={14.00, 13.00}, size={67.00, 99.00}, title="Event" GroupBox group_event, help={"Toggle the display of the event traces"} - SetVariable setvar_event_block_size, pos={13.00, 215.00}, size={120.00, 18.00}, bodyWidth=44, proc=PSX_SetVarBlockSize + SetVariable setvar_event_block_size, pos={14.00, 205.00}, size={120.00, 18.00}, bodyWidth=44, proc=PSX_SetVarBlockSize SetVariable setvar_event_block_size, title="Block size [%]" SetVariable setvar_event_block_size, help={"Allows to restrict the all event graph to only a percentage of the events."} SetVariable setvar_event_block_size, limits={0, 100, 1}, value=_NUM:100 - PopupMenu popup_block, pos={51.00, 238.00}, size={82.00, 19.00}, bodyWidth=50, proc=PSX_PopupMenuBlockNumber + PopupMenu popup_block, pos={14.00, 228.00}, size={82.00, 19.00}, bodyWidth=50, proc=PSX_PopupMenuBlockNumber PopupMenu popup_block, title="Block" PopupMenu popup_block, help={"Select which of the event blocks to display"} PopupMenu popup_block, userdata(NumberOfBlocks)="1" PopupMenu popup_block, mode=1, popvalue="", value=#"\"\"" - PopupMenu popupmenu_event_offset, pos={136.00, 168.00}, size={53.00, 19.00}, proc=PSX_PopupMenuState + PopupMenu popupmenu_event_offset, pos={14.00, 157.00}, size={53.00, 19.00}, proc=PSX_PopupMenuState PopupMenu popupmenu_event_offset, help={"Select the time point in x direction for aligning the single event traces in the all event graph"} PopupMenu popupmenu_event_offset, mode=1, popvalue="Onset", value=#"\"Onset;Peak;Slew\"" - SetVariable setvar_fit_start_amplitude, pos={13.00, 193.00}, size={144.00, 18.00}, bodyWidth=44, proc=PSX_FitStartAmplitude + SetVariable setvar_fit_start_amplitude, pos={14.00, 182.00}, size={144.00, 18.00}, bodyWidth=44, proc=PSX_FitStartAmplitude SetVariable setvar_fit_start_amplitude, title="Fit start amplitude" SetVariable setvar_fit_start_amplitude, help={"Percentage of the amplitude used to define the fit start point."} - SetVariable setvar_fit_start_amplitude, limits={0, 100, 1}, value=_NUM:20, proc=PSX_FitStartAmplitude - DefineGuide leftMenu={FL, 0.2, FR}, horizCenter={leftMenu, 0.5, FR} + SetVariable setvar_fit_start_amplitude, limits={0, 100, 1}, value=_NUM:20 + GroupBox group_fit, pos={172.00, 14.00}, size={54.00, 98.00}, title="Fit" + Button button_fit_results_reject, pos={199.00, 52.00}, size={18.00, 16.00}, proc=PSX_CopyHelpToClipboard + Button button_fit_results_reject, title="i", help={"
- none -
"} + Button button_fit_results_reject, userdata="- none -" + CheckBox checkbox_events_fit_reject, pos={181.00, 53.00}, size={14.00, 14.00}, proc=PSX_CheckboxProcFitAverage + CheckBox checkbox_events_fit_reject, title="" + CheckBox checkbox_events_fit_reject, help={"Fit the reject average and store the outcome in the results wave"} + CheckBox checkbox_events_fit_reject, value=0 + Button button_fit_results_undetermined, pos={199.00, 70.00}, size={18.00, 16.00}, proc=PSX_CopyHelpToClipboard + Button button_fit_results_undetermined, title="i", help={"
- none -
"} + Button button_fit_results_undetermined, userdata="- none -" + CheckBox checkbox_events_fit_undetermined, pos={181.00, 72.00}, size={14.00, 14.00}, proc=PSX_CheckboxProcFitAverage + CheckBox checkbox_events_fit_undetermined, title="" + CheckBox checkbox_events_fit_undetermined, help={"Fit the undet average and store the outcome in the results wave"} + CheckBox checkbox_events_fit_undetermined, value=0 + Button button_fit_results_all, pos={199.00, 90.00}, size={18.00, 16.00}, proc=PSX_CopyHelpToClipboard + Button button_fit_results_all, title="i", help={"
- none -
"} + Button button_fit_results_all, userdata="- none -" + CheckBox checkbox_events_fit_all, pos={181.00, 91.00}, size={14.00, 14.00}, proc=PSX_CheckboxProcFitAverage + CheckBox checkbox_events_fit_all, title="" + CheckBox checkbox_events_fit_all, help={"Fit the all average and store the outcome in the results wave"} + CheckBox checkbox_events_fit_all, value=0 + DefineGuide leftMenu={FL, 0.214995, FR}, horizCenter={leftMenu, 0.5, FR} SetWindow kwTopWin, hook(resetScaling)=IH_ResetScaling SetWindow kwTopWin, hook(ctrl)=PSX_AllEventGraphHook - Execute/Q/Z "SetWindow kwTopWin sizeLimit={750,200.25,inf,inf}" // sizeLimit requires Igor 7 or later + Execute/Q/Z "SetWindow kwTopWin sizeLimit={820,200.25,inf,inf}" // sizeLimit requires Igor 7 or later Display/W=(225, 62, 675, 188)/FG=(horizCenter, FT, FR, FB)/HOST=# RenameWindow #, Single SetActiveSubwindow ## - Display/W=(225, 62, 675, 188)/FG=(leftMenu, FT, horizCenter, FB)/HOST=# + Display/W=(287, 62, 675, 188)/FG=(leftMenu, FT, horizCenter, FB)/HOST=# RenameWindow #, All SetActiveSubwindow ## RenameWindow #, SpecialEventPanel diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index 2b7d2955ae..a0b16d9efe 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8723,41 +8723,47 @@ Function/WAVE GetPSXAverageWave(DFREF dfr, variable state) return wv End -Function/WAVE GetPSXAcceptedRiseAverageFitWaveFromDFR(DFREF dfr) +Function/WAVE GetPSXRiseAverageFitWaveFromDFR(DFREF dfr, variable state) - WAVE/Z/D/SDFR=dfr wv = acceptedRiseAverageFit + string name = "riseAverageFit_" + PSX_StateToString(state) + + WAVE/Z/D/SDFR=dfr wv = $name if(WaveExists(wv)) return wv endif - Make/D/N=(0) dfr:acceptedRiseAverageFit/WAVE=wv + Make/D/N=(0) dfr:$name/WAVE=wv return wv End -Function/WAVE GetPSXAcceptedDecayAverageFitWaveFromDFR(DFREF dfr) +Function/WAVE GetPSXDecayAverageFitWaveFromDFR(DFREF dfr, variable state) - WAVE/Z/D/SDFR=dfr wv = acceptedDecayAverageFit + string name = "decayAverageFit_" + PSX_StateToString(state) + + WAVE/Z/D/SDFR=dfr wv = $name if(WaveExists(wv)) return wv endif - Make/D/N=(0) dfr:acceptedDecayAverageFit/WAVE=wv + Make/D/N=(0) dfr:$name/WAVE=wv return wv End -Function/WAVE GetPSXAcceptedAverageFitWaveFromDFR(DFREF dfr) +Function/WAVE GetPSXAverageFitWaveFromDFR(DFREF dfr, variable state) + + string name = "averageFit_" + PSX_StateToString(state) - WAVE/Z/D/SDFR=dfr wv = acceptedAverageFit + WAVE/Z/D/SDFR=dfr wv = $name if(WaveExists(wv)) return wv endif - Make/D/N=(0) dfr:acceptedAverageFit/WAVE=wv + Make/D/N=(0) dfr:$name/WAVE=wv return wv End From 71c1cc5be1d576376e00463d5d94641a071f30cb Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Thu, 5 Jun 2025 20:55:46 +0200 Subject: [PATCH 35/57] MIES_Utilities_Algorithm.ipf: Add helper routines for DoFFT These routines allow to calculate all values between 2^1 and 2^30 which have 2 and 3 as prime factors. This helps in deciding a good cut off value for FFT as it only performs when the primes are smaller than 5. --- Packages/MIES/MIES_Utilities_Algorithm.ipf | 64 ++++++++++++++ Packages/tests/Basic/UTF_Utils_Algorithm.ipf | 90 ++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/Packages/MIES/MIES_Utilities_Algorithm.ipf b/Packages/MIES/MIES_Utilities_Algorithm.ipf index 6153befd34..fbfe540f64 100644 --- a/Packages/MIES/MIES_Utilities_Algorithm.ipf +++ b/Packages/MIES/MIES_Utilities_Algorithm.ipf @@ -6,6 +6,8 @@ #pragma ModuleName = MIES_UTILS_ALGORITHM #endif // AUTOMATED_TESTING +static Constant FFT_PRIME_FACTOR_LIMIT = 30 + /// @file MIES_Utilities_Algorithm.ipf /// @brief utility functions for common algorithms @@ -1275,3 +1277,65 @@ Function FindSequenceReverseWrapper(WAVE sequence, WAVE source) start = foundIndex + 1 endfor End + +/// @brief Return all prime factors from num as free wave +threadsafe Function/WAVE GetPrimeFactors(variable num) + + PrimeFactors/Q num + + WAVE W_primeFactors + MakeWaveFree(W_primeFactors) + + return W_primeFactors +End + +threadsafe static Function/WAVE FFTHelperSeriesImpl(variable n, variable a, variable b) + + ASSERT_TS(n >= 1 && n < 30, "Invalid range") + + variable numEntries = ceil(n / 3) + variable lambda, i + + Make/FREE/D/N=(numEntries) results + + for(i = 0; i < numEntries; i += 1) + lambda = n - (a + 3 * i) + if(lambda < 0) + break + endif + + results[i] = 3^(b + 2 * i) * 2^(lambda) + endfor + + Redimension/N=(i) results + + return results +End + +threadsafe Function/WAVE FFTHelperSilverSeries(variable n) + + return FFTHelperSeriesImpl(n, 3, 2) +End + +threadsafe Function/WAVE FFTHelperGoldSeries(variable n) + + return FFTHelperSeriesImpl(n, 1, 1) +End + +// @brief Returns a wave with all values which only have 2's and 3's as prime factors up to 2^30 +threadsafe Function/WAVE GetGoodFFTSizes() + + Make/FREE/WAVE/N=(FFT_PRIME_FACTOR_LIMIT) gold, silver + gold[0] = NewFreeWave(IGOR_TYPE_64BIT_FLOAT, 0) + gold[1, Inf] = FFTHelperGoldSeries(p) + + silver[0] = NewFreeWave(IGOR_TYPE_64BIT_FLOAT, 0) + silver[1, Inf] = FFTHelperSilverSeries(p) + + Concatenate/FREE/NP=(ROWS) {gold, silver}, resultsWave + Concatenate/FREE/NP=(ROWS) {resultsWave}, results + + Sort results, results + + return results +End diff --git a/Packages/tests/Basic/UTF_Utils_Algorithm.ipf b/Packages/tests/Basic/UTF_Utils_Algorithm.ipf index cfcf370054..237f50f50a 100644 --- a/Packages/tests/Basic/UTF_Utils_Algorithm.ipf +++ b/Packages/tests/Basic/UTF_Utils_Algorithm.ipf @@ -1326,3 +1326,93 @@ static Function TestFindSequenceReverseWrapper() End /// @} + +static Function TestFFTHelperSilverSeries() + + try + MIES_UTILS_ALGORITHM#FFTHelperSilverSeries(0) + FAIL() + catch + CHECK_NO_RTE() + endtry + + try + MIES_UTILS_ALGORITHM#FFTHelperSilverSeries(30) + FAIL() + catch + CHECK_NO_RTE() + endtry + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperSilverSeries(4) + CHECK_EQUAL_WAVES(results, {18}, mode = WAVE_DATA) + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperSilverSeries(5) + CHECK_EQUAL_WAVES(results, {36}, mode = WAVE_DATA) + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperSilverSeries(7) + CHECK_EQUAL_WAVES(results, {144, 162}, mode = WAVE_DATA) +End + +static Function TestFFTHelperGoldSeries() + + try + MIES_UTILS_ALGORITHM#FFTHelperGoldSeries(0) + FAIL() + catch + CHECK_NO_RTE() + endtry + + try + MIES_UTILS_ALGORITHM#FFTHelperGoldSeries(30) + FAIL() + catch + CHECK_NO_RTE() + endtry + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperGoldSeries(4) + CHECK_EQUAL_WAVES(results, {24, 27}, mode = WAVE_DATA) + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperGoldSeries(5) + CHECK_EQUAL_WAVES(results, {48, 54}, mode = WAVE_DATA) + + WAVE results = MIES_UTILS_ALGORITHM#FFTHelperGoldSeries(7) + CHECK_EQUAL_WAVES(results, {192, 216, 243}, mode = WAVE_DATA) +End + +static Function CheckPrimeFactors(variable num) + + variable numEntries + + WAVE primes = GetPrimeFactors(num) + WAVE/Z result = GetUniqueEntries(primes) + CHECK_WAVE(result, NUMERIC_WAVE) + numEntries = DimSize(result, ROWS) + + Sort result, result + + INFO("result: %s", s0 = NumericWaveToList(result, ", ", trailSep = 0)) + + switch(numEntries) + case 1: + CHECK(result[0] == 2 || result[0] == 3) // NOLINT + break + case 2: + CHECK_EQUAL_WAVES(result, {2, 3}, mode = WAVE_DATA) + break + default: + FAIL() + endswitch +End + +static Function TestGetGoodFFTSizes() + + variable numEntries + + WAVE wv = GetGoodFFTSizes() + numEntries = DimSize(wv, ROWS) + CHECK_GT_VAR(numEntries, 0) + + Make/FREE/N=(numEntries) junkWave + + junkWave[] = CheckPrimeFactors(wv[p]) +End From 75558152505d36d110111566831eab7a46144994 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 6 Jun 2025 19:08:26 +0200 Subject: [PATCH 36/57] PSX: Implement sweep data shortening for faster FFT The FFT implementation in Igor Pro requires the prime factors of the length to be < 5 for speedy execution. For some cases these prime factors can be very large (~1e5) and thus FFT would be dead slow, i.e. in the order of minutes on common machines. We now employ a data shortening to a size which has only 2 and 3 as prime factors, which makes FFT very fast. This is only done if we have prime factors larger than a 1000 in the original size. --- Packages/MIES/MIES_Cache.ipf | 6 ++++ Packages/MIES/MIES_SweepFormula_PSX.ipf | 34 +++++++++++++++------- Packages/MIES/MIES_Utilities_Algorithm.ipf | 30 +++++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Packages/MIES/MIES_Cache.ipf b/Packages/MIES/MIES_Cache.ipf index 7dfdac6550..21c730bdf7 100644 --- a/Packages/MIES/MIES_Cache.ipf +++ b/Packages/MIES/MIES_Cache.ipf @@ -582,6 +582,12 @@ threadsafe Function/S CA_CalculateFetchEpochsKey(WAVE numericalvalues, WAVE text return "Version 1:" + Hash(key + num2istr(crc), HASH_SHA2_256) End + +Function/S CA_GetGoodFFTSizesKeys() + + return "GetGoodFFTSizes Version 1" +End + ///@} /// @brief Make space for one new entry in the cache waves diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index e65d73f458..cdabf96404 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -357,7 +357,7 @@ End /// @param deconvFilter deconvolution filter settings static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAVE sweepData, WAVE/C psxKernelFFT, WAVE deconvFilter) - variable numPoints, fftSize, samp, low, high, maxOrder + variable numPoints, samp, low, high, maxOrder low = deconvFilter[%$"Filter Low"] high = deconvFilter[%$"Filter High"] @@ -376,11 +376,11 @@ static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAV endif numPoints = DimSize(sweepData, ROWS) - fftSize = DimSize(psxKernelFFT, ROWS) // no window function on purpose WAVE/C outputFFT = DoFFT(sweepData, padSize = numPoints) + ASSERT(DimSize(outputFFT, ROWS) == DimSize(psxKernelFFT, ROWS), "Unmatched wave sizes") Multithread outputFFT[] = outputFFT[p] / (psxKernelFFT[p] + 1e-5) IFFT/DEST=Deconv/FREE outputFFT @@ -5183,8 +5183,8 @@ End // psxKernel([select(...), riseTau, decayTau, amp]) Function/WAVE PSX_OperationKernel(STRUCT SF_ExecutionData &exd) - variable riseTau, decayTau, amp, dt, numPoints, numCombos, i, offset, idx - string parameterPath, key + variable riseTau, decayTau, amp, dt, numPoints, numCombos, i, offset, idx, shorterPerc + string parameterPath, key, bsPanel, comboKey, msg SFH_CheckArgumentCount(exd, SF_OP_PSX_KERNEL, 0, maxArgs = 4) @@ -5210,11 +5210,13 @@ Function/WAVE PSX_OperationKernel(STRUCT SF_ExecutionData &exd) Make/FREE/N=(0) allResolvedRanges Make/FREE/N=(0)/T allSelectHashes + bsPanel = BSP_GetPanel(exd.graph) + for(i = 0; i < numCombos; i += 1) - WAVE/Z sweepData = sweepDataRef[i] - ASSERT(WaveExists(sweepData), "Can't handle invalid sweepData waves") + WAVE/Z sweepDataRaw = sweepDataRef[i] + ASSERT(WaveExists(sweepDataRaw), "Can't handle invalid sweepData waves") - [WAVE singleSelectData, WAVE range] = SFH_ParseToSelectDataWaveAndRange(sweepData) + [WAVE singleSelectData, WAVE range] = SFH_ParseToSelectDataWaveAndRange(sweepDataRaw) [WAVE resolvedRanges, WAVE/T epochRangeNames] = SFH_GetNumericRangeFromEpochFromSingleSelect(exd.graph, singleSelectData, range) @@ -5222,12 +5224,24 @@ Function/WAVE PSX_OperationKernel(STRUCT SF_ExecutionData &exd) continue endif - numPoints = DimSize(sweepData, ROWS) - dt = DimDelta(sweepData, ROWS) + numPoints = DimSize(sweepDataRaw, ROWS) if(IsOdd(numPoints)) // throw away one point so that FFT works - Redimension/N=(--numPoints) sweepData + Redimension/N=(--numPoints) sweepDataRaw + endif + + WAVE sweepData = ShortenWaveForFFTIfRequired(sweepDataRaw) + + numPoints = DimSize(sweepData, ROWS) + dt = DimDelta(sweepData, ROWS) + + if(numPoints != DimSize(sweepDataRaw, ROWS)) + comboKey = PSX_GenerateComboKey(exd.graph, singleSelectData, range) + shorterPerc = (DimSize(sweepDataRaw, ROWS) - numPoints) / DimSize(sweepDataRaw, ROWS) * ONE_TO_PERCENT + sprintf msg, "psxKernel: The sweep for combination \"%s\" has a very unfitting length and was therefore shortened by %.3g%%.", comboKey, shorterPerc + SF_SetOutputState(msg, SF_MSG_WARN) + SF_DisplayOutputStateInGUI(bsPanel) endif WAVE/WAVE result = PSX_GetPSXKernel(riseTau, decayTau, amp, numPoints, dt, range) diff --git a/Packages/MIES/MIES_Utilities_Algorithm.ipf b/Packages/MIES/MIES_Utilities_Algorithm.ipf index fbfe540f64..0d5b8efcad 100644 --- a/Packages/MIES/MIES_Utilities_Algorithm.ipf +++ b/Packages/MIES/MIES_Utilities_Algorithm.ipf @@ -1042,6 +1042,36 @@ threadsafe Function DoPowerSpectrum(WAVE input, WAVE output, variable col) output[][col] = magsqr(powerSpectrum[p]) End +/// @brief Cut off some trailing points from the wave to allow fast FFT +Function/WAVE ShortenWaveForFFTIfRequired(WAVE input) + + variable numRowsOpt, numRows + string key + + numRows = DimSize(input, ROWS) + WAVE primes = GetPrimeFactors(numRows) + + if(WaveMax(primes) > 1000) + + key = CA_GetGoodFFTSizesKeys() + WAVE/Z goodFFTSizes = CA_TryFetchingEntryFromCache(key) + + if(!WaveExists(goodFFTSizes)) + WAVE goodFFTSizes = GetGoodFFTSizes() + CA_StoreEntryIntoCache(key, goodFFTSizes) + endif + + FindLevel/Q goodFFTSizes, numRows + ASSERT_TS(!V_flag, "Could not find good FFT size") + + numRowsOpt = goodFFTSizes[trunc(V_LevelX)] + Duplicate/FREE/RMD=[0, numRowsOpt - 1] input, inputOpt + return inputOpt + endif + + return input +End + /// @brief Perform FFT on input with optionally given window function /// /// @param input Wave to perform FFT on From cbf5e673aeadbefb7c1b276cb94ffde29a66c12b Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 10 Jun 2025 16:04:00 +0200 Subject: [PATCH 37/57] PSX: Supply default values for filtering derived from the kernel We now reserve space for the defaults in the psxDeconvBPFilter/psxSweepBPFilter operations so that we can later on add the calculated defaults, which need parameters from psxKernel, can be added via PSX_AddDefaultFilterFrequencies. --- Packages/MIES/MIES_Constants.ipf | 6 +-- Packages/MIES/MIES_SweepFormula_PSX.ipf | 64 ++++++++++++++++++++----- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 285f91d74e..3df01770cc 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2428,10 +2428,8 @@ Constant PSX_HORIZ_OFFSET_PEAK = 1 Constant PSX_HORIZ_OFFSET_SLEW = 2 ///@} -Constant PSX_DECONV_FILTER_DEF_LOW = 500 -Constant PSX_DECONV_FILTER_DEF_HIGH = 50 -Constant PSX_SWEEP_FILTER_DEF_ORDER = 6 -Constant PSX_DECONV_FILTER_DEF_ORDER = 8 +Constant PSX_SWEEP_FILTER_DEF_ORDER = 4 +Constant PSX_DECONV_FILTER_DEF_ORDER = 4 StrConstant PSX_JWN_COMBO_KEYS_NAME = "ComboKeys" diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index cdabf96404..9389a4cf9d 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -282,8 +282,16 @@ static Function [variable realOrder, WAVE filtered] PSX_FilterSweepData(WAVE/Z s high = sweepFilter[%$"Filter High"] maxOrder = sweepFilter[%$"Filter Order"] + if(IsNaN(low)) + low = sweepFilter[%$"Filter Low (Default)"] + endif + + if(IsNaN(high)) + high = sweepFilter[%$"Filter High (Default)"] + endif + if(IsNaN(maxOrder)) - maxOrder = PSX_SWEEP_FILTER_DEF_ORDER + maxOrder = sweepFilter[%$"Filter Order (Default)"] endif [realOrder, WAVE filtered] = BandPassWithRingingDetection(sweepDataOff, high, low, maxOrder) @@ -350,6 +358,25 @@ static Function [WAVE sweepDataOff, variable offset] PSX_OffsetSweepData(WAVE sw return [output, offset] End +static Function PSX_AddDefaultFilterFrequencies(WAVE filter, variable riseTau, variable decayTau, variable filterType) + + variable fac + + switch(filterType) + case PSX_FILTER_SWEEP: + fac = 1.5 + break + case PSX_FILTER_DECONV: + fac = 2 + break + default: + FATAL_ERROR("Invalid case") + endswitch + + filter[%$"Filter Low (Default)"] = 1 / (fac * pi * riseTau * MILLI_TO_ONE) + filter[%$"Filter High (Default)"] = 1 / (fac * pi * decayTau * MILLI_TO_ONE) +End + /// @brief Return the deconvoluted sweep data /// /// @param sweepData data from a single sweep and channel *without* inserted TP @@ -364,15 +391,15 @@ static Function [variable realOrder, WAVE filtered] PSX_DeconvoluteSweepData(WAV maxOrder = deconvFilter[%$"Filter Order"] if(IsNaN(low)) - low = PSX_DECONV_FILTER_DEF_LOW + low = deconvFilter[%$"Filter Low (Default)"] endif if(IsNaN(high)) - high = PSX_DECONV_FILTER_DEF_HIGH + high = deconvFilter[%$"Filter High (Default)"] endif if(IsNaN(maxOrder)) - maxOrder = PSX_DECONV_FILTER_DEF_ORDER + maxOrder = deconvFilter[%$"Filter Order (Default)"] endif numPoints = DimSize(sweepData, ROWS) @@ -5021,6 +5048,15 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) ASSERT(IsNumericWave(riseTime), "Invalid return from psxRiseTime") WAVE deconvFilter = SFH_GetArgumentAsWave(exd, SF_OP_PSX, 6, defOp = "psxDeconvBPFilter()", singleResult = 1) + path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL + kernelRiseTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/riseTau") + ASSERT(IsFinite(kernelRiseTau), "riseTau must be finite") + kernelDecayTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/decayTau") + ASSERT(IsFinite(kernelDecayTau), "decayTau must be finite") + + PSX_AddDefaultFilterFrequencies(sweepFilter, kernelRiseTau, kernelDecayTau, PSX_FILTER_SWEEP) + PSX_AddDefaultFilterFrequencies(deconvFilter, kernelRiseTau, kernelDecayTau, PSX_FILTER_DECONV) + parameterJsonID = JWN_GetWaveNoteAsJSON(psxKernelDataset) parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX JSON_AddTreeObject(parameterJsonID, parameterPath) @@ -5032,16 +5068,24 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) JSON_AddVariable(parameterJsonID, parameterPath + "/upperThreshold", riseTime[%$"Upper Threshold"]) JSON_AddVariable(parameterJsonID, parameterPath + "/lowerThreshold", riseTime[%$"Lower Threshold"]) JSON_AddVariable(parameterJsonID, parameterPath + "/differentiateThreshold", riseTime[%$"Differentiate Threshold"]) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_DECONV_BP_FILTER JSON_AddTreeObject(parameterJsonID, parameterPath) JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", deconvFilter[%$"Filter Low"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", deconvFilter[%$"Filter High"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", deconvFilter[%$"Filter Order"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterLowDefault", deconvFilter[%$"Filter Low (Default)"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterHighDefault", deconvFilter[%$"Filter High (Default)"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrderDefault", deconvFilter[%$"Filter Order (Default)"]) + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_SWEEP_BP_FILTER JSON_AddTreeObject(parameterJsonID, parameterPath) JSON_AddVariable(parameterJsonID, parameterPath + "/filterLow", sweepFilter[%$"Filter Low"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterHigh", sweepFilter[%$"Filter High"]) JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrder", sweepFilter[%$"Filter Order"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterLowDefault", sweepFilter[%$"Filter Low (Default)"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterHighDefault", sweepFilter[%$"Filter High (Default)"]) + JSON_AddVariable(parameterJsonID, parameterPath + "/filterOrderDefault", sweepFilter[%$"Filter Order (Default)"]) numCombos = DimSize(psxKernelDataset, ROWS) / PSX_KERNEL_OUTPUTWAVES_PER_ENTRY ASSERT(IsInteger(numCombos) && numCombos > 0, "Invalid number of input sets from psxKernel()") @@ -5106,10 +5150,6 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL kernelAmp = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/amp") ASSERT(IsFinite(kernelAmp), "psxKernel amplitude must be finite") - kernelRiseTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/riseTau") - ASSERT(IsFinite(kernelRiseTau), "riseTau must be finite") - kernelDecayTau = JWN_GetNumberFromWaveNote(psxKernelDataset, path + "/decayTau") - ASSERT(IsFinite(kernelDecayTau), "decayTau must be finite") for(i = 0; i < numCombos; i += 1) PSX_OperationImpl(exd.graph, parameterJsonID, id, peakThresh, maxTauFactor, riseTime, kernelAmp, kernelRiseTau, kernelDecayTau, i, output) @@ -5329,8 +5369,8 @@ Function/WAVE PSX_OperationDeconvBPFilter(STRUCT SF_ExecutionData &exd) order += 1 endif - Make/D/FREE params = {low, high, order} - SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) + Make/D/FREE params = {low, high, order, NaN, NaN, PSX_DECONV_FILTER_DEF_ORDER} + SetDimensionLabels(params, "Filter Low;Filter High;Filter Order;Filter Low (Default);Filter High (Default);Filter Order (Default)", ROWS) WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX_DECONV_BP_FILTER, 1) @@ -5361,8 +5401,8 @@ Function/WAVE PSX_OperationSweepBPFilter(STRUCT SF_ExecutionData &exd) order += 1 endif - Make/D/FREE params = {low, high, order} - SetDimensionLabels(params, "Filter Low;Filter High;Filter Order", ROWS) + Make/D/FREE params = {low, high, order, NaN, NaN, PSX_SWEEP_FILTER_DEF_ORDER} + SetDimensionLabels(params, "Filter Low;Filter High;Filter Order;Filter Low (Default);Filter High (Default);Filter Order (Default)", ROWS) WAVE/WAVE output = SFH_CreateSFRefWave(exd.graph, SF_OP_PSX_SWEEP_BP_FILTER, 1) From 8a719288712d446cfd39cc26b5cb14c2d0f511ad Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 10 Jun 2025 19:14:43 +0200 Subject: [PATCH 38/57] PSX: Add Onset and Rise y values to psxEvent And allow querying them from psxstats. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 28 +++++++++++++++----- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 24 ++++++++++------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 9389a4cf9d..54f0b3502a 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -757,11 +757,15 @@ static Function [WAVE/D peakX, WAVE/D peakY] PSX_AnalyzePeaks(WAVE sweepDataOffF riseTimeParams[%$"Differentiate Threshold"], \ p) + Multithread psxEvent[][%$"Onset"] = GetInterpolatedYValue(sweepDataOffFilt, psxEvent[p][%$"Onset Time"]) + Multithread psxEvent[][%$"Rise Time"] = PSX_CalculateRiseTimeWrapper(sweepDataOffFilt, psxEvent, kernelAmp, \ riseTimeParams[%$"Lower Threshold"], \ riseTimeParams[%$"Upper Threshold"], \ p) + Multithread psxEvent[][%$"Rise"] = GetInterpolatedYValue(sweepDataOffFilt, psxEvent[p][%$"Rise Time"]) + Make/FREE/N=(DimSize(psxEvent, ROWS)) indexHelper indexHelper[] = PSX_FitEventDecay(sweepDataOffFilt, psxEvent, maxTauFactor, eventFit, p) @@ -1286,11 +1290,17 @@ static Function [WAVE/D results, WAVE eventIndex, WAVE marker, WAVE/T comboKeys] case "slewratetime": propLabel = "Slew Rate Time" break + case "onsettime": + propLabel = "Onset Time" + break + case "onset": + propLabel = "Onset" + break case "risetime": propLabel = "Rise Time" break - case "onsettime": - propLabel = "Onset Time" + case "rise": + propLabel = "Rise" break default: FATAL_ERROR("Impossible prop: " + prop) @@ -1634,13 +1644,18 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE s case "slewratetime": propLabelAxis = "Slew Rate time" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" break + case "onsettime": + propLabelAxis = "Onset time" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" + break + case "onset": + propLabelAxis = "Onset" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_Y_DATA_UNIT) + ")" + break case "risetime": propLabelAxis = "Rise time" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" break - case "onsettime": - propLabelAxis = "Onset time" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_X_DATA_UNIT) + ")" + case "rise": + propLabelAxis = "Rise" + " (" + JWN_GetStringFromWaveNote(allEvents[0], PSX_Y_DATA_UNIT) + ")" break - default: FATAL_ERROR("Impossible prop: " + prop) endswitch @@ -5421,7 +5436,8 @@ static Function/WAVE PSX_GetAllStatsProperties() "slowtau", "fasttau", "weightedtau", \ "estate", "fstate", "fitresult", \ "slewrate", "slewratetime", \ - "risetime", "onsettime"} + "onsettime", "onset", \ + "risetime", "rise"} return allProps End diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index a0b16d9efe..b0c3826154 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8437,15 +8437,15 @@ End /// @name SweepFormula PSX ///@{ -static Constant PSX_WAVE_VERSION = 4 -static Constant PSX_EVENT_WAVE_COLUMNS = 19 +static Constant PSX_WAVE_VERSION = 5 +static Constant PSX_EVENT_WAVE_COLUMNS = 21 /// @brief Return the upgraded psxEvent wave Function/WAVE UpgradePSXEventWave(WAVE psxEvent) if(WaveVersionIsAtLeast(psxEvent, PSX_WAVE_VERSION)) return psxEvent - elseif(WaveVersionIsAtLeast(psxEvent, 2)) // Version 2 and 3 + elseif(WaveVersionIsAtLeast(psxEvent, 2)) // Version 2-4 if(!AlreadyCalledOnce(CO_PSX_UPGRADE_EVENT)) print "The algorithm for psp/psc event detection was heavily overhauled, therefore we are very sorry " \ @@ -8491,10 +8491,12 @@ End /// - `]-10000, 0[`: CurveFit error codes /// - `]-inf, -10000]`: Custom error codes, one of @ref FitEventDecayCustomErrors /// - 14/Event manual QC call: One of @ref PSXStates -/// - 15/Onset time as calculated by PSX_CalculateOnsetTime -/// - 16/Rise Time as calculated by PSX_CalculateRiseTime -/// - 17/Slew Rate -/// - 18/Slew Rate Time +/// - 15/Onset Time as calculated by PSX_CalculateOnsetTime +/// - 16/Onset y-value +/// - 17/Rise Time as calculated by PSX_CalculateRiseTime +/// - 18/Rise y-value +/// - 19/Slew Rate +/// - 20/Slew Rate Time Function/WAVE GetPSXEventWaveAsFree() variable versionOfWave = PSX_WAVE_VERSION @@ -8526,9 +8528,11 @@ static Function SetPSXEventDimensionLabels(WAVE wv) SetDimLabel COLS, 13, $"Fit result", wv SetDimLabel COLS, 14, $"Event manual QC call", wv SetDimLabel COLS, 15, $"Onset Time", wv - SetDimLabel COLS, 16, $"Rise Time", wv - SetDimLabel COLS, 17, $"Slew Rate", wv - SetDimLabel COLS, 18, $"Slew Rate Time", wv + SetDimLabel COLS, 16, $"Onset", wv + SetDimLabel COLS, 17, $"Rise Time", wv + SetDimLabel COLS, 18, $"Rise", wv + SetDimLabel COLS, 19, $"Slew Rate", wv + SetDimLabel COLS, 20, $"Slew Rate Time", wv End Function/WAVE GetPSXSingleEventFitWaveFromDFR(DFREF dfr) From 7041da8f617acfd3f0755e356d9a1c0c02f72d63 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 10 Jun 2025 19:43:41 +0200 Subject: [PATCH 39/57] PSX_FitAverage: Store fit results in datafolder hierarchy --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 5 +++++ Packages/MIES/MIES_WaveDataFolderGetters.ipf | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 54f0b3502a..13518a4715 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2630,6 +2630,11 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime browser = SFH_GetBrowserForFormulaGraph(win) PSX_StoreIntoResultsWave(browser, SFH_RESULT_TYPE_PSX_MISC, input, "accepted average fit results") + + WAVE AverageFitResults = GetPSXAverageFitResultsWaveFromDFR(averageDFR, state) + Redimension/N=(0, -1) AverageFitResults + + Concatenate/NP=(ROWS) {InputAvg, InputRise, InputDecay}, AverageFitResults End static Function PSX_StoreIntoResultsWave(string browser, variable resultType, WAVE data, string name) diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index b0c3826154..490d8c3e9d 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -8772,6 +8772,21 @@ Function/WAVE GetPSXAverageFitWaveFromDFR(DFREF dfr, variable state) return wv End +Function/WAVE GetPSXAverageFitResultsWaveFromDFR(DFREF dfr, variable state) + + string name = "averageFitResults_" + PSX_StateToString(state) + + WAVE/Z/T/SDFR=dfr wv = $name + + if(WaveExists(wv)) + return wv + endif + + Make/T/N=(0, 2) dfr:$name/WAVE=wv + + return wv +End + ///@} /// @brief Returns a wave with the names of all log files From cdde11acb5be212383136be5c1661da1ccae4591 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 10 Jun 2025 21:44:22 +0200 Subject: [PATCH 40/57] Packages/doc/SweepFormula.rst: Add units for rise and decay tau --- Packages/doc/SweepFormula.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index 07bc74b429..26943046ed 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -1356,10 +1356,10 @@ select selections and range to operate on from the `select` operation riseTau - Time constant for kernel, defaults to 1 + Time constant for kernel, defaults to 1ms decayTau - Time constant for kernel, defaults to 15 + Time constant for kernel, defaults to 15ms amp Amplitude for kernel, defaults to -5 From 20a2564131cb890f4148b29dabe1a21eca6727c2 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 25 Jun 2025 11:47:09 +0200 Subject: [PATCH 41/57] PSX_Operation: Add peakThreshold to parameter JSON This is convenient for documentation purposes although this is not an input parameter. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 13518a4715..cf646879fd 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -5186,6 +5186,9 @@ Function/WAVE PSX_Operation(STRUCT SF_ExecutionData &exd) SFH_FATAL_ERROR("Could not gather sweep data for psx") endtry + parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX + JSON_AddVariable(parameterJsonID, parameterPath + "/peakThreshold", peakThresh) + JWN_SetWaveNoteFromJSON(output, parameterJsonID) JWN_SetStringInWaveNote(output, SF_META_DATATYPE, SF_DATATYPE_PSX) JWN_SetStringInWaveNote(output, SF_META_OPSTACK, AddListItem(SF_OP_PSX, "")) From 1f4bbe3b898a63b6a59c6856cbe3111a463665dc Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 9 Jul 2025 19:23:05 +0200 Subject: [PATCH 42/57] PSX_OperationStatsImpl: Output more entries for stats postprocessing --- Packages/MIES/MIES_Constants.ipf | 2 +- Packages/MIES/MIES_SweepFormula_PSX.ipf | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 3df01770cc..9d0c1e7146 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -2415,7 +2415,7 @@ Constant PSX_DECAY_FIT_INVALID_RANGE_ERROR = -10001 ///@} -StrConstant PSX_STATS_LABELS = "Average;Median;Average Deviation;Standard deviation;Skewness;Kurtosis" +StrConstant PSX_STATS_LABELS = "Average;Median;Average Deviation;Standard deviation;Skewness;Kurtosis;Lower quartile;Upper quartile;Inter-quartile range;Median absolute deviation;Most frequent value" /// @name Horizontal offset modes in all event graph /// diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index cf646879fd..bc95a557fc 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -1689,13 +1689,19 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE s WaveStats/Q/M=2 resultsRawClean - Make/FREE/D results = {V_avg, NaN, V_adev, V_sdev, V_skew, V_kurt} + Make/FREE/D results = {V_avg, NaN, V_adev, V_sdev, V_skew, V_kurt, NaN, NaN, NaN, NaN, NaN} + SetDimensionLabels(results, PSX_STATS_LABELS, ROWS) StatsQuantiles/Q/Z resultsRawClean MakeWaveFree($"W_StatsQuantiles") if(!V_Flag) - results[1] = V_Median + results[%$"Median"] = V_Median + results[%$"Lower Quartile"] = V_Q25 + results[%$"Upper Quartile"] = V_Q75 + results[%$"Inter-quartile range"] = V_IQR + results[%$"Median absolute deviation"] = V_MAD + results[%$"Most frequent value"] = V_Mode endif WAVE/T statsLabels = ListToTextWave(PSX_STATS_LABELS, ";") From 8f97842a541e87246f06f6b6752376c757bf81d3 Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Mon, 28 Jul 2025 13:05:02 -0700 Subject: [PATCH 43/57] PSX_FitAverage: Enhance onset calculation --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 57 +++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index bc95a557fc..dfafca1698 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -196,7 +196,7 @@ static Function/DF PSX_GetWorkingFolder(string win) return $GetUserData(mainWindow, "", PSX_USER_DATA_WORKING_FOLDER) End -static Function/S PSX_GetSpecialPanel(string win) +Function/S PSX_GetSpecialPanel(string win) return GetMainWindow(win) + "#" + PSX_SPECIAL_EVENT_PANEL End @@ -2566,8 +2566,10 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime riselowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE riseAndDecayUpperThreshold = 0.9 - FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) - + // FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) + variable onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) + variable level = (riseLowerThreshold * (extrema - average(onsetX))) + average(onsetX) + FindLevel/EDGE=(edge)/R=(extrema_t, onsetX)/Q average, level riseStart = V_LevelX FindLevel/EDGE=(edge)/Q average, (riseAndDecayUpperThreshold * extrema) riseStop = V_levelX @@ -6310,3 +6312,52 @@ Function PSX_UpdateVisualizationHelpers(STRUCT WMCheckboxAction &cba) : CheckBox return 0 End + +Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variable meanOnsetTime, variable meanPeakTime) + + print "onset: ", meanonsettime, "peak: ", meanpeaktime + + duplicate/FREE AvgEvent, AvgEventDiff + differentiate AvgEventDiff + wavestats/Q AvgEvent + duplicate AvgEventDiff, forDisp + + variable eventPeak, eventPeak_t, edge, backwardEdge, level, slewrate, slewrate_t + + if(kernelAmp > 0) + eventPeak = V_max + eventPeak_t = V_maxLoc + edge = FINDLEVEL_EDGE_INCREASING + backwardEdge = FINDLEVEL_EDGE_DECREASING + elseif(kernelAmp < 0) + eventPeak = V_min + eventPeak_t = V_minLoc + edge = FINDLEVEL_EDGE_DECREASING + backwardEdge = FINDLEVEL_EDGE_INCREASING + else + ASSERT(0, "Invalid kernel amp") + endif + + variable searchEnd = eventPeak_t - 2 * (meanPeakTime - abs(meanOnsetTime)) // get ride of the * 2 hack + + wavestats/Q/R=(searchEnd, eventPeak_t) AvgEventDiff + + if(kernelAmp > 0) + slewRate = v_max + slewRate_t = v_maxloc + elseif(kernelAmp < 0) + slewRate = V_min + slewRate_t = V_minLoc + endif + + level = 0.20 * slewRate + + FindLevel/R=(eventPeak_t, searchEnd)/EDGE=(edge)/Q AvgEventDiff, level + + if(V_flag) + return NaN + endif + print V_levelX + return V_levelX + +End From 431ff0f659407acb593caa61e96d291233995735 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 29 Jul 2025 13:01:56 +0200 Subject: [PATCH 44/57] PSX_UpdateAverageTraces: Fix indexing confusion Ever since 7aa2be662f (PSX: Add average fit for all states, 2025-06-04) we are using the per-combo index for gathering the eventKernelAmp/eventOnsetTime/eventPeakTime values. This is wrong as we must use the index of the displayed events. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index dfafca1698..2d42f28ed9 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2367,12 +2367,12 @@ static Function PSX_AdaptColorsInAllEventGraph(string win, [variable forceAverag endif if(averageUpdateRequired || forceAverageUpdate) - PSX_UpdateAverageTraces(win, eventIndexFromTraces, comboIndizesFromTraces, stateNew, indexMapper, comboFolders) + PSX_UpdateAverageTraces(win, comboIndizesFromTraces, stateNew, indexMapper, comboFolders) endif End /// @brief Update the contents of the average waves for the all event graph -static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, WAVE/T comboIndizesFromTraces, WAVE stateNew, WAVE indexMapper, WAVE/DF comboFolders) +static Function PSX_UpdateAverageTraces(string win, WAVE/T comboIndizesFromTraces, WAVE stateNew, WAVE indexMapper, WAVE/DF comboFolders) variable i, idx, numEvents, eventState, start, stop variable acceptIndex, rejectIndex, undetIndex, extractStartAbs, extractStopAbs, fitStartAbs @@ -2380,13 +2380,15 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, extAllGraph = PSX_GetAllEventGraph(win) - numEvents = DimSize(eventIndexFromTraces, ROWS) + numEvents = DimSize(stateNew, ROWS) Make/WAVE/FREE/N=(numEvents) contAverageAll, contAverageAccept, contAverageReject, contAverageUndet Make/FREE=1/D/N=(numEvents) eventOnsetTime, eventPeakTime, eventStopTime, eventKernelAmp for(i = 0; i < numEvents; i += 1) - idx = str2num(eventIndexFromTraces[i]) + // i: index of the displayed events across multiple combos + // idx: index in a single combo + idx = indexMapper[i] DFREF comboDFR = comboFolders[str2num(comboIndizesFromTraces[i])] @@ -2401,12 +2403,12 @@ static Function PSX_UpdateAverageTraces(string win, WAVE/T eventIndexFromTraces, // single event waves are zeroed in x-direction to extractStartAbs [extractStartAbs, extractStopAbs] = PSX_GetSingleEventRange(psxEvent, sweepDataOffFilt, idx) - eventStopTime[idx] = extractStopAbs - extractStartAbs - eventOnsetTime[idx] = psxEvent[idx][%$"Onset Time"] - extractStartAbs - eventPeakTime[idx] = psxEvent[idx][%peak_t] - extractStartAbs + eventStopTime[i] = extractStopAbs - extractStartAbs + eventOnsetTime[i] = psxEvent[idx][%$"Onset Time"] - extractStartAbs + eventPeakTime[i] = psxEvent[idx][%peak_t] - extractStartAbs - path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL - eventKernelAmp[idx] = JWN_GetNumberFromWaveNote(psxEvent, path + "/amp") + path = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL + eventKernelAmp[i] = JWN_GetNumberFromWaveNote(psxEvent, path + "/amp") switch(stateNew[i]) case PSX_ACCEPT: @@ -6335,7 +6337,7 @@ Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variab edge = FINDLEVEL_EDGE_DECREASING backwardEdge = FINDLEVEL_EDGE_INCREASING else - ASSERT(0, "Invalid kernel amp") + FATAL_ERROR("Invalid kernel amp") endif variable searchEnd = eventPeak_t - 2 * (meanPeakTime - abs(meanOnsetTime)) // get ride of the * 2 hack From 14be85784a24226aaeed7a750931bdce012c970d Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Tue, 5 Aug 2025 11:28:45 -0700 Subject: [PATCH 45/57] new function to determine the fit end time for the decay to the average waveform --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 127 +++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 2d42f28ed9..de884bdbc8 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2506,6 +2506,19 @@ Function PSX_FilterFitAverageParameters(WAVE input, WAVE newState, variable stat return Inf End +/// @brief Compute and store fitted average traces for a given event state +/// +/// Fits average, rise, and decay components of events marked with the specified state. +/// If fitting is disabled via GUI checkbox, outputs are set to NaN. +/// +/// @param win Name of the calling window (used to access GUI settings) +/// @param averageDFR Data folder reference containing the average waves +/// @param eventOnsetTime Wave of event onset times +/// @param eventPeakTime Wave of event peak times +/// @param eventStopTime Wave of event stop times +/// @param eventKernelAmp Wave of event amplitudes (used to infer polarity) +/// @param newState Wave containing per-event classification bit flags +/// @param state Bit flag representing the target event state (e.g., PSX_ACCEPT) static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime, WAVE eventPeakTime, WAVE eventStopTime, WAVE eventKernelAmp, WAVE newState, variable state) string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc @@ -2546,7 +2559,8 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime CopyScales average, AverageFit, RiseAverageFit, DecayAverageFit meanOnsetTime = PSX_FilterFitAverageParameters(eventOnsetTime, newState, state) - meanStopTime = PSX_FilterFitAverageParameters(eventStopTime, newState, state) + // meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, binwidth = 0.1, showplot = 1) + meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state) meanKernelAmp = PSX_FilterFitAverageParameters(eventKernelAmp, newState, state, requireFiniteResult = 1) meanPeakTime = PSX_FilterFitAverageParameters(eventPeakTime, newState, state, requireFiniteResult = 1) @@ -2647,6 +2661,117 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime Concatenate/NP=(ROWS) {InputAvg, InputRise, InputDecay}, AverageFitResults End +/// @brief Return most frequent stop time peak, ignoring edge bins. Falls back to 2×kernelDecayTau if no peak. +/// +/// @param stopTimes Wave of event stop times (with wave note from PSX pipeline) +/// @param newState Wave of state bitmasks per event +/// @param state Bitmask value to include (e.g., PSX_ACCEPT) +/// @param [binWidth] Optional histogram bin width (default = 0.1) +/// @param [showPlot] Optional flag to plot histogram (default = 0) +Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable state, [variable binWidth, variable showPlot]) + + variable i + variable n = DimSize(stopTimes, ROWS) + variable count = 0 + + if(ParamIsDefault(binWidth)) + binWidth = 0.1 + endif + if(ParamIsDefault(showPlot)) + showPlot = 0 + endif + + // Filter stopTimes + Make/FREE/D/N=(n) tmp = NaN + for(i = 0; i < n; i += 1) + if((newState[i] & state) && IsFinite(stopTimes[i])) + tmp[count] = stopTimes[i] + count += 1 + endif + endfor + + if(count == 0) + if(showPlot) + Print "No matching stop times found." + endif + return NaN + endif + + Redimension/N=(count) tmp + + // Histogram setup + variable minVal = WaveMin(tmp) + variable maxVal = WaveMax(tmp) + + if(minVal == maxVal) + if(showPlot) + Print "Identical stop times: using value = ", minVal + endif + return minVal + endif + + variable nBins = ceil((maxVal - minVal) / binWidth) + if(nBins < 5) + if(showPlot) + Print "Too few bins (", nBins, ")." + endif + return NaN + endif + + // Named waves for plotting + KillWaves/Z root:PSX_hist, root:PSX_histX + Make/D/N=(nBins) root:PSX_hist, root:PSX_histX + WAVE hist = root:PSX_hist + WAVE histX = root:PSX_histX + + Histogram/B={minVal, binWidth, nBins}/DEST=hist tmp + histX[] = minVal + binWidth * (p + 0.5) + Smooth 3, hist + + // Optional plot + if(showPlot) + Display/K=1/N=PSX_StopTimeHistogram + AppendToGraph hist vs histX + ModifyGraph mode=4, lsize=2, rgb=(0, 0, 65535) + ModifyGraph grid=1, mirror=1, axThick=1.5 + Label bottom, "Stop Time" + Label left, "Event Count" + SetAxis/A + endif + + // Find dominant internal peak + variable peakMaxVal = -Inf + variable maxIdx = -1 + for(i = 1; i < (nBins - 1); i += 1) + if(hist[i] > peakMaxVal) + peakMaxVal = hist[i] + maxIdx = i + endif + endfor + + if(maxIdx >= 0) + if(showPlot) + Print "Dominant internal peak at ", histX[maxIdx] + endif + return histX[maxIdx] + endif + + // Fallback: Try to retrieve decayTau only if needed + variable decayTau = JWN_GetNumberFromWaveNote(stopTimes, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") + + if(IsFinite(decayTau)) + if(showPlot) + Print "No peak found. Falling back to 2 × decayTau = ", 2 * decayTau + endif + return 2 * decayTau + endif + + if(showPlot) + Print "No peak found and decayTau not available. Returning NaN." + endif + return NaN +End + static Function PSX_StoreIntoResultsWave(string browser, variable resultType, WAVE data, string name) string lastBrowser From e7d1d4f3b269b2880972addfb256b8457ccef3c6 Mon Sep 17 00:00:00 2001 From: Tim Jarsky Date: Wed, 6 Aug 2025 14:09:15 -0700 Subject: [PATCH 46/57] Packages/MIES/MIES_SweepFormula_PSX.ipf: Fix variable casing and enhance PSX_CalculateOnsetTimeFromAvg --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 181 +++++++++++++++++++----- 1 file changed, 143 insertions(+), 38 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index de884bdbc8..4124f61623 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -945,7 +945,7 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable fitRange = endTime - startTime - if((IsFinite(weightedTau) && weightedTau > (maxTauFactor * fitRange)) || fastTau <= 0 || SlowTau <= 0 || weightedTau <= 0) + if((IsFinite(weightedTau) && weightedTau > (maxTauFactor * fitRange)) || fastTau <= 0 || slowTau <= 0 || weightedTau <= 0) psxEvent[eventIndex][%$"Fit manual QC call"] = PSX_REJECT psxEvent[eventIndex][%$"Fit result"] = PSX_DECAY_FIT_ERROR psxEvent[eventIndex][%weightedTau] = NaN @@ -2559,8 +2559,8 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime CopyScales average, AverageFit, RiseAverageFit, DecayAverageFit meanOnsetTime = PSX_FilterFitAverageParameters(eventOnsetTime, newState, state) - // meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, binwidth = 0.1, showplot = 1) - meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state) + // meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, eventKernelAmp, binwidth = 0.1, showplot = 1) + meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, eventKernelAmp) meanKernelAmp = PSX_FilterFitAverageParameters(eventKernelAmp, newState, state, requireFiniteResult = 1) meanPeakTime = PSX_FilterFitAverageParameters(eventPeakTime, newState, state, requireFiniteResult = 1) @@ -2584,7 +2584,8 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime riseAndDecayUpperThreshold = 0.9 // FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) variable onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) - variable level = (riseLowerThreshold * (extrema - average(onsetX))) + average(onsetX) + assert(!isNaN(onsetX), "onset time calculation must cannot be Nan") + variable level = (riseLowerThreshold * (extrema - average(onsetX))) + average(onsetX) FindLevel/EDGE=(edge)/R=(extrema_t, onsetX)/Q average, level riseStart = V_LevelX FindLevel/EDGE=(edge)/Q average, (riseAndDecayUpperThreshold * extrema) @@ -2661,14 +2662,14 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime Concatenate/NP=(ROWS) {InputAvg, InputRise, InputDecay}, AverageFitResults End -/// @brief Return most frequent stop time peak, ignoring edge bins. Falls back to 2×kernelDecayTau if no peak. -/// -/// @param stopTimes Wave of event stop times (with wave note from PSX pipeline) -/// @param newState Wave of state bitmasks per event -/// @param state Bitmask value to include (e.g., PSX_ACCEPT) -/// @param [binWidth] Optional histogram bin width (default = 0.1) -/// @param [showPlot] Optional flag to plot histogram (default = 0) -Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable state, [variable binWidth, variable showPlot]) +//// @brief Return most frequent stop time peak, ignoring edge bins and bins < decayTau. Falls back to 2×decayTau. +/// @param stopTimes Wave of event stop times (with kernel decayTau in wave note) +/// @param newState Bitmask wave of per-event state +/// @param state Target state value (e.g., PSX_ACCEPT) +/// @param psxKernel kernel +/// @param [binWidth] Histogram bin width (default = 0.1) +/// @param [showPlot] Whether to display the histogram (default = 0) +Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable state, WAVE psxKernel, [variable binWidth, variable showPlot]) variable i variable n = DimSize(stopTimes, ROWS) @@ -2681,7 +2682,7 @@ Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable st showPlot = 0 endif - // Filter stopTimes + // Step 1: Filter stopTimes by state Make/FREE/D/N=(n) tmp = NaN for(i = 0; i < n; i += 1) if((newState[i] & state) && IsFinite(stopTimes[i])) @@ -2692,20 +2693,20 @@ Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable st if(count == 0) if(showPlot) - Print "No matching stop times found." + Print "No valid stop times for selected state." endif return NaN endif Redimension/N=(count) tmp - // Histogram setup + // Step 2: Bin setup variable minVal = WaveMin(tmp) variable maxVal = WaveMax(tmp) if(minVal == maxVal) if(showPlot) - Print "Identical stop times: using value = ", minVal + Print "All stop times identical. Using ", minVal endif return minVal endif @@ -2713,14 +2714,14 @@ Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable st variable nBins = ceil((maxVal - minVal) / binWidth) if(nBins < 5) if(showPlot) - Print "Too few bins (", nBins, ")." + Print "Too few bins. Returning NaN." endif return NaN endif - // Named waves for plotting + // Step 3: Create histogram KillWaves/Z root:PSX_hist, root:PSX_histX - Make/D/N=(nBins) root:PSX_hist, root:PSX_histX + Make/O/D/N=(nBins) root:PSX_hist, root:PSX_histX WAVE hist = root:PSX_hist WAVE histX = root:PSX_histX @@ -2728,10 +2729,18 @@ Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable st histX[] = minVal + binWidth * (p + 0.5) Smooth 3, hist - // Optional plot + // Step 4: Optional plot if(showPlot) - Display/K=1/N=PSX_StopTimeHistogram - AppendToGraph hist vs histX + // Display/K=1/N=PSX_StopTimeHistogram + + if(!WindowExists("PSX_StopTimeHistogram")) // 1 = graph window + Display/N=PSX_StopTimeHistogram + else + DoWindow/F PSX_StopTimeHistogram // Bring to front if already exists + // AppendToGraph/W=PSX_StopTimeHistogram stopTimes vs stopTimeHist + endif + + AppendToGraph/W=PSX_StopTimeHistogram hist vs histX ModifyGraph mode=4, lsize=2, rgb=(0, 0, 65535) ModifyGraph grid=1, mirror=1, axThick=1.5 Label bottom, "Stop Time" @@ -2739,35 +2748,41 @@ Function PSX_FindDominantStopTimePeak(WAVE stopTimes, WAVE newState, variable st SetAxis/A endif - // Find dominant internal peak + // Step 5: Try to find decayTau + variable decayTau = GetKernelDecayTau() + variable validTau = IsFinite(decayTau) + + // Step 6: Find max internal peak above decayTau variable peakMaxVal = -Inf variable maxIdx = -1 for(i = 1; i < (nBins - 1); i += 1) + if(validTau && histX[i] < (2 * decayTau)) + continue + endif if(hist[i] > peakMaxVal) peakMaxVal = hist[i] maxIdx = i endif endfor + // Step 7: Return result if(maxIdx >= 0) if(showPlot) - Print "Dominant internal peak at ", histX[maxIdx] + Print "Dominant peak at ", histX[maxIdx], " (passes decayTau filter)" endif return histX[maxIdx] endif - // Fallback: Try to retrieve decayTau only if needed - variable decayTau = JWN_GetNumberFromWaveNote(stopTimes, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") - - if(IsFinite(decayTau)) + // Step 8: Fallback + if(validTau) if(showPlot) - Print "No peak found. Falling back to 2 × decayTau = ", 2 * decayTau + Print "No peak ≥ decayTau found. Fallback to 2 × decayTau = ", 2 * decayTau endif return 2 * decayTau endif if(showPlot) - Print "No peak found and decayTau not available. Returning NaN." + Print "No peak found. DecayTau not available. Returning NaN." endif return NaN End @@ -6442,12 +6457,12 @@ End Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variable meanOnsetTime, variable meanPeakTime) - print "onset: ", meanonsettime, "peak: ", meanpeaktime - duplicate/FREE AvgEvent, AvgEventDiff differentiate AvgEventDiff + smooth 500, AvgEventDiff wavestats/Q AvgEvent - duplicate AvgEventDiff, forDisp + duplicate/O AvgEventDiff, root:forDisp + smooth 500, root:forDisp variable eventPeak, eventPeak_t, edge, backwardEdge, level, slewrate, slewrate_t @@ -6465,7 +6480,7 @@ Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variab FATAL_ERROR("Invalid kernel amp") endif - variable searchEnd = eventPeak_t - 2 * (meanPeakTime - abs(meanOnsetTime)) // get ride of the * 2 hack + variable searchEnd = -Inf // optionally: eventPeak_t - (meanPeakTime) or similar wavestats/Q/R=(searchEnd, eventPeak_t) AvgEventDiff @@ -6477,14 +6492,104 @@ Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variab slewRate_t = V_minLoc endif - level = 0.20 * slewRate + level = 0.2 * slewRate FindLevel/R=(eventPeak_t, searchEnd)/EDGE=(edge)/Q AvgEventDiff, level - if(V_flag) + if(!V_flag) + return V_levelX + endif + + // --- Fallback: Use second derivative to enforce slope direction --- + duplicate/FREE AvgEventDiff, d2 + differentiate d2 + smooth 200, d2 // optional smoothing for stability + + variable closestIdx = -1 + variable closestDist = Inf + variable n = DimSize(AvgEventDiff, 0) + variable xval, diffVal, d2Val + variable i + + for(i = 0; i < n; i += 1) + xval = pnt2x(AvgEventDiff, i) + + // Constrain time range + if(xval < searchEnd || xval > eventPeak_t) + continue + endif + + diffVal = AvgEventDiff[i] + d2Val = d2[i] + + // Constrain direction of slope change at the candidate level crossing + if(edge == FINDLEVEL_EDGE_INCREASING && d2Val < 0) + continue // not increasing slope + elseif(edge == FINDLEVEL_EDGE_DECREASING && d2Val > 0) + continue // not decreasing slope + endif + + // Find closest to desired level + if(abs(diffVal - level) < closestDist) + closestDist = abs(diffVal - level) + closestIdx = i + endif + endfor + + if(closestIdx >= 0) + return pnt2x(AvgEventDiff, closestIdx) + endif + + return NaN +End + +Function GetKernelDecayTau() + + string psxPath + string ListOfwin = WinList("*", ";", "WIN:64") // Only graph windows + variable i + variable num = ItemsInList(ListOfwin, ";") + + // Search all graph windows for one with psxFolder user data + for(i = 0; i < num; i += 1) + string baseWin = StringFromList(i, ListOfwin, ";") + psxPath = GetUserData(baseWin, "", "psxFolder") + if(strlen(psxPath) > 0) + break + endif + endfor + + if(strlen(psxPath) == 0) + printf "No psxFolder user data found in any graph window.\r" return NaN endif - print V_levelX - return V_levelX + DFREF psxDFR = $psxPath + + // Search combo_* subfolders for a valid psxEvent wave + variable j = 0 + do + string folderName = GetIndexedObjName(psxPath, 4, j) // 4 = data folder + if(strlen(folderName) == 0) + break + endif + + if(stringmatch(folderName, "combo_*")) + DFREF comboDFR = psxDFR:$folderName + if(DataFolderRefStatus(comboDFR) != 0) + WAVE/Z psxEvent = comboDFR:psxEvent + if(WaveExists(psxEvent)) + variable tau = JWN_GetNumberFromWaveNote(psxEvent, "User/Parameters/psxKernel/decayTau") + if(IsFinite(tau)) + return tau + endif + endif + endif + endif + + j += 1 + while(1) + + printf "No valid psxEvent wave with decayTau found.\r" + return NaN End From 9a33ce4144d38b76f56b194fada40baf0271368d Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 18:58:46 +0100 Subject: [PATCH 47/57] PSX_FitAverage: Don't clutter the current datafolder We can just do the fits in a free datafolder so that the current datafolder is not cluttered. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 4124f61623..d3846c21c1 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2598,6 +2598,9 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime AssertOnAndClearRTError() + DFREF currentDFR = GetDataFolderDFR() + SetDataFolder NewFreeDataFolder() + Make/FREE/T/N=(11, 2) InputAvg, InputRise, InputDecay Make/FREE/D/N=5 coefWave @@ -2645,6 +2648,8 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime sprintf msg, "Fit in the range [%g, %g] finished with %d (%s)\r", riseStart, decayStop, err, GetErrMessage(err) DEBUGPRINT(msg) + SetDataFolder currentDFR + if(err) return NaN endif From 1223c974719978dc67074a13d580c049cf3de632 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 14:10:01 +0100 Subject: [PATCH 48/57] GetTestCode: Remove setting code twice --- Packages/tests/UTF_HelperFunctions.ipf | 2 -- 1 file changed, 2 deletions(-) diff --git a/Packages/tests/UTF_HelperFunctions.ipf b/Packages/tests/UTF_HelperFunctions.ipf index 3b02de2d4c..84e551bd86 100644 --- a/Packages/tests/UTF_HelperFunctions.ipf +++ b/Packages/tests/UTF_HelperFunctions.ipf @@ -1839,8 +1839,6 @@ Function/S GetTestCode(string postProc, [string eventState, string prop]) prop = "peaktime" endif - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 5, 100, 0)" - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 1.5, 100, 0)" code += "\r and \r" code += "psxStats(myId, select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), " + prop + ", " + eventState + ", " + postProc + ")" From 6980b330bd77550b864b7e2b717b7ec26f018da1 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 18:59:51 +0100 Subject: [PATCH 49/57] PSX_CalculateOnsetTimeFromAvg: Make it more robust We now don't error out anymore if the Smooth operation fails due to too few points. Also remove code which created global waves. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index d3846c21c1..ed878a6aa6 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -6462,14 +6462,15 @@ End Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variable meanOnsetTime, variable meanPeakTime) + variable eventPeak, eventPeak_t, edge, backwardEdge, level, slewrate, slewrate_t + duplicate/FREE AvgEvent, AvgEventDiff differentiate AvgEventDiff - smooth 500, AvgEventDiff - wavestats/Q AvgEvent - duplicate/O AvgEventDiff, root:forDisp - smooth 500, root:forDisp - variable eventPeak, eventPeak_t, edge, backwardEdge, level, slewrate, slewrate_t + AssertOnAndClearRTError() + smooth 500, AvgEventDiff; ClearRTError() + + wavestats/Q AvgEvent if(kernelAmp > 0) eventPeak = V_max @@ -6508,7 +6509,7 @@ Function PSX_CalculateOnsetTimeFromAvg(WAVE AvgEvent, variable kernelAmp, variab // --- Fallback: Use second derivative to enforce slope direction --- duplicate/FREE AvgEventDiff, d2 differentiate d2 - smooth 200, d2 // optional smoothing for stability + smooth 200, d2; ClearRTError() // optional smoothing for stability variable closestIdx = -1 variable closestDist = Inf From 3b2585bcd4c5f21e8f74b10f49197bac60c87647 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Wed, 28 May 2025 16:53:17 +0200 Subject: [PATCH 50/57] Packages/doc/SweepFormula.rst: Update it --- Packages/doc/SweepFormula.rst | 58 +++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index 26943046ed..2e33fff0f4 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -1299,7 +1299,7 @@ The `psx` operation allows to classify miniature PSC/PSP's interactively. .. code-block:: bash - psx(id, [psxKernel(), numSDs, filterLow, filterHigh, maxTauFactor, psxRiseTime(), psxDeconvBPFilter()]) + psx(id, [psxKernel(), numSDs, psxSweepBPFilter(), maxTauFactor, psxRiseTime(), psxDeconvBPFilter()]) The function accepts one to seven arguments. @@ -1313,11 +1313,8 @@ psxKernel numSDs Number of standard deviations for the gaussian fit of the all points histogram, defaults to 2.5 -filterLow - low threshold for the bandpass filter, defaults to 550 Hz - -filterHigh - high threshold for the bandpass filter, defaults to 0 Hz +psxSweepBPFilter + results from the `psxSweepBPFilter` operation maxTauFactor maximum tau factor, the decay tau from fitting the event must be smaller than the fit range @@ -1332,10 +1329,20 @@ psxDeconvBPFilter The plotting is implemented in a custom way. Due to that multiple `psx` operations can only be separated by `with` and not `and`. +The filter order is internally made even as there is no difference in filter +order `n` and `n + 1` due to implementation details of the used operation +`FilterIIR`. + +The filtering for both the sweep data and the deconvoluted data uses a backing down +algorithm for determining the filter order. The implementation starts with the +given order and decrements it by two as long as the filtering is not +successfull for all sweeps. If we reach zero we bail out. The used filter order +is stored in the wave note. + .. code-block:: bash psx(myID) - psx(psxkernel(), 3, 400, 100) + psx(anotherID, psxkernel(), 3, psxSweepBPFilter(400, 100), 12) See :ref:`sweepformula_psx` for an in-depth explanation of the available user interface for acceptance/rejectance. @@ -1434,15 +1441,46 @@ order defaults to `NaN` The default values of `NaN` are replaced inside `psx`. For the order this is -`7`, for the frequencies `500` (`lowFreq`) and `50` (`highFreq`). +`4`, the frequencies are calculated from rise and decay tau. Here `lowFreq` is the end and `highFreq` the start of the -passband, see also the description of `/LO` and `/HI` from `FilterIIR`. +passband, see also the description of `/LO` and `/HI` from `FilterIIR`. If the +frequency values are not ordered correctly, they are swapped. .. code-block:: bash psxDeconvBPFilter(800, 100) psxDeconvBPFilter(400, 50, 11) +psxSweepBPFilter +""""""""""""""""" + +The `psxSweepBPFilter` operation is a helper operation for `psx` to manage the sweep filter settings. +This filter is a bandpass filter. + + psxSweepBPFilter([lowFreq, highFreq, order]) + +The function accepts zero to three arguments. + +lowFreq [Hz] + defaults to `NaN` + +highFreq [Hz] + defaults to `NaN` + +order + defaults to `NaN` + +The default values of `NaN` are replaced inside `psx`. For the order this is +`4`, the frequencies are calculated from rise and decay tau. +Here `lowFreq` is the end and `highFreq` the start of the +passband, see also the description of `/LO` and `/HI` from `FilterIIR`. If the +frequency values are not ordered correctly, they are swapped. + +.. code-block:: bash + + psxSweepBPFilter(800, 100) + psxSweepBPFilter(400, 50, 11) + psxstats """""""" @@ -1474,7 +1512,7 @@ select prop column of the `psx` event results waves to plot. Choices are: `amp`, `peak`, `peaktime`, `deconvpeak`, `deconvpeaktime`, `baseline`, `baselinetime`, `xinterval`, - `tau`, `estate`, `fstate`, `fitresult`, `slewrate`, `slewratetime`, `risetime`, `onsettime` + `slowtau`, `fasttau`, `weightedtau`, `estate`, `fstate`, `fitresult`, `slewrate`, `slewratetime`, `risetime`, `rise`, `onsettime`, `onset` state QC state to select the events. From 977970b4c523bd336063c526f3d74d5b85862876 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 19:39:45 +0100 Subject: [PATCH 51/57] PSX_GetFindPeakBoxSize: Remove pointless special casing for the tests We always return 10 anyway. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index ed878a6aa6..cb5b6b9bc9 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -174,10 +174,6 @@ End static Function PSX_GetFindPeakBoxSize() -#ifdef AUTOMATED_TESTING - return 10 -#endif // AUTOMATED_TESTING - return 10 End From b1a2b69d26d996eedcac4781cac30c654eb656e7 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 20:00:11 +0100 Subject: [PATCH 52/57] PSX_FitAverage: Don't assert out on onsetX being NaN We can easily have this case as the CheckResultsWavesForAverageFitResult test shows. --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index cb5b6b9bc9..74f714cdc2 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -2520,7 +2520,7 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime string specialEventPanel, str, htmlStr, rawCode, browser, msg, fitFunc variable err, numAveragePoints, start, stop, meanStopTime, meanOnsetTime, meanPeakTime, meanKernelAmp variable xStart, xEnd, yStart, yEnd, riselowerThreshold, riseAndDecayUpperThreshold - variable extrema, extrema_t, edge, riseStart, riseStop, decayStart, decayStop, wTau, backwardEdge + variable extrema, extrema_t, edge, riseStart, riseStop, decayStart, decayStop, wTau, backwardEdge, onsetX, level WAVE AverageFit = GetPSXAverageFitWaveFromDFR(averageDFR, state) WAVE RiseAverageFit = GetPSXRiseAverageFitWaveFromDFR(averageDFR, state) @@ -2579,9 +2579,14 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime riselowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE riseAndDecayUpperThreshold = 0.9 // FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) - variable onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) - assert(!isNaN(onsetX), "onset time calculation must cannot be Nan") - variable level = (riseLowerThreshold * (extrema - average(onsetX))) + average(onsetX) + onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) + + if(IsNaN(onsetX)) + print "onset time calculation must not be Nan, skipping average fits" + return NaN + endif + + level = (riseLowerThreshold * (extrema - average(onsetX))) + average(onsetX) FindLevel/EDGE=(edge)/R=(extrema_t, onsetX)/Q average, level riseStart = V_LevelX FindLevel/EDGE=(edge)/Q average, (riseAndDecayUpperThreshold * extrema) From da16efaf0905ae04946e49735ac88d015065c4ee Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 20:02:47 +0100 Subject: [PATCH 53/57] MIES_SweepFormula_PSX.ipf: Remove commented out code --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 74f714cdc2..72c24e22bc 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -857,7 +857,7 @@ static Function [variable start, variable stop] PSX_GetEventFitRange(WAVE sweepD if(eventIndex == (DimSize(psxEvent, ROWS) - 1)) stop = start + maxLength else - stop = min(start + 5 * decayTau, psxEvent[eventIndex + 1][%baseline_t]) // min(psxEvent[eventIndex + 1][%peak_t] * PSX_FIT_RANGE_PERC, psxEvent[eventIndex + 1][%baseline_t]) + stop = min(start + 5 * decayTau, psxEvent[eventIndex + 1][%baseline_t]) endif stop = min(stop, IndexToScale(sweepDataOffFilt, DimSize(sweepDataOffFilt, ROWS), ROWS)) @@ -2214,7 +2214,7 @@ End /// @param forceSingleEventUpdate [optional, defaults to false] update every single event trace regardless of its old state static Function PSX_AdaptColorsInAllEventGraph(string win, [variable forceAverageUpdate, variable forceSingleEventUpdate]) - string extAllGraph, trace, stateType + string extAllGraph, trace, stateType, msg variable i, numWaves, stateMatchPattern, numCombos, stateColumn variable idx, averageUpdateRequired, numSingleEventTraces @@ -2329,7 +2329,10 @@ static Function PSX_AdaptColorsInAllEventGraph(string win, [variable forceAverag trace = traceNames[i] - // printf "Trace %s changed, differentHideState %d, differentEventState %d, new state %s\r", trace, differentHideState[i], differentEventState[i], PSX_StateToString(stateNew[i]) +#ifdef DEBUGGING_ENABLED + sprintf msg, "Trace %s changed, differentHideState %d, differentEventState %d, new state %s\r", trace, differentHideState[i], differentEventState[i], PSX_StateToString(stateNew[i]) + DEBUGPRINT(msg) +#endif // DEBUGGING_ENABLED if(differentHideState[i] && differentEventState[i]) averageUpdateRequired = 1 @@ -2555,7 +2558,6 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime CopyScales average, AverageFit, RiseAverageFit, DecayAverageFit meanOnsetTime = PSX_FilterFitAverageParameters(eventOnsetTime, newState, state) - // meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, eventKernelAmp, binwidth = 0.1, showplot = 1) meanStopTime = PSX_FindDominantStopTimePeak(eventStopTime, newState, state, eventKernelAmp) meanKernelAmp = PSX_FilterFitAverageParameters(eventKernelAmp, newState, state, requireFiniteResult = 1) meanPeakTime = PSX_FilterFitAverageParameters(eventPeakTime, newState, state, requireFiniteResult = 1) @@ -2578,8 +2580,7 @@ static Function PSX_FitAverage(string win, DFREF averageDFR, WAVE eventOnsetTime riselowerThreshold = GetSetVariable(specialEventPanel, "setvar_fit_start_amplitude") * PERCENT_TO_ONE riseAndDecayUpperThreshold = 0.9 - // FindLevel/EDGE=(edge)/Q average, (riselowerThreshold * extrema) - onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) + onsetX = PSX_CalculateOnsetTimeFromAvg(average, meanKernelAmp, meanOnsetTime, meanPeakTime) if(IsNaN(onsetX)) print "onset time calculation must not be Nan, skipping average fits" From 31c35748c744a04835c4bc47ccb45f5ee3ce8808 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 19:27:58 +0100 Subject: [PATCH 54/57] UTF_SweepFormula_PSX.ipf: Fix tests again --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 26 ++- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 208 +++++++++++------- Packages/tests/UTF_DataGenerators.ipf | 34 +-- Packages/tests/UTF_HelperFunctions.ipf | 2 +- 4 files changed, 165 insertions(+), 105 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 72c24e22bc..73d1e40861 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -790,7 +790,7 @@ static Function PSX_GetGoodTauImpl(WAVE psxEvent) variable numEvents, err, xVal, idx, tau - idx = FindDimLabel(psxEvent, COLS, "tau") + idx = FindDimLabel(psxEvent, COLS, "weightedTau") Duplicate/FREE/RMD=[][idx] psxEvent, tauWithNaN WAVE/Z taus = ZapNaNs(tauWithNaN) @@ -921,11 +921,23 @@ static Function PSX_FitEventDecay(WAVE sweepDataOffFilt, WAVE psxEvent, variable err = !overrideResults[eventIndex][%$comboKey][%$"Fit Result"] ASSERT(IsFinite(err), "err needs to be finite") - overrideTau = overrideResults[eventIndex][%$comboKey][%Tau] + overrideTau = overrideResults[eventIndex][%$comboKey][%weightedTau] if(!IsNaN(overrideTau)) weightedTau = overrideTau endif + + overrideTau = overrideResults[eventIndex][%$comboKey][%slowTau] + + if(!IsNaN(overrideTau)) + slowTau = overrideTau + endif + + overrideTau = overrideResults[eventIndex][%$comboKey][%fastTau] + + if(!IsNaN(overrideTau)) + fastTau = overrideTau + endif endif #endif // AUTOMATED_TESTING @@ -982,9 +994,9 @@ static Function/WAVE PSX_CreateOverrideResults(variable numEvents, WAVE/T combos numCombos = DimSize(combos, ROWS) - Make/D/N=(numEvents, numCombos, 3) root:overrideResults/WAVE=wv + Make/D/N=(numEvents, numCombos, 5) root:overrideResults/WAVE=wv SetDimensionLabels(wv, TextWaveToList(combos, ";"), COLS) - SetDimensionLabels(wv, "Fit Result;Tau;KernelAmpSignQC", LAYERS) + SetDimensionLabels(wv, "Fit Result;WeightedTau;FastTau;SlowTau;KernelAmpSignQC", LAYERS) wv[] = NaN @@ -1315,7 +1327,9 @@ static Function [WAVE/D results, WAVE eventIndex, WAVE marker, WAVE/T comboKeys] case "Event manual QC call": // fallthrough case "Slew Rate": // fallthrough case "Slew Rate Time": // fallthrough + case "Rise": // fallthrough case "Rise Time": // fallthrough + case "Onset": // fallthrough case "Onset Time": stateType = "Event manual QC call" break @@ -1752,8 +1766,8 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE s // truncate the input data to get usable histogram bins // using allEvents assumes that the same psxKernel was used for // all input events, which sounds reasonable. - if(!cmpstr(prop, "tau") || !cmpstr(prop, "amp")) - if(!cmpstr(prop, "tau")) + if(!cmpstr(prop, "weightedTau") || !cmpstr(prop, "amp")) + if(!cmpstr(prop, "weightedTau")) lowerBoundary = 0 upperBoundary = PSX_STATS_TAU_FACTOR * JWN_GetNumberFromWaveNote(allEvents, SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/psxKernel/decayTau") ASSERT(IsFinite(upperBoundary) && upperBoundary > 0, "Upper boundary for tau must be finite and positive") diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index d07bb0affa..0fc56c1411 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -596,7 +596,7 @@ static Function StatsRangeTesting() Make/FREE/WAVE psxEventContainer = {psxEventA, psxEventB, psxEventC, psxEventD} MIES_PSX#PSX_StoreIntoResultsWave(browser, SFH_RESULT_TYPE_PSX_EVENTS, psxEventContainer, id) - prop = "tau" + prop = "weightedTau" stateAsStr = MIES_PSX#PSX_StateToString(PSX_ACCEPT) postProc = "nothing" @@ -703,7 +703,7 @@ static Function FillEventWave_IGNORE(WAVE psxEvent, string id, string comboKey) INFO("Check that the size of psxEvent is what we expect") - CHECK_EQUAL_VAR(DimSize(psxEvent, COLS), 17) + CHECK_EQUAL_VAR(DimSize(psxEvent, COLS), 21) psxEvent[][%index] = p psxEvent[][%deconvpeak_t] = 100 * p @@ -714,7 +714,9 @@ static Function FillEventWave_IGNORE(WAVE psxEvent, string id, string comboKey) psxEvent[][%baseline_t] = NaN psxEvent[][%amplitude] = (p == 0) ? NaN : (10 * p) psxEvent[][%iei] = 1000 * p - psxEvent[][%tau] = 1e-6 * p + psxEvent[][%weightedTau] = 1e-6 * p + psxEvent[][%slowTau] = 1e-5 * p + psxEvent[][%fastTau] = 1e-7 * p psxEvent[][%$"Rise Time"] = (p == 0) ? NaN : (0.1 * p) psxEvent[][%$"Onset Time"] = (p == 0) ? NaN : (0.2 * p) psxEvent[][%$"Slew Rate"] = NaN @@ -1192,7 +1194,9 @@ static Function TestOperationPSX([STRUCT IUTF_mData &m]) // all decay fits are successfull overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"fastTau"] = 1 + overrideResults[][][%$"slowTau"] = 1 + overrideResults[][][%$"weightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 [win, device] = CreateEmptyUnlockedDataBrowserWindow() @@ -1200,7 +1204,7 @@ static Function TestOperationPSX([STRUCT IUTF_mData &m]) win = CreateFakeSweepData(win, device, sweepNo = 0, sweepGen = FakeSweepDataGeneratorPSX) win = CreateFakeSweepData(win, device, sweepNo = 2, sweepGen = FakeSweepDataGeneratorPSX) - str = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 15, " + num2str(kernelAmp) + "), 2.5, 100, 0)" + str = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 15, " + num2str(kernelAmp) + "), 2.5, psxSweepBPFilter(100, 0))" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) CHECK_WAVE(dataWref, WAVE_WAVE) CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), 2 * 7) @@ -1222,7 +1226,7 @@ static Function TestOperationPSX([STRUCT IUTF_mData &m]) WAVE/Z params = JSON_GetKeys(jsonID, SF_META_USER_GROUP + "Parameters/" + SF_OP_PSX) CHECK_WAVE(params, TEXT_WAVE) - CHECK_EQUAL_VAR(DimSize(params, ROWS), 5) + CHECK_EQUAL_VAR(DimSize(params, ROWS), 4) WAVE/Z params = JSON_GetKeys(jsonID, SF_META_USER_GROUP + "Parameters/" + SF_OP_PSX_RISETIME) CHECK_WAVE(params, TEXT_WAVE) @@ -1237,7 +1241,7 @@ static Function TestOperationPSX([STRUCT IUTF_mData &m]) CHECK_WAVE(dataWref, WAVE_WAVE) // without events found we get empty waves - str = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 10, 15, -5), 250, 10, 0)" + str = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 10, 15, -5), 250, psxSweepBPFilter(10, 0))" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) CHECK_WAVE(dataWref, WAVE_WAVE) Make/FREE/N=(DimSize(dataWref, ROWS)) sizes = WaveExists(dataWref[p]) ? DimSize(dataWref[p], ROWS) : NaN @@ -1245,7 +1249,7 @@ static Function TestOperationPSX([STRUCT IUTF_mData &m]) // complains with no sweep data try - str = "psx(myID, psxKernel(select(selrange([150, 160]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 2, -5), 2.5, 100, 0)" + str = "psx(myID, psxKernel(select(selrange([150, 160]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 2, -5), 2.5, psxSweepBPFilter(100, 0))" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch @@ -1281,7 +1285,7 @@ static Function PSXHandlesPartialResults() overrideResults[][][%$"Fit Result"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 - str = "psx(myID, psxKernel(select(selrange([25, 120]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 2, -5), 2.5, 10, 0)" + str = "psx(myID, psxKernel(select(selrange([25, 120]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 2, -5), 2.5, psxSweepBPFilter(10, 0))" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, browser, useVariables = 0) PASS() End @@ -1299,7 +1303,7 @@ static Function TestOperationPSXTooLargeDecayTau() // all decay fits are successfull overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%Tau] = 1000 + overrideResults[][][%weightedTau] = 1000 overrideResults[][][%$"KernelAmpSignQC"] = 1 [win, device] = CreateEmptyUnlockedDataBrowserWindow() @@ -1307,7 +1311,7 @@ static Function TestOperationPSXTooLargeDecayTau() win = CreateFakeSweepData(win, device, sweepNo = 0, sweepGen = FakeSweepDataGeneratorPSX) win = CreateFakeSweepData(win, device, sweepNo = 2, sweepGen = FakeSweepDataGeneratorPSX) - str = "psx(myID, psxKernel(select(selrange([50, 150]),selchannels(AD6), selsweeps([0]), selvis(all)), 1, 15, -5), 10, 100, 0)" + str = "psx(myID, psxKernel(select(selrange([50, 150]),selchannels(AD6), selsweeps([0]), selvis(all)), 1, 15, -5), 10, psxSweepBPFilter(100, 0))" WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) CHECK_WAVE(dataWref, WAVE_WAVE) @@ -1353,11 +1357,10 @@ static Function CheckEventDataHelper(WAVE/Z/WAVE dataWref, variable index, varia INFO("index = %d, V_numNaNs = %d, kernelAmpSign = %d", n0 = index, n1 = V_numNans, n2 = kernelAmpSign) - // 5 NaNs for the first event only, the rest is onset Time if(kernelAmpSign == 1) - CHECK_EQUAL_VAR(V_numNaNs, 5) + CHECK_EQUAL_VAR(V_numNaNs, 10) elseif(kernelAmpSign == -1) - CHECK_EQUAL_VAR(V_numNaNs, 9) + CHECK_EQUAL_VAR(V_numNaNs, 13) else FAIL() endif @@ -1371,6 +1374,7 @@ static Function CheckPSXEventField(WAVE/WAVE psxEventWaves, WAVE/T colLabels, WA for(colLabel : colLabels) for(WAVE psxEvent : psxEventWaves) for(idx : indices) + REQUIRE_LT_VAR(idx, DimSize(psxEvent, ROWS)) INFO("psxEvent %s, colLabel \"%s\", index %d, state: actual %s vs ref %s", s0 = NameOfWave(psxEvent), s1 = colLabel, n0 = idx, s3 = MIES_PSX#PSX_StateToString(psxEvent[idx][%$colLabel]), s2 = MIES_PSX#PSX_StateToString(val)) CHECK_EQUAL_VAR(psxEvent[idx][%$colLabel], val) endfor @@ -1406,7 +1410,9 @@ static Function MouseSelectionPSX() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"FastTau"] = 1 + overrideResults[][][%$"SlowTau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = DB_OpenDataBrowser() @@ -1417,7 +1423,7 @@ static Function MouseSelectionPSX() browser = MIES_DB#DB_LockToDevice(browser, device) - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 5, 100, 0)" + code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 5, psxSweepBPFilter(100, 0))" // combo0 is the current one @@ -1431,7 +1437,7 @@ static Function MouseSelectionPSX() CheckPSXEventField({psxEvent_0, psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0, 1}, PSX_UNDET) // select event 0 - SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFiltDeconv" 80, 15e-2, 110, 5e-2 + SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFiltDeconv" 40, 15e-2, 80, 5e-2 SetActiveSubwindow $psxPlot PSX_MouseEventSelection(PSX_ACCEPT, PSX_STATE_EVENT) @@ -1445,7 +1451,7 @@ static Function MouseSelectionPSX() CheckPSXEventField({psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0, 1}, PSX_UNDET) // select event 1 - SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFiltDeconv" 120, 25e-2, 200, 5e-2 + SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFiltDeconv" 80, 25e-2, 110, 5e-2 SetActiveSubwindow $psxPlot PSX_MouseEventSelection(PSX_REJECT, PSX_STATE_EVENT) @@ -1458,7 +1464,7 @@ static Function MouseSelectionPSX() CheckPSXEventField({psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0, 1}, PSX_UNDET) // select both events top axis pair, event and fit state - SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFilt" 50, -1, 200, 11 + SetMarquee/W=$psxPlot/HAX=bottom/VAX=$"leftOffFilt" 40, -1, 110, 11 SetActiveSubwindow $psxPlot PSX_MouseEventSelection(PSX_REJECT, PSX_STATE_FIT | PSX_STATE_EVENT) @@ -1551,7 +1557,9 @@ static Function MouseSelectionPSXStats([STRUCT IUTF_mData &m]) WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"weightedTau"] = 1 + overrideResults[][][%$"slowTau"] = 1 + overrideResults[][][%$"fastTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 postProc = m.s0 @@ -1580,7 +1588,7 @@ static Function MouseSelectionPSXStats([STRUCT IUTF_mData &m]) // select event 0 from combo 0 SetActiveSubwindow $psxStatsGraph - SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 110), 0.1, AdaptForPostProc(postProc, 100) + SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 55), 0.1, AdaptForPostProc(postProc, 40) PSX_MouseEventSelection(PSX_ACCEPT, PSX_STATE_EVENT) @@ -1598,7 +1606,7 @@ static Function MouseSelectionPSXStats([STRUCT IUTF_mData &m]) // select event 0 from combo 1 SetActiveSubwindow $psxStatsGraph - SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 50), 0.1, AdaptForPostProc(postProc, 80) + SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 60), 0.1, AdaptForPostProc(postProc, 55) PSX_MouseEventSelection(PSX_REJECT, PSX_STATE_EVENT) @@ -1638,20 +1646,20 @@ static Function MouseSelectionStatsPostProcNonFinite() combos[1] = comboKey WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) - overrideResults[1][%$combos[0]][%$"Fit Result"] = 1 - overrideResults[1][%$combos[0]][%$"Tau"] = -Inf + overrideResults[1][%$combos[0]][%$"Fit Result"] = 1 + overrideResults[1][%$combos[0]][%$"weightedTau"] = -Inf - overrideResults[0][%$combos[0]][%$"Fit Result"] = 0 - overrideResults[0][%$combos[0]][%$"Tau"] = NaN + overrideResults[0][%$combos[0]][%$"Fit Result"] = 0 + overrideResults[0][%$combos[0]][%$"weightedTau"] = NaN - overrideResults[0][%$combos[1]][%$"Fit Result"] = 1 - overrideResults[0][%$combos[1]][%$"Tau"] = +Inf + overrideResults[0][%$combos[1]][%$"Fit Result"] = 1 + overrideResults[0][%$combos[1]][%$"weightedTau"] = +Inf overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() - code = GetTestCode("nonfinite", eventState = "all", prop = "tau") + code = GetTestCode("nonfinite", eventState = "all", prop = "weightedTau") win = ExecuteSweepFormulaCode(browser, code) @@ -1675,7 +1683,7 @@ static Function MouseSelectionStatsPostProcNonFinite() CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0}, PSX_REJECT) CheckPSXEventField({psxEvent_0}, {"Event manual QC call"}, {0}, PSX_UNDET) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call", "Event manual QC call"}, {1}, PSX_UNDET) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call", "Event manual QC call"}, {2}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0}, PSX_UNDET) @@ -1691,7 +1699,7 @@ static Function MouseSelectionStatsPostProcNonFinite() // unchanged CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0}, PSX_REJECT) CheckPSXEventField({psxEvent_0}, {"Event manual QC call"}, {0}, PSX_UNDET) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call", "Event manual QC call"}, {1}, PSX_UNDET) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call", "Event manual QC call"}, {2}, PSX_UNDET) // changed CheckPSXEventField({psxEvent_1}, {"Fit manual QC call", "Event manual QC call"}, {0}, PSX_ACCEPT) @@ -1710,7 +1718,7 @@ static Function KeyboardSelectionPSXStatsVS() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -1772,6 +1780,10 @@ static Function CheckTraceColors(string win, WAVE/T traces, variable state) endswitch for(trace : traces) + if(strsearch(trace, "AverageFit", 0) >= 0) + Make/FREE refColors = {65535, 0, 0} + endif + tInfo = TraceInfo(win, trace, 0) WAVE traceColors = NumericWaveByKey("rgb(x)", tInfo, keySep = "=", listSep = ";") @@ -1800,8 +1812,11 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) combos[1] = comboKey WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) - overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"Fit Result"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 + overrideResults[][][%$"slowTau"] = 1 + overrideResults[][][%$"fastTau"] = 1 + overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -1837,23 +1852,50 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) WAVE/T allTraces = GetTracesHelper(extAllGraph, 1) - Make/FREE/T allTracesRef = {"T000000", "T000001", "T000002", "T000003", \ - "T000004_averageAccept_ComboIndex0", "T000005_averageReject_ComboIndex0", \ - "T000006_averageUndetermined_ComboIndex0", "T000007_averageAll_ComboIndex0", \ - "T000008_acceptAverageFit_ComboIndex0", \ - "T000009", "T000010", "T000011", \ - "T000012_averageAccept_ComboIndex1", "T000013_averageReject_ComboIndex1", \ - "T000014_averageUndetermined_ComboIndex1", "T000015_averageAll_ComboIndex1", \ - "T000016_acceptAverageFit_ComboIndex1", \ - "T000017_averageAccept_global", "T000018_averageReject_global", \ - "T000019_averageUndetermined_global", "T000020_averageAll_global", \ - "T000021_acceptAverageFit_global"} + Make/FREE/T allTracesRef = {"T000000", "T000001", "T000002", "T000003_averageAccept_ComboIndex0", \ + "T000004_Accept_AverageFit_ComboIndex0", \ + "T000005_Accept_RiseAverageFit_ComboIndex0", \ + "T000006_Accept_DecayAverageFit_ComboIndex0", \ + "T000007_averageReject_ComboIndex0", "T000008_Reject_AverageFit_ComboIndex0", \ + "T000009_Reject_RiseAverageFit_ComboIndex0", \ + "T000010_Reject_DecayAverageFit_ComboIndex0", \ + "T000011_averageUndetermined_ComboIndex0", \ + "T000012_Undetermined_AverageFit_ComboIndex0", \ + "T000013_Undetermined_RiseAverageFit_ComboIndex0", \ + "T000014_Undetermined_DecayAverageFit_ComboIndex0", \ + "T000015_averageAll_ComboIndex0", "T000016_All_AverageFit_ComboIndex0", \ + "T000017_All_RiseAverageFit_ComboIndex0", \ + "T000018_All_DecayAverageFit_ComboIndex0", "T000019", "T000020", "T000021", \ + "T000022", "T000023_averageAccept_ComboIndex1", \ + "T000024_Accept_AverageFit_ComboIndex1", \ + "T000025_Accept_RiseAverageFit_ComboIndex1", \ + "T000026_Accept_DecayAverageFit_ComboIndex1", \ + "T000027_averageReject_ComboIndex1", "T000028_Reject_AverageFit_ComboIndex1", \ + "T000029_Reject_RiseAverageFit_ComboIndex1", \ + "T000030_Reject_DecayAverageFit_ComboIndex1", \ + "T000031_averageUndetermined_ComboIndex1", \ + "T000032_Undetermined_AverageFit_ComboIndex1", \ + "T000033_Undetermined_RiseAverageFit_ComboIndex1", \ + "T000034_Undetermined_DecayAverageFit_ComboIndex1", \ + "T000035_averageAll_ComboIndex1", "T000036_All_AverageFit_ComboIndex1", \ + "T000037_All_RiseAverageFit_ComboIndex1", \ + "T000038_All_DecayAverageFit_ComboIndex1", "T000039_averageAccept_global", \ + "T000040_Accept_AverageFit_global", "T000041_Accept_RiseAverageFit_global", \ + "T000042_Accept_DecayAverageFit_global", "T000043_averageReject_global", \ + "T000044_Reject_AverageFit_global", "T000045_Reject_RiseAverageFit_global", \ + "T000046_Reject_DecayAverageFit_global", \ + "T000047_averageUndetermined_global", \ + "T000048_Undetermined_AverageFit_global", \ + "T000049_Undetermined_RiseAverageFit_global", \ + "T000050_Undetermined_DecayAverageFit_global", "T000051_averageAll_global", \ + "T000052_All_AverageFit_global", "T000053_All_RiseAverageFit_global", \ + "T000054_All_DecayAverageFit_global"} CHECK_EQUAL_TEXTWAVES(allTracesRef, allTraces) // currently shown traces WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000003", "T000009", "T000010", "T000011"} + Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000019", "T000020", "T000021", "T000022"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, dispTraces, PSX_UNDET) @@ -1883,7 +1925,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_undetermined", val = 1) WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000019_averageUndetermined_global"} + Make/FREE/T dispTracesRef = {"T000047_averageUndetermined_global", "T000048_Undetermined_AverageFit_global", "T000049_Undetermined_RiseAverageFit_global", "T000050_Undetermined_DecayAverageFit_global"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, dispTraces, PSX_UNDET) @@ -1893,7 +1935,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_all", val = 1) WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000020_averageAll_global"} + Make/FREE/T dispTracesRef = {"T000051_averageAll_global", "T000052_All_AverageFit_global", "T000053_All_RiseAverageFit_global", "T000054_All_DecayAverageFit_global"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, dispTraces, PSX_ALL) @@ -1901,7 +1943,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) // restrict to current combo PGC_SetAndActivateControl(specialEventPanel, "checkbox_restrict_events_to_current_combination", val = 1) WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000015_averageAll_ComboIndex1"} + Make/FREE/T dispTracesRef = {"T000035_averageAll_ComboIndex1", "T000036_All_AverageFit_ComboIndex1", "T000037_All_RiseAverageFit_ComboIndex1", "T000038_All_DecayAverageFit_ComboIndex1"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, dispTraces, PSX_ALL) @@ -1915,13 +1957,13 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_undetermined", val = 0) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_all", val = 1) - WAVE averageWaveFromTrace = TraceNameToWaveRef(extAllGraph, "T000007_averageAll_ComboIndex0") + WAVE averageWaveFromTrace = TraceNameToWaveRef(extAllGraph, "T000015_averageAll_ComboIndex0") DFREF comboDFR = MIES_PSX#PSX_GetCurrentComboFolder(win) DFREF singleEventDFR = GetPSXSingleEventFolder(comboDFR) WAVE/WAVE singleEventWaves = ListToWaveRefWave(GetListOfObjects(singleEventDFR, ".*", fullPath = 1)) - CHECK_EQUAL_VAR(DimSize(singleEventWaves, ROWS), 4) + CHECK_EQUAL_VAR(DimSize(singleEventWaves, ROWS), 3) WAVE/Z/WAVE calcAvgPack = MIES_fWaveAverage(singleEventWaves, 1, IGOR_TYPE_64BIT_FLOAT) CHECK_WAVE(calcAvgPack, WAVE_WAVE) WAVE/Z calcAvg = calcAvgPack[0] @@ -1932,7 +1974,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_undetermined", val = 1) PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_all", val = 0) - WAVE averageWaveFromTrace = TraceNameToWaveRef(extAllGraph, "T000006_averageUndetermined_ComboIndex0") + WAVE averageWaveFromTrace = TraceNameToWaveRef(extAllGraph, "T000011_averageUndetermined_ComboIndex0") CHECK_EQUAL_WAVES(calcAvg, averageWaveFromTrace, mode = WAVE_DATA) // now let's change some event/fit states @@ -1950,7 +1992,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) MIES_PSX#PSX_UpdateEventWaves(win, val = PSX_ACCEPT, index = 1, stateType = PSX_STATE_FIT) WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000003"} + Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, dispTraces, PSX_UNDET) @@ -1961,7 +2003,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) DoUpdate WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000003"} + Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) CheckTraceColors(extAllGraph, {"T000000"}, PSX_REJECT) @@ -1977,7 +2019,7 @@ static Function AllEventGraph([STRUCT IUTF_mData &m]) PGC_SetAndActivateControl(specialEventPanel, "popupmenu_state_type", str = "Event*") WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000000", "T000002", "T000003"} + Make/FREE/T dispTracesRef = {"T000000", "T000002"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) End @@ -2085,7 +2127,7 @@ static Function JumpToSelectedEvents([STRUCT IUTF_mData &m]) // select event 0, combo 1 SetActiveSubwindow $psxStatsGraph - SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 49), 0.1, AdaptForPostProc(postProc, 80) + SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 55), 0.1, AdaptForPostProc(postProc, 80) PSX_JumpToEvents() @@ -2094,7 +2136,7 @@ static Function JumpToSelectedEvents([STRUCT IUTF_mData &m]) // select all events SetActiveSubwindow $psxStatsGraph - SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 49), 1.1, AdaptForPostProc(postProc, 100) + SetMarquee/W=$psxStatsGraph/HAX=bottom/VAX=left -0.1, AdaptForPostProc(postProc, 40), 1.1, AdaptForPostProc(postProc, 200) PSX_JumpToEvents() @@ -2248,7 +2290,7 @@ static Function KeyboardInteractions() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -2516,7 +2558,7 @@ static Function KeyboardInteractionsStats() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -2786,7 +2828,7 @@ static Function KeyboardInteractionsStatsSpecial() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -2857,23 +2899,23 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() combos[1] = comboKey WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) - overrideResults[1][%$combos[0]][%$"Fit Result"] = 1 - overrideResults[1][%$combos[0]][%$"Tau"] = -Inf + overrideResults[1][%$combos[0]][%$"Fit Result"] = 1 + overrideResults[1][%$combos[0]][%$"weightedTau"] = -Inf - overrideResults[3][%$combos[0]][%$"Fit Result"] = 1 - overrideResults[3][%$combos[0]][%$"Tau"] = -Inf + overrideResults[3][%$combos[0]][%$"Fit Result"] = 1 + overrideResults[3][%$combos[0]][%$"weightedTau"] = -Inf - overrideResults[0][%$combos[0]][%$"Fit Result"] = 0 - overrideResults[0][%$combos[0]][%$"Tau"] = NaN + overrideResults[0][%$combos[0]][%$"Fit Result"] = 0 + overrideResults[0][%$combos[0]][%$"weightedTau"] = NaN - overrideResults[0][%$combos[1]][%$"Fit Result"] = 1 - overrideResults[0][%$combos[1]][%$"Tau"] = +Inf + overrideResults[0][%$combos[1]][%$"Fit Result"] = 1 + overrideResults[0][%$combos[1]][%$"weightedTau"] = +Inf overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() - code = GetTestCode("nonfinite", eventState = "all", prop = "tau") + code = GetTestCode("nonfinite", eventState = "all", prop = "weightedTau") win = ExecuteSweepFormulaCode(browser, code) @@ -2895,8 +2937,8 @@ static Function KeyboardInteractionsStatsPostProcNonFinite() [WAVE psxEvent_0, WAVE psxEvent_1] = GetPSXEventWavesHelper(psxStatsGraph) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0}, PSX_REJECT) - CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {1, 2, 3}, PSX_UNDET) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {2}, PSX_UNDET) + CheckPSXEventField({psxEvent_0}, {"Fit manual QC call"}, {0, 1, 3}, PSX_REJECT) CheckPSXEventField({psxEvent_0}, {"Event manual QC call"}, {0, 1, 2, 3}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Fit manual QC call"}, {0, 1, 2}, PSX_UNDET) CheckPSXEventField({psxEvent_1}, {"Event manual QC call"}, {0, 1, 2}, PSX_UNDET) @@ -2930,7 +2972,7 @@ static Function NoEventsAtAll() browser = SetupDatabrowserWithSomeData() - code = "psx(psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 100, 100, 0)" + code = "psx(psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 100, psxSweepBPFilter(100, 0))" win = ExecuteSweepFormulaCode(browser, code, expectFailure = 1) @@ -2978,7 +3020,7 @@ static Function CheckResultsWavesForAverageFitResult() PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_accept", val = 1) CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) - PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_fit", val = 1) + PGC_SetAndActivateControl(specialEventPanel, "checkbox_events_fit_accept", val = 1) // our data makes the fit fail CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) @@ -3018,7 +3060,7 @@ static Function TestBlockIndexLogic() CHECK_EQUAL_STR(PSX_GetAllEventBlockNumbers(specialEventPanel), "0;") WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000003", "T000009", "T000010", "T000011"} + Make/FREE/T dispTracesRef = {"T000000", "T000001", "T000002", "T000003", "T000020", "T000021", "T000022"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) // 50% block size @@ -3043,7 +3085,7 @@ static Function TestBlockIndexLogic() CHECK_EQUAL_STR(GetPopupMenuString(specialEventPanel, "popup_block"), "1") WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) - Make/FREE/T dispTracesRef = {"T000003", "T000009", "T000010", "T000011"} + Make/FREE/T dispTracesRef = {"T000003", "T000020", "T000021", "T000022"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) // 33% block size @@ -3094,7 +3136,7 @@ static Function TestBlockIndexLogic() WAVE/T dispTraces = GetTracesHelper(extAllGraph, 1 + 2^2) // while it is suprising to see four here and only one in the other blocks it // works with larger event numbers from real data - Make/FREE/T dispTracesRef = {"T000003", "T000009", "T000010", "T000011"} + Make/FREE/T dispTracesRef = {"T000003", "T000020", "T000021", "T000022"} CHECK_EQUAL_TEXTWAVES(dispTracesRef, dispTraces) // current combination only @@ -3273,7 +3315,7 @@ static Function TestOperationPrep() win = CreateFakeSweepData(win, device, sweepNo = 0, sweepGen = FakeSweepDataGeneratorPSX) - psxCode = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 15, -5), 2.5, 100, 0)" + psxCode = "psx(myID, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), 1, 15, -5), 2.5, psxSweepBPFilter(100, 0))" sprintf code, "psxPrep(%s)", psxCode WAVE/WAVE dataWref = SFE_ExecuteFormula(code, win, useVariables = 0) @@ -3320,7 +3362,7 @@ static Function TestStoreAndLoad() WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) overrideResults[][][%$"Fit Result"] = 1 - overrideResults[][][%$"Tau"] = 1 + overrideResults[][][%$"WeightedTau"] = 1 overrideResults[][][%$"KernelAmpSignQC"] = 1 browser = SetupDatabrowserWithSomeData() @@ -3414,7 +3456,7 @@ static Function [variable filterLow, variable filterHigh, variable filterOrder] CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), 1) WAVE/Z data = dataWref[0] CHECK_WAVE(data, NUMERIC_WAVE) - CHECK_EQUAL_VAR(DimSize(data, ROWS), 3) + CHECK_EQUAL_VAR(DimSize(data, ROWS), 6) filterLow = data[%$"Filter Low"] if(IsNaN(filterLow)) @@ -3435,9 +3477,13 @@ static Function [variable filterLow, variable filterHigh, variable filterOrder] if(IsNaN(filterOrder)) PASS() else - CHECK(IsOdd(filterOrder)) + CHECK(IsEven(filterOrder)) endif + CHECK_EQUAL_VAR(data[%$"Filter Low (Default)"], NaN) + CHECK_EQUAL_VAR(data[%$"Filter High (Default)"], NaN) + CHECK_EQUAL_VAR(data[%$"Filter Order (Default)"], 4) + return [filterLow, filterHigh, filterOrder] End @@ -3476,7 +3522,7 @@ static Function TestOperationDeconvBPFilter() // we automatically fixup the order for the user CHECK_EQUAL_VAR(filterLow, 50) CHECK_EQUAL_VAR(filterHigh, 40) - CHECK_EQUAL_VAR(filterOrder, 11) + CHECK_EQUAL_VAR(filterOrder, 12) // check parameters try diff --git a/Packages/tests/UTF_DataGenerators.ipf b/Packages/tests/UTF_DataGenerators.ipf index f65dd95df4..6595a6ed05 100644 --- a/Packages/tests/UTF_DataGenerators.ipf +++ b/Packages/tests/UTF_DataGenerators.ipf @@ -1242,7 +1242,7 @@ static Function/WAVE StatsTest_GetInput() Duplicate/FREE/T template, wv3 WAVE/T input = wv3 - input[%prop] = "tau" + input[%prop] = "weightedTau" input[%state] = "accept" input[%postProc] = "nothing" @@ -1258,11 +1258,12 @@ static Function/WAVE StatsTest_GetInput() input[%state] = "accept" input[%postProc] = "stats" - JWN_SetWaveInWaveNote(input, "/results", {40, 40, 20, 25.81988897471611, 0, -2.0775}) + JWN_SetWaveInWaveNote(input, "/results", {40, 40, 20, 25.81988897471611, 0, -2.0775, 20, 60, 40, 20, NaN}) JWN_SetWaveInWaveNote(input, "/xValues", ListToTextWave(PSX_STATS_LABELS, ";")) JWN_SetWaveInWaveNote(input, "/marker", {PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, \ - PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT}) - + PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, \ + PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT, \ + PSX_MARKER_ACCEPT, PSX_MARKER_ACCEPT}) // wv5 Duplicate/FREE/T template, wv5 WAVE/T input = wv5 @@ -1414,7 +1415,7 @@ Function/WAVE StatsTestSpecialCases_GetInput() Duplicate/FREE/T template, wv3 WAVE/T input = wv3 - input[%prop] = "tau" + input[%prop] = "weightedTau" input[%state] = "undetermined" input[%postProc] = "hist" input[%refNumOutputRows] = "0" @@ -1454,10 +1455,12 @@ Function/WAVE StatsTestSpecialCases_GetInput() input[%outOfRange] = "0" JWN_CreatePath(input, "/0") - JWN_SetWaveInWaveNote(input, "/0/results", {10, NaN, 0, 0, NaN, NaN}) + JWN_SetWaveInWaveNote(input, "/0/results", {10, NaN, 0, 0, NaN, NaN, NaN, NaN, NaN, NaN, NaN}) JWN_SetWaveInWaveNote(input, "/0/xValues", ListToTextWave(PSX_STATS_LABELS, ";")) JWN_SetWaveInWaveNote(input, "/0/marker", {PSX_MARKER_REJECT, PSX_MARKER_REJECT, PSX_MARKER_REJECT, \ - PSX_MARKER_REJECT, PSX_MARKER_REJECT, PSX_MARKER_REJECT}) + PSX_MARKER_REJECT, PSX_MARKER_REJECT, PSX_MARKER_REJECT, \ + PSX_MARKER_REJECT, PSX_MARKER_REJECT, PSX_MARKER_REJECT, \ + PSX_MARKER_REJECT, PSX_MARKER_REJECT}) // wv6 // stats ignores NaN and with all data NaN we don't get anything @@ -1538,27 +1541,24 @@ static Function/WAVE GetCodeVariations() string code - Make/T/N=(3)/FREE wv - - wv[0] = GetTestCode("nothing") - code = "" + Make/T/N=(2)/FREE wv // one sweep per operation separated with `with` - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0]), selvis(all))), 2, 100, 0)" + code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0]), selvis(all))), 2, psxSweepBPFilter(100, 0))" code += "\r with \r" - code += "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([2]), selvis(all))), 1.5, 100, 0)" + code += "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([2]), selvis(all))), 0.3, psxSweepBPFilter(100, 0))" code += "\r and \r" code += "psxStats(myId, select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), peak, all, nothing)" - wv[1] = code + wv[0] = code code = "" // same as code[1] but with a select array for stats - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0]), selvis(all))), 2, 100, 0)" + code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0]), selvis(all))), 2, psxSweepBPFilter(100, 0))" code += "\r with \r" - code += "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([2]), selvis(all))), 1.5, 100, 0)" + code += "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([2]), selvis(all))), 0.3, psxSweepBPFilter(100, 0))" code += "\r and \r" code += "psxStats(myId, [select(selrange([50, 150]), selchannels(AD6), selsweeps([0]), selvis(all)), select(selrange([50, 150]), selchannels(AD6), selsweeps([2]), selvis(all))], peak, all, nothing)" - wv[2] = code + wv[1] = code code = "" return wv diff --git a/Packages/tests/UTF_HelperFunctions.ipf b/Packages/tests/UTF_HelperFunctions.ipf index 84e551bd86..1ef4a9d2f3 100644 --- a/Packages/tests/UTF_HelperFunctions.ipf +++ b/Packages/tests/UTF_HelperFunctions.ipf @@ -1839,7 +1839,7 @@ Function/S GetTestCode(string postProc, [string eventState, string prop]) prop = "peaktime" endif - code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 1.5, 100, 0)" + code = "psx(myId, psxKernel(select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all))), 0.3, psxSweepBPFilter(100, 0))" code += "\r and \r" code += "psxStats(myId, select(selrange([50, 150]), selchannels(AD6), selsweeps([0, 2]), selvis(all)), " + prop + ", " + eventState + ", " + postProc + ")" From f57c291a7cc8abf3a8b491fec8d800d43e4d714f Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 26 Jan 2026 19:38:59 +0100 Subject: [PATCH 55/57] GetAllFilesRecursivelyFromPath: Kill path before returning We currently leak Igor path objects, so let's kill it before returning. Bug introduced in 2436f408c7 (GetAllFilesRecursivelyFromPath: Rework it completely, 2025-04-16). --- Packages/MIES/MIES_Utilities_File.ipf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Packages/MIES/MIES_Utilities_File.ipf b/Packages/MIES/MIES_Utilities_File.ipf index b05fe70fea..8830fb4ea1 100644 --- a/Packages/MIES/MIES_Utilities_File.ipf +++ b/Packages/MIES/MIES_Utilities_File.ipf @@ -293,6 +293,8 @@ Function/WAVE GetAllFilesRecursivelyFromPath(string pathName, [string regex, var endif endfor + KillPath $workPath + if(!numFiles) return $"" endif From 759770e2765295f091bfbc901eccb7b4c0dea46b Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 23 Jan 2026 23:03:01 +0100 Subject: [PATCH 56/57] UTF_SweepFormula_PSX.ipf: Add some more tests --- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 122 +++++++++++++++--- Packages/tests/UTF_DataGenerators.ipf | 29 +++++ 2 files changed, 136 insertions(+), 15 deletions(-) diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 0fc56c1411..9a9efbcdb0 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -2984,9 +2984,89 @@ static Function NoEventsAtAll() endtry End -static Function CheckResultsWavesForAverageFitResult() +static Function CheckNumUserTicksForAxis(string graph, string axis, variable enabled) - string browser, code, psxGraph, win, mainWindow, specialEventPanel, name, entry, comboKey + string info + variable numUserTicks + + enabled = !!enabled + + // disabled axis has + // userticks(x)=0 + // and an enabled one has + // userticks(x)={::MIES:HardwareDevices:ITC16:Device0:Databrowser0:FormulaData:psx:combo_0:eventTicks_freeDeconvAxis, + // ::MIES:HardwareDevices:ITC16:Device0:Databrowser0:FormulaData:psx:combo_0:eventLabels_freeDeconvAxis} + // which converts to NaN as numerical + info = AxisInfo(graph, axis) + CHECK_PROPER_STR(info) + numUserTicks = GetNumFromModifyStr(info, "userTicks", "", 0); ClearRTError() + + if(enabled) + CHECK_EQUAL_VAR(numUserTicks, NaN) + else + CHECK_EQUAL_VAR(numUserTicks, 0) + endif +End + +// IUTF_TD_GENERATOR s0:DataGenerators#GetPSXHelperAxisTypes +static Function CheckHelperFreeAxis([STRUCT IUTF_mData &m]) + + string browser, code, psxGraph, win, mainWindow, specialEventPanel, name, entry, comboKey, state, axisType, ctrl + string info, axisName + variable numUserTicks, numTicks + + axisType = m.s0 + + Make/FREE/T/N=2 combos + sprintf comboKey, "Range[50, 150], Sweep [0], Channel [AD6], Device [ITC16_Dev_0], Experiment [%s]", GetExperimentName() + combos[0] = comboKey + sprintf comboKey, "Range[50, 150], Sweep [2], Channel [AD6], Device [ITC16_Dev_0], Experiment [%s]", GetExperimentName() + combos[1] = comboKey + WAVE overrideResults = MIES_PSX#PSX_CreateOverrideResults(4, combos) + + overrideResults[][][%$"KernelAmpSignQC"] = 1 + + browser = SetupDatabrowserWithSomeData() + + code = GetTestCode("nothing") + + win = ExecuteSweepFormulaCode(browser, code) + + mainWindow = GetMainWindow(win) + psxGraph = MIES_PSX#PSX_GetPSXGraph(win) + specialEventPanel = MIES_PSX#PSX_GetSpecialPanel(win) + + REQUIRE(WindowExists(psxGraph)) + + SetActiveSubwindow $psxGraph + + CHECK_EQUAL_STR(AxisList(psxGraph), "leftOffFilt;bottom;leftOffFiltDeconv;freeDeconvAxis;freePeakAxis;freeBaselineAxis;") + + // by default shown + CheckNumUserTicksForAxis(psxGraph, "freeDeconvAxis", 1) + + // hidden by default + CheckNumUserTicksForAxis(psxGraph, "freePeakAxis", 0) + CheckNumUserTicksForAxis(psxGraph, "freeBaselineAxis", 0) + + if(cmpstr(axisType, NONE)) + PGC_SetAndActivateControl(mainWindow, "checkbox_show_deconv_lines", val = 0) + CheckNumUserTicksForAxis(psxGraph, "freeDeconvAxis", 0) + + sprintf ctrl, "checkbox_show_%s_lines", axisType + PGC_SetAndActivateControl(mainWindow, ctrl, val = 1) + + sprintf axisName, "free%sAxis", UpperCaseFirstChar(axisType) + + CheckNumUserTicksForAxis(psxGraph, axisName, 1) + endif +End + +// IUTF_TD_GENERATOR v0:DataGenerators#GetAllPSXStates +static Function CheckResultsWavesForAverageFitResult([STRUCT IUTF_mData &m]) + + string browser, code, psxGraph, win, mainWindow, specialEventPanel, name, entry, comboKey, state + state = MIES_PSX#PSX_StateToString(m.v0) Make/FREE/T/N=2 combos sprintf comboKey, "Range[50, 150], Sweep [0], Channel [AD6], Device [ITC16_Dev_0], Experiment [%s]", GetExperimentName() @@ -3017,14 +3097,23 @@ static Function CheckResultsWavesForAverageFitResult() WAVE/T textualResultsValues = GetLogbookWaves(LBT_RESULTS, LBN_TEXTUAL_VALUES) CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) - PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_accept", val = 1) + PGC_SetAndActivateControl(specialEventPanel, "checkbox_average_events_" + state, val = 1) CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) - PGC_SetAndActivateControl(specialEventPanel, "checkbox_events_fit_accept", val = 1) + PGC_SetAndActivateControl(specialEventPanel, "checkbox_events_fit_" + state, val = 1) + // our data makes the fit fail - CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) + strswitch(state) + case "all": // fallthrough + case "undetermined": + CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 2) + break + default: + CHECK_EQUAL_VAR(GetNumberFromWaveNote(textualResultsValues, NOTE_INDEX), 1) + break + endswitch - name = SFH_FormatResultsKey(SFH_RESULT_TYPE_PSX_MISC, "accepted average fit results") + name = SFH_FormatResultsKey(SFH_RESULT_TYPE_PSX_MISC, state + "ed average fit results") entry = GetLastSettingTextIndep(textualResultsValues, NaN, name, SWEEP_FORMULA_RESULT) CHECK_EMPTY_STR(entry) End @@ -3487,28 +3576,31 @@ static Function [variable filterLow, variable filterHigh, variable filterOrder] return [filterLow, filterHigh, filterOrder] End -static Function TestOperationDeconvBPFilter() +/// IUTF_TD_GENERATOR s0:DataGenerators#GetAllPSXFilterOperations +static Function TestOperationFilters([STRUCT IUTF_mData &m]) - string win, str + string win, str, op variable filterLow, filterHigh, filterOrder win = SetupDatabrowserWithSomeData() - str = "psxDeconvBPFilter()" + op = m.s0 + + sprintf str, "%s()", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) CHECK_EQUAL_VAR(filterLow, NaN) CHECK_EQUAL_VAR(filterHigh, NaN) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvBPFilter(40)" + sprintf str, "%s(40)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) CHECK_EQUAL_VAR(filterLow, 40) CHECK_EQUAL_VAR(filterHigh, NaN) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvBPFilter(40, 50)" + sprintf str, "%s(40, 50)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) // we automatically fixup the order for the user @@ -3516,7 +3608,7 @@ static Function TestOperationDeconvBPFilter() CHECK_EQUAL_VAR(filterHigh, 40) CHECK_EQUAL_VAR(filterOrder, NaN) - str = "psxDeconvBPFilter(40, 50, 11)" + sprintf str, "%s(40, 50, 11)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) [filterLow, filterHigh, filterOrder] = TestDevonvFilterContainer(dataWref) // we automatically fixup the order for the user @@ -3526,7 +3618,7 @@ static Function TestOperationDeconvBPFilter() // check parameters try - str = "psxDeconvBPFilter(-1)" + sprintf str, "%s(-1)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch @@ -3534,7 +3626,7 @@ static Function TestOperationDeconvBPFilter() endtry try - str = "psxDeconvBPFilter(1, -1)" + sprintf str, "%s(1, -1)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch @@ -3542,7 +3634,7 @@ static Function TestOperationDeconvBPFilter() endtry try - str = "psxDeconvBPFilter(1, 1, -1)" + sprintf str, "%s(1, 1, -1)", op WAVE/WAVE dataWref = SFE_ExecuteFormula(str, win, useVariables = 0) FAIL() catch diff --git a/Packages/tests/UTF_DataGenerators.ipf b/Packages/tests/UTF_DataGenerators.ipf index 6595a6ed05..a59bf76e89 100644 --- a/Packages/tests/UTF_DataGenerators.ipf +++ b/Packages/tests/UTF_DataGenerators.ipf @@ -1965,3 +1965,32 @@ static Function/WAVE DG_SourceLocationsContent() return wv End + +static Function/WAVE GetAllPSXFilterOperations() + + Make/FREE/T wv = {"psxDeconvBPFilter", "psxSweepBPFilter"} + + SetDimensionLabelsFromWaveContents(wv) + + return wv +End + +static Function/WAVE GetAllPSXStates() + + WAVE wv = MIES_PSX#PSX_GetStates(withAllState = 1) + + Make/FREE/T/N=(DimSize(wv, ROWS)) labels = MIES_PSX#PSX_StateToString(wv[p]) + + SetDimensionLabels(wv, TextWaveToList(labels, ";"), ROWS) + + return wv +End + +static Function/WAVE GetPSXHelperAxisTypes() + + Make/FREE/T wv = {NONE, "deconv", "peak", "baseline"} + + SetDimensionLabelsFromWaveContents(wv) + + return wv +End From 8d71c757f534f759ffd572bc2a062e2681ef4d8a Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 26 Jan 2026 22:48:27 +0100 Subject: [PATCH 57/57] Packages/MIES/SweepFormulaHelp.ifn: Update it --- Packages/MIES/SweepFormulaHelp.ifn | Bin 376926 -> 379959 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Packages/MIES/SweepFormulaHelp.ifn b/Packages/MIES/SweepFormulaHelp.ifn index 2f802f9f212ee66fb5efdaf63d959522d75d3e2a..5d4d74547c11a29ab8e7ba735d8c1fd6b8660e09 100644 GIT binary patch delta 12407 zcmciIe_WO2;qdY6zB%XcBS1t#RK!bWibaG(=8VWl36%_$nd9pSkV9Yy1V=VpsB3h%83w(y^w}Uk>nkBTDC0zYfY6!Hmakaz zAGhbnh?lyjo*bW1M7tEC@-|iE^Cpf@6i+L{(^VBiD}}g6a}c+`rx!lt-~GN`1XJc) zgqDB*7oqlcfV$GnQm}^p{Lc^cBFvI?*H7crbO)v3^fL}iztf{{>(MKvfy#Z_?hs|| zlB}G(y+@74tcaM%v~-g(J79gFR{o&kwfG_7Nvra!e_C^hvhKF8%6&`w{2t|=)6IvM zw*}m*gsRIHTl@L%6}pLF?b-Vk-(~;OOoGhJGG-^5(lRsL+JZJJYm~*F)&}|bR^!3U zjNFJAE%AP3nXBX&H1sBQZVwm^_@A zY?Q`aV}?x3k)~8*w$V}`BU2`)rKFI`I7mi@y#_b2!&;}UqJ)eDnV69*6Efsoazlbl zK4O%nOqq5#E8Te5m|-?q`z(`!Yey{ACtGJEGyRCkOgq+Bw3s6kWm4k7 zr!6%kXCBRPvurjY64Rf`%uX|<9=29XM~&Hs(=sfDELGh)Ye6!WoSQ?lk1WTOm~G0D zN2$T`0XMPDTI*A3mg&qiTW8xcLG&rxG9#2tGs&c5(sVS_GTrH!v}Buhdt;WSt9xW7jw5u-u5L3uqBtq)V?ry~s>DRQi3hEP5<(LKZ3~obeC7x{G|jTUY}|v1=?9Ob zCz=>lN_OU9nVptnv~CQ#k;tqk9$|BBw$`C7-llxr(Y_6|o!gX0(=5|zUBz%~i4=R~ z$7F6|`Vo5zOpxsq(Zs>b>}=yfQ~EJ$XO3BWkaILKE6bQ1GZ!i-*#)}oK zgs!;R5zGB(y`yMzuk9AonBK2xm5(Ts|DUGV{QqQ%{cf3J`^ap!Nx-I?Ta`=ZOa7hC zldh+0|LpAh)g0JMS~vB5mW{pP^ltOY)6Kj~dgSN@eHSlSb|alhInn9&@wZW>R}=r?O1@Xs6=0+_FC{ zMKUvTj3!gs;ja>HU+h%&zHFAmd^uf*KVOz%&O+`(7EY3`$0b}LZE#_25rac6LY^S3 z!61gQ=QhgY24Y-=$VCC_(S$iEA0 zw_pO=(T^cqhxZa8HX#m0IEw*XhnJTSq1b~sbYS!CbOb(TZ$^R0rE~)2m_&$=5Di$q zOo(K(q6?!?mkY55k;p;?Ix!A)1@EvGNjQXVcyTADVhDHg+hPymV9w)FjUG&32AlcK zQidj+hi;V+u^51#B#r~fM-93$0o`gL)*=$gC_pv3F##8UA%d|F`Kb06X3<4t3a(#c z)YykSRH7AK&~fj^qY?9nxr1LyC__8C;jvbT6m-C29ivAldN2V)fDi$QK`yG%9bo1q z5#4$r0N5Q?J)CV2Wz{NQNTNzM$v~G@Oq3wNJasicF_rJ zew?+2YYd&kC_;AASrnrdqnLx-!;I|_G6DyPC!-Xt7&P;6*~{AE)LtQn2uBebOT%bK zH%j(#H{j9}LX4o|03C=YMEaAA9%s>v2`nH!fz5_qOu#>pI#G_wL?K&X?k6z>k;G+# zUD$_F%pvt48|I*p)r9rv#}w&?WTxK8R3k8jsmB;np5oY{2f9?spc1vILUs^dz%{s} z2~n3O%rfH;6{7ZOHW#wfsSF{988yn$heOuP-qzh425h=q@o!?-zJVMG@}ojinyqXgp4Chg83AWCNiqO!!$vjV7H9Kps!fSNy01ed4aQx1Gt8e625>CjpH~0m+x{MF^FOKpJX$hWPhg*=Y|;+LT-WfPT>N7#vw?=ed3M;7vM2Gh6(&oXvBick;t?{lmXiVDLprdr5DgeTC9J`}vl zF@}2$9mD}7<18kzfQTQm$58e|A)6$(!W6C|`*qGU#$Ffl z3qsF&))(>ceuE>|z~|xlBTfU(U<%@_kju|9i%3BsMt;m?grG(S)5!XtBGQ8~Y<`p5 z48?B>SxeY~t8i*!C;x=?Mi+Xq<{Wzf*%*ZGE%wPW!tdwo1tg&geV9aGCsU2QP9aYbR-q4*xbX`**~PTK%OJY> z`bOk?bPV&@cY#@h?tPXOQwZvzW9Wj}@JkA#0?p9%vb#}%8HD_bP483gPrIKfkaaCsB_}xPmpqGy)UKFofXWah_3u0Zji+NOgptVu(T#vVPC5#|(l; z*=IO}GBb~}7{WZfKjCO08>OG{HA~oyD=>^PaulEuEin9n-3^a%I*TEU!u3;jI+9U< zSXa~PGAgc|ID`>IxvfnYb*gyLVQj=NXF#ntiRuNju&b%h#3_Bg=vR6%W}Yk zGR$MsU#Sf3xPIA`YJaf2@)q~8#- zcb=)o0vi570pu+Zq7|-Ra-$&@va3Jo~D{?DgH1bf0ehk6epa?%i;RH%C z0T)NbEIb_*5sF|;jRcD96~nA5xz(fkw``%O3;944`u{q z@LtRaaR9lC63^TZH=Hcl@#_fvOiX>dc9F}|2dko_S)TPW6 z%HZLnh*lg}#w;yYL=$59FJT8zfMQ%nt}nBIK@1~cCCh>^KSl2IWBs#u_^wjK8bnH# z0Vh$9pw&!225@<`BCipCfjj-_EIM!jsb5n>7Ru0q4h+L^2eXKLR3UFIn{yqriY)=u z9l-i$5ph}12XW?3MO32)rn{I4%poL@hLChOOM?sOgL@F`idYn&813lB0?yx~h)&30 zMQp|#JT_1U-5V4+vVrxVAmI|iG9U?u&<>A#87a2jtH@fyvlzuBL@4!ZVSEx3BWhkpdqk0P8zCwg!N zhDTU?Y(xz9qX7JeUQvsFEMVhSW^k(__Y)@IESeFujnAQEo0&Bt(u01CV;;Il?g4}% z3{gl%I=Z2Il=Vjy#xMoHC^~|KC`INI7NHN9EP2~GC8$9IMlgXnGY`)loOJ|Y2lgNy z`6$9=EZ@mGq8YQ8M@Tdsf+<>&WrP*zK|lN-BOhg`z!hxW#eIMRT*8{iS^qd5NoYkE zuHy#0V`vZ!IEMjv?`D(;LoO~Mcn`~iQ)tF8eD_j0_8<=B=t3`iW7%iu#>H6He};(v zH@GN}k7{(_0w&e`*Avj0`#CC*C3x`A7R|g!=r#TL?OlyPN&Ln4e^Ea5@Xo< zZF-GfgcdP+oP+y!SX&Gs>jWLe40O*c((idXi5MIrtt)0@qYsy0Ui$)Tf>act5iM{j zVH60#CB%G}Mo@+kOnjH0XoR|x+yU5uMzlcpBGZgJk&hxY!1H@-X!v6bBFb3*9cA25 z5U+7k%sk34gi*NHF=}+7^mV2gyXx75XvHYz5cdXOJ~$7L2CiD9V;ZqP zqGOnV?^$LJ=P`)|)clwkZlny%B|Oe!0KRW>$)Fn}Zz^(@a2{)$STB@f5`I5X#HODp zat~n~icyQJh&;!7qxzg8TM4@`4fQQL+)P_=eVaOQ>21Xr_t}i7Koicx zy@v}79k_sb=zht5F!R{)OMd2aEz<{pkA_FC3imW4SH1n8&?hpKU zL=)6;&N~`$0e!fFS*V|~nXneY2*(a2;rOSV2*Mh4<0AG?FmjyyBXywUC@b!?qt{ZWDjoP+uqBR~{l(2US&))wig#Ed2VUkm^ZIET;~@=%TX8AWyx z_7dvEF70WZ`mBCxy(-pf4?C)NXdZf1r@97e-g-4qpK)3hE3Ekj%b!er_Xk=w+3JQj zv|2)kO+Qk#k80JW6?)af@h;&aHvCxCcKfL741pv9-n5hoa10`T&rekCy=Ce;t*~kX(Lc z%XEH_Oy$=%d*K*Pa-mPvHmqQD50Ut?-|~R~Ey0=jx-no)I7aZ|uY_*+u zUs@B69=zDAIB2iw)pd@GNW?kV6aFL;^$yy@?&@6*sgCyCHRNVG+Y@(?$i2;;SV!VH zH(NrJi&*j#i|nt~^XkRL_Cz#^Q%h_K!(${~zFlix#1hmlb= zrW2Wa*IP0J3}5FkMc$<~Qb0W$q74)3`&xD4HtSh){02pT+pe=sP%Mej4&6Twnqi;i zZKqc5$%jX~v@T0H^S*Y4P`|2ACtlH>^y1va(MHDyTGSGdyH^tLK^w#kjo~A@js21Zx zz0MRkv{7rJfV$%WZQN4u*n_r$j)y5&_>eu3LE?pn?TIWBWsm5!#g5!K-An24OWU;I z72HMHygBu#z1R^FFK@Rc44EWqcUqbaRJF&n?q!rVkyr7!t$-H1jKzO-x8Azm23KBH z?zI*(;s^b1ARi$fUrN3!?G&7`56ZOD4!qpkMO93}C`c3Z+Zm&8nmUM#V#NCU}#blzM<$2Um2ziTV{LY%(}2k6@3 z{PphzXpg7qd)of`xIWL=7W1*X*kR>j{x=OjgslH8VJNXBsx7@l+pt~V=zjChZf$=u zsy+N`b+xuATJP+1^RGzTUVB4zRkXMqeUAr!3~B%4(OZiDZ@F#He5yLD+TnNfJ}W*K zw<|iuTlt$mU4IP6kr& delta 11054 zcmb8#e^^v?zQFPC8JOXRh=_!Qh(|(1no2ED=k@dRdlbiSB!tv3%}x|G2m3d7bZ%bI$kI z=leaw(_>rt+Tr}QYgNMZ->*fDD$IJd=2EJ8cI{(+M*jHc#^CYWFqLHOjNwBoB?bUb*3V>4f4FEcqPG_@|xni(qMkZJ@uuR=(EXzpW%# z>bH|Tl)d4SyYhIrbe;5!aw1$>qcKlTNxwx3Ry4Osk*6Sn#LC&<1Dtm#gw6Sl}Kqp8+ZX<7<% zt)HyOcS<)%vXXG8bi2n&acL#jFHe>L|J41$?UJ|Rw^y>W`>8G?wpEWjKyW1aDwQ zu$IXq3<)SiEqX8shm8yjkw`=VYSDukxOxc@f&>(x#!DE)88R2)b`>`fk33YM29pT) z7NQiR2-+k>8uX|{E#?sA!}4Md5t~^R)T0MeaP;L}#GwGS=Fz`DO_VVR#~s8G4VXpQ&FplHU>d=p><{GOFpi-cXD|fY zTWBA?D7=OE7n7-iTNtB;AF5D?fLm!0@kmAooWj{$XodA{Y&yiE6y@l|X>8xg{y^C6 zOay%>j$r5CLHqEEq(0I>Z0}?iSVUcv5KS1t0=(`b&Pc>Gl6Nu4pA&0bM6ixy3Aei$ zEYffor!WM~ZdPo!kbMzGJ`$OzKr3bpT;`E;kC5B%;YGMd(=jShi}XFj3MYRlL>CUm zF@Shd?7o-TqXb6uU<~02Y&JBY2ex}@6WPeyE97!ClQ*<;8Hd;>L?A+N3Pad+9~l8Y};S?cPk=EfnW^iP`kfT#g=RcJP z9$<4JCXL46_8_xHHk!~2Z91RdbRj2@rl1TB2tB}(ViGkOtR}J_;vNDs892(&i!p>} zG0-d_CzGaU8Q3&rnyHA;^T|XBjEH!c4Tp$q8id~?%m$|soxIiHZh2$w_Tu?I#p!Lxu5Re_L0Nh6R0Lm3qXPq0kT6tY{9j275F$!KvF_JdlUE#%{9 zA>V;XSQk@Q%*KM-@0cZaU=QMvi?cY7c_fsuKTw2OEW-U6P74YVQcC#Hh}Ke;ZHdgm zXSsW|3HdN-70zKAyIv>Ws6hkl z8`%ShK`SO<{RV%#yun68BJvQ>L|D;@({MOJ=ZHo=j-N2F3}mdEIUdbI-cGt31*m}a zTg)EKScG*evw(XW$FEJuyGT=DL=zHEa&oYUxVH%%sxW~Wc(rr%un%QuH*h%z>vz}; zh(JD?&4Y5fl4}_!p6B$GMM$JWXnQme?W&nSUX^=!9b*e|R7c#i;EQau?|!>^~O5 z8>jkN8Mu7H215dlpc-!H2rbgivA;;GaP}PWpCD6to(3_EIqdk1QKJYa(GU9pIzcS- zXou_PoM#+F3(kHn7CO|!!ar*Bp^Au+riQf`KRHGiMX_gbI=!V00{K}yUgK(Q61jxWR z{Qto~(F^N~#6SEZouL(dNc%6A9TV6(%PgVCC_Mj3W2nSQgwJs#(G7=x(J{`#d7coW z3Dc1O%_sQZ{0&GNiCi4RoPo;{b}tCIVUeZB7>d8=0mLtnq5>BY@B^!ZY%C#KNFok} zXvH+VBuRuK7DrHuaV)?^CCPp&NdzGQh7>Lxn8ZZ{SV zTeT#d)RMfFGyw5PMl;&62y2Zb{E&)F^k4?_8p$BsZ6pzhY;>W=Mv_NKC*fzyNRfxb z7)F2{vqB#n?IpRuUJ^wZg3Ag?cpx2(XvHF29V8KhOjM!HL6T3A_Bt3C1sSnYlIK=R zVhK7&9!562S4l#PNF*a2#Rza>MWDwbtXH!-h+8em*`%fD!XOfyC6R(fSg#>G=)(Z! z4W^1KB(W6{IENt!S4NLcj9>!pYv~YksBojhD_JF!xigq`l#z%u3?R;fRX{5`;kcf# zz{^vTLp&w12L{y!N!UXx69#0U2re60ezc%tqa>dv9fp$^gGCkUuR9 z{g^otk%mgtVhqQxkwi5#{*sU}ggI1R%QM$Xau;dOwZwmh%se6j=m5ts3&-mu;d`AV z7m^mE3q2TvZy+5)4~>_ph)T?09@=2SiD*=y2F)17Bo?5(k@yF4*?~0ZaT3}P zUO+>LB%dZdiv_4|lEglwq6Q5Z#yC_v*cS-E9@L=;4mUHg*dEG9DwOz_ zlc_>K2BErzS-~4oNCyAkR2)MiS}_WjFzzEdOp?<{v(bTW#NNt4QI0B{!w_cR5YD^s zMR>S@M##iKkApaN8%u_M*zc5t^G-?Dk_KWQQqcwH+gT|Dq6njKiD2-^z$wf_+`%9b zhfcngv*^Yf?g~@6(tE75h%tnoI)?eU2HA{p%@OkB;k+kh(#Gj zF$ssC6D9;;HwZ zuYGigd^BMU&iBz7^k~31d=i-yTF`;$B;udXr4i>a53gj-FOHxSQ*gbXj}Q){3nGOM zQ34}$``HJn99bMhAx0h`oG49W(>}-`;FQj!a16Qw9N7cJ|1=rr3}%W-)WSED9SkEX zF^cglmR`@&<2V+P_b`LPGy=1kF%nRU8R#BiRd60N*pj$;xR;rb-Mo`>1dC_@vPpW;1eiZ~TW zgb|hKgU{2%-;YZ?YSD->Xp31+q$2NkEHNhFSVCxU7SYdeG_i!pQW{4;Zs-Y>P z4Bs+I4kL|3CW_&5ls$8l_-B&I#Ys$K4(Z3(92mwpqJB?@n8qA@jqDGEC>*(RmKP&v z{sS|H!*R|xa+B0FNxFespKIfqZjrsu%nTWQy4-_6~`09 z*!?0iK`p$i86eW(_!3KuVa&t3hA$wBF$@2fS&5e=IgvEYz{Q9rEF!v=V}-g}{z4_~ z#T=YoAxvn+ML5(+vQHf|K@2isUr%Q^2qVH?ljL@6Z{VjE*{@4-X`>`Jz9Gq8CnUL| zS&|L%n=B{lk=nwDG4>Yc{w+z?v`VtvN<6U-2eANM8#6^VIuUY`B}OA!F$@2 zCozCIxb`puM56&MJ(7HubOQDtFfi1@sh1^3DHf6aA>WcSd@o=q;xYlhkLU~~a63y1 zBOLz1@k9X{(F(sl_5)IJ5?u)Vm=Gcp#VAMf$C7-8)Tf_WV*n%A^$8(D$tT3$NGALo zUnHb|%IH32my*_F3`@|RrvVh>6o%pQ8Ji3`#C#^nnWVWG#yn-`0cMF<=+Og@&lw<2 z!+nswGD!S`$;2TMnaG0?RcJ&z`Y|=giTEq$8~)gi9s~|?DiH7mZ6O&a(T_PSp=Fp= z`;ryLKD1y6^S|QaI>HYPGLegu=z@HKA0jkh2)3il1`Rk3*ROaFHE4jt7^^bIpW38B zV~TNwDqkJzr4l~MLyoFVN~nX%T9xmu>~v6R)uT_V#Jc5rjp<(neE&P8nrhX{&ntbT zRt+zx6sf~{t>W*XaZ-I)RB&>-Y})Unm|*FCze~c*+#zp zO-r{y6kdEwX? z)cpD*MP5yRy1!i3s?ARB-2L%o&)VHiP2?w+)=#sM;vJtZPu}QEx4O@k^BS4@Uw&@R z-$6e9ujc#)@=0GPrOqs1+Lz|K_0(luFy}qU=YD0*uOolxZ_9ZlV-1ZT`PzKrN^U&! zjX8fOdE?*Bc{lRUe`|E1?V9h*RW4M$_K)SfdJXwz<>NJ~EgEOCZ~t?-S{ZSn>8^j7 zpLgQM`~NoQSCK#apXI!b7x_=WS1Au&p}JZ%^n(_rHh5NmUpbV^WBwJ%86Appr$an&U{Csq44E;(>q$L z{tcE&TPok*xZIHD2F_LcRmva_s64J#Y_H_l?RdjltS}#iU-0N1C#;v}YZFaD_onqv zXHK)nbo(u(i?>v|Hf7qB#&;+l?zD3Bl(l$Hd1jqzlO~o<{JNDKQ}x&H|5$DMUxEc6 zDCekFd3~h#uj6RNQ&)A?l-H^}`;>T7de^5)F{w1AT(gcy?&Fcvf#t^)Cl4Yy@wwva z!E+xDo1XL4B$*xDTJ@K+x zYYtHpk*vtRENmh9+ftMSFXkDVs+5@0+tSSUe#^bEbaTF#{LL9k=@w2v=v7QZm$_W9 zDdomqy=92cQb@@*7c@sH9LQ1nuA<4DTvO_6r%-q3;H8zbE2mhRr@pl13n)I9Z<$4t zH?ycNu(W)bTdx*c3MCYp4qLi;nnK$VC3F+pv{PBViBE!osy`Q79xkJBro>VxqVP$n zxuD2C^e|Xvxpkad7mis9zo#%^G#4~R3g4A0O+NH6ryTO3hbO4|zQXeGS_;-LmRy^wEQBK zNw1qXlT9^+gf}eRyhP#t6PI?6aSJm^e$#yKsqrl;Si!n&e2e-$Uqw2k?q0<|+e=*j zr;C<@x=IwwiF;ytz7x;fz}32BwIAe^MtQkwWd zJSd(N{}LI(sx9I@_02Xu|D$E0KX9-9K*qg4{yRbQzm;rzqDQ^nO8L)+>X82i1#zsE