Re: SNR calculations

2024-04-01 Thread Al Grant
Hi Fons

I am detecting beeps on CW that happen about 1/second with a pulse length
of 18ms. At a decimated sampling rate of 10240 this equates to 189 samples.

I found that after the smoothing filter (orange line) I could calculate the
orange beep length and divide by 2 to get close to blue beep length.

The blue line as you guessed is after a filter and abs.

Al

On Mon, 1 Apr 2024, 22:21 Fons Adriaensen,  wrote:

> On Mon, Apr 01, 2024 at 01:50:32PM +1300, Al Grant wrote:
>
> > I have a block of code in my wildlife tracker that detects high/low beeps
> > in a frequency.
>
> It's not clear what the signal (blue trace in your png) actually
> represents and what exactly you are trying to do.
>
> - Detect any signal that exceeds the noise level by some margin ?
>
>   In that case S/N ratio makes sense.
>
> - Detect a signal at a particular frequency ?
>
>   In that case S/N isn't relevant, S/No (signal to noise density
>   ratio) is the relevant metric.
>
> So until you provide a bit more detail, just a few comments.
>
> I assume the signal (blue trace) is the absolute value or
> square of something - it must be nonnegative for what you
> do to make sense.
>
> >  samples = signal.convolve(samples, [1]*189, 'same')/189
>
> So you are convolving with a rectangular pulse in the time domain,
> which becomes a sinc in the frequency domain. This is not a very
> good choice. If you are convolving anyway (there may be simpler
> solutions), better use a shape that transforms to a good lowpass
> filter. This would be a truncated or windowed sinc in the time
> domain.
>
> > 189 is the number of expected samples
>
> What do you mean by 'expected' ? Why 189 ?
>
> > 1. Beep length calculations are off because of the extended
> > length of "high samples"
>
> Look at the points in your plot where the orange line intersects
> the blue pulse. This happens at half the peak value of the orange
> trace. A threshold at that level will give you a good estimate of
> the pulse lenght.
>
> > 2. SNR calculations are considerably higher (and I am not sure
> > which SNR is "correct")
>
> Which may be because S/N isn't the relevant parameter (see above).
> You need to provide more details about what comes before (how
> you get the blue signal).
>
> Ciao,
>
> --
> FA
>
>
>
>


SNR calculations

2024-03-31 Thread Al Grant
Hi,

I have a block of code in my wildlife tracker that detects high/low beeps
in a frequency. When the signal is weak the smoothing is particularly
helpful at detecting real beeps from the background - but it causing me
issues with calculating beep length and SNR calculations - please let me
explain:

This is my smoothing:

# smoothing
samples = signal.convolve(samples, [1]*189, 'same')/189

189 is the number of expected samples and after smoothing looks like this (
triangular orange line / blue line before smoothing )
[image: 90b66410-2d68-4d5f-bc29-0eb568eb077a.png]
The high/low samples detection looks like:

# Get a boolean array for all samples higher or lower than the
threshold
self.threshold = np.median(samples) * 1.5 # go just above noise
floor
low_samples = samples < self.threshold
high_samples = samples >= self.threshold

# Compute the rising edge and falling edges by comparing the
current value to the next with
# the boolean operator & (if both are true the result is true) and
converting this to an index
# into the current array
rising_edge_idx = np.nonzero(low_samples[:-1] &
np.roll(high_samples, -1)[:-1])[0]
falling_edge_idx = np.nonzero(high_samples[:-1] &
np.roll(low_samples, -1)[:-1])[0]

if len(rising_edge_idx) > 0:
self.rising_edge = rising_edge_idx[0]

if len(falling_edge_idx) > 0:
self.falling_edge = falling_edge_idx[0]

While the smoothing (orange line) improves beep detection when the signal
is weak, it introduces two new issues:

1. Beep length calculations are off because of the extended length of "high
samples" and;
2. SNR calculations are considerably higher (and I am not sure which SNR is
"correct")

Any suggestions on how to work with this issue would be appreciated greatly.

Thanks

AL


Clipping - Gain too high

2024-02-23 Thread Al Grant
Good Morning,

I am trying to work out when my beeps are getting clipped due to gain too high.

This is in pure python (apologies if too OT here).

What I have come up with so far - which doesn't seem to work:

```python
def clipping(samples, rising_edge_idx, falling_edge_idx, beep_slice):
rising_edge_idx = rising_edge_idx*100
falling_edge_idx = falling_edge_idx*100
clipping = np.sqrt(np.mean(samples[rising_edge_idx:falling_edge_idx]))
clipping = 
np.sqrt(np.square(np.real(clipping))+np.square(np.imag(clipping)))
print(clipping)
return clipping

Rising Edge/Falling edge gives me the boundaries of a beep. I think
clipping occurs when magnitude is approaching 1.

The above equation is maximum at 0.5 no matter how high I ramp the gain up.

Any thoughts?

Thanks

Al



-- 
"Beat it punk!"
- Clint Eastwood



fft beep detection with overlap

2024-02-05 Thread Al Grant
Hi All,

My code is setup to detect regular beeps in a real time buffered
stream of samples from a SDR. For efficiency reasons I perform beep
detection using fft - and if no beeps are detected no further
processing is done.

This works pretty well except for one edge case - where the beep is on
the edge of the fft, then it gets skipped.

I think I need to overlap the fft processing. My beeps are 0.018s long
with a usual sample rate of 2.048e6.

So this is my current code without overlapping:

def process(self, samples: SamplesT):

# look for the presence of a beep within the chunk and :
# (1) if beep found calculate the offset
# (2) if beep not found iterate the counters and move on

start_time = time.time()
fft_size = self.fft_size
f = np.linspace(self.sample_rate/-2, self.sample_rate/2, fft_size)
num_ffts = len(samples) // fft_size # // is an integer
division which rounds down
fft_thresh = 0.1
beep_freqs = []
for i in range(num_ffts):
fft =
np.abs(np.fft.fftshift(np.fft.fft(samples[i*fft_size:(i+1)*fft_size])))
/ fft_size
if np.max(fft) > fft_thresh:
beep_freqs.append(np.linspace(self.sample_rate/-2,
self.sample_rate/2, fft_size)[np.argmax(fft)])
finish_time = time.time()

# if not beeps increment and exit early
if len(beep_freqs) == 0:
self.stateful_index += (samples.size/100) + 14
return

And this is my current attempt at overlapping:

def process(self, samples: SamplesT):
# look for the presence of a beep within the chunk and :
# (1) if beep found calculate the offset
# (2) if beep not found iterate the counters and move on

fft_size = self.fft_size
f = np.linspace(self.sample_rate/-2, self.sample_rate/2, fft_size)


size = fft_size # 8704
# step = self.sample_rate * self.beep_duration + 1000
step = 7704
samples_to_send_to_fft = [samples[i : i + size] for i in
range(0, len(samples)//fft_size, step)]

num_ffts = len(samples_to_send_to_fft[0]) # // is an integer
division which rounds down
fft_thresh = 0.1
beep_freqs = []

for i in range(num_ffts):

# fft =
np.abs(np.fft.fftshift(np.fft.fft(samples[i*fft_size:(i+1)*fft_size])))
/ fft_size
fft =
np.abs(np.fft.fftshift(np.fft.fft(samples_to_send_to_fft[0][i]))) /
fft_size
if np.max(fft) > fft_thresh:
beep_freqs.append(np.linspace(self.sample_rate/-2,
self.sample_rate/2, fft_size)[np.argmax(fft)])

# if no beeps increment and exit early
if len(beep_freqs) == 0:
self.stateful_index += (samples.size/100)
return

However it doesn't work at all. I think the way the array is
constructed with this overlapping is different structure?

My overlapping technique is as per here :
https://stackoverflow.com/questions/38163366/split-list-into-separate-but-overlapping-chunks

I am very interested if anyone can help with this please?

Thank you

Al



npy file into URH

2024-01-28 Thread Al Grant
Hi All,
It would be super handy if I could get my npy files into a format that
URH can digest.
Would make it much easier to validate expected results.
I hope this is not too OT here.
Is there a way?

Thanks

Al



Python Code - Suspect dropping samples

2023-12-13 Thread Al Grant
Hi All,

Some of you may recall that I am trying to sample at 2.4M and count
beeps between samples (usually 80 Beeps per minute, sometimes about 47
beeps per minute).

80 beeps per second equates to a beep every 1.3sec.

Anyway I have saved a 10 sec numpy array to disk, then processed it in
small chunks, lets say about 0.2 seconds worth of chunks. This works
perfectly.

The issue is when I try to do this in realtime from the RTLSDR I am
getting more like 120 beeps per minute which is wrong.

I suspected the sampling rate at 2.4M might be to blame so I went as
low as possible to 250k and the beeps per minute got to 86 (closer).

I still need to be certain if I am dropping samples, and the code that
acquires data is threaded and buffered, so quite complicated.

I assume I can add some time stampss to check for missing samples
against expected samples?

I change the sample rate variable to 250k, but I dont think I needed
to change any other calculations, since freq offset stays the same?

Code is here :
https://github.com/bigalnz/test_fft/blob/read_from_sdr/test_fft.py

Thanks

Al

-- 
"Beat it punk!"
- Clint Eastwood



Wildlife tracker - python

2023-12-06 Thread Al Grant
Good Morning,

I have with some generous help from others managed to get my wildlife
tracker processing signals in Python.

By way of background I am trying to workout the time between CW pulses
on 160Mhz where the pulses are at 80 / per min (1.33/s) 48 / min
(0.8/s) or 30 /min (0.5/s) and the signal strength of each pulse and
for the moment output to console in near real-time.

So goals:
1. Output beeps per minute (80/48/30)
2. Output sigal strength for each beep
3. Calculate beep high state duration is 0.0176s (validation from noise)
4. Make the logic resilient to handle samples that may have no beeps in them

So far it outputs:

BPM: 80.0 RSSI: 1.04
BPM: 80.0 RSSI: 104.

I am capturing at 2.4e6 and after decimation the samp_rate is 24e3.

There is a loop in the code which finds rising edge, falling edge and
then when it find another rising edge, the calculation's are done for
beep interval and RSSI. A rolling index keeps the calculations
stateful between chunks.

Problems:
1. If the chunk happens to end in the middle of a beep the RSSI
calculation is wrong since the rssi needs to mean the high state of
the beep and half the beep is in the previous chunk which is not kept.

2. I probably needs some optimisation and when it runs, its a bit/start stop'ish

3. Would threading help here? The way it is at the moment I am not
sure it is reading in next sample while processing current sample.

The full code is here :

https://github.com/bigalnz/test_fft/blob/main/test_fft.py

Many thanks in advance,

Al



Vector Sink & Source

2023-11-28 Thread Al Grant
Hello,

Experimenting with Vector sources and sinks.

Why can a Vector Source not connect to a GUI Vector Sink?

https://imgur.com/a/BMxmpde

I get the types are different, but why cant a vector source. well, be
a source of vectors?

Cheers

Al



Re: Rational Resampler - block executor error

2023-11-28 Thread Al Grant
Thanks Marcus.

Time to put this to the test:

https://imgur.com/a/hRsYAFu

I need a way to test that the values I detect in java are the same
values that I get in GR. I tried a Message Debug connected to the
output of "Complex to Mag" but that gave me a red arrow so not
allowed.

Any suggestions?

Thanks

A

On Tue, Nov 28, 2023 at 2:07 AM Marcus Müller  wrote:
>
> Hi Al,
>
> On 27.11.23 06:44, Al Grant wrote:
> > Thanks for the detailed reply Marcus.
>
> you're most welcome!
>
> > *M=800*
> > Where is M=800 in my rational resampler? I see looking at the link to the
> > picture I posted I had Decimation=100 (is M shorthand for decimation?)
>
> https://imgur.com/a/B2HqCKc shows "Rational Resampler" with "Decimation: 
> 800", not 100.
>
> > *PICTURE*
> > To your picture:
> >
> > [image: image.png]
> >
> > But my sample only has a repeating tone on 1 frequency, but in the picture
> > above with frequency on the x axis (and power on the y?), and  3 peaks I
> > have circled in red, at first glance I would have said this shows a signal
> > on the 3 different frequencies?
>
> No, it doesn't have 3 different frequencies! You missed my point there: this 
> is *one*
> signal. The frequency axis extends to - infinity and to + infinity; there's 
> infinitely
> many (light green) repetitions, not 3, in a discrete-time signal. (I just 
> couldn't draw
> infinitely wide pictures!)
> We just conveniently decide that, to look at the signal, it suffices to look 
> at the
> baseband one repetition, the dark green one.
>
> It does suffice to describe all the information in the signal! But: it breaks 
> down when
> you do things like decimating or interpolating, because suddenly you get the 
> effects of
> these repetitions you "just tried very hard to forget about" :D
>
> > I am assuming you didn't mean what I have written above, but I
> > can't reconcile the picture with the concept you are trying to convey.
>
> Hope the above helps!
>
>
> > *FILTER TYPE*
> > I see you have suggested a series of resampling filters, instead of 1 big
> > one, is that for computing efficiency or because it wont work the way I
> > expect the way I have done it?
> >
> > What is the difference between just a straight decimating block and
> > rational resampler?
>
> If you choose to use a filter that cuts out the lower 1/M of the original 
> bandwidth, it's
> the same. (assuming the rational resampler is set to "Interpolation: 1")
>
> > *BASEBAND*
> > Since my original post I have a slightly better understanding of a baseband
> > file. Correct me if I get it wrong, but for example a RTL-SDR can capture
> > baseband at 2.4Mhz (i.e. spectral width).
>
> Sounds right!
>
> > To my example I am interested in
> > 160.100Mhz to 160.200 with 100 channels spaced at 160.100, 160.110Mhz etc
> > etc.
>
> So, a bandwidth of 100 · 0.01 MHz, if I get you correctly, or 1 MHz. Add a 
> bit "left and
> right", because the analog filters aren't perfect, so maybe 1.2 MHz, just 
> because we can
> trivially afford to be so generous.
>
> > So in one baseband file I can capture all 100 channels. Cool.
>
> Exactly!
>
> > For the moment I just want to focus on 1 disaster (channel) at a time, and
> > am interested in getting the file into Java and doing the processing there.
>
> And you seem to be doing the right thing in principle (I didn't look at the 
> numbers being
> sensible here): you're selecting a single channel, say "OK, because that 
> channel is only
> 1/M of the overall bandwidth, I decimate by M".
>
> The thing I'm not understanding about your flow graph is then why the 
> following low-pass
> filter at all?
> (it's also incorrectly parameterized, as far as I can tell, because your 
> input sampling
> rate wasn't 32 kHz (as specified in the low-pass filter) times 800 (=25.6 
> MHz) (or 100,
> assuming the 800 was a typo, so 100·32 kHz = 3.2 MHz), but probably a 
> bandwidth that your
> RTL dongle actually supports.)
>
>
> Best regards,
> Marcus
>


-- 
"Beat it punk!"
- Clint Eastwood



Rational Resampler - block executor error

2023-11-23 Thread Al Grant
I am still trying to learn how GR works.

Coming from Java the idea of being able to do some processing there
interests me.

So I am trying to use a baseband file from SDR++ as a file source, and
process it in such a way that I can get the amplitude in Java. I
presume this would mean reading in the bin file and decoding the bits
to the I and Q values.

The source file is an unmodulated pulse on about 160.7807Mhz about 2
times per second.

Here is my block setup: https://imgur.com/a/B2HqCKc
And a link to the github project with the baseband file :
https://github.com/bigalnz/java_test

The issue I am currently having :

block_executor :error: sched:  (1)> is requesting more input
data  than we can provide.  ninput_items_required = 27862
max_possible_items_available = 8191  If this is a filter, consider
reducing the number of taps.

What is going on here and how do I fix this?

Thanks

Al



Wildlife Tracker (again)

2023-11-20 Thread Al Grant
Background:
I am trying to get GR to receive a signal from a wild life transmitter
on about 160.7073Mhz and calculate the beeps per minute - with an
ultimate goal of logging it to a db or csv.

The project is not for profit and I work with an endangered species
which are fitted with these transmitters. Because the birds are in a
very remote location we would like to use a Rpi4+ and RTL-SDR to
record daily the status of the birds as we only get to the remote
location occasionally ( a few times a year).

I may even invest in a SDRPlay which I understand is a better build of
receiver. I have some java programming experience, and python looks
easy enough for this...anyway to detail what I have done so far:

Signal:
The transmitted pulse is an unmodulated burst of RF energy about 0.2s
in duration and the time between pulses can vary to indicate the
animal is alive (30 beeps per minute), dead (90bpm) or incubating
(48bpm).

Every 10 minutes the are a series of other pulses which convery even
more information, but for now I just want to determine the time
between pulses and workout beeps per minute from that.

In my research I see that a very similar question has already been
asked by others both on this list and here
https://dsp.stackexchange.com/questions/50103/checking-for-vhf-pulse-with-sdr-and-python
with great answers from Andy Walls. I have read all of that and:

1. Read the beginner tutorials
2. Read 
https://dsp.stackexchange.com/questions/50103/checking-for-vhf-pulse-with-sdr-and-python
3. With help from another GR user got a baseband recording of the
signal isolated in a GR project
4. Have completed the tags tutorial on the wiki :
https://wiki.gnuradio.org/index.php?title=Python_Block_Tags
5. Tried to modify the tags tutorial as it applies to my project
6. Got as far as : https://github.com/bigalnz/tracker

This is where I got stuck - I tried to create a embedded python block
using the tags wiki article to my pulse isolator.

I expected `for index in range(len(input_items[0])):` to result in a
counter from 0,1,2,3,.x but it just seemed to be 0 and 1?

The second issue I encountered was I thought I could detect peaks with
this logic:

if input_items[0][index-1] > input_items[0][index] and
input_items[0][index-1] > input_items[0][index-2]:

But that did not put the tags on the peaks - so before I can even
think about calculating time gaps I presume I need to get the peak
detection working, and to do that I need to understand why the index
is not iterating beyond 0 and 1.

I am very grateful to those that have helped so far, and appreciate
any further input I can get.

Being able to capture this data will be really helpful for the future
of this species - full disclosure this is not a project I am marked
on, I am not a student and am not making any money from this.

Much thanks in advance

Al


** CODE SNIPPET **

def work(self, input_items, output_items):

print(f"length of input_items : {len(input_items[0])}")
for index in range(len(input_items[0])):
#print(f" the index is increasing : {index}")
if (input_items[0][index] >= self.threshold and
self.readyForTag == True):
#print("inside loop *")
#print(f"index : {index} input itmes index :
{input_items[0][index]}")
#print(f"index : {index} input itmes -1 :
{input_items[0][index-1]}")
#print(f"index : {index} input itmes -2:
{input_items[0][index-2]}")
if input_items[0][index-1] > input_items[0][index] and
input_items[0][index-1] > input_items[0][index-2]:
#print(f"peak at {input_items[0][index-1]}")
key = pmt.intern("detect")
value =
pmt.from_float(np.round(float(input_items[0][index-1]),2))
writeIndex = self.nitems_written(0) + index-1
#print(f"Index:{writeIndex}")
#print(f"Value:{value}")

self.add_item_tag(0, writeIndex, key, value )
self.readyForTag = False

if (self.readyForTag == False):
self.timer = self.timer + 1
if (self.timer >= self.report_period):
self.timer = 0
self.readyForTag = True

output_items[0][:] = input_items[0]
return len(output_items[0])