/* Copyright 2013--2018 James E. McClure, Virginia Polytechnic & State University Copyright Equnior ASA This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #include "analysis/imfilter.h" #include "ProfilerApp.h" #include #include #define IMFILTER_INSIST INSIST #define IMFILTER_ASSERT ASSERT #define IMFILTER_ERROR ERROR // Function to convert an index static inline int imfilter_index(int index, const int N, const imfilter::BC bc) { if (index < 0 || index >= N) { if (bc == imfilter::BC::symmetric) { index = (2 * N - index) % N; } else if (bc == imfilter::BC::replicate) { index = index < 0 ? 0 : N - 1; } else if (bc == imfilter::BC::circular) { index = (index + N) % N; } else if (bc == imfilter::BC::fixed) { index = -1; } } return index; } // Function to copy a 1D array and pad with the appropriate BC template static inline void copy_array(const int N, const int Ns, const int Nh, const TYPE *A, const imfilter::BC BC, const TYPE X, TYPE *B) { // Fill the center with a memcpy for (int i = 0; i < N; i++) B[i + Nh] = A[i * Ns]; // Fill the boundaries for (int i = 0; i < Nh; i++) { int j1 = imfilter_index(-(i + 1), N, BC); int j2 = imfilter_index(N + i, N, BC); B[Nh - i - 1] = j1 == -1 ? X : B[Nh + j1]; B[N + Nh + i] = j2 == -1 ? X : B[Nh + j2]; } } /******************************************************** * Perform a 1D filter in a single direction * ********************************************************/ template static void filter_direction(int Ns, int N, int Ne, int Nh, const TYPE *H, imfilter::BC boundary, TYPE X, TYPE *A) { if (Nh < 0) IMFILTER_ERROR("Invalid filter size"); if (Nh == 0) { for (int i = 0; i < Ns * N * Ne; i++) A[i] *= H[0]; return; } TYPE *tmp = new TYPE[N + 2 * Nh]; for (int j = 0; j < Ne; j++) { for (int i = 0; i < Ns; i++) { copy_array(N, Ns, Nh, &A[i + j * Ns * N], boundary, X, tmp); for (int k = 0; k < N; k++) { TYPE tmp2 = 0; for (int m = 0; m <= 2 * Nh; m++) tmp2 += H[m] * tmp[k + m]; A[i + k * Ns + j * Ns * N] = tmp2; } } } delete[] tmp; } template static void filter_direction(int Ns, int N, int Ne, int Nh, std::function &)> H, imfilter::BC boundary, TYPE X, TYPE *A) { if (Nh < 0) IMFILTER_ERROR("Invalid filter size"); TYPE *tmp = new TYPE[N + 2 * Nh]; Array tmp2(2 * Nh + 1); for (int j = 0; j < Ne; j++) { for (int i = 0; i < Ns; i++) { copy_array(N, Ns, Nh, &A[i + j * Ns * N], boundary, X, tmp); for (int k = 0; k < N; k++) { for (int m = 0; m <= 2 * Nh; m++) tmp2(m) = tmp[k + m]; A[i + k * Ns + j * Ns * N] = H(tmp2); } } } delete[] tmp; } template static void filter_direction(int Ns, int N, int Ne, int Nh, std::function H, imfilter::BC boundary, TYPE X, TYPE *A) { if (Nh < 0) IMFILTER_ERROR("Invalid filter size"); TYPE *tmp = new TYPE[N + 2 * Nh]; int Nh2 = 2 * Nh + 1; for (int j = 0; j < Ne; j++) { for (int i = 0; i < Ns; i++) { copy_array(N, Ns, Nh, &A[i + j * Ns * N], boundary, X, tmp); for (int k = 0; k < N; k++) A[i + k * Ns + j * Ns * N] = H(Nh2, &tmp[k]); } } delete[] tmp; } /******************************************************** * Create a filter * ********************************************************/ template Array imfilter::create_filter(const std::vector &N0, const std::string &type, const void *args) { std::vector N2(N0.size()); for (size_t i = 0; i < N2.size(); i++) N2[i] = 2 * N0[i] + 1; Array h(N2); h.fill(0); if (type == "average") { // average h.fill(1.0 / static_cast(h.length())); } else if (type == "gaussian") { // gaussian if (N0.size() > 3) IMFILTER_ERROR("Not implimented for dimensions > 3"); TYPE std[3] = {0.5, 0.5, 0.5}; if (args != NULL) { const TYPE *args2 = reinterpret_cast(args); for (size_t d = 0; d < N0.size(); d++) std[d] = args2[d]; } auto N = N0; N.resize(3, 0); for (int k = -N[2]; k <= N[2]; k++) { for (int j = -N[1]; j <= N[1]; j++) { for (int i = -N[0]; i <= N[0]; i++) { h(i + N[0], j + N[1], k + N[2]) = exp(-i * i / (2 * std[0] * std[0])) * exp(-j * j / (2 * std[1] * std[1])) * exp(-k * k / (2 * std[2] * std[2])); } } } h.scale(1.0 / h.sum()); } else { IMFILTER_ERROR("Unknown filter"); } return h; } // Perform 2-D filtering template void imfilter_2D(int Nx, int Ny, const TYPE *A, int Nhx, int Nhy, const TYPE *H, imfilter::BC BCx, imfilter::BC BCy, const TYPE X, TYPE *B) { IMFILTER_ASSERT(A != B); PROFILE_START("imfilter_2D"); memset(B, 0, Nx * Ny * sizeof(TYPE)); for (int j1 = 0; j1 < Ny; j1++) { for (int i1 = 0; i1 < Nx; i1++) { TYPE tmp = 0; if (i1 >= Nhx && i1 < Nx - Nhx && j1 >= Nhy && j1 < Ny - Nhy) { int ijkh = 0; for (int j2 = j1 - Nhy; j2 <= j1 + Nhy; j2++) { for (int i2 = i1 - Nhx; i2 <= i1 + Nhx; i2++, ijkh++) tmp += H[ijkh] * A[i2 + j2 * Nx]; } } else { int ijkh = 0; for (int jh = -Nhy; jh <= Nhy; jh++) { int j2 = imfilter_index(j1 + jh, Ny, BCy); for (int ih = -Nhx; ih <= Nhx; ih++) { int i2 = imfilter_index(i1 + ih, Nx, BCx); bool fixed = i2 == -1 || j2 == -1; TYPE A2 = fixed ? X : A[i2 + j2 * Nx]; tmp += H[ijkh] * A2; ijkh++; } } } B[i1 + j1 * Nx] = tmp; } } PROFILE_STOP("imfilter_2D"); } // Perform 3-D filtering template void imfilter_3D(int Nx, int Ny, int Nz, const TYPE *A, int Nhx, int Nhy, int Nhz, const TYPE *H, imfilter::BC BCx, imfilter::BC BCy, imfilter::BC BCz, const TYPE X, TYPE *B) { IMFILTER_ASSERT(A != B); PROFILE_START("imfilter_3D"); memset(B, 0, Nx * Ny * Nz * sizeof(TYPE)); for (int k1 = 0; k1 < Nz; k1++) { for (int j1 = 0; j1 < Ny; j1++) { for (int i1 = 0; i1 < Nx; i1++) { TYPE tmp = 0; int ijkh = 0; for (int kh = -Nhz; kh <= Nhz; kh++) { int k2 = imfilter_index(k1 + kh, Nz, BCz); for (int jh = -Nhy; jh <= Nhy; jh++) { int j2 = imfilter_index(j1 + jh, Ny, BCy); for (int ih = -Nhx; ih <= Nhx; ih++) { int i2 = imfilter_index(i1 + ih, Nx, BCx); bool fixed = i2 == -1 || j2 == -1 || k2 == -1; TYPE A2 = fixed ? X : A[i2 + j2 * Nx + k2 * Nx * Ny]; tmp += H[ijkh] * A2; ijkh++; } } } B[i1 + j1 * Nx + k1 * Nx * Ny] = tmp; } } } PROFILE_STOP("imfilter_3D"); } /******************************************************** * Perform N-D filtering * ********************************************************/ template Array imfilter::imfilter(const Array &A, const Array &H, const std::vector &BC, const TYPE X) { IMFILTER_ASSERT(A.ndim() == H.ndim()); IMFILTER_ASSERT(A.ndim() == BC.size()); std::vector Nh = H.size(); for (int d = 0; d < A.ndim(); d++) { Nh[d] = (H.size(d) - 1) / 2; IMFILTER_INSIST(2 * Nh[d] + 1 == H.size(d), "Filter must be of size 2*N+1"); } auto B = A; if (A.ndim() == 1) { PROFILE_START("imfilter_1D"); filter_direction(1, A.size(0), 1, Nh[0], H.data(), BC[0], X, B.data()); PROFILE_STOP("imfilter_1D"); } else if (A.ndim() == 2) { imfilter_2D(A.size(0), A.size(1), A.data(), Nh[0], Nh[1], H.data(), BC[0], BC[1], X, B.data()); } else if (A.ndim() == 3) { imfilter_3D(A.size(0), A.size(1), A.size(2), A.data(), Nh[0], Nh[1], Nh[2], H.data(), BC[0], BC[1], BC[2], X, B.data()); } else { IMFILTER_ERROR("Arbitrary dimension not yet supported"); } return B; } template Array imfilter::imfilter(const Array &A, const std::vector &Nh0, std::function &)> H, const std::vector &BC0, const TYPE X) { PROFILE_START("imfilter (lambda)"); IMFILTER_ASSERT(A.ndim() == Nh0.size()); IMFILTER_ASSERT(A.ndim() == BC0.size()); std::vector Nh2(A.size()); for (int d = 0; d < A.ndim(); d++) Nh2[d] = 2 * Nh0[d] + 1; auto B = A; Array data(Nh2); IMFILTER_INSIST(A.ndim() <= 3, "Not programmed for more than 3 dimensions yet"); auto N = A.size(); auto Nh = Nh0; auto BC = BC0; N.resize(3, 1); Nh.resize(3, 0); BC.resize(3, imfilter::BC::fixed); for (int k1 = 0; k1 < N[2]; k1++) { for (int j1 = 0; j1 < N[1]; j1++) { for (int i1 = 0; i1 < N[0]; i1++) { for (int kh = -Nh[2]; kh <= Nh[2]; kh++) { int k2 = imfilter_index(k1 + kh, N[2], BC[2]); for (int jh = -Nh[1]; jh <= Nh[1]; jh++) { int j2 = imfilter_index(j1 + jh, N[1], BC[1]); for (int ih = -Nh[0]; ih <= Nh[0]; ih++) { int i2 = imfilter_index(i1 + ih, N[0], BC[0]); bool fixed = i2 == -1 || j2 == -1 || k2 == -1; data(ih + Nh[0], jh + Nh[1], kh + Nh[2]) = fixed ? X : A(i2, j2, k2); } } } B(i1, j1, k1) = H(data); } } } PROFILE_STOP("imfilter (lambda)"); return B; } /******************************************************** * imfilter with separable filter functions * ********************************************************/ template Array imfilter::imfilter_separable( const Array &A, const std::vector> &H, const std::vector &boundary, const TYPE X) { PROFILE_START("imfilter_separable"); IMFILTER_ASSERT(A.ndim() == (int)H.size()); IMFILTER_ASSERT(A.ndim() == (int)boundary.size()); std::vector Nh(H.size()); for (int d = 0; d < A.ndim(); d++) { IMFILTER_ASSERT(H[d].ndim() == 1); Nh[d] = (H[d].length() - 1) / 2; IMFILTER_INSIST(2 * Nh[d] + 1 == H[d].length(), "Filter must be of size 2*N+1"); } auto B = A; for (int d = 0; d < A.ndim(); d++) { int N = A.size(d); int Ns = 1; int Ne = 1; for (int d2 = 0; d2 < d; d2++) Ns *= A.size(d2); for (int d2 = d + 1; d2 < A.ndim(); d2++) Ne *= A.size(d2); filter_direction(Ns, N, Ne, Nh[d], H[d].data(), boundary[d], X, B.data()); } PROFILE_STOP("imfilter_separable"); return B; } template Array imfilter::imfilter_separable( const Array &A, const std::vector &Nh, std::vector &)>> H, const std::vector &boundary, const TYPE X) { PROFILE_START("imfilter_separable (lambda)"); IMFILTER_ASSERT(A.ndim() == (int)boundary.size()); auto B = A; for (int d = 0; d < A.ndim(); d++) { int N = A.size(d); int Ns = 1; int Ne = 1; for (int d2 = 0; d2 < d; d2++) Ns *= A.size(d2); for (int d2 = d + 1; d2 < A.ndim(); d2++) Ne *= A.size(d2); filter_direction(Ns, N, Ne, Nh[d], H[d], boundary[d], X, B.data()); } PROFILE_STOP("imfilter_separable (lambda)"); return B; } template Array imfilter::imfilter_separable( const Array &A, const std::vector &Nh, std::vector> H, const std::vector &boundary, const TYPE X) { PROFILE_START("imfilter_separable (function)"); IMFILTER_ASSERT(A.ndim() == (int)boundary.size()); auto B = A; for (int d = 0; d < A.ndim(); d++) { int N = A.size(d); int Ns = 1; int Ne = 1; for (int d2 = 0; d2 < d; d2++) Ns *= A.size(d2); for (int d2 = d + 1; d2 < A.ndim(); d2++) Ne *= A.size(d2); filter_direction(Ns, N, Ne, Nh[d], H[d], boundary[d], X, B.data()); } PROFILE_STOP("imfilter_separable (function)"); return B; }