NaNs during 10 minutes recordings

I use Python to run 10 minutes power recordings with Joulescope on Windows 10 computer, however I get a lot of NaNs in the recorded data. The script is based on the StreamProcess, and I run it for 10 minutes with sampling frequency 1 kHz and run it for 10 minutes. I only get the power data and save them in a numpyzip. Do you have any idea on how to avoid packet loss, or loss of data, while recording data?

Couldn’t find a better way to share the code, but I run it as this:

    class StreamProcess:

         def __init__(self,field,duration,sampling_frequency):
               self.fs=sampling_frequency
               self.f=field
               samples=int(duration*sampling_frequency)
               self.chunk_size = 10000
               length = ((samples + self.chunk_size - 1) // self.chunk_size) * self.chunk_size
               self._buffer = np.empty((length, 1), dtype=np.float32)
               self._buffer[:] = 0.0  
               self.idx = 0    
         def __str__(self):
               return 'StreamProcess'
          @property
           def data(self):
                 return self._buffer[:self.idx,:]
  
          def stream_notify(self, stream_buffer):
                 start_id, end_id = stream_buffer.sample_id_range
                 idx_next = self.idx + self.chunk_size
        
                 while idx_next <= end_id and idx_next <= len(self._buffer):
                     # Get and store power measurement
                     data = stream_buffer.samples_get(self.idx, idx_next, fields=[self.f])
                     self._buffer[self.idx:idx_next, 0] = data['signals'][self.f]['value']
                     self.idx = idx_next
                     idx_next += self.chunk_size

           def export_data(self, outfolder, filename):
        
                 if not os.path.isdir(outfolder):
                     os.makedirs(outfolder)
        
                 fpath=outfolder+'/'+filename+'.npz'
                 print("Saving capture to file")
                 self.time = np.arange(0,len(self.data)/self.fs,1./self.fs)
                 data_type=self.f
                 np.savez(fpath,**{data_type: self.data})
                 print("Capture saved")

           def plot_data(self):
                 print("---- Plotting captured data ----")
                 print("Close plot to continue")
                 data=self.data
                 plt.plot(self.time,data)
                 plt.xlabel('Time (s)')
                 plt.ylabel(self.f)
                 plt.title("Captured raw data")

                 plt.show()


       def capture(case,config):
                 _quit = False

                 def on_stop(*args, **kwargs):
                     nonlocal _quit
                     _quit = True
    
                 duration =config['duration']
                 f_s = config['sampling_frequency']
                 field=config['measurement']

             try:
                 device = joulescope.scan_require_one(config='auto')
                 print("JouleScope connected")
             except RuntimeError:
                 print("Connect a Joulescope and restart")
                 sys.exit(1)

             device.parameter_set('buffer_duration',2)
             stream_process= StreamProcess(field,duration,f_s)

             signal.signal(signal.SIGINT, on_stop)
             print("Start wait of:",config['wait'])
             time.sleep(config['wait'])
          
            with device:
        
                 device.stream_process_register(stream_process)
                 device.start(stop_fn=on_stop, duration=duration)
                 print("Capture started")
            while not _quit:
                     time.sleep(0.1)
  
             device.stop()  # for CTRL-C handling (safe for duplicate calls)
             print("Capture finished")

             stream_process.export_data(config['resultfolder'],case)
             stream_process.plot_data()

Best regards
Anders

Hi @Andersbg and welcome to the Joulescope forum!

You do have to be very careful in the stream_notify() callback to process the data quickly to avoid data loss. Based upon your configuration, this code will store 4 bytes/sample * 1000 samples/second * 60 seconds/minute * 10 minutes = 2,400,000 Bytes = 2.4 MB, which is perfectly suitable for buffering in RAM.

Normally, recording at 1 kHz is pretty easy and your PC can easily keep up. However, it appears that the code is not using device.parameter_set to configure sampling_frequency, so it is capturing at the full 2 Msps. When you start streaming, your Joulescope does often skip samples as the PC and Joulescope start asynchronously. Since you thought you were capturing 1000 kHz but you were actually capture 2 Msps, you saw a LOT more NaNs than you would expect. In fact, your entire buffer was filled in about 0.25 seconds.

Here are my changes:

  1. Configured ‘sampling_frequency’ parameter
  2. Allowed StreamProcess to signal the end of the capture, not a fixed duration
  3. Made it a working example, not a code snippet
  4. Added a progress bar
  5. Printed number of NaNs & samples at the end of the capture
  6. Added copyright info

You can get the updated code as a GitHub Gist here.

Running this on my PC shows:

c:\projects\Jetperch\Joulescope\support\2021\20211105_capture>python capture_fs_cbk.py
JouleScope connected
Start wait of: 0
Capture started
Capture finished=============================================] 100.0% ...
Found 0 NaNs in 600000 samples
Saving capture to file
Capture saved
---- Plotting captured data ----
Close plot to continue

Does this update work for you?

Thank you for the response!
I’ll give it a try, but your reasoning sounds convincing so I’ll expect it to work :slight_smile:

Best regards
Anders

1 Like

Hi again,

I have tested the script now, however the result was unfortunately not as hoped. I still get NaNs, and according to the script I got 22183 NaNs in 60000 Samples. (I tested for 1 minute with fs=1kHz).

Do you have any idea on how to debug this further?

It looks like you are running this code as part of a larger system. Did you run the Gist exactly as I provided with no modifications? What did it report?

Does the Joulescope UI work on this machine? You should see that the USB indicator stays on as solid green, like this:

Hi,
I use Thinkpad running windows10, and I run the scripts using python 3.8.

I’ve also ran the gist exactly as provided and it shows similar performance. I just tried it again, and it reported:
JouleScope connected Start wait of: 0 Capture started Capture finished=============================================] 100.0% … Found 12512 NaNs in 600000 samples Saving capture to file Capture saved ---- Plotting captured data ---- Close plot to continue

Running the UI shows a solid green for the USB indicator. However, I’ve one behavior that I wonder if is expected behavior: To run the scripts, I first have to connect the Joulescope and then open the UI and close it before I can run the scripts. If the UI is not ran, it does not find the device. And if I try to run scripts while the UI is runnig, I can’t connect.

Best regards
Anders

Hi @Andersbg - Ok, so your computer is good since it works with the Joulescope UI. It sounds like something is up with your Python installation. You should definitely be able to run the script without first running the UI. I just double-checked on my machine, and the capture_fs_cbk.py script works great on my machine without first running the UI, and zero NaNs:

C:\projects\Jetperch\JouleScope\support\2021\20211105_capture>python capture_fs_cbk.py
JouleScope connected
Start wait of: 0
Capture started
Capture finished=============================================] 100.0% …
Found 0 NaNs in 600000 samples
Saving capture to file
Capture saved
---- Plotting captured data ----
Close plot to continue

Note that running the script while connected to the device through the UI is not expected to work. Only one program may open a Joulescope at a time.

Q1) What python distribution are you running?

Q2) What is the output for python -VV (note that is two V, not W).

If you shut down the Joulescope UI and all Joulescope scripts, unplug your Joulescope, plug it back it, and run:

python -m joulescope info

Q3) What do you see?

On my machine with one Joulescope connected, I see:

C:\projects\Jetperch\JouleScope\support\2021\20211105_capture>python -m joulescope info
System information
Python: 3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]
Platform: Windows-10-10.0.22000-SP0 (win32)
Processor: Intel64 Family 6 Model 158 Stepping 13, GenuineIntel
executable: c:\bin\python3_9_7\python.exe
frozen: False

joulescope version: 0.9.7
Found 1 connected Joulescope:
Joulescope:000147 ctl=1.3.4 sensor=

Could you try using stock 64-bit python 3.9? Here’s how:

  1. Download the latest python 3.9.8 64-bit.
  2. Install it to your desired location. I tend to use something like c:\bin\python3_9_8. Let’s call this {path}.
  3. Open a command window.
  4. set PATH={path};{path}\Scripts
    but replace {path} with your install path from step (2)
  5. pip install joulescope

Q4) Now, what do you see when you run python -m joulescope info?

You should now be able to run the script:

python capture_fs_cbk.py

Q5) Does this change anything for you?

To answer the first questions, I run Python 3.8.10 using a virtual environment.
The print out shows the following information:

C:\Users\AndersBarstadGjeruld>python -VV

Python 3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]  

and

C:\Users\AndersBarstadGjeruld>python -m joulescope info
System information
Python: 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] Platform: Windows-10-10.0.19041-SP0 (win32) Processor: Intel64 Family 6 Model 142 Stepping 12, GenuineIntel
executable: C:\Users\AndersBarstadGjeruld\Documents\python_38\Scripts\python.exe
frozen: False

joulescope version: 0.9.7
Found 1 connected Joulescope:
Joulescope:003356 ctl=1.3.4 sensor=1.3.4

I will have to try with python 3.9 later :slight_smile:

Hi @Andersbg - Make sure you unplug and replug the Joulescope before running the python -m joulescope info. Since you mentioned that you need to run the UI first, I want to see what this reports on a freshly connected Joulescope. If you had unplugged and replugged, I would expect sensor=1.3.4 to be sensor= since the sensor should not yet be turned on.

yes, you’re right. Got this info now:
C:\Users\AndersBarstadGjeruld>python -m joulescope info
System information
Python: 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] Platform: Windows-10-10.0.19041-SP0 (win32) Processor: Intel64 Family 6 Model 142 Stepping 12, GenuineIntel executable: C:\Users\AndersBarstadGjeruld\Documents\python_38\Scripts\python.exe frozen: False joulescope version: 0.9.7 Found 1 connected Joulescope: Joulescope:003356 ctl=1.3.4 sensor=

Well, the python -m joulescope info looks good to me. You mentioned that

What is the exact error you see? When you copy your text into the response, please highlight it and click the “preformatted text button” in the editor, which looks like this:
image

I have tried to install python 3.9.8, both as virtual environment and a fresh re-install on my computer. However, I still get NaNs in the recordings. I get the following info on set-up:

C:\Users\AndersBarstadGjeruld>python -VV                                                                                                                                                                                                
Python 3.9.8 (tags/v3.9.8:bb3fdcf, Nov  5 2021, 20:48:33) [MSC v.1929 64 bit (AMD64)]

and
C:\Users\AndersBarstadGjeruld>python -m joulescope info Error processing line 7 of C:\Users\AndersBarstadGjeruld\AppData\Roaming\Python\Python39\site-packages\pywin32.pth: Traceback (most recent call last): File "C:\Users\AndersBarstadGjeruld\AppData\Local\Programs\Python\Python39\lib\site.py", line 169, in addpackage exec(line) File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'pywin32_bootstrap' Remainder of file ignored System information Python: 3.9.8 (tags/v3.9.8:bb3fdcf, Nov 5 2021, 20:48:33) [MSC v.1929 64 bit (AMD64)] Platform: Windows-10-10.0.19041 (win32) Processor: Intel64 Family 6 Model 142 Stepping 12, GenuineIntel executable: C:\Users\AndersBarstadGjeruld\AppData\Local\Programs\Python\Python39\python.exe frozen: False joulescope version: 0.9.7 Found 1 connected Joulescope: Joulescope:003356 ctl=1.3.4 sensor=

As you can see, it complains on a module called pywin32_bootstrap. I am not sure of the cause nor the effect it has on the Joulsescope. I have not been able to resolve the issue.

I could not reproduce the problem with running joulescope from cmd-line, so it might have been a problem with my first scropt.

The pywin32 error is resolved, but I still get NaNs…

Let’s step back. NaNs come from sample drops. Sample drops happen:

  1. At streaming startup, when the host PC doesn’t initially keep up. Most computers are able to start and continue cleanly. However, other computers may get a group of samples, drop a group, and then continue normally.
  2. During capture, when the host PC does not service USB in a timely manner. Troubleshooting this leads to a whole bunch of PC performance issues including virtual memory, other greedy programs that may be running, power modes, etc.

In the 1000 kHz mode, we use these filters. With respect to 2 MHz, the total filter length is

66 * 68 * 108 * 66 samples = 31990464 samples (2 MHz) = 16 seconds

At 1000 kHz, this corresponds to 15995 samples. Looking back up in the thread, you observed
22183 and 12512 NaNs. This is close enough that I suspect (1) may be the cause on this machine.

Assuming that (1) is the cause, the easiest workaround is to ignore some duration of streaming data before we start adding the samples to the buffer. The pyjoulescope downsampling implementation always applies the downsampling filters, and I found that calling reset while streaming does not work. However, we can get around this with the script by streaming full-rate data to the script and applying the downsampling filter in the script.

Can you try this Gist? This code discards the first 0.5 seconds of data, downsamples to 1000 kHz, and records 10 minutes.

Do you still get NaNs?

Thank you, I will give it a try.
I also tried my script at a different pc, and there it seems to not drop any frames as long as I keep the cmd prompt open and don’t click on anything outside the cmd prompt window.

If you are using a laptop, you may have to adjust the power options. Some Windows laptops are VERY aggressive at throttling back USB & CPU service. If you go to your computer’s Power Options settings, select Change advanced power settings.

Settings seem to vary based upon the computer, but here are some settings you may want to change:

  • PCI Express → Link State Power Management = Off
  • USB settings → USB selective suspend setting = Disabled
  • Processor power management → Minimum processor state = 100% (not 0%)

And if you are capturing overnight, you definitely need to keep your computer from going to sleep or performing updates. I recommend disconnecting from the network for any critical tests, just to be sure.

1 Like

I still get NaNs with the new script. I’ll try to change the settings as you recommend, cause I agree that it seems to be a problem on the laptop side.

1 Like

Changing the power settings on my laptop seems to have solved the problem! :smiley:
Thanks a lot for the help.

I just have one question: When running the joulescope at 1Khz, the sampling frequency is still 2 MHz, right? How does the low pass filtering work, will the 1 kHz data be the average of the samples in-between? (ie. each 1 kHz sample is the average of 2k samples?)

1 Like

Great to hear that changing the laptop power settings fixed the sample drops!

Yes. Your Joulescope always samples at 2 Msps.

Conceptually, the software applies a low-pass filter and then decimates (takes every Nth sample and discards the rest). In practice, it actually performs several steps of filtering then downsampling, which reduces the overall computational and storage requirements.

The idea behind filtering is to prevent aliasing of higher frequency content down into the new reduced frequency band. The ideal filter is a sharp rectangle, but this is not achievable with real filter implementations. Taking the average is a low-pass filter (moving average filter), but it has many non-ideal frequency characteristics. See this StackExchange post. We have designed our downsampling filters to match the Joulescope JS110 performance criteria. You can see the 1000 kHz downsampling filter coefficients here.

1 Like