void GenericReconCartesianGrappaGadget::perform_unwrapping(IsmrmrdReconBit &recon_bit, ReconObjType &recon_obj,
                                                               size_t e) {

        typedef std::complex<float> T;

        typedef std::complex<float> T;

        size_t RO = recon_bit.data_.data_.get_size(0);
        size_t E1 = recon_bit.data_.data_.get_size(1);
        size_t E2 = recon_bit.data_.data_.get_size(2);
        size_t dstCHA = recon_bit.data_.data_.get_size(3);
        size_t N = recon_bit.data_.data_.get_size(4);
        size_t S = recon_bit.data_.data_.get_size(5);
        size_t SLC = recon_bit.data_.data_.get_size(6);

        hoNDArray<std::complex<float> > &src = recon_obj.ref_calib_;

        size_t ref_RO = src.get_size(0);
        size_t ref_E1 = src.get_size(1);
        size_t ref_E2 = src.get_size(2);
        size_t srcCHA = src.get_size(3);
        size_t ref_N = src.get_size(4);
        size_t ref_S = src.get_size(5);
        size_t ref_SLC = src.get_size(6);

        size_t unmixingCoeff_CHA = recon_obj.unmixing_coeff_.get_size(3);

        size_t convkRO = recon_obj.kernel_.get_size(0);
        size_t convkE1 = recon_obj.kernel_.get_size(1);
        size_t convkE2 = recon_obj.kernel_.get_size(2);

        recon_obj.recon_res_.data_.create(RO, E1, E2, 1, N, S, SLC);

        if (!debug_folder_full_path_.empty()) {
            std::stringstream os;
            os << "encoding_" << e;
            std::string suffix = os.str();
            gt_exporter_.export_array_complex(recon_bit.data_.data_, debug_folder_full_path_ + "data_src_" + suffix);
        }

        // compute aliased images
        data_recon_buf_.create(RO, E1, E2, dstCHA, N, S, SLC);

        if (E2 > 1) {
            Gadgetron::hoNDFFT<float>::instance()->ifft3c(recon_bit.data_.data_, complex_im_recon_buf_,
                                                          data_recon_buf_);
        } else {
            Gadgetron::hoNDFFT<float>::instance()->ifft2c(recon_bit.data_.data_, complex_im_recon_buf_,
                                                          data_recon_buf_);
        }

        // SNR unit scaling
        float effective_acce_factor(1), snr_scaling_ratio(1);
        this->compute_snr_scaling_factor(recon_bit, effective_acce_factor, snr_scaling_ratio);
        if (effective_acce_factor > 1) {
            // since the grappa in gadgetron is doing signal preserving scaling, to perserve noise level, we need this compensation factor
            double grappaKernelCompensationFactor = 1.0 / (acceFactorE1_[e] * acceFactorE2_[e]);
            Gadgetron::scal((float) (grappaKernelCompensationFactor * snr_scaling_ratio), complex_im_recon_buf_);

            if (this->verbose.value()) GDEBUG_STREAM(
                    "GenericReconCartesianGrappaGadget, grappaKernelCompensationFactor*snr_scaling_ratio : "
                            << grappaKernelCompensationFactor * snr_scaling_ratio);
        }

        if (!debug_folder_full_path_.empty()) {
            std::stringstream os;
            os << "encoding_" << e;
            std::string suffix = os.str();
            gt_exporter_.export_array_complex(complex_im_recon_buf_, debug_folder_full_path_ + "aliasedIm_" + suffix);
        }

        // unwrapping

        long long num = N * S * SLC;

        long long ii;

#pragma omp parallel default(none) private(ii) shared(num, N, S, RO, E1, E2, srcCHA, convkRO, convkE1, convkE2, ref_N, ref_S, recon_obj, dstCHA, unmixingCoeff_CHA, e) if(num>1)
        {
#pragma omp for
            for (ii = 0; ii < num; ii++) {
                size_t slc = ii / (N * S);
                size_t s = (ii - slc * N * S) / N;
                size_t n = ii - slc * N * S - s * N;

                // combined channels
                T *pIm = &(complex_im_recon_buf_(0, 0, 0, 0, n, s, slc));

                size_t usedN = n;
                if (n >= ref_N) usedN = ref_N - 1;

                size_t usedS = s;
                if (s >= ref_S) usedS = ref_S - 1;

                T *pUnmix = &(recon_obj.unmixing_coeff_(0, 0, 0, 0, usedN, usedS, slc));

                T *pRes = &(recon_obj.recon_res_.data_(0, 0, 0, 0, n, s, slc));
                hoNDArray<std::complex<float> > res(RO, E1, E2, 1, pRes);

                hoNDArray<std::complex<float> > unmixing(RO, E1, E2, unmixingCoeff_CHA, pUnmix);
                hoNDArray<std::complex<float> > aliasedIm(RO, E1, E2,
                                                          ((unmixingCoeff_CHA <= srcCHA) ? unmixingCoeff_CHA : srcCHA),
                                                          1, pIm);
                Gadgetron::apply_unmix_coeff_aliased_image_3D(aliasedIm, unmixing, res);
            }
        }

        if (!debug_folder_full_path_.empty()) {
            std::stringstream os;
            os << "encoding_" << e;
            std::string suffix = os.str();
            gt_exporter_.export_array_complex(recon_obj.recon_res_.data_,
                                              debug_folder_full_path_ + "unwrappedIm_" + suffix);
        }

    }
    void MultiChannelCartesianGrappaReconGadget::perform_unwrapping(IsmrmrdReconBit& recon_bit, ReconObjType& recon_obj, size_t e)
    {
        try
        {
            typedef std::complex<float> T;

            size_t RO = recon_bit.data_.data_.get_size(0);
            size_t E1 = recon_bit.data_.data_.get_size(1);
            size_t E2 = recon_bit.data_.data_.get_size(2);
            size_t dstCHA = recon_bit.data_.data_.get_size(3);
            size_t N = recon_bit.data_.data_.get_size(4);
            size_t S = recon_bit.data_.data_.get_size(5);
            size_t SLC = recon_bit.data_.data_.get_size(6);

            hoNDArray< std::complex<float> >& src = recon_obj.ref_calib_;
            hoNDArray< std::complex<float> >& dst = recon_obj.ref_calib_;

            size_t ref_RO = src.get_size(0);
            size_t ref_E1 = src.get_size(1);
            size_t ref_E2 = src.get_size(2);
            size_t srcCHA = src.get_size(3);
            size_t ref_N = src.get_size(4);
            size_t ref_S = src.get_size(5);
            size_t ref_SLC = src.get_size(6);

            size_t convkRO = recon_obj.kernel_.get_size(0);
            size_t convkE1 = recon_obj.kernel_.get_size(1);
            size_t convkE2 = recon_obj.kernel_.get_size(2);

            recon_obj.recon_res_.data_.create(RO, E1, E2, dstCHA, N, S, SLC);

            // compute aliased images
            data_recon_buf_.create(RO, E1, E2, dstCHA, N, S, SLC);

            if (E2>1)
            {
                Gadgetron::hoNDFFT<float>::instance()->ifft3c(recon_bit.data_.data_, complex_im_recon_buf_, data_recon_buf_);
            }
            else
            {
                Gadgetron::hoNDFFT<float>::instance()->ifft2c(recon_bit.data_.data_, complex_im_recon_buf_, data_recon_buf_);
            }

            // SNR unit scaling
            float effectiveAcceFactor = acceFactorE1_[e] * acceFactorE2_[e];
            if (effectiveAcceFactor > 1)
            {
                float fftCompensationRatio = (float)(1.0 / std::sqrt(effectiveAcceFactor));
                Gadgetron::scal(fftCompensationRatio, complex_im_recon_buf_);
            }

            // unwrapping

            long long num = N*S*SLC;

            long long ii;

#pragma omp parallel default(none) private(ii) shared(num, N, S, RO, E1, E2, srcCHA, convkRO, convkE1, convkE2, ref_N, ref_S, recon_obj, dstCHA, e) if(num>1)
            {
#pragma omp for 
                for (ii = 0; ii < num; ii++)
                {
                    size_t slc = ii / (N*S);
                    size_t s = (ii - slc*N*S) / N;
                    size_t n = ii - slc*N*S - s*N;

                    // combined channels
                    T* pIm = &(complex_im_recon_buf_(0, 0, 0, 0, n, s, slc));
                    hoNDArray< std::complex<float> > aliasedIm(RO, E1, E2, srcCHA, 1, pIm);

                    size_t usedN = n;
                    if (n >= ref_N) usedN = ref_N - 1;

                    size_t usedS = s;
                    if (s >= ref_S) usedS = ref_S - 1;

                    T* pUnmix = &(recon_obj.unmixing_coeff_(0, 0, 0, 0, usedN, usedS, slc));
                    hoNDArray< std::complex<float> > unmixing(RO, E1, E2, srcCHA, pUnmix);

                    T* pRes = &(recon_obj.recon_res_.data_(0, 0, 0, 0, n, s, slc));
                    hoNDArray< std::complex<float> > res(RO, E1, E2, dstCHA, pRes);
		
                    Gadgetron::apply_unmix_coeff_aliased_image_3D(aliasedIm, unmixing, res);
                }
            }
        }
        catch (...)
        {
            GADGET_THROW("Errors happened in MultiChannelCartesianGrappaReconGadget::perform_unwrapping(...) ... ");
        }
    }