2021-02-15 21:20:05 +01:00
|
|
|
#include "chris_reader.h"
|
2022-10-03 11:55:25 +02:00
|
|
|
#include "../crc.h"
|
2025-07-23 12:21:02 +02:00
|
|
|
#include "common/repack.h"
|
|
|
|
|
#include "common/utils.h"
|
2025-05-25 14:30:24 +02:00
|
|
|
#include "image/io.h"
|
2025-07-23 12:21:02 +02:00
|
|
|
#include "logger.h"
|
|
|
|
|
#include "products/image_product.h"
|
|
|
|
|
#include "utils/binary.h"
|
2025-05-26 19:21:10 +01:00
|
|
|
#include "utils/stats.h"
|
2025-07-23 12:21:02 +02:00
|
|
|
#include <filesystem>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <map>
|
2022-04-11 16:26:40 +02:00
|
|
|
|
2021-02-15 21:20:05 +01:00
|
|
|
#define ALL_MODE 2
|
|
|
|
|
#define WATER_MODE 3
|
|
|
|
|
#define LAND_MODE 3
|
|
|
|
|
#define CHLOROPHYL_MODE 3
|
|
|
|
|
#define LAND_ALL_MODE 100 // Never seen yet...
|
|
|
|
|
|
|
|
|
|
namespace proba
|
|
|
|
|
{
|
|
|
|
|
namespace chris
|
|
|
|
|
{
|
2025-07-23 12:21:02 +02:00
|
|
|
CHRISReader::CHRISReader(std::string &outputfolder, satdump::products::DataSet &dataset) : dataset(dataset) { output_folder = outputfolder; }
|
2021-02-15 21:20:05 +01:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
CHRISImageParser::CHRISImageParser()
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
2022-10-03 15:27:20 +02:00
|
|
|
img_buffer = std::vector<uint16_t>(748 * 7680 * absolute_max_cnt, 0);
|
2021-02-15 21:20:05 +01:00
|
|
|
mode = 0;
|
|
|
|
|
current_width = 12096;
|
|
|
|
|
current_height = 748;
|
2022-10-03 15:27:20 +02:00
|
|
|
max_value = 0; // 710;
|
2021-02-15 21:20:05 +01:00
|
|
|
frame_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-23 12:21:02 +02:00
|
|
|
CHRISImageParser::~CHRISImageParser() { img_buffer.clear(); }
|
2022-04-11 16:26:40 +02:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
void CHRISImageParser::work(ccsds::CCSDSPacket &packet)
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
|
|
|
|
uint16_t count_marker = packet.payload[10] << 8 | packet.payload[11];
|
|
|
|
|
int mode_marker = packet.payload[9] & 0x03;
|
|
|
|
|
|
2022-10-03 15:27:20 +02:00
|
|
|
// Reverse bits... Recorder thing
|
2022-05-12 18:19:01 +02:00
|
|
|
for (int i = 0; i < (int)packet.payload.size(); i++)
|
2025-07-23 12:21:02 +02:00
|
|
|
packet.payload[i] = satdump::reverseBits(packet.payload[i]);
|
2022-04-11 16:26:40 +02:00
|
|
|
|
2022-10-03 15:27:20 +02:00
|
|
|
// Check marker is in range
|
2022-10-03 19:41:59 +02:00
|
|
|
if (count_marker > max_value - 1 && count_marker < absolute_max_cnt)
|
2022-10-03 15:27:20 +02:00
|
|
|
max_value = count_marker + 1;
|
2022-04-11 16:26:40 +02:00
|
|
|
|
2022-10-03 15:27:20 +02:00
|
|
|
// Repack to 12-bits
|
2022-06-14 09:51:59 +02:00
|
|
|
bool bad = (packet.payload[16] & 0b1111111); //!(packet.payload[2] & 0b01000000);
|
2022-10-03 15:27:20 +02:00
|
|
|
repackBytesTo12bits(&packet.payload[bad ? 18 : 16], packet.payload.size() - 16, words_tmp);
|
2022-04-11 16:26:40 +02:00
|
|
|
|
2021-02-15 21:20:05 +01:00
|
|
|
// Convert into 12-bits values
|
2022-04-11 16:26:40 +02:00
|
|
|
for (int i = 0; i < 7680; i += 1)
|
2022-10-03 15:27:20 +02:00
|
|
|
if (count_marker < absolute_max_cnt)
|
2025-07-23 12:21:02 +02:00
|
|
|
img_buffer[count_marker * 7680 + (i + 0) + (bad ? 14 : 0)] = std::min<int>(65535, satdump::reverse16Bits(words_tmp[i]) << 1);
|
2021-02-15 21:20:05 +01:00
|
|
|
|
|
|
|
|
frame_count++;
|
|
|
|
|
|
|
|
|
|
// Frame counter
|
2022-10-03 11:55:25 +02:00
|
|
|
// if (count_marker == max_value)
|
|
|
|
|
//{
|
|
|
|
|
// save();
|
|
|
|
|
// frame_count = 0;
|
|
|
|
|
// modeMarkers.clear();
|
|
|
|
|
//}
|
2021-02-15 21:20:05 +01:00
|
|
|
|
|
|
|
|
if ((count_marker > 50 && count_marker < 70) || (count_marker > 500 && count_marker < 520) || (count_marker > 700 && count_marker < 720))
|
|
|
|
|
{
|
2025-05-26 19:21:10 +01:00
|
|
|
mode = satdump::most_common(modeMarkers.begin(), modeMarkers.end(), 0);
|
2021-02-15 21:20:05 +01:00
|
|
|
|
|
|
|
|
if (mode == WATER_MODE || mode == CHLOROPHYL_MODE || mode == LAND_MODE)
|
|
|
|
|
{
|
|
|
|
|
current_width = 7296;
|
|
|
|
|
current_height = 748;
|
2022-10-03 15:27:20 +02:00
|
|
|
// max_value = 710;
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
else if (mode == ALL_MODE)
|
|
|
|
|
{
|
|
|
|
|
current_width = 12096;
|
|
|
|
|
current_height = 374;
|
2022-10-03 15:27:20 +02:00
|
|
|
// max_value = 588;
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
else if (mode == LAND_ALL_MODE)
|
|
|
|
|
{
|
|
|
|
|
current_width = 7680;
|
|
|
|
|
current_height = 374;
|
2022-10-03 15:27:20 +02:00
|
|
|
// max_value = 588;
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modeMarkers.push_back(mode_marker);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-02 01:03:35 +02:00
|
|
|
void CHRISReader::work(ccsds::CCSDSPacket &packet)
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
|
|
|
|
if (packet.payload.size() < 11538)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
if (check_proba_crc(packet))
|
|
|
|
|
{
|
|
|
|
|
logger->error("CHRIS : Bad CRC!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-02-15 21:20:05 +01:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
uint32_t img_full_id = packet.payload[0] << 24 | packet.payload[1] << 16 | packet.payload[2] << 8 | packet.payload[3];
|
|
|
|
|
int img_tag = (packet.payload[0] - 1) << 16 | packet.payload[1] << 8 | packet.payload[2];
|
2021-02-15 21:20:05 +01:00
|
|
|
|
|
|
|
|
// Start new image
|
2022-10-03 11:55:25 +02:00
|
|
|
if (imageParsers.find(img_full_id) == imageParsers.end())
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
2022-10-03 11:55:25 +02:00
|
|
|
logger->info("Found new CHRIS image! Tag " + std::to_string(img_tag));
|
|
|
|
|
imageParsers.insert(std::pair<uint32_t, std::shared_ptr<CHRISImageParser>>(img_full_id, std::make_shared<CHRISImageParser>()));
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
imageParsers[img_full_id]->work(packet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHRISImagesT CHRISImageParser::process()
|
|
|
|
|
{
|
2022-10-03 15:27:20 +02:00
|
|
|
int height_detected = (max_value * 7680) / current_width;
|
|
|
|
|
int final_height = height_detected > current_height ? height_detected : current_height;
|
2023-05-08 23:08:34 +02:00
|
|
|
logger->trace("CHRIS Image size : %dx%d", current_width, final_height);
|
2022-10-03 15:27:20 +02:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
CHRISImagesT ret;
|
|
|
|
|
ret.mode = mode;
|
2024-05-09 01:16:08 +02:00
|
|
|
ret.raw = image::Image(img_buffer.data(), 16, current_width, final_height, 1);
|
2022-10-03 11:55:25 +02:00
|
|
|
|
|
|
|
|
if (mode == ALL_MODE)
|
|
|
|
|
for (int i = 0; i < 63; i++)
|
|
|
|
|
ret.channels.push_back(ret.raw.crop_to(4 + i * 192, 4 + i * 192 + 186));
|
2022-10-03 15:27:20 +02:00
|
|
|
else if (mode == LAND_ALL_MODE)
|
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
|
|
|
ret.channels.push_back(ret.raw.crop_to(5 + i * 384, 5 + i * 384 + 375));
|
2022-10-03 11:55:25 +02:00
|
|
|
else
|
|
|
|
|
for (int i = 0; i < 19; i++)
|
|
|
|
|
ret.channels.push_back(ret.raw.crop_to(5 + i * 384, 5 + i * 384 + 375));
|
|
|
|
|
|
|
|
|
|
return ret;
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
void CHRISReader::save()
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
2022-10-03 11:55:25 +02:00
|
|
|
logger->info("Saving CHRIS data! (if any)");
|
|
|
|
|
|
|
|
|
|
std::map<int, CHRISFullFrameT> full_frames_wip;
|
|
|
|
|
|
|
|
|
|
for (std::pair<const uint32_t, std::shared_ptr<CHRISImageParser>> ¤tPair : imageParsers)
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
2022-10-03 11:55:25 +02:00
|
|
|
uint8_t tag[3];
|
|
|
|
|
tag[0] = (currentPair.first >> 24) & 0xFF;
|
|
|
|
|
tag[1] = (currentPair.first >> 16) & 0xFF;
|
|
|
|
|
tag[2] = (currentPair.first >> 8) & 0xFF;
|
|
|
|
|
int img_tag = (tag[0] - 1) << 16 | tag[1] << 8 | tag[2];
|
|
|
|
|
bool is_2nd_half = currentPair.first & 0xFF;
|
|
|
|
|
|
|
|
|
|
if (currentPair.second->frame_count > 0)
|
|
|
|
|
{
|
|
|
|
|
CHRISImagesT chris_img = currentPair.second->process();
|
2022-06-14 09:51:59 +02:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
// Save Half alone
|
|
|
|
|
std::string image_name = std::to_string(img_tag) + "_Half_" + (is_2nd_half ? "2" : "1");
|
2022-06-14 09:51:59 +02:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
std::string dir_path = output_folder + "/CHRIS-" + image_name;
|
2022-06-14 09:51:59 +02:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
if (!std::filesystem::exists(dir_path))
|
|
|
|
|
std::filesystem::create_directories(dir_path);
|
2022-06-14 09:51:59 +02:00
|
|
|
|
2024-05-09 01:16:08 +02:00
|
|
|
image::save_img(chris_img.raw, dir_path + "/RAW");
|
2021-02-15 21:20:05 +01:00
|
|
|
|
2025-03-11 21:32:38 +01:00
|
|
|
satdump::products::ImageProduct chris_products;
|
2022-10-03 11:55:25 +02:00
|
|
|
chris_products.instrument_name = "chris";
|
|
|
|
|
|
2022-10-03 15:27:20 +02:00
|
|
|
for (int i = 0; i < (int)chris_img.channels.size(); i++)
|
2022-06-14 09:51:59 +02:00
|
|
|
{
|
2024-05-09 01:16:08 +02:00
|
|
|
image::Image ch = chris_img.channels[i];
|
2022-10-03 15:27:20 +02:00
|
|
|
ch.resize(ch.width() * 2, ch.height());
|
2025-03-11 21:32:38 +01:00
|
|
|
chris_products.images.push_back({i, "CHRIS-" + std::to_string(i + 1), std::to_string(i + 1), ch, 12});
|
2022-06-14 09:51:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
chris_products.save(dir_path);
|
|
|
|
|
dataset.products_list.push_back("CHRIS/CHRIS-" + image_name);
|
|
|
|
|
|
|
|
|
|
// Also push the data to attempt recomposing later
|
|
|
|
|
if (full_frames_wip.find(img_tag) == full_frames_wip.end())
|
|
|
|
|
full_frames_wip.insert(std::pair<uint32_t, CHRISFullFrameT>(img_tag, CHRISFullFrameT()));
|
2021-02-15 21:20:05 +01:00
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
if (is_2nd_half)
|
|
|
|
|
{
|
|
|
|
|
full_frames_wip[img_tag].half2 = chris_img;
|
|
|
|
|
full_frames_wip[img_tag].has_half_2 = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
full_frames_wip[img_tag].half1 = chris_img;
|
|
|
|
|
full_frames_wip[img_tag].has_half_1 = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
for (std::pair<const int, CHRISFullFrameT> ¤tPair : full_frames_wip)
|
|
|
|
|
{
|
|
|
|
|
auto &frm = currentPair.second;
|
|
|
|
|
|
|
|
|
|
if (frm.has_half_1 && frm.has_half_2)
|
|
|
|
|
{
|
|
|
|
|
CHRISImagesT chris_img = frm.recompose();
|
|
|
|
|
|
|
|
|
|
// Save Full frame
|
|
|
|
|
std::string image_name = std::to_string(currentPair.first) + "_Full";
|
|
|
|
|
|
|
|
|
|
std::string dir_path = output_folder + "/CHRIS-" + image_name;
|
|
|
|
|
|
|
|
|
|
if (!std::filesystem::exists(dir_path))
|
|
|
|
|
std::filesystem::create_directories(dir_path);
|
|
|
|
|
|
2024-05-09 01:16:08 +02:00
|
|
|
image::save_img(chris_img.raw, dir_path + "/RAW");
|
2021-02-15 21:20:05 +01:00
|
|
|
|
2025-03-11 21:32:38 +01:00
|
|
|
satdump::products::ImageProduct chris_products;
|
2022-10-03 11:55:25 +02:00
|
|
|
chris_products.instrument_name = "chris";
|
|
|
|
|
|
2022-10-03 15:27:20 +02:00
|
|
|
for (int i = 0; i < (int)chris_img.channels.size(); i++)
|
|
|
|
|
{
|
2024-05-09 01:16:08 +02:00
|
|
|
image::Image ch = chris_img.channels[i];
|
2025-03-11 21:32:38 +01:00
|
|
|
chris_products.images.push_back({i, "CHRIS-" + std::to_string(i + 1), std::to_string(i + 1), ch, 12});
|
2022-10-03 15:27:20 +02:00
|
|
|
}
|
2022-10-03 11:55:25 +02:00
|
|
|
|
|
|
|
|
chris_products.save(dir_path);
|
|
|
|
|
dataset.products_list.push_back("CHRIS/CHRIS-" + image_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-15 21:20:05 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-03 11:55:25 +02:00
|
|
|
std::string getModeName(int mode)
|
2021-02-15 21:20:05 +01:00
|
|
|
{
|
|
|
|
|
if (mode == ALL_MODE)
|
|
|
|
|
return "ALL";
|
|
|
|
|
else if (mode == WATER_MODE)
|
|
|
|
|
return "LAND/WATER/CHLOROPHYL";
|
|
|
|
|
else if (mode == LAND_MODE)
|
|
|
|
|
return "LAND/WATER/CHLOROPHYL";
|
|
|
|
|
else if (mode == CHLOROPHYL_MODE)
|
|
|
|
|
return "LAND/WATER/CHLOROPHYL";
|
|
|
|
|
else if (mode == LAND_ALL_MODE)
|
|
|
|
|
return "ALL-LAND";
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
}
|
2022-10-03 11:55:25 +02:00
|
|
|
|
|
|
|
|
CHRISImagesT CHRISFullFrameT::recompose()
|
|
|
|
|
{
|
|
|
|
|
CHRISImagesT full;
|
|
|
|
|
full.mode = half1.mode;
|
|
|
|
|
full.raw = interleaveCHRIS(half1.raw, half2.raw);
|
2022-10-03 15:27:20 +02:00
|
|
|
for (int i = 0; i < std::min<int>(half1.channels.size(), half2.channels.size()); i++)
|
2022-10-03 11:55:25 +02:00
|
|
|
full.channels.push_back(interleaveCHRIS(half1.channels[i], half2.channels[i]));
|
|
|
|
|
return full;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 01:16:08 +02:00
|
|
|
image::Image CHRISFullFrameT::interleaveCHRIS(image::Image img1, image::Image img2)
|
2022-10-03 11:55:25 +02:00
|
|
|
{
|
2024-05-09 01:16:08 +02:00
|
|
|
image::Image img_final(img1.depth(), img1.width() * 2, img1.height(), 1);
|
2022-10-03 15:27:20 +02:00
|
|
|
for (int i = 0; i < (int)img_final.width(); i += 2)
|
2022-10-03 11:55:25 +02:00
|
|
|
{
|
2022-10-03 15:27:20 +02:00
|
|
|
for (int y = 0; y < (int)img_final.height(); y++)
|
2022-10-03 11:55:25 +02:00
|
|
|
{
|
2024-05-09 01:16:08 +02:00
|
|
|
img_final.set(y * img_final.width() + i + 0, img1.get(y * img1.width() + i / 2));
|
|
|
|
|
img_final.set(y * img_final.width() + i + 1, img2.get(y * img2.width() + i / 2));
|
2022-10-03 11:55:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return img_final;
|
|
|
|
|
}
|
2021-02-15 21:20:05 +01:00
|
|
|
} // namespace chris
|
|
|
|
|
} // namespace proba
|