Skip to content

Commit

Permalink
Optimize .et3 and .mes loading routine. Generate optimized thorax sha…
Browse files Browse the repository at this point in the history
…pe using distmesh and shapely (#38)

Optimize .et3 and .mes loading routine. Generate optimized thorax shape using distmesh and shapely.
  • Loading branch information
liubenyuan authored Apr 14, 2022
1 parent a40f85a commit 14b17db
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 170 deletions.
7 changes: 6 additions & 1 deletion examples/mesh_distmesh2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def example6():
(-0.1653, 0.6819),
]
# build triangles
p, t = distmesh.build(thorax, fh=area_uniform, pfix=p_fix, h0=0.05)
p, t = distmesh.build(thorax, fh=area_uniform, pfix=p_fix, h0=0.1)
# plot
fig, ax = plt.subplots()
ax.triplot(p[:, 0], p[:, 1], t)
Expand All @@ -176,6 +176,11 @@ def example6():
plt.show()


def example_head_symm():
"""head phantom (symmetric)"""
pass


def example_voronoi_plot():
"""draw voronoi plots for triangle elements"""

Expand Down
4 changes: 2 additions & 2 deletions pyeit/feature_extraction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
EIT dynamic images and static properties.
"""

from .transfer_impedance import ati, fmmu_index, ati_lr, ati_df
from .transfer_impedance import ati, fmmu_index, ati_roi, ati_df
from .mesh_geometry import SimpleMeshGeometry

__all__ = ["ati", "fmmu_index", "ati_lr", "ati_df", "SimpleMeshGeometry"]
__all__ = ["ati", "fmmu_index", "ati_roi", "ati_df", "SimpleMeshGeometry"]
17 changes: 8 additions & 9 deletions pyeit/feature_extraction/transfer_impedance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import numpy as np


def nansum(x):
"""implement numpy-1.8 behavior of nansum"""
return np.NAN if np.isnan(x).any() else np.nansum(np.abs(x))


def ati(x):
"""
averaged total impedance, unit (mV),
Expand All @@ -16,13 +21,7 @@ def ati(x):
-----
if I=1mA, then ati returns Ohms
"""
# implement old behavior of numpy.nansum
if np.isnan(x).any():
v = np.nan
else:
v = np.sum(np.abs(x)) / 192.0

return v
return nansum(x) / 192.0


def ati_df(x):
Expand Down Expand Up @@ -66,8 +65,8 @@ def fmmu_index(n_el=16, dist=8, step=1):
return left_sel, right_sel


def ati_lr(x, sel):
"""extract ATI left, right"""
def ati_roi(x, sel):
"""extract ATI from ROI region"""
x_sel = np.nanmean(np.abs(x[sel]))

return x_sel
Expand Down
33 changes: 20 additions & 13 deletions pyeit/io/et3.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def load(self):
d = fh.read(self.frame_size)
# extract time ticks
time_array[i] = unpack("d", d[8:16])[0]
# extract ADC samples
# extract ADC samples (double precision)
dp = d[960 : self.header_size]
adc_array[i] = np.array(unpack("8d", dp))
# extract demodulated I,Q data
Expand Down Expand Up @@ -206,24 +206,31 @@ def to_df(self):
return df

def to_dp(self, adc_filter=False):
"""convert raw ADC data to DataFrame"""
# left ear, right ear, Nasopharyngeal, rectal
columns = ["tle", "tre", "tn", "tr", "c4", "c5", "c6", "c7"]
"""
in new ET3 data, the left ear, right ear, Nasopharyngeal, rectal
temperature are recorded in the headers of .et3 file.
This script convert raw ADC data to DataFrame.
"""
#
columns = [
"t_left_ear",
"t_right_ear",
"t_naso",
"t_renal",
"aux_c4",
"aux_c5",
"aux_c6",
"aux_c7",
]
dp = pd.DataFrame(self.adc_array, index=self.ts, columns=columns)
dp = dp[~dp.index.duplicated()]

if adc_filter:
# correct temperature (temperature can accidently be 0)
dp.loc[dp["tle"] == 0, "tle"] = np.nan
dp.loc[dp["tre"] == 0, "tre"] = np.nan
dp.loc[dp["tn"] == 0, "tn"] = np.nan
dp.loc[dp["tr"] == 0, "tr"] = np.nan

# filter auxillary sampled data
dp.tle = med_outlier(dp.tle)
dp.tre = med_outlier(dp.tre)
dp.tn = med_outlier(dp.tn)
dp.tr = med_outlier(dp.tr)
for c in columns:
dp.loc[dp[c] == 0, c] = np.NAN
dp[c] = med_outlier(dp[c])

return dp

Expand Down
80 changes: 41 additions & 39 deletions pyeit/io/mes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"""
# Copyright (c) Benyuan Liu. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
import os
import ctypes
import struct
import numpy as np
Expand Down Expand Up @@ -158,57 +159,58 @@ def extract_el(fh):
return el_pos


if __name__ == "__main__":
# How to load and use a .mes file (github.com/liubenyuan/eitmesh)
mstr = resource_filename("eitmesh", "data/I0007.mes")
mesh_obj, el_pos = load(fstr=mstr)

# print the size
e, pts = mesh_obj["element"], mesh_obj["node"]
mesh_center = np.array([np.median(pts[:, 0]), np.median(pts[:, 1])])
# print('tri size = (%d, %d)' % e.shape)
# print('pts size = (%d, %d)' % pts.shape)

# show mesh
fig, ax = plt.subplots(1, figsize=(6, 6))
ax.triplot(pts[:, 0], pts[:, 1], e)
ax.plot(pts[el_pos, 0], pts[el_pos, 1], "ro")
def mesh_plot(ax, mesh_obj, el_pos, imstr="", title=None):
"""plot and annotate mesh"""
p, e, perm = mesh_obj["node"], mesh_obj["element"], mesh_obj["perm"]
annotate_color = "k"
if os.path.exists(imstr):
im = plt.imread(imstr)
annotate_color = "w"
ax.imshow(im, origin="lower")
ax.tripcolor(p[:, 0], p[:, 1], e, facecolors=perm, edgecolors="k", alpha=0.4)
ax.triplot(p[:, 0], p[:, 1], e, lw=1)
ax.plot(p[el_pos, 0], p[el_pos, 1], "ro")
for i, el in enumerate(el_pos):
xy = np.array([pts[el, 0], pts[el, 1]])
xy = np.array([p[el, 0], p[el, 1]])
text_offset = (xy - mesh_center) * [1, -1] * 0.05
ax.annotate(
str(i + 1),
xy=xy,
xytext=text_offset,
textcoords="offset points",
color="k",
color=annotate_color,
ha="center",
va="center",
)
ax.set_aspect("equal")
ax.set_title(title)
ax.invert_yaxis()

# bmp and mesh overlay
fig, ax = plt.subplots(figsize=(6, 6))
return ax


if __name__ == "__main__":
# How to load and use a .mes file (github.com/liubenyuan/eitmesh)
mstr = resource_filename("eitmesh", "data/IM470.mes")
imstr = mstr.replace(".mes", ".bmp")
im = plt.imread(imstr)
ax.imshow(im)
ax.set_aspect("equal")
mesh_obj, el_pos = load(fstr=mstr)

# the plot will automatically align with an overlay image
ax.triplot(pts[:, 0], pts[:, 1], e)
ax.plot(pts[el_pos, 0], pts[el_pos, 1], "ro")
for i, el in enumerate(el_pos):
xy = np.array([pts[el, 0], pts[el, 1]])
text_offset = (xy - mesh_center) * [1, -1] * 0.05
ax.annotate(
str(i + 1),
xy=xy,
xytext=text_offset,
textcoords="offset points",
color="w",
ha="center",
va="center",
)
ax.axis("off")
plt.show()
# print the size
e, pts, perm = mesh_obj["element"], mesh_obj["node"], mesh_obj["perm"]
mesh_center = np.array([np.median(pts[:, 0]), np.median(pts[:, 1])])
# print('tri size = (%d, %d)' % e.shape)
# print('pts size = (%d, %d)' % pts.shape)
fig, ax = plt.subplots(1, figsize=(6, 6))
mesh_plot(ax, mesh_obj, el_pos, imstr=imstr)
# fig.savefig("IM470.png", dpi=100)

# compare two mesh
mstr = resource_filename("eitmesh", "data/DLS2.mes")
mesh_obj2, el_pos2 = load(fstr=mstr)
mesh_array = [[mesh_obj, el_pos, "IM470"], [mesh_obj2, el_pos2, "DLS2"]]

fig, axs = plt.subplots(1, 2, figsize=(12, 6))
for i, ax in enumerate(axs):
mesh, elp, title = mesh_array[i]
mesh_plot(ax, mesh, elp, title=title)
# fig.savefig("mesh_plot.png", dpi=100)
53 changes: 10 additions & 43 deletions pyeit/mesh/distmesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
from numpy import sqrt
from scipy.spatial import Delaunay
from scipy.sparse import csr_matrix

from .utils import dist, edge_project

from .shape import thorax


class DISTMESH:
"""class for distmesh"""
Expand Down Expand Up @@ -101,15 +98,8 @@ def __init__(
self.num_density = 0
self.num_move = 0

"""
keep points that are inside the thorax shape using a function that returns a matrix containing
True if the corresponing point is inside the shape, False if not.
"""
if fd == thorax:
p = p[fd(p)]
else:
# keep points inside (minus distance) with a small gap (geps)
p = p[fd(p) < self.geps] # pylint: disable=E1136
# keep points inside (minus distance) with a small gap (geps)
p = p[fd(p) < self.geps] # pylint: disable=E1136

# rejection points by sampling on fh
r0 = 1.0 / fh(p) ** self.n_dim
Expand All @@ -122,21 +112,12 @@ def __init__(
self.pfix = p_fix
self.nfix = len(p_fix)

# convert boolean array to 2D to be compatible with Delaunay pts paramater (must be 2D)
if fd == thorax:
p = np.reshape(p, (-1, 2))

# remove duplicated points of p and p_fix
# avoid overlapping of mesh points
if self.nfix > 0:
p = remove_duplicate_nodes(p, p_fix, self.geps)
p = np.vstack([p_fix, p])

if fd == thorax:
p = np.reshape(
p, (-1, 2)
) # convert boolean array to 2D to be compatible with Delaunay pts paramater (must be 2D)

# store p and N
self.N = p.shape[0]
self.p = p
Expand All @@ -163,23 +144,12 @@ def triangulate(self):
self.pold = self.p.copy()

# triangles where the points are arranged counterclockwise
if self.fd != thorax:
tri = Delaunay(self.p).simplices
else:
tri = Delaunay(
self.p, qhull_options="QJ"
).simplices # QJ parameter so tuples don't exceed boundary

# QJ parameter so tuples don't exceed boundary
tri = Delaunay(self.p, qhull_options="QJ").simplices
pmid = np.mean(self.p[tri], axis=1)

if self.fd != thorax:
# keeps only interior points
t = tri[self.fd(pmid) < -self.geps]
else:
# adapting returned triangles matrix with the thorax integrated fd
tri_pmid = [p[0] for p in self.fd(pmid)]
tri_pmid = np.array(tri_pmid)
t = tri[tri_pmid]
# keeps only interior points
t = tri[self.fd(pmid) < -self.geps]
# extract edges (bars)
bars = t[:, self.edge_combinations].reshape((-1, 2))
# sort and remove duplicated edges, eg (1,2) and (2,1)
Expand Down Expand Up @@ -459,13 +429,10 @@ def build(
# calculate bar forces
Ftot = dm.bar_force(L, L0, barvec)

if fd != thorax:
# update p
converge = dm.move_p(Ftot)
# the stopping ctriterion (movements interior are small)
if converge:
break
else: # Thorax mesh is created so far without iteration process (to be updated)
# update p
converge = dm.move_p(Ftot)
# the stopping ctriterion (movements interior are small)
if converge:
break

# at the end of iteration, (p - pold) is small, so we recreate delaunay
Expand Down
Loading

0 comments on commit 14b17db

Please sign in to comment.