#include <iostream> #include <fstream> #include <map> #include <i3d/draw.h> #include <i3d/morphology.h> #include <i3d/transform.h> #include <i3d/DistanceTransform.h> #include <i3d/filters.h> #include "TriangleMesh.h" #include "../cmath3d_v/TriangleMesh_v.h" #include "../src/rnd_generators.h" #include "../src/texture/texture.h" #undef min #undef max //some debug-code enabling triggers //#define SAVE_INTERMEDIATE_IMAGES //'multiple' should be ideally 10^desired_decimal_accuracy int inline RoundTo(const float val, const float multiple=1000.f) { return ( int(floorf(val*multiple)) ); } //puts v1 into Pos, with mPos being a helper structure preventing having //v1 multiple times inside the Pos long unsigned int Enlist( const Vector3FC& v1, std::vector<Vector3FC>& Pos, std::map< int,std::map< int,std::map< int,long unsigned int > > >& mPos) { long unsigned int o1; //ret val std::map< int,std::map< int,long unsigned int > >& mY=mPos[RoundTo(v1.x)]; if (mY.empty()) { Pos.push_back(v1); o1=Pos.size(); //add reference to this vertex in the mPos structure std::map< int,long unsigned int > mZ; mZ[RoundTo(v1.z)]=o1; std::map< int,std::map< int,long unsigned int > > my; my[RoundTo(v1.y)]=mZ; mPos[RoundTo(v1.x)]=my; } else { std::map< int,long unsigned int >& mZ=mY[RoundTo(v1.y)]; if (mZ.empty()) { Pos.push_back(v1); o1=Pos.size(); //add reference to this vertex in the mPos structure std::map< int,long unsigned int > mZ; mZ[RoundTo(v1.z)]=o1; mY[RoundTo(v1.y)]=mZ; } else { if (mZ[RoundTo(v1.z)] == 0) { Pos.push_back(v1); o1=Pos.size(); //add reference to this vertex in the mPos structure mZ[RoundTo(v1.z)]=o1; } else { o1=mZ[RoundTo(v1.z)]; } } } return o1; } int ActiveMesh::ImportSTL(const char *filename) { Pos.clear(); ID.clear(); norm.clear(); //a helper map to (efficiently) search for already stored vertices inside Pos std::map< int,std::map< int,std::map< int,long unsigned int > > > mPos; // x y z offset+1 in Pos //try to open the file std::ifstream file(filename); if (!file.is_open()) return 1; //read the "header" line char tmp[1024]; file >> tmp; //dangerous... //check tmp for "solid" or complain if (tmp[0] != 's' || tmp[1] != 'o' || tmp[2] != 'l' || tmp[3] != 'i' || tmp[4] != 'd') { file.close(); return(2); } //read (and skip) the rest of the header line file.ignore(10240,'\n'); //read facet by facet while (file >> tmp) { //check tmp for "facet" or "endsolid" or complain if (tmp[0] != 'f' || tmp[1] != 'a' || tmp[2] != 'c' || tmp[3] != 'e' || tmp[4] != 't') { //no new face starting, end of file then? if (tmp[0] != 'e' || tmp[1] != 'n' || tmp[2] != 'd' || tmp[3] != 's' || tmp[4] != 'o') { file.close(); return(3); } else break; } //read normal file >> tmp; //"normal" keyword float x,y,z; file >> x >> y >> z; Vector3F normal(x,y,z); //read triangle vertices file >> tmp; //check tmp for "outer" or complain if (tmp[0] != 'o' || tmp[1] != 'u' || tmp[2] != 't' || tmp[3] != 'e' || tmp[4] != 'r') { file.close(); return(4); } file >> tmp; //"loop" keyword file >> tmp; //"vertex" keyword file >> x >> y >> z; Vector3FC v1(x,y,z); file >> tmp; file >> x >> y >> z; Vector3FC v2(x,y,z); file >> tmp; file >> x >> y >> z; Vector3FC v3(x,y,z); file >> tmp; //"endloop" keyword file >> tmp; //"endfacet" keyword //add this triangle to the ActiveMesh data structures //we need to: // scale, round and use this for comparison against already // discovered vertices to avoid for having the same vertex saved twice long unsigned int o1,o2,o3; o1=Enlist(v1,Pos,mPos); o2=Enlist(v2,Pos,mPos); o3=Enlist(v3,Pos,mPos); // // three offsets to the Pos array should be output // add them to the ID array ID.push_back(o1-1); ID.push_back(o2-1); ID.push_back(o3-1); // add normal to the norm array norm.push_back(normal); /* std::cout << "v1: " << v1.x << "," << v1.y << "," << v1.z << " -- o1=" << o1 << "\n"; std::cout << "v2: " << v2.x << "," << v2.y << "," << v2.z << " -- o2=" << o2 << "\n"; std::cout << "v3: " << v3.x << "," << v3.y << "," << v3.z << " -- o3=" << o3 << "\n"; std::cout << "normal: " << normal.x << "," << normal.y << "," << normal.z << "\n\n"; */ } file.close(); return(0); } int ActiveMesh::ExportSTL(const char *filename) { //try to open the file std::ofstream file(filename); if (!file.is_open()) return(1); file << "solid Vladimir Ulman - meshSurface testing app\n"; for (unsigned int i=0; i < ID.size(); i+=3) { file << "facet normal " << norm[i/3].x << " " << norm[i/3].y << " " << norm[i/3].z << "\n"; file << "outer loop\n"; file << "vertex " << Pos[ID[i+0]].x << " " << Pos[ID[i+0]].y << " " << Pos[ID[i+0]].z << "\n"; file << "vertex " << Pos[ID[i+1]].x << " " << Pos[ID[i+1]].y << " " << Pos[ID[i+1]].z << "\n"; file << "vertex " << Pos[ID[i+2]].x << " " << Pos[ID[i+2]].y << " " << Pos[ID[i+2]].z << "\n"; file << "endloop\nendfacet\n"; } file.close(); return(0); } int ActiveMesh::ImportVTK(const char *filename) //surface version { Pos.clear(); ID.clear(); norm.clear(); //try to open the file std::ifstream file(filename); if (!file.is_open()) return 1; //read the "header" line char tmp[1024]; file >> tmp >> tmp; //dangerous... //check tmp for "vtk" or complain if (tmp[0] != 'v' || tmp[1] != 't' || tmp[2] != 'k') { file.close(); return(2); } //read (and skip) the rest of the header line file.ignore(10240,'\n'); //ignore "vtk output" file.ignore(10240,'\n'); //read "ASCII" file >> tmp; if (tmp[0] != 'A' || tmp[1] != 'S' || tmp[2] != 'C' || tmp[3] != 'I' || tmp[4] != 'I') { file.close(); return(3); } file.ignore(10240,'\n'); //search until DATASET lines is found int counter=0; file >> tmp; file.ignore(10240,'\n'); while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T' || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E') { file >> tmp; file.ignore(10240,'\n'); ++counter; if (counter == 10) { file.close(); return(35); } } //read points header int itemCount; file >> tmp >> itemCount;; if (tmp[0] != 'P' || tmp[1] != 'O' || tmp[2] != 'I' || tmp[3] != 'N' || tmp[4] != 'T' || tmp[5] != 'S') { file.close(); return(4); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " point coordinates\n"; Pos.reserve(itemCount); //read all points... float x,y,z; while (itemCount > 0 && file >> x) { file >> y >> z; //... and save them Vector3FC v1(x,y,z); //v1*=100.f; Pos.push_back(v1); --itemCount; } //std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n"; //prepare "information about faces normals" Vector3F fictiveNormal(1.f,0.f,0.f); //read polyhedra header file >> tmp >> itemCount; if ((tmp[0] != 'P' || tmp[1] != 'O' || tmp[2] != 'L' || tmp[3] != 'Y' || tmp[4] != 'G' || tmp[5] != 'O' || tmp[6] != 'N' || tmp[7] != 'S') && (tmp[0] != 'C' || tmp[1] != 'E' || tmp[2] != 'L' || tmp[3] != 'L' || tmp[4] != 'S')) { file.close(); return(5); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " triangles\n"; ID.reserve(3*itemCount); norm.reserve(itemCount); //read all polyhedra vertices int ignore,v1,v2,v3; while (itemCount > 0 && file >> ignore && ignore == 3) { file >> v1 >> v2 >> v3; //save v1,v2,v3 (TODO: if not already saved...) //make triangles use CW winding order ID.push_back(v1); ID.push_back(v3); ID.push_back(v2); norm.push_back(fictiveNormal); --itemCount; } //std::cout << "last triangle was: " << v1 << "," << v2 << "," << v3 << "\n"; file.close(); return(0); } int ActiveMesh::ImportVTK_Volumetric(const char *filename,bool saveAlsoTetrahedra) { Pos.clear(); ID.clear(); norm.clear(); if (saveAlsoTetrahedra) VolID.clear(); //try to open the file std::ifstream file(filename); if (!file.is_open()) return 1; //read the "header" line char tmp[1024]; file >> tmp >> tmp; //dangerous... //check tmp for "vtk" or complain if (tmp[0] != 'v' || tmp[1] != 't' || tmp[2] != 'k') { file.close(); return(2); } //read (and skip) the rest of the header line file.ignore(10240,'\n'); //ignore "vtk output" file.ignore(10240,'\n'); //read "ASCII" file >> tmp; if (tmp[0] != 'A' || tmp[1] != 'S' || tmp[2] != 'C' || tmp[3] != 'I' || tmp[4] != 'I') { file.close(); return(3); } file.ignore(10240,'\n'); //search until DATASET lines is found int counter=0; file >> tmp; file.ignore(10240,'\n'); while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T' || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E') { file >> tmp; file.ignore(10240,'\n'); ++counter; if (counter == 10) { file.close(); return(35); } } //read points header int itemCount; file >> tmp >> itemCount;; if (tmp[0] != 'P' || tmp[1] != 'O' || tmp[2] != 'I' || tmp[3] != 'N' || tmp[4] != 'T' || tmp[5] != 'S') { file.close(); return(4); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " point coordinates\n"; Pos.reserve(itemCount); //read all points... float x,y,z; while (itemCount > 0 && file >> x) { file >> y >> z; //... and save them Vector3FC v1(x,y,z); //v1*=100.f; Pos.push_back(v1); --itemCount; } //std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n"; //prepare "information about faces normals" Vector3F fictiveNormal(1.f,0.f,0.f); //read polyhedra header file >> tmp >> itemCount; if (tmp[0] != 'C' || tmp[1] != 'E' || tmp[2] != 'L' || tmp[3] != 'L' || tmp[4] != 'S') { file.close(); return(5); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " polyhedra\n"; ID.reserve(3*itemCount); norm.reserve(itemCount); //read all polyhedra vertices int ignore,v1,v2,v3,v4; while (itemCount > 0 && file >> ignore && ignore == 4) { file >> v1 >> v2 >> v3 >> v4; //save v1,v2,v3 (TODO: if not already saved...) ID.push_back(v1); ID.push_back(v2); ID.push_back(v3); norm.push_back(fictiveNormal); //save v1,v2,v4 (TODO: if not already saved...) ID.push_back(v1); ID.push_back(v2); ID.push_back(v4); norm.push_back(fictiveNormal); //save v1,v4,v3 (TODO: if not already saved...) ID.push_back(v1); ID.push_back(v4); ID.push_back(v3); norm.push_back(fictiveNormal); //save v4,v2,v3 (TODO: if not already saved...) ID.push_back(v4); ID.push_back(v2); ID.push_back(v3); norm.push_back(fictiveNormal); if (saveAlsoTetrahedra) { VolID.push_back(v1); VolID.push_back(v2); VolID.push_back(v3); VolID.push_back(v4); } --itemCount; } //std::cout << "last polyhedron was: " << v1 << "," << v2 << "," << v3 << "," << v4 << "\n"; file.close(); return(0); } int ActiveMesh::ImportVTK_Ftree(const char *filename,const int idx, const float stretch,bool resetMesh) { const int PointsOnRadiusPeriphery=10; const float radiusCorrection=stretch; //make sure the F-tree array is long enough to host the requested index while (Ftree.size() < idx+1) Ftree.push_back(mesh_t()); mesh_t& filoMesh=Ftree[idx]; if (resetMesh) { filoMesh.Pos.clear(); filoMesh.ID.clear(); filoMesh.norm.clear(); } //local/temporary list of vertices of segments that make up the f-tree //later, we convert these to triangles and save these guys instead fPoints.clear(); segFromPoint.clear(); segToPoint.clear(); segFromRadius.clear(); //try to open the file std::ifstream file(filename); if (!file.is_open()) return 1; //read the "header" line char tmp[1024]; file >> tmp >> tmp; //dangerous... //check tmp for "vtk" or complain if (tmp[0] != 'v' || tmp[1] != 't' || tmp[2] != 'k') { file.close(); return(2); } //read (and skip) the rest of the header line file.ignore(10240,'\n'); //ignore "vtk output" file.ignore(10240,'\n'); //read "ASCII" file >> tmp; if (tmp[0] != 'A' || tmp[1] != 'S' || tmp[2] != 'C' || tmp[3] != 'I' || tmp[4] != 'I') { file.close(); return(3); } file.ignore(10240,'\n'); //search until DATASET lines is found int counter=0; file >> tmp; file.ignore(10240,'\n'); while (tmp[0] != 'D' || tmp[1] != 'A' || tmp[2] != 'T' || tmp[3] != 'A' || tmp[4] != 'S' || tmp[5] != 'E') { file >> tmp; file.ignore(10240,'\n'); ++counter; if (counter == 10) { file.close(); return(35); } } //read points header int itemCount; file >> tmp >> itemCount;; if (tmp[0] != 'P' || tmp[1] != 'O' || tmp[2] != 'I' || tmp[3] != 'N' || tmp[4] != 'T' || tmp[5] != 'S') { file.close(); return(4); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " point coordinates\n"; fPoints.reserve(itemCount); //read all points... float x,y,z; while (itemCount > 0 && file >> x) { file >> y >> z; //... and save them Vector3FC v1(x,y,z); //v1*=100.f; fPoints.push_back(v1); --itemCount; } //std::cout << "last coordinate was: " << x << "," << y << "," << z << "\n"; //read segments header file >> tmp >> itemCount; if (tmp[0] != 'C' || tmp[1] != 'E' || tmp[2] != 'L' || tmp[3] != 'L' || tmp[4] != 'S') { file.close(); return(5); } file.ignore(10240,'\n'); //std::cout << "reading " << itemCount << " segments\n"; segFromPoint.reserve(itemCount); segToPoint.reserve(itemCount); segFromRadius.reserve(itemCount); //read all segments vertices int ignore,v1,v2; while (itemCount > 0 && file >> ignore && ignore == 2) { file >> v1 >> v2; segFromPoint.push_back(v1); segToPoint.push_back(v2); --itemCount; } //std::cout << "last segment was: " << v1 << "," << v2 << "\n"; //"ignore" cell types header, body file >> tmp >> itemCount; if (tmp[0] != 'C' || tmp[1] != 'E' || tmp[2] != 'L' || tmp[3] != 'L' || tmp[4] != '_' || tmp[5] != 'T' || tmp[6] != 'Y') { file.close(); return(6); } file.ignore(10240,'\n'); while (itemCount > 0) { file.ignore(10240,'\n'); --itemCount; } //read radii, i.e. CELL_DATA header file >> tmp >> itemCount; if (tmp[0] != 'C' || tmp[1] != 'E' || tmp[2] != 'L' || tmp[3] != 'L' || tmp[4] != '_' || tmp[5] != 'D' || tmp[6] != 'A') { file.close(); return(7); } file.ignore(10240,'\n'); file.ignore(10240,'\n'); //ignore SCALARS.. file.ignore(10240,'\n'); //ignore LOOKUP_TABLE //read the radii v1=0; while (itemCount > 0 && file >> x) { segFromRadius.push_back(x*radiusCorrection); --itemCount; ++v1; } file.close(); //now widen segments and save triangles... //prepare "information about faces normals" Vector3F fictiveNormal(1.f,0.f,0.f); size_t firstRadiusPoint=filoMesh.Pos.size(); PointsFirstOffset=firstRadiusPoint; //for all bending points except the last one (tip) for (unsigned int i=0; i < segFromPoint.size(); ++i) { //we need to construct rotation matrix that would //rotate points on a circle which lays in the XZ plane //(Y axis is normal to it then) such that the points //would lay in the plane to which this new Y axis //would be normal // //new Y axis Vector3F nYaxis=fPoints[segToPoint[i]]; if (i == 0) nYaxis-=fPoints[segFromPoint[i]]; else { nYaxis-=fPoints[segFromPoint[i-1]]; nYaxis/=2.f; //unnecessary scaling } Vector3F nZaxis(0.f,1.f,0.f); //in fact it is original Yaxis _for now_ Vector3F nXaxis; //new X axis is perpendicular to the original and new Y axis Mul(nYaxis,nZaxis,nXaxis); //new Z axis is perpendicular to the new X and Y axes Mul(nYaxis,nXaxis,nZaxis); //normalize... nXaxis/=nXaxis.Len(); nZaxis/=nZaxis.Len(); //now render the points on the circle and project them into the scene for (int p=0; p < PointsOnRadiusPeriphery; ++p) { //the point in its original position float x=segFromRadius[i]*cosf(6.28f*float(p)/float(PointsOnRadiusPeriphery)); float z=segFromRadius[i]*sinf(6.28f*float(p)/float(PointsOnRadiusPeriphery)); //rotate Vector3FC V(x*nXaxis); V+=z*nZaxis; //shift to the centre and save V+=fPoints[segFromPoint[i]]; filoMesh.Pos.push_back(V); } } //finally, add the tip point filoMesh.Pos.push_back(fPoints[segToPoint.back()]); //now create triangles for all segment strips //except the last one (that leads to the tip) for (unsigned int i=1; i < segFromPoint.size(); ++i) { int p=0; for (; p < PointsOnRadiusPeriphery-1; ++p) { filoMesh.ID.push_back(firstRadiusPoint+p); filoMesh.ID.push_back(firstRadiusPoint+p+1); filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery); filoMesh.norm.push_back(fictiveNormal); filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery+1); filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery); filoMesh.ID.push_back(firstRadiusPoint+p+1); filoMesh.norm.push_back(fictiveNormal); } filoMesh.ID.push_back(firstRadiusPoint+p); filoMesh.ID.push_back(firstRadiusPoint); filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery); filoMesh.norm.push_back(fictiveNormal); filoMesh.ID.push_back(firstRadiusPoint +PointsOnRadiusPeriphery); filoMesh.ID.push_back(firstRadiusPoint+p+PointsOnRadiusPeriphery); filoMesh.ID.push_back(firstRadiusPoint); filoMesh.norm.push_back(fictiveNormal); firstRadiusPoint+=PointsOnRadiusPeriphery; } //the last segment... int p=0; for (; p < PointsOnRadiusPeriphery-1; ++p) { filoMesh.ID.push_back(firstRadiusPoint+p); filoMesh.ID.push_back(firstRadiusPoint+p+1); filoMesh.ID.push_back(firstRadiusPoint+PointsOnRadiusPeriphery); filoMesh.norm.push_back(fictiveNormal); } filoMesh.ID.push_back(firstRadiusPoint+p); filoMesh.ID.push_back(firstRadiusPoint); filoMesh.ID.push_back(firstRadiusPoint+PointsOnRadiusPeriphery); filoMesh.norm.push_back(fictiveNormal); //render the filopodium texture points along its skeleton //the amount of points is determined from the radius, from circle area -> square of radius for (size_t i=0; i < segFromPoint.size(); ++i) { //time-savers... const Vector3F& fP=fPoints[segFromPoint[i]]; const Vector3F& tP=fPoints[segToPoint[i]]; const float length=(tP-fP).Len(); //full segment has length = 0.005 //stepping along the axis const int S=(int)ceil(5.f*length/0.005); //"fraction factor" std::cout << "S=" << S << ", length=" << length << "\n"; for (int s=0; s < S; ++s) { //ratios "from" and "to" const float fS=float(S-s)/float(S); const float tS=float(s)/float(S); //target radius const float target_r=(i+1 < segFromPoint.size())? segFromRadius[i+1] : 0.f; //current radius float r=fS*segFromRadius[i] + tS*target_r; //current position Vector3F p =fS*fP; p+=tS*tP; //modify coordinates exactly the same as mesh coordinates will be modified p.x*=180.f; p.y*=180.f; p.z*=250.f; p.x+=13.75f; p.y+=13.75f; p.z+=11.00f; //insert fl. dots r*=4000.f; std::cout << s << ": r=" << r << ", r*r=" << r*r << "\n"; //float dotsCount=10.f*r*r; float dotsCount=100.f*r; int qM=(dotsCount > 1300.f)? 1300 : (int)dotsCount; for (int q=0; q < qM; ++q) { dots_filo.push_back(p); dots_filo.push_back(p+Vector3F(0.0f,0.0f,+0.1f)); dots_filo.push_back(p+Vector3F(0.0f,0.0f,-0.1f)); } } } return(0); } void ActiveMesh::RenderMask_body(i3d::Image3d<i3d::GRAY16>& mask,const bool showTriangles) { //time savers: resolution const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); //time savers: offset const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; //create "aside" empty image similar to the input-output one i3d::Image3d<i3d::GRAY16> tmpI; tmpI.CopyMetaData(mask); tmpI.GetVoxelData()=0; //over all triangles for (unsigned int i=0; i < ID.size()/3; ++i) //for (unsigned int i=0; i < 15; ++i) { const Vector3F& v1=Pos[ID[3*i+0]]; const Vector3F& v2=Pos[ID[3*i+1]]; const Vector3F& v3=Pos[ID[3*i+2]]; //sweeping (and rendering) the triangle for (float c=0.f; c <= 1.0f; c += 0.1f) for (float b=0.f; b <= (1.0f-c); b += 0.1f) { float a=1.0f -b -c; Vector3F v=a*v1; v+=b*v2; v+=c*v3; //std::cout << "ID #" << i << ": v=(" << v.x << "," << v.y << "," << v.z << ")\n"; //nearest neighbor pixel coordinate const int x=(int)roundf( (v.x-xOff) *xRes); const int y=(int)roundf( (v.y-yOff) *yRes); const int z=(int)roundf( (v.z-zOff) *zRes); short val=(showTriangles)? short(i%5 *30 +100) : 100; if (tmpI.Include(x,y,z)) tmpI.SetVoxel(x,y,z,val); } } //this floods cell exterior from the image corner i3d::Image3d<i3d::GRAY16> tmpMask(tmpI); i3d::Dilation(tmpMask,tmpI,i3d::nb3D_o18); i3d::FloodFill(tmpI,(i3d::GRAY16)50,0); //this removes the flooding while filling //everything else (and closing holes in this was) i3d::GRAY16* pI=tmpI.GetFirstVoxelAddr(); i3d::GRAY16* pM=mask.GetFirstVoxelAddr(); i3d::GRAY16* const pL=pM+mask.GetImageSize(); while (pM != pL) { *pM=(*pI != 50)? std::max(*pI,i3d::GRAY16(100)) : *pM; ++pI; ++pM; } } void ActiveMesh::RenderMask_filo(i3d::Image3d<i3d::GRAY16>& mask,const int idx,const bool showTriangles) { //if index out of range, do nothing if (idx >= Ftree.size()) return; //time savers: resolution const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); //time savers: offset const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; //create "aside" empty image similar to the input-output one i3d::Image3d<i3d::GRAY16> tmpI; tmpI.CopyMetaData(mask); tmpI.GetVoxelData()=0; //over all triangles of the given filopodia const mesh_t& filoMesh=Ftree[idx]; for (unsigned int i=0; i < filoMesh.ID.size()/3; ++i) { const Vector3F& v1=filoMesh.Pos[filoMesh.ID[3*i+0]]; const Vector3F& v2=filoMesh.Pos[filoMesh.ID[3*i+1]]; const Vector3F& v3=filoMesh.Pos[filoMesh.ID[3*i+2]]; //sweeping (and rendering) the triangle for (float c=0.f; c <= 1.0f; c += 0.1f) for (float b=0.f; b <= (1.0f-c); b += 0.1f) { float a=1.0f -b -c; Vector3F v=a*v1; v+=b*v2; v+=c*v3; //nearest neighbor pixel coordinate const int x=(int)roundf( (v.x-xOff) *xRes); const int y=(int)roundf( (v.y-yOff) *yRes); const int z=(int)roundf( (v.z-zOff) *zRes); short val=(showTriangles)? short(i%5 *30 +100) : 101+idx; if (tmpI.Include(x,y,z)) tmpI.SetVoxel(x,y,z,val); } } //this floods cell exterior from the image corner i3d::Image3d<i3d::GRAY16> tmpMask(tmpI); i3d::Dilation(tmpMask,tmpI,i3d::nb3D_o18); i3d::FloodFill(tmpI,(i3d::GRAY16)50,0); //this removes the flooding while filling //everything else (and closing holes in this was) i3d::GRAY16* pI=tmpI.GetFirstVoxelAddr(); i3d::GRAY16* pM=mask.GetFirstVoxelAddr(); i3d::GRAY16* const pL=pM+mask.GetImageSize(); while (pM != pL) { *pM=(*pI != 50)? std::max(*pI,i3d::GRAY16(101+idx)) : *pM; ++pI; ++pM; } } void ActiveMesh::RenderMaskB(i3d::Image3d<i3d::GRAY16>& mask) { //time savers: resolution const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); //time savers: offset const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; //over all triangles //for (unsigned int i=0; i < ID.size()/3; ++i) for (unsigned int i=0; i < 15; ++i) { const Vector3F& v1=Pos[ID[3*i+0]]; const Vector3F& v2=Pos[ID[3*i+1]]; const Vector3F& v3=Pos[ID[3*i+2]]; //Vector3F e12=v2-v1; //ID=0 //Vector3F e13=v3-v1; //ID=1 //Vector3F e23=v3-v2; //ID=2 Vector3F edges[3]={v2-v1,v3-v1,v3-v2}; short order[3]={0,1,2}; if (edges[1].LenQ() < edges[0].LenQ()) { order[0]=1; order[1]=0; } if (edges[2].LenQ() < edges[order[1]].LenQ()) { order[2]=order[1]; order[1]=2; if (edges[2].LenQ() < edges[order[0]].LenQ()) { order[1]=order[0]; order[0]=2; } } //point within a triangle will of the form: vv + b*vb + c*vc //vb is the shortest edge, vc is the longest edge //vv is their common vertex //vb,vc are oriented outward from this vertex Vector3F vv,vb,vc; if (order[0]==0 && order[2]==1) { //order[0] "points" at shortest edge vv=v1; vb=edges[0]; vc=edges[1]; } else if (order[0]==0 && order[2]==2) { vv=v2; vb=-edges[0]; vc=edges[2]; } else if (order[0]==1 && order[2]==0) { vv=v1; vb=edges[1]; vc=edges[0]; } else if (order[0]==1 && order[2]==2) { vv=v3; vb=-edges[1]; vc=-edges[2]; } else if (order[0]==2 && order[2]==0) { vv=v2; vb=edges[2]; vc=-edges[0]; } else if (order[0]==2 && order[2]==1) { vv=v3; vb=-edges[2]; vc=-edges[1]; } //optimal increment for the short and long edge, respectively float db=(vb.x*xRes > vb.y*yRes)? vb.x*xRes : vb.y*yRes; db=(vb.z*zRes > db)? vb.z*zRes : db; db=1.f/db; float dc=(vc.x*xRes > vc.y*yRes)? vc.x*xRes : vc.y*yRes; dc=(vc.z*zRes > dc)? vc.z*zRes : dc; dc=1.f/dc; std::cout << "\nID #" << i << ": v1=(" << v1.x << "," << v1.y << "," << v1.z << ")\n"; std::cout << "ID #" << i << ": v2=(" << v2.x << "," << v2.y << "," << v2.z << ")\n"; std::cout << "ID #" << i << ": v3=(" << v3.x << "," << v3.y << "," << v3.z << ")\n"; //sweeping (and rendering) the triangle for (float c=0.f; c <= 1.0f; c += dc) for (float b=0.f; b <= (1.0f-c); b += db) { Vector3F v=vv; v+=b*vb; v+=c*vc; std::cout << "ID #" << i << ": v=(" << v.x << "," << v.y << "," << v.z << ")\n"; //nearest neighbor pixel coordinate const int x=(int)roundf( (v.x-xOff) *xRes); const int y=(int)roundf( (v.y-yOff) *yRes); const int z=(int)roundf( (v.z-zOff) *zRes); if (mask.Include(x,y,z)) mask.SetVoxel(x,y,z,short(i)); } } } void ActiveMesh::RenderOneTimeTexture(const i3d::Image3d<i3d::GRAY16>& mask, i3d::Image3d<i3d::GRAY16>& texture) { i3d::Image3d<float> dt; i3d::GrayToFloat(mask,dt); i3d::EDM(dt,0,100.f,false); #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("1_DTAlone.ics"); #endif i3d::Image3d<float> perlinInner,perlinOutside; perlinInner.CopyMetaData(mask); DoPerlin3D(perlinInner,5.0,0.8*1.5,0.7*1.5,6); #ifdef SAVE_INTERMEDIATE_IMAGES //perlinInner.SaveImage("2_PerlinAlone_Inner.ics"); #endif perlinOutside.CopyMetaData(mask); DoPerlin3D(perlinOutside,1.8,0.8*1.5,0.7*1.5,6); #ifdef SAVE_INTERMEDIATE_IMAGES //perlinOutside.SaveImage("2_PerlinAlone_Outside.ics"); #endif i3d::Image3d<i3d::GRAY16> erroded; i3d::ErosionO(mask,erroded,1); //initial object intensity levels float* p=dt.GetFirstVoxelAddr(); float* const pL=p+dt.GetImageSize(); const float* pI=perlinInner.GetFirstVoxelAddr(); const float* pO=perlinOutside.GetFirstVoxelAddr(); const i3d::GRAY16* er=erroded.GetFirstVoxelAddr(); while (p != pL) { //are we within the mask? if (*p > 0.f) { //close to the surface? if (*p < 0.3f || *er == 0) *p=2000.f + 5000.f*(*pO); //corona else *p=600.f + 600.f*(*pI); //inside if (*er == 0) *p=2000.f; //std::max(*p,2000.f); //corona if (*p < 0.f) *p=0.f; } ++p; ++pI; ++pO; ++er; } #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("3_texture.ics"); #endif perlinInner.DisposeData(); perlinOutside.DisposeData(); erroded.DisposeData(); i3d::GaussIIR(dt,3.f,3.f,2.5f); #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("4_texture_filtered.ics"); #endif //downsample now: float factor[3]={1.f,1.f,0.125f}; i3d::Image3d<float>* tmp=NULL; i3d::lanczos_resample(&dt,tmp,factor,2); #ifdef SAVE_INTERMEDIATE_IMAGES tmp->SaveImage("5_texture_filtered_resampled.ics"); #endif dt=*tmp; delete tmp; p=dt.GetFirstVoxelAddr(); for (int z=0; z < (signed)dt.GetSizeZ(); ++z) for (int y=0; y < (signed)dt.GetSizeY(); ++y) for (int x=0; x < (signed)dt.GetSizeX(); ++x, ++p) { //background signal: float distSq=((float)x-110.f)*((float)x-110.f) + ((float)y-110.f)*((float)y-110.f); *p+=150.f*expf(-0.5f * distSq / 2500.f); //uncertainty in the number of incoming photons const float noiseMean = sqrtf(*p), // from statistics: shot noise = sqrt(signal) noiseVar = noiseMean; // for Poisson distribution E(X) = D(X) *p+=40.f*((float)GetRandomPoisson(noiseMean) - noiseVar); //constants are parameters of Andor iXon camera provided from vendor: //photon shot noise: dark current *p+=(float)GetRandomPoisson(0.06f); //read-out noise: // variance up to 25.f (old camera on ILBIT) // variance about 1.f (for the new camera on ILBIT) *p+=GetRandomGauss(480.f,20.f); //*p+=530.f; } #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("6_texture_filtered_resampled_finalized.ics"); #endif //obtain final GRAY16 image i3d::FloatToGrayNoWeight(dt,texture); } void ActiveMesh::CenterMesh(const Vector3F& newCentre) { //calc geom. centre double x=0.,y=0.,z=0.; for (unsigned int i=0; i < Pos.size(); ++i) { x+=Pos[i].x; y+=Pos[i].y; z+=Pos[i].z; } x/=double(Pos.size()); y/=double(Pos.size()); z/=double(Pos.size()); //std::cout << "mesh centre is: " << x << "," << y << "," << z << "\n"; x-=newCentre.x; y-=newCentre.y; z-=newCentre.z; //shift the centre to point (0,0,0) for (unsigned int i=0; i < Pos.size(); ++i) { Pos[i].x-=float(x); Pos[i].y-=float(y); Pos[i].z-=float(z); } for (unsigned int i=0; i < fPoints.size(); ++i) { fPoints[i].x-=float(x); fPoints[i].y-=float(y); fPoints[i].z-=float(z); } } void ActiveMesh::ScaleMesh(const Vector3F& scale) { //cell body for (unsigned int i=0; i < Pos.size(); ++i) { Pos[i].x*=scale.x; Pos[i].y*=scale.y; Pos[i].z*=scale.z; } //filopodia for (size_t f=0; f < Ftree.size(); ++f) { mesh_t& filoMesh=Ftree[f]; for (unsigned int i=0; i < filoMesh.Pos.size(); ++i) { filoMesh.Pos[i].x*=scale.x; filoMesh.Pos[i].y*=scale.y; filoMesh.Pos[i].z*=scale.z; } } } void ActiveMesh::TranslateMesh(const Vector3F& shift) { //cell body for (unsigned int i=0; i < Pos.size(); ++i) { Pos[i].x+=shift.x; Pos[i].y+=shift.y; Pos[i].z+=shift.z; } //filopodia for (size_t f=0; f < Ftree.size(); ++f) { mesh_t& filoMesh=Ftree[f]; for (unsigned int i=0; i < filoMesh.Pos.size(); ++i) { filoMesh.Pos[i].x+=shift.x; filoMesh.Pos[i].y+=shift.y; filoMesh.Pos[i].z+=shift.z; } } } // ============== surface fitting ============== int ActiveMesh::CalcQuadricSurface_Taubin(const int vertexID, float (&coeffs)[10]) { //determine some reasonable number of nearest neighbors std::vector< std::vector<size_t> > neigsLeveled; ulm::getVertexNeighbours(*this,vertexID,2,neigsLeveled); //make it flat... std::vector<size_t> neigs; for (unsigned int l=0; l < neigsLeveled.size(); ++l) for (unsigned int i=0; i < neigsLeveled[l].size(); ++i) neigs.push_back(neigsLeveled[l][i]); neigsLeveled.clear(); //V be the vector of [x,y,z] combinations, a counterpart to coeffs float V[10],V1[10],V2[10],V3[10]; //M be the matrix holding initially sum of (square matrices) V*V' //N is similar, just a sum of three different Vs is used float M[100],N[100]; for (int i=0; i < 100; ++i) M[i]=N[i]=0.f; //over all neighbors (including the centre vertex itself), // //this order garuantees that array V will be relevant for input //vertexID after the cycles are over -- will become handy later for (signed int n=(int)neigs.size()-1; n >= 0; --n) { //shortcut to the current point const Vector3FC& v=Pos[neigs[n]]; //get Vs for the given point 'v' V[0]=1.f; V1[0]=0.f; V2[0]=0.f; V3[0]=0.f; V[1]=v.x; V1[1]=1.f; V2[1]=0.f; V3[1]=0.f; V[2]=v.y; V1[2]=0.f; V2[2]=1.f; V3[2]=0.f; V[3]=v.z; V1[3]=0.f; V2[3]=0.f; V3[3]=1.f; V[4]=v.x*v.y; V1[4]=v.y; V2[4]=v.x; V3[4]=0.f; V[5]=v.x*v.z; V1[5]=v.z; V2[5]=0.f; V3[5]=v.x; V[6]=v.y*v.z; V1[6]=0.f; V2[6]=v.z; V3[6]=v.y; V[7]=v.x*v.x; V1[7]=2.f*v.x; V2[7]=0.f; V3[7]=0.f; V[8]=v.y*v.y; V1[8]=0.f; V2[8]=2.f*v.y; V3[8]=0.f; V[9]=v.z*v.z; V1[9]=0.f; V2[9]=0.f; V3[9]=2.f*v.z; //construct V*V' and add it to M and N for (int j=0; j < 9; ++j) //for column for (int i=0; i < 9; ++i) //for row; note the order optimal for Fortran { //C order (row-major): M_i,j -> M[i][j] -> &M +i*STRIDE +j //Lapack/Fortran order (column-major): M_i,j -> M[i][j] -> &M +j*STRIDE +i //const int off=i*10 +j; //C const int off=j*9 +i; //Fortran M[off]+= V[j+1]* V[i+1]; N[off]+=V1[j+1]*V1[i+1]; N[off]+=V2[j+1]*V2[i+1]; N[off]+=V3[j+1]*V3[i+1]; } } //now, solve the generalized eigenvector of the matrix pair (M,N): //MC = nNC // //M,N are (reduced) 9x9 matrices constructed above, //C is vector (infact, the coeff), n is scalar Lagrange multiplier // //if M,N were (full-size) 10x10 matrices, the N would be singular //as the 1st row would contain only zeros, //it is therefore reduced to 9x9 sacrifing the first row // //according to netlib (Lapack) docs, http://www.netlib.org/lapack/lug/node34.html //type 1, Az=lBz -- A=M, B=N, z=C //function: SSYGV /* lapack_int itype=1; char jobz='V'; char uplo='U'; lapack_int n=9; float w[10]; float work[512]; lapack_int lwork=512; lapack_int info; LAPACK_ssygv(&itype,&jobz,&uplo,&n,M,&n,N,&n,w,work,&lwork,&info); std::cout << "vertices considered: " << neigs.size() << "\n"; std::cout << "info=" << info << " (0 is OK)\n"; std::cout << "work(1)=" << work[0] << " (should be below 512)\n"; //if some error, report it to the caller if (info != 0) return info; //M is now matrix of eigenvectors //it should hold (according to Lapack docs): //Z^T N Z = I where Z is one eigenvector, I is identity matrix // //w holds eigenvalues in ascending order //our result c[1]...c[9] is the eigenvector //corresponding to the smallest non-negative eigenvalue, so the j-th eigenvector int j=0; while (j < 9 && w[j] < 0.f) ++j; //have we found some non-negative eigenvalue? if (j == 9) return(-9999); //also: //the last missing coefficient c[0] we will determine by submitting //the given input vertex to the algebraic expresion of the surface //(given with coeffs) and equating it to zero: coeffs[0]=0.f; for (int i=0; i < 9; ++i) { coeffs[i+1]=M[j*9 +i]; //copy eigenvector coeffs[0]-=coeffs[i+1]*V[i+1]; //determine c[0] } std::cout << "w(j)=" << w[j] << ", j=" << j << "\n"; */ return 0; } bool ActiveMesh::GetPointOnQuadricSurface(const float x,const float y, float &z1, float &z2, const float (&coeffs)[10]) { const float a=coeffs[9]; const float b=coeffs[3] +coeffs[5]*x +coeffs[6]*y; const float c=coeffs[0] +coeffs[1]*x +coeffs[2]*y +coeffs[4]*x*y +coeffs[7]*x*x +coeffs[8]*y*y; const float sqArg=b*b - 4*a*c; if (sqArg < 0.f) return false; if (a == 0.f) return false; z1=(-b + sqrtf(sqArg)) / (2.f*a); z2=(-b - sqrtf(sqArg)) / (2.f*a); return true; } float ActiveMesh::GetClosestPointOnQuadricSurface(Vector3F& point, const float (&coeffs)[10]) { //backup original input coordinate const float x=point.x; const float y=point.y; const float z=point.z; float tmp1,tmp2; //list of possible coordinates std::vector<Vector3F> pointAdepts; //took a pair of coordinates, calculate the third one //and make it an adept... if (GetPointOnQuadricSurface(x,y,tmp1,tmp2,coeffs)) { pointAdepts.push_back(Vector3F(x,y,tmp1)); pointAdepts.push_back(Vector3F(x,y,tmp2)); } if (GetPointOnQuadricSurface(x,z,tmp1,tmp2,coeffs)) { pointAdepts.push_back(Vector3F(x,tmp1,z)); pointAdepts.push_back(Vector3F(x,tmp2,z)); } if (GetPointOnQuadricSurface(y,z,tmp1,tmp2,coeffs)) { pointAdepts.push_back(Vector3F(tmp1,y,z)); pointAdepts.push_back(Vector3F(tmp2,y,z)); } //are we doomed? if (pointAdepts.size() == 0) return (-999999.f); //find the closest int closestIndex=ChooseClosestPoint(pointAdepts,point); //calc distance to it point-=pointAdepts[closestIndex]; tmp1=point.Len(); //adjust the input/output point point=pointAdepts[closestIndex]; return (tmp1); } int ActiveMesh::ChooseClosestPoint(const std::vector<Vector3F>& points, const Vector3F& point) { int minIndex=-1; float minSqDist=9999999999999.f; Vector3F p; for (unsigned int i=0; i < points.size(); ++i) { p=point; p-=points[i]; if (p.LenQ() < minSqDist) { minIndex=i; minSqDist=p.LenQ(); } } return minIndex; } void ActiveMesh::InitDots_body(const i3d::Image3d<i3d::GRAY16>& mask) { i3d::Image3d<float> dt; i3d::GrayToFloat(mask,dt); i3d::EDM(dt,0,100.f,false); #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("1_DTAlone.ics"); #endif i3d::Image3d<float> perlinInner,perlinOutside; perlinInner.CopyMetaData(mask); DoPerlin3D(perlinInner,5.0,0.8*1.5,0.7*1.5,6); #ifdef SAVE_INTERMEDIATE_IMAGES //perlinInner.SaveImage("2_PerlinAlone_Inner.ics"); #endif perlinOutside.CopyMetaData(mask); DoPerlin3D(perlinOutside,1.8,0.8*1.5,0.7*1.5,6); #ifdef SAVE_INTERMEDIATE_IMAGES //perlinOutside.SaveImage("2_PerlinAlone_Outside.ics"); #endif i3d::Image3d<i3d::GRAY16> erroded; i3d::ErosionO(mask,erroded,1); //initial object intensity levels float* p=dt.GetFirstVoxelAddr(); float* const pL=p+dt.GetImageSize(); const float* pI=perlinInner.GetFirstVoxelAddr(); const float* pO=perlinOutside.GetFirstVoxelAddr(); const i3d::GRAY16* er=erroded.GetFirstVoxelAddr(); while (p != pL) { //are we within the mask? if (*p > 0.f) { //close to the surface? if (*p < 0.3f || *er == 0) *p=2000.f + 5000.f*(*pO); //corona else *p=600.f + 600.f*(*pI); //inside if (*er == 0) *p=2000.f; //std::max(*p,2000.f); //corona if (*p < 0.f) *p=0.f; } ++p; ++pI; ++pO; ++er; } #ifdef SAVE_INTERMEDIATE_IMAGES dt.SaveImage("3_texture.ics"); #endif perlinInner.DisposeData(); perlinOutside.DisposeData(); erroded.DisposeData(); //now, read the "molecules" dots.clear(); dots.reserve(1<<23); //time savers... const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; p=dt.GetFirstVoxelAddr(); for (int z=0; z < (signed)dt.GetSizeZ(); ++z) for (int y=0; y < (signed)dt.GetSizeY(); ++y) for (int x=0; x < (signed)dt.GetSizeX(); ++x, ++p) for (int v=0; v < *p; v+=50) { //convert px coords into um const float X=(float)x/xRes + xOff; const float Y=(float)y/yRes + yOff; const float Z=(float)z/zRes + zOff; dots.push_back(Vector3F(X,Y,Z)); if (dots.size() == dots.capacity()) { std::cout << "reserving more at position: " << x << "," << y << "," << z << "\n"; dots.reserve(dots.size()+(1<<20)); } } //#ifdef SAVE_INTERMEDIATE_IMAGES std::cout << "intiated " << dots.size() << " fl. molecules (capacity is for " << dots.capacity() << ")\n"; //#endif } void ActiveMesh::InitDots_filo(const i3d::Image3d<i3d::GRAY16>& mask) { //time savers... const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; const i3d::GRAY16* p=mask.GetFirstVoxelAddr(); for (int z=0; z < (signed)mask.GetSizeZ(); ++z) for (int y=0; y < (signed)mask.GetSizeY(); ++y) for (int x=0; x < (signed)mask.GetSizeX(); ++x, ++p) if (*p > 100) { //convert px coords into um const float X=(float)x/xRes + xOff; const float Y=(float)y/yRes + yOff; const float Z=(float)z/zRes + zOff; for (int q=0; q < 80; ++q) dots.push_back(Vector3F(X,Y,Z)); } } void ActiveMesh::BrownDots(const i3d::Image3d<i3d::GRAY16>& mask) { //TODO REMOVE ME for (size_t i=0; i < dots.size(); ++i) dots[i].x+=1.0f; } template <class VT> VT GetPixel(i3d::Image3d<VT> const &img,const float x,const float y,const float z) { /* //nearest neighbor: int X=static_cast<int>(roundf(x)); int Y=static_cast<int>(roundf(y)); int Z=static_cast<int>(roundf(z)); if (img.Include(X,Y,Z)) return(img.GetVoxel(X,Y,Z)); else return(0); */ //nearest not-greater integer coordinate, "o" in the picture in docs //X,Y,Z will be coordinate of the voxel no. 2 const int X=static_cast<int>(floorf(x)); const int Y=static_cast<int>(floorf(y)); const int Z=static_cast<int>(floorf(z)); //now we can write only to pixels at [X or X+1,Y or Y+1,Z or Z+1] //quit if too far from the "left" borders of the image //as we wouldn't be able to draw into the image anyway if ((X < -1) || (Y < -1) || (Z < -1)) return (0); //residual fraction of the input coordinate const float Xfrac=x - static_cast<float>(X); const float Yfrac=y - static_cast<float>(Y); const float Zfrac=z - static_cast<float>(Z); //the weights float A=0.0f,B=0.0f,C=0.0f,D=0.0f; //for 2D //x axis: A=D=Xfrac; B=C=1.0f - Xfrac; //y axis: A*=1.0f - Yfrac; B*=1.0f - Yfrac; C*=Yfrac; D*=Yfrac; //z axis: float A_=A,B_=B,C_=C,D_=D; A*=1.0f - Zfrac; B*=1.0f - Zfrac; C*=1.0f - Zfrac; D*=1.0f - Zfrac; A_*=Zfrac; B_*=Zfrac; C_*=Zfrac; D_*=Zfrac; //portions of the value in a bit more organized form, w[z][y][x] const float w[2][2][2]={{{B ,A },{C ,D }}, {{B_,A_},{C_,D_}}}; //the return value float v=0; //reading from the input image, //for (int zi=0; zi < 2; ++zi) if (Z+zi < (signed)img.GetSizeZ()) { //shortcut for 2D cases to avoid some computations... for (int zi=0; zi < 2; ++zi) for (int yi=0; yi < 2; ++yi) for (int xi=0; xi < 2; ++xi) if (img.Include(X+xi,Y+yi,Z+zi)) { //if we got here then we can safely change coordinate types v+=(float)img.GetVoxel((size_t)X+xi,(size_t)Y+yi,(size_t)Z+zi) * w[zi][yi][xi]; } //} return ( static_cast<VT>(v) ); } void ActiveMesh::FFDots(const i3d::Image3d<i3d::GRAY16>& mask, const FlowField<float> &FF) { //TODO: tests: FF consistency, same size as mask? //time savers... const float xRes=mask.GetResolution().GetX(); const float yRes=mask.GetResolution().GetY(); const float zRes=mask.GetResolution().GetZ(); const float xOff=mask.GetOffset().x; const float yOff=mask.GetOffset().y; const float zOff=mask.GetOffset().z; //apply FF on the this->dots (no boundary checking) for (size_t i=0; i < dots.size(); ++i) { //turn micron position into pixel one const float X=(dots[i].x -xOff) *xRes; const float Y=(dots[i].y -yOff) *yRes; const float Z=(dots[i].z -zOff) *zRes; //note: GetPixel() returns 0 in case we ask for value outside the image //TODO: check against mask dots[i].x += GetPixel(*FF.x, X,Y,Z); dots[i].y += GetPixel(*FF.y, X,Y,Z); dots[i].z += GetPixel(*FF.z, X,Y,Z); } } void ActiveMesh::RenderDots(const i3d::Image3d<i3d::GRAY16>& mask, i3d::Image3d<i3d::GRAY16>& texture) { texture.CopyMetaData(mask); texture.GetVoxelData()=0; //time savers... const float xRes=texture.GetResolution().GetX(); const float yRes=texture.GetResolution().GetY(); const float zRes=texture.GetResolution().GetZ(); const float xOff=texture.GetOffset().x; const float yOff=texture.GetOffset().y; const float zOff=texture.GetOffset().z; //time-savers for boundary checking... const int maxX=(int)texture.GetSizeX()-1; const int maxY=(int)texture.GetSizeY()-1; const int maxZ=(int)texture.GetSizeZ()-1; //time-savers for accessing neigbors... const size_t xLine=texture.GetSizeX(); const size_t Slice=texture.GetSizeY() *xLine; i3d::GRAY16* const T=texture.GetFirstVoxelAddr(); //render the points into the texture image for (size_t i=0; i < dots.size(); ++i) { const int x=(int)roundf( (dots[i].x-xOff) *xRes); const int y=(int)roundf( (dots[i].y-yOff) *yRes); const int z=(int)roundf( (dots[i].z-zOff) *zRes); if ((x > 0) && (y > 0) && (z > 0) && (x < maxX) && (y < maxY) && (z < maxZ)) T[z*Slice +y*xLine +x]+=i3d::GRAY16(50); } for (size_t i=0; i < dots_filo.size(); ++i) { const int x=(int)roundf( (dots_filo[i].x-xOff) *xRes); const int y=(int)roundf( (dots_filo[i].y-yOff) *yRes); const int z=(int)roundf( (dots_filo[i].z-zOff) *zRes); if ((x > 0) && (y > 0) && (z > 0) && (x < maxX) && (y < maxY) && (z < maxZ)) T[z*Slice +y*xLine +x]+=i3d::GRAY16(50); } } void ActiveMesh::PhaseII(const i3d::Image3d<i3d::GRAY16>& texture, i3d::Image3d<float>& intermediate) { i3d::GrayToFloat(texture,intermediate); //i3d::GaussIIR(intermediate,3.f,3.f,2.5f); i3d::GaussIIR(intermediate,2.f,2.f,2.f); #ifdef SAVE_INTERMEDIATE_IMAGES intermediate.SaveImage("4_texture_filtered.ics"); #endif } void ActiveMesh::PhaseIII(i3d::Image3d<float>& intermediate, i3d::Image3d<i3d::GRAY16>& texture) { #ifdef SAVE_INTERMEDIATE_IMAGES intermediate.SaveImage("5_texture_filtered_resampled.ics"); #endif float* p=intermediate.GetFirstVoxelAddr(); for (int z=0; z < (signed)intermediate.GetSizeZ(); ++z) for (int y=0; y < (signed)intermediate.GetSizeY(); ++y) for (int x=0; x < (signed)intermediate.GetSizeX(); ++x, ++p) { //background signal: float distSq=((float)x-110.f)*((float)x-110.f) + ((float)y-110.f)*((float)y-110.f); *p+=150.f*expf(-0.5f * distSq / 2500.f); //uncertainty in the number of incoming photons const float noiseMean = sqrtf(*p), // from statistics: shot noise = sqrt(signal) noiseVar = noiseMean; // for Poisson distribution E(X) = D(X) *p+=40.f*((float)GetRandomPoisson(noiseMean) - noiseVar); //constants are parameters of Andor iXon camera provided from vendor: //photon shot noise: dark current *p+=(float)GetRandomPoisson(0.06f); //read-out noise: // variance up to 25.f (old camera on ILBIT) // variance about 1.f (for the new camera on ILBIT) *p+=GetRandomGauss(480.f,20.f); //*p+=530.f; } #ifdef SAVE_INTERMEDIATE_IMAGES intermediate.SaveImage("6_texture_filtered_resampled_finalized.ics"); #endif //obtain final GRAY16 image i3d::FloatToGrayNoWeight(intermediate,texture); } void ActiveMesh::ConstructFF(FlowField<float> &FF) { //erase the flow field //iterate: // put displacement vectors // smooth //tests: TODO //FF must be consistent //oldVolPos and newVolPos must be of the same length //erase FF.x->GetVoxelData()=0; FF.y->GetVoxelData()=0; FF.z->GetVoxelData()=0; //time savers... const float xRes=FF.x->GetResolution().GetX(); const float yRes=FF.x->GetResolution().GetY(); const float zRes=FF.x->GetResolution().GetZ(); const float xOff=FF.x->GetOffset().x; const float yOff=FF.x->GetOffset().y; const float zOff=FF.x->GetOffset().z; //time-savers for boundary checking... const int maxX=(int)FF.x->GetSizeX()-1; const int maxY=(int)FF.x->GetSizeY()-1; const int maxZ=(int)FF.x->GetSizeZ()-1; //time-savers for accessing neigbors... const size_t xLine=FF.x->GetSizeX(); const size_t Slice=FF.x->GetSizeY() *xLine; float* const ffx=FF.x->GetFirstVoxelAddr(); float* const ffy=FF.y->GetFirstVoxelAddr(); float* const ffz=FF.z->GetFirstVoxelAddr(); //inject displacements for (size_t i=0; i < oldVolPos.size(); ++i) { const int x=(int)roundf( (oldVolPos[i].x-xOff) *xRes); const int y=(int)roundf( (oldVolPos[i].y-yOff) *yRes); const int z=(int)roundf( (oldVolPos[i].z-zOff) *zRes); const float dx=newVolPos[i].x - oldVolPos[i].x; const float dy=newVolPos[i].y - oldVolPos[i].y; const float dz=newVolPos[i].z - oldVolPos[i].z; if ((x > 0) && (y > 0) && (z > 0) && (x < maxX) && (y < maxY) && (z < maxZ)) { ffx[z*Slice +y*xLine +x]=dx; ffy[z*Slice +y*xLine +x]=dy; ffz[z*Slice +y*xLine +x]=dz; } } //smooth i3d::GaussIIR(*FF.x,15.0f); i3d::GaussIIR(*FF.y,15.0f); i3d::GaussIIR(*FF.z,15.0f); //multiply (to "correct" after normalized smoothing) FF.x->GetVoxelData()*=1240.f; FF.y->GetVoxelData()*=1240.f; FF.z->GetVoxelData()*=1240.f; } void ActiveMesh::ConstructFF_T(FlowField<float> &FF) { //erase the flow field //add displacement vectors by "rendering" displacement tetrahedra //smooth //tests: TODO //FF must be consistent //oldVolPos and newVolPos must be of the same length //length of oldVolPos*4 and length of VolID must be the same //erase FF.x->GetVoxelData()=0; FF.y->GetVoxelData()=0; FF.z->GetVoxelData()=0; i3d::Image3d<i3d::GRAY16> imgCounts; imgCounts.CopyMetaData(*FF.x); imgCounts.GetVoxelData()=0; //time savers... const float xRes=FF.x->GetResolution().GetX(); const float yRes=FF.x->GetResolution().GetY(); const float zRes=FF.x->GetResolution().GetZ(); const float xOff=FF.x->GetOffset().x; const float yOff=FF.x->GetOffset().y; const float zOff=FF.x->GetOffset().z; //time-savers for boundary checking... const int maxX=(int)FF.x->GetSizeX()-1; const int maxY=(int)FF.x->GetSizeY()-1; const int maxZ=(int)FF.x->GetSizeZ()-1; //time-savers for accessing neigbors... const size_t xLine=FF.x->GetSizeX(); const size_t Slice=FF.x->GetSizeY() *xLine; float* const ffx=FF.x->GetFirstVoxelAddr(); float* const ffy=FF.y->GetFirstVoxelAddr(); float* const ffz=FF.z->GetFirstVoxelAddr(); i3d::GRAY16* const ffC=imgCounts.GetFirstVoxelAddr(); //over all tetrahedra for (size_t i=0; i < VolID.size(); i+=4) { //tetrahedron to drive positions in the FF //(positions tetrahedron) const Vector3F& v1=oldVolPos[VolID[i+0]]; const Vector3F& v2=oldVolPos[VolID[i+1]]; const Vector3F& v3=oldVolPos[VolID[i+2]]; const Vector3F& v4=oldVolPos[VolID[i+3]]; //now iterate over the tetrahedra: for (float d=0.f; d <= 1.0f; d += 0.04f) for (float c=0.f; c <= (1.0f-d); c += 0.04f) for (float b=0.f; b <= (1.0f-d-c); b += 0.04f) { float a=1.0f -b -c -d; //float-point coordinate: Vector3F tmp; tmp =a*v1; tmp+=b*v2; tmp+=c*v3; tmp+=d*v4; //pixel (integer) coordinate: const int x=(int)roundf( (tmp.x-xOff) *xRes); const int y=(int)roundf( (tmp.y-yOff) *yRes); const int z=(int)roundf( (tmp.z-zOff) *zRes); if ((x > 0) && (y > 0) && (z > 0) && (x < maxX) && (y < maxY) && (z < maxZ)) { //tetrahedron to drive values to place at these positions //(values tetrahedron) const Vector3F dv1=newVolPos[VolID[i+0]] - v1; const Vector3F dv2=newVolPos[VolID[i+1]] - v2; const Vector3F dv3=newVolPos[VolID[i+2]] - v3; const Vector3F dv4=newVolPos[VolID[i+3]] - v4; //value tmp =a*dv1; tmp+=b*dv2; tmp+=c*dv3; tmp+=d*dv4; ffx[z*Slice +y*xLine +x]+=tmp.x; ffy[z*Slice +y*xLine +x]+=tmp.y; ffz[z*Slice +y*xLine +x]+=tmp.z; ffC[z*Slice +y*xLine +x]+=1; } } } //finish the averaging of FF for (size_t i=0; i < imgCounts.GetImageSize(); ++i) if (*(ffC+i)) { *(ffx+i)/=float(*(ffC+i)); *(ffy+i)/=float(*(ffC+i)); *(ffz+i)/=float(*(ffC+i)); } //imgCounts.SaveImage("counts.ics"); //TODO REMOVE //a bit more of fine-tunning: // make 2 rounds of 1px dilations into a copy image // extract the added 2px wide shell // widen the shell into another image with 2 rounds of 1px dilations // smooth sigma=1px this wider shell // mask/narrow the smoothed shell according to the previous shell // and combine with the original FF i3d::Image3d<i3d::GRAY16> tmp,shell,shellSmooth; i3d::Image3d<float> tmpF; tmpF=*FF.x; tmpF.GetVoxelData()*=1000.f; //tmpF.GetVoxelData()+=32768.f; tmpF.GetVoxelData()+=30000.f; i3d::FloatToGrayNoWeight(tmpF,shell); tmpF.SaveImage("shell0_F.ics"); shell.SaveImage("shell0.ics"); // make 2 rounds of 1px dilations into a copy image i3d::Dilation(shell,tmp,i3d::nb3D_o18); i3d::Dilation(tmp,shell,i3d::nb3D_o18); shell.SaveImage("shell1.ics"); // extract the added 2px wide shell i3d::Dilation(shell,tmp,i3d::nb3D_o18); i3d::Dilation(tmp,shell,i3d::nb3D_o18); shell.SaveImage("shell2.ics"); GrayToFloat(shell,tmpF); tmpF.SaveImage("shell2_F.ics"); tmpF.GetVoxelData()-=30000.f; tmpF.GetVoxelData()/=1000.f; tmpF.SaveImage("shell2_normalValues_F.ics"); // widen the shell into another image with 2 rounds of 1px dilations // smooth sigma=1px this wider shell // mask/narrow the smoothed shell according to the previous shell // and combine with the original FF }