Skip to content

Commit e0ad700

Browse files
committed
initial commit
0 parents  commit e0ad700

31 files changed

+35693
-0
lines changed

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# WebSSM
2+
![webssm](thumbnail.jpg)
3+
a web-based tool for visualizing, manipulating and sampling statistical shape models in [scalismo](https://github.com/unibas-gravis/scalismo)
4+
5+
## Motivation
6+
[scalismo](https://scalismo.org/) is a library for statistical shape modelling and model-based image analysis in Scala.
7+
WebSSM aims to bringing these statistical shape models to the convenience of web. So anyone accessing the web can view and interact with your shape model, also create samples and download them for their use.
8+
9+
## Demo
10+
you can see a Demo of how WebSSM looks in [here](https://www.youtube.com/watch?v=7YUsT4kE_Zg).
11+
12+
## Screenshots
13+
14+
## Tech/Framework used
15+
- Python
16+
- Flask
17+
- Scala
18+
- Scalismo
19+
- Javascript
20+
- Node.js
21+
- VTK.js
22+
23+
## How to use
24+
you can fork the project (click fork in top right of this page), then you can start working on the project from your GitHub account. Then clone the code to your local machine.
25+
26+
### step1. start by making your shape model ready for web
27+
#### quick
28+
Having sbt installed, place your statistical shape model file (.h5 extension) in `data` folder of `data-preparation` directory, then open a terminal and run `sbt run`. it will extract all required parameters from your shape model and writes them in 4 .csv files in the results folder.
29+
#### advanced
30+
Follow the [tutorial](https://scalismo.org/docs/ide) in Scalismo website, you'll be able to open the `data-preparatio` folder as a project in IntellijIdea IDE, make any change you like in the code and build it. It's especially useful for some cases that your shape model is too big for the lightweight web view, then you can change the code a bit to sample from the shape model.
31+
32+
### step2. continue with the backend
33+
Within `backend` directory, place .csv file generated in step 1, into the folder `data`. In the `backend` folder open a terminal and do following tasks:
34+
run the following commands in cmd or powershell, while in `backend` folder:
35+
`python -m venv venv` //creates a virtual environment in which requirements will be installed
36+
`./venv/Scripts/activate.bat` //(if you're using powershell, change .bat to .ps1)
37+
`pip install -r requirements.txt` //installs requirement in the virtual environment
38+
39+
`$env:FLASK_APP = "app.py"`
40+
`flask run`
41+
42+
### step3. finish with frontend
43+
Now if you just open the `index.html` file from the `frontend` directory, into your browser, you should see the your shape model as you expect.
44+
#### advanced
45+
having Node.js installed, open a terminal in the `frontend` folder and run `npm install`. it will generate a folder called node_modules and installs everything you need to build the project.
46+
In case you wanted to make changes in the frontend (like changing menus, adding functionalities, ...) you can make your changes basically in `src/webssm.js` file and then build the project with command `npx webpack --progress`
47+
48+
49+
### deployment on server
50+
enjoying viewing your shape model in your browser? if you want to deploy your web-based tool to your real server, you have a few more steps to do. Then if you send me a message in email or in [Scalismo google discussion group](https://groups.google.com/g/scalismo), I'll provide you with an easy step-by-step guide.

backend/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
data/*.csv
3+
venv
4+
__pycache__

backend/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
create a python virtual environment (python 3.7 is the version I've used):
2+
open a windows cmd or powershell:
3+
cd [this folder]
4+
python -m venv venv
5+
./venv/Scripts/activate.bat //(if you're using powershell, change .bat to .ps1)
6+
pip install -r requirements.txt
7+
8+
$env:FLASK_APP = "app.py"
9+
flask run
10+
11+
now it's listening on port 5000, and can serve the frontend

backend/app.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from flask import Flask, jsonify, request
2+
import vtk
3+
from reconstructSurface import ReconstructSurface
4+
from vtk.util.misc import vtkGetDataRoot
5+
from datetime import datetime
6+
import json
7+
from flask_cors import CORS
8+
9+
10+
#if you're deploying to a server, you might want to change the host_ip something like '0.0.0.0'
11+
host_ip = '127.0.0.1'
12+
13+
VTK_DATA_ROOT = vtkGetDataRoot()
14+
15+
app = Flask(__name__)
16+
cors = CORS(app, resources={r"/*": {"origins":"*"}})
17+
18+
#this is the piece of code that serves the frontend everytime a shape mode changes
19+
@app.route('/surfacereconstructor', methods=['GET', 'POST'])
20+
def surfacereconstructor():
21+
if request.method == 'POST':
22+
points_array = request.get_json()["array"]
23+
cntr_nr = request.get_json()["cntr_nr"]
24+
recsurf = ReconstructSurface()
25+
recsurf.pts = points_array
26+
recsurf.cntr_nr = cntr_nr
27+
cf = recsurf.reconstruct()
28+
surface = cf.GetOutput()
29+
pts,cls,nms = extr_surf_vals(surface)
30+
response = {"points":pts, "cells":cls, "normals":nms}
31+
return response
32+
33+
#this part serves the frontend for the first time it's loading
34+
@app.route('/readdata', methods=['GET'])
35+
def readdata():
36+
if request.method == 'GET':
37+
csv_dict = {}
38+
with open('data/stddev.csv', 'r') as f:
39+
csv_dict['stddev'] = f.read()
40+
with open('data/basisMatrix.csv', 'r') as f:
41+
csv_dict['basisMatrix'] = f.read()
42+
with open('data/meanVector.csv', 'r') as f:
43+
csv_dict['meanVector'] = f.read()
44+
with open('data/meanShape.csv', 'r') as f:
45+
csv_dict['meanShape'] = f.read()
46+
return jsonify(csv_dict)
47+
48+
#this function extracts the vtk surface data to be sent to frontend
49+
def extr_surf_vals(polyData):
50+
numCells = polyData.GetNumberOfPolys()
51+
numPoints = polyData.GetNumberOfPoints()
52+
53+
points = [0.0 for i in range(numPoints*3)]
54+
normals = [0.0 for i in range(numPoints*3)]
55+
cells = [0 for i in range(numCells*3)]
56+
coords = [0.0, 0.0, 0.0]
57+
58+
cellArray = polyData.GetPolys()
59+
cellArray.InitTraversal()
60+
for i in range(numCells):
61+
idList = vtk.vtkIdList()
62+
cellArray.GetNextCell(idList)
63+
for j in range(idList.GetNumberOfIds()):
64+
id = idList.GetId(j)
65+
cells[i*3+j] = id
66+
67+
polyData.GetPoint(id, coords)
68+
points[id * 3 + 0] = coords[0]
69+
points[id * 3 + 1] = coords[1]
70+
points[id * 3 + 2] = coords[2]
71+
72+
normalArray = polyData.GetPointData().GetNormals()
73+
for i in range(normalArray.GetNumberOfTuples()):
74+
normalArray.GetTuple(i, coords)
75+
76+
normals[i * 3 + 0] = coords[0]
77+
normals[i * 3 + 1] = coords[1]
78+
normals[i * 3 + 2] = coords[2]
79+
return (points, cells, normals)
80+
81+
if __name__ == "__main__":
82+
app.run(host=host_ip)

backend/data/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
put the .csv files you generated in data_preparation step in here. the server will take and provide them to frontend upon first request.

backend/reconstructSurface.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import vtk
2+
import numpy as np
3+
from vtk.util.misc import vtkGetDataRoot
4+
from datetime import datetime
5+
VTK_DATA_ROOT = vtkGetDataRoot()
6+
7+
class ReconstructSurface:
8+
def __init__(self):
9+
self.pts = ""
10+
self.cntr_nr = 0
11+
self.pointSource = vtk.vtkProgrammableSource()
12+
self.pointSource.SetExecuteMethod(self.readPoints)
13+
14+
def readPoints(self):
15+
output = self.pointSource.GetPolyDataOutput()
16+
points = vtk.vtkPoints()
17+
output.SetPoints(points)
18+
for i in np.arange(0, len(self.pts) - 1, 3):
19+
x, y, z = float(self.pts[i]), float(self.pts[i + 1]), float(self.pts[i + 2])
20+
points.InsertNextPoint(x,y,z)
21+
22+
def reconstruct(self):
23+
# Read some points. Use a programmable filter to read them.
24+
# Construct the surface and create isosurface.
25+
surf = vtk.vtkSurfaceReconstructionFilter()
26+
surf.SetSampleSpacing(1)
27+
surf.SetInputConnection(self.pointSource.GetOutputPort())
28+
29+
cf = vtk.vtkContourFilter()
30+
cf.SetInputConnection(surf.GetOutputPort())
31+
cf.SetValue(0, 0.0)
32+
33+
# Sometimes the contouring algorithm can create a volume whose gradient
34+
# vector and ordering of polygon (using the right hand rule) are
35+
# inconsistent. vtkReverseSense cures this problem.
36+
reverse = vtk.vtkReverseSense()
37+
reverse.SetInputConnection(cf.GetOutputPort())
38+
reverse.ReverseCellsOn()
39+
reverse.ReverseNormalsOn()
40+
41+
map = vtk.vtkPolyDataMapper()
42+
map.SetInputConnection(reverse.GetOutputPort())
43+
map.ScalarVisibilityOff()
44+
45+
surfaceActor = vtk.vtkActor()
46+
surfaceActor.SetMapper(map)
47+
surfaceActor.GetProperty().SetDiffuseColor(1.0000, 0.3882, 0.2784)
48+
surfaceActor.GetProperty().SetSpecularColor(1, 1, 1)
49+
surfaceActor.GetProperty().SetSpecular(.4)
50+
surfaceActor.GetProperty().SetSpecularPower(50)
51+
52+
# Create the RenderWindow, Renderer and both Actors
53+
ren = vtk.vtkRenderer()
54+
renWin = vtk.vtkRenderWindow()
55+
renWin.AddRenderer(ren)
56+
iren = vtk.vtkRenderWindowInteractor()
57+
iren.SetRenderWindow(renWin)
58+
59+
# Add the actors to the renderer, set the background and size
60+
ren.AddActor(surfaceActor)
61+
ren.SetBackground(1, 1, 1)
62+
renWin.SetSize(400, 400)
63+
ren.GetActiveCamera().SetFocalPoint(0, 0, 0)
64+
ren.GetActiveCamera().SetPosition(1, 0, 0)
65+
ren.GetActiveCamera().SetViewUp(0, 0, 1)
66+
ren.ResetCamera()
67+
ren.GetActiveCamera().Azimuth(20)
68+
ren.GetActiveCamera().Elevation(30)
69+
ren.GetActiveCamera().Dolly(1.2)
70+
ren.ResetCameraClippingRange()
71+
72+
return cf

backend/requirements.txt

324 Bytes
Binary file not shown.

data_preparation/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.idea
2+
data/*.csv
3+
data/*.h5
4+
results/*.csv
5+
project/target
6+
project/project
7+
target

data_preparation/.scalafmt.conf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version=2.3.2
2+
project.git = true
3+
4+
align.openParenCallSite = true
5+
align.openParenDefnSite = true
6+
maxColumn = 120
7+
continuationIndent.defnSite = 2
8+
assumeStandardLibraryStripMargin = true
9+
danglingParentheses = true
10+
rewrite.rules = [SortImports, SortModifiers]
11+
docstrings = JavaDoc
12+
13+
onTestFailure = "To fix this, run ./scalafmt from the project root directory"

data_preparation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
place your shape model (.h5) in data folder, then run the src code, it extracts the shape model parameters and places them in data folder as .csv files.

0 commit comments

Comments
 (0)