gr-satnogs/lib/noaa_apt_sink_impl.cc

294 lines
11 KiB
C++
Raw Normal View History

2017-04-08 20:50:18 +02:00
/* -*- c++ -*- */
2017-07-19 15:57:00 +02:00
/*
* gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
*
* Copyright (C) 2017,2018 Libre Space Foundation <http://librespacefoundation.org/>
2017-07-19 15:57:00 +02:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2017-04-08 20:50:18 +02:00
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gnuradio/io_signature.h>
#include "noaa_apt_sink_impl.h"
2017-04-08 20:50:18 +02:00
#include <cmath>
namespace gr
{
namespace satnogs
{
2018-01-03 00:34:32 +01:00
// Noaa apt sync pattern A
// (see https://sourceforge.isae.fr/attachments/download/1813/apt_synch.gif)
const bool noaa_apt_sink_impl::synca_seq[] = {false, false, false, false,
2018-01-02 22:42:51 +01:00
true, true, false, false, // Pulse 1
true, true, false, false, // Pulse 2
true, true, false, false, // Pulse 3
true, true, false, false, // Pulse 4
true, true, false, false, // Pulse 5
true, true, false, false, // Pulse 6
true, true, false, false, // Pulse 7
false, false, false, false,
false, false, false, false};
2018-01-03 00:34:32 +01:00
// Noaa apt sync pattern B
// (see https://sourceforge.isae.fr/attachments/download/1813/apt_synch.gif)
const bool noaa_apt_sink_impl::syncb_seq[] = {false, false, false, false,
2018-01-02 22:42:51 +01:00
true, true, true, false, false,
true, true, true, false, false,
true, true, true, false, false,
true, true, true, false, false,
true, true, true, false, false,
true, true, true, false, false,
true, true, true, false, false,
false};
2017-04-08 20:50:18 +02:00
noaa_apt_sink::sptr
2017-07-19 15:57:00 +02:00
noaa_apt_sink::make (const char *filename_png, size_t width, size_t height,
bool sync, bool flip)
2017-04-08 20:50:18 +02:00
{
return gnuradio::get_initial_sptr (
new noaa_apt_sink_impl (filename_png, width, height, sync,
2017-07-19 15:57:00 +02:00
flip));
2017-04-08 20:50:18 +02:00
}
2018-01-03 00:34:32 +01:00
2017-04-08 20:50:18 +02:00
/*
* The private constructor
*/
2017-04-10 14:01:02 +02:00
noaa_apt_sink_impl::noaa_apt_sink_impl (const char *filename_png,
2017-07-19 15:57:00 +02:00
size_t width, size_t height,
bool sync, bool flip) :
2017-07-19 15:57:00 +02:00
gr::sync_block ("noaa_apt_sink",
gr::io_signature::make (1, 1, sizeof(float)),
gr::io_signature::make (0, 0, 0)),
2018-01-05 13:37:55 +01:00
f_average_alpha (0.25),
2017-07-19 15:57:00 +02:00
d_filename_png (filename_png),
d_width (width),
d_height (height),
d_synchronize_opt (sync),
2018-01-02 19:00:53 +01:00
d_flip (flip),
d_history_length (40),
d_has_sync (false),
d_image_received(false),
2018-01-02 19:00:53 +01:00
d_current_x (0),
d_current_y (0),
2017-07-19 15:57:00 +02:00
d_num_images (0),
2018-01-02 19:00:53 +01:00
f_max_level(0.0),
2018-01-02 22:42:51 +01:00
f_min_level(1.0),
f_average(0.0)
2017-04-08 20:50:18 +02:00
{
2018-01-02 19:00:53 +01:00
set_history(d_history_length);
d_full_image = png::image<png::gray_pixel>(d_width, d_height);
2017-04-08 20:50:18 +02:00
}
2018-01-02 19:00:53 +01:00
2017-04-10 14:01:02 +02:00
void
2018-01-04 16:06:54 +01:00
noaa_apt_sink_impl::write_image (png::image<png::gray_pixel> image,
std::string filename)
2017-07-19 15:57:00 +02:00
{
2018-01-04 16:06:54 +01:00
// In case the flip option is set
2018-01-02 19:00:53 +01:00
if(d_flip) {
size_t width = image.get_width();
size_t height = image.get_height();
2018-01-04 16:06:54 +01:00
// An image of same size is created ...
2018-01-02 19:00:53 +01:00
png::image<png::gray_pixel> flipped(width, height);
2018-01-04 16:06:54 +01:00
// ... and all the lines are copied over reverse order
2018-01-02 19:00:53 +01:00
for(size_t y = 0; y < height; y++) {
for(size_t x = 0; x < width; x++) {
2018-01-05 13:37:55 +01:00
png::gray_pixel pixel = image.get_pixel(x, height - y - 1);
2018-01-02 19:00:53 +01:00
flipped.set_pixel(x, y, pixel);
}
}
2018-01-04 16:06:54 +01:00
// Write out the flipped image
2018-01-02 19:00:53 +01:00
flipped.write(filename);
2017-07-19 15:57:00 +02:00
}
2018-01-04 16:06:54 +01:00
// In case the flip option is not set
2018-01-02 19:00:53 +01:00
else {
2018-01-04 16:06:54 +01:00
// Write out the original
2018-01-02 19:00:53 +01:00
image.write(filename);
2017-07-19 15:57:00 +02:00
}
2017-04-10 17:03:00 +02:00
}
2018-01-02 19:00:53 +01:00
noaa_apt_sink_impl::~noaa_apt_sink_impl () {
2018-02-02 17:44:32 +01:00
}
bool
noaa_apt_sink_impl::stop(){
if(!d_image_received){
write_image(d_full_image, d_filename_png);
}
2018-02-02 17:44:32 +01:00
return true;
2018-01-02 19:00:53 +01:00
}
2017-04-10 17:03:00 +02:00
2018-01-02 22:42:51 +01:00
void noaa_apt_sink_impl::set_pixel (size_t x, size_t y, float sample) {
// We can encounter NaN here since skip_to read the history whithout checking
if(std::isnan(sample)) {
sample = 0.0;
}
2018-01-04 16:06:54 +01:00
// Adjust dynamic range, using minimum and maximum values
2018-01-02 22:42:51 +01:00
sample = (sample - f_min_level) / (f_max_level - f_min_level) * 255;
2018-01-04 16:06:54 +01:00
// Set the pixel in the full image
2018-01-02 22:42:51 +01:00
d_full_image.set_pixel(x, y, sample);
2017-04-10 17:03:00 +02:00
}
void
2018-01-02 22:42:51 +01:00
noaa_apt_sink_impl::skip_to (size_t new_x, size_t pos, const float *samples) {
2018-01-04 16:06:54 +01:00
// Check if the skip is forward or backward
2018-01-02 22:42:51 +01:00
if(new_x > d_current_x) {
2018-01-04 16:06:54 +01:00
// In case it is forward there will be a new_x - d_current_x sized hole
// in the image. Holes up 39 pixels can be filled from the modules history
2018-01-02 22:42:51 +01:00
size_t dist = std::min(size_t(39), new_x - d_current_x);
2018-01-04 16:06:54 +01:00
// Fill the hole using the previous samples of pos
2018-01-02 22:42:51 +01:00
for(size_t i = 0; i < dist; i++) {
set_pixel(new_x - dist + i, d_current_y, samples[pos - dist + i]);
}
2017-07-19 15:57:00 +02:00
}
2018-01-04 16:06:54 +01:00
// Jump to new location
2018-01-02 22:42:51 +01:00
d_current_x = new_x;
2018-01-02 19:00:53 +01:00
}
2017-07-21 19:49:44 +02:00
2018-01-03 00:34:32 +01:00
2018-01-02 22:42:51 +01:00
noaa_apt_sync_marker
noaa_apt_sink_impl::is_marker(size_t pos, const float *samples) {
2018-01-04 16:06:54 +01:00
// Initialize counters for 'hacky' correlation
2018-01-02 22:42:51 +01:00
size_t count_a = 0;
size_t count_b = 0;
2017-07-19 15:57:00 +02:00
2018-01-02 22:42:51 +01:00
for(size_t i = 0; i < 40; i++) {
2018-01-04 16:06:54 +01:00
// history of previous 39 samples + current one
// -> start 39 samples in the past
2018-01-02 22:42:51 +01:00
float sample = samples[pos - 39 + i];
2018-01-04 16:06:54 +01:00
// Remove DC-offset (aka. the average value of the sync pattern)
2018-01-02 22:42:51 +01:00
sample = sample - f_average;
2018-01-04 16:06:54 +01:00
// Very basic 1/0 correlation between pattern constan and history
if((sample > 0 && synca_seq[i]) || (sample < 0 && !synca_seq[i])) {
2018-01-02 22:42:51 +01:00
count_a += 1;
2018-01-02 19:00:53 +01:00
}
if((sample > 0 && syncb_seq[i]) || (sample < 0 && !syncb_seq[i])) {
2018-01-02 22:42:51 +01:00
count_b += 1;
2018-01-02 19:00:53 +01:00
}
2017-07-21 19:49:44 +02:00
}
2017-07-19 15:57:00 +02:00
2018-01-04 16:06:54 +01:00
// Prefer sync pattern a as it is detected more reliable
2018-01-02 22:42:51 +01:00
if(count_a > 35) {
return noaa_apt_sync_marker::SYNC_A;
}
else if(count_b > 35) {
return noaa_apt_sync_marker::SYNC_B;
}
else {
return noaa_apt_sync_marker::NONE;
2018-01-02 19:00:53 +01:00
}
2017-04-08 20:50:18 +02:00
}
2017-04-10 17:03:00 +02:00
2018-01-03 00:34:32 +01:00
2017-04-08 20:50:18 +02:00
int
noaa_apt_sink_impl::work (int noutput_items,
2017-07-19 15:57:00 +02:00
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
2017-04-08 20:50:18 +02:00
{
2018-01-02 19:00:53 +01:00
const float *in = (const float *) input_items[0];
/* If we have already produced one image, ignore the remaining observation*/
if(d_image_received){
return noutput_items;
}
2018-01-04 16:06:54 +01:00
// Structure of in[]:
// - d_history_length many historical samples
// - noutput_items many samples to process
for (size_t i = d_history_length - 1;
i < noutput_items + d_history_length - 1; i++) {
// Get the current sample
2018-01-02 19:00:53 +01:00
float sample = in[i];
// For some reason the first sample on a Raspberry Pi can be NaN
if(std::isnan(sample)) {
continue;
}
2018-01-04 16:06:54 +01:00
// Update min and max level to adjust dynamic range in set pixel
2018-01-02 19:00:53 +01:00
f_max_level = std::fmax(f_max_level, sample);
2018-01-02 22:42:51 +01:00
f_min_level = std::fmin(f_min_level, sample);
// Update exponential smoothing average used in sync pattern detection
f_average = f_average_alpha * sample + (1.0 - f_average_alpha) * f_average;
2018-01-02 22:42:51 +01:00
2018-01-04 16:06:54 +01:00
// If line sync is enabled
2018-01-02 22:42:51 +01:00
if(d_synchronize_opt) {
2018-01-04 16:06:54 +01:00
// Check if the history for the current sample is a sync pattern
noaa_apt_sync_marker marker = is_marker(i, in);
// For pattern a
if(marker == noaa_apt_sync_marker::SYNC_A) {
// Skip to right location, pattern starts 40 samples in the past
2018-01-02 22:42:51 +01:00
skip_to(39, i, in);
2018-01-04 16:06:54 +01:00
// If this is the first sync, reset min and max
if(!d_has_sync) {
f_max_level = 0.0;
f_min_level = 1.0;
d_has_sync = true;
}
2017-07-19 15:57:00 +02:00
}
2018-01-04 16:06:54 +01:00
// For pattern b
else if(marker == noaa_apt_sync_marker::SYNC_B) {
// Skip to right location, pattern starts 40 samples in the past
2018-01-02 22:42:51 +01:00
skip_to(d_width / 2 + 39, i, in);
2018-01-04 16:06:54 +01:00
// If this is the first sync, reset min and max
if(!d_has_sync) {
f_max_level = 0.0;
f_min_level = 1.0;
d_has_sync = true;
}
2017-07-19 15:57:00 +02:00
}
2018-01-02 22:42:51 +01:00
}
2018-01-04 16:06:54 +01:00
// Set the the pixel at the current position
2018-01-02 22:42:51 +01:00
set_pixel(d_current_x, d_current_y, sample);
2018-01-02 19:00:53 +01:00
2018-01-04 16:06:54 +01:00
// Increment x position
2018-01-02 19:00:53 +01:00
d_current_x += 1;
2018-01-04 16:06:54 +01:00
// If we are beyond the end of line
2018-01-02 19:00:53 +01:00
if(d_current_x >= d_width) {
2018-01-04 16:06:54 +01:00
// Increment y position
2018-01-02 22:42:51 +01:00
d_current_y += 1;
2018-01-04 16:06:54 +01:00
// Reset x position to line start
2018-01-02 22:42:51 +01:00
d_current_x = 0;
2018-01-04 16:06:54 +01:00
// Split the image if there are enough lines decoded
2018-01-02 22:42:51 +01:00
if(d_current_y >= d_height) {
d_current_y = 0;
d_num_images += 1;
// Write out the full image
write_image(d_full_image, d_filename_png);
d_image_received = true;
2017-07-19 15:57:00 +02:00
}
}
}
2018-01-02 19:00:53 +01:00
2018-01-04 16:06:54 +01:00
// Tell gnu radio how many samples were consumed
2018-01-02 19:00:53 +01:00
return noutput_items;
2017-04-08 20:50:18 +02:00
}
} /* namespace satnogs */
} /* namespace gr */