/*
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/uCT.h"
#include "analysis/analysis.h"
#include "analysis/distance.h"
#include "analysis/filters.h"
#include "analysis/imfilter.h"
template
inline int sign( T x )
{
if ( x==0 )
return 0;
return x>0 ? 1:-1;
}
inline float trilinear( float dx, float dy, float dz, float f1, float f2,
float f3, float f4, float f5, float f6, float f7, float f8 )
{
double f, dx2, dy2, dz2, h0, h1;
dx2 = 1.0 - dx;
dy2 = 1.0 - dy;
dz2 = 1.0 - dz;
h0 = ( dx * f2 + dx2 * f1 ) * dy2 + ( dx * f4 + dx2 * f3 ) * dy;
h1 = ( dx * f6 + dx2 * f5 ) * dy2 + ( dx * f8 + dx2 * f7 ) * dy;
f = h0 * dz2 + h1 * dz;
return ( f );
}
void InterpolateMesh( const Array &Coarse, Array &Fine )
{
PROFILE_START("InterpolateMesh");
// Interpolate values from a Coarse mesh to a fine one
// This routine assumes cell-centered meshes with 1 ghost cell
// Fine mesh
int Nx = int(Fine.size(0))-2;
int Ny = int(Fine.size(1))-2;
int Nz = int(Fine.size(2))-2;
// Coarse mesh
int nx = int(Coarse.size(0))-2;
int ny = int(Coarse.size(1))-2;
int nz = int(Coarse.size(2))-2;
// compute the stride
int hx = Nx/nx;
int hy = Ny/ny;
int hz = Nz/nz;
ASSERT(nx*hx==Nx);
ASSERT(ny*hy==Ny);
ASSERT(nz*hz==Nz);
// value to map distance between meshes (since distance is in voxels)
// usually hx=hy=hz (or something very close)
// the mapping is not exact
// however, it's assumed the coarse solution will be refined
// a good guess is the goal here!
float mapvalue = sqrt(hx*hx+hy*hy+hz*hz);
// Interpolate to the fine mesh
for (int k=-1; k=-1&&k0=0&&dz<=1);
for (int j=-1; j=-1&&j0=0&&dy<=1);
for (int i=-1; i=-1&&i0=0&&dx<=1);
float val = trilinear( dx, dy, dz,
Coarse(i1,j1,k1), Coarse(i2,j1,k1), Coarse(i1,j2,k1), Coarse(i2,j2,k1),
Coarse(i1,j1,k2), Coarse(i2,j1,k2), Coarse(i1,j2,k2), Coarse(i2,j2,k2) );
Fine(i+1,j+1,k+1) = mapvalue*val;
}
}
}
PROFILE_STOP("InterpolateMesh");
}
// Smooth the data using the distance
void smooth( const Array& VOL, const Array& Dist, float sigma, Array& MultiScaleSmooth, fillHalo& fillFloat )
{
for (size_t i=0; i0 ? -1:1;
MultiScaleSmooth(i) = tmp*VOL(i) + (1-tmp)*value;
}
fillFloat.fill(MultiScaleSmooth);
}
// Segment the data
void segment( const Array& data, Array& ID, float tol )
{
ASSERT(data.size()==ID.size());
for (size_t i=0; i tol )
ID(i) = 0;
else
ID(i) = 1;
}
}
// Remove disconnected phases
void removeDisconnected( Array& ID, const Domain& Dm )
{
// Run blob identification to remove disconnected volumes
BlobIDArray GlobalBlobID;
DoubleArray SignDist(ID.size());
DoubleArray Phase(ID.size());
for (size_t i=0; i 0 )
ID(i) = 0;
ID(i) = GlobalBlobID(i);
}
}
// Solve a level (without any coarse level information)
void solve( const Array& VOL, Array& Mean, Array& ID,
Array& Dist, Array& MultiScaleSmooth, Array& NonLocalMean,
fillHalo& fillFloat, const Domain& Dm, int nprocx,
float threshold, float lamda, float sigsq, int depth)
{
PROFILE_SCOPED(timer,"solve");
// Compute the median filter on the sparse array
Med3D( VOL, Mean );
fillFloat.fill( Mean );
segment( Mean, ID, threshold );
// Compute the distance using the segmented volume
CalcDist( Dist, ID, Dm );
fillFloat.fill(Dist);
smooth( VOL, Dist, 2.0, MultiScaleSmooth, fillFloat );
// Compute non-local mean
// int depth = 5;
// float sigsq=0.1;
int nlm_count = NLM3D( MultiScaleSmooth, Mean, Dist, NonLocalMean, depth, sigsq);
NULL_USE( nlm_count );
fillFloat.fill(NonLocalMean);
}
// Refine a solution from a coarse grid to a fine grid
void refine( const Array& Dist_coarse,
const Array& VOL, Array& Mean, Array& ID,
Array& Dist, Array& MultiScaleSmooth, Array& NonLocalMean,
fillHalo& fillFloat, const Domain& Dm, int nprocx, int level,
float threshold, float lamda, float sigsq, int depth)
{
PROFILE_SCOPED(timer,"refine");
int ratio[3] = { int(Dist.size(0)/Dist_coarse.size(0)),
int(Dist.size(1)/Dist_coarse.size(1)),
int(Dist.size(2)/Dist_coarse.size(2)) };
// Interpolate the distance from the coarse to fine grid
InterpolateMesh( Dist_coarse, Dist );
// Compute the median filter on the array and segment
Med3D( VOL, Mean );
fillFloat.fill( Mean );
segment( Mean, ID, threshold );
// If the ID has the wrong distance, set the distance to 0 and run a simple filter to set neighbors to 0
for (size_t i=0; i0 ? 1:0;
if ( id != ID(i) )
Dist(i) = 0;
}
fillFloat.fill( Dist );
std::function filter_1D = []( int N, const float* data )
{
bool zero = data[0]==0 || data[2]==0;
return zero ? data[1]*1e-12 : data[1];
};
std::vector BC(3,imfilter::BC::replicate);
std::vector> filter_set(3,filter_1D);
Dist = imfilter::imfilter_separable( Dist, {1,1,1}, filter_set, BC );
fillFloat.fill( Dist );
// Smooth the volume data
float h = 2*lamda*sqrt(double(ratio[0]*ratio[0]+ratio[1]*ratio[1]+ratio[2]*ratio[2]));
smooth( VOL, Dist, h, MultiScaleSmooth, fillFloat );
// Compute non-local mean
// int depth = 3;
// float sigsq = 0.1;
int nlm_count = NLM3D( MultiScaleSmooth, Mean, Dist, NonLocalMean, depth, sigsq);
NULL_USE( nlm_count );
fillFloat.fill(NonLocalMean);
segment( NonLocalMean, ID, 0.001 );
for (size_t i=0; i0 ? 1:0;
if ( id!=ID(i) || fabs(Dist(i))<1 )
Dist(i) = 2.0*ID(i)-1.0;
}
// Remove disconnected domains
//removeDisconnected( ID, Dm );
// Compute the distance using the segmented volume
if ( level > 0 ) {
CalcDist( Dist, ID, Dm );
fillFloat.fill(Dist);
}
}
// Remove regions that are likely noise by shrinking the volumes by dx,
// removing all values that are more than dx+delta from the surface, and then
// growing by dx+delta and intersecting with the original data
void filter_final( Array& ID, Array& Dist,
fillHalo& fillFloat, const Domain& Dm,
Array& Mean, Array& Dist1, Array& Dist2 )
{
PROFILE_SCOPED(timer,"filter_final");
int rank = Dm.Comm.getRank();
int Nx = Dm.Nx-2;
int Ny = Dm.Ny-2;
int Nz = Dm.Nz-2;
// Calculate the distance
CalcDist( Dist, ID, Dm );
fillFloat.fill(Dist);
// Compute the range to shrink the volume based on the L2 norm of the distance
Array Dist0(Nx,Ny,Nz);
fillFloat.copy(Dist,Dist0);
float tmp = 0;
for (size_t i=0; i(Dist0.length()) );
const float dx1 = 0.3*tmp;
const float dx2 = 1.05*dx1;
if (rank==0)
printf(" %0.1f %0.1f %0.1f\n",tmp,dx1,dx2);
// Update the IDs/Distance removing regions that are < dx of the range
Dist1 = Dist;
Dist2 = Dist;
Array ID1 = ID;
Array ID2 = ID;
for (size_t i=0; i dx1 ? 1:0;
}
//Array Dist1 = Dist;
//Array Dist2 = Dist;
CalcDist( Dist1, ID1, Dm );
CalcDist( Dist2, ID2, Dm );
fillFloat.fill(Dist1);
fillFloat.fill(Dist2);
// Keep those regions that are within dx2 of the new volumes
Mean = Dist;
for (size_t i=0; i0 && ID(i)<=0 ) {
Mean(i) = -1;
} else if ( Dist2(i)+dx2>0 && ID(i)>0 ) {
Mean(i) = 1;
} else {
Mean(i) = Dist(i)>0 ? 0.5:-0.5;
}
}
// Find regions of uncertainty that are entirely contained within another region
fillHalo fillDouble(Dm.Comm,Dm.rank_info,{Nx,Ny,Nz},{1,1,1},0,1);
fillHalo fillInt(Dm.Comm,Dm.rank_info,{Nx,Ny,Nz},{1,1,1},0,1);
BlobIDArray GlobalBlobID;
DoubleArray SignDist(ID.size());
for (size_t i=0; i mean(N_blobs,0);
std::vector count(N_blobs,0);
for (int k=1; k<=Nz; k++) {
for (int j=1; j<=Ny; j++) {
for (int i=1; i<=Nx; i++) {
int id = GlobalBlobID(i,j,k);
if ( id >= 0 ) {
if ( GlobalBlobID(i-1,j,k)<0 ) {
mean[id] += Mean(i-1,j,k);
count[id]++;
}
if ( GlobalBlobID(i+1,j,k)<0 ) {
mean[id] += Mean(i+1,j,k);
count[id]++;
}
if ( GlobalBlobID(i,j-1,k)<0 ) {
mean[id] += Mean(i,j-1,k);
count[id]++;
}
if ( GlobalBlobID(i,j+1,k)<0 ) {
mean[id] += Mean(i,j+1,k);
count[id]++;
}
if ( GlobalBlobID(i,j,k-1)<0 ) {
mean[id] += Mean(i,j,k-1);
count[id]++;
}
if ( GlobalBlobID(i,j,k+1)<0 ) {
mean[id] += Mean(i,j,k+1);
count[id]++;
}
}
}
}
}
mean = Dm.Comm.sumReduce(mean);
count = Dm.Comm.sumReduce(count);
for (size_t i=0; i= 0 ) {
if ( fabs(mean[id]) > 0.95 ) {
// Isolated domain surrounded by one domain
GlobalBlobID(i) = -2;
Mean(i) = sign(mean[id]);
} else {
// Boarder volume, set to liquid
Mean(i) = 1;
}
}
}
// Perform the final segmentation and update the distance
fillFloat.fill(Mean);
segment( Mean, ID, 0.01 );
CalcDist( Dist, ID, Dm );
fillFloat.fill(Dist);
}
// Filter the original data
void filter_src( const Domain& Dm, Array& src )
{
PROFILE_START("Filter source data");
int Nx = Dm.Nx-2;
int Ny = Dm.Ny-2;
int Nz = Dm.Nz-2;
fillHalo fillFloat(Dm.Comm,Dm.rank_info,{Nx,Ny,Nz},{1,1,1},0,1);
// Perform a hot-spot filter on the data
std::vector BC = { imfilter::BC::replicate, imfilter::BC::replicate, imfilter::BC::replicate };
std::function&)> filter_3D = []( const Array& data )
{
float min1 = std::min(data(0,1,1),data(2,1,1));
float min2 = std::min(data(1,0,1),data(1,2,1));
float min3 = std::min(data(1,1,0),data(1,1,2));
float max1 = std::max(data(0,1,1),data(2,1,1));
float max2 = std::max(data(1,0,1),data(1,2,1));
float max3 = std::max(data(1,1,0),data(1,1,2));
float min = std::min(min1,std::min(min2,min3));
float max = std::max(max1,std::max(max2,max3));
return std::max(std::min(data(1,1,1),max),min);
};
std::function&)> filter_1D = []( const Array& data )
{
float min = std::min(data(0),data(2));
float max = std::max(data(0),data(2));
return std::max(std::min(data(1),max),min);
};
//LOCVOL[0] = imfilter::imfilter( LOCVOL[0], {1,1,1}, filter_3D, BC );
std::vector&)>> filter_set(3,filter_1D);
src = imfilter::imfilter_separable( src, {1,1,1}, filter_set, BC );
fillFloat.fill( src );
// Perform a gaussian filter on the data
int Nh[3] = { 2, 2, 2 };
float sigma[3] = { 1.0, 1.0, 1.0 };
std::vector> H(3);
H[0] = imfilter::create_filter( { Nh[0] }, "gaussian", &sigma[0] );
H[1] = imfilter::create_filter( { Nh[1] }, "gaussian", &sigma[1] );
H[2] = imfilter::create_filter( { Nh[2] }, "gaussian", &sigma[2] );
src = imfilter::imfilter_separable( src, H, BC );
fillFloat.fill( src );
PROFILE_STOP("Filter source data");
}