From c73c1609377ed199a86ea2e00452e5894765e26a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 22:10:33 +0000 Subject: [PATCH 1/2] Optimise binary data reading: skip redundant array copies and vectorise marker scanning Co-authored-by: simonge <5457667+simonge@users.noreply.github.com> Agent-Logs-Url: https://github.com/simonge/AcquPy/sessions/8ea991c6-e373-4261-8041-56699093856d --- Acqu.py | 14 ++++---- Mk1Format.py | 43 +++++++++++++------------ Mk2Format.py | 44 +++++++++++++++----------- __pycache__/Mk1Format.cpython-312.pyc | Bin 0 -> 4587 bytes __pycache__/Mk2Format.cpython-312.pyc | Bin 0 -> 5237 bytes 5 files changed, 55 insertions(+), 46 deletions(-) create mode 100644 __pycache__/Mk1Format.cpython-312.pyc create mode 100644 __pycache__/Mk2Format.cpython-312.pyc diff --git a/Acqu.py b/Acqu.py index 5c0b78d..35155be 100755 --- a/Acqu.py +++ b/Acqu.py @@ -182,8 +182,8 @@ def runFunction(function,minEvents=0,maxEvents=0): print(i, 'Bad data format or file end') return - # Set event limits - eventBuffers = np.append([1],np.where(dataBuffer==df.EEndEvent)[0]+1) + # Set event limits — np.concatenate avoids the extra copy that np.append makes + eventBuffers = np.concatenate(([1], np.where(dataBuffer==df.EEndEvent)[0]+1)) # Loop over events in buffer for j, (start,stop) in enumerate(zip(eventBuffers[0:-1],eventBuffers[1:])): @@ -260,7 +260,8 @@ def processEvent(eventData): # Get Epics Data in event if(df.EPICSExist): epicsBuffers, epicsIndices, epicsInfo = df.FillEPICSArray(eventArray) - eventArray = np.delete(eventArray,epicsIndices) + if len(epicsIndices): + eventArray = np.delete(eventArray, epicsIndices) if(len(epicsBuffers)): epicsEvent=1 @@ -268,17 +269,16 @@ def processEvent(eventData): #Separate scaler data out if(df.ScalersExist): scalerBuffers, scalerIndices = df.FillScalerArray(eventArray) - #if(len(scalerBuffers)): - eventArray = np.delete(eventArray,scalerIndices) + if len(scalerIndices): + eventArray = np.delete(eventArray, scalerIndices) if(len(scalerBuffers)): scalerEvent=1 # Check for errors errorIndices = df.CheckErrors(eventArray) - eventArray = np.delete(eventArray,errorIndices) if len(errorIndices): - #print(eventArray.view(np.uint16).reshape(-1,2) + eventArray = np.delete(eventArray, errorIndices) return 0 #adcArray = eventArray.view(np.uint16).reshape(-1,2) diff --git a/Mk1Format.py b/Mk1Format.py index de2fbf2..cdcc32e 100644 --- a/Mk1Format.py +++ b/Mk1Format.py @@ -89,26 +89,27 @@ def MakeScalerArray(moduleList,scalerList): def FillScalerArray(dataArray): - #np.set_printoptions(threshold=np.nan) - scalerHeaders = [] - scalerIndices = [] scalerLocations = np.where(dataArray==EScalerBuffer)[0] - if(len(scalerLocations)): - if(len(scalerLocations)!=len(NScalerBlock)): - print('Bad scaler block') - return [], [] - for i, index in enumerate(scalerLocations): - scalerHeaders += [index,index+1] - scalerIndices += range(index+2,index+2+NScalerBlock[i]) - scalerArray = np.column_stack((np.arange(NScaler),np.take(dataArray,scalerIndices))) - dataArray - return scalerArray, scalerIndices+scalerHeaders - return [], [] - + if not len(scalerLocations): + return [], [] + if len(scalerLocations) != len(NScalerBlock): + print('Bad scaler block') + return [], [] + + block_sizes = np.asarray(NScalerBlock, dtype=np.intp) + # Fully vectorised ragged range: replaces Python for-loop list builds + bases = np.repeat(scalerLocations + 2, block_sizes) + cum = np.concatenate([[0], np.cumsum(block_sizes[:-1])]) + offsets = np.arange(NScaler, dtype=np.intp) - np.repeat(cum, block_sizes) + scalerIndices = bases + offsets + + scalerHeaders = np.ravel(np.column_stack([scalerLocations, scalerLocations + 1])) + scalerArray = np.column_stack((np.arange(NScaler), dataArray[scalerIndices])) + return scalerArray, np.concatenate([scalerIndices, scalerHeaders]) + def CheckErrors(dataArray): - errorIndices = [] - for errorMark in np.where(dataArray==EReadError)[0]: - #print errorMark - #print np.frombuffer(dataArray[errorMark:], dtype=readError, count=1) - errorIndices += range(errorMark,errorMark+4) - return errorIndices + errorMarks = np.where(dataArray==EReadError)[0] + if not len(errorMarks): + return [] + # Vectorised: broadcast each error mark across offsets 0..3 + return (errorMarks[:, np.newaxis] + np.arange(4, dtype=np.intp)).ravel() diff --git a/Mk2Format.py b/Mk2Format.py index 921f08d..ff9105a 100644 --- a/Mk2Format.py +++ b/Mk2Format.py @@ -78,18 +78,26 @@ ('fTrailer', 'zW zoy8AbDX5i7_9a0ZiFOyMK_D%aDm9f>+V1nd*lVOlqevU6yzsQy@)g6+c4XlZn<$Rbr#aAT)<~#4~A>V0c5qz#9`r z-jp!$=7gEIBrH6eV0ml8%G(k)-kz}Yj)a4ECYKms$?uDzf>jv(Ik z2j&61hqOck=r;21L=%kLwzA}$=*j8~Umx*O%0kg!AFa~mRUn)&4 z4|W2_L{y#`kL9{xVi#laGhIQ27NUoJ7~cA zxCsG6VUi7Yh_E?f%*jG5iv!vg_U5GXQUnOl7jv29sP?X^txKfNUgCPh zxXh8Geu?iEqp6H8;Hnc~b2om>wh?x$HziA2U?$;OEK8E%V338yu;OWfkJ0Jgv=TQuhQUW7CNnl{KbjbEr0K4;eXbZYYYxS5USkz;kX z-nxxO$!?7*04S5l#u_zFeJlW26B%#fQEM&Q2i2W`chtO`L?3KJXxb>(jCIi&wWvmz zybHlzebQ_~Qz&ST;a1ScG;xVjOm;44SDe*i>`iBri3RqA)6SU^z!XyW| zoW+VUEo2n7s;*don;8K$iPrL9R!Wg%rEUNhr-{52Fszr(z%PJUna`u zE|o5SP`do9(z)SE=);Pi1ASlHI%3RwX3Q(rriI4&3%_rjcb2WM-g5rS2<#0A2PLat zP(q_xg4-dj=a5{Jjp@sPs*>cEYbmc!Yc=Jk8JVCwZS~}qOe1IniH11ZOj_s3Bof-R zBpUCAMDfU^R%zpF-*E;!R(;mv-K=nGKI`c^R!e2gFG3HETB<{V);(@gPEbUqJQx-X zH4CH8qM`0nqj+Y7(P=bk{Q3yRhj??n?CSTBX_L;90Q^f$CiqI*|GGY%zs*)>s;?^# zcfM(J!B{Yp^-f!KmZX+nYC7qTmqaivFtBeH;-|hg?Ubk4pSIOWe{u&je+5*q5GWIC z*)nY{San`DIirH5z}6!pxf*0+m=r+X+spB7Dq^%zjJVGzW?qJm=~D`1RlqAE`pA<;bSBu58UO_>RMIgy8zq- zJkrmhj*94sieK|n5BHUKbyxg{mwaXaQNSsOd*R|$L`Qq?OzAK3@(Mg-CpU6mfg|fspY1Y`BSq4#pshJ|J>=>(+fj)hwlt8W|wZ3 z&JC4M43+l9D?86unl2QhD-c|0Hl)7q;ro?W-h4Fm?ap%dK&gGO(r~W$?y|$bV7TkP z<6d+wRnj#C3orATZ{6(VEO2R9R4ZCbjf1*#Z0xbU~}GwCs@~xD&#~ zEsJSgDQrf#&fQ2!YN7NIqqD@wfcs%k>~MX|;wXp5C5cpQTbr7>j_|0E9H)LHOfohD zkMtgNMYQ5;oD0kb=7aYV<CD&T+QvI9qY_mrVWil!I-GIg^{1oT3X-Oc}Vj zEB4+gSs0+@sMsRpuZ_S4kdX`@xh4Qy*dr>C00W01Li|2-iZc=!JQI$qB2XV3osy`f zD?-Y*KIQl%VMJh)py0h~N$WZ4?MTP5Q_s{uajNAl(HZU`Z)UMTJyIeL*rK?EYjQZ7 z%gFFwfC0#^&_5h?Vh8Q>n1i1_48W(xqTRyCJGI*xrELOPu}b3FLGLGvCYLHq@P!`N zeMEPZ9_BOaF?9*YG0A1}1(#lje$B`*%=f5amGu=(t1frZzUpl)+LqO>y=W(ti*%H7 zRt3OYZEONTV^axvYdt6d*STgvZvV{Crx$NsTu0u(kqzW^A1$(LuOhdn$S!*uzz`_M zs%x97Rcbl3+|muP1m4;}3|#o^Iu}%UhzP+ELmk!q|468}Tt_xyV*?xen!gqk*p!+N zS6puuZL8kEZNr>(*1C=?I}U6hi|@^%ZOw}sTZ^7`lbzYK9%x`rF&lf$Oq8L!`Csda B6>0zg literal 0 HcmV?d00001 diff --git a/__pycache__/Mk2Format.cpython-312.pyc b/__pycache__/Mk2Format.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36bcd4506eee77e6473d6d676b81ba9d33e3f872 GIT binary patch literal 5237 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 bqg#k}bYfEt)LCltkc&F9 Date: Mon, 23 Mar 2026 22:11:20 +0000 Subject: [PATCH 2/2] Add .gitignore and remove committed pycache files Co-authored-by: simonge <5457667+simonge@users.noreply.github.com> Agent-Logs-Url: https://github.com/simonge/AcquPy/sessions/8ea991c6-e373-4261-8041-56699093856d --- .gitignore | 1 + __pycache__/Mk1Format.cpython-312.pyc | Bin 4587 -> 0 bytes __pycache__/Mk2Format.cpython-312.pyc | Bin 5237 -> 0 bytes 3 files changed, 1 insertion(+) create mode 100644 .gitignore delete mode 100644 __pycache__/Mk1Format.cpython-312.pyc delete mode 100644 __pycache__/Mk2Format.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/__pycache__/Mk1Format.cpython-312.pyc b/__pycache__/Mk1Format.cpython-312.pyc deleted file mode 100644 index f1d5114f59eb052b73c3eed7a65e0db63204269f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4587 zcmcInU2GFq7QW-Z@&8W}94EjM(z3zW zoy8AbDX5i7_9a0ZiFOyMK_D%aDm9f>+V1nd*lVOlqevU6yzsQy@)g6+c4XlZn<$Rbr#aAT)<~#4~A>V0c5qz#9`r z-jp!$=7gEIBrH6eV0ml8%G(k)-kz}Yj)a4ECYKms$?uDzf>jv(Ik z2j&61hqOck=r;21L=%kLwzA}$=*j8~Umx*O%0kg!AFa~mRUn)&4 z4|W2_L{y#`kL9{xVi#laGhIQ27NUoJ7~cA zxCsG6VUi7Yh_E?f%*jG5iv!vg_U5GXQUnOl7jv29sP?X^txKfNUgCPh zxXh8Geu?iEqp6H8;Hnc~b2om>wh?x$HziA2U?$;OEK8E%V338yu;OWfkJ0Jgv=TQuhQUW7CNnl{KbjbEr0K4;eXbZYYYxS5USkz;kX z-nxxO$!?7*04S5l#u_zFeJlW26B%#fQEM&Q2i2W`chtO`L?3KJXxb>(jCIi&wWvmz zybHlzebQ_~Qz&ST;a1ScG;xVjOm;44SDe*i>`iBri3RqA)6SU^z!XyW| zoW+VUEo2n7s;*don;8K$iPrL9R!Wg%rEUNhr-{52Fszr(z%PJUna`u zE|o5SP`do9(z)SE=);Pi1ASlHI%3RwX3Q(rriI4&3%_rjcb2WM-g5rS2<#0A2PLat zP(q_xg4-dj=a5{Jjp@sPs*>cEYbmc!Yc=Jk8JVCwZS~}qOe1IniH11ZOj_s3Bof-R zBpUCAMDfU^R%zpF-*E;!R(;mv-K=nGKI`c^R!e2gFG3HETB<{V);(@gPEbUqJQx-X zH4CH8qM`0nqj+Y7(P=bk{Q3yRhj??n?CSTBX_L;90Q^f$CiqI*|GGY%zs*)>s;?^# zcfM(J!B{Yp^-f!KmZX+nYC7qTmqaivFtBeH;-|hg?Ubk4pSIOWe{u&je+5*q5GWIC z*)nY{San`DIirH5z}6!pxf*0+m=r+X+spB7Dq^%zjJVGzW?qJm=~D`1RlqAE`pA<;bSBu58UO_>RMIgy8zq- zJkrmhj*94sieK|n5BHUKbyxg{mwaXaQNSsOd*R|$L`Qq?OzAK3@(Mg-CpU6mfg|fspY1Y`BSq4#pshJ|J>=>(+fj)hwlt8W|wZ3 z&JC4M43+l9D?86unl2QhD-c|0Hl)7q;ro?W-h4Fm?ap%dK&gGO(r~W$?y|$bV7TkP z<6d+wRnj#C3orATZ{6(VEO2R9R4ZCbjf1*#Z0xbU~}GwCs@~xD&#~ zEsJSgDQrf#&fQ2!YN7NIqqD@wfcs%k>~MX|;wXp5C5cpQTbr7>j_|0E9H)LHOfohD zkMtgNMYQ5;oD0kb=7aYV<CD&T+QvI9qY_mrVWil!I-GIg^{1oT3X-Oc}Vj zEB4+gSs0+@sMsRpuZ_S4kdX`@xh4Qy*dr>C00W01Li|2-iZc=!JQI$qB2XV3osy`f zD?-Y*KIQl%VMJh)py0h~N$WZ4?MTP5Q_s{uajNAl(HZU`Z)UMTJyIeL*rK?EYjQZ7 z%gFFwfC0#^&_5h?Vh8Q>n1i1_48W(xqTRyCJGI*xrELOPu}b3FLGLGvCYLHq@P!`N zeMEPZ9_BOaF?9*YG0A1}1(#lje$B`*%=f5amGu=(t1frZzUpl)+LqO>y=W(ti*%H7 zRt3OYZEONTV^axvYdt6d*STgvZvV{Crx$NsTu0u(kqzW^A1$(LuOhdn$S!*uzz`_M zs%x97Rcbl3+|muP1m4;}3|#o^Iu}%UhzP+ELmk!q|468}Tt_xyV*?xen!gqk*p!+N zS6puuZL8kEZNr>(*1C=?I}U6hi|@^%ZOw}sTZ^7`lbzYK9%x`rF&lf$Oq8L!`Csda B6>0zg diff --git a/__pycache__/Mk2Format.cpython-312.pyc b/__pycache__/Mk2Format.cpython-312.pyc deleted file mode 100644 index 36bcd4506eee77e6473d6d676b81ba9d33e3f872..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5237 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 bqg#k}bYfEt)LCltkc&F9