-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
faces with zero volume in large models #21
Comments
Thanks for reporting this. What is the
mc = MC(vol, Int64)
march(mc) I also suspect a
I'm always interested in correctness ! Especially in this case were the same algorithm is used and should return the same number of points / faces. |
The type of mc is: typeof(mc) = MC{Float16, Int64} using CSV
using MarchingCubes
using Meshes
using SparseArrays
using FileIO
using StaticArrays
using Tables
using PlyIO
output(PlyIO::Module, m::MC, fn::AbstractString = "test.ply") = begin
ply = PlyIO.Ply()
push!(
ply,
PlyIO.PlyElement(
"vertex",
PlyIO.ArrayProperty("x", map(v -> Float32(v[1]), m.vertices)),
PlyIO.ArrayProperty("y", map(v -> Float32(v[2]), m.vertices)),
PlyIO.ArrayProperty("z", map(v -> Float32(v[3]), m.vertices)),
PlyIO.ArrayProperty("nx", map(n -> Float32(n[1]), m.normals)),
PlyIO.ArrayProperty("ny", map(n -> Float32(n[2]), m.normals)),
PlyIO.ArrayProperty("nz", map(n -> Float32(n[3]), m.normals)),
),
)
vertex_indices = PlyIO.ListProperty("vertex_indices", UInt8, Int32)
for i ∈ eachindex(m.triangles)
push!(vertex_indices, m.triangles[i] .- 1)
end
push!(ply, PlyIO.PlyElement("face", vertex_indices))
PlyIO.save_ply(ply, fn, ascii = true)
nothing
end
function read_microns(filename,dfmap)
dfmap::Matrix{Int64} .= CSV.File(filename) |> Tables.matrix
end
function fill_phitemp(phitemp,countslice,startframe,xval,yval,k_start,k_end,leni,lenj,dfmap,unique,dir_name,frame_name)
layers = countslice + startframe - 1
current_z = (countslice-1)*5
println(layers)
filename = string(dir_name,frame_name, layers, "_",xval,"_",yval,".csv")
read_microns(filename,dfmap)
for k in k_start:k_end
for j in 1:lenj
for i in 1:leni
phitemp[i, j,current_z+k ] = Float16(Int8(dfmap[i, j] == unique)*(-2)+1)
end
end
end
end
function make_ply(uniquelist,slices,startframe,xval,yval,dir_name,frame_name)
filename = string(dir_name, frame_name,startframe, "_",xval,"_",yval,".csv")
dfmap_temp = CSV.File(filename) |> Tables.matrix
leni, lenj = size(dfmap_temp)
@show leni, lenj
dfmap = Matrix{Int64}(undef,leni,lenj)
phitemp = zeros(Float16, leni, lenj,(slices-1)*5 )
current_z = 0
for unique in uniquelist
countslice = 1
k_start = 1
k_end = 3
fill_phitemp(phitemp,countslice,startframe,xval,yval,k_start,k_end,leni,lenj,dfmap,unique,dir_name,frame_name)
for countslice in 2:slices-1
k_start = -1
k_end = 3
fill_phitemp(phitemp,countslice,startframe,xval,yval,k_start,k_end,leni,lenj,dfmap,unique,dir_name,frame_name)
end
countslice = slices
k_start = -1
k_end = 0
fill_phitemp(phitemp,countslice,startframe,xval,yval,k_start,k_end,leni,lenj,dfmap,unique,dir_name,frame_name)
mc = MC(phitemp)
@show typeof(mc)
march(mc)
filename_out = string(dir_name, unique,"_",startframe, "_",xval,"_",yval,"_","16.ply")
output(PlyIO, mc, filename_out)
end
end
uniquelist = [864691136812081779]
slices = 400
startframe = 17241
xval = 170398
yval = 61241
dir_name = string(@__DIR__, "/../Neuron_transport_data/")
frame_name = "frame2_"
make_ply(uniquelist,slices,startframe,xval,yval,dir_name,frame_name) |
|
I've checked against the c++ code of http://thomas.lewiner.org/publication_page.php%EF%B9%96pubkey=marching_cubes_jgt.html for all simple cases (torus, hyperboloid, sphere, plane, ...): Here is the debug output, we have the same results in
|
I'm now testing with a resolution of
How was the code modified ? Could you try #22 ? |
Int32 is more than sufficient in my Ply case and should be okay in almost any circumstance, as a large model has tens of millions of vertices, not billions. The Int32 problem comes when the number of voxels is > 2e9, which is actually pretty easy to do. However, even your test with 1 billion voxels, you won't hit the Int32 limit. Does your test check for zero face volumes? The meshes look nearly identical to the LewinerMarchingCubes but with about 0.1% of faces with zero volume. My modifications were just to change some int to long and increase ALLOC_SIZE because I had > 2e9 voxels. You can diff the MarchingCubes.h and MarchingCubes1.2.c against the LewinerMarchingCubes to see the differences. Note that these files also have changes associated with reading data from CSV files. You can find the files at https://github.com/elbert5770/julia_educational_examples. |
I don't know if this is related but SpaceClaim finds no problems with cushin from LewinerMarchingCubes but finds 'Inconsistent triangles' with MarchingCubes.jl. All the other examples look fine in SpaceClaim. The models look the same visually. |
Silly me, I'll check with a bigger case.
No, I don't recall the c++ doing that either.
👍
I'll try to check the |
Diffing is difficult because you are using a It would help if we could reduce the bug to the |
Here's the c version I started from: https://github.com/neurolabusc/LewinerMarchingCubes. The diff is pretty straightforward, but don't look in the cpp folder, use the MarchingCubes.c and MarchingCubes.h. I assume their LUTs are identical to the original code. They have an interesting quote: "This is a simple NIfTI to mesh implementation using the AFNI marching cubes code. Ziad Saad ported the C++ algorithm of Thomas Lewiner to C. Ziad's original port used the lookup table dated 13/07/2002, whereas this repository updates this to the tables from 12/08/2002. This updated table provides consistent winding order for triangles." I used the default resolution for the test case. It would be nice if the cushin example isolated the bug. If you find it, I can quickly test against the 30 GB case. |
@elbert5770 and @t-bltg the rationale for my repository was to fix winding errors in AFNI, as described here. I would suggest you visualize results on a tool that uses winding order to distinguish front and back faces (e.g. turn off Back Face Lighting). For example Surfice or the web based NiiVue. I do also have a WASM port that allows you to validate your results with an online web demo. I note that Julia already has the QuadricMeshSimplification that I ported from Sven Forstmann's incredibly efficient implementation. I would suggest that the README page for the MarchingCubes also provides an example that applies simplification following meshification, as huge volumes can create immensely complicated meshes. |
Thanks @neurolabusc for the clarification and the pointers here. What I'm failed to understand is that we already use the latest table from 12/08/2002.
Good point, I've opened a dedicated issue about this (#23).
|
This is a nice reproducer to play with ( using MarchingCubes
using PlyIO
using NIfTI
main() = begin
ni = niread("bet.nii")
@show size(ni)
mc = MC(Float32.(ni.raw))
println("lewiner")
march(mc, 128)
MarchingCubes.output(PlyIO, mc)
println("legacy")
march_legacy(mc, 128)
MarchingCubes.output(PlyIO, mc)
return
end
main() And the corresponding output from afni/afni#342:
|
@elbert5770 I think I've found the bug, despite being careful in porting the algorithm (see #24). Here is the new output, matching the c code:
Could you check the Unfortunately, it does seem to impact the tests, and I don't want to add a binary file ( |
Working on it now |
Can I suggest for your test cases you generate the voxel-based images using a simple formula. This will provide a robust test without requiring much disk space for the code. The most popular shape for this would be a static scene with some 3D metaballs. The figure from the Lewiner paper includes interlocking tori to show the edge cases where the classic marching cubes does poorly. For my own software (e.g. MRIcroGL) I use the borg surface. I am not familiar with Julia, but here is a minimal Matlab script for borg voxels. The formula creates voxels with continuous intensities, and you choose a threshold brightness to define the mesh isosurface. |
It's exactly the same errors in SpaceClaim and 8514 faces with zero volume with the following packages:
|
Hi @neurolabusc, I've just added more examples from the MarchingCubes code.
Do you mean evaluate an implicit function to generate data on the fly ? I think that this is already what we do in the tests: MarchingCubes.jl/test/runtests.jl Lines 8 to 105 in 5d1b469
MarchingCubes.jl/src/example.jl Line 48 in 5d1b469
Unfortunately, they do not seem to hit the bogus line as the surface in How was this file generated ? |
Damn, there might be more than one bug then 😕. |
@t-bltg I wonder if you can run the marching cubes on your existing tests but adjust the isosurface threshold until you find a level that fails. The |
I would actually recommend looking at OffsetArrays.jl https://github.com/JuliaArrays/OffsetArrays.jl |
I went that road, also tried to use regular n-dimensional julia arrays, but I remember performance issues wrt compiled language such as c++. |
@elbert5770 would you be able to reduce the size of the 30Gb file for a reproducer, or be able to share it somehow, so that I can try to narrow the issue as I did with the |
Do you still have that code? Because there are potentially other reasons for the slowdown. Perhaps we could start a repository to play around in. |
I think the first version in the git history of It shouldn't be too difficult to adapt to OffsetArrays. |
@elbert5770, I've added a check script in #26 to enforce checks for 0/1 based indexing errors of the LUTs. Some LUTs were changed for consistency, but it shouldn't change the MarchingCubes algorithm output. Can you check the 30Gb case against latest |
I'm closing this, since there haven't been any answer over the last weeks. |
The following is a mesh analysis with SpaceClaim of a mesh generated in C with the LewinerMarchingCubes from NeurolabUSC, modified to accept Int64 and thus large numbers of voxels and arrays instead of NIIFTI:
The listed 'errors' are not actually errors.
However, with MarchingCubes.jl, there are 8514 faces with zero volume, out of 5,421,474 faces:

I suspect that the 2000 x 2000 x 1995 voxels are hitting an Int32 limit somewhere, or there is a slight error in the LUT's due to the 1-index conversion. I think the latter is more plausible. The source data is 30 GB, so we'd need to arrange some way to transfer this if you are interested in trying to reproduce the result.
The text was updated successfully, but these errors were encountered: