/* -*- c++ -*- */ /* * gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module * * Copyright (C) 2017,2018 Libre Space Foundation * * 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "noaa_apt_sink_impl.h" #include namespace gr { namespace satnogs { // 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, 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 }; // 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, 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 }; noaa_apt_sink::sptr noaa_apt_sink::make(const char *filename_png, size_t width, size_t height, bool sync, bool flip) { return gnuradio::get_initial_sptr( new noaa_apt_sink_impl(filename_png, width, height, sync, flip)); } /* * The private constructor */ noaa_apt_sink_impl::noaa_apt_sink_impl(const char *filename_png, size_t width, size_t height, bool sync, bool flip) : gr::sync_block("noaa_apt_sink", gr::io_signature::make(1, 1, sizeof(float)), gr::io_signature::make(0, 0, 0)), f_average_alpha(0.25), d_filename_png(filename_png), d_width(width), d_height(height), d_synchronize_opt(sync), d_flip(flip), d_history_length(40), d_has_sync(false), d_image_received(false), d_current_x(0), d_current_y(0), d_num_images(0), f_max_level(0.0), f_min_level(1.0), f_average(0.0) { set_history(d_history_length); d_full_image = png::image(d_width, d_height); } void noaa_apt_sink_impl::write_image(png::image image, std::string filename) { // In case the flip option is set if (d_flip) { size_t width = image.get_width(); size_t height = image.get_height(); // An image of same size is created ... png::image flipped(width, height); // ... and all the lines are copied over reverse order for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { png::gray_pixel pixel = image.get_pixel(x, height - y - 1); flipped.set_pixel(x, y, pixel); } } // Write out the flipped image flipped.write(filename); } // In case the flip option is not set else { // Write out the original image.write(filename); } } noaa_apt_sink_impl::~noaa_apt_sink_impl() { } bool noaa_apt_sink_impl::stop() { if (!d_image_received) { write_image(d_full_image, d_filename_png); } return true; } 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; } // Adjust dynamic range, using minimum and maximum values sample = (sample - f_min_level) / (f_max_level - f_min_level) * 255; // Set the pixel in the full image d_full_image.set_pixel(x, y, sample); } void noaa_apt_sink_impl::skip_to(size_t new_x, size_t pos, const float *samples) { // Check if the skip is forward or backward if (new_x > d_current_x) { // 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 size_t dist = std::min(size_t(39), new_x - d_current_x); // Fill the hole using the previous samples of pos for (size_t i = 0; i < dist; i++) { set_pixel(new_x - dist + i, d_current_y, samples[pos - dist + i]); } } // Jump to new location d_current_x = new_x; } noaa_apt_sync_marker noaa_apt_sink_impl::is_marker(size_t pos, const float *samples) { // Initialize counters for 'hacky' correlation size_t count_a = 0; size_t count_b = 0; for (size_t i = 0; i < 40; i++) { // history of previous 39 samples + current one // -> start 39 samples in the past float sample = samples[pos - 39 + i]; // Remove DC-offset (aka. the average value of the sync pattern) sample = sample - f_average; // Very basic 1/0 correlation between pattern constan and history if ((sample > 0 && synca_seq[i]) || (sample < 0 && !synca_seq[i])) { count_a += 1; } if ((sample > 0 && syncb_seq[i]) || (sample < 0 && !syncb_seq[i])) { count_b += 1; } } // Prefer sync pattern a as it is detected more reliable 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; } } int noaa_apt_sink_impl::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { 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; } // 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 float sample = in[i]; // For some reason the first sample on a Raspberry Pi can be NaN if (std::isnan(sample)) { continue; } // Update min and max level to adjust dynamic range in set pixel f_max_level = std::fmax(f_max_level, sample); 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; // If line sync is enabled if (d_synchronize_opt) { // 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 skip_to(39, i, in); // 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; } } // For pattern b else if (marker == noaa_apt_sync_marker::SYNC_B) { // Skip to right location, pattern starts 40 samples in the past skip_to(d_width / 2 + 39, i, in); // 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; } } } // Set the the pixel at the current position set_pixel(d_current_x, d_current_y, sample); // Increment x position d_current_x += 1; // If we are beyond the end of line if (d_current_x >= d_width) { // Increment y position d_current_y += 1; // Reset x position to line start d_current_x = 0; // Split the image if there are enough lines decoded 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; } } } // Tell gnu radio how many samples were consumed return noutput_items; } } /* namespace satnogs */ } /* namespace gr */