How to accurately sample in python

1.1k views Asked by At

At work, I have a need: to do sampling every 0.08 seconds in 10 seconds.

I use while loop but it fails.

import time
start_t =time.time()
while time.time() -start_t <=10:
    if float(time.time() -start_t) % float(0.08) == 0:
       """do sample record""

finally, I got no data at all, I think the if float(time.time() -start_t) % float(0.08) == 0: does not work.

I am confused how to set the condition to enter the sampling code.

4

There are 4 answers

1
Maximilian Janisch On

The easiest way is to use time.sleep:

from time import sleep
for i in range(125):
    """do sample record"""
    sleep(0.08)

You probably get no data because you collect the time only at discrete moments. In these moments, they will never be perfect multiples of 0.08.

3
CuCaRot On

You use float number divide by float number, and time.time() will return a long decimal number so you get no data because your result always 0.00001234 or something like that. I think you should use round to get 2 decimal number

temp = time.time()-start_t
if round(temp,2) % 0.08 == 0:
   """do sample record"""

However, this script will return about 27000 result in 10 second. Because you will have 0.08, 0.081,0.082,etc and they all do your recording work.

So I think you should work with Maximilian Janisch solution (using sleep function) is better. I just want to explain why you reach no solution.

Hope this helpful!


EPILOGUE :

With all due respect, the proposed code is awfully dangerous & mis-leading.
Just test how naive it gets :
8.00 % 0.08 yields 0.07999999999999984 that is by no means == 0,
while the if-condition ought be served & sample taken, if it were not for the (known) trap in real-numbers IEEE-754 handling.
So as to see the scope of the disaster, try :
sum( [ round( i * 0.08, 2 ) % 0.08 == 0 for i in range( 126 ) ] )
+ compare it with 125-samples the task was defined above to acquire.
Get 8 samples instead of 125 @ regular, 12.5 [Hz] sampling
is nowhere near a solution! – user3666197 22 hours ago


@user3666197 wow, a very clear explanation, I think I should delete this answer to avoid misleading in the future. Thank you! – Toby 5 hours ago


Better do not remove the Answer, as it documents what shall never be done,
which is of a specific value to the Community
- best to mention the rationale, not to use this kind of approaches in any real-life system.

The overall lesson is positive
- all learned a next step towards better system designs. I wish you all the best, man! – user3666197 4 mins ago

10
Marc On

this will probably never exactly be true as checking for equality with floats like this will need to be very precise.

try doing something like:

start_t =time.time()
looped_t = start_t
while time.time() - start_t <= 10:
    if time.time() - looped_t >= 0.08:
       looped_t = time.time()
       """do sample record""

The sleep answer from Maximillian is fine as well, except if your sampling takes a significant amount of time (several hundreds of a second) then you will not stay near the 10 second requirement.

It also depends on what you prioritize as this method will at most provide 124 samples instead of the exact 125 you would expect (and do get with the sleep function).

0
user3666197 On

Q : "How to accurately sample in python"

At work ( Chongqing ),
I have a need: to do sampling every 0.08 seconds in 10 seconds.

Given the is to be used, the such precise sampling will need a pair of signal.signal()-handlers on the unix-systems,

import signal

#------------------------------------------------------------------
# DEFINE HANDLER, responsible for a NON-BLOCKING data-acquisition
#------------------------------------------------------------------
def aSIG_HANDLER( aSigNUM, aPythonStackFRAME ):
    ... collect data ...
    return

#------------------------------------------------------------------    
# SET THE SIGNAL->HANDLER MAPPING
#------------------------------------------------------------------
signal.signal( signal.SIGALM, aSIG_HANDLER )

#------------------------------------------------------------------
# SET THE INTERVAL OF SIGNAL-ACTIVATIONS
#------------------------------------------------------------------
signal.setitimer( signal.ITIMER_REAL, seconds  = 0,      # NOW WAIT ZERO-SECONDS
                                      interval = 0.08    #     FIRE EACH 80 [ms]
                  )

#------------------------------------------------------------------
# ... more or less accurately wait for 10 seconds, doing NOP-s ...
#------------------------------------------------------------------

#----------------------------------------------------------------
# AFTER 10 [s] turn off the signal.ITIMER_REAL activated launcher
#----------------------------------------------------------------
signal.setitimer( signal.ITIMER_REAL, seconds  = 0,      # NOW WAIT ZERO-SECONDS
                                      interval = 0.0     #     STOP SENDING SIGALM-s
                  )

or,
for a Windows-based systems,
there is a chance to tweak ( and fine-tune up to a self-correcting, i.e. non-drifting ) Tkinter-based sampler as shown in this answer.

class App():

    def __init__( self ):
        self.root = tk.Tk()
        self.label = tk.Label( text = "init" )
        self.label.pack()
        self.sampler_get_one()          # inital call to set a scheduled sampler
        self.root.lower()               # hide the Tk-window from GUI-layout
        self.root.mainloop()

    def sampler_get_one( self ):
        # \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
        #
        # DEMO to show real plasticity of the Tkinter scheduler timing(s)
        #
        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
        ... review a drift of the activation + adapt the actual delay for a next .after()
        #    SET .after()  vv-----------# re-calculate this value to adapt/avoid drifting
        self.root.after(   80,          # re-instate a next scheduled call,
                         self.sampler_get_one
                         )              # .after a given ( self-corrected ) delay in [ms]
        #-------------------------------#-NOW--------------------------------------------
        ... acquire ... data ...        # best in a non-blocking manner & leave ASAP