From 4dffb5b755ca13610d69f23ad366fff51dce1860 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 22:57:04 +0100 Subject: [PATCH 01/20] SplitWavesToDimension: Add it --- Packages/MIES/MIES_Utilities.ipf | 32 +++++++++++++++++ Packages/tests/Basic/UTF_Utils.ipf | 58 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/Packages/MIES/MIES_Utilities.ipf b/Packages/MIES/MIES_Utilities.ipf index 538a4ef070..5fc6646ba8 100644 --- a/Packages/MIES/MIES_Utilities.ipf +++ b/Packages/MIES/MIES_Utilities.ipf @@ -6838,3 +6838,35 @@ Function/WAVE ZapNullRefs(WAVE/WAVE input) return result End + +/// @brief Split multidimensional waves inside input to the given dimension +/// +/// @param input wave reference wave +/// @param sdim [optional, defaults to 1] dimensionality to split to +Function/WAVE SplitWavesToDimension(WAVE/WAVE input, [variable sdim]) + + ASSERT_TS(IsWaveRefWave(input), "Expected a wave reference wave") + + if(ParamIsDefault(sdim)) + sdim = 1 + else + ASSERT_TS(IsInteger(sdim) && sdim >= 1 && sdim <= MAX_DIMENSION_COUNT, "Invalid sdim parameter") + endif + + Make/FREE/WAVE/N=(0) output, singleWaves + + for(WAVE/Z wv : input) + ASSERT_TS(WaveExists(wv), "Invalid contained wv") + + if(DimSize(wv, COLS) > 1) + /// @todo workaround IP issue 4979 (singleWaves is not a free wave) + SplitWave/NOTE/O/FREE/OREF=singleWaves/SDIM=(sdim) wv + else + Make/WAVE/FREE singleWaves = {wv} + endif + + Concatenate/NP {singleWaves}, output + endfor + + return output +End diff --git a/Packages/tests/Basic/UTF_Utils.ipf b/Packages/tests/Basic/UTF_Utils.ipf index abb0d8f0bb..c88ca705b8 100644 --- a/Packages/tests/Basic/UTF_Utils.ipf +++ b/Packages/tests/Basic/UTF_Utils.ipf @@ -7535,3 +7535,61 @@ static Function TestGetRowIndex() CHECK_EQUAL_VAR(GetRowIndex(waveRefWave, refWave = $""), 0) CHECK_EQUAL_VAR(GetRowIndex(waveRefWave, refWave = waveRefWave), NaN) End + +static Function TestSplitWavesToDimension() + + // bails on invalid wave + try + Make/FREE wv + SplitWavesToDimension(wv) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // bails on invalid sdim parameter + try + Make/FREE wvData = {{1, 2}, {3, 4}} + Make/FREE/WAVE wvRef = {wvData} + SplitWavesToDimension(wvRef, sdim = MAX_DIMENSION_COUNT + 1) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // bails on invalid contained wv + try + Make/FREE/WAVE wvRef + SplitWavesToDimension(wvRef) + FAIL() + catch + CHECK_NO_RTE() + endtry + + Make/FREE wvData1 = {{1, 2}, {3, 4}} + Make/FREE wvData2 = {5, 6} + Make/FREE/WAVE wvRef = {wvData1, wvData2} + + WAVE/WAVE/Z result = SplitWavesToDimension(wvRef) + CHECK_WAVE(result, WAVE_WAVE) + CHECK_EQUAL_VAR(DimSize(result, ROWS), 3) + CHECK_EQUAL_VAR(DimSize(result, COLS), 0) + CHECK_EQUAL_WAVES(result[0], {1, 2}, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(result[1], {3, 4}, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(result[2], {5, 6}, mode = WAVE_DATA) + + Make/FREE wvData1 = {{1, 2}, {3, 4}} + Make/FREE/T wvDataTxt1 = {{"a", "b"}, {"c", "d"}} + Make/FREE/WAVE wvRef = {wvData1, wvDataTxt1} + + WAVE/WAVE/Z result = SplitWavesToDimension(wvRef) + CHECK_WAVE(result, WAVE_WAVE) + CHECK_EQUAL_VAR(DimSize(result, ROWS), 4) + CHECK_EQUAL_VAR(DimSize(result, COLS), 0) + CHECK_EQUAL_WAVES(result[0], {1, 2}, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(result[1], {3, 4}, mode = WAVE_DATA) + CHECK_EQUAL_TEXTWAVES(result[2], {"a", "b"}, mode = WAVE_DATA) + CHECK_EQUAL_TEXTWAVES(result[3], {"c", "d"}, mode = WAVE_DATA) + + CHECK_EMPTY_FOLDER() +End From bc8d2a6ecc37b4b6efb422315182481462b3755c Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:03:31 +0100 Subject: [PATCH 02/20] IsDoubleFloatingPointWave/IsSingleFloatingPointWave: Add them --- Packages/MIES/MIES_Utilities.ipf | 18 ++++++++++++++++++ Packages/MIES/MIES_WaveBuilder.ipf | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_Utilities.ipf b/Packages/MIES/MIES_Utilities.ipf index 5fc6646ba8..8fa07d632f 100644 --- a/Packages/MIES/MIES_Utilities.ipf +++ b/Packages/MIES/MIES_Utilities.ipf @@ -3664,6 +3664,24 @@ threadsafe Function IsFloatingPointWave(wv) return (type & IGOR_TYPE_32BIT_FLOAT) || (type & IGOR_TYPE_64BIT_FLOAT) End +/// @brief Return 1 if the wave is a double (64bit) precision floating point wave +/// +/// UTF_NOINSTRUMENTATION +threadsafe Function IsDoubleFloatingPointWave(wv) + WAVE wv + + return WaveType(wv) & IGOR_TYPE_64BIT_FLOAT +End + +/// @brief Return 1 if the wave is a single (32bit) precision floating point wave +/// +/// UTF_NOINSTRUMENTATION +threadsafe Function IsSingleFloatingPointWave(wv) + WAVE wv + + return WaveType(wv) & IGOR_TYPE_32BIT_FLOAT +End + /// @brief Return 1 if the wave is a global wave (not a null wave and not a free wave) threadsafe Function IsGlobalWave(wv) WAVE wv diff --git a/Packages/MIES/MIES_WaveBuilder.ipf b/Packages/MIES/MIES_WaveBuilder.ipf index d68128092f..42682801b3 100644 --- a/Packages/MIES/MIES_WaveBuilder.ipf +++ b/Packages/MIES/MIES_WaveBuilder.ipf @@ -1402,7 +1402,7 @@ End static Function WB_TrigCalculateInflectionPoints(struct SegmentParameters &pa, variable k0, variable k1, variable k2, variable k3, WAVE inflectionPoints) variable i, idx, xzero, offset, lowerBound, upperBound - ASSERT(WaveType(inflectionPoints) == IGOR_TYPE_64BIT_FLOAT, "Expected double wave") + ASSERT(IsDoubleFloatingPointWave(inflectionPoints), "Expected a double wave") if(pa.amplitude == 0) print "Can't calculate inflection points with amplitude zero" From 81a6be8137633c68383315251cd5ffcbcc64c6a9 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 22:57:24 +0100 Subject: [PATCH 03/20] Tests/Test_WaveRefNumeric(): Enhance them Make the wave sizes and contents assymetric so that wrong row/column major order issues would be found. --- Packages/tests/Basic/UTF_JSONWaveNotes.ipf | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Packages/tests/Basic/UTF_JSONWaveNotes.ipf b/Packages/tests/Basic/UTF_JSONWaveNotes.ipf index 45de4ebc0c..021b841642 100644 --- a/Packages/tests/Basic/UTF_JSONWaveNotes.ipf +++ b/Packages/tests/Basic/UTF_JSONWaveNotes.ipf @@ -187,30 +187,30 @@ static Function Test_WaveRefNumeric() Make/FREE wvData1 = {1, 2, 3, 4} Make/FREE/N=0 wvData2 - Make/N=(1, 2)/FREE wvData3 = p + q + Make/N=(2, 3)/FREE wvData3 = p * 2 + q Make/FREE/WAVE wvRef = {wvData0, wvData1, wvData2, wvData3} JWN_SetWaveInWaveNote(wv, "refWave", wvRef) WAVE/Z data = JWN_GetNumericWaveFromWaveNote(wv, "refWave/0") - CHECK_EQUAL_WAVES(data, wvData0, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(data, wvData0, mode = WAVE_DATA | DIMENSION_SIZES) WAVE/Z data = JWN_GetNumericWaveFromWaveNote(wv, "refWave/1") - CHECK_EQUAL_WAVES(data, wvData1, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(data, wvData1, mode = WAVE_DATA | DIMENSION_SIZES) WAVE/Z data = JWN_GetNumericWaveFromWaveNote(wv, "refWave/2") - CHECK_EQUAL_WAVES(data, wvData2, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(data, wvData2, mode = WAVE_DATA | DIMENSION_SIZES) WAVE/Z data = JWN_GetNumericWaveFromWaveNote(wv, "refWave/3") - CHECK_EQUAL_WAVES(data, wvData3, mode = WAVE_DATA) + CHECK_EQUAL_WAVES(data, wvData3, mode = WAVE_DATA | DIMENSION_SIZES) WAVE/WAVE/Z container = JWN_GetWaveRefNumericFromWaveNote(wv, "refWave") CHECK_EQUAL_VAR(DimSize(container, ROWS), 4) CHECK_EQUAL_VAR(DimSize(container, COLS), 0) - CHECK_EQUAL_WAVES(wvRef[0], container[0], mode = WAVE_DATA) - CHECK_EQUAL_WAVES(wvRef[1], container[1], mode = WAVE_DATA) - CHECK_EQUAL_WAVES(wvRef[2], container[2], mode = WAVE_DATA) - CHECK_EQUAL_WAVES(wvRef[3], container[3], mode = WAVE_DATA) + CHECK_EQUAL_WAVES(wvRef[0], container[0], mode = WAVE_DATA | DIMENSION_SIZES) + CHECK_EQUAL_WAVES(wvRef[1], container[1], mode = WAVE_DATA | DIMENSION_SIZES) + CHECK_EQUAL_WAVES(wvRef[2], container[2], mode = WAVE_DATA | DIMENSION_SIZES) + CHECK_EQUAL_WAVES(wvRef[3], container[3], mode = WAVE_DATA | DIMENSION_SIZES) // empty wave ref wave Note/K wv From 422eac807d7605d70e278280506b7246360226e2 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Sat, 20 Jan 2024 04:23:53 +0100 Subject: [PATCH 04/20] SF: Adapt SFH_EvaluateRange for multi dataset input - SFH_EvaluateRange did not accept multi dataset input, which prevented formulas like "epochs(ST) + [1, -2]" to work. This functionality is added. - Changed SFH_GetSweepsForFormula to accept dataset input only. - Adapted calling sites. - added tests as part of tests for data operation - also added test for (n, 2) numerical ranges --- Packages/MIES/MIES_SweepFormula.ipf | 5 +- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 97 ++++++++++++++------- Packages/tests/Basic/UTF_SweepFormula.ipf | 39 ++++++++- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 7f20e74110..c86f327e0b 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -2767,7 +2767,8 @@ static Function/WAVE SF_OperationTPImpl(string graph, WAVE/WAVE mode, WAVE/Z sel singleSelect[0][%CHANNELTYPE] = chanType singleSelect[0][%CHANNELNUMBER] = chanNr - WAVE/WAVE sweepDataRef = SFH_GetSweepsForFormula(graph, {-Inf, Inf}, singleSelect, SF_OP_TP) + WAVE/WAVE range = SFH_AsDataSet(SFH_GetFullRange()) + WAVE/WAVE sweepDataRef = SFH_GetSweepsForFormula(graph, range, singleSelect, SF_OP_TP) SFH_ASSERT(DimSize(sweepDataRef, ROWS) == 1, "Could not retrieve sweep data for " + num2istr(sweepNo)) WAVE/Z sweepData = sweepDataRef[0] SFH_ASSERT(WaveExists(sweepData), "No sweep data for " + num2istr(sweepNo) + " found.") @@ -4343,7 +4344,7 @@ static Function/WAVE SF_OperationData(variable jsonId, string jsonPath, string g SFH_ASSERT(numArgs >= 1, "data function requires at least 1 argument.") SFH_ASSERT(numArgs <= 2, "data function has maximal 2 arguments.") - WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_DATA, 0) + WAVE/WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_DATA, 0) WAVE/Z selectData = SFH_GetArgumentSelect(jsonID, jsonPath, graph, SF_OP_DATA, 1) WAVE/WAVE output = SFH_GetSweepsForFormula(graph, range, selectData, SF_OP_DATA) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index 69b8216ddf..7f0d11be37 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -318,9 +318,35 @@ Function SFH_IsFullRange(WAVE range) return EqualWaves(rangeRef, range, EQWAVES_DATA) End +Function/WAVE SFH_AsDataSet(WAVE data) + + Make/FREE/WAVE/N=1 output + output[0] = data + + return output +End + +/// @brief Formula "cursors(A,B)" can return NaNs if no cursor(s) are set. +static Function SFH_ExtendIncompleteRanges(WAVE ranges) + + for(WAVE wv : ranges) + if(IsNumericWave(wv)) + SFH_ASSERT(DimSize(wv, ROWS) == 2, "Numerical range must have two rows in the form [start, end].") + wv[0][] = IsNaN(wv[0][q]) ? -Inf : wv[0][q] + wv[1][] = IsNaN(wv[1][q]) ? Inf : wv[1][q] + endif + endfor +End + /// @brief Evaluate range parameter /// -/// Range can be `[100-200]` or implicit as `cursors(A, B)` or a named epoch `E0` or a wildcard expression with epochs `E*` +/// Range is read as dataset(s), it can be per dataset: +/// numerical 1d: `[start,end]` +/// numerical 2d with multiple ranges: `[[start1,start2,start3],[end1,end2,end3]]` +/// implicit: `cursors(A, B)` or `[cursors(A, B), cursors(C, D)]` +/// implicit: `epochs([E0, TP])` +/// implicit with offset calculcation: `epochs(E0) + [1, -1]` +/// named epoch: `E0` or a as wildcard expression `E*` Function/WAVE SFH_EvaluateRange(variable jsonId, string jsonPath, string graph, string opShort, variable argNum) variable numArgs @@ -328,23 +354,13 @@ Function/WAVE SFH_EvaluateRange(variable jsonId, string jsonPath, string graph, numArgs = SFH_GetNumberOfArguments(jsonId, jsonPath) if(argNum < numArgs) - WAVE range = SFH_ResolveDatasetElementFromJSON(jsonID, jsonPath, graph, opShort, argNum, checkExist=1) - else - return SFH_GetFullRange() - endif - - SFH_ASSERT(DimSize(range, COLS) == 0, "Range must be a 1d wave.") + WAVE/WAVE ranges = SF_ResolveDatasetFromJSON(jsonId, jsonPath, graph, argNum) + SFH_ExtendIncompleteRanges(ranges) - if(IsTextWave(range)) - SFH_ASSERT(DimSize(range, ROWS) > 0, "Epoch range can not be empty.") - else - SFH_ASSERT(DimSize(range, ROWS) == 2, "A numerical range is of the form [rangeStart, rangeEnd].") - // convert an empty range to a full range - // an empty range can happen with cursors() as input when there are no cursors - range[] = !IsNaN(range[p]) ? range[p] : (p == 0 ? -1 : 1) * inf + return ranges endif - return range + return SFH_AsDataSet(SFH_GetFullRange()) End /// @brief Returns a range from a epochName @@ -393,35 +409,31 @@ Function/WAVE SFH_GetRangeFromEpoch(string graph, string epochName, variable swe return range End -/// @brief Return a wave reference wave with the requested sweep data +/// @brief Return a wave reference wave with the requested sweep data. The argument range can contain multiple datasets, +/// if it is a single dataset the range(s) are extracted from each selection, +/// if there are multiple datasets then the number of datasets must equal the number of selections, +/// for that case range datasets and selections are indexed the same. +/// This is usually only senseful if the same select arguments are used for e.g. data to retrieve sweeps and epochs to retrieve ranges. /// /// All wave input parameters are treated as const and are thus *not* modified. /// /// @param graph name of databrowser graph -/// @param range numeric/text wave defining the x-range of the extracted -/// data, see also SFH_EvaluateRange() +/// @param range wave ref wave with range specification defining the x-range of the extracted +/// data, see also SFH_EvaluateRange(), range specification per dataset can be numerical or text /// @param selectData channel/sweep selection, see also SFH_GetArgumentSelect() /// @param opShort operation name (short) -Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE range, WAVE/Z selectData, string opShort) +Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z selectData, string opShort) - variable i, j, rangeStart, rangeEnd, sweepNo + variable i, j, rangeStart, rangeEnd, sweepNo, isSingleRange variable chanNr, chanType, cIndex, isSweepBrowser - variable numSelected, index, numEpochPatterns, numRanges, numEpochs, epIndex, lastx + variable numSelected, index, numRanges, numEpochs, epIndex, lastx string dimLabel, device, dataFolder, epochTag, epochShortName string allEpochsRegex = "^.*$" ASSERT(WindowExists(graph), "graph window does not exist") - SFH_ASSERT(DimSize(range, COLS) == 0, "Range must be a 1d wave.") - if(IsTextWave(range)) - SFH_ASSERT(DimSize(range, ROWS) > 0, "Epoch range can not be empty.") - WAVE/T epochNames = range - numEpochPatterns = DimSize(epochNames, ROWS) - else - SFH_ASSERT(DimSize(range, ROWS) == 2, "A numerical range must have two rows for range start and end.") - numEpochPatterns = 1 - endif - if(!WaveExists(selectData)) + isSingleRange = DimSize(range, ROWS) == 1 + if(!WaveExists(selectData) || DimSize(range, ROWS) == 0) WAVE/WAVE output = SFH_CreateSFRefWave(graph, opShort, 0) JWN_SetStringInWaveNote(output, SF_META_DATATYPE, SF_DATATYPE_SWEEP) return output @@ -429,6 +441,10 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE range, WAVE/Z selectDat SFH_ASSERT(DimSize(selectData, COLS) == 3, "Select data must have 3 columns.") numSelected = DimSize(selectData, ROWS) + if(!isSingleRange) + SFH_ASSERT(DimSize(range, ROWS) == numSelected, "Number of ranges is not equal number of selection.") + endif + WAVE/WAVE output = SFH_CreateSFRefWave(graph, opShort, numSelected) isSweepBrowser = BSP_IsSweepBrowser(graph) @@ -444,6 +460,19 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE range, WAVE/Z selectDat for(i = 0; i < numSelected; i += 1) + WAVE/Z setRange = range[isSingleRange ? 0 : i] + + if(!WaveExists(setRange)) + continue + endif + + if(IsTextWave(setRange)) + WAVE/T epochNames = setRange + SFH_ASSERT(!DimSize(epochNames, COLS), "Expected 1d text wave for epoch specification") + else + WAVE/Z/T epochNames = $"" + endif + sweepNo = selectData[i][%SWEEP] chanNr = selectData[i][%CHANNELNUMBER] chanType = selectData[i][%CHANNELTYPE] @@ -504,8 +533,10 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE range, WAVE/Z selectDat endif endfor else - Duplicate/FREE range, adaptedRange - Redimension/N=(-1, 1) adaptedRange + Duplicate/FREE setRange, adaptedRange + if(!DimSize(adaptedRange, COLS)) + Redimension/N=(-1, 1) adaptedRange + endif endif SFH_ASSERT(!SFH_IsEmptyRange(adaptedRange), "Specified range not valid.") diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index a58403a194..9ec31bc268 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -2488,7 +2488,7 @@ End static Function TestOperationData() variable i, j, numChannels, sweepNo, sweepCnt, numResultsRef, clampMode - string str, epochStr, name, trace + string str, strSelect, epochStr, name, trace string win, device variable mode = DATA_ACQUISITION_MODE variable numSweeps = 2 @@ -2591,6 +2591,43 @@ static Function TestOperationData() CheckSweepsFromData(dataWref, sweepRef, numResultsref, {3, 1, 3, 1}, ranges=ranges) CheckSweepsMetaData(dataWref, {0, 0, 0, 0}, {6, 6, 7, 7}, {0, 0, 0, 0}, SF_DATATYPE_SWEEP) + // this part specifies to numerical range 0,2 and 0,4 + // Selected is sweep 0, AD, channel 6 and sweep 0, AD, channel 7 + sweepCnt = 1 + strSelect = "select(channels(AD),[" + num2istr(sweepNo) + "],all)" + str = "data([[0,0],[2,4]]," + strSelect + ")" + WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) + numResultsRef = sweepCnt * numChannels / 2 * 2 // 2 ranges specified + + Make/FREE/N=(numResultsRef, 2) ranges + ranges[0][0] = 0 + ranges[0][1] = 2 + ranges[1][0] = 0 + ranges[1][1] = 4 + ranges[2][0] = 0 + ranges[2][1] = 2 + ranges[3][0] = 0 + ranges[3][1] = 4 + CheckSweepsFromData(dataWref, sweepRef, numResultsref, {1, 3, 1, 3}, ranges=ranges) + CheckSweepsMetaData(dataWref, {0, 0, 0, 0}, {6, 6, 7, 7}, {0, 0, 0, 0}, SF_DATATYPE_SWEEP) + + // This part uses a epochs operation with offset to retrieve ranges + // Selected is sweep 0, AD, channel 6 and sweep 0, AD, channel 7 + // The epoch "TestEpoch" is retrieved for both and offsetted by zero. + sweepCnt = 1 + strSelect = "select(channels(AD),[" + num2istr(sweepNo) + "],all)" + str = "data(epochs(\"TestEpoch\"," + strSelect + ")+[0,0]," + strSelect + ")" + WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) + numResultsRef = sweepCnt * numChannels / 2 + + Make/FREE/N=(numResultsRef, 2) ranges + ranges[0][0] = rangeStart0 + ranges[0][1] = rangeEnd0 + ranges[1][0] = rangeStart0 + ranges[1][1] = rangeEnd0 + CheckSweepsFromData(dataWref, sweepRef, numResultsref, {1, 3}, ranges=ranges) + CheckSweepsMetaData(dataWref, {0, 0}, {6, 7}, {0, 0}, SF_DATATYPE_SWEEP) + sweepCnt = 1 str = "data([\"TestEpoch\",\"TestEpoch1\"],select(channels(AD),[" + num2istr(sweepNo) + "],all))" WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) From 909df2e8f293a2466eec83beb9e1e7dd02a4a265 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:06:00 +0100 Subject: [PATCH 05/20] SF_GetTraceAnnotationText: Avoid duplicating code --- Packages/MIES/MIES_SweepFormula.ipf | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index c86f327e0b..5b3c3b106d 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1208,33 +1208,40 @@ static Function [WAVE/WAVE formulaResults, STRUCT SF_PlotMetaData plotMetaData] return [formulaResults, plotMetaData] End +static Function/S SF_GetAnnotationPrefix(string dataType) + + strswitch(dataType) + case SF_DATATYPE_SWEEP: + return "" + case SF_DATATYPE_TP: + return "TP " + default: + ASSERT(0, "Invalid dataType") + endswitch +End + static Function/S SF_GetTraceAnnotationText(STRUCT SF_PlotMetaData& plotMetaData, WAVE data) variable channelNumber, channelType, sweepNo, isAveraged string channelId, prefix - string traceAnnotation + string traceAnnotation, annotationPrefix prefix = RemoveEnding(ReplaceString(";", plotMetaData.opStack, " "), " ") strswitch(plotMetaData.dataType) + case SF_DATATYPE_SWEEP: // fallthrough case SF_DATATYPE_TP: sweepNo = JWN_GetNumberFromWaveNote(data, SF_META_SWEEPNO) + annotationPrefix = SF_GetAnnotationPrefix(plotMetaData.dataType) if(IsValidSweepNumber(sweepNo)) channelNumber = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELNUMBER) channelType = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELTYPE) channelId = StringFromList(channelType, XOP_CHANNEL_NAMES) + num2istr(channelNumber) - sprintf traceAnnotation, "TP Sweep %d %s", sweepNo, channelId + sprintf traceAnnotation, "%sSweep %d %s", annotationPrefix, sweepNo, channelId else - sprintf traceAnnotation, "TP" + sprintf traceAnnotation, "%s", annotationPrefix endif break - case SF_DATATYPE_SWEEP: - channelNumber = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELNUMBER) - channelType = JWN_GetNumberFromWaveNote(data, SF_META_CHANNELTYPE) - sweepNo = JWN_GetNumberFromWaveNote(data, SF_META_SWEEPNO) - channelId = StringFromList(channelType, XOP_CHANNEL_NAMES) + num2istr(channelNumber) - sprintf traceAnnotation, "Sweep %d %s", sweepNo, channelId - break default: if(WhichListItem(SF_OP_DATA, plotMetaData.opStack) == -1) sprintf traceAnnotation, "%s", prefix From 8f13c6fc609d7a0cffcca6086ee118e3b39dbda8 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:09:11 +0100 Subject: [PATCH 06/20] SFH_GetSweepsForFormula: Ensure that all input ranges are double precision And fix call sites which don't fullfill that requirement. --- Packages/MIES/MIES_SweepFormula.ipf | 6 +++--- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 5b3c3b106d..1a2e952653 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -3155,10 +3155,10 @@ Static Function/WAVE SF_OperationEpochsImpl(string graph, WAVE/T epochPatterns, Make/FREE/T wt = {epNames[epIndex]} WAVE out = wt elseif(epType == EPOCHS_TYPE_TREELEVEL) - Make/FREE wv = {str2num(epochInfo[epIndex][EPOCH_COL_TREELEVEL])} + Make/FREE/D wv = {str2num(epochInfo[epIndex][EPOCH_COL_TREELEVEL])} WAVE out = wv else - Make/FREE wv = {str2num(epochInfo[epIndex][EPOCH_COL_STARTTIME]) * ONE_TO_MILLI, str2num(epochInfo[epIndex][EPOCH_COL_ENDTIME]) * ONE_TO_MILLI} + Make/FREE/D wv = {str2num(epochInfo[epIndex][EPOCH_COL_STARTTIME]) * ONE_TO_MILLI, str2num(epochInfo[epIndex][EPOCH_COL_ENDTIME]) * ONE_TO_MILLI} WAVE out = wv endif @@ -4563,7 +4563,7 @@ static Function/WAVE SF_OperationCursors(variable jsonId, string jsonPath, strin wvT[i] = csrName[0] endfor endif - Make/FREE/N=(numArgs) out = NaN + Make/FREE/N=(numArgs)/D out = NaN for(i = 0; i < numArgs; i += 1) SFH_ASSERT(GrepString(wvT[i], "^(?i)[A-J]$"), "Invalid Cursor Name") if(IsEmpty(graph)) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index 7f0d11be37..e18f2b5c9c 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -470,6 +470,7 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele WAVE/T epochNames = setRange SFH_ASSERT(!DimSize(epochNames, COLS), "Expected 1d text wave for epoch specification") else + ASSERT(IsDoubleFloatingPointWave(setRange), "Expected a double wave") WAVE/Z/T epochNames = $"" endif From bb67c9eb36e9edf1c06dcfbcd96d7b601c2ac47d Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:10:16 +0100 Subject: [PATCH 07/20] SF_OperationEpochs: Add operations stack support This is nicer for the data as we then have sweep numbers in the annotation and correct trace colors. --- Packages/MIES/MIES_SweepFormula.ipf | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 1a2e952653..5fe90c58dc 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -1211,6 +1211,8 @@ End static Function/S SF_GetAnnotationPrefix(string dataType) strswitch(dataType) + case SF_DATATYPE_EPOCHS: + return "Epoch " case SF_DATATYPE_SWEEP: return "" case SF_DATATYPE_TP: @@ -1229,7 +1231,8 @@ static Function/S SF_GetTraceAnnotationText(STRUCT SF_PlotMetaData& plotMetaData prefix = RemoveEnding(ReplaceString(";", plotMetaData.opStack, " "), " ") strswitch(plotMetaData.dataType) - case SF_DATATYPE_SWEEP: // fallthrough + case SF_DATATYPE_EPOCHS: // fallthrough + case SF_DATATYPE_SWEEP: // fallthrough case SF_DATATYPE_TP: sweepNo = JWN_GetNumberFromWaveNote(data, SF_META_SWEEPNO) annotationPrefix = SF_GetAnnotationPrefix(plotMetaData.dataType) @@ -1285,7 +1288,7 @@ Function [STRUCT RGBColor s] SF_GetTraceColor(string graph, string opStack, WAVE s.blue = 0x0000 Make/FREE/T stopInheritance = {SF_OPSHORT_MINUS, SF_OPSHORT_PLUS, SF_OPSHORT_DIV, SF_OPSHORT_MULT} - Make/FREE/T doInheritance = {SF_OP_DATA, SF_OP_TP, SF_OP_PSX, SF_OP_PSX_STATS} + Make/FREE/T doInheritance = {SF_OP_DATA, SF_OP_TP, SF_OP_PSX, SF_OP_PSX_STATS, SF_OP_EPOCHS} WAVE/T opStackW = ListToTextWave(opStack, ";") numDoInh = DimSize(doInheritance, ROWS) @@ -3178,6 +3181,8 @@ Static Function/WAVE SF_OperationEpochsImpl(string graph, WAVE/T epochPatterns, JWN_SetStringInWaveNote(output, SF_META_XAXISLABEL, "Sweeps") JWN_SetStringInWaveNote(output, SF_META_YAXISLABEL, yAxisLabel) + SFH_AddOpToOpStack(output, "", SF_OP_EPOCHS) + return output End From 151fe3d11a0e115720b0d1b5d88fff6e557065ef Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:18:02 +0100 Subject: [PATCH 08/20] TestOperationData: Ensure correct channels for fail tests We want to check that something else than the channel number fails. Bug introduced in 365018ed (SF: Add test for operation data to check for skip of non-existing epochs, 2022-09-20) and 504ba606 (SF: Add Tests for data operation, 2022-01-21). --- Packages/tests/Basic/UTF_SweepFormula.ipf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index 9ec31bc268..ad257383f9 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -2684,12 +2684,12 @@ static Function TestOperationData() REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 0) // non existing sweep - str = "data(TestEpoch,select(channels(AD4),[" + num2istr(sweepNo + 1337) + "],all))" + str = "data(TestEpoch,select(channels(AD),[" + num2istr(sweepNo + 1337) + "],all))" WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 0) // non existing epoch - str = "data(WhatEpochIsThis,select(channels(AD4),[" + num2istr(sweepNo) + "],all))" + str = "data(WhatEpochIsThis,select(channels(AD),[" + num2istr(sweepNo) + "],all))" WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 0) From 71f6358b8c3e6fee4772b67c1812f4985e45cde2 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:19:39 +0100 Subject: [PATCH 09/20] SF_OperationEpochs: Output grouped ranges Since we added support for inputting multiple numeric ranges into data the case of sel=select(channels(AD9),[31,32,35,37,39],all) data(epochs(["E0", "TP"] , $sel), $sel) did not work correctly. This was because epochs returns 10 waves for this case but we only have 5 sweeps. The information that we have two ranges per sweep was lost. The epochs operation now outputs $numSweeps datasets were all ranges are contained in a 2D wave. --- Packages/MIES/MIES_SweepFormula.ipf | 31 +++++++++----- Packages/MIES/SweepFormulaHelp.ifn | Bin 320686 -> 321446 bytes Packages/doc/SweepFormula.rst | 16 +++++-- Packages/tests/Basic/UTF_SweepFormula.ipf | 39 ++++++++++++------ .../UTF_SweepFormulaHardware.ipf | 8 +++- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index 5fe90c58dc..2cf92ca180 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -3056,7 +3056,7 @@ static Function/WAVE SF_OperationTPImpl(string graph, WAVE/WAVE mode, WAVE/Z sel End // epochs(string shortName[, array selectData, [string type]]) -// returns 2xN wave for type = range except for a single range result +// returns 2xN waves for range and 1xN otherwise, where N is the number of epochs static Function/WAVE SF_OperationEpochs(variable jsonId, string jsonPath, string graph) variable numArgs, epType @@ -3101,6 +3101,7 @@ End Static Function/WAVE SF_OperationEpochsImpl(string graph, WAVE/T epochPatterns, WAVE/Z selectData, variable epType, string opShort) variable i, j, numSelected, sweepNo, chanNr, chanType, index, numEpochs, epIndex, settingsIndex, numPatterns, numEntries + variable hasValidData string epName, epShortName, epEntry, yAxisLabel, epAxisName ASSERT(WindowExists(graph), "graph window does not exist") @@ -3112,7 +3113,7 @@ Static Function/WAVE SF_OperationEpochsImpl(string graph, WAVE/T epochPatterns, endif numSelected = DimSize(selectData, ROWS) - WAVE/WAVE output = SFH_CreateSFRefWave(graph, opShort, MINIMUM_WAVE_SIZE) + WAVE/WAVE output = SFH_CreateSFRefWave(graph, opShort, numSelected) epAxisName = TextWaveToList(epochPatterns, "/") if(epType == EPOCHS_TYPE_NAME) @@ -3165,17 +3166,25 @@ Static Function/WAVE SF_OperationEpochsImpl(string graph, WAVE/T epochPatterns, WAVE out = wv endif - JWN_SetNumberInWaveNote(out, SF_META_SWEEPNO, sweepNo) - JWN_SetNumberInWaveNote(out, SF_META_CHANNELTYPE, chanType) - JWN_SetNumberInWaveNote(out, SF_META_CHANNELNUMBER, chanNr) - JWN_SetWaveInWaveNote(out, SF_META_XVALUES, {sweepNo}) - - EnsureLargeEnoughWave(output, indexShouldExist=index) - output[index] = out - index +=1 + if(!WaveExists(output[i])) + output[i] = out + else + WAVE target = output[i] + Concatenate {out}, target + endif endfor + + JWN_SetNumberInWaveNote(output[i], SF_META_SWEEPNO, sweepNo) + JWN_SetNumberInWaveNote(output[i], SF_META_CHANNELTYPE, chanType) + JWN_SetNumberInWaveNote(output[i], SF_META_CHANNELNUMBER, chanNr) + JWN_SetWaveInWaveNote(output[i], SF_META_XVALUES, {sweepNo}) + + hasValidData = 1 endfor - Redimension/N=(index) output + + if(!hasValidData) + Redimension/N=(0) output + endif JWN_SetStringInWaveNote(output, SF_META_DATATYPE, SF_DATATYPE_EPOCHS) JWN_SetStringInWaveNote(output, SF_META_XAXISLABEL, "Sweeps") diff --git a/Packages/MIES/SweepFormulaHelp.ifn b/Packages/MIES/SweepFormulaHelp.ifn index 73c6c6a696d1988c5dede3084dff9a664223263e..8f5a1c8a689b615874604ec4344c4eb25f8c431e 100644 GIT binary patch delta 6120 zcma*r4_MXpy}GCIglBL>CC3<%8Z#m!W=Kc~1O#*h;+&9(M@)&C zGewW_<8~cGBw}WanNxB`=FG@BV~!j-=Fp57V_fo@m+P3__c=c>@AKSe&-Hm;zwhVw z{r~U$^s(jR=}n(ZpJ@=zKRbm@lo)&6#?uFP&3rMW%~fU+Tik?LN~K+--y{9M^LL%- zTUYd~6r;0*uvSV@M$Hs@@X3>+kM|NEA--7dCLa2WQOqL$$;(Dzq0TF$&OiT;)UnWT z&tzx4%M*mi|I#QTwXfcn3*JdF7=EVphe%IH?!F|)u$Q;Pe~yPoXsEJn$j&aZxpKPq zrH@xxSg8GimVwgWM|#kqP-zkrZ}G3XLkH(f0*N?snZ?p4IYNj zP{)f6%N-s2^W|E{qQ-U3e(nkq(^!F0iqdTjb7Y9CdPie|V|djpnd~@PAD}km*>jaW zH(S~48O3&0n3H46w%Zvp^98lgvfk#{)ZpuE_Ae*pd3z*VwpAO-kV@OEr1wJe_yn>t z>={AXMTJl2+JXWeUKn6jfz-TTEl_J0TFlDw)L4e&gRhzU z+VhI6*-CG%ate!8VL|rf1+LR^?WD@EZpe7Sri!y|R91NfDkCc^&z@IUkYQC%7h0cR zUtkNFEVOODGD5D>ruxZw^D_#vRBndt+m(lAlVi_YpKWt?;M|bn!VNafGFwK=hqUKr z%SGM=8SAszAXlMlHqK7Z&z4dDs>0EHKN&c|x#3SFjPC|V`%Jc76=xJY!*I?OjV*3< z!n$RaQ0H@@+K`)Foc-tB7b7qP(?&2CYK1^3sMY z7(mo7=?r!p#&Pr^;E)i(D93S}!YF*+;X{N(Y{Tw%ggQui7*{aBd+%W;i-=zdu?*>$ zQgMXFSd0WDqXd3Og$P6%`Y;4@n-DQ=LZy?I;Q&q{_FV=>ABNz0Oo(GRi7W6tE`&G2 z(2mm>K>d3{R5bHAgYz)9Ga!0V^*#fhU>i~X0h2k&JK~XqlZg3^5VfdBGe)rY6a#)J z)VvRch{hnU!{;L*{E-4XuEDc|^RMaHi&7`fkf>9KI%qpSI2oZ!B~gBKo2b zmDr9J^x!Ite_}A$QG;&u!q~%Lh=(2BF#eed!-7oYV>dd`3I9tRF>e%3*XD^7?xoxs!@k?xPU&)y22467|BS-Zj2!DbIw2I zbD`{?b1rB`E4;4K5Q#{^cI-hby3q^c7hFW7;Si2}AygM>4?O#rH0scVVT>U7OYRF& zU`P3v6`TV>J5J*o0{hv8exYJWOkcBd zSP+jSl;8;3(TPbnSTw?JaQIga zl7FvC5d;hJQHj%Vcca`*s$kMEq@fg3j8fc!X@V41RM>eO#&KN2Ns|<3Fo4#HQncX$ z4!JWpdQdt^icJ`SxJ!y;q@x3!2=J7m7(=*$lF9T7_bE&Yi(yCG6j>oWzavE!HhxE{ zTGD!)!ByTjda+Whm?}ja>TwKLFp9uwQkYSRMjS-kblO9BGayzY)my3((v3KPQwW?v z+Zh#P5>%lP*D(s^BSr2^DT=TI)w86ih0onmN0P4Wa^8BNd&n+|SkrOVNz71yXEA7nYhC z1gGKoT`9Z~gCX3&sSx@LWp}Xy!k_JYsMPzAM>ML}M#VkMMg%DK?=UH{l)2Dq~qC(ou$Cj3D|^ z>ftnQ!4xNzier&TgcWDeg(;75{ysbw;{Xn!6MY!M%6Kjhu45G8tN2)<9F?o2N=%R< zB|$1XX*rtF3ghEkOhlj@m1sd1dN3`K#UeP7^A97~gsq8EHIN?1C5)i#N8DN%R`ZQO zCt{yqa!5+ztA^t+tl=n-i9u(Y%r6GLh<}p0*o0d}A!O{!MXHu$Y$@O4rpkS3$H zg2y44(%DM1!k9rJicpHz(STMQM;9((5F_x)WGgeJT0t6z0+hh?G°v*-(@Xhc7Z z*<3zUEaowQtQ=O2TJ*sE8NO6lhFH|030EL)fPF|WDo|i`KMaIW@$EDHEIdY z^c$15(V4#Kqj_zWL8f3aNwmz;PHZ7wK>Vh!W_ei#X_hV0XsGjZRhsx+3f}NHXwmN( zXKHGT^zsNHr%4&aH(JKaj1=mu5a8^^Y^V#=hDhD^&ogM9fd;+7#4u{q2ayvrp0kLY z{lQ~7)s@oQ&}7!xQ7B;njZTRm!<3RY~q(PwN0Ap;5f5zA<3QrCHf!nI>3pP8&zn!)H&uEZ zL~MFRkMAd*{1aEKMZZet-mmHfk5Mptrz`e|Cl1&(en_TOyv}rJov+gWl-Km?3FOVt zwCJ5O$it1;{oig^H}d+=*IgGxYuzD@6GrylE;CW~_d31VMB?81+ePlYe%jzHG8;M? zwE|}vd{EmOTPugOm_Q4i8e`1oeC|oZxtr2{& zmuj1<>Bjm4t<{-fGNn?tI#$aiZ-OP>C^l-BtL0peG_qS( zjlY`K@oIityXI`UHc3;vNpG&v;z=izK6$&^(^R{Wq8)G+f40t9EvUx9Mwe!2CI`P0 z|6F~B6Z4qv-2AcW=JH~;YSqpfiwedvo&QdcDAER!pZ1ulJ01d}f{_XZribZ@*m= zi^=i+KyRS+zQ$J1{>@nPLW%-D)FzHNa7G5 zLkyu*F5#t0EmA@VrGyYeSeA7OrIvN6vDRfNRo1wKTI*6u4QpJ&_B-=JH@i>&SfA%J z=XdVsJ?FfyTK)OV`hS`^$0W`_-~AQ^X4qIph)Gg1NIug`7#1? zG-tyvCW&P9n`NzDSgs!UIM!tPkxD3j{`Br*t-`(7?eyYekIE>OMoCDqUb(n8;ug+J#o6}y}pO~?4ueLiaLEC#E&AxkI+9A#U@&PR|HF1Ap zn*EUW@;-aAW=~Gk4%v5S*j)`1X>8xOKT$i7 ze`AoOmr_%;wB7%wum3dt)O;19wz(D+KCvj*)ZZHCf7W8pNKDjH6EhQ2|JNG)r*XFY znI-|n2UBgI?(kKAG?#8t-k&BfQ8iCFdQOY@R*D4SET;ZgWGOl^w@PFcI^p#m?T~{y zj3KOAB(hqx1kzNLqX~207tvtHIRw^-RAU%-QTc&LE$p@8klI?2MhqhJCn8<2)QNb) zAJI61MqI%tyno7qu?t1G2y?y2A}mJ^+Hs{`v=P!V_enrVk<$lr;0zz@+aypPC&`R_SC1YiJzu(om>h(r~Z z{sTLVjI-?GKZ-co`AKP`03|4g$7d`CeHg$XLfS>HBl>5erT$DL7ppo%f)V#~kpxtr z1wrR%hb~+`C)yzC2>gD*j-nZDNb2PL(>j?94QNI;%;z}@WT6b1|ID4}5-C8)FGV7d z29FE$L(%6VC(w?Jiz2sh8v(y!BJ8@v@uCf#nEGpu3(>#k{9hwDiYA=JC>k&GB(VMq z7YaQXMt(QD)Gb;)X%qS|fWUucVK{>8xC!4Lwi{u{#d%!9Z7li?<08_*Pa|4z1-`v> zLL~B0f)@DxmNG;m6E$c+4?M5%dLjW;sKX_AUuB}JqOBzjM+UNS0k<&sqVjx2Ox(eF7Hbm1Cq!1t!ecI>(-S~lqs zG@u!te_$L$Iryo@DEx2nG|0mh%=iz^5ZO3_GiXH*d{H^+()M4-&UPb)egqQnEH>5B|>pbIw7IY`6yYaw0hDe^x+P27AZM~J`7;VVkNQIgI-)m}vA$Tv>2AK_)8Df`ARI8aWt)Wh1X5A~q^*7iltzP=~3JJQcPe26o)UFsx5< z`QVRZ=);;#TyA9IFk0d0*rc?g&D_k*N;^Z^iW?Y0_!cfEveAkT3}V(+B|eBoEK-nx zHgs-P+I7-V_(t(CXu*=Fc>PcXYcy{Z{GaChOP}Ud!zjYGaTKV8?HN+kLW@ySfKs&M zJi?ykNpKilxQq?kl|&&I6{y3x?Mk~w8oYzMgIl=0LusC|d~0HP|Mw8Qh6^w~#|H$V zh(i(TaRJ@WDQ$@K4&vgNJWgrHNz2fKVMIL7PGSh*JE=nhn$e5v7{VxK#j`^WeryOr z6jI`qc9b+9^=LxGE^a)sb~6~7(GR~pOoCL{v3xJvi#qhcCxIP70*)XLeHg%!MCL_E zK0nqZ-b-wN9nF~X0v8CGI1F<#7Y?bgCo8Rhv@}^yIjVBp63UeRM!A}!?|M^NEhaHZ z-U;3NO=X*OOPOSzzA{(cr>DQE%qDlUo=Lgs!CbK+5*(VP$zn>+Lux^E(Q%L+U@%s-t zi}k2u%yDYv#GIC3s`S;a9*+_hJY@7(OZ@i3`n6-q=9crwx0WzDgqqhMop>J0<75v% zW^@WAe&umjthc?b%x?DYe!FKF)d$y2%shD=*_0BFg^o zsu3?H9)8`3mk@vLaK(Dg_ZeJ{7!}K?a6f9qe#D-+Mtnc<>|?t1ID`A-8D*O(Tbysi zTZoqzIM2k-vXywjan~IhI?mW$MXolJqbQ$WVv;`_Z-5(FYpK!2OgyQ~c^_;hIpHds zY@zI{lYedQPWHwRjVce~t3R5k(svax%f-`rQW4!(eV~_+x@}uw)(@^UkG~Gj&?Ejq zeaP7&X{GL4OpEx{X8p5w)H2Jn!arLc(z7Wv*@N{O(n%kLn&qTkSi-9sOLcgdZZ4tt znv1j$lcWL}ZoU^gbl(SPGBXwJvXspqF9}lqNh4D->nKk>2jNVYn`}63F}mzDj0Iw~;A5>D;HD@tW7D3F|d7Z^fNdFv0E9xRd6{O@9F@f&C`{ diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index 2bdec7a655..9009a5679f 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -687,6 +687,10 @@ The returned data type is `SF_DATATYPE_SWEEP`. // Shows epoch "E1" range of the AD channels of all displayed sweeps data("E1", select(channels(AD), sweeps())) + // Shows epoch "E1" range with the start offsetted by 10ms of the AD channels of all displayed sweeps + sel = select(channels(AD), sweeps()) + data(epochs("E1", $sel) + [10, 0], $sel) + // Shows sweep data from all epochs starting with "E" of the AD channels of all displayed sweeps data("E*", select(channels(AD), sweeps())) @@ -1308,16 +1312,17 @@ For example if `select()` is used for the selectData argument then all channels Thus, there are data waves only returned for the `DA` channels. If a selection has epoch information stored in the labnotebook and the specified epoch does not exist it is skipped and thus, not included in the output waves. -The output data varies depending on the requested type. +The output data varies depending on the requested type. Multiple epochs for one +sweep always result in additional columns. range: -Each output data wave is numeric and contains two elements with the start and end time of the epoch in [ms]. +Each output data wave is numeric and has the start/end times in the rows [ms]. name: -Each output data wave is textual and contains one elements with the name of the epoch. +Each output data wave is textual and contains name of the epoch. treelevel: -Each output data wave is numeric with one element with the tree level of the epoch. +Each output data wave is numeric and has the tree level of the epoch. The returned data type is `SF_DATATYPE_EPOCHS`. The default suggested x-axis values for the formula plotter are sweep numbers. The suggested y-axis label is the combination of the requested type (`name`, `tree level`, `range`) and the epoch name wildcards. @@ -1336,6 +1341,9 @@ The default suggested x-axis values for the formula plotter are sweep numbers. T // get stimset range from specified epochs from all displayed sweeps and channels epochs(["TP_B?", "E?_*"], select(channels(AD), sweeps())) + // get ranges for epochs TP_B0/TP_B1 where the start is offsetted by 5/10 ms + epochs(["TP_B0", "TP_B1"], select(channels(AD), sweeps())) + [[5, 10], [0, 0]] + tp "" diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index ad257383f9..b31a460885 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -2693,6 +2693,19 @@ static Function TestOperationData() WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 0) + // empty range from epochs + str = "select(channels(AD),[" + num2istr(sweepNo) + "],all)" + str = "data(epochs(WhatEpochIsThis, " + str + "), " + str + ")" + WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) + REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 0) + + // one null range from epochs as TestEpoch1 only exists for sweepNo + str = "select(channels(AD6),[" + num2istr(sweepNo) + ", " + num2istr(sweepNo + 1) + "],all)" + str = "data(epochs([TestEpoch1], " + str + "), " + str + ")" + WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) + REQUIRE_EQUAL_VAR(DimSize(dataWref, ROWS), 1) + REQUIRE_EQUAL_VAR(DimSize(dataWref[0], ROWS), 6) + // range begin str = "data([12, 10],select(channels(AD),[" + num2istr(sweepNo) + "],all))" try @@ -2769,7 +2782,6 @@ static Function TestOperationData() catch PASS() endtry - End Function/WAVE FakeSweepDataGeneratorPS(WAVE sweep, variable numChannels) @@ -3088,23 +3100,24 @@ static Function TestOperationEpochs() // finds only epoch without shortname from test epochs str = "epochs(\"!E0_PT_P48*\", select(channels(DA), 0))" WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) - CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), activeChannelsDA * 2) + CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), activeChannelsDA) + CHECK_EQUAL_VAR(DimSize(dataWref, COLS), 0) + + for(WAVE/Z data : dataWref) + CHECK_WAVE(data, NUMERIC_WAVE) + CHECK_EQUAL_VAR(DimSize(data, ROWS), 2) + CHECK_EQUAL_VAR(DimSize(data, COLS), 2) + endfor // the first wildcard matches both setup epochs, the second only the first setup epoch // only unique epochs are returned, thus two str = "epochs([\"E0_PT_*\",\"E0_PT_P48*\"], select(channels(DA), 0))" WAVE/WAVE dataWref = SF_ExecuteFormula(str, win, useVariables=0) - CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), 8) - Make/FREE/D refData1 = {500, 510} - Make/FREE/D refData2 = {503, 510} - i = 0 - for(data : dataWref) - if(!i) - CHECK_EQUAL_WAVES(data, refData1, mode = WAVE_DATA) - else - CHECK_EQUAL_WAVES(data, refData2, mode = WAVE_DATA) - endif - i = 1 - i + CHECK_EQUAL_VAR(DimSize(dataWref, ROWS), 4) + Make/FREE/D refData = {{500, 510}, {503, 510}} + for(WAVE/Z data : dataWref) + CHECK_WAVE(data, NUMERIC_WAVE) + CHECK_EQUAL_WAVES(data, refData, mode = WAVE_DATA) endfor // channel(s) with no epochs diff --git a/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf b/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf index 84a8e8a8bf..031f6edada 100644 --- a/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf +++ b/Packages/tests/HardwareBasic/UTF_SweepFormulaHardware.ipf @@ -800,6 +800,8 @@ static Function SF_UnassociatedDATTL_Epochs_REENTRY([string str]) WAVE/WAVE data = SF_ExecuteFormula(formula, graph, useVariables=0) CHECK_WAVE(data, WAVE_WAVE) CHECK_EQUAL_VAR(DimSize(data, ROWS), 29) + CHECK_EQUAL_VAR(DimSize(data, COLS), 0) + WAVE epochData = data[0] CHECK_WAVE(epochData, NUMERIC_WAVE) CHECK_GT_VAR(DimSize(epochData, ROWS), 1) @@ -807,8 +809,12 @@ static Function SF_UnassociatedDATTL_Epochs_REENTRY([string str]) formula = "epochs(\"E0_PT_*\",select(channels(DA2),sweeps()),name)" WAVE/WAVE data = SF_ExecuteFormula(formula, graph, useVariables=0) CHECK_WAVE(data, WAVE_WAVE) - CHECK_EQUAL_VAR(DimSize(data, ROWS), 29) + CHECK_EQUAL_VAR(DimSize(data, ROWS), 1) + CHECK_EQUAL_VAR(DimSize(data, COLS), 0) + WAVE/T epochDataT = data[0] CHECK_WAVE(epochDataT, TEXT_WAVE) + CHECK_EQUAL_VAR(DimSize(epochDataT, ROWS), 1) + CHECK_EQUAL_VAR(DimSize(epochDataT, COLS), 29) CHECK_EQUAL_STR(epochDataT[0], "E0_PT_P0_P") End From 416838c41ba88b2e6c97764e4aa81c6187195da6 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:22:45 +0100 Subject: [PATCH 10/20] SFH_GetSweepsForFormula: Enforce that rangeStart < rangeEnd --- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 1 + 1 file changed, 1 insertion(+) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index e18f2b5c9c..5111b6ec50 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -559,6 +559,7 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele rangeEnd = limit(rangeEnd, -inf, lastx) endif + SFH_ASSERT(rangeStart < rangeEnd, "Starting range must be smaller than the ending range for sweep " + num2istr(sweepNo) + ".") SFH_ASSERT(rangeStart == -inf || (IsFinite(rangeStart) && rangeStart >= leftx(sweep) && rangeStart < lastx), "Specified starting range not inside sweep " + num2istr(sweepNo) + ".") SFH_ASSERT(rangeEnd == inf || (IsFinite(rangeEnd) && rangeEnd > leftx(sweep) && rangeEnd <= lastx), "Specified ending range not inside sweep " + num2istr(sweepNo) + ".") Duplicate/FREE/R=(rangeStart, rangeEnd) sweep, rangedSweepData From ccdb0632b797f2cac12e7c95683f21305889dd53 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:23:33 +0100 Subject: [PATCH 11/20] PSX_OperationKernel: Mention that this is not SF_META_RANGE --- 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 2b7b556d4c..01babc210d 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -4303,7 +4303,7 @@ Function/WAVE PSX_OperationKernel(variable jsonId, string jsonPath, string graph parameterPath = SF_META_USER_GROUP + PSX_JWN_PARAMETERS + "/" + SF_OP_PSX_KERNEL JWN_CreatePath(output, parameterPath) - JWN_SetWaveInWaveNote(output, parameterPath + "/range", range) + JWN_SetWaveInWaveNote(output, parameterPath + "/range", range) // not the same as SF_META_RANGE JWN_SetNumberInWaveNote(output, parameterPath + "/riseTau", riseTau) JWN_SetNumberInWaveNote(output, parameterPath + "/decayTau", decayTau) JWN_SetNumberInWaveNote(output, parameterPath + "/amp", amp) From 595daef00ef31daa66250e430baa3b60f771109d Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Fri, 2 Feb 2024 23:24:05 +0100 Subject: [PATCH 12/20] MIES_SweepFormula_PSX.ipf: Add support for multiple ranges --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 46 +++++++++++-------- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 8 ++-- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index 01babc210d..bf66b0ffe9 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -1102,7 +1102,7 @@ static Function/WAVE PSX_GenerateSweepEquiv(WAVE selectData) End /// @brief Helper function of the `psxStats` operation -static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE rangeParam, WAVE selectData, string prop, string stateAsStr, string postProc) +static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE rangeParam, WAVE selectData, string prop, string stateAsStr, string postProc) string propLabelAxis, comboKey variable numRows, numCols, i, j, k, index, sweepNo, chanNr, chanType, state, numRanges, lowerBoundary, upperBoundary, temp, err @@ -1116,16 +1116,8 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE rangeP numRows = DimSize(selectDataEquiv, ROWS) numCols = DimSize(selectDataEquiv, COLS) - // see also SFH_EvaluateRange() - if(IsNumericWave(rangeParam)) - numRanges = 1 - Make/FREE/WAVE allRanges = {rangeParam} - elseif(IsTextWave(rangeParam)) - numRanges = DimSize(rangeParam, ROWS) - WAVE/T rangeParamText = rangeParam - Make/FREE/WAVE/N=(numRanges) allRanges = ListToTextWave(rangeParamText[p], ";") - WaveClear rangeParamText - endif + WAVE/WAVE allRanges = SplitWavesToDimension(rangeParam) + numRanges = DimSize(allRanges, ROWS) WaveClear rangeParam WAVE/Z eventContainerFromResults = PSX_GetEventContainerFromResults(id) @@ -3572,9 +3564,9 @@ End /// @brief Read the user JWN from results and create a legend from all operation parameters static Function PSX_AddLegend(string win, WAVE/WAVE results) - variable jsonID, value, type, i, j, numOperations, numParameters + variable jsonID, value, type, i, j, numOperations, numParameters, numRows string line, op, param, prefix, opNice, mainWindow, jsonPathOp, jsonPathParam - string str + string str, containerSep string sep = ", " jsonID = JWN_GetWaveNoteAsJSON(results) @@ -3623,13 +3615,27 @@ static Function PSX_AddLegend(string win, WAVE/WAVE results) str = TextWaveToList(wvText, sep) WaveClear wvText else - WAVE wv = JSON_GetWave(jsonID, jsonPathParam, waveMode = 1) - ASSERT(IsNumericWave(wv), "Expected numeric wave") - str = NumericWaveToList(wv, sep) - WaveClear wv + if(!cmpstr(param, "range")) + WAVE/WAVE container = JWN_GetWaveRefNumericFromWaveNote(results, jsonPathParam) + containerSep = "; " + else + WAVE wv = JSON_GetWave(jsonID, jsonPathParam, waveMode = 1) + Make/FREE/WAVE container = {wv} + containerSep = "" + endif + + str = "" + for(WAVE wv : container) + ASSERT(IsNumericWave(wv), "Expected numeric wave") + // NumericWaveToList outputs in column-major order but we want row-major + MatrixOp/FREE dest = wv^t + str += RemoveEnding(NumericWaveToList(dest, sep), sep) + containerSep + endfor + + str = RemoveEnding(str, containerSep) endif - sprintf line, "%s: %s", param, RemoveEnding(str, ", ") + sprintf line, "%s: %s", param, RemoveEnding(str, sep) break default: ASSERT(0, "Unsupported type") @@ -4257,7 +4263,7 @@ Function/WAVE PSX_OperationKernel(variable jsonId, string jsonPath, string graph variable riseTau, decayTau, amp, dt, numPoints, numCombos, i, offset string parameterPath, key - WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_PSX_KERNEL, 0) + WAVE/WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_PSX_KERNEL, 0) WAVE/Z selectData = SFH_GetArgumentSelect(jsonID, jsonPath, graph, SF_OP_PSX_KERNEL, 1) @@ -4358,7 +4364,7 @@ Function/WAVE PSX_OperationStats(variable jsonId, string jsonPath, string graph) id = SFH_GetArgumentAsText(jsonID, jsonPath, graph, SF_OP_PSX, 0, checkFunc = IsValidObjectName) - WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_PSX_STATS, 1) + WAVE/WAVE range = SFH_EvaluateRange(jsonId, jsonPath, graph, SF_OP_PSX_STATS, 1) WAVE/Z selectData = SFH_GetArgumentSelect(jsonID, jsonPath, graph, SF_OP_PSX_STATS, 2) SFH_Assert(WaveExists(selectData), "Missing select data") diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 73bbb25b34..7223bed42c 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -421,7 +421,7 @@ static Function StatsComplainsWithoutEvents() // matching id but no events try - MIES_PSX#PSX_OperationStatsImpl(browser, id, range, selectData, prop, stateAsStr, postProc) + MIES_PSX#PSX_OperationStatsImpl(browser, id, {range}, selectData, prop, stateAsStr, postProc) FAIL() catch error = ROStr(GetSweepFormulaParseErrorMessage()) @@ -432,7 +432,7 @@ static Function StatsComplainsWithoutEvents() // mismatched id try - MIES_PSX#PSX_OperationStatsImpl(browser, id, range, selectData, prop, stateAsStr, postProc) + MIES_PSX#PSX_OperationStatsImpl(browser, id, {range}, selectData, prop, stateAsStr, postProc) FAIL() catch error = ROStr(GetSweepFormulaParseErrorMessage()) @@ -669,7 +669,7 @@ static Function StatsWorksWithResults([STRUCT IUTF_mData &m]) MIES_PSX#PSX_StoreIntoResultsWave(browser, SFH_RESULT_TYPE_PSX_EVENTS, psxEvent, id) - WAVE/WAVE output = MIES_PSX#PSX_OperationStatsImpl(browser, id, range, selectData, prop, stateAsStr, postProc) + WAVE/WAVE output = MIES_PSX#PSX_OperationStatsImpl(browser, id, {range}, selectData, prop, stateAsStr, postProc) CHECK_WAVE(output, WAVE_WAVE) Make/FREE/N=4 dims = DimSize(output, p) @@ -950,7 +950,7 @@ static Function StatsWorksWithResultsSpecialCases([STRUCT IUTF_mData &m]) refNum = NaN endif - WAVE/WAVE output = MIES_PSX#PSX_OperationStatsImpl(browser, id, range, allSelectData, prop, stateAsStr, postProc) + WAVE/WAVE output = MIES_PSX#PSX_OperationStatsImpl(browser, id, {range}, allSelectData, prop, stateAsStr, postProc) CHECK_WAVE(output, WAVE_WAVE) if(outOfRange) From 88db009c3f88eda8f87c77a2fd1862f9d375cdb7 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 5 Feb 2024 16:36:07 +0100 Subject: [PATCH 13/20] SFH_GetNumericRangeFromEpoch: Factor it out --- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 130 ++++++++++++-------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index 5111b6ec50..c33fa468d7 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -426,10 +426,8 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele variable i, j, rangeStart, rangeEnd, sweepNo, isSingleRange variable chanNr, chanType, cIndex, isSweepBrowser - variable numSelected, index, numRanges, numEpochs, epIndex, lastx - string dimLabel, device, dataFolder, epochTag, epochShortName - string allEpochsRegex = "^.*$" - + variable numSelected, index, numRanges, lastx + string dimLabel, device, dataFolder ASSERT(WindowExists(graph), "graph window does not exist") isSingleRange = DimSize(range, ROWS) == 1 @@ -466,14 +464,6 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele continue endif - if(IsTextWave(setRange)) - WAVE/T epochNames = setRange - SFH_ASSERT(!DimSize(epochNames, COLS), "Expected 1d text wave for epoch specification") - else - ASSERT(IsDoubleFloatingPointWave(setRange), "Expected a double wave") - WAVE/Z/T epochNames = $"" - endif - sweepNo = selectData[i][%SWEEP] chanNr = selectData[i][%CHANNELNUMBER] chanType = selectData[i][%CHANNELTYPE] @@ -501,47 +491,14 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele continue endif - if(WaveExists(epochNames)) - - WAVE/T/Z epochInfo = EP_GetEpochs(numericalValues, textualValues, sweepNo, chanType, chanNr, allEpochsRegex) - if(!WaveExists(epochInfo)) - continue - endif - WAVE/T allEpNames = SFH_GetEpochNamesFromInfo(epochInfo) - WAVE/Z epIndices = SFH_GetEpochIndicesByWildcardPatterns(allEpNames, epochNames) - if(!WaveExists(epIndices)) - continue - endif - numEpochs = DimSize(epIndices, ROWS) - WAVE adaptedRange = SFH_GetEmptyRange() - - Make/FREE/T/N=(numEpochs) epochRangeNames - - Redimension/N=(-1, numEpochs) adaptedRange - for(j = 0; j < numEpochs; j += 1) - epIndex = epIndices[j] - adaptedRange[0][j] = str2num(epochInfo[epIndex][EPOCH_COL_STARTTIME]) * ONE_TO_MILLI - adaptedRange[1][j] = str2num(epochInfo[epIndex][EPOCH_COL_ENDTIME]) * ONE_TO_MILLI - - epochTag = epochInfo[epIndex][EPOCH_COL_TAGS] - - epochShortName = EP_GetShortName(epochTag) + WAVE/ZZ adaptedRange + WAVE/T/ZZ epochRangeNames + [adaptedRange, epochRangeNames] = SFH_GetNumericRangeFromEpoch(numericalValues, textualValues, setRange, sweepNo, chanType, chanNr) - if(IsEmpty(epochShortName)) - epochRangeNames[j] = epochTag - else - epochRangeNames[j] = epochShortName - endif - endfor - else - Duplicate/FREE setRange, adaptedRange - if(!DimSize(adaptedRange, COLS)) - Redimension/N=(-1, 1) adaptedRange - endif + if(!WaveExists(adaptedRange) && !WaveExists(epochRangeNames)) + continue endif - SFH_ASSERT(!SFH_IsEmptyRange(adaptedRange), "Specified range not valid.") - numRanges = DimSize(adaptedRange, COLS) for(j = 0; j < numRanges; j += 1) rangeStart = adaptedRange[0][j] @@ -551,7 +508,7 @@ Function/WAVE SFH_GetSweepsForFormula(string graph, WAVE/WAVE range, WAVE/Z sele // we did not cap epoch ranges properly on aborted/shortened sweeps // we also did not calculate the sampling points for TP and Stimesets exactly the same way // Thus, if necessary we clip the data here. - if(WaveExists(epochNames)) + if(WaveExists(epochRangeNames)) // complete epoch starting at or beyond sweep end if(rangeStart >= lastx) continue @@ -1361,3 +1318,74 @@ Function/WAVE SFH_GetStimsetRange(string graph, WAVE data, WAVE selectData) return range End + +/// @brief From a single numeric/textual range wave we return a 2xN numeric range +/// +/// Supports numeric ranges, epochs, and epochs with wildcards. +/// +/// @param numericalValues numeric labnotebok +/// @param textualValues textual labnotebok +/// @param range one numerical or one/multiple epoch ranges with optional wildcard, @see SFH_EvaluateRange +/// @param sweepNo sweep number +/// @param chanType channel type +/// @param chanNr channel number +/// +/// @retval adaptedRange 2xN numeric wave with the start/stop ranges [ms] +/// @retval epochRangeNames epoch names (wildcard expanded) in case range was textual, a null wave ref otherwise +Function [WAVE adaptedRange, WAVE/T epochRangeNames] SFH_GetNumericRangeFromEpoch(WAVE numericalValues, WAVE textualValues, WAVE range, variable sweepNo, variable chanType, variable chanNr) + + string epochTag, epochShortName + variable numEpochs, epIndex, i, j + string allEpochsRegex = "^.*$" + + if(IsNumericWave(range)) + ASSERT(IsDoubleFloatingPointWave(range), "Expected a double wave") + + Duplicate/FREE range, adaptedRange + if(!DimSize(adaptedRange, COLS)) + Redimension/N=(-1, 1) adaptedRange + endif + + SFH_ASSERT(!SFH_IsEmptyRange(adaptedRange), "Specified range not valid.") + + return [adaptedRange, $""] + endif + + WAVE/T epochNames = range + SFH_ASSERT(IsTextWave(epochNames) && !DimSize(epochNames, COLS), "Expected 1d text wave for epoch specification") + + WAVE/T/Z epochInfo = EP_GetEpochs(numericalValues, textualValues, sweepNo, chanType, chanNr, allEpochsRegex) + if(!WaveExists(epochInfo)) + return [$"", $""] + endif + + WAVE/T allEpNames = SFH_GetEpochNamesFromInfo(epochInfo) + WAVE/Z epIndices = SFH_GetEpochIndicesByWildcardPatterns(allEpNames, epochNames) + if(!WaveExists(epIndices)) + return [$"", $""] + endif + + numEpochs = DimSize(epIndices, ROWS) + WAVE adaptedRange = SFH_GetEmptyRange() + + Make/FREE/T/N=(numEpochs) epochRangeNames + + Redimension/N=(-1, numEpochs) adaptedRange + for(j = 0; j < numEpochs; j += 1) + epIndex = epIndices[j] + adaptedRange[0][j] = str2num(epochInfo[epIndex][EPOCH_COL_STARTTIME]) * ONE_TO_MILLI + adaptedRange[1][j] = str2num(epochInfo[epIndex][EPOCH_COL_ENDTIME]) * ONE_TO_MILLI + + epochTag = epochInfo[epIndex][EPOCH_COL_TAGS] + + epochShortName = EP_GetShortName(epochTag) + + if(IsEmpty(epochShortName)) + epochRangeNames[j] = epochTag + else + epochRangeNames[j] = epochShortName + endif + endfor + + return [adaptedRange, epochRangeNames] +End From 637ed89d8277297ab90a65f8d04c8bac24c138af Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 5 Feb 2024 21:38:28 +0100 Subject: [PATCH 14/20] SFH_GetRangeFromEpoch: Simplify epoch name handling We already have the epoch shortnames, with fallback to the tag if empty, so we can just use these. --- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index c33fa468d7..ba7d4442ee 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -1368,24 +1368,14 @@ Function [WAVE adaptedRange, WAVE/T epochRangeNames] SFH_GetNumericRangeFromEpoc numEpochs = DimSize(epIndices, ROWS) WAVE adaptedRange = SFH_GetEmptyRange() - Make/FREE/T/N=(numEpochs) epochRangeNames - Redimension/N=(-1, numEpochs) adaptedRange for(j = 0; j < numEpochs; j += 1) epIndex = epIndices[j] adaptedRange[0][j] = str2num(epochInfo[epIndex][EPOCH_COL_STARTTIME]) * ONE_TO_MILLI adaptedRange[1][j] = str2num(epochInfo[epIndex][EPOCH_COL_ENDTIME]) * ONE_TO_MILLI - - epochTag = epochInfo[epIndex][EPOCH_COL_TAGS] - - epochShortName = EP_GetShortName(epochTag) - - if(IsEmpty(epochShortName)) - epochRangeNames[j] = epochTag - else - epochRangeNames[j] = epochShortName - endif endfor + Make/FREE/T/N=(numEpochs) epochRangeNames = allEpNames[epIndices[p]] + return [adaptedRange, epochRangeNames] End From 62c326aa767f8fd1d2cf16f49b082079f13d2af2 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 5 Feb 2024 22:02:16 +0100 Subject: [PATCH 15/20] SFH_GetNumericRangeFromEpoch: Rename epochNames --- Packages/MIES/MIES_SweepFormula_Helpers.ipf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Helpers.ipf b/Packages/MIES/MIES_SweepFormula_Helpers.ipf index ba7d4442ee..07f2f36f3d 100644 --- a/Packages/MIES/MIES_SweepFormula_Helpers.ipf +++ b/Packages/MIES/MIES_SweepFormula_Helpers.ipf @@ -1351,8 +1351,8 @@ Function [WAVE adaptedRange, WAVE/T epochRangeNames] SFH_GetNumericRangeFromEpoc return [adaptedRange, $""] endif - WAVE/T epochNames = range - SFH_ASSERT(IsTextWave(epochNames) && !DimSize(epochNames, COLS), "Expected 1d text wave for epoch specification") + WAVE/T epochPatterns = range + SFH_ASSERT(IsTextWave(epochPatterns) && !DimSize(epochPatterns, COLS), "Expected 1d text wave for epoch specification") WAVE/T/Z epochInfo = EP_GetEpochs(numericalValues, textualValues, sweepNo, chanType, chanNr, allEpochsRegex) if(!WaveExists(epochInfo)) @@ -1360,7 +1360,7 @@ Function [WAVE adaptedRange, WAVE/T epochRangeNames] SFH_GetNumericRangeFromEpoc endif WAVE/T allEpNames = SFH_GetEpochNamesFromInfo(epochInfo) - WAVE/Z epIndices = SFH_GetEpochIndicesByWildcardPatterns(allEpNames, epochNames) + WAVE/Z epIndices = SFH_GetEpochIndicesByWildcardPatterns(allEpNames, epochPatterns) if(!WaveExists(epIndices)) return [$"", $""] endif From 429c81dd9c6040f804684e615355eb9a8a240ece Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Mon, 5 Feb 2024 23:44:14 +0100 Subject: [PATCH 16/20] AreIntervalsIntersecting: Add it --- Packages/MIES/MIES_Utilities.ipf | 45 +++++++++++++++++ Packages/tests/Basic/UTF_Utils.ipf | 77 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/Packages/MIES/MIES_Utilities.ipf b/Packages/MIES/MIES_Utilities.ipf index 8fa07d632f..980e3e5aca 100644 --- a/Packages/MIES/MIES_Utilities.ipf +++ b/Packages/MIES/MIES_Utilities.ipf @@ -6888,3 +6888,48 @@ Function/WAVE SplitWavesToDimension(WAVE/WAVE input, [variable sdim]) return output End + +threadsafe static Function AreIntervalsIntersectingImpl(variable index, WAVE intervals) + + ASSERT_TS(!IsNaN(intervals[index][0]) && !IsNaN(intervals[index][1]), "Expected finite entries") + ASSERT_TS(intervals[index][0] < intervals[index][1], "Expected interval start < end") + + if(index == 0) + return 0 + endif + + // check that every interval, starting from the second interval + // starts later than the previous one ends + return (intervals[index][0] < intervals[index - 1][1]) +End + +/// @brief Return the truth if any of the given intervals ]A, B[ intersect. +/// +/// @param intervalsParam Nx2 wave with the intervals +threadsafe Function AreIntervalsIntersecting(WAVE intervalsParam) + + variable numRows, i + + ASSERT_TS(IsNumericWave(intervalsParam), "Expected a numeric wave") + + numRows = DimSize(intervalsParam, ROWS) + // two columns: start, end + ASSERT_TS(DimSize(intervalsParam, COLS) == 2, "Expected exactly two columns") + + if(numRows <= 1) + return 0 + endif + + // sort start column in ascending order + Duplicate/FREE intervalsParam, intervals + WaveClear intervalsParam + SortColumns/KNDX={0} sortWaves=intervals + + Make/FREE/R/N=(numRows) result = NaN + + Multithread result = AreIntervalsIntersectingImpl(p, intervals) + + ASSERT_TS(IsNaN(GetRowIndex(result, val = NaN)), "Error evaluating intervals") + + return IsFinite(GetRowIndex(result, val = 1)) +End diff --git a/Packages/tests/Basic/UTF_Utils.ipf b/Packages/tests/Basic/UTF_Utils.ipf index c88ca705b8..79567a6ffd 100644 --- a/Packages/tests/Basic/UTF_Utils.ipf +++ b/Packages/tests/Basic/UTF_Utils.ipf @@ -7593,3 +7593,80 @@ static Function TestSplitWavesToDimension() CHECK_EMPTY_FOLDER() End + +static Function TestAreIntervalsIntersecting() + + // wrong wave type + try + Make/FREE/T wvText + AreIntervalsIntersecting(wvText) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // 1D wave + try + Make/FREE wv + AreIntervalsIntersecting(wv) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // trivial case #1: empty + Make/FREE/N=(0, 2) empty + CHECK_EQUAL_VAR(0, AreIntervalsIntersecting(empty)) + + // trivial case #2: only one interval + Make/FREE single = {{1}, {2}} + CHECK_EQUAL_VAR(0, AreIntervalsIntersecting(single)) + + // contains NaN values (start) + Make/FREE infValues = {{1, inf}, {2, 4}} + + try + AreIntervalsIntersecting(infValues) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // contains NaN values (end) + Make/FREE infValues = {{1, 3}, {2, NaN}} + + try + AreIntervalsIntersecting(infValues) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // invalid ordering + Make/FREE invalidOrder = {{2, 3}, {1, 4}} + + try + AreIntervalsIntersecting(invalidOrder) + FAIL() + catch + CHECK_NO_RTE() + endtry + + // works + Make/FREE data = {{1, 3}, {2, 4}} + CHECK(!AreIntervalsIntersecting(data)) + + // intervals which have start == end are okay + Make/FREE data = {{1, 2}, {2, 3}} + CHECK(!AreIntervalsIntersecting(data)) + + Make/FREE data = {{2.5, 1, 2.7}, {2.6, 2.4, 4}} + CHECK(!AreIntervalsIntersecting(data)) + + Make/FREE data = {{2, 1}, {3, 4}} + CHECK(AreIntervalsIntersecting(data)) + + // works also with infinite + Make/FREE data = {{-inf, 3}, {2, inf}} + CHECK(!AreIntervalsIntersecting(data)) +End From 6d107905af9574e0df705898c084572223dab874 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 6 Feb 2024 00:01:03 +0100 Subject: [PATCH 17/20] PSX_OperationStatsImpl: Assert on intersecting ranges --- Packages/MIES/MIES_SweepFormula_PSX.ipf | 66 ++++++++++++++++++- Packages/tests/Basic/UTF_SweepFormula_PSX.ipf | 31 +++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/Packages/MIES/MIES_SweepFormula_PSX.ipf b/Packages/MIES/MIES_SweepFormula_PSX.ipf index bf66b0ffe9..995895b270 100644 --- a/Packages/MIES/MIES_SweepFormula_PSX.ipf +++ b/Packages/MIES/MIES_SweepFormula_PSX.ipf @@ -1101,6 +1101,64 @@ static Function/WAVE PSX_GenerateSweepEquiv(WAVE selectData) return sweepEquiv End +/// @brief Collect all resolved ranges in allResolvedRanges together with a hash of the select data +Function PSX_CollectResolvedRanges(string graph, WAVE range, WAVE singleSelectData, WAVE allResolvedRanges, WAVE/T allSelectHashes) + + variable sweepNo, chanNr, chanType, numRows + + sweepNo = singleSelectData[0][%SWEEP] + chanNr = singleSelectData[0][%CHANNELNUMBER] + chanType = singleSelectData[0][%CHANNELTYPE] + + WAVE/Z numericalValues = BSP_GetLogbookWave(graph, LBT_LABNOTEBOOK, LBN_NUMERICAL_VALUES, sweepNumber = sweepNo) + WAVE/Z textualValues = BSP_GetLogbookWave(graph, LBT_LABNOTEBOOK, LBN_TEXTUAL_VALUES, sweepNumber = sweepNo) + SFH_ASSERT(WaveExists(textualValues) && WaveExists(numericalValues), "LBN not found for sweep " + num2istr(sweepNo)) + + [WAVE resolvedRanges, WAVE/T epochRangeNames] = SFH_GetNumericRangeFromEpoch(numericalValues, textualValues, range, sweepNo, chanType, chanNr) + ASSERT(DimSize(resolvedRanges, COLS) == 1, "psxStats does not support epoch wildcards") + + numRows = DimSize(allSelectHashes, ROWS) + Redimension/N=(numRows + 1) allSelectHashes + allSelectHashes[numRows] = WaveHash(singleSelectData, HASH_SHA2_256) + + Concatenate/NP {resolvedRanges}, allResolvedRanges + + if(DimSize(allResolvedRanges, COLS) == 0) + Redimension/N=(-1, 1) allResolvedRanges + endif +End + +/// @brief Check that the 2xN wave allResolvedRanges has only +/// non-intersecting ranges for the same select data hash +static Function PSX_CheckResolvedRanges(WAVE allResolvedRanges, WAVE/T allSelectHashes) + + string selectHash + variable numRows, numColumns, i, idx + + numRows = DimSize(allResolvedRanges, ROWS) + numColumns = DimSize(allResolvedRanges, COLS) + + ASSERT(numColumns == DimSize(allSelectHashes, ROWS), "Mismatched row sizes") + + for(selectHash : GetUniqueEntries(allSelectHashes)) + Make/N=(numRows, numColumns)/FREE work + + for(i = 0, idx = 0; i < numColumns; i += 1) + if(!cmpstr(selectHash, allSelectHashes[i])) + work[][idx] = allResolvedRanges[p][i] + idx += 1 + endif + endfor + + MatrixOp/FREE workTransposed = work^t + + ASSERT(idx > 0, "Invalid idx after searching") + Redimension/N=(idx, -1) workTransposed + + ASSERT(!AreIntervalsIntersecting(workTransposed), "Can't work with multiple intersecting ranges") + endfor +End + /// @brief Helper function of the `psxStats` operation static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE rangeParam, WAVE selectData, string prop, string stateAsStr, string postProc) @@ -1120,6 +1178,9 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE r numRanges = DimSize(allRanges, ROWS) WaveClear rangeParam + Make/D/FREE/N=(0) allResolvedRanges + Make/T/FREE/N=(0) allSelectHashes + WAVE/Z eventContainerFromResults = PSX_GetEventContainerFromResults(id) WAVE/Z eventContainer = PSX_GetEventContainer(graph, requestID = id) @@ -1161,8 +1222,9 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE r EnsureLargeEnoughWave(allEvents, indexShouldExist = idx) allEvents[idx] = events idx += 1 - WaveClear events + + PSX_CollectResolvedRanges(graph, range, singleSelectData, allResolvedRanges, allSelectHashes) endfor Redimension/N=(idx) allEvents @@ -1360,6 +1422,8 @@ static Function/WAVE PSX_OperationStatsImpl(string graph, string id, WAVE/WAVE r endfor endfor + PSX_CheckResolvedRanges(allResolvedRanges, allSelectHashes) + Redimension/N=(index) output // PSX_MouseEventSelection works for "nothing" and "log10" post processing diff --git a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf index 7223bed42c..fbec5a4147 100644 --- a/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula_PSX.ipf @@ -991,6 +991,37 @@ static Function StatsWorksWithResultsSpecialCases([STRUCT IUTF_mData &m]) endfor End +static Function StatsComplainsAboutIntersectingRanges() + + string browser, device, formulaGraph, comboKey, id + + [browser, device, formulaGraph] = CreateFakeDataBrowserWithSweepFormulaGraph() + + [WAVE range0, WAVE selectData] = GetFakeRangeAndSelectData() + + // 1st event wave + WAVE/Z psxEvent = GetEventWave(comboIndex = 0) + comboKey = MIES_PSX#PSX_GenerateComboKey(browser, selectData, range0) + id = "myID" + FillEventWave_IGNORE(psxEvent, id, comboKey) + + Duplicate/FREE range0, range1 + + // 2nd event wave where we shift the range + WAVE/Z psxEvent = CreateEventWaveInComboFolder_IGNORE(comboIndex = 1) + range1[] += 0.5 * (range0[1] - range0[0]) + comboKey = MIES_PSX#PSX_GenerateComboKey(browser, selectData, range1) + id = "myID" + FillEventWave_IGNORE(psxEvent, id, comboKey) + + try + MIES_PSX#PSX_OperationStatsImpl(browser, id, {range0, range1}, selectData, "amp", "all", "nothing") + FAIL() + catch + CHECK_NO_RTE() + endtry +End + Function/WAVE FakeSweepDataGeneratorPSXKernel(WAVE sweep, variable numChannels) variable pnts = 1001 From 1fd930b1c27236c70d8418aa02b179b50faed51e Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 6 Feb 2024 16:51:24 +0100 Subject: [PATCH 18/20] Tests: Add some more for ZapNaNs --- Packages/tests/Basic/UTF_Utils.ipf | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Packages/tests/Basic/UTF_Utils.ipf b/Packages/tests/Basic/UTF_Utils.ipf index 79567a6ffd..84c962e7ab 100644 --- a/Packages/tests/Basic/UTF_Utils.ipf +++ b/Packages/tests/Basic/UTF_Utils.ipf @@ -5060,12 +5060,33 @@ Function ZN_AllNaNToNull() WAVE/Z reduced = ZapNaNs(wv) CHECK_WAVE(reduced, NULL_WAVE) End + Function ZN_RemovesNaNs() Make/FREE wv = {NaN, inf, 1} WAVE/Z reduced = ZapNaNs(wv) CHECK_EQUAL_WAVES(reduced, {inf, 1}) End + +Function ZN_RemovesNaNs2D() + + // row is NaN + Make/FREE wv = {{NaN, inf}, { NaN, 1}} + WAVE/Z reduced = ZapNaNs(wv) + CHECK_EQUAL_WAVES(reduced, {inf, 1}) + + // column is NaN + Make/FREE wv = {{NaN, NaN}, {inf, 1}} + WAVE/Z reduced = ZapNaNs(wv) + CHECK_EQUAL_WAVES(reduced, {inf, 1}) + + // single point NaN only + Make/FREE wv = {{NaN, 2}, {inf, 1}} + WAVE/Z reduced = ZapNaNs(wv) + CHECK_EQUAL_WAVES(reduced, {2, inf, 1}) + +End + /// @} // BinarySearchText From 5c257e93da90ed96dc70fb7a2748a0bf08d152c4 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 6 Feb 2024 17:45:40 +0100 Subject: [PATCH 19/20] tools/check-code.sh: Fix typo in error message --- tools/check-code.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check-code.sh b/tools/check-code.sh index 4b1f750479..b2950cb053 100755 --- a/tools/check-code.sh +++ b/tools/check-code.sh @@ -146,7 +146,7 @@ matches=$(git grep $opts -e '^(?U)[[:space:]]*for\(.*\(.*\).*\)' --and --not -e if [[ -n "$matches" ]] then - echo "Function call in a foor loop statement check failed (use \`// NOLINT\` to suppress if appropriate):" + echo "Function call in a for loop statement check failed (use \`// NOLINT\` to suppress if appropriate):" echo "$matches" ret=1 fi From c826f60775eabf0c66b5dcb60c04dcb9e5f77b62 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Tue, 6 Feb 2024 17:46:16 +0100 Subject: [PATCH 20/20] tools/check-code.sh: Tighten function-call-in-for-loop check We only want to match classical for loops with for(<>; <>; <>) and not range-based for loops with for(<> : <>). --- tools/check-code.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check-code.sh b/tools/check-code.sh index b2950cb053..dd7b5877c4 100755 --- a/tools/check-code.sh +++ b/tools/check-code.sh @@ -142,7 +142,7 @@ then fi # U means non-greedy matching -matches=$(git grep $opts -e '^(?U)[[:space:]]*for\(.*\(.*\).*\)' --and --not -e '//[[:space:]]*NOLINT$' '*/MIES_*.ipf') +matches=$(git grep $opts -e '^(?U)[[:space:]]*for\(.*\(.*\).*\)' --and -e ';' --and --not -e ':' --and --not -e '//[[:space:]]*NOLINT$' '*/MIES_*.ipf') if [[ -n "$matches" ]] then