diff --git a/src/buildblock/extend_projdata.cxx b/src/buildblock/extend_projdata.cxx index 59fa14288..abbaf1b75 100644 --- a/src/buildblock/extend_projdata.cxx +++ b/src/buildblock/extend_projdata.cxx @@ -27,6 +27,7 @@ #include "stir/round.h" #include "stir/warning.h" #include "stir/error.h" +#include "stir/format.h" START_NAMESPACE_STIR @@ -37,7 +38,8 @@ Array<3, float> extend_segment(const SegmentBySinogram& segment, const int view_extension, const int axial_extension, - const int tangential_extension) + const int tangential_extension, + const SegmentBySinogram* opposite_segment_ptr) { Array<3, float> out(segment); BasicCoordinate<3, int> min_dim, max_dim; @@ -58,6 +60,7 @@ extend_segment(const SegmentBySinogram& segment, // check, whether the projection data cover 180° or 360° bool flip_views = false; + bool use_opposite_segment = false; bool extend_without_wrapping = false; float min_phi = _PI, max_phi = -_PI; for (auto view = segment.get_proj_data_info_sptr()->get_min_view_num(); @@ -76,8 +79,29 @@ extend_segment(const SegmentBySinogram& segment, // use a rather large tolerance to cope with non-uniform sampling in BlocksOnCylindrical if (std::abs(phi_range - 2 * _PI) < 5 * average_phi_sampling) flip_views = false; // if views cover 360°, we can simply wrap around - else if ((std::abs(phi_range - _PI) < 5 * average_phi_sampling) && (segment.get_segment_num() == 0)) - flip_views = true; // if views cover 180°, the tangential positions need to be flipped + else if ((std::abs(phi_range - _PI) < 5 * average_phi_sampling)) + { + // 180°: the tangential positions need to be flipped when wrapping. + // For segment 0 the mirrored data is this same segment; for any + // other segment n, it lives in segment -n instead. + if (segment.get_segment_num() == 0) + { + flip_views = true; + } + else if (opposite_segment_ptr != nullptr) + { + flip_views = true; + use_opposite_segment = true; + } + else + { + extend_without_wrapping = true; + warning(format("extend_segment: 180 degree wrap-around extension for segment {} requires segment {} " + "which was not provided. Falling back to nearest-neighbour extension.", + segment.get_segment_num(), + -segment.get_segment_num())); + } + } else { extend_without_wrapping = true; @@ -85,6 +109,15 @@ extend_segment(const SegmentBySinogram& segment, "neighbour."); } + auto flip_source = [&](int axial_pos, int view, int tang_pos) -> float { + if (tang_pos < opposite_segment_ptr->get_min_tangential_pos_num() + || tang_pos > opposite_segment_ptr->get_max_tangential_pos_num()) + return 0.0F; + const int src_axial + = std::max(opposite_segment_ptr->get_min_index(), std::min(opposite_segment_ptr->get_max_index(), axial_pos)); + return (*opposite_segment_ptr)[src_axial][view][tang_pos]; + }; + // fill the view extensions by wrapping around for (int view_edge = 0; view_edge < view_extension; view_edge++) { @@ -95,6 +128,23 @@ extend_segment(const SegmentBySinogram& segment, out[axial_pos][min_dim[2] + view_edge] = out[axial_pos][min_dim[2] + view_extension]; out[axial_pos][max_dim[2] - view_edge] = out[axial_pos][max_dim[2] - view_extension]; } + else if (flip_views && use_opposite_segment) + { + // make_swapped_segment resolves which segment/axial/view the detector-pair-swapped + // LOR belongs to, but that swap alone does not apply the tangential sign flip that + // the view+pi wraparound itself requires (p(theta+pi, s) = p(theta, -s)). So we still + // need to read swapped_opposite at -tang_pos, not tang_pos, to get the correct value + // for this destination tangential position. + // This has been visually validated against the oblique sinograms. + + for (int tang_pos = min_dim[3]; tang_pos <= max_dim[3]; tang_pos++) + { + out[axial_pos][min_dim[2] + view_edge][tang_pos] + = flip_source(axial_pos, max_dim[2] - 2 * view_extension + view_edge + 1, -tang_pos); + out[axial_pos][max_dim[2] - view_extension + 1 + view_edge][tang_pos] + = flip_source(axial_pos, min_dim[2] + view_extension + view_edge, -tang_pos); + } + } else if (flip_views) { const int sym_dim = std::min(std::abs(min_dim[3]), max_dim[3]); diff --git a/src/buildblock/interpolate_projdata.cxx b/src/buildblock/interpolate_projdata.cxx index 8e0eee6a8..f9eb992df 100644 --- a/src/buildblock/interpolate_projdata.cxx +++ b/src/buildblock/interpolate_projdata.cxx @@ -21,6 +21,8 @@ */ #include "stir/ProjData.h" //#include "stir/display.h" +#include "stir/ProjDataInMemory.h" +#include "stir/ProjDataInterfile.h" #include "stir/ProjDataInfo.h" #include "stir/ProjDataInfoCylindricalNoArcCorr.h" #include "stir/ProjDataInfoGenericNoArcCorr.h" @@ -35,12 +37,70 @@ #include "stir/extend_projdata.h" #include "stir/numerics/sampling_functions.h" #include "stir/error.h" -#include +#include "stir/Coordinate4D.h" +#include "stir/IndexRange4D.h" +#include "stir/LORCoordinates.h" +#include +#include START_NAMESPACE_STIR namespace detail_interpolate_projdata { + +inline int +bspline_degree(BSpline::BSplineType type) +{ + switch (type) + { + case BSpline::near_n: + return 0; + case BSpline::linear: + return 1; + case BSpline::quadratic: + return 2; + case BSpline::cubic: + return 3; + case BSpline::quartic: + return 4; + case BSpline::quintic: + return 5; + case BSpline::oMoms: + return 3; + default: + return 0; + } + return 0; +} + +inline int +bspline_min_samples(BSpline::BSplineType type) +{ + return bspline_degree(type) + 1; +} + +// step an interpolation type down to the next-lowest degree +inline BSpline::BSplineType +bspline_step_down(BSpline::BSplineType type) +{ + switch (type) + { + case BSpline::oMoms: + case BSpline::cubic: + return BSpline::quadratic; + case BSpline::quartic: + return BSpline::cubic; + case BSpline::quintic: + return BSpline::quartic; + case BSpline::quadratic: + return BSpline::linear; + case BSpline::linear: + return BSpline::near_n; + default: + return BSpline::near_n; + } +} + /* Collection of functions to remove interleaving in non-arccorrected data. It does this by doubling the number of views, and filling in the new tangential positions by averaging the 4 neighbouring bins. @@ -517,6 +577,410 @@ interpolate_blocks_on_cylindrical_projdata(ProjData& proj_data_out, const ProjDa if (proj_data_out.set_segment(sino_3D_out) == Succeeded::no) return Succeeded::no; } + return Succeeded::yes; +} + +// Returns a copy of `other_segment` re-expressed in `target_segment_num`'s own +// (axial, view, tangential) coordinate convention, via a detector-position swap. +// After this transform, `result[axial][view][tang]` and a segment with +// segment_num == target_segment_num can be compared index-for-index directly -- +// no further view/tang mirroring needed anywhere downstream. +SegmentBySinogram +make_swapped_segment(const SegmentBySinogram& other_segment, + const ProjDataInfoCylindricalNoArcCorr& proj_data_info, + const int target_segment_num) +{ + auto result = proj_data_info.get_empty_segment_by_sinogram(target_segment_num, false); + + for (int axial_pos = result.get_min_index(); axial_pos <= result.get_max_index(); ++axial_pos) + for (int view = result.get_min_view_num(); view <= result.get_max_view_num(); ++view) + for (int tang = result.get_min_tangential_pos_num(); tang <= result.get_max_tangential_pos_num(); ++tang) + { + const Bin bin(target_segment_num, view, axial_pos, tang); + DetectionPositionPair<> det_pos_pair; + proj_data_info.get_det_pos_pair_for_bin(det_pos_pair, bin); + const DetectionPositionPair<> swapped(det_pos_pair.pos2(), det_pos_pair.pos1(), det_pos_pair.timing_pos()); + + Bin swapped_bin; + if (proj_data_info.get_bin_for_det_pos_pair(swapped_bin, swapped) == Succeeded::no) + { + result[axial_pos][view][tang] = 0.0F; + continue; + } + result[axial_pos][view][tang] + = other_segment[swapped_bin.axial_pos_num()][swapped_bin.view_num()][swapped_bin.tangential_pos_num()]; + } + return result; +} + +Succeeded +interpolate_projdata_3d(ProjData& proj_data_out, + const ProjData& proj_data_in, + const BSpline::BSplineType& spline_type, + const bool remove_interleaving, + bool clean_up) +{ + const ProjDataInfo& proj_data_info_in = *proj_data_in.get_proj_data_info_sptr(); + const ProjDataInfo& proj_data_info_out = *proj_data_out.get_proj_data_info_sptr(); + + shared_ptr scanner_sptr(new Scanner(*proj_data_info_in.get_scanner_sptr())); + + const auto proj_data_info_in_no_arc_corr_sptr + = dynamic_pointer_cast(proj_data_in.get_proj_data_info_sptr()); + if (!proj_data_info_in_no_arc_corr_sptr) + error("Expected the in projection data info to be a ProjDataInfoCylindricalNoArcCorr."); + + scanner_sptr->set_num_detectors_per_ring(proj_data_info_out.get_scanner_sptr()->get_num_detectors_per_ring()); + scanner_sptr->set_max_num_non_arccorrected_bins(proj_data_info_out.get_scanner_sptr()->get_max_num_non_arccorrected_bins()); + scanner_sptr->set_default_bin_size(proj_data_info_out.get_scanner_sptr()->get_default_bin_size()); + scanner_sptr->set_default_num_arccorrected_bins(proj_data_info_out.get_scanner_sptr()->get_default_num_arccorrected_bins()); + + auto proj_data_info_in_up_uptr = ProjDataInfo::construct_proj_data_info(scanner_sptr, + 1, + (int)(proj_data_info_in.get_num_segments() / 2), + proj_data_info_out.get_num_views(), + proj_data_info_out.get_num_tangential_poss(), + false); + + if (proj_data_info_in.get_scanner_sptr()->get_scanner_geometry() == "BlocksOnCylindrical") + { + // interpolate_axial_position(*projdata_out, *projdata_in_sptr); + return Succeeded::no; + } + + // check for the same ring radius + // This is strictly speaking only necessary for non-arccorrected data, but + // we leave it in for all cases. + if (fabs(proj_data_info_in.get_scanner_sptr()->get_inner_ring_radius() + - proj_data_info_out.get_scanner_sptr()->get_inner_ring_radius()) + > 1) + { + error("interpolate_projdata needs both projection to be of a scanner with the same ring radius"); + } + + // const std::size_t num_bins = proj_data_in_up_info_sptr->size_all(); + // const std::size_t bytes = num_bins * sizeof(float); + // const std::size_t threshold_bytes = std::size_t(1) << 30; // 1 GB, make this configurable + + // const bool store_in_memory = requested_store_in_memory && (bytes <= threshold_bytes); + // if (requested_store_in_memory && bytes > threshold_bytes) + // warning(boost::format("Intermediate projdata would need %1% MB, exceeding the %2% MB in-memory threshold — falling back to + // disk.") + // % (bytes / (1024*1024)) % (threshold_bytes / (1024*1024))); + + { + shared_ptr proj_data_info_in_up_sptr(std::move(proj_data_info_in_up_uptr)); + shared_ptr proj_data_in_up_sptr; + + if (clean_up) + { + proj_data_in_up_sptr = std::make_shared( + proj_data_in.get_exam_info_sptr(), proj_data_info_in_up_sptr, "tmp_in_up", std::ios::out); + } + else + { + proj_data_in_up_sptr = std::make_shared(proj_data_in.get_exam_info_sptr(), + proj_data_info_in_up_sptr, + true); // I pressume 1 but we should check! + } + + info("interpolate_projdata: Interpolating views and tangential positions ..."); + + BasicCoordinate<3, BSpline::BSplineType> these_types; + these_types[2] = these_types[3] = spline_type; + // these_types[1] = these_types[2] = these_types[3] = spline_type; + // BSpline::BSplinesRegularGrid<3, float, float> proj_data_interpolator(these_types); + +#ifdef STIR_OPENMP +# if _OPENMP < 201107 +# pragma omp parallel for +# else +# pragma omp parallel for schedule(dynamic) +# endif +#endif +#ifdef STIR_TOF + for (int i_tof_in = proj_data_in_up_sptr->get_min_tof_pos_num(); i_tof_in <= proj_data_in_up_sptr->get_max_tof_pos_num(); + ++i_tof_in) + { +#endif + for (int i_seg_in = proj_data_in_up_sptr->get_min_segment_num(); i_seg_in <= proj_data_in_up_sptr->get_max_segment_num(); + ++i_seg_in) + { + info(boost::format("Now processing segment #: %1%") % i_seg_in); + // for Cylindrical, spacing is regular in all directions, which makes mapping trivial + std::function(const BasicCoordinate<3, int>&)> index_converter; + + BasicCoordinate<3, double> offset, step; + const float in_sampling_m = proj_data_info_in.get_sampling_in_m(Bin(0, 0, 0, 0)); + const float out_sampling_m = proj_data_info_in_up_sptr->get_sampling_in_m(Bin(0, 0, 0, 0)); + + // offset in 'in' index units + offset[1] + = (proj_data_info_in_up_sptr->get_m(Bin(0, 0, 0, 0)) - proj_data_info_in.get_m(Bin(0, 0, 0, 0))) / in_sampling_m; + step[1] = out_sampling_m / in_sampling_m; + + const float in_sampling_phi = (proj_data_info_in.get_phi(Bin(i_seg_in, 1, 0, 0 /*, i_tof_in*/)) + - proj_data_info_in.get_phi(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/))) + / (remove_interleaving ? 2 : 1); + const float in_up_sampling_phi = proj_data_info_in_up_sptr->get_phi(Bin(i_seg_in, 1, 0, 0 /*, i_tof_in*/)) + - proj_data_info_in_up_sptr->get_phi(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/)); + + offset[2] = (proj_data_info_in_up_sptr->get_phi(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/)) + - proj_data_info_in.get_phi(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/))) + / in_sampling_phi; + step[2] = in_up_sampling_phi / in_sampling_phi; + + const float in_sampling_s = proj_data_info_in.get_sampling_in_s(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/)); + const float in_up_sampling_s = proj_data_info_in_up_sptr->get_sampling_in_s(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/)); + + offset[3] = (proj_data_info_in_up_sptr->get_s(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/)) + - proj_data_info_in.get_s(Bin(i_seg_in, 0, 0, 0 /*, i_tof_in*/))) + / in_sampling_s; + step[3] = in_up_sampling_s / in_sampling_s; + + // define a function to translate indices in the output proj data to indices in input proj data + index_converter = [ptr = proj_data_info_in_up_sptr.get(), offset, step]( + const BasicCoordinate<3, int>& index_out) -> BasicCoordinate<3, double> { + // translate to indices in input proj data + BasicCoordinate<3, double> index_in; + for (auto dim = 1; dim <= 3; dim++) + index_in[dim] = index_out[dim] * step[dim] + offset[dim]; + + return index_in; + }; + + auto segment = remove_interleaving + ? make_non_interleaved_segment(*(make_non_interleaved_proj_data_info(proj_data_info_in)), + proj_data_in.get_segment_by_sinogram(i_seg_in /*,i_tof_in*/)) + : proj_data_in.get_segment_by_sinogram(i_seg_in /*,i_tof_in*/); + + const Array<3, float> extended3 = [&]() -> Array<3, float> { + if (i_seg_in != 0) + { + SegmentBySinogram opposite_segment = proj_data_in.get_segment_by_sinogram(-i_seg_in); + const SegmentBySinogram swapped_opposite + = make_swapped_segment(opposite_segment, *proj_data_info_in_no_arc_corr_sptr, segment.get_segment_num()); + // views, axial, tangential + return extend_segment(segment, 5, 0, 5, &swapped_opposite); + } + else + { + return extend_segment(segment, 5, 0, 5, nullptr); + } + }(); + + if (segment.get_num_axial_poss() < bspline_min_samples(spline_type)) + { + BSpline::BSplineType axial_type = spline_type; + while (segment.get_num_axial_poss() < bspline_min_samples(axial_type) && axial_type != BSpline::near_n) + { + axial_type = bspline_step_down(axial_type); + } + warning( + (boost::format( + "The axial support (%1%) for interpolation degree (%2%) was not sufficient. Switching to degree (%3%).") + % segment.get_num_axial_poss() % spline_type % axial_type) + .str()); + these_types[1] = axial_type; + } + else + these_types[1] = spline_type; + + BSpline::BSplinesRegularGrid<3, float, float> proj_data_interpolator(these_types); + proj_data_interpolator.set_coef(extended3); + auto sino_3D_out = proj_data_info_in_up_sptr->get_empty_segment_by_sinogram(i_seg_in, false); + sample_function_using_index_converter(sino_3D_out, proj_data_interpolator, index_converter); + if (proj_data_in_up_sptr->set_segment(sino_3D_out) == Succeeded::no) + { + warning(boost::format("We could not set the segment (%1%) in proj_data_in_up") % i_seg_in); + } + } + +#ifdef STIR_TOF + } +#endif + } + + info("interpolate_projdata: Finished interpolating views and tangential positions!"); + + //! I don't think we need to re-read. + const auto proj_data_in_up = ProjData::read_from_file("tmp_in_up.hs"); + const auto proj_data_info_in_up_sptr = proj_data_in_up->get_proj_data_info_sptr(); + + const auto proj_data_info_out_no_arc_corr_sptr + = dynamic_pointer_cast(proj_data_out.get_proj_data_info_sptr()); + if (!proj_data_info_out_no_arc_corr_sptr) + error("Expected the up-sampled projection data info to be a ProjDataInfoCylindricalNoArcCorr."); + + // const auto cloned_proj_data_info_in_up_sptr = proj_data_in_up->get_proj_data_info_sptr()->create_shared_clone(); + const auto proj_data_info_in_up_no_arc_corr_sptr + = dynamic_pointer_cast(proj_data_info_in_up_sptr); + if (!proj_data_info_in_up_no_arc_corr_sptr) + error("Expected the up-sampled projection data info to be a ProjDataInfoCylindricalNoArcCorr."); + + // Add two more rings, to have a wider space for the interpolation, this should depend to the type of interpolation + IndexRange4D mich_index(0, + proj_data_info_in_up_sptr->get_scanner_sptr()->get_num_rings() - 1, + 0, + proj_data_info_in_up_sptr->get_scanner_sptr()->get_num_rings() - 1, + 0, + proj_data_info_out.get_scanner_sptr()->get_num_detectors_per_ring() - 1, + 0, + proj_data_info_out.get_scanner_sptr()->get_num_detectors_per_ring() - 1); + + BasicCoordinate<4, BSpline::BSplineType> these_types_4; + // these_types_4[1] = these_types_4[2] = these_types_4[3] = these_types_4[4] = spline_type; + these_types_4[1] = these_types_4[2] = spline_type; // r1, r2 — interpolate + these_types_4[3] = these_types_4[4] = BSpline::near_n; // c1, c2 — just snap + + const int num_rings_in = proj_data_info_in_up_no_arc_corr_sptr->get_scanner_sptr()->get_num_rings(); + const int num_rings_out = proj_data_info_out_no_arc_corr_sptr->get_scanner_sptr()->get_num_rings(); + const double ring_ratio = proj_data_info_out_no_arc_corr_sptr->get_ring_spacing() + / proj_data_info_in_up_no_arc_corr_sptr->get_ring_spacing(); // 0.4/2 = 0.2 + +#ifdef STIR_TOF // Now it is better to NOT parallelise the TOF bins but the geometric loops. + info("interpolate_projdata: Creating michelogram for 3D interpolation ..."); + for (int i_tof_in = proj_data_info_in_up_sptr->get_min_tof_pos_num(); + i_tof_in <= proj_data_info_in_up_sptr->get_max_tof_pos_num(); + ++i_tof_in) + { + const int cur_tof = i_tof_in; +#endif + + // Create the 4D Michelogram R1.R1.D1.D2 + Array<4, float> downsampled_array_4d(mich_index); + downsampled_array_4d.fill(0.0); + +#ifdef STIR_OPENMP +# pragma omp parallel for schedule(dynamic) // collapse(3) +#endif + for (int i_seg = proj_data_info_in_up_sptr->get_min_segment_num(); + i_seg <= proj_data_info_in_up_sptr->get_max_segment_num(); + ++i_seg) + { + int min_axial_pos = proj_data_info_in_up_sptr->get_min_axial_pos_num(i_seg); + int max_axial_pos = proj_data_info_in_up_sptr->get_max_axial_pos_num(i_seg); + + // info( std::string("interpolate_projdata: Processing segment:") << i_seg ); + info(boost::format("interpolate_projdata: Processing segment: %1%") % i_seg); + + SegmentBySinogram sino3D = proj_data_in_up->get_segment_by_sinogram(i_seg /*, cur_tof*/); + + for (int i_axial = min_axial_pos; i_axial <= max_axial_pos; ++i_axial) + { + ProjDataInfoCylindrical::RingNumPairs ring_pairs + = proj_data_info_in_up_no_arc_corr_sptr->get_all_ring_pairs_for_segment_axial_pos_num(i_seg, i_axial); + if (ring_pairs.size() == 0) + { + warning(boost::format("zero number of ring pairs for segment %1%, axial %2%") % i_seg, i_axial); + continue; + } + // int r1, r2; + // proj_data_info_in_up_no_arc_corr_sptr->get_ring_pair_for_segment_axial_pos_num(r1, r2, i_seg, i_axial); + // std::cout << r1 << " " << r2 << " " << ring_pairs[0].first << " "<< ring_pairs[0].second << std::endl; + // For the view and tangential position we have to use the finer template, as otherwise we might run + // into problems with the downsampled. + for (int i_view = proj_data_info_in_up_no_arc_corr_sptr->get_min_view_num(); + i_view <= proj_data_info_in_up_no_arc_corr_sptr->get_max_view_num(); + ++i_view) + { + for (int i_tang = proj_data_info_in_up_no_arc_corr_sptr->get_min_tangential_pos_num(); + i_tang <= proj_data_info_in_up_no_arc_corr_sptr->get_max_tangential_pos_num(); + ++i_tang) + { + int c1, c2; + proj_data_info_in_up_no_arc_corr_sptr->get_det_num_pair_for_view_tangential_pos_num(c1, c2, i_view, i_tang); + + for (size_t i_ring_pair = 0; i_ring_pair < ring_pairs.size(); ++i_ring_pair) + { + if (ring_pairs[i_ring_pair].first >= 0 && ring_pairs[i_ring_pair].second >= 0 + && ring_pairs[i_ring_pair].first < proj_data_info_in_up_sptr->get_scanner_sptr()->get_num_rings() + && ring_pairs[i_ring_pair].second < proj_data_info_in_up_sptr->get_scanner_sptr()->get_num_rings()) + downsampled_array_4d[ring_pairs[i_ring_pair].first][ring_pairs[i_ring_pair].second][c1][c2] + = sino3D[i_axial][i_view][i_tang]; + } + // downsampled_array_4d[r1][r2][c1][c2] = sino3D[i_axial][i_view][i_tang]; + } + } + } + } + + BSpline::BSplinesRegularGrid<4, float, float> mich_data_interpolator(these_types_4); + mich_data_interpolator.set_coef(downsampled_array_4d); + + info(boost::format("interpolate_projdata: Finished creating michelogram for TOF position %1%!") % cur_tof); + +#ifdef STIR_OPENMP +# pragma omp parallel for schedule(dynamic) +#endif + for (int i_seg = proj_data_info_out_no_arc_corr_sptr->get_min_segment_num(); + i_seg <= proj_data_info_out_no_arc_corr_sptr->get_max_segment_num(); + ++i_seg) + { + int min_axial_pos = proj_data_info_out_no_arc_corr_sptr->get_min_axial_pos_num(i_seg); + int max_axial_pos = proj_data_info_out_no_arc_corr_sptr->get_max_axial_pos_num(i_seg); + auto _sino3D + = proj_data_info_out_no_arc_corr_sptr->get_empty_segment_by_sinogram(i_seg, false /*, + cur_tof*/); + info(boost::format("interpolate_projdata: Interpolating michelogram, segment: %1%") % i_seg); + + for (int i_view = proj_data_info_out_no_arc_corr_sptr->get_min_view_num(); + i_view <= proj_data_info_out_no_arc_corr_sptr->get_max_view_num(); + ++i_view) + { + + for (int i_tang = proj_data_info_out_no_arc_corr_sptr->get_min_tangential_pos_num(); + i_tang <= proj_data_info_out_no_arc_corr_sptr->get_max_tangential_pos_num(); + ++i_tang) + { + + int d1, d2; + proj_data_info_out_no_arc_corr_sptr->get_det_num_pair_for_view_tangential_pos_num(d1, + d2, + i_view, /*view*/ + i_tang /* tangential pos */); + + for (int i_axial = min_axial_pos; i_axial <= max_axial_pos; ++i_axial) + { + // std::cout << i_axial << " " << i_view << " " << i_tang << std::endl; + float value = 0.0; + // int dr1, dr2; + // proj_data_info_out_no_arc_corr_sptr->get_ring_pair_for_segment_axial_pos_num(dr1, dr2, i_seg, i_axial); + ProjDataInfoCylindrical::RingNumPairs ring_pairs + = proj_data_info_out_no_arc_corr_sptr->get_all_ring_pairs_for_segment_axial_pos_num(i_seg, i_axial); + // if(dr1 != ring_pairs[0].first || dr2 != ring_pairs[0].second) + // std::cout << ring_pairs.size() << ": " << dr1 << " " << dr2 << " " << ring_pairs[0].first << " "<< + // ring_pairs[0].second << std::endl; + for (size_t i_ring_pair = 0; i_ring_pair < ring_pairs.size(); ++i_ring_pair) + { + // std::cout < pos = make_coordinate(r1, r2, (double)(d1), (double)(d2)); + value += mich_data_interpolator(pos); // * norm_seg[cur_slice][cur_phi * mh_normsino.numray + + } + value /= static_cast(ring_pairs.size()); + _sino3D[i_axial][i_view][i_tang] = value; + } + } + } + if (proj_data_out.set_segment(_sino3D) == Succeeded::no) + error("set_segment_failed"); + } + +#ifdef STIR_TOF + } +#endif + + info("interpolate_projdata: Finished michelogram interpolation!"); + if (clean_up) + { + info("Cleaning up tmp_in_up"); + std::remove("tmp_in_up.hs"); + std::remove("tmp_in_up.s"); + } return Succeeded::yes; } diff --git a/src/buildblock/scale_sinograms.cxx b/src/buildblock/scale_sinograms.cxx index dd67af64e..b5b95f41d 100644 --- a/src/buildblock/scale_sinograms.cxx +++ b/src/buildblock/scale_sinograms.cxx @@ -97,7 +97,7 @@ get_scale_factors_per_sinogram(const ProjData& numerator_proj_data, { warning("Problem at segment %d, axial pos %d in finding sinogram scaling factor.\n" "Weighted data in denominator %g is very small compared to total in sinogram %g.\n" - "Adjust weights?.\n" + "Adjust weights?. This could be an axial gap.\n" "I will use scale factor %g", bin.segment_num(), bin.axial_pos_num(), diff --git a/src/include/stir/extend_projdata.h b/src/include/stir/extend_projdata.h index fef294edc..4609b2e8f 100644 --- a/src/include/stir/extend_projdata.h +++ b/src/include/stir/extend_projdata.h @@ -45,7 +45,14 @@ This is probably only useful before calling interpolation routines, or for FORE. Array<3, float> extend_segment(const SegmentBySinogram& segment, const int view_extension = 5, const int axial_extension = 5, - const int tangential_extension = 5); + const int tangential_extension = 5, + const SegmentBySinogram* opposite_segment_ptr = nullptr); + +// Array<3, float> extend_segment(const SegmentBySinogram& segment, +// const SegmentBySinogram& inv_segment, +// const int view_extension = 5, +// const int axial_extension = 5, +// const int tangential_extension = 5); //@} END_NAMESPACE_STIR diff --git a/src/include/stir/interpolate_projdata.h b/src/include/stir/interpolate_projdata.h index 0fe97060c..3571fe850 100644 --- a/src/include/stir/interpolate_projdata.h +++ b/src/include/stir/interpolate_projdata.h @@ -61,6 +61,16 @@ Succeeded interpolate_projdata(ProjData& proj_data_out, const bool remove_interleaving); Succeeded interpolate_blocks_on_cylindrical_projdata(ProjData& proj_data_out, const ProjData& proj_data_in, bool remove_interleaving); + +Succeeded interpolate_projdata_3d(ProjData& proj_data_out, + const ProjData& proj_data_in, + const BSpline::BSplineType& spline_type, + const bool remove_interleaving = false, + bool clean_up = true); +SegmentBySinogram make_swapped_segment(const SegmentBySinogram& other_segment, + const ProjDataInfoCylindricalNoArcCorr& proj_data_info, + const int target_segment_num); + //@} END_NAMESPACE_STIR diff --git a/src/include/stir/numerics/BSplinesRegularGrid.inl b/src/include/stir/numerics/BSplinesRegularGrid.inl index 5dcf5c6db..9e7cdaaf6 100644 --- a/src/include/stir/numerics/BSplinesRegularGrid.inl +++ b/src/include/stir/numerics/BSplinesRegularGrid.inl @@ -33,7 +33,13 @@ BSplinesRegularGrid::set_privat { this->_spline_types = this_type; for (int i = 1; i <= num_dimensions; ++i) - detail::set_BSpline_values(this->_z1s[i], this->_z2s[i], this->_lambdas[i], this_type[i]); + { + constantsT z1{}, z2{}, lambda{}; + detail::set_BSpline_values(z1, z2, lambda, this_type[i]); + this->_z1s[i] = z1; + this->_z2s[i] = z2; + this->_lambdas[i] = lambda; + } } template diff --git a/src/include/stir/numerics/sampling_functions.h b/src/include/stir/numerics/sampling_functions.h index 6b619a023..92502392e 100644 --- a/src/include/stir/numerics/sampling_functions.h +++ b/src/include/stir/numerics/sampling_functions.h @@ -19,6 +19,8 @@ \author Markus Jehl */ +#include "stir/Array.h" +#include "stir/common.h" START_NAMESPACE_STIR /*! @@ -69,6 +71,12 @@ inline void sample_function_on_regular_grid(Array<3, elemT>& out, template inline void sample_function_using_index_converter(Array<3, elemT>& out, FunctionType func, Lambda&& index_converter); +template +inline void sample_function_on_regular_grid(Array<2, elemT>& out, + FunctionType func, + const BasicCoordinate<2, positionT>& offset, + const BasicCoordinate<2, positionT>& step); + END_NAMESPACE_STIR #include "stir/numerics/sampling_functions.inl" diff --git a/src/include/stir/scatter/ScatterEstimation.h b/src/include/stir/scatter/ScatterEstimation.h index 84d91e745..c52d5b496 100644 --- a/src/include/stir/scatter/ScatterEstimation.h +++ b/src/include/stir/scatter/ScatterEstimation.h @@ -105,7 +105,8 @@ class ScatterEstimation : public ParsingObject const float max_scale_factor, const unsigned half_filter_width, BSpline::BSplineType spline_type = BSpline::BSplineType::linear, - const bool remove_interleaving = true); + const bool remove_interleaving = true, + const bool do_3D = false); //! Default constructor (calls set_defaults()) ScatterEstimation(); diff --git a/src/scatter_buildblock/ScatterEstimation.cxx b/src/scatter_buildblock/ScatterEstimation.cxx index 093922242..833df0c8b 100644 --- a/src/scatter_buildblock/ScatterEstimation.cxx +++ b/src/scatter_buildblock/ScatterEstimation.cxx @@ -497,7 +497,9 @@ ScatterEstimation::set_up() error("ScatterEstimation: Please define a scatter simulation method. Aborting."); if (!run_in_2d_projdata) - error("ScatterEstimation: Currently, only running the estimation in 2D is supported."); + warning("ScatterEstimation: Currently, only running the estimation in 2D is supported."); + else + warning("This is highly experimental. Try it on your own risk!"); if (!this->recompute_mask_projdata) { @@ -523,16 +525,18 @@ ScatterEstimation::set_up() #if 1 // Calculate the SSRB - if (input_projdata_sptr->get_num_segments() > 1) - { - info("ScatterEstimation: Running SSRB on input data..."); - this->input_projdata_2d_sptr = make_2D_projdata_sptr(this->input_projdata_sptr); - } - else + if (run_in_2d_projdata) { - input_projdata_2d_sptr = input_projdata_sptr; + if (input_projdata_sptr->get_num_segments() > 1) + { + info("ScatterEstimation: Running SSRB on input data..."); + this->input_projdata_2d_sptr = make_2D_projdata_sptr(this->input_projdata_sptr); + } + else + { + input_projdata_2d_sptr = input_projdata_sptr; + } } - #else { std::string tmp_input2D = "./extras/nema_proj_f1g1d0b0.hs_2d.hs"; @@ -664,91 +668,114 @@ ScatterEstimation::set_up_iterative(shared_ptrset_input_data(this->input_projdata_sptr); - // - // Multiplicative projdata - // - shared_ptr tmp_atten_projdata_sptr = this->get_attenuation_correction_factors_sptr(this->multiplicative_binnorm_sptr); - shared_ptr atten_projdata_2d_sptr; + if (run_in_2d_projdata) + { + info("ScatterEstimation: 3.Calculating the attenuation projection data..."); + // + // Multiplicative projdata + // + shared_ptr tmp_atten_projdata_sptr + = this->get_attenuation_correction_factors_sptr(this->multiplicative_binnorm_sptr); + shared_ptr atten_projdata_2d_sptr; - info("ScatterEstimation: 3.Calculating the attenuation projection data..."); + if (tmp_atten_projdata_sptr->get_num_segments() > 1) + { + info("ScatterEstimation: 3.1. Running SSRB on attenuation correction coefficients ..."); - if (tmp_atten_projdata_sptr->get_num_segments() > 1) - { - info("ScatterEstimation: Running SSRB on attenuation correction coefficients ..."); + std::string out_filename = "tmp_atten_sino_2d.hs"; + atten_projdata_2d_sptr = make_2D_projdata_sptr(tmp_atten_projdata_sptr, out_filename); + } + else + { + // TODO: this needs more work. -- Setting directly 2D proj_data is buggy right now. + atten_projdata_2d_sptr = tmp_atten_projdata_sptr; + } + + info("ScatterEstimation: 4.Calculating the normalisation data..."); + shared_ptr norm3d_sptr = this->get_normalisation_object_sptr(this->multiplicative_binnorm_sptr); + shared_ptr norm_coeff_2d_sptr; + + if (input_projdata_sptr->get_num_segments() > 1) + { + // Some BinNormalisation classes don't know about SSRB. + // we need to get norm2d=1/SSRB(1/norm3d)) + + info("ScatterEstimation: 4.1 Constructing 2D normalisation coefficients ..."); + + std::string out_filename = "tmp_inverted_normdata.hs"; + shared_ptr inv_projdata_3d_sptr + = create_new_proj_data(out_filename, + this->input_projdata_sptr->get_exam_info_sptr(), + this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); + inv_projdata_3d_sptr->fill(1.f); + + // Essentially since inv_projData_sptr is 1s then this is an inversion. + // inv_projdata_sptr = 1/norm3d + norm3d_sptr->undo(*inv_projdata_3d_sptr); - std::string out_filename = "tmp_atten_sino_2d.hs"; - atten_projdata_2d_sptr = make_2D_projdata_sptr(tmp_atten_projdata_sptr, out_filename, true); + info("ScatterEstimation: 4.2 Performing SSRB on efficiency factors ..."); + + shared_ptr norm_projdata_2d_sptr = make_2D_projdata_sptr(inv_projdata_3d_sptr); + norm_projdata_2d_sptr->write_to_file("extras/tmp_normdata_2d.hs"); + + // Crucial: Avoid divisions by zero!! + // This should be resolved after https://github.com/UCL/STIR/issues/348 + pow_times_add min_threshold(0.0f, 1.0f, 1.0f, 1E-20f, NumericInfo().max_value()); + apply_to_proj_data(*norm_projdata_2d_sptr, min_threshold); + + pow_times_add invert(0.0f, 1.0f, -1.0f, NumericInfo().min_value(), NumericInfo().max_value()); + apply_to_proj_data(*norm_projdata_2d_sptr, invert); + + norm_coeff_2d_sptr.reset(new BinNormalisationFromProjData(norm_projdata_2d_sptr)); + } + else + { + norm_coeff_2d_sptr = norm3d_sptr; + } + + shared_ptr atten_coeff_2d_sptr(new BinNormalisationFromProjData(atten_projdata_2d_sptr)); + this->multiplicative_binnorm_2d_sptr.reset(new ChainedBinNormalisation(norm_coeff_2d_sptr, atten_coeff_2d_sptr)); + + this->multiplicative_binnorm_2d_sptr->set_up( + this->back_projdata_sptr->get_exam_info_sptr(), + this->input_projdata_2d_sptr->get_proj_data_info_sptr()->create_shared_clone()); + iterative_object->get_objective_function_sptr()->set_normalisation_sptr(multiplicative_binnorm_2d_sptr); } else { - // TODO: this needs more work. -- Setting directly 2D proj_data is buggy right now. - atten_projdata_2d_sptr = tmp_atten_projdata_sptr; - } - - info("ScatterEstimation: 4.Calculating the normalisation data..."); - { - if (run_in_2d_projdata) - { - shared_ptr norm3d_sptr = this->get_normalisation_object_sptr(this->multiplicative_binnorm_sptr); - shared_ptr norm_coeff_2d_sptr; - - if (input_projdata_sptr->get_num_segments() > 1) - { - // Some BinNormalisation classes don't know about SSRB. - // we need to get norm2d=1/SSRB(1/norm3d)) - - info("ScatterEstimation: Constructing 2D normalisation coefficients ..."); - - std::string out_filename = "tmp_inverted_normdata.hs"; - shared_ptr inv_projdata_3d_sptr - = create_new_proj_data(out_filename, - this->input_projdata_sptr->get_exam_info_sptr(), - this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); - inv_projdata_3d_sptr->fill(1.f); - - out_filename = "tmp_normdata_2d.hs"; - shared_ptr norm_projdata_2d_sptr - = create_new_proj_data(out_filename, - this->input_projdata_2d_sptr->get_exam_info_sptr(), - this->input_projdata_2d_sptr->get_proj_data_info_sptr()->create_shared_clone()); - norm_projdata_2d_sptr->fill(0.f); - - // Essentially since inv_projData_sptr is 1s then this is an inversion. - // inv_projdata_sptr = 1/norm3d - norm3d_sptr->undo(*inv_projdata_3d_sptr); - - info("ScatterEstimation: Performing SSRB on efficiency factors ..."); - - norm_projdata_2d_sptr = make_2D_projdata_sptr(inv_projdata_3d_sptr); - - // Crucial: Avoid divisions by zero!! - // This should be resolved after https://github.com/UCL/STIR/issues/348 - pow_times_add min_threshold(0.0f, 1.0f, 1.0f, 1E-20f, NumericInfo().max_value()); - apply_to_proj_data(*norm_projdata_2d_sptr, min_threshold); - - pow_times_add invert(0.0f, 1.0f, -1.0f, NumericInfo().min_value(), NumericInfo().max_value()); - apply_to_proj_data(*norm_projdata_2d_sptr, invert); - - norm_coeff_2d_sptr.reset(new BinNormalisationFromProjData(norm_projdata_2d_sptr)); - } - else - { - norm_coeff_2d_sptr = norm3d_sptr; - } - - shared_ptr atten_coeff_2d_sptr(new BinNormalisationFromProjData(atten_projdata_2d_sptr)); - this->multiplicative_binnorm_2d_sptr.reset(new ChainedBinNormalisation(norm_coeff_2d_sptr, atten_coeff_2d_sptr)); - - this->multiplicative_binnorm_2d_sptr->set_up( - this->input_projdata_2d_sptr->get_exam_info_sptr(), - this->input_projdata_2d_sptr->get_proj_data_info_sptr()->create_shared_clone()); - iterative_object->get_objective_function_sptr()->set_normalisation_sptr(multiplicative_binnorm_2d_sptr); - } - else // run_in_2d_projdata + // shared_ptr norm_coeff_3d_sptr; + // // run_in_2d_projdata + // // I need the inverted norm factors here!!!! << Tomorrow + // // Create the inverted version of the normalisation factors with very large values in the gaps. + // //As for reconstruction. + // shared_ptr norm3d_sptr = + // this->get_normalisation_object_sptr(this->multiplicative_binnorm_sptr); + // std::string out_filename = "tmp_inverted_normdata.hs"; + // shared_ptr inv_projdata_3d_sptr = create_new_proj_data(out_filename, + // this->input_projdata_sptr->get_exam_info_sptr(), + // this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); + // inv_projdata_3d_sptr->fill(1.f); + + // norm3d_sptr->undo(*inv_projdata_3d_sptr); + + // // Crucial: Avoid divisions by zero!! + // // This should be resolved after https://github.com/UCL/STIR/issues/348 + // pow_times_add min_threshold (0.0f, 1.0f, 1.0f, 1E-20f, NumericInfo().max_value()); + // apply_to_proj_data(*inv_projdata_3d_sptr, min_threshold); + + // pow_times_add invert (0.0f, 1.0f, -1.0f, NumericInfo().min_value(), NumericInfo().max_value()); + // apply_to_proj_data(*inv_projdata_3d_sptr, invert); + + // norm_coeff_3d_sptr.reset(new BinNormalisationFromProjData(inv_projdata_3d_sptr)); + + // shared_ptratten_coeff_sptr(new + // BinNormalisationFromProjData(tmp_atten_projdata_sptr)); this->multiplicative_binnorm_3d_sptr.reset( + // new ChainedBinNormalisation(norm_coeff_3d_sptr, atten_coeff_sptr)); + // this->multiplicative_binnorm_3d_sptr->set_up(this->input_projdata_sptr->get_exam_info_sptr(), + // this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); iterative_object->get_objective_function_sptr()->set_normalisation_sptr(multiplicative_binnorm_sptr); - } + } info("ScatterEstimation: Done normalisation coefficients."); - // // Set background (randoms) projdata // @@ -756,18 +783,16 @@ ScatterEstimation::set_up_iterative(shared_ptrback_projdata_sptr)) { - if (back_projdata_sptr->get_num_segments() > 1) - { - info("ScatterEstimation: Running SSRB on the background data ..."); - - this->back_projdata_2d_sptr = make_2D_projdata_sptr(back_projdata_sptr); - } - else + if (run_in_2d_projdata) { - this->back_projdata_2d_sptr = back_projdata_sptr; + if (back_projdata_sptr->get_num_segments() > 1) + { + info("ScatterEstimation: 5.2 Running SSRB on the background data ..."); + this->back_projdata_2d_sptr = make_2D_projdata_sptr(back_projdata_sptr); + } } } - else // We will need a background for the scatter, so let's create a simple empty ProjData + else { if (run_in_2d_projdata) { @@ -970,16 +995,17 @@ ScatterEstimation::process_data() scaled_est_projdata_sptr->fill(0.F); - upsample_and_fit_scatter_estimate(*scaled_est_projdata_sptr, - *data_to_fit_projdata_sptr, - *unscaled_est_projdata_sptr, + upsample_and_fit_scatter_estimate(*scaled_est_projdata_sptr, // output: Now is normalized + *data_to_fit_projdata_sptr, // emission + *unscaled_est_projdata_sptr, // unscaled: OK *normalisation_factors_sptr, - *this->mask_projdata_sptr, + *this->mask_projdata_sptr, // weights: OK local_min_scale_value, local_max_scale_value, this->half_filter_width, spline_type, - true); + run_in_2d_projdata, + !run_in_2d_projdata); if (this->run_debug_mode) { @@ -990,12 +1016,11 @@ ScatterEstimation::process_data() scaled_est_projdata_sptr->write_to_file(tmp.get_string()); } - // When saving we need to go 3D. if (this->export_scatter_estimates_of_each_iteration || i_scat_iter == this->num_scatter_iterations) { shared_ptr temp_scatter_projdata; - + // When saving we need to go 3D. if (run_in_2d_projdata) { info("ScatterEstimation: upsampling scatter to 3D"); @@ -1050,13 +1075,17 @@ ScatterEstimation::process_data() shared_ptr normalisation_factors_3d_sptr = this->get_normalisation_object_sptr(this->multiplicative_binnorm_sptr); - ProjDataInMemory interpolated_scatter(this->input_projdata_sptr->get_exam_info_sptr(), - this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); - inverse_SSRB(interpolated_scatter, *temp_projdata); - normalisation_factors_3d_sptr->set_up(this->input_projdata_sptr->get_exam_info_sptr(), - this->input_projdata_sptr->get_proj_data_info_sptr()->create_shared_clone()); - normalisation_factors_3d_sptr->undo(interpolated_scatter); - scatter_estimate_sptr->fill(interpolated_scatter); + upsample_and_fit_scatter_estimate(*scatter_estimate_sptr, + *this->input_projdata_sptr, + *temp_projdata, + *normalisation_factors_3d_sptr, + *this->input_projdata_sptr, + 1.0f, + 1.0f, + 1, + spline_type, + false, + false); } else { @@ -1083,6 +1112,7 @@ ScatterEstimation::process_data() add_proj_data(*temp_additive_projdata, *this->back_projdata_sptr); } + // Apply * this->multiplicative_binnorm_sptr->apply(*temp_additive_projdata); } } @@ -1103,7 +1133,14 @@ ScatterEstimation::process_data() else { // TODO restructure code to move additive_projdata code from above - error("ScatterEstimation: You should not be here. This is not 2D."); + warning("ScatterEstimation: Great this is 3D!"); + this->add_projdata_sptr->fill(*scaled_est_projdata_sptr); + + if (!is_null_ptr(this->back_projdata_sptr)) + { + add_proj_data(*add_projdata_sptr, *this->back_projdata_sptr); + } + this->multiplicative_binnorm_sptr->apply(*add_projdata_sptr); } if (this->restart_reconstruction_every_scatter_iteration) diff --git a/src/scatter_buildblock/upsample_and_fit_scatter_estimate.cxx b/src/scatter_buildblock/upsample_and_fit_scatter_estimate.cxx index f12d7c117..730239766 100644 --- a/src/scatter_buildblock/upsample_and_fit_scatter_estimate.cxx +++ b/src/scatter_buildblock/upsample_and_fit_scatter_estimate.cxx @@ -52,50 +52,70 @@ ScatterEstimation::upsample_and_fit_scatter_estimate(ProjData& scaled_scatter_pr const float max_scale_factor, const unsigned half_filter_width, BSpline::BSplineType spline_type, - const bool remove_interleaving) + const bool remove_interleaving, + const bool do_3D) { - info("upsample_and_fit_scatter_estimate: Interpolating scatter estimate to size of emission data"); - shared_ptr interpolated_direct_scatter_proj_data_info_sptr(emission_proj_data.get_proj_data_info_sptr()->clone()); - interpolated_direct_scatter_proj_data_info_sptr->reduce_segment_range(0, 0); + shared_ptr interpolated_scatter_proj_data_info_sptr(emission_proj_data.get_proj_data_info_sptr()->clone()); - ProjDataInMemory interpolated_direct_scatter(emission_proj_data.get_exam_info_sptr(), - interpolated_direct_scatter_proj_data_info_sptr); - { - bool actual_remove_interleaving = remove_interleaving; + info("upsample_and_fit_scatter_estimate: Interpolating scatter estimate to size of emission data"); + shared_ptr interpolated_scatter( + new ProjDataInMemory(emission_proj_data.get_exam_info_sptr(), interpolated_scatter_proj_data_info_sptr)); - if (remove_interleaving - && emission_proj_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_scanner_geometry() != "Cylindrical") - { - warning("upsample_and_fit_scatter_estimate: forcing remove_interleaving to false as non-cylindrical projdata"); - actual_remove_interleaving = false; - } + bool actual_remove_interleaving = remove_interleaving; - interpolate_projdata(interpolated_direct_scatter, scatter_proj_data, spline_type, actual_remove_interleaving); - } + if (remove_interleaving + && emission_proj_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_scanner_geometry() != "Cylindrical") + { + warning("upsample_and_fit_scatter_estimate: forcing remove_interleaving to false as non-cylindrical projdata, or trying 3D " + "scatter estimation"); + actual_remove_interleaving = false; + } + if (emission_proj_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_scanner_geometry() == "Cylindrical" && !do_3D) + { + interpolated_scatter_proj_data_info_sptr->reduce_segment_range(0, 0); + interpolate_projdata(*interpolated_scatter, scatter_proj_data, spline_type, actual_remove_interleaving); + } + else + { + std::cout << " In 3D ... " << std::endl; + interpolate_projdata_3d(*interpolated_scatter, scatter_proj_data, spline_type); + scaled_scatter_proj_data.fill_from(interpolated_scatter->begin()); + } - // now call inverse_SSRB, and normalise/scale if we need to if (min_scale_factor != 1 || max_scale_factor != 1 || !scatter_normalisation.is_trivial()) { - ProjDataInMemory interpolated_scatter(emission_proj_data.get_exam_info_sptr(), - emission_proj_data.get_proj_data_info_sptr()->create_shared_clone()); - inverse_SSRB(interpolated_scatter, interpolated_direct_scatter); + shared_ptr _interpolated_scatter; + if (!do_3D) + { + _interpolated_scatter.reset(new ProjDataInMemory(emission_proj_data.get_exam_info_sptr(), + emission_proj_data.get_proj_data_info_sptr()->create_shared_clone())); + if (emission_proj_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_scanner_geometry() != "Cylindrical") + { + _interpolated_scatter->fill_from(interpolated_scatter->begin()); + } + else + { + inverse_SSRB(*_interpolated_scatter, *interpolated_scatter); + } + } + else + _interpolated_scatter = interpolated_scatter; scatter_normalisation.set_up(emission_proj_data.get_exam_info_sptr(), emission_proj_data.get_proj_data_info_sptr()->create_shared_clone()); - scatter_normalisation.undo(interpolated_scatter); + // div + scatter_normalisation.undo(*_interpolated_scatter); Array<2, float> scale_factors; if (min_scale_factor == max_scale_factor) { if (min_scale_factor == 1.F) { - scaled_scatter_proj_data.fill(interpolated_scatter); + scaled_scatter_proj_data.fill(*_interpolated_scatter); return; // all done } - // Set all scale_factors to min_scale_factor (which is equal to max_scale_factor here) - // Sadly a bit complicated to get index range ok const ProjDataInfo& proj_data_info = *emission_proj_data.get_proj_data_info_sptr(); IndexRange2D sinogram_range(proj_data_info.get_min_segment_num(), proj_data_info.get_max_segment_num(), 0, 0); for (int segment_num = proj_data_info.get_min_segment_num(); segment_num <= proj_data_info.get_max_segment_num(); @@ -110,7 +130,7 @@ ScatterEstimation::upsample_and_fit_scatter_estimate(ProjData& scaled_scatter_pr else { info("upsample_and_fit_scatter_estimate: Finding scale factors by sinogram", 3); - scale_factors = get_scale_factors_per_sinogram(emission_proj_data, interpolated_scatter, weights_proj_data); + scale_factors = get_scale_factors_per_sinogram(emission_proj_data, *_interpolated_scatter, weights_proj_data); info(boost::format("upsample_and_fit_scatter_estimate: scale factors before thresholding:\n%1%") % scale_factors, 2); @@ -124,15 +144,22 @@ ScatterEstimation::upsample_and_fit_scatter_estimate(ProjData& scaled_scatter_pr info(boost::format("upsample_and_fit_scatter_estimate: scale factors after filtering:\n%1%") % scale_factors, 2); } info("upsample_and_fit_scatter_estimate: applying scale factors", 3); - if (scale_sinograms(scaled_scatter_proj_data, interpolated_scatter, scale_factors) != Succeeded::yes) + if (scale_sinograms(scaled_scatter_proj_data, *_interpolated_scatter, scale_factors) != Succeeded::yes) { error("upsample_and_fit_scatter_estimate: writing of scaled sinograms failed"); } } - else // min/max_scale_factor equal to 1 and no norm + else // min/max_scale_factor equal to 1 and no norm -> Go to 3D if not { - inverse_SSRB(scaled_scatter_proj_data, interpolated_direct_scatter); + if (emission_proj_data.get_proj_data_info_sptr()->get_scanner_sptr()->get_scanner_geometry() != "Cylindrical") + scaled_scatter_proj_data.fill_from(interpolated_scatter->begin()); + else + { + if (!do_3D) + inverse_SSRB(scaled_scatter_proj_data, *interpolated_scatter); + } } + info("Getting out of Upsampling"); } END_NAMESPACE_STIR diff --git a/src/scatter_utilities/upsample_and_fit_single_scatter.cxx b/src/scatter_utilities/upsample_and_fit_single_scatter.cxx index 115d30198..970605e1b 100644 --- a/src/scatter_utilities/upsample_and_fit_single_scatter.cxx +++ b/src/scatter_utilities/upsample_and_fit_single_scatter.cxx @@ -61,6 +61,7 @@ print_usage_and_exit(const char* const prog_name) << "\t[--min-scale-factor ]\\\n" << "\t[--max-scale-factor ]\\\n" << "\t[--remove-interleaving <1|0>]\\\n" + << "\t[--do-3D <1|0>]\\\n" << "\t[--half-filter-width ]\\\n" << "\t--output-filename \\\n" << "\t--data-to-fit \\\n" @@ -86,6 +87,7 @@ main(int argc, const char* argv[]) float min_scale_factor = 1.E-5F; float max_scale_factor = 1.E5F; bool remove_interleaving = true; + bool do_3D = false; unsigned int half_filter_width = 1; stir::BSpline::BSplineType spline_type = stir::BSpline::linear; std::string data_to_fit_filename; @@ -127,6 +129,12 @@ main(int argc, const char* argv[]) argc -= 2; argv += 2; } + else if (strcmp(argv[1], "--do-3D") == 0) + { + do_3D = atoi(argv[2]) != 0; + argc -= 2; + argv += 2; + } else if (strcmp(argv[1], "--output-filename") == 0) { output_filename = (argv[2]); @@ -202,14 +210,15 @@ main(int argc, const char* argv[]) data_to_fit_proj_data_sptr->get_exam_info_sptr(), data_to_fit_proj_data_info_sptr, output_filename); stir::ScatterEstimation::upsample_and_fit_scatter_estimate(output_proj_data, - *data_to_fit_proj_data_sptr, - *data_to_scale_proj_data_sptr, + *data_to_fit_proj_data_sptr, // emission_proj_data + *data_to_scale_proj_data_sptr, // scatter_proj_data *normalisation_sptr, *weights_proj_data_sptr, min_scale_factor, max_scale_factor, half_filter_width, spline_type, - remove_interleaving); + remove_interleaving, + do_3D); return EXIT_SUCCESS; } diff --git a/src/swig/stir.i b/src/swig/stir.i index 8442e2268..4b24289d5 100644 --- a/src/swig/stir.i +++ b/src/swig/stir.i @@ -1200,6 +1200,7 @@ ADD_REPR(stir::Succeeded, %arg($self->succeeded() ? "yes" : "no")); %include "stir/scatter/SingleScatterSimulation.h" %shared_ptr(stir::ScatterEstimation); +%include "stir/numerics/BSplines.h" %include "stir/scatter/ScatterEstimation.h" %shared_ptr(stir::CreateTailMaskFromACFs); diff --git a/src/test/test_interpolate_projdata.cxx b/src/test/test_interpolate_projdata.cxx index 68baee66c..aef49d9ae 100644 --- a/src/test/test_interpolate_projdata.cxx +++ b/src/test/test_interpolate_projdata.cxx @@ -26,6 +26,7 @@ #endif #include "stir/ProjDataInfo.h" +#include "stir/ProjDataInfoCylindricalNoArcCorr.h" #include "stir/ExamInfo.h" #include "stir/ProjDataInMemory.h" #include "stir/Succeeded.h" @@ -42,6 +43,9 @@ #include "stir/recon_buildblock/ForwardProjectorByBinUsingProjMatrixByBin.h" #include "stir/scatter/SingleScatterSimulation.h" #include "stir/format.h" +#include "stir/extend_projdata.h" + +#include #include "stir/RunTests.h" @@ -56,17 +60,21 @@ class InterpolationTests : public RunTests shared_ptr exam_info_sptr; // need to call this first! void create_exam_info(); + void swap_segments_test(); + void extend_projdata_test(); void scatter_interpolation_test_blocks(); - void scatter_interpolation_test_cyl(); + void scatter_interpolation_test_cyl(const bool do_3d = false, const bool do_span = false); void scatter_interpolation_test_blocks_asymmetric(); - void scatter_interpolation_test_cyl_asymmetric(); + void scatter_interpolation_test_cyl_asymmetric(const bool do_3d = false, const bool do_span = false); void scatter_interpolation_test_blocks_downsampled(); void transaxial_upsampling_interpolation_test_blocks(); - void check_symmetry(const SegmentBySinogram& segment); + void check_symmetry(const SegmentBySinogram& segment, + const std::shared_ptr> other_sptr = nullptr); void compare_segment(const SegmentBySinogram& segment1, const SegmentBySinogram& segment2, float maxDiff); - void - compare_segment_shape(const SegmentBySinogram& shape_segment, const SegmentBySinogram& test_segment, int erosion); + void compare_segment_shape(const SegmentBySinogram& shape_segment, + const SegmentBySinogram& test_segment, + int dilation); //! forward project emission_map shared_ptr create_data(const shared_ptr& proj_data_info_sptr, const VoxelsOnCartesianGrid& emission_map, @@ -79,8 +87,11 @@ class InterpolationTests : public RunTests }; void -InterpolationTests::check_symmetry(const SegmentBySinogram& segment) +InterpolationTests::check_symmetry(const SegmentBySinogram& segment, + const std::shared_ptr> other_sptr) { + bool with_opposite = (other_sptr != nullptr); + // compare lower half of slices with upper half - image should be axially symmetric auto maxAbsDifference = 0.0; auto sumAbsValues = 0.0; @@ -94,7 +105,8 @@ InterpolationTests::check_symmetry(const SegmentBySinogram& segment) for (auto tang = segment.get_min_tangential_pos_num(); tang <= segment.get_max_tangential_pos_num(); tang++) { auto voxel1 = std::abs(segment[increasing_index][view][tang]); - auto voxel2 = std::abs(segment[decreasing_index][view][tang]); + auto voxel2 = with_opposite ? std::abs((*other_sptr)[decreasing_index][view][tang]) + : std::abs(segment[decreasing_index][view][tang]); if (std::abs(voxel1 - voxel2) > maxAbsDifference) maxAbsDifference = std::abs(voxel1 - voxel2); if (voxel1 > 0) @@ -117,38 +129,240 @@ InterpolationTests::check_symmetry(const SegmentBySinogram& segment) 0.0001 * sumAbsValues / summedEntries, "symmetry errors larger than 0.01\% of absolute values in axial direction"); - // compare the first half of the views with the second half - even for the BlocksOnCylindrical scanner they should be identical - maxAbsDifference = 0.0; - sumAbsValues = 0.0; - summedEntries = 0.0; - for (auto view = 0; view < segment.get_num_views() / 2; view++) + if (!with_opposite) { - for (auto axial = segment.get_min_axial_pos_num(); axial <= segment.get_max_axial_pos_num(); axial++) + // compare the first half of the views with the second half - even for the BlocksOnCylindrical scanner they should be + // identical + maxAbsDifference = 0.0; + sumAbsValues = 0.0; + summedEntries = 0.0; + for (auto view = 0; view < segment.get_num_views() / 2; view++) { - for (auto tang = segment.get_min_tangential_pos_num(); tang <= segment.get_max_tangential_pos_num(); tang++) + for (auto axial = segment.get_min_axial_pos_num(); axial <= segment.get_max_axial_pos_num(); axial++) { - auto voxel1 = segment[axial][view][tang]; - auto voxel2 = segment[axial][view + segment.get_num_views() / 2][tang]; - if (std::abs(voxel1 - voxel2) > maxAbsDifference) - maxAbsDifference = std::abs(voxel1 - voxel2); - if (voxel1 > 0) + for (auto tang = segment.get_min_tangential_pos_num(); tang <= segment.get_max_tangential_pos_num(); tang++) { - sumAbsValues += voxel1; - summedEntries++; + auto voxel1 = segment[axial][view][tang]; + auto voxel2 = segment[axial][view + segment.get_num_views() / 2][tang]; + if (std::abs(voxel1 - voxel2) > maxAbsDifference) + maxAbsDifference = std::abs(voxel1 - voxel2); + if (voxel1 > 0) + { + sumAbsValues += voxel1; + summedEntries++; + } + if (voxel2 > 0) + { + sumAbsValues += voxel2; + summedEntries++; + } } - if (voxel2 > 0) + } + } + // if the largest symmetry error is larger than 0.1% of the mean absolute value, then there is something wrong + // TODO: this tolerance can be tightened to 0.01% if https://github.com/UCL/STIR/issues/1176 is resolved + check_if_less(maxAbsDifference, + 0.001 * sumAbsValues / summedEntries, + "symmetry errors larger than 0.1\% of absolute values across views"); + } +} + +void +InterpolationTests::swap_segments_test() +{ + info("Performing tests on swapping ProjData segment"); + auto small_scanner = Scanner(Scanner::User_defined_scanner, + "Some_symmetric_scanner", + 64, + 6, + int(150 * 64 / 192), + int(150 * 64 / 192), + 127, + 4.3, + 20.0, + 133 * 3.14 / 64, + -0.38956 /* 0.0 */, + 1, + 1, + 6, + 64, + 1, + 1, + 1, + 0.17, + 511, + -1, + 01.F, + -1.F, + "Cylindrical", + 20.0, + 12.0, + 120.0, + 72.0); + + auto small_proj_data_info = shared_ptr(std::move( + ProjDataInfo::construct_proj_data_info(std::make_shared(small_scanner), 1, 5, 32, int(150 * 64 / 192), false))); + + auto proj_data = ProjDataInMemory(this->exam_info_sptr, small_proj_data_info); + + int counter = 1; + for (int i_seg = small_proj_data_info->get_min_segment_num(); i_seg <= small_proj_data_info->get_max_segment_num(); ++i_seg) + { + auto seg = proj_data.get_empty_segment_by_sinogram(i_seg); + for (int i_axial = seg.get_min_axial_pos_num(); i_axial <= seg.get_max_axial_pos_num(); ++i_axial) + { + for (int i_view = seg.get_min_view_num(); i_view <= seg.get_max_view_num(); ++i_view) + { + for (int i_tang = seg.get_min_tangential_pos_num(); i_tang <= seg.get_max_tangential_pos_num(); ++i_tang) { - sumAbsValues += voxel2; - summedEntries++; + seg[i_axial][i_view][i_tang] = counter; + counter++; } } } + proj_data.set_segment(seg); + } + + auto no_arc = dynamic_pointer_cast(small_proj_data_info); + + for (int i_seg = small_proj_data_info->get_min_segment_num(); i_seg < 0; ++i_seg) + { + auto seg = proj_data.get_segment_by_sinogram(-i_seg); + auto swapped = make_swapped_segment(seg, *no_arc, i_seg); + std::unordered_set seen_values; + int num_duplicates = 0; + int num_zero_fallback = 0; + + for (auto it = swapped.begin_all(); it != swapped.end_all(); ++it) + { + if (*it == 0.0F) + { + ++num_zero_fallback; // the deliberate out-of-range fallback in find_swapped_bin, not a real swapped value + continue; + } + if (!seen_values.insert(*it).second) + { + ++num_duplicates; + std::cerr << "value " << *it << " appears more than once in the swapped segment (segment " << i_seg << ")\n"; + } + } + + check_if_equal(num_duplicates, 0, "The detector swap reused the same source bin for more than one destination bin."); + check_if_equal(num_zero_fallback, 0, "During segment swapping bins were unclaimed."); + check_if_equal(seg, swapped, "Segments are equal"); + } +} + +void +InterpolationTests::extend_projdata_test() +{ + info("Performing tests on extending ProjData"); + auto small_scanner = Scanner(Scanner::User_defined_scanner, + "Some_symmetric_scanner", + 64, + 6, + int(150 * 64 / 192), + int(150 * 64 / 192), + 127, + 4.3, + 20.0, + 133 * 3.14 / 64, + -0.38956 /* 0.0 */, + 1, + 1, + 6, + 64, + 1, + 1, + 1, + 0.17, + 511, + -1, + 01.F, + -1.F, + "Cylindrical", + 20.0, + 12.0, + 120.0, + 72.0); + + auto small_proj_data_info = shared_ptr(std::move( + ProjDataInfo::construct_proj_data_info(std::make_shared(small_scanner), 1, 5, 32, int(150 * 64 / 192), false))); + + auto proj_data = ProjDataInMemory(this->exam_info_sptr, small_proj_data_info); + + int counter = 0; + for (int i_seg = small_proj_data_info->get_min_segment_num(); i_seg <= small_proj_data_info->get_max_segment_num(); ++i_seg) + { + auto seg = proj_data.get_empty_segment_by_sinogram(i_seg); + for (int i_axial = seg.get_min_axial_pos_num(); i_axial <= seg.get_max_axial_pos_num(); ++i_axial) + { + for (int i_view = seg.get_min_view_num(); i_view <= seg.get_max_view_num(); ++i_view) + { + for (int i_tang = seg.get_min_tangential_pos_num(); i_tang <= seg.get_max_tangential_pos_num(); ++i_tang) + { + seg[i_axial][i_view][i_tang] = counter; + counter++; + } + } + } + proj_data.set_segment(seg); + } + + auto no_arc = dynamic_pointer_cast(small_proj_data_info); + + for (int i_seg = small_proj_data_info->get_min_segment_num(); i_seg <= small_proj_data_info->get_min_segment_num(); ++i_seg) + { + auto seg = proj_data.get_segment_by_sinogram(i_seg); + auto opp_seg = proj_data.get_segment_by_sinogram(-i_seg); + auto swapped = make_swapped_segment(opp_seg, *no_arc, i_seg); + + int extend_views = seg.get_num_views() / 2; + auto ext = extend_segment(seg, extend_views, 0, 0, &swapped); + + { + int i_view_o = 0; + const int max_view = swapped[0].get_max_index(); + for (int i_view = ext[0].get_min_index(); i_view < 0; ++i_view, i_view_o++) + { + // Don't compare the first tang pos, it cannot align after full rotation + for (int i_tang = ext[0][0].get_min_index() + 1; i_tang <= ext[0][0].get_max_index(); ++i_tang) + { + const int src_view = max_view - extend_views + i_view_o + 1; + float diff = ext[0][i_view][i_tang] - swapped[0][src_view][-i_tang]; + check_if_equal(diff, 0.0, "The north extended sinogram does not match the swapped"); + } + } + } + + { + for (int i_view = 0; i_view <= seg.get_max_view_num(); ++i_view) + { + for (int i_tang = ext[0][0].get_min_index() + 1; i_tang <= ext[0][0].get_max_index(); ++i_tang) + { + // std::cout << i_view << " " << i_tang << " " << " : "; + // std::cout << ext[0][i_view][i_tang]<< " " + // << seg[0][i_view][i_tang] << std::endl; + float diff = ext[0][i_view][i_tang] - seg[0][i_view][i_tang]; + check_if_equal(diff, 0.0, "The central extended sinogram match the original"); + } + } + } + + { + int i_view_o = 0; + for (int i_view = seg.get_num_views(); i_view < ext[0].get_max_index(); ++i_view, i_view_o++) + { + for (int i_tang = ext[0][0].get_min_index() + 1; i_tang <= ext[0][0].get_max_index(); ++i_tang) + { + // std::cout << i_view << " " << i_tang << " " << i_view_o << " : "; + // std::cout << ext[0][i_view][i_tang] << " " << swapped[0][i_view_o][-i_tang] << std::endl; + float diff = ext[0][i_view][i_tang] - swapped[0][i_view_o][-i_tang]; + check_if_equal(diff, 0.0, "The south extended sinogram matches the swapped"); + } + } + } } - // if the largest symmetry error is larger than 0.1% of the mean absolute value, then there is something wrong - // TODO: this tolerance can be tightened to 0.01% if https://github.com/UCL/STIR/issues/1176 is resolved - check_if_less(maxAbsDifference, - 0.001 * sumAbsValues / summedEntries, - "symmetry errors larger than 0.1\% of absolute values across views"); } void @@ -176,9 +390,11 @@ InterpolationTests::compare_segment(const SegmentBySinogram& segment1, void InterpolationTests::compare_segment_shape(const SegmentBySinogram& shape_segment, const SegmentBySinogram& test_segment, - int erosion) + int dilation) { - auto maxTestValue = test_segment.find_max(); + const float test_threshold = 0.1 * test_segment.find_max(); + // const float shape_threshold = 0.1 * shape_segment.find_max(); + // compute difference and compare against empirically found value from visually validated sinograms auto sumVoxelsOutsideMask = 0U; for (auto axial = test_segment.get_min_axial_pos_num(); axial <= test_segment.get_max_axial_pos_num(); axial++) @@ -187,21 +403,21 @@ InterpolationTests::compare_segment_shape(const SegmentBySinogram& shape_ { for (auto tang = test_segment.get_min_tangential_pos_num(); tang <= test_segment.get_max_tangential_pos_num(); tang++) { - if (test_segment[axial][view][tang] < 0.1 * maxTestValue) + if (test_segment[axial][view][tang] < test_threshold) continue; - // now go through the erosion neighbourhood of the voxel to see if it is near a non-zero voxel + // now go through the dilation neighbourhood of the voxel to see if it is near a non-zero voxel bool isNearNonZero = false; - for (auto axialShape = std::max(axial - erosion, test_segment.get_min_axial_pos_num()); - axialShape <= std::min(axial + erosion, test_segment.get_max_axial_pos_num()); + for (auto axialShape = std::max(axial - dilation, test_segment.get_min_axial_pos_num()); + axialShape <= std::min(axial + dilation, test_segment.get_max_axial_pos_num()); axialShape++) { - for (auto viewShape = std::max(view - erosion, test_segment.get_min_view_num()); - viewShape <= std::min(view + erosion, test_segment.get_max_view_num()); + for (auto viewShape = std::max(view - dilation, test_segment.get_min_view_num()); + viewShape <= std::min(view + dilation, test_segment.get_max_view_num()); viewShape++) { - for (auto tangShape = std::max(tang - erosion, test_segment.get_min_tangential_pos_num()); - tangShape <= std::min(tang + erosion, test_segment.get_max_tangential_pos_num()); + for (auto tangShape = std::max(tang - dilation, test_segment.get_min_tangential_pos_num()); + tangShape <= std::min(tang + dilation, test_segment.get_max_tangential_pos_num()); tangShape++) { if (shape_segment[axialShape][viewShape][tangShape] > 0) @@ -271,15 +487,27 @@ InterpolationTests::create_upsampled_data(const shared_ptr& // interpolate the downsampled proj data to the original scanner size and fill in oblique sinograms // TODO reduce_segment - auto interpolated_direct_proj_data = ProjDataInMemory(this->exam_info_sptr, proj_data_info_sptr); - interpolate_projdata(interpolated_direct_proj_data, *downsampled_proj_data, BSpline::linear, false); - auto interpolated_proj_data_sptr = std::make_shared(this->exam_info_sptr, proj_data_info_sptr); - inverse_SSRB(*interpolated_proj_data_sptr, interpolated_direct_proj_data); + if (downsampled_proj_data->get_num_segments() == 1) + { + auto interpolated_direct_proj_data = ProjDataInMemory(this->exam_info_sptr, proj_data_info_sptr); + interpolate_projdata(interpolated_direct_proj_data, *downsampled_proj_data, BSpline::linear, false); + auto interpolated_proj_data_sptr = std::make_shared(this->exam_info_sptr, proj_data_info_sptr); + inverse_SSRB(*interpolated_proj_data_sptr, interpolated_direct_proj_data); - // write the proj data to file - interpolated_proj_data_sptr->write_to_file("interpolated_sino" + suffix + ".hs"); + // write the proj data to file + interpolated_proj_data_sptr->write_to_file("interpolated_sino" + suffix + ".hs"); - return interpolated_proj_data_sptr; + return interpolated_proj_data_sptr; + } + else + { + auto interpolated_proj_data_sptr = std::make_shared(this->exam_info_sptr, proj_data_info_sptr); + interpolate_projdata_3d(*interpolated_proj_data_sptr, *downsampled_proj_data, BSpline::linear, false); + // write the proj data to file + interpolated_proj_data_sptr->write_to_file("interpolated_sino" + suffix + ".hs"); + + return interpolated_proj_data_sptr; + } } void @@ -363,7 +591,7 @@ InterpolationTests::scatter_interpolation_test_blocks() } void -InterpolationTests::scatter_interpolation_test_cyl() +InterpolationTests::scatter_interpolation_test_cyl(const bool do_3d, const bool do_span) { info("Performing symmetric interpolation test for Cylindrical scanner"); @@ -425,10 +653,12 @@ InterpolationTests::scatter_interpolation_test_cyl() 120.0, 72.0); + int span = do_span ? 3 : 1; auto proj_data_info = shared_ptr( - std::move(ProjDataInfo::construct_proj_data_info(std::make_shared(scanner), 1, 29, 96, 150, false))); + std::move(ProjDataInfo::construct_proj_data_info(std::make_shared(scanner), span, 29, 96, 150, false))); + int downsampled_rings = do_3d ? 5 : 0; auto downsampled_proj_data_info = shared_ptr(std::move(ProjDataInfo::construct_proj_data_info( - std::make_shared(downsampled_scanner), 1, 0, 32, int(150 * 64 / 192), false))); + std::make_shared(downsampled_scanner), 1, downsampled_rings, 32, int(150 * 64 / 192), false))); auto proj_data = ProjDataInMemory(this->exam_info_sptr, proj_data_info); @@ -440,8 +670,23 @@ InterpolationTests::scatter_interpolation_test_cyl() auto interpolated_proj_data_sptr = this->create_upsampled_data(proj_data_info, downsampled_proj_data_info, emission_map, "_cyl"); - // use symmetry to check that there are no significant errors in the interpolation - check_symmetry(interpolated_proj_data_sptr->get_segment_by_sinogram(0)); + const auto proj_data_info_no_arc_corr_sptr + = dynamic_pointer_cast(interpolated_proj_data_sptr->get_proj_data_info_sptr()); + if (!proj_data_info_no_arc_corr_sptr) + error("Expected the in projection data info to be a ProjDataInfoCylindricalNoArcCorr."); + + for (int i_seg = interpolated_proj_data_sptr->get_min_segment_num(); i_seg <= 0; ++i_seg) + { + info(stir::format("Checking segment: {}", i_seg)); + if (i_seg != 0) + { + auto opp_sptr + = std::make_shared>(interpolated_proj_data_sptr->get_segment_by_sinogram(-i_seg)); + check_symmetry(interpolated_proj_data_sptr->get_segment_by_sinogram(i_seg), opp_sptr); + } + else + check_symmetry(interpolated_proj_data_sptr->get_segment_by_sinogram(i_seg)); + } } void @@ -541,7 +786,7 @@ InterpolationTests::scatter_interpolation_test_blocks_asymmetric() } void -InterpolationTests::scatter_interpolation_test_cyl_asymmetric() +InterpolationTests::scatter_interpolation_test_cyl_asymmetric(const bool do_3d, const bool do_span) { info("Performing asymmetric interpolation test for Cylindrical scanner"); @@ -603,10 +848,12 @@ InterpolationTests::scatter_interpolation_test_cyl_asymmetric() 60.0, 72.0); + int downsampled_rings = do_3d ? 11 : 0; + int span = do_span ? 3 : 1; auto proj_data_info = shared_ptr(std::move( - ProjDataInfo::construct_proj_data_info(std::make_shared(scanner), 1, 29, 48, int(150 * 96 / 192), false))); + ProjDataInfo::construct_proj_data_info(std::make_shared(scanner), span, 29, 48, int(150 * 96 / 192), false))); auto downsampled_proj_data_info = shared_ptr(std::move(ProjDataInfo::construct_proj_data_info( - std::make_shared(downsampled_scanner), 1, 0, 32, int(150 * 64 / 192), false))); + std::make_shared(downsampled_scanner), 1, downsampled_rings, 32, int(150 * 64 / 192), false))); // define asymetric object auto emission_map = VoxelsOnCartesianGrid(this->exam_info_sptr, *proj_data_info, 1); @@ -627,11 +874,25 @@ InterpolationTests::scatter_interpolation_test_cyl_asymmetric() box.construct_volume(emission_map, CartesianCoordinate3D(1, 1, 1)); emission_map += cyl_map; auto interpolated_proj_data_sptr - = this->create_upsampled_data(proj_data_info, downsampled_proj_data_info, emission_map, "asym_block"); + = this->create_upsampled_data(proj_data_info, downsampled_proj_data_info, emission_map, "asym_cyl"); - // compare to ground truth - compare_segment_shape( - full_size_model_sino_sptr->get_segment_by_sinogram(0), interpolated_proj_data_sptr->get_segment_by_sinogram(0), 2); + if (downsampled_proj_data_info->get_num_segments() == 1) + { + // compare to ground truth + compare_segment_shape( + full_size_model_sino_sptr->get_segment_by_sinogram(0), interpolated_proj_data_sptr->get_segment_by_sinogram(0), 2); + } + else + { + for (int i_seg = proj_data_info->get_min_segment_num(); i_seg <= proj_data_info->get_max_segment_num(); ++i_seg) + { + // compare to ground truth + std::cout << i_seg << std::endl; + compare_segment_shape(full_size_model_sino_sptr->get_segment_by_sinogram(i_seg), + interpolated_proj_data_sptr->get_segment_by_sinogram(i_seg), + 4); + } + } } void @@ -852,10 +1113,17 @@ void InterpolationTests::run_tests() { create_exam_info(); + swap_segments_test(); + extend_projdata_test(); scatter_interpolation_test_blocks(); scatter_interpolation_test_cyl(); + scatter_interpolation_test_cyl(true); // 3D + scatter_interpolation_test_cyl(true, true); // 3D, span scatter_interpolation_test_blocks_asymmetric(); scatter_interpolation_test_cyl_asymmetric(); + scatter_interpolation_test_cyl_asymmetric(true); // 3D + //! TODO: This tests shapre rather then values. With dilation 4 span is ok. + scatter_interpolation_test_cyl_asymmetric(true, true); // 3D, span scatter_interpolation_test_blocks_downsampled(); transaxial_upsampling_interpolation_test_blocks(); }