private static Volume3D <byte> ToVolume3D( this ContoursPerSlice contours, double spacingX, double spacingY, double spacingZ, Point3D origin, Matrix3 direction, Region3D <int> roi) { ContoursPerSlice subContours = new ContoursPerSlice( contours.Where(x => x.Value != null).Select( contour => new KeyValuePair <int, IReadOnlyList <ContourPolygon> >( contour.Key - roi.MinimumZ, contour.Value.Select(x => new ContourPolygon( x.ContourPoints.Select( point => new PointF(point.X - roi.MinimumX, point.Y - roi.MinimumY)).ToArray(), 0)) .ToList())).ToDictionary(x => x.Key, y => y.Value)); var result = new Volume3D <byte>(roi.MaximumX - roi.MinimumX + 1, roi.MaximumY - roi.MinimumY + 1, roi.MaximumZ - roi.MinimumZ + 1, spacingX, spacingY, spacingZ, origin, direction); result.Fill(subContours, ModelConstants.MaskForegroundIntensity); return(result); }
public MarchingCubesResults Generate(Region3D region, double step, double isoLevel) { var grid = this.BuildGrid(region, step); var results = Generate(grid, isoLevel); return(results); }
private static ContoursPerSlice GenerateContoursPerSlice( Volume3D <byte> volume, bool fillContours, byte foregroundId, SliceType sliceType, bool filterEmptyContours, Region3D <int> regionOfInterest, ContourSmoothingType axialSmoothingType) { var region = regionOfInterest ?? new Region3D <int>(0, 0, 0, volume.DimX - 1, volume.DimY - 1, volume.DimZ - 1); int startPoint; int endPoint; // Only smooth the output on the axial slices var smoothingType = axialSmoothingType; switch (sliceType) { case SliceType.Axial: startPoint = region.MinimumZ; endPoint = region.MaximumZ; break; case SliceType.Coronal: startPoint = region.MinimumY; endPoint = region.MaximumY; smoothingType = ContourSmoothingType.None; break; case SliceType.Sagittal: startPoint = region.MinimumX; endPoint = region.MaximumX; smoothingType = ContourSmoothingType.None; break; default: throw new ArgumentOutOfRangeException(nameof(sliceType), sliceType, null); } var numberOfSlices = endPoint - startPoint + 1; var arrayOfContours = new Tuple <int, IReadOnlyList <ContourPolygon> > [numberOfSlices]; for (var i = 0; i < arrayOfContours.Length; i++) { var z = startPoint + i; var volume2D = ExtractSlice.Slice(volume, sliceType, z); var contours = fillContours ? ContoursFilled(volume2D, foregroundId, smoothingType): ContoursWithHoles(volume2D, foregroundId, smoothingType); arrayOfContours[i] = Tuple.Create(z, contours); } return(new ContoursPerSlice( arrayOfContours .Where(x => !filterEmptyContours || x.Item2.Count > 0) .ToDictionary(x => x.Item1, x => x.Item2))); }
public List <GridCube> Сheck(double from, double to, double step) { var edgesPerSide = Region3D.GetLenght(from, to) / step; var countOfCubes = Math.Pow(edgesPerSide, 3); var vertexPerSide = edgesPerSide + 1; var uniqueEdgesPerSide = edgesPerSide * vertexPerSide; uniqueEdgesPerSide += uniqueEdgesPerSide; var edjesCount = vertexPerSide * uniqueEdgesPerSide + (vertexPerSide * vertexPerSide * edgesPerSide); var marchingCubes = new MarchingCubesAlgorithm(null); var region = new CommonTypes.Region3D(from, to, from, to, from, to); var cubes = marchingCubes.BuildGrid(region, step); Assert.AreEqual(cubes.Count, countOfCubes); var uniqueVertexes = new List <Point>(); // 18 unique edges var lines = new List <GridLine>(); foreach (var cube in cubes) { foreach (var edge in cube.Edges) { if (!lines.Contains(edge)) { lines.Add(edge); } } foreach (var vert in cube.Vertex) { // Check that objects are not created. Exclude overriden values. //if (!uniqueVertexes.Any(p => object.ReferenceEquals(uniqueVertexes, vert))) if (!uniqueVertexes.Contains(vert)) { uniqueVertexes.Add(vert); } } } var totalVertexes = vertexPerSide * vertexPerSide * vertexPerSide; Assert.AreEqual(uniqueVertexes.Count, totalVertexes); Assert.AreEqual(lines.Count, edjesCount); // Check last cube vertex var firstCube = cubes.FirstOrDefault(); CheckVertexes(firstCube, from, from + step); var lastCube = cubes.LastOrDefault(); CheckVertexes(lastCube, to - step, to); return(cubes); }
/// <summary> /// Extracts contours from all slices of the given volume, searching for the given foreground value. /// Contour extraction will take holes into account. /// </summary> /// <param name="volume"></param> /// <param name="foregroundId">The voxel value that should be used in the contour search as foreground.</param> /// <param name="axialSmoothingType">The smoothing that should be applied when going from a point polygon to /// contours. This will only affect axial slice, for other slice types no smoothing will be applied. /// <param name="sliceType">The type of slice that should be used for contour extraction.</param> /// <param name="filterEmptyContours">If true, contours with no points are not extracted.</param> /// <param name="regionOfInterest"></param> /// <returns></returns> public static ContoursPerSlice ContoursWithHolesPerSlice( this Volume3D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, SliceType sliceType = SliceType.Axial, bool filterEmptyContours = true, Region3D <int> regionOfInterest = null, ContourSmoothingType axialSmoothingType = ContourSmoothingType.Small) => ExtractContours.ContoursWithHolesPerSlice(volume, foregroundId, sliceType, filterEmptyContours, regionOfInterest, axialSmoothingType);
/// <summary> /// Extracts contours from all slices of the given volume, searching for the given foreground value. /// Contour extraction will take holes into account. /// </summary> /// <param name="volume"></param> /// <param name="foregroundId">The voxel value that should be used in the contour search as foreground.</param> /// <param name="axialSmoothingType">The smoothing that should be applied when going from a point polygon to /// contours. This will only affect axial slice, for other slice types no smoothing will be applied. /// <param name="sliceType">The type of slice that should be used for contour extraction.</param> /// <param name="filterEmptyContours">If true, contours with no points are not extracted.</param> /// <param name="regionOfInterest"></param> /// <returns></returns> public static ContoursPerSlice ContoursFilledPerSlice( Volume3D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, SliceType sliceType = SliceType.Axial, bool filterEmptyContours = true, Region3D <int> regionOfInterest = null, ContourSmoothingType axialSmoothingType = ContourSmoothingType.Small) { return(GenerateContoursPerSlice(volume, true, foregroundId, sliceType, filterEmptyContours, regionOfInterest, axialSmoothingType)); }
public static IRegion2D ConvertTo2D(IRegion3D region3D, IMatrix44 lcs) { var regionCopy = new Region3D(region3D); GeomOperation.TransformToLCS(lcs, regionCopy); var outline2D = ConvertTo2D(regionCopy.Outline); var region2D = new Region2D(outline2D); if (regionCopy.OpeningsCount > 0) { foreach (var opening3D in regionCopy.Openings) { region2D.Openings.Add(ConvertTo2D(opening3D)); } } return(region2D); }
/// <summary> /// Gets whether the present object is inside of the given <paramref name="outer"/> /// region. If the present object is at the boundaries along any of the edges, /// this is still considered inside. /// </summary> /// <param name="region"></param> /// <param name="outer"></param> /// <returns></returns> public static bool InsideOf(this Region3D <int> region, Region3D <int> outer) { if (region.IsEmpty()) { throw new ArgumentException("This operation can only be computed on non-empty regions.", nameof(region)); } if (outer.IsEmpty()) { throw new ArgumentException("This operation can only be computed on non-empty regions.", nameof(outer)); } return(region.MinimumX >= outer.MinimumX && region.MaximumX <= outer.MaximumX && region.MinimumY >= outer.MinimumY && region.MaximumY <= outer.MaximumY && region.MinimumZ >= outer.MinimumZ && region.MaximumZ <= outer.MaximumZ); }
public static Region3D <int> Dilate <T>(this Region3D <int> region, Volume3D <T> volume, double mmDilationX, double mmDilationY, double mmDilationZ) { if (region.IsEmpty()) { return(region.Clone()); } var dilatedMinimumX = region.MinimumX - (int)Math.Ceiling(mmDilationX / volume.SpacingX); var dilatedMaximumX = region.MaximumX + (int)Math.Ceiling(mmDilationX / volume.SpacingX); var dilatedMinimumY = region.MinimumY - (int)Math.Ceiling(mmDilationY / volume.SpacingY); var dilatedMaximumY = region.MaximumY + (int)Math.Ceiling(mmDilationY / volume.SpacingY); var dilatedMinimumZ = region.MinimumZ - (int)Math.Ceiling(mmDilationZ / volume.SpacingZ); var dilatedMaximumZ = region.MaximumZ + (int)Math.Ceiling(mmDilationZ / volume.SpacingZ); return(new Region3D <int>( dilatedMinimumX < 0 ? 0 : dilatedMinimumX, dilatedMinimumY < 0 ? 0 : dilatedMinimumY, dilatedMinimumZ < 0 ? 0 : dilatedMinimumZ, dilatedMaximumX >= volume.DimX ? volume.DimX - 1 : dilatedMaximumX, dilatedMaximumY >= volume.DimY ? volume.DimY - 1 : dilatedMaximumY, dilatedMaximumZ >= volume.DimZ ? volume.DimZ - 1 : dilatedMaximumZ)); }
/// <summary> /// Create a grid to build a model based on. /// </summary> /// <returns>List of the created cubes.</returns> public List <GridCube> BuildGrid(Region3D region, double step) { if (step <= 0) { throw new ArgumentException(nameof(step) + " cannot be <=0"); } var toReturn = new List <GridCube>(); var totalX = 0; var totalZ = 0; var column = 0; var prevColumn = 0; for (var y = region.MinY; y < region.MaxY;) { var isLastColumn = (y + step) > region.MaxY; var isFirstY = column == 0; var nextYValue = isLastColumn ? region.MaxY : y + step; var depth = 0; var prevDepth = 0; var zIndex = 0; for (var z = region.MinZ; z < region.MaxZ;) { var isLastDepth = (z + step) > region.MaxZ; var isFirstZ = depth == 0; var nextZValue = isLastDepth ? region.MaxZ : z + step; zIndex++; var xIndex = 0; var prevX = region.MinX; for (var x = region.MinX; x < region.MaxX;) { var isLastX = (x + step) > region.MaxX; var isFirstX = xIndex == 0; var nextXValue = isLastX ? region.MaxX : x + step; if (!isLastColumn && !isLastX && !isLastDepth || (isFirstX && isLastX)) { GridCube prevXCube = null; if (!isFirstX) { prevXCube = toReturn[toReturn.Count - 1]; } GridCube prevYCube = null; if (!isFirstY) { var index = toReturn.Count - totalZ * totalX; prevYCube = toReturn[index]; } GridCube prevZCube = null; if (!isFirstZ) { var index = toReturn.Count - totalX; prevZCube = toReturn[index]; } var newCube = new GridCube(); // Dont create object duplicates. Cubes edges are merged. var edges = new List <GridLine>(); //0 var edge = GetMergeEdge(prevYCube, 4, prevZCube, 2); edge = edge == null ? new GridLine(new Point(x, y, z) /*0*/, new Point(nextXValue, y, z) /*1*/, AxissConsts.X) : edge; edges.Add(edge); newCube.Vertex[0] = edge.Point1; newCube.Vertex[1] = edge.Point2; //1 edge = GetMergeEdge(prevYCube, 5);//, prevXCube, 3); edge = edge ?? new GridLine(newCube.Vertex[1] /*1*/, new Point(nextXValue, y, nextZValue) /*2*/, AxissConsts.Z); edges.Add(edge); newCube.Vertex[2] = edge.Point2; //2 edge = GetMergeEdge(prevYCube, 6);//, prevZCube, 0); edge = edge ?? new GridLine(new Point(x, y, nextZValue) /*3*/, newCube.Vertex[2] /*2*/, AxissConsts.X); edges.Add(edge); newCube.Vertex[3] = edge.Point1; //3 edge = GetMergeEdge(prevXCube, 1, prevYCube, 7); edge = edge ?? new GridLine(newCube.Vertex[3] /*3*/, newCube.Vertex[0] /*0*/, AxissConsts.Z); edges.Add(edge); //4 edge = GetMergeEdge(prevZCube, 6); edge = edge ?? new GridLine(new Point(x, nextYValue, z) /*4*/, new Point(nextXValue, nextYValue, z) /*5*/, AxissConsts.X); edges.Add(edge); newCube.Vertex[4] = edge.Point1; newCube.Vertex[5] = edge.Point2; //5 edge = new GridLine(newCube.Vertex[5] /*5*/, new Point(nextXValue, nextYValue, nextZValue) /*6*/, AxissConsts.Z); edges.Add(edge); newCube.Vertex[6] = edge.Point2; //6 edge = new GridLine(new Point(x, nextYValue, nextZValue) /*7*/, newCube.Vertex[6] /*6*/, AxissConsts.X); edges.Add(edge); newCube.Vertex[7] = edge.Point1; //7 edge = GetMergeEdge(prevXCube, 5); edge = edge ?? new GridLine(newCube.Vertex[4] /*4*/, newCube.Vertex[7] /*7*/, AxissConsts.Y); edges.Add(edge); //8 edge = GetMergeEdge(prevXCube, 9, prevZCube, 11); edge = edge == null ? new GridLine(newCube.Vertex[0] /*0*/, newCube.Vertex[4] /*4*/, AxissConsts.Y) : edge; edges.Add(edge); //9 edge = GetMergeEdge(prevZCube, 10); edge = edge ?? new GridLine(newCube.Vertex[1] /*1*/, newCube.Vertex[5] /*5*/, AxissConsts.Y); edges.Add(edge); //10 edge = new GridLine(newCube.Vertex[2] /*2*/, newCube.Vertex[6] /*6*/, AxissConsts.Y); edges.Add(edge); //11 edge = GetMergeEdge(prevXCube, 10, null, 0); edge = edge ?? new GridLine(newCube.Vertex[3] /*3*/, newCube.Vertex[7] /*7*/, AxissConsts.Y); edges.Add(edge); newCube.Edges = edges.ToArray(); toReturn.Add(newCube); } prevX = x; xIndex++; x = nextXValue; } totalX = xIndex; prevDepth = depth; depth++; z = nextZValue; } totalZ = zIndex; prevColumn = column; column++; y = nextYValue; } return(toReturn); }
/// <summary> /// Gets whether the region contains zero voxels. That is the case if, for any of the /// dimensions X, Y, Z, the minimum is larger than the maximum. /// </summary> /// <param name="region"></param> /// <returns></returns> public static bool IsEmpty(this Region3D <int> region) { return(region.MinimumX > region.MaximumX || region.MinimumY > region.MaximumY || region.MinimumZ > region.MaximumZ); }
/// <summary> /// Gets the length of the region along the Z dimensions. The length /// of the region is the number of points (integers) that are included /// in the region. If the region has Minimum of 4, and Maximum of 5, /// the length is 2 (Minimum and Maximum are inclusive). /// </summary> /// <param name="region3D"></param> /// <returns></returns> public static int LengthZ(this Region3D <int> region3D) { var length = region3D.MaximumZ - region3D.MinimumZ + 1; return(length < 0 ? 0 : length); }
/// <summary> /// Gets the number of points in the region, that is the product of the /// region length across the three dimensions. /// </summary> /// <param name="region"></param> /// <returns></returns> public static int Size(this Region3D <int> region) { return(region.LengthX() * region.LengthY() * region.LengthZ()); }
/// <summary> /// Gets whether a point with the given (x, y, z) coordinates is inside the region. /// </summary> /// <param name="region"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="z"></param> /// <returns></returns> public static bool ContainsPoint(this Region3D <int> region, int x, int y, int z) { return(x >= region.MinimumX && x <= region.MaximumX && y >= region.MinimumY && y <= region.MaximumY && z >= region.MinimumZ && z <= region.MaximumZ); }
/// <summary> /// Creates a volume that has the same spacing and coordinate system as the reference volume, /// and fills all points that fall inside of the contours in the present object with the /// default foreground value. The returned volume has its size determined by the given region of interest. /// Contour points are transformed using the region of interest. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="contours">The contours to use for filling.</param> /// <param name="refVolume3D">The reference volume to copy spacing and coordinate system from.</param> /// <param name="regionOfInterest"></param> /// <returns></returns> public static Volume3D <byte> ToVolume3D <T>(this ContoursPerSlice contours, Volume3D <T> refVolume3D, Region3D <int> regionOfInterest) { return(contours.ToVolume3D( refVolume3D.SpacingX, refVolume3D.SpacingY, refVolume3D.SpacingZ, refVolume3D.Origin, refVolume3D.Direction, regionOfInterest)); }