# Compute normal vectors from planar contours

**URL:** <https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238>\
**Category:** Development\
**Created:** [April 22, 2020, 6:11am UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238 "2020-04-22T06:11:15Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [April 22, 2020, 6:11am UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/1 "2020-04-22T06:11:15Z")

</div>

Hi,

Is it possible to compute normal vectors for each 3D point of planar contour in SlicerRT? If yes , how we can do it.

thank’s in advance

---

<div class="post-metadata">

**Author:** ![gcsharp](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/gcsharp/32/863_2.png) [@gcsharp](https://discourse.slicer.org/u/gcsharp)\
**Post date:** [April 22, 2020, 10:59pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/2 "2020-04-22T22:59:47Z")

</div>

Computing surface normals is not a SlicerRT function. I am not an expert, but I think converting to model representation would be the first step. In what format would you want the normal vectors and why do you think you need them?

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [April 23, 2020, 3:59pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/3 "2020-04-23T15:59:05Z")

</div>

I want implement an implicit method to construct a 3D surface from a set of planar contours. So I need to compute a normal vector for each 3D point of the contour to be able to evalute the implicit function. More precisely, i want convert planar contours to a 3D surface using the converter rule planarcontoursto3d surface. there is an interpolation method in slicerRT but the compute of the 3d surface via this method does not need the knowledge of normal of each 3D point.

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 24, 2020, 4:13am UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/4 "2020-04-24T04:13:21Z")

</div>

> [@loubna](#):
>
> I need to compute a normal vector for each 3D point of the contour to be able to evalute the implicit function

Computing the normal vector correctly is hard (comparable to the difficulty of reconstructing a surface). SlicerRT can compute it after it has already reconstructed the closed surface.

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [April 24, 2020, 10:42pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/5 "2020-04-24T22:42:17Z")

</div>

Hi professor;

thank’s for the response. I just want to know if I can access to the points of each contour separately?. I want implement a flood fill algorithm in order to be able to compute an approximate normal vector.

Thank’s in advance

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 24, 2020, 10:45pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/6 "2020-04-24T22:45:32Z")

</div>

You can find the closest point on the reconstructed surface using a [VTK locator](https://vtk.org/doc/nightly/html/classvtkPointLocator.html#a7ab83a64b5035755c440922a8db6a1dc) and get the surface normal from there.

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [April 24, 2020, 10:53pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/7 "2020-04-24T22:53:07Z")

</div>

but i need to knwo the normal vector before reconstructing the 3D surface . I have implemented the implicot method in 3D slicer but the results were not very satisfactory. So i want reimplement it in SlicerRT because the planar contours is the master representation.

I thought modify thie existing method [https://github.com/SlicerRt/SlicerRT/blob/master/DicomRtImportExport/ConversionRules/vtkPlanarContourToClosedSurfaceConversionRule.h](https://github.com/SlicerRt/SlicerRT/blob/master/DicomRtImportExport/ConversionRules/vtkPlanarContourToClosedSurfaceConversionRule.h)

but there are just vtkPolyData\* inputROIPoints which contains all points of all contours. Is there any way to extract the points of each contours from inputROIPoints?

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 24, 2020, 11:04pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/8 "2020-04-24T23:04:17Z")

</div>

You can let SlicerRT reconstruct the surface and provide you the normals. If your reconstructed surface is significantly better than what SlicerRT generates then you can start thinking about how to get the normals (if it’s OK to use SlicerRT as a preprocessor or you want to use an alternative normal estimation method).

---

<div class="post-metadata">

**Author:** ![Chris\_Rorden](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/chris_rorden/32/4073_2.png) [@Chris\_Rorden](https://discourse.slicer.org/u/Chris_Rorden)\
**Post date:** [April 25, 2020, 12:21pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/9 "2020-04-25T12:21:31Z")

</div>

If you want to compute the surface normal for a triangulated mesh, you simply compute the cross-product for two sides. The winding-order of the triangles is crucial to disambiguate the front and back face of the triangle.

If you can to compute the surface normal for a voxelwise 3D image (using the intensity of each voxel), you can compute the [gradient](https://www.mccauslandcenter.sc.edu/mricrogl/gradients) using a Sobel operator. You can do this using the CPU, but tor 3D textures a GPU using GLSL is tremendously faster. CPU and GLSL code is provided with [MRIcroGL](https://github.com/rordenlab/MRIcroGL12). MRIcroGL does this once for the entire 3D volume to help estimate lighting (specular, diffuse, MatCaps).

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [April 25, 2020, 2:54pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/10 "2020-04-25T14:54:40Z")

</div>

In vtkPlanarContourToClosedSurfaceConversionRule, all contours are set to be counter-clockwise ? I don’t understand what is the difference between counter-clockwise and clockwise contours.

Do you mean by this the existence of small contours inside the large contour?

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 25, 2020, 3:12pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/11 "2020-04-25T15:12:29Z")

</div>

In computer graphics, direction of a polygon (which way direction is towards inside/outside) is usually defined by its winding.

In planar contours in DICOM RTSTRUCT information objects, winding is not used. Instead, concavities (which may appear as holes in planar contours) and holes are specified using keyhole technique. See details in the DICOM standard.

---

<div class="post-metadata">

**Author:** ![cpinter](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/cpinter/32/7995_2.png) [@cpinter](https://discourse.slicer.org/u/cpinter)\
**Post date:** [April 27, 2020, 8:52am UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/13 "2020-04-27T08:52:37Z")

</div>

SlicerRT building instructions can be found here: [https://github.com/SlicerRt/SlicerRT/wiki/SlicerRt-developers-page](https://github.com/SlicerRt/SlicerRT/wiki/SlicerRt-developers-page)

If you mention any errors, it is very useful to actually send those errors so that we can give a meaningful answer.

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 27, 2020, 8:56pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/17 "2020-04-27T20:56:47Z")

</div>

If you build your own Sicer, then you also need to build all extensions you need. It is very simple to do that, just clone the repository on your computer, configure with CMake (set you Slicer-build folder as Slicer\_DIR), and build.

---

<div class="post-metadata">

**Author:** ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)\
**Post date:** [April 27, 2020, 9:01pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/19 "2020-04-27T21:01:24Z")

</div>

In Visual Studio, you need to choose the same build configuration (Debug/Release) that you chose when you built Slicer.

---

<div class="post-metadata">

**Author:** ![gcsharp](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/gcsharp/32/863_2.png) [@gcsharp](https://discourse.slicer.org/u/gcsharp)\
**Post date:** [April 29, 2020, 3:18am UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/24 "2020-04-29T03:18:50Z")

</div>

The repository you cloned is a fork of the official repository. It was forked May 2018, and therefore it is two years out of date.

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [May 12, 2020, 3:58pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/26 "2020-05-12T15:58:04Z")

</div>

Hi Mr;

thank’s forthe reply. I search in MRIcroGL code but i Don’t found how the normal vectors are computed from binary volume using filters on CPU. Can you give me more précisions please.

thank’s in advance

---

<div class="post-metadata">

**Author:** ![Chris\_Rorden](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/chris_rorden/32/4073_2.png) [@Chris\_Rorden](https://discourse.slicer.org/u/Chris_Rorden)\
**Post date:** [May 12, 2020, 5:33pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/27 "2020-05-12T17:33:39Z")

</div>

The function [sobel()](https://github.com/rordenlab/MRIcroGL12/blob/d151da089dacd7bd4b4f4e78e0ebe835de585644/nifti.pas) estimates the [Sobel operator](https://en.wikipedia.org/wiki/Sobel_operator).

This is easiest to describe in 2D, where each pixel has 8 neighbors (in 3D each voxel has 26 neighbors that share a face, edge or corner). Since the principle is the same, I will describe for 2D. Consider a 2D image where each pixel has 8 neighbors that are Left/Right(L/R), Up/Down(U/D) from the center: LU, U, RU, L, R, LD, D, RD.

The left-right gradient `x` is  
`x = (LU+2*L+LD) - (RU+2*R+RD)`  
The up-down gradient `y` is  
`y = (LU+2*U+RU) - (LD+2*D+RD)`

Extending to 3 dimensions is described [here](https://en.wikipedia.org/wiki/Sobel_operator#Extension_to_other_dimensions).

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [May 12, 2020, 5:53pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/28 "2020-05-12T17:53:36Z")

</div>

thank’s again for reply. what about “vtkImageSobel3D”?

I have applied gaussian filter to blur the binary volume folowed by vtkImageSobel3D but i have artifacts in some régions of model. what is the difference by the sobel operator illustrated in your exemple the the vtkSobelImage filter.? the both give same results?

---

<div class="post-metadata">

**Author:** ![Chris\_Rorden](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/chris_rorden/32/4073_2.png) [@Chris\_Rorden](https://discourse.slicer.org/u/Chris_Rorden)\
**Post date:** [May 14, 2020, 3:28pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/29 "2020-05-14T15:28:20Z")

</div>

You can see the code for [vtkImageSobel3D.cxx](https://github.com/Kitware/VTK/blob/master/Imaging/General/vtkImageSobel3D.cxx) here. It is conceptually identical to the [wiki Sobel 3D](https://en.wikipedia.org/wiki/Sobel_operator#Extension_to_other_dimensions) formula. The wiki uses weights 1,2,4 for voxels that share a corner, edge or face while VTK uses the weights 1, 1.71, 3.41. These should provide very similar results.

In general, your approach of applying a Gaussian blur prior to a Sobel is a wise choice. I am not sure what artifacts you are experiencing. Working with binary data might be part of the issue. If you look at my application, I apply these to continuous data (e.g. different voxels in CT and MRI have slightly different intensities). Specifically, I usually apply it to the alpha channel (transparency) of the image I am working with. Below is an example of a CT scan with a window center of 188 and a window width of 288 (in Hounsfield units). The horizontal axis in the graph shows the selected intensity range (from 114…302) and the vertical axis shows the alpha channel, which in this case is a linear ramp. Thus, values darker than 114 are completely transparent and values greater than 302 are completely opaque. Intermediate values are translucent. Using this approach helps with partial volume issues (e.g. a voxel that is only partially bone). The gradient is shown on the right. So my sense is you want to think about a segmentation that is continuous rather than binary to capture partial volume effects.

 ![gradient](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/b/9/b9e50b7415555e409b2138a7dbe76b7e4656dfff.jpeg)

---

<div class="post-metadata">

**Author:** ![loubna](https://avatars.discourse-cdn.com/v4/letter/l/cab0a1/32.png) [@loubna](https://discourse.slicer.org/u/loubna)\
**Post date:** [May 16, 2020, 9:47pm UTC](https://discourse.slicer.org/t/compute-normal-vectors-from-planar-contours/11238/30 "2020-05-16T21:47:36Z")

</div>

Thank you so much for clarification
