Gi-z/CSIKit

Unable to get example version of CSIKit library working

Closed this issue · 4 comments

Hi there!

Thank you so much for your fantastic library, it is a really unique resource within the CSI community.

I have been able to use CSIKit from the command line just fine, but am struggling to get the library version working using the generic example. Would you be able to assist please? I have come across a couple of issues:

1. Unable to import CSIKit.filters

When attempting to import CSIKit.filters using the example line from CSIKit.filters import lowpass, hampel, running_mean I get the error: ImportError: cannot import name 'lowpass' from 'CSIKit.filters' (/usr/local/lib/python3.9/site-packages/CSIKit/filters/__init__.py).

I have been able to work around this by running from CSIKit.util.filters import lowpass, hampel, running_mean, but I'm not 100% if that is valid or if I'm just avoiding the issue.

2. Unable to process frames

Running for x in no_frames: using the example code gives me the error: TypeError: 'int' object is not iterable. This is even whilst using the example data and trying out both csv and pcap data. I'm guessing I may not be importing a required third party library but I'm not sure.

Setup

  • Python 3.9.7
  • macOS
import numpy as np 
from CSIKit.util.filters import lowpass, hampel, running_mean
from CSIKit.reader import get_reader
from CSIKit.util import csitools

my_reader = get_reader("/MY_PATH/csikit/CSIKit/data/esp32/example_data.csv")
csi_data = my_reader.read_file("/MY_PATH/csikit/CSIKit/data/esp32/example_data.csv", scaled=True)
csi_matrix, no_frames, no_subcarriers = csitools.get_CSI(csi_data, metric="amplitude", squeeze_output=True)
csi_matrix_trans = np.transpose(csi_matrix)

#This example assumes CSI data is sampled at ~100Hz.
#In this example, we apply (sequentially):
#  - a lowpass filter to isolate frequencies below 10Hz (order = 5)
#  - a hampel filter to reduce high frequency noise (window size = 10, significance = 3)
#  - a running mean filter for smoothing (window size = 10)

for x in no_frames:
  csi_matrix_trans[x] = lowpass(csi_matrix_trans[x], 10, 100, 5)
  csi_matrix_trans[x] = hampel(csi_matrix_trans[x], 10, 3)
  csi_matrix_trans[x] = running_mean(csi_matrix_trans[x], 10)

Thanks so much for your help!

Gi-z commented

Hi, apologies for not replying to your last issue. Some issues require a bit more explanation than others, and I've been pretty busy this last couple of months. For reference, the example files provided within CSIKit are for testing the parsers. If you're looking to train models using CSI data, you need to look for a relevant dataset for your planned application. There's a list available here, all of which should be parseable with CSIKit.

  1. Unable to import CSIKit.filters.
    CSIKit.filters is a newer, separate module which is where I'm moving all the filters for future versions. However, the original implementations are also available in their original place CSIKit.util.filters. For the examples, you can see I left the import statement using the older version.

  2. Unable to process frames
    no_frames is just an integer. If you want to run a for loop on that many iterations, you need to wrap the integer in a range call such as range(no_frames). That's a typo in the example, and I'll get that sorted just now.

Thanks so much @Gi-z, super super useful context! Not at all, really appreciate your response.

I have now been able to import and utilize the filters, thank you! The only thing I'm not sure about is what the purpose is of transposing the CSI matrix prior to applying the filtering. Surely the columns of the CSI already represent the frames, so I don't understand why we would want to switch the columns and rows (doing so generates an out of bounds index exception during the for loop).

Gi-z commented

You know, it's clear to me that the example has been incorrect for quite a while now. Thanks for pointing this out, and I'm going to resolve it right away.

There is a point in transposing the CSI matrix. When it is returned from csitools.get_CSI, its shape is (no_frames, no_subcarriers, no_rx_antennas, no_tx_antennas). When setting squeeze_output=True, the matrix is returned with any singular dimensions removed (such as cases like this one, where there is 1 rx/tx pairing). The matrix's shape is now (no_frames, no_subcarriers). If we want to filter this data, the point would be to consider each subcarrier stream as one set of timestream values, as we are viewing the CSI value at each subcarrier sampled once for each frame we have. To filter these, we can transpose the matrix so we now observe it as (no_subcarriers, no_frames). This (should) make it easier to follow that we are filtering the matrix along the first axis. Regrettably this has not come through clearly, so I'll try to add better explanation.

Truthfully, I've been planning to properly document the project for quite a while, however deciding which docs framework to use has been difficult. Hopefully this gives you a clearer introduction of how to interact with CSI data.

This makes a ton of sense, thanks SO much @Gi-z for your fantastic explanation!