Skip to content

Commit f359ee5

Browse files
committed
[test] add tests for volume processing, brain1020 is working
1 parent dc49d0e commit f359ee5

File tree

8 files changed

+383
-90
lines changed

8 files changed

+383
-90
lines changed

README.md

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
* **Copyright**: (C) Qianqian Fang (2024-2025) <q.fang at neu.edu>
66
* **License**: GNU Public License V3 or later
7-
* **Version**: 0.3.8
7+
* **Version**: 0.4.0
88
* **URL**: [https://pypi.org/project/iso2mesh/](https://pypi.org/project/iso2mesh/)
99
* **Homepage**: [https://iso2mesh.sf.net](https://iso2mesh.sf.net)
1010
* **Github**: [https://github.com/NeuroJSON/pyiso2mesh](https://github.com/NeuroJSON/pyiso2mesh)
@@ -25,6 +25,7 @@
2525
- [Run built-in unit-tests](#run-built-in-unit-tests)
2626
- [How to Use](#how-to-use)
2727
- [Iso2Mesh function port status](#iso2mesh-function-port-status)
28+
- [How to Cite](#how-to-cite)
2829
- [Acknowledgement](#acknowledgement)
2930

3031
## Introduction
@@ -197,27 +198,27 @@ tracked in https://github.com/NeuroJSON/pyiso2mesh/issues/1
197198

198199
| Ported | Unit-tested | | Ported | Unit-tested |
199200
| ------ | ------ | --- | ------ | ------ |
200-
| > All-in-one pipeline shortcuts | | | > File I/O | |
201+
| > All-in-one pipeline shortcuts | | | > File I/O | |
201202
|`v2m.m` | ✅ tested | |`saveasc.m` | ⭕️ tested |
202203
|`v2s.m` | ✅ tested | |`savedxf.m` | ⭕️ tested |
203204
|`s2m.m` | ✅ tested | |`savestl.m` | ⭕️ tested |
204205
|`s2v.m` | ✅ tested | |`savebinstl.m` | ⭕️ tested |
205-
|`m2v.m` | tested | |`saveinr.m` | ⭕️ tested |
206+
|`m2v.m` | ✅ tested | |`saveinr.m` | ⭕️ tested |
206207
|`sms.m` | ✅ tested | |`saveoff.m` | ✅ tested |
207208
| > Streamlined mesh generation| | | ⭕️ `savesmf.m` | ⭕️ tested |
208209
|`vol2mesh.m` | ✅ tested | |`savesurfpoly.m` | ✅ tested |
209210
|`vol2surf.m` | ✅ tested | | ⭕️ `savegts.m` | ⭕️ tested |
210211
|`surf2mesh.m` | ✅ tested | | ⭕️ `readgts.m` | ⭕️ tested |
211212
|`surf2vol.m` | ✅ tested | | ⭕️ `savemsh.m` | ⭕️ tested |
212-
|`mesh2vol.m` | tested | | ⭕️ `savevrml.m` | ⭕️ tested |
213+
|`mesh2vol.m` | ✅ tested | | ⭕️ `savevrml.m` | ⭕️ tested |
213214
| > Iso2mesh main function backend| | |`readasc.m` | ⭕️ tested |
214215
|`binsurface.m` | ✅ tested | | ⭕️ `readinr.m` | ⭕️ tested |
215216
|`cgalv2m.m` | ✅ tested | |`readmedit.m` | ⭕️ tested |
216217
|`cgals2m.m` | ✅ tested | |`readoff.m` | ✅ tested |
217218
|`vol2restrictedtri.m` | ✅ tested | | ⭕️ `readsmf.m` | ⭕️ tested |
218219
|`surf2volz.m` | ✅ tested | |`readtetgen.m` | ✅ tested |
219-
|`mesh2mask.m` | tested | |`deletemeshfile.m` | ✅ tested |
220-
| > Iso2mesh primitive meshing| | |`mcpath.m` | ✅ tested |
220+
|`mesh2mask.m` | ✅ tested | |`deletemeshfile.m` | ✅ tested |
221+
| > Iso2mesh primitive meshing| | |`mcpath.m` | ✅ tested |
221222
|`meshabox.m` | ✅ tested | |`mwpath.m` | ✅ tested |
222223
|`meshasphere.m` | ✅ tested | |`savemedit.m` | ✅ tested |
223224
|`meshanellip.m` | ✅ tested | | ⭕️ `savejson.m` | ⭕️ tested |
@@ -244,65 +245,87 @@ tracked in https://github.com/NeuroJSON/pyiso2mesh/issues/1
244245
|`maxsurf.m` | ⭕️ tested | | ⭕️ `loadjmesh.m` | ⭕️ tested |
245246
|`flatsegment.m` | ⭕️ tested | | ⭕️ `readobjmesh.m` | ⭕️ tested |
246247
|`orderloopedge.m` | ⭕️ tested | | > Volumetric image pre-processing| |
247-
|`mesheuler.m` | ✅ tested | | ⭕️ `bwislands.m` | ⭕️ tested |
248-
|`bbxflatsegment.m` | ⭕️ tested | | ⭕️ `fillholes3d.m` | ⭕️ tested |
249-
|`surfplane.m` | ⭕️ tested | | ⭕️ `deislands2d.m` | ⭕️ tested |
250-
|`surfinterior.m` | ⭕️ tested | | ⭕️ `deislands3d.m` | ⭕️ tested |
251-
|`surfpart.m` | ⭕️ tested | | ⭕️ `ndgaussian.m` | ⭕️ tested |
252-
|`surfseeds.m` | ⭕️ tested | | ⭕️ `ndimfilter.m` | ⭕️ tested |
253-
|`meshquality.m` | ✅ tested | | ⭕️ `imedge3d.m` | ⭕️ tested |
254-
|`meshedge.m` | ✅ tested | | ⭕️ `internalpoint.m` | ⭕️ tested |
255-
|`meshface.m` | ✅ tested | | ⭕️ `smoothbinvol.m` | ⭕️ tested |
256-
|`surfacenorm.m` | ✅ tested | | ⭕️ `thickenbinvol.m` | ⭕️ tested |
257-
|`nodesurfnorm.m` | ✅ tested | | ⭕️ `thinbinvol.m` | ⭕️ tested |
258-
|`uniqedges.m` | ✅ tested | | ⭕️ `maskdist.m` | ⭕️ tested |
259-
|`uniqfaces.m` | ✅ tested | | > Mesh plotting| |
260-
|`advancefront.m` | ⭕️ tested | | `plotmesh.m` | tested |
261-
|`innersurf.m` | ⭕️ tested | |`plotsurf.m` | ✅ tested |
262-
|`outersurf.m` | ⭕️ tested | |`plottetra.m` | ✅ tested |
263-
|`surfvolume.m` | ✅ tested | | `plotedges.m` | tested |
264-
|`insurface.m` | ✅ tested | | `qmeshcut.m` | ✅ tested |
265-
| > Mesh processing and reparing| | | > Miscellaneous functions| |
266-
|`meshcheckrepair.m` | ✅ tested | | ⭕️ `surfdiffuse.m` | ⭕️ tested |
267-
|`meshreorient.m` | ✅ tested | | ⭕️ `volmap2mesh.m` | ⭕️ tested |
268-
|`removedupelem.m` | ✅ tested | | ⭕️ `isoctavemesh.m` | ⭕️ tested |
269-
|`removedupnodes.m` | ✅ tested | | ⭕️ `getvarfrom.m` | ⭕️ tested |
270-
|`removeisolatednode.m` | ✅ tested | |`raytrace.m` | ✅ tested |
271-
|`removeisolatedsurf.m` | ⭕️ tested | | ⭕️ `linextriangle.m` | ⭕️ tested |
272-
|`surfaceclean.m` | ⭕️ tested | | ⭕️ `getplanefrom3pt.m` | ⭕️ tested |
273-
|`getintersecttri.m` | ⭕️ tested | | `getexeext.m` | tested |
274-
|`delendelem.m` | ⭕️ tested | | `fallbackexeext.m` | tested |
275-
|`surfreorient.m` | ✅ tested | | ⭕️ `iso2meshver.m` | ⭕️ tested |
276-
| > Mesh registration | | | ⭕️ `raysurf.m` | ⭕️ tested |
277-
|`proj2mesh.m` | ⭕️ tested | | ⭕️ `getoptkey.m` | ⭕️ tested |
278-
|`dist2surf.m` | ⭕️ tested | |`rotatevec3d.m` | ⭕️ tested |
279-
|`regpt2surf.m` | ⭕️ tested | |`rotmat2vec.m` | ⭕️ tested |
280-
|`affinemap.m` | ⭕️ tested | | `varargin2struct.m` | ⭕️ tested |
281-
| > Polyline handling| | | `jsonopt.m` | ⭕️ tested |
282-
| `slicesurf.m` | ⭕️ tested | | ⭕️ `mergestruct.m` | ⭕️ tested |
283-
| `slicesurf3.m` | ⭕️ tested | | ⭕️ `orthdisk.m` | ⭕️ tested |
284-
| `polylinelen.m` | ⭕️ tested | | ⭕️ `nestbracket2dim.m` | ⭕️ tested |
285-
| `polylinesimplify.m` | ⭕️ tested | | ⭕️ `memmapstream.m` | ⭕️ tested |
286-
| `polylineinterp.m` | ⭕️ tested | | ⭕️ `aos2soa.m` | ⭕️ tested |
287-
| `closestnode.m` | ⭕️ tested | | ⭕️ `soa2aos.m` | ⭕️ tested |
288-
| > Mesh resampling and optimization| |
289-
|`meshresample.m` | ✅ tested |
290-
|`remeshsurf.m` | ✅ tested |
291-
|`smoothsurf.m` | ✅ tested |
292-
|`sortmesh.m` | ⭕️ tested |
293-
|`mergemesh.m` | ✅ tested |
294-
|`meshrefine.m` | ✅ tested |
295-
|`mergesurf.m` | ⭕️ tested |
296-
|`surfboolean.m` | ✅ tested |
297-
|`fillsurf.m` | ⭕️ tested |
298-
|`highordertet.m` | ✅ tested |
248+
|`mesheuler.m` | ✅ tested | | `volgrow.m` | tested |
249+
|`bbxflatsegment.m` | ⭕️ tested | | `volshrink.m` | tested |
250+
|`surfplane.m` | ⭕️ tested | | `volopen.m` | tested |
251+
|`surfinterior.m` | ⭕️ tested | | `volclose.m` | tested |
252+
|`surfpart.m` | ⭕️ tested | | `fillholes3d.m` | tested |
253+
|`surfseeds.m` | ⭕️ tested | | ⭕️ `bwislands.m` | ⭕️ tested |
254+
|`meshquality.m` | ✅ tested | | ⭕️ `laplacefill.m` | ⭕️ tested |
255+
|`meshedge.m` | ✅ tested | | ⭕️ `deislands2d.m` | ⭕️ tested |
256+
|`meshface.m` | ✅ tested | | ⭕️ `deislands3d.m` | ⭕️ tested |
257+
|`surfacenorm.m` | ✅ tested | | ⭕️ `ndgaussian.m` | ⭕️ tested |
258+
|`nodesurfnorm.m` | ✅ tested | | ⭕️ `ndimfilter.m` | ⭕️ tested |
259+
|`uniqedges.m` | ✅ tested | | ⭕️ `imedge3d.m` | ⭕️ tested |
260+
|`uniqfaces.m` | ✅ tested | | ⭕️ `internalpoint.m` | ⭕️ tested |
261+
|`advancefront.m` | ⭕️ tested | | ⭕️ `smoothbinvol.m` | ⭕️ tested |
262+
|`innersurf.m` | ⭕️ tested | |`thickenbinvol.m` | ✅ tested |
263+
|`outersurf.m` | ⭕️ tested | |`thinbinvol.m` | ✅ tested |
264+
|`surfvolume.m` | ✅ tested | | ⭕️ `maskdist.m` | ⭕️ tested |
265+
|`insurface.m` | ✅ tested | | > Mesh plotting| |
266+
| > Mesh processing and reparing| | | `plotmesh.m` | ✅ tested |
267+
|`meshcheckrepair.m` | ✅ tested | | `plotsurf.m` | tested |
268+
|`meshreorient.m` | ✅ tested | | `plottetra.m` | tested |
269+
|`removedupelem.m` | ✅ tested | | `plotedges.m` | tested |
270+
|`removedupnodes.m` | ✅ tested | | `qmeshcut.m` | tested |
271+
|`removeisolatednode.m` | ✅ tested | | > Miscellaneous functions| |
272+
|`removeisolatedsurf.m` | ⭕️ tested | | ⭕️ `surfdiffuse.m` | ⭕️ tested |
273+
|`surfaceclean.m` | ⭕️ tested | | ⭕️ `volmap2mesh.m` | ⭕️ tested |
274+
|`getintersecttri.m` | ⭕️ tested | | ⭕️ `isoctavemesh.m` | ⭕️ tested |
275+
|`delendelem.m` | ⭕️ tested | | ⭕️ `getvarfrom.m` | ⭕️ tested |
276+
|`surfreorient.m` | ✅ tested | | `raytrace.m` | tested |
277+
| > Mesh registration | | | ⭕️ `linextriangle.m` | ⭕️ tested |
278+
|`proj2mesh.m` | ⭕️ tested | | ⭕️ `getplanefrom3pt.m` | ⭕️ tested |
279+
|`dist2surf.m` | ⭕️ tested | |`getexeext.m` | tested |
280+
|`regpt2surf.m` | ⭕️ tested | |`fallbackexeext.m` | tested |
281+
|`affinemap.m` | ⭕️ tested | | ⭕️ `iso2meshver.m` | ⭕️ tested |
282+
| > Polyline handling| | | ⭕️ `raysurf.m` | ⭕️ tested |
283+
|`slicesurf.m` | ⭕️ tested | | ⭕️ `getoptkey.m` | ⭕️ tested |
284+
|`slicesurf3.m` | ⭕️ tested | | `rotatevec3d.m` | ⭕️ tested |
285+
|`polylinelen.m` | ⭕️ tested | | `rotmat2vec.m` | ⭕️ tested |
286+
|`polylinesimplify.m` | ⭕️ tested | | `varargin2struct.m` | ⭕️ tested |
287+
|`polylineinterp.m` | ⭕️ tested | | `jsonopt.m` | ⭕️ tested |
288+
|`closestnode.m` | ⭕️ tested | | ⭕️ `mergestruct.m` | ⭕️ tested |
289+
| > Mesh resampling and optimization| | | ⭕️ `orthdisk.m` | ⭕️ tested |
290+
|`meshresample.m` | ✅ tested | | ⭕️ `nestbracket2dim.m` | ⭕️ tested |
291+
|`remeshsurf.m` | ✅ tested | | ⭕️ `memmapstream.m` | ⭕️ tested |
292+
|`smoothsurf.m` | ✅ tested | | ⭕️ `aos2soa.m` | ⭕️ tested |
293+
|`sortmesh.m` | ⭕️ tested | | ⭕️ `soa2aos.m` | ⭕️ tested |
294+
|`mergemesh.m` | ✅ tested | | > Brain2mesh toolbox | |
295+
|`meshrefine.m` | ✅ tested | |`brain2mesh.m` | ⭕️ tested |
296+
|`mergesurf.m` | ⭕️ tested | |`brain1020.m` | ⭕️ tested |
297+
|`surfboolean.m` | ✅ tested | |`intriangulation.m` | ⭕️ tested |
298+
|`fillsurf.m` | ⭕️ tested | |`label2tpm.m` | ⭕️ tested |
299+
|`highordertet.m` | ✅ tested | |`tpm2label.m` | ⭕️ tested |
299300
|`elemfacecenter.m` | ✅ tested |
300301
|`barydualmesh.m` | ✅ tested |
301302
|`meshinterp.m` | ⭕️ tested |
302303
|`meshremap.m` | ⭕️ tested |
303304
|`extrudesurf.m` | ⭕️ tested |
304305

305306

307+
## How to Cite
308+
309+
Users of the pyiso2mesh toolbox should consider citing the below publications related to iso2mesh.
310+
311+
The original conference paper that described the **iso2mesh** toolbox workflow was published in 2009:
312+
313+
- Qianqian Fang and David Boas, "Tetrahedral mesh generation from volumetric binary and gray-scale images,"
314+
Proceedings of IEEE International Symposium on Biomedical Imaging 2009, pp. 1142-1145, 2009
315+
316+
A journal paper, published in 2020 describing the **brain2mesh** toolbox contains a much more
317+
up-to-date description of iso2mesh in the context of complex brain mesh generation
318+
319+
- Anh Phong Tran, Shijie Yan and Qianqian Fang*, (2020) "Improving model-based fNIRS analysis using
320+
mesh-based anatomical and light-transport models," Neurophotonics, 7(1), 015008
321+
322+
If one utilized the `brain1020` function in your research, you should cite the below paper where
323+
the workflow of this function was described
324+
325+
- Ashlyn McCann, Edward Xu, Fan-Yu Yen, Noah Joseph, Qianqian Fang, "Creating anatomically derived,
326+
standardized, customizable, and three-dimensional printable head caps for functional neuroimaging,"
327+
Neurophoton. 12(1) 015016 (18 March 2025) https://doi.org/10.1117/1.NPh.12.1.015016, PMID: 39257741; PMCID: PMC11383710.
328+
306329

307330
## Acknowledgement
308331

iso2mesh/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,13 @@
171171
volgrow,
172172
volshrink,
173173
volopen,
174-
volshrink,
174+
volclose,
175175
fillholes3d,
176176
thickenbinvol,
177177
thinbinvol,
178178
)
179179

180-
__version__ = "0.3.8"
180+
__version__ = "0.4.0"
181181
__all__ = [
182182
"advancefront",
183183
"barycentricgrid",
@@ -308,7 +308,7 @@
308308
"volgrow",
309309
"volshrink",
310310
"volopen",
311-
"volshrink",
311+
"volclose",
312312
"fillholes3d",
313313
"thickenbinvol",
314314
"thinbinvol",

iso2mesh/brain2mesh.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ def brain1020(
12881288
)
12891289

12901290
# Parse user options
1291-
showplot = kwargs.get("display", True)
1291+
showplot = kwargs.get("display", False)
12921292
baseplane = kwargs.get("baseplane", True)
12931293
tol = kwargs.get("cztol", 1e-6)
12941294
dosimplify = kwargs.get("minangle", 0)
@@ -1387,18 +1387,8 @@ def brain1020(
13871387
# At this point, initpoints contains {nz, iz, lpa, rpa, cz0}
13881388
# Plot the head mesh
13891389
if showplot:
1390-
fig = plt.figure(figsize=(10, 8))
1391-
ax = fig.add_subplot(111, projection="3d")
1392-
# Plot mesh with transparency
1393-
ax.plot_trisurf(
1394-
node[:, 0],
1395-
node[:, 1],
1396-
node[:, 2],
1397-
triangles=face - 1,
1398-
alpha=0.6,
1399-
color="wheat",
1400-
linewidth=0,
1401-
)
1390+
hh = plotmesh(node, face, alpha=0.6, color="wheat", linewidth=0)
1391+
ax = hh["ax"][0]
14021392
ax.set_xlabel("X")
14031393
ax.set_ylabel("Y")
14041394
ax.set_zlabel("Z")
@@ -1581,7 +1571,7 @@ def brain1020(
15811571
)
15821572

15831573
# Step 5: computing all anterior coronal cuts, moving away from the medial cut (cm) toward frontal
1584-
idxcz = closestnode(landmarks["sm"], landmarks["cz"])
1574+
idxcz = closestnode(landmarks["sm"], landmarks["cz"])[0]
15851575
skipcount = int(np.floor(10 / perc2))
15861576

15871577
for i in range(

iso2mesh/line.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def closestnode(node, p):
144144
dist_sq = np.sum(dd * dd, axis=1)
145145
idx = np.argmin(dist_sq)
146146
dist = dist_sq[idx]
147-
return idx, dist
147+
return idx + 1, dist
148148

149149

150150
def polylinelen(node, p0=None, p1=None, pmid=None):
@@ -197,7 +197,7 @@ def polylinelen(node, p0=None, p1=None, pmid=None):
197197

198198
if p0 < pmid and pmid < p1:
199199
inputreversed = 0
200-
node = node[p0 : p1 + 1, :]
200+
node = node[range(p0, p1 + 1), :]
201201
elif p0 < pmid and p1 < pmid:
202202
inputreversed = min(p0, p1) == p0
203203
idx_range = list(range(min(p0, p1), -1, -1)) + list(
@@ -208,7 +208,7 @@ def polylinelen(node, p0=None, p1=None, pmid=None):
208208
node = np.flipud(node)
209209
elif p0 > pmid and pmid > p1:
210210
inputreversed = 1
211-
node = node[p0 : p1 - 1 : -1, :]
211+
node = node[range(p0, p1 - 1, -1), :]
212212
elif p0 > pmid and p1 > pmid:
213213
inputreversed = max(p0, p1) == p1
214214
idx_range = list(range(max(p0, p1), node.shape[0])) + list(

iso2mesh/plot.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ def plotasurf(node, face, *args, **kwargs):
144144
node_values = node[:, 3] if node.shape[1] > 3 else node[:, 2]
145145
face_values = np.array([np.mean(node_values[f]) for f in face[:, :3] - 1])
146146
norm = Normalize(vmin=face_values.min(), vmax=face_values.max())
147-
colmap = kwargs["cmap"](norm(face_values))
147+
colmap = []
148+
if "cmap" in kwargs:
149+
colmap = kwargs["cmap"](norm(face_values))
148150
return poly3d, colmap
149151

150152

iso2mesh/volume.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"volgrow",
88
"volshrink",
99
"volopen",
10-
"volshrink",
10+
"volclose",
1111
"fillholes3d",
1212
"thickenbinvol",
1313
"thinbinvol",
@@ -32,13 +32,10 @@ def validatemask(mask, ndim=3):
3232
if mask is None or mask.size == 0:
3333
if ndim == 3:
3434
# Create 3D cross-shaped mask for 3D volumes
35-
mask = np.zeros((3, 3, 3), dtype=np.float32)
36-
mask[1, 1, :] = 1 # z-direction line (index 1 is center in 0-based)
37-
mask[:, 1, 1] = 1 # y-direction line
38-
mask[1, :, 1] = 1 # x-direction line
35+
mask = ndimage.generate_binary_structure(3, 1)
3936
else:
4037
# Create 2D cross-shaped mask for 2D images
41-
mask = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.float32)
38+
mask = ndimage.generate_binary_structure(2, 1)
4239

4340
# Rotate mask by 180 degrees (equivalent to rot90(mask, 2) in MATLAB)
4441
if mask.ndim == 3:
@@ -82,7 +79,7 @@ def volgrow(
8279
This function is part of iso2mesh toolbox (http://iso2mesh.sf.net)
8380
"""
8481

85-
mask = validatemask(mask)
82+
mask = validatemask(mask, vol.ndim)
8683

8784
# Convert vol to appropriate type for processing
8885
newvol = vol.astype(np.float32)
@@ -124,7 +121,7 @@ def volshrink(
124121
The volume image after the thinning operations
125122
"""
126123

127-
mask = validatemask(mask)
124+
mask = validatemask(mask, vol.ndim)
128125

129126
# Convert input to binary
130127
newvol = vol != 0
@@ -166,7 +163,7 @@ def volclose(
166163
if vol is None:
167164
raise ValueError("must provide a volume")
168165

169-
mask = validatemask(mask)
166+
mask = validatemask(mask, vol.ndim)
170167

171168
# Convert input to binary
172169
newvol = vol != 0
@@ -208,7 +205,7 @@ def volopen(
208205
if vol is None:
209206
raise ValueError("must provide a volume")
210207

211-
mask = validatemask(mask)
208+
mask = validatemask(mask, vol.ndim)
212209

213210
# Convert input to binary
214211
newvol = vol != 0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
setup(
77
name="iso2mesh",
88
packages=["iso2mesh"],
9-
version="0.3.8",
9+
version="0.4.0",
1010
license="GPLv3+",
1111
description="One-liner 3D Surface and Tetrahedral Mesh Generation Toolbox",
1212
long_description=readme,

0 commit comments

Comments
 (0)