Skip to content

Commit

Permalink
Merge branch 'release-1.0' of github.com:unibas-gravis/scalismo into …
Browse files Browse the repository at this point in the history
…release-1.0
  • Loading branch information
marcelluethi committed Jan 23, 2024
2 parents 019ac45 + 9752c5c commit e4f6cb6
Show file tree
Hide file tree
Showing 29 changed files with 7,543 additions and 336 deletions.
15 changes: 15 additions & 0 deletions src/main/scala/scalismo/io/FileReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package scalismo.io

import java.io.RandomAccessFile
import java.nio.ByteBuffer

object FileReader {
def readFileToByteBuffer(file: String): ByteBuffer = {
val raf = new RandomAccessFile(file, "r")
val channel = raf.getChannel
val buffer = ByteBuffer.allocate(channel.size.toInt)
channel.read(buffer)
buffer.rewind()
buffer
}
}
195 changes: 12 additions & 183 deletions src/main/scala/scalismo/io/MeshIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import scalismo.common.{PointId, Scalar, UnstructuredPoints}
import scalismo.geometry.*
import scalismo.hdf5json.HDFPath
import scalismo.io.statisticalmodel.{NDArray, StatisticalModelIOUtils}
import scalismo.io.stl.STL
import scalismo.mesh.TriangleMesh.*
import scalismo.mesh.*
import scalismo.utils.{MeshConversion, TetrahedralMeshConversion}
import vtk.*
import scalismo.io.ply.PLY

import scala.reflect.ClassTag
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -84,13 +86,11 @@ object MeshIO {
val filename = file.getAbsolutePath
filename match {
case f if f.endsWith(".vtk") => readVTK(file)
case f if f.endsWith(".stl") => readSTL(file)
case f if f.endsWith(".stl") => STL.read(file.toString)
case f if f.endsWith(".ply") => {
readPLY(file).map { res =>
res match {
case Right(vertexColor) => vertexColor.shape
case Left(shape) => shape
}
PLY.read(file).map {
case Right(vertexColor) => vertexColor.shape
case Left(shape) => shape
}
}
case _ =>
Expand All @@ -102,11 +102,9 @@ object MeshIO {
val filename = file.getAbsolutePath
filename match {
case f if f.endsWith(".ply") =>
readPLY(file).map { r =>
r match {
case Right(colorMesh3D) => colorMesh3D
case Left(_) => throw new Exception("Indicated PLY file does not contain color values.")
}
PLY.read(file).map {
case Right(colorMesh3D) => colorMesh3D
case Left(_) => throw new Exception("Indicated PLY file does not contain color values.")
}

case _ =>
Expand Down Expand Up @@ -361,8 +359,8 @@ object MeshIO {
filename match {
case f if f.endsWith(".h5") => writeHDF5(mesh, file)
case f if f.endsWith(".vtk") => writeVTK(mesh, file)
case f if f.endsWith(".stl") => writeSTL(mesh, file)
case f if f.endsWith(".ply") => writePLY(Left(mesh), file)
case f if f.endsWith(".ply") => PLY.write(mesh, file)
case f if f.endsWith(".stl") => STL.write(mesh, file.toString)
case _ =>
Failure(new IOException("Unknown file type received" + filename))
}
Expand All @@ -377,7 +375,7 @@ object MeshIO {
def writeVertexColorMesh3D(mesh: VertexColorMesh3D, file: File): Try[Unit] = {
val filename = file.getAbsolutePath
filename match {
case f if f.endsWith(".ply") => writePLY(Right(mesh), file)
case f if f.endsWith(".ply") => PLY.write(mesh, file)
case _ =>
Failure(new IOException("Unknown file type received" + filename))
}
Expand Down Expand Up @@ -425,64 +423,6 @@ object MeshIO {
err
}

def writeSTL(surface: TriangleMesh[_3D], file: File): Try[Unit] = {
val vtkPd = MeshConversion.meshToVtkPolyData(surface)
val err = writeVTKPdAsSTL(vtkPd, file)
vtkPd.Delete()
err
}

private def writePLY(surface: Either[TriangleMesh[_3D], VertexColorMesh3D], file: File): Try[Unit] = {

val vtkPd = surface match {
case Right(colorMesh) => MeshConversion.meshToVtkPolyData(colorMesh.shape)
case Left(shapeOnly) => MeshConversion.meshToVtkPolyData(shapeOnly)
}

// add the colours if it is a vertex color
surface match {
case Right(colorMesh) => {

val vtkColors = new vtkUnsignedCharArray()
vtkColors.SetNumberOfComponents(4)

// Add the three colors we have created to the array
for (id <- colorMesh.shape.pointSet.pointIds) {
val color = colorMesh.color(id)
vtkColors.InsertNextTuple4((color.r * 255).toShort,
(color.g * 255).toShort,
(color.b * 255).toShort,
color.a * 255
)
}
vtkColors.SetName("RGBA")
vtkPd.GetPointData().SetScalars(vtkColors)

}
case _ => {}
}
val writer = new vtkPLYWriter()
writer.SetFileName(file.getAbsolutePath)
writer.SetArrayName("RGBA")
writer.SetComponent(0)
writer.SetEnableAlpha(true)
writer.SetInputData(vtkPd)
writer.SetColorModeToDefault()
writer.SetFileTypeToBinary()
writer.Update()

val succOrFailure = if (writer.GetErrorCode() != 0) {
Failure(
new IOException(s"could not write file ${file.getAbsolutePath} (received error code ${writer.GetErrorCode})")
)
} else {
Success(())
}
writer.Delete()
vtkPd.Delete()
succOrFailure
}

private def writeVTKPdasVTK(vtkPd: vtkPolyData, file: File): Try[Unit] = {
val writer = new vtkPolyDataWriter()
writer.SetFileName(file.getAbsolutePath)
Expand All @@ -500,23 +440,6 @@ object MeshIO {
succOrFailure
}

private def writeVTKPdAsSTL(vtkPd: vtkPolyData, file: File): Try[Unit] = {
val writer = new vtkSTLWriter()
writer.SetFileName(file.getAbsolutePath)
writer.SetInputData(vtkPd)
writer.SetFileTypeToBinary()
writer.Update()
val succOrFailure = if (writer.GetErrorCode() != 0) {
Failure(
new IOException(s"could not write file ${file.getAbsolutePath} (received error code ${writer.GetErrorCode})")
)
} else {
Success(())
}
writer.Delete()
succOrFailure
}

private def readVTKPolydata(file: File): Try[vtkPolyData] = {

val vtkReader = new vtkPolyDataReader()
Expand Down Expand Up @@ -544,35 +467,6 @@ object MeshIO {
}
}

private def readSTL(file: File, correctMesh: Boolean = false): Try[TriangleMesh[_3D]] = {
val stlReader = new vtkSTLReader()
stlReader.SetFileName(file.getAbsolutePath)

stlReader.MergingOn()

// With the default point locator, it may happen that the stlReader merges
// points that are very close by but not identical. To make sure that this never happens
// we explicitly specify the tolerance.
val pointLocator = new vtkMergePoints()
pointLocator.SetTolerance(0.0)

stlReader.SetLocator(pointLocator)
stlReader.Update()
val errCode = stlReader.GetErrorCode()
if (errCode != 0) {
return Failure(new IOException(s"Could not read stl mesh (received error code $errCode"))
}

val vtkPd = stlReader.GetOutput()
val mesh =
if (correctMesh) MeshConversion.vtkPolyDataToCorrectedTriangleMesh(vtkPd)
else MeshConversion.vtkPolyDataToTriangleMesh(vtkPd)

stlReader.Delete()
vtkPd.Delete()
mesh
}

private def getColorArray(polyData: vtkPolyData): Option[(String, vtkDataArray)] = {
if (polyData.GetPointData() == null || polyData.GetPointData().GetNumberOfArrays() == 0) None
else {
Expand All @@ -584,71 +478,6 @@ object MeshIO {
}
}

private def readPLY(file: File): Try[Either[TriangleMesh[_3D], VertexColorMesh3D]] = {

// read the ply header to find out if the ply is a textured mesh in ASCII (in which case we return a failure since VTKPLYReader Update() would crash otherwise)

if (!file.exists()) {
val filename = file.getCanonicalFile
Failure(new IOException(s"Could not read ply file with name $filename. Reason: The file does not exist"))
} else {
val breader = new BufferedReader(new FileReader(file))
val lineIterator = Iterator.continually(breader.readLine())

val headerLines = lineIterator.dropWhile(_ != "ply").takeWhile(_ != "end_header").toIndexedSeq

if (headerLines.exists(_.contains("TextureFile")) && headerLines.exists(_.contains("format ascii"))) {
Failure(
new IOException(
"PLY file $filename seems to be a textured mesh in ASCII format which creates issues with the VTK ply reader. Please convert it to a binary ply or to a vertex color or shape only ply."
)
)
} else {
readPLYUsingVTK(file)
}
}
}

private def readPLYUsingVTK(file: File): Try[Either[TriangleMesh[_3D], VertexColorMesh3D]] = {
val filename = file.getCanonicalFile
val plyReader = new vtkPLYReader()
plyReader.SetFileName(file.getAbsolutePath)
plyReader.Update()

val errCode = plyReader.GetErrorCode()
if (errCode != 0) {
return Failure(new IOException(s"Could not read ply mesh $filename (received VTK error code $errCode"))
}

val vtkPd = plyReader.GetOutput()
val mesh = for {
meshGeometry <- MeshConversion.vtkPolyDataToTriangleMesh(vtkPd)
} yield {
getColorArray(vtkPd) match {
case Some(("RGBA", colorArray)) => {

val colors = for (i <- 0 until colorArray.GetNumberOfTuples().toInt) yield {
val rgba = colorArray.GetTuple4(i)
RGBA(rgba(0) / 255.0, rgba(1) / 255.0, rgba(2) / 255.0, rgba(3) / 255.0)
}
Right(VertexColorMesh3D(meshGeometry, new SurfacePointProperty[RGBA](meshGeometry.triangulation, colors)))
}
case Some(("RGB", colorArray)) => {
val colors = for (i <- 0 until colorArray.GetNumberOfTuples().toInt) yield {
val rgb = colorArray.GetTuple3(i)
RGBA(RGB(rgb(0) / 255.0, rgb(1) / 255.0, rgb(2) / 255.0))
}
Right(VertexColorMesh3D(meshGeometry, new SurfacePointProperty[RGBA](meshGeometry.triangulation, colors)))
}
case Some(_) => Left(meshGeometry)
case None => Left(meshGeometry)
}
}
plyReader.Delete()
vtkPd.Delete()
mesh
}

private def NDArrayToPointSeq(ndarray: NDArray[Double]): IndexedSeq[Point[_3D]] = {
// take block of 3, map them to 3dPoints and convert the resulting array to an indexed seq
ndarray.data.grouped(3).map(grp => Point(grp(0).toFloat, grp(1).toFloat, grp(2).toFloat)).toIndexedSeq
Expand Down
42 changes: 42 additions & 0 deletions src/main/scala/scalismo/io/ply/PLY.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2015 University of Basel, Graphics and Vision Research Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scalismo.io.ply

import scalismo.geometry._3D
import scalismo.mesh.{TriangleMesh, VertexColorMesh3D}

import java.io.{File, IOException}
import scala.util.{Failure, Try}

object PLY {

def write(mesh: TriangleMesh[_3D], filename: File): Try[Unit] = {
PLYMeshWriter.write(mesh, None, filename)
}

def write(mesh: VertexColorMesh3D, filename: File): Try[Unit] = {
PLYMeshWriter.write(mesh.shape, Option(mesh.color.pointData.iterator), filename)
}

def read(file: File): Try[Either[TriangleMesh[_3D], VertexColorMesh3D]] = {
if (!file.exists()) {
val filename = file.getCanonicalFile
Failure(new IOException(s"Could not read ply file with name $filename. Reason: The file does not exist"))
} else {
PLYMeshReader.readFileAndParseHeader(file)
}
}
}
Loading

0 comments on commit e4f6cb6

Please sign in to comment.