From 013297047f4bb0733926b348b173a31b193fe444 Mon Sep 17 00:00:00 2001 From: Ghazi Bouabene Date: Fri, 22 Feb 2019 10:41:42 +0100 Subject: [PATCH] Vertex color merge (#31) * implementation of colored mesh, depending on scalismo vertexColor * added ply (vertex color) reading to the ui * added .ply in triangle mesh read * some refactoring of names --- .../scala/scalismo/ui/api/ShowInScene.scala | 19 +++-- src/main/scala/scalismo/ui/api/Views.scala | 58 +++++++++++++- .../scala/scalismo/ui/model/GroupNode.scala | 15 +++- .../ui/model/VertexColorMeshNode.scala | 79 +++++++++++++++++++ .../scala/scalismo/ui/rendering/Caches.scala | 9 ++- .../ui/rendering/actor/ActorsFactory.scala | 14 +++- .../ui/rendering/actor/MeshActor.scala | 66 +++++++++++++++- .../scalismo/ui/util/FileIoMetadata.scala | 8 +- 8 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 src/main/scala/scalismo/ui/model/VertexColorMeshNode.scala diff --git a/src/main/scala/scalismo/ui/api/ShowInScene.scala b/src/main/scala/scalismo/ui/api/ShowInScene.scala index bd78563a..c4060918 100644 --- a/src/main/scala/scalismo/ui/api/ShowInScene.scala +++ b/src/main/scala/scalismo/ui/api/ShowInScene.scala @@ -20,7 +20,7 @@ package scalismo.ui.api import scalismo.common._ import scalismo.geometry.{ Landmark, Point, EuclideanVector, _3D } import scalismo.image.DiscreteScalarImage -import scalismo.mesh.{ LineMesh, ScalarMeshField, TriangleMesh } +import scalismo.mesh.{ LineMesh, ScalarMeshField, TriangleMesh, VertexColorMesh3D } import scalismo.registration.{ RigidTransformation, RigidTransformationSpace } import scalismo.statisticalmodel.{ DiscreteLowRankGaussianProcess, LowRankGaussianProcess, StatisticalMeshModel } import scalismo.ui.model._ @@ -60,10 +60,6 @@ trait LowPriorityImplicits { } } -} - -object ShowInScene extends LowPriorityImplicits { - implicit object ShowInSceneMesh extends ShowInScene[TriangleMesh[_3D]] { override type View = TriangleMeshView @@ -74,6 +70,19 @@ object ShowInScene extends LowPriorityImplicits { } +} + +object ShowInScene extends LowPriorityImplicits { + + implicit def ShowVertexColorMesh = new ShowInScene[VertexColorMesh3D] { + override type View = VertexColorMeshView + + override def showInScene(mesh: VertexColorMesh3D, name: String, group: Group): VertexColorMeshView = { + + VertexColorMeshView(group.peer.colorMeshes.add(mesh, name)) + } + } + implicit object ShowInSceneLineMesh extends ShowInScene[LineMesh[_3D]] { override type View = LineMeshView diff --git a/src/main/scala/scalismo/ui/api/Views.scala b/src/main/scala/scalismo/ui/api/Views.scala index 941dd0cd..e3500caf 100644 --- a/src/main/scala/scalismo/ui/api/Views.scala +++ b/src/main/scala/scalismo/ui/api/Views.scala @@ -23,7 +23,7 @@ import breeze.linalg.DenseVector import scalismo.common.{DiscreteDomain, DiscreteField, DiscreteScalarField} import scalismo.geometry.{EuclideanVector, Landmark, Point, _3D} import scalismo.image.DiscreteScalarImage -import scalismo.mesh.{LineMesh, ScalarMeshField, TriangleMesh} +import scalismo.mesh.{LineMesh, ScalarMeshField, TriangleMesh, VertexColorMesh3D} import scalismo.registration.RigidTransformation import scalismo.statisticalmodel.{DiscreteLowRankGaussianProcess, StatisticalMeshModel} import scalismo.ui.control.NodeVisibility @@ -192,6 +192,62 @@ object TriangleMeshView { } } +case class VertexColorMeshView private[ui](override protected[api] val peer: VertexColorMeshNode) extends ObjectView { + + type PeerType = VertexColorMeshNode + + def opacity = peer.opacity.value + + def opacity_=(o: Double): Unit = { + peer.opacity.value = o + } + + def lineWidth = peer.lineWidth.value + + def lineWidth_=(width: Int): Unit = { + peer.lineWidth.value = width + } + + def colorMesh: VertexColorMesh3D = peer.source + + def transformedTriangleMesh: VertexColorMesh3D = peer.transformedSource +} + +object VertexColorMeshView { + + implicit object FindInSceneColorMesh extends FindInScene[VertexColorMeshView] { + override def createView(s: SceneNode): Option[VertexColorMeshView] = { + s match { + case peer: VertexColorMeshNode => Some(VertexColorMeshView(peer)) + case _ => None + } + } + } + + implicit object callbacksColorMeshView extends HandleCallback[VertexColorMeshView] { + + override def registerOnAdd[R](g: Group, f: VertexColorMeshView => R): Unit = { + g.peer.listenTo(g.peer.colorMeshes) + g.peer.reactions += { + case ChildAdded(collection, newNode: VertexColorMeshNode) => + val tmv = VertexColorMeshView(newNode) + f(tmv) + } + } + + override def registerOnRemove[R](g: Group, f: VertexColorMeshView => R): Unit = { + g.peer.listenTo(g.peer.colorMeshes) + g.peer.reactions += { + case ChildRemoved(collection, removedNode: VertexColorMeshNode) => + val tmv = VertexColorMeshView(removedNode) + f(tmv) + } + } + } +} + + + case class LineMeshView private[ui] (override protected[api] val peer: LineMeshNode) extends ObjectView { type PeerType = LineMeshNode diff --git a/src/main/scala/scalismo/ui/model/GroupNode.scala b/src/main/scala/scalismo/ui/model/GroupNode.scala index e88f58c4..2942239f 100644 --- a/src/main/scala/scalismo/ui/model/GroupNode.scala +++ b/src/main/scala/scalismo/ui/model/GroupNode.scala @@ -50,6 +50,7 @@ class GroupNode(override val parent: GroupsNode, initialName: String, private va val landmarks = new LandmarksNode(this) val triangleMeshes = new TriangleMeshesNode(this) + val colorMeshes = new VertexColorMeshesNode(this) val scalarMeshFields = new ScalarMeshFieldsNode(this) val lineMeshes = new LineMeshesNode(this) val vectorFields = new VectorFieldsNode(this) @@ -57,7 +58,19 @@ class GroupNode(override val parent: GroupsNode, initialName: String, private va val images = new ImagesNode(this) val scalarFields = new ScalarFieldsNode(this) - override val children: List[SceneNode] = List(genericTransformations, shapeModelTransformations, landmarks, triangleMeshes, lineMeshes, scalarMeshFields, pointClouds, images, scalarFields, vectorFields) + override val children: List[SceneNode] = List( + genericTransformations, + shapeModelTransformations, + landmarks, + triangleMeshes, + colorMeshes, + lineMeshes, + scalarMeshFields, + pointClouds, + images, + scalarFields, + vectorFields + ) // this is a convenience method to add a statistical model as a (gp, mesh) combination. def addStatisticalMeshModel(model: StatisticalMeshModel, initialName: String): Unit = { diff --git a/src/main/scala/scalismo/ui/model/VertexColorMeshNode.scala b/src/main/scala/scalismo/ui/model/VertexColorMeshNode.scala new file mode 100644 index 00000000..e9a98fcf --- /dev/null +++ b/src/main/scala/scalismo/ui/model/VertexColorMeshNode.scala @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 University of Basel, Graphics and Vision Research Group + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +package scalismo.ui.model + +import java.io.File + +import scalismo.geometry.{ Point, Point3D, _3D } +import scalismo.geometry +import scalismo.io.MeshIO +import scalismo.mesh.VertexColorMesh3D +import scalismo.ui.model.capabilities._ +import scalismo.ui.model.properties._ +import scalismo.ui.util.{ FileIoMetadata, FileUtil } + +import scala.util.{ Failure, Success, Try } + +class VertexColorMeshesNode(override val parent: GroupNode) extends SceneNodeCollection[VertexColorMeshNode] with Loadable { + override val name: String = "Vertex Colored Triangle Meshes" + + override def loadMetadata: FileIoMetadata = FileIoMetadata.VertexColorMesh + + override def load(file: File): Try[Unit] = { + val r = MeshIO.readVertexColorMesh3D(file) + r match { + case Failure(ex) => Failure(ex) + case Success(mesh) => + add(mesh, FileUtil.basename(file)) + Success(()) + } + } + + def add(mesh: VertexColorMesh3D, name: String): VertexColorMeshNode = { + val node = new VertexColorMeshNode(this, mesh, name) + add(node) + node + } +} + +class VertexColorMeshNode(override val parent: VertexColorMeshesNode, override val source: VertexColorMesh3D, initialName: String) + extends Transformable[VertexColorMesh3D] with InverseTransformation with Removeable with Renameable with HasLineWidth with HasOpacity with Saveable { + name = initialName + + override def group: GroupNode = parent.parent + + override def remove(): Unit = parent.remove(this) + + override def inverseTransform(point: Point3D): Point3D = { + val id = transformedSource.shape.pointSet.findClosestPoint(point).id + source.shape.pointSet.point(id) + } + + override def transform(untransformed: VertexColorMesh3D, transformation: PointTransformation): VertexColorMesh3D = { + untransformed.transform(transformation) + } + + override val opacity = new OpacityProperty() + + override val lineWidth = new LineWidthProperty() + + override def save(file: File): Try[Unit] = MeshIO.writeVertexColorMesh3D(transformedSource, file) + + override def saveMetadata: FileIoMetadata = FileIoMetadata.VertexColorMesh + +} diff --git a/src/main/scala/scalismo/ui/rendering/Caches.scala b/src/main/scala/scalismo/ui/rendering/Caches.scala index fc3252b0..ea3c5ddf 100644 --- a/src/main/scala/scalismo/ui/rendering/Caches.scala +++ b/src/main/scala/scalismo/ui/rendering/Caches.scala @@ -20,7 +20,7 @@ package scalismo.ui.rendering import scalismo.common.{ DiscreteDomain, DiscreteScalarField } import scalismo.geometry._3D import scalismo.image.DiscreteScalarImage -import scalismo.mesh.{ ScalarMeshField, TriangleMesh3D } +import scalismo.mesh.{ ScalarMeshField, TriangleMesh3D, VertexColorMesh3D } import scalismo.ui.util.Cache import vtk.{ vtkPolyData, vtkStructuredPoints } @@ -35,8 +35,15 @@ object Caches { override lazy val hashCode: Int = (31 + tm.pointSet.hashCode()) * (31 + tm.triangulation.hashCode()) } + case class FastCachingVertexColorMesh(mesh: VertexColorMesh3D) { + override lazy val hashCode: Int = + (31 + mesh.shape.pointSet.hashCode()) * (31 + mesh.shape.triangulation.hashCode() * 31 + mesh.color.hashCode()) + } + final val TriangleMeshCache = new Cache[FastCachingTriangleMesh, vtkPolyData] final val ImageCache = new Cache[DiscreteScalarImage[_3D, _], vtkStructuredPoints] final val ScalarMeshFieldCache = new Cache[ScalarMeshField[Float], vtkPolyData] final val ScalarFieldCache = new Cache[DiscreteScalarField[_3D, DiscreteDomain[_3D], Float], vtkPolyData] + final val VertexColorMeshCache = new Cache[FastCachingVertexColorMesh, vtkPolyData] + } diff --git a/src/main/scala/scalismo/ui/rendering/actor/ActorsFactory.scala b/src/main/scala/scalismo/ui/rendering/actor/ActorsFactory.scala index aa0b9159..bd04714a 100644 --- a/src/main/scala/scalismo/ui/rendering/actor/ActorsFactory.scala +++ b/src/main/scala/scalismo/ui/rendering/actor/ActorsFactory.scala @@ -31,7 +31,19 @@ import scala.reflect.ClassTag * */ object ActorsFactory { - val BuiltinFactories: List[ActorsFactory] = List(BoundingBoxActor, TriangleMeshActor, ScalarMeshFieldActor, LineMeshActor, PointCloudActor, LandmarkActor, ImageActor, ScalarFieldActor, VectorFieldActor, TransformationGlyphActor) + val BuiltinFactories: List[ActorsFactory] = List( + BoundingBoxActor, + TriangleMeshActor, + ScalarMeshFieldActor, + VertexColorMeshActor, + LineMeshActor, + PointCloudActor, + LandmarkActor, + ImageActor, + ScalarFieldActor, + VectorFieldActor, + TransformationGlyphActor + ) var _factories: Map[Class[_ <: Renderable], ActorsFactory] = Map.empty diff --git a/src/main/scala/scalismo/ui/rendering/actor/MeshActor.scala b/src/main/scala/scalismo/ui/rendering/actor/MeshActor.scala index 04b1f540..b50c0e86 100644 --- a/src/main/scala/scalismo/ui/rendering/actor/MeshActor.scala +++ b/src/main/scala/scalismo/ui/rendering/actor/MeshActor.scala @@ -18,18 +18,18 @@ package scalismo.ui.rendering.actor import scalismo.geometry._3D -import scalismo.mesh.{ LineMesh, ScalarMeshField, TriangleMesh } +import scalismo.mesh.{LineMesh, ScalarMeshField, TriangleMesh, VertexColorMesh3D} import scalismo.ui.model.capabilities.Transformable import scalismo.ui.model.properties._ import scalismo.ui.model._ import scalismo.ui.rendering.Caches -import scalismo.ui.rendering.Caches.FastCachingTriangleMesh +import scalismo.ui.rendering.Caches.{FastCachingVertexColorMesh, FastCachingTriangleMesh} import scalismo.ui.rendering.actor.MeshActor.MeshRenderable import scalismo.ui.rendering.actor.mixin._ import scalismo.ui.rendering.util.VtkUtil -import scalismo.ui.view.{ ViewportPanel, ViewportPanel2D, ViewportPanel3D } +import scalismo.ui.view.{ViewportPanel, ViewportPanel2D, ViewportPanel3D} import scalismo.utils.MeshConversion -import vtk.vtkPolyData +import vtk.{vtkPolyData, vtkUnsignedCharArray} object TriangleMeshActor extends SimpleActorsFactory[TriangleMeshNode] { @@ -60,6 +60,16 @@ object LineMeshActor extends SimpleActorsFactory[LineMeshNode] { } } +object VertexColorMeshActor extends SimpleActorsFactory[VertexColorMeshNode] { + override def actorsFor(renderable: VertexColorMeshNode, viewport: ViewportPanel): Option[Actors] = { + viewport match { + case _3d: ViewportPanel3D => Some(new VertexColorMeshActor3D(renderable)) + case _2d: ViewportPanel2D => Some(new VertexColorMeshActor2D(renderable, _2d)) + } + } +} + + object MeshActor { trait MeshRenderable { @@ -90,6 +100,20 @@ object MeshActor { def color: ColorProperty = node.color } + class VertexColorMeshRenderable(override val node: VertexColorMeshNode) extends MeshRenderable { + + type MeshType = TriangleMesh[_3D] + + override def mesh: TriangleMesh[_3D] = node.transformedSource.shape + + override def opacity: OpacityProperty = node.opacity + + override def lineWidth: LineWidthProperty = node.lineWidth + + def colorMesh = node.transformedSource + } + + class ScalarMeshFieldRenderable(override val node: ScalarMeshFieldNode) extends MeshRenderable { type MeshType = TriangleMesh[_3D] @@ -120,6 +144,8 @@ object MeshActor { def apply(source: TriangleMeshNode): TriangleMeshRenderable = new TriangleMeshRenderable(source) + def apply(source: VertexColorMeshNode): VertexColorMeshRenderable = new VertexColorMeshRenderable(source) + def apply(source: ScalarMeshFieldNode): ScalarMeshFieldRenderable = new ScalarMeshFieldRenderable(source) def apply(source: LineMeshNode): LineMeshRenderable = new LineMeshRenderable(source) @@ -189,6 +215,31 @@ trait TriangleMeshActor extends MeshActor[MeshRenderable.TriangleMeshRenderable] } +trait VertexColorMeshActor extends MeshActor[MeshRenderable.VertexColorMeshRenderable] { + + override def renderable: MeshRenderable.VertexColorMeshRenderable + + override protected def meshToPolyData(template: Option[vtkPolyData]): vtkPolyData = { + + def colorMeshToVtkPd(colorMesh : VertexColorMesh3D) : vtkPolyData = { + + val pd = MeshConversion.meshToVtkPolyData(colorMesh.shape) + val vtkColors = new vtkUnsignedCharArray() + vtkColors.SetNumberOfComponents(3); + vtkColors.SetName("RGB") + + for (id <- colorMesh.shape.pointSet.pointIds) { + val color = colorMesh.color(id) + vtkColors.InsertNextTuple3((color.r * 255).toShort, (color.g * 255).toShort, (color.b * 255).toShort) + } + pd.GetPointData().SetScalars(vtkColors) + pd + } + Caches.VertexColorMeshCache.getOrCreate(FastCachingVertexColorMesh(renderable.colorMesh), colorMeshToVtkPd(renderable.colorMesh)) + } +} + + trait LineMeshActor extends MeshActor[MeshRenderable.LineMeshRenderable] with ActorColor with ActorLineWidth { override def renderable: MeshRenderable.LineMeshRenderable @@ -214,6 +265,9 @@ trait ScalarMeshFieldActor extends MeshActor[MeshRenderable.ScalarMeshFieldRende } + + + abstract class MeshActor3D[R <: MeshRenderable](override val renderable: R) extends MeshActor[R] { // not declaring this as lazy causes all sorts of weird VTK errors, probably because the methods which use @@ -252,6 +306,10 @@ class TriangleMeshActor3D(node: TriangleMeshNode) extends MeshActor3D(MeshRender class TriangleMeshActor2D(node: TriangleMeshNode, viewport: ViewportPanel2D) extends MeshActor2D(MeshRenderable(node), viewport) with TriangleMeshActor +class VertexColorMeshActor3D(node: VertexColorMeshNode) extends MeshActor3D(MeshRenderable(node)) with VertexColorMeshActor + +class VertexColorMeshActor2D(node: VertexColorMeshNode, viewport: ViewportPanel2D) extends MeshActor2D(MeshRenderable(node), viewport) with VertexColorMeshActor + class ScalarMeshFieldActor3D(node: ScalarMeshFieldNode) extends MeshActor3D(MeshRenderable(node)) with ScalarMeshFieldActor class ScalarMeshFieldActor2D(node: ScalarMeshFieldNode, viewport: ViewportPanel2D) extends MeshActor2D(MeshRenderable(node), viewport) with ScalarMeshFieldActor diff --git a/src/main/scala/scalismo/ui/util/FileIoMetadata.scala b/src/main/scala/scalismo/ui/util/FileIoMetadata.scala index b0dbacae..fbc6dd87 100644 --- a/src/main/scala/scalismo/ui/util/FileIoMetadata.scala +++ b/src/main/scala/scalismo/ui/util/FileIoMetadata.scala @@ -44,7 +44,7 @@ object FileIoMetadata { val TriangleMesh = new FileIoMetadata { override val description = "Triangle Mesh" - override val fileExtensions = List("stl", "vtk") + override val fileExtensions = List("stl", "vtk", "ply") } val ScalarMeshField = new FileIoMetadata { @@ -52,6 +52,12 @@ object FileIoMetadata { override val fileExtensions = List("vtk") } + val VertexColorMesh = new FileIoMetadata { + + override def fileExtensions: List[String] = List("ply") + override def description: String = "Triangle Mesh with vertex color" + } + val Image = new FileIoMetadata { override val description = "3D Image" override val fileExtensions = List("nii", "vtk")