// SPDX-License-Identifier: MPL-2.0 #include "aare/RawFile.hpp" #include "aare/DetectorGeometry.hpp" #include "aare/PixelMap.hpp" #include "aare/ROIGeometry.hpp" #include "aare/algorithm.hpp" #include "aare/defs.hpp" #include "aare/logger.hpp" #include "aare/utils/utility_functions.hpp" #include #include using json = nlohmann::json; namespace aare { /** * @brief Get ROIs from disabled UDP ports * @param disabled_ports vector of disabled UDP ports * @param udp_port_types vector of UDP port types (e.g. "left", "right", "top", * "bottom") * @param pixels_per_module_x number of pixels per module in x direction * @param pixels_per_module_y number of pixels per module in y direction * @param modules_x number of modules in x direction * @param modules_y number of modules in y direction * @return vector of ROIs corresponding to the disabled UDP ports */ std::vector get_rois_from_disabled_udp_ports( std::vector &disabled_ports, std::vector &udp_port_types, const ssize_t pixels_per_module_x, const ssize_t pixels_per_module_y, const ssize_t modules_x, const ssize_t modules_y) { const size_t num_udp_port_types = udp_port_types.size(); size_t first_port = disabled_ports[0] % num_udp_port_types; bool all_ports_equal = std::all_of(disabled_ports.begin(), disabled_ports.end(), [&num_udp_port_types, first_port](size_t &port) { return port % num_udp_port_types == first_port; }); std::vector rois; // TODO: code assumes modules stacked on top - adapt if stacked horizontally if (all_ports_equal) { if (udp_port_types[first_port] == "left") { rois.push_back({pixels_per_module_x, 2 * pixels_per_module_x, 0, modules_y * pixels_per_module_y}); } if (udp_port_types[first_port] == "right") { rois.push_back( {0, pixels_per_module_x, 0, modules_y * pixels_per_module_y}); } if (udp_port_types[first_port] == "top") { rois.resize(disabled_ports.size()); // assumes euclidean coordinate system with origin at bottom left // corner of the detector std::transform( disabled_ports.begin(), disabled_ports.end(), rois.begin(), [&num_udp_port_types, pixels_per_module_x, pixels_per_module_y, modules_x, modules_y](size_t &port) { ssize_t module_idx = static_cast(port / num_udp_port_types); return ROI{0, modules_x * pixels_per_module_x, module_idx * 2 * pixels_per_module_y, (module_idx * 2 * pixels_per_module_y + pixels_per_module_y)}; }); } if (udp_port_types[first_port] == "bottom") { rois.resize(disabled_ports.size()); std::transform( disabled_ports.begin(), disabled_ports.end(), rois.begin(), [&num_udp_port_types, pixels_per_module_x, pixels_per_module_y, modules_x, modules_y](size_t &port) { ssize_t module_idx = static_cast(port / num_udp_port_types); return ROI{0, modules_x * pixels_per_module_x, module_idx * 2 * pixels_per_module_y + pixels_per_module_y, module_idx * 2 * pixels_per_module_y + 2 * pixels_per_module_y}; }); } } else { // iterate over all ports and create ROIs for each disabled port rois.reserve(disabled_ports.size()); for (auto disabled_port : disabled_ports) { std::string disabled_port_type = udp_port_types[disabled_port % num_udp_port_types]; // modulo needed as // indexing is // relative to half // module // // TODO: string_to? // - anyway defined // in // slsDetectorDefs ROI roi{}; ssize_t module_idx = static_cast(disabled_port / num_udp_port_types); if (disabled_port_type == "bottom") { // assumes euclidean coordinate system with origin at bottom // left corner roi = ROI{0, pixels_per_module_x, module_idx * 2 * pixels_per_module_y + pixels_per_module_y, module_idx * 2 * pixels_per_module_y + 2 * pixels_per_module_y}; } else if (disabled_port_type == "top") { roi = ROI{0, pixels_per_module_x, module_idx * 2 * pixels_per_module_y, (module_idx * 2 * pixels_per_module_y + pixels_per_module_y)}; } else if (disabled_port_type == "left") { roi = ROI{pixels_per_module_x, 2 * pixels_per_module_x, module_idx * 2 * pixels_per_module_y, module_idx * 2 * pixels_per_module_y + pixels_per_module_y}; } else if (disabled_port_type == "right") { roi = ROI{0, pixels_per_module_x, module_idx * 2 * pixels_per_module_y, module_idx * 2 * pixels_per_module_y + pixels_per_module_y}; } else { throw std::runtime_error( LOCATION + "Unknown UDP port type: " + disabled_port_type); } rois.push_back(roi); } if (udp_port_types == std::vector{"left", "right"}) { rois = merge_consecutive_rois(rois); } else if (udp_port_types == std::vector{"bottom", "top"}) { rois = merge_consecutive_rois(rois); } else { throw std::runtime_error(LOCATION + "Unsupported UDP port types"); } } return rois; } RawFile::RawFile(const std::filesystem::path &fname, const std::string &mode) : m_master(fname), m_geometry(m_master.geometry(), m_master.pixels_x(), m_master.pixels_y(), m_master.udp_interfaces_per_module(), m_master.quad()) { m_mode = mode; if (mode == "r") { // TODO: should we support both ROI and disabled udp port - which one // should have precedence? if (m_master.disabled_udp_ports().has_value() && m_master.disabled_udp_ports().value().size() > 0) { auto disabled_ports = m_master.disabled_udp_ports().value(); auto udp_port_types = m_master.udp_port_types().value(); std::vector rois = get_rois_from_disabled_udp_ports( disabled_ports, udp_port_types, m_master.pixels_x(), m_master.pixels_y(), m_geometry.modules_x(), m_geometry.modules_y()); m_subfiles.resize(rois.size()); m_ROI_geometries.reserve(rois.size()); for (size_t roi_index = 0; roi_index < rois.size(); ++roi_index) { m_ROI_geometries.push_back( ROIGeometry(rois[roi_index], m_geometry)); open_subfiles(roi_index); } } else if (m_master.rois().has_value()) { m_ROI_geometries.reserve(m_master.rois()->size()); m_subfiles.resize(m_master.rois()->size()); // iterate over all ROIS size_t roi_index = 0; const auto rois = m_master.rois().value(); for (const auto &roi : rois) { m_ROI_geometries.push_back(ROIGeometry(roi, m_geometry)); // open subfiles open_subfiles(roi_index); ++roi_index; } } else { // no ROI use full detector m_subfiles.resize(1); m_ROI_geometries.reserve(1); m_ROI_geometries.push_back(ROIGeometry(m_geometry)); open_subfiles(0); } } else { throw std::runtime_error(LOCATION + " Unsupported mode. Can only read RawFiles."); } } Frame RawFile::read_roi(const size_t roi_index) { if (!m_master.rois()) { throw std::runtime_error(LOCATION + "No ROIs defined in the master file."); } if (roi_index >= m_ROI_geometries.size()) { throw std::runtime_error(LOCATION + "ROI index out of range."); } return get_frame(m_current_frame++, roi_index); } std::vector RawFile::read_rois() { if (!m_master.rois()) { throw std::runtime_error(LOCATION + "No ROIs defined in the master file."); } const size_t num_rois = m_ROI_geometries.size(); std::vector frames; frames.reserve(num_rois); for (size_t roi_idx = 0; roi_idx < num_rois; ++roi_idx) { frames.push_back(get_frame(m_current_frame, roi_idx)); } ++m_current_frame; return frames; } Frame RawFile::read_frame() { if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Multiple ROIs present in file. " "Use read_ROIs() instead."); } return get_frame(m_current_frame++); } Frame RawFile::read_frame(size_t frame_number) { if (m_ROI_geometries.size() > 1) { throw std::runtime_error( LOCATION + "Multiple ROIs present in file. " "Use read_ROIs(const size_t frame_number) instead."); } seek(frame_number); return read_frame(); } void RawFile::read_into(std::byte *image_buf, size_t n_frames) { // TODO: implement this in a more efficient way if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs."); } for (size_t i = 0; i < n_frames; i++) { this->get_frame_into(m_current_frame++, image_buf); image_buf += bytes_per_frame(); } } void RawFile::read_into(std::byte *image_buf) { if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs. Use " "read_roi_into() for a single ROI instead."); } return get_frame_into(m_current_frame++, image_buf); } void RawFile::read_roi_into(std::byte *image_buf, const size_t roi_index, const size_t frame_number, DetectorHeader *header) { if (m_ROI_geometries.size() <= 1) { throw std::runtime_error(LOCATION + "No ROIs defined in the master file."); } if (roi_index >= num_rois()) { throw std::runtime_error(LOCATION + "ROI index out of range."); } return get_frame_into(frame_number, image_buf, roi_index, header); } void RawFile::read_into(std::byte *image_buf, DetectorHeader *header) { if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Cannot use read_into for multiple ROIs. Use " "read_roi_into() for a single ROI instead."); } return get_frame_into(m_current_frame++, image_buf, 0, header); } void RawFile::read_into(std::byte *image_buf, size_t n_frames, DetectorHeader *header) { // return get_frame_into(m_current_frame++, image_buf, header); if (m_ROI_geometries.size() > 1) { throw std::runtime_error( LOCATION + "Cannot use read_into for multiple ROIs."); // TODO: maybe // pass // roi_index so // one can use // read_into for // a specific // ROI } for (size_t i = 0; i < n_frames; i++) { this->get_frame_into(m_current_frame++, image_buf, 0, header); image_buf += bytes_per_frame(); if (header) header += m_ROI_geometries[0].num_modules_in_roi(); } } size_t RawFile::bytes_per_frame() { if (m_ROI_geometries.size() > 1) { throw std::runtime_error( LOCATION + "Pass the desired roi_index to bytes_per_frame to get " "bytes_per_frame for the specific ROI. "); } return bytes_per_frame(0); } size_t RawFile::bytes_per_frame(const size_t roi_index) { return m_ROI_geometries.at(roi_index).pixels_x() * m_ROI_geometries.at(roi_index).pixels_y() * m_master.bitdepth() / bits_per_byte; } size_t RawFile::pixels_per_frame() { if (m_ROI_geometries.size() > 1) { throw std::runtime_error( LOCATION + "Pass the desired roi_index to pixels_per_frame to get " "pixels_per_frame for the specific ROI. "); } return pixels_per_frame(0); } size_t RawFile::pixels_per_frame(const size_t roi_index) { return m_ROI_geometries.at(roi_index).pixels_x() * m_ROI_geometries.at(roi_index).pixels_y(); } DetectorType RawFile::detector_type() const { return m_master.detector_type(); } void RawFile::seek(size_t frame_index) { // check if the frame number is greater than the total frames // if frame_number == total_frames, then the next read will throw an // error if (frame_index > total_frames()) { throw std::runtime_error( fmt::format("frame number {} is greater than total frames {}", frame_index, total_frames())); } m_current_frame = frame_index; } size_t RawFile::tell() { return m_current_frame; } size_t RawFile::total_frames() const { return m_master.frames_in_file(); } size_t RawFile::rows() const { if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Pass the desired roi_index to rows to get " "rows for the specific ROI. "); } return rows(0); } size_t RawFile::rows(const size_t roi_index) const { return m_ROI_geometries.at(roi_index).pixels_y(); } size_t RawFile::cols() const { if (m_ROI_geometries.size() > 1) { throw std::runtime_error(LOCATION + "Pass the desired roi_index to cols to get " "cols for the specific ROI. "); } return cols(0); } size_t RawFile::cols(const size_t roi_index) const { return m_ROI_geometries.at(roi_index).pixels_x(); } size_t RawFile::bitdepth() const { return m_master.bitdepth(); } xy RawFile::geometry() const { return xy{static_cast(m_geometry.modules_y()), static_cast(m_geometry.modules_x())}; } size_t RawFile::n_modules() const { return m_geometry.n_modules(); }; size_t RawFile::num_rois() const { if (m_master.rois().has_value()) { return m_master.rois()->size(); } else { return 0; } } const ROIGeometry &RawFile::roi_geometries(size_t roi_index) const { return m_ROI_geometries[roi_index]; } std::vector RawFile::n_modules_in_roi() const { std::vector results(m_ROI_geometries.size()); std::transform( m_ROI_geometries.begin(), m_ROI_geometries.end(), results.begin(), [](const ROIGeometry &roi) { return roi.num_modules_in_roi(); }); return results; } void RawFile::open_subfiles(const size_t roi_index) { if (m_mode == "r") { m_subfiles[roi_index].reserve( m_ROI_geometries[roi_index].num_modules_in_roi()); auto module_indices = m_ROI_geometries[roi_index].module_indices_in_roi(); for (const size_t i : module_indices) { const auto pos = m_geometry.get_module_geometries(i); m_subfiles[roi_index].emplace_back(std::make_unique( m_master.data_fname(i, 0), m_master.detector_type(), pos.height, pos.width, m_master.bitdepth(), pos.row_index, pos.col_index)); } } else { throw std::runtime_error(LOCATION + "Unsupported mode. Can only read RawFiles."); } } DetectorHeader RawFile::read_header(const std::filesystem::path &fname) { DetectorHeader h{}; FILE *fp = fopen(fname.string().c_str(), "r"); if (!fp) throw std::runtime_error( fmt::format("Could not open: {} for reading", fname.string())); size_t const rc = fread(reinterpret_cast(&h), sizeof(h), 1, fp); if (rc != 1) throw std::runtime_error(LOCATION + "Could not read header from file"); if (fclose(fp)) { throw std::runtime_error(LOCATION + "Could not close file"); } return h; } RawMasterFile RawFile::master() const { return m_master; } Frame RawFile::get_frame(size_t frame_index, const size_t roi_index) { auto f = Frame(m_ROI_geometries[roi_index].pixels_y(), m_ROI_geometries[roi_index].pixels_x(), Dtype::from_bitdepth(m_master.bitdepth())); std::byte *frame_buffer = f.data(); get_frame_into(frame_index, frame_buffer, roi_index); return f; } size_t RawFile::bytes_per_pixel() const { return m_master.bitdepth() / 8; } void RawFile::get_frame_into(size_t frame_index, std::byte *frame_buffer, const size_t roi_index, DetectorHeader *header) { LOG(logDEBUG) << "RawFile::get_frame_into(" << frame_index << ")"; if (frame_index >= total_frames()) { throw std::runtime_error(LOCATION + "Frame number out of range"); } std::vector frame_numbers( m_ROI_geometries[roi_index].num_modules_in_roi()); std::vector frame_indices( m_ROI_geometries[roi_index].num_modules_in_roi(), frame_index); // sync the frame numbers if (m_ROI_geometries[roi_index].num_modules_in_roi() != 1) { // if we have more than one module for (size_t part_idx = 0; part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); ++part_idx) { frame_numbers[part_idx] = m_subfiles[roi_index][part_idx]->frame_number(frame_index); } // 1. if frame number vector is the same break while (!all_equal(frame_numbers)) { // 2. find the index of the minimum frame number, auto min_frame_idx = std::distance( frame_numbers.begin(), std::min_element(frame_numbers.begin(), frame_numbers.end())); // 3. increase its index and update its respective frame // number frame_indices[min_frame_idx]++; // 4. if we can't increase its index => throw error if (frame_indices[min_frame_idx] >= total_frames()) { throw std::runtime_error(LOCATION + "Frame number out of range"); } frame_numbers[min_frame_idx] = m_subfiles[roi_index][min_frame_idx]->frame_number( frame_indices[min_frame_idx]); } } if (m_master.geometry().col == 1) { // get the part from each subfile and copy it to the frame for (size_t part_idx = 0; part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); ++part_idx) { auto corrected_idx = frame_indices[part_idx]; // This is where we start writing auto offset = (m_geometry .get_module_geometries( m_ROI_geometries[roi_index].module_indices_in_roi( part_idx)) .origin_y * m_ROI_geometries[roi_index].pixels_x() + m_geometry .get_module_geometries( m_ROI_geometries[roi_index].module_indices_in_roi( part_idx)) .origin_x) * m_master.bitdepth() / 8; if (m_geometry .get_module_geometries( m_ROI_geometries[roi_index].module_indices_in_roi( part_idx)) .origin_x != 0) throw std::runtime_error( LOCATION + " Implementation error. x pos not 0."); // TODO: // origin // can still // change if // roi // changes // TODO! What if the files don't match? m_subfiles[roi_index][part_idx]->seek(corrected_idx); m_subfiles[roi_index][part_idx]->read_into(frame_buffer + offset, header); if (header) ++header; } } else { // TODO! should we read row by row? // create a buffer large enough to hold a full module auto bytes_per_part = m_master.pixels_y() * m_master.pixels_x() * m_master.bitdepth() / 8; // TODO! replace with image_size_in_bytes // TODO // shouldnt it only be the module size? - check auto *part_buffer = new std::byte[bytes_per_part]; // TODO! if we have many submodules we should reorder them on // the module level for (size_t part_idx = 0; part_idx != m_ROI_geometries[roi_index].num_modules_in_roi(); ++part_idx) { auto pos = m_geometry.get_module_geometries( m_ROI_geometries[roi_index].module_indices_in_roi(part_idx)); auto corrected_idx = frame_indices[part_idx]; m_subfiles[roi_index][part_idx]->seek(corrected_idx); m_subfiles[roi_index][part_idx]->read_into(part_buffer, header); if (header) ++header; for (size_t cur_row = 0; cur_row < static_cast(pos.height); cur_row++) { auto irow = (pos.origin_y + cur_row); auto icol = pos.origin_x; auto dest = (irow * m_ROI_geometries[roi_index].pixels_x() + icol); dest = dest * m_master.bitdepth() / 8; memcpy(frame_buffer + dest, part_buffer + cur_row * pos.width * m_master.bitdepth() / 8, pos.width * m_master.bitdepth() / 8); } } delete[] part_buffer; } } std::vector RawFile::read_n(size_t n_frames) { // TODO: implement this in a more efficient way if (num_rois() > 1) { throw std::runtime_error(LOCATION + "Multiple ROIs defined in the master " "file. Use " "read_num_rois for a specific ROI or use " "read_ROIs to read one frame after " "the other."); } std::vector frames; frames.reserve(n_frames); for (size_t i = 0; i < n_frames; i++) { frames.push_back(this->get_frame(m_current_frame)); m_current_frame++; } return frames; } std::vector RawFile::read_n_with_roi(const size_t n_frames, const size_t roi_index) { if (roi_index >= num_rois()) { throw std::runtime_error(LOCATION + "ROI index out of range."); } std::vector frames; frames.reserve(n_frames); for (size_t i = 0; i < n_frames; i++) { frames.push_back(this->get_frame(m_current_frame, roi_index)); m_current_frame++; } return frames; } size_t RawFile::frame_number(size_t frame_index) { if (frame_index >= m_master.frames_in_file()) { throw std::runtime_error(LOCATION + " Frame number out of range"); } return m_subfiles[0][0]->frame_number(frame_index); } } // namespace aare