hbs 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hbs-0.1.1/PKG-INFO +93 -0
- hbs-0.1.1/README.md +80 -0
- hbs-0.1.1/hbs/__init__.py +6 -0
- hbs-0.1.1/hbs/boundary.py +155 -0
- hbs-0.1.1/hbs/conformal_welding.py +150 -0
- hbs-0.1.1/hbs/hbs.py +103 -0
- hbs-0.1.1/hbs/mesh.py +341 -0
- hbs-0.1.1/hbs/qc/__init__.py +2 -0
- hbs-0.1.1/hbs/qc/bc.py +26 -0
- hbs-0.1.1/hbs/qc/lsqc.py +115 -0
- hbs-0.1.1/hbs/utils/__init__.py +0 -0
- hbs-0.1.1/hbs/utils/geodesic_welding.py +78 -0
- hbs-0.1.1/hbs/utils/mobius.py +27 -0
- hbs-0.1.1/hbs/utils/poisson.py +49 -0
- hbs-0.1.1/hbs/utils/tool_functions.py +38 -0
- hbs-0.1.1/hbs/utils/zipper.py +268 -0
- hbs-0.1.1/hbs.egg-info/PKG-INFO +93 -0
- hbs-0.1.1/hbs.egg-info/SOURCES.txt +21 -0
- hbs-0.1.1/hbs.egg-info/dependency_links.txt +1 -0
- hbs-0.1.1/hbs.egg-info/requires.txt +4 -0
- hbs-0.1.1/hbs.egg-info/top_level.txt +1 -0
- hbs-0.1.1/setup.cfg +4 -0
- hbs-0.1.1/setup.py +20 -0
hbs-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hbs
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python library for computing Harmonic Beltrami Signature(HBS)
|
|
5
|
+
Home-page: https://github.com/ChanceAroundYou/hbs_python
|
|
6
|
+
Author: Lin Chenran
|
|
7
|
+
Author-email: chenranlin.17@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# HBS - Harmonic Beltrami Signature
|
|
15
|
+
|
|
16
|
+
This is a Python library for computing Harmonic Beltrami Signature(HBS). It provides a set of tools for boundary conditions, mesh generation, conformal welding and other mathematical problems, particularly suited for numerical computations in complex analysis.
|
|
17
|
+
|
|
18
|
+
## Reference
|
|
19
|
+
This implementation is based on the paper:
|
|
20
|
+
|
|
21
|
+
**Harmonic Beltrami Signature: A Novel 2D Shape Representation for Object Classification**
|
|
22
|
+
Chenran Lin, Lok Ming Lui
|
|
23
|
+
DOI: [10.1137/22M1470852](https://doi.org/10.1137/22M1470852)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install directly from PyPI:
|
|
28
|
+
```bash
|
|
29
|
+
pip install hbs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or install from source:
|
|
33
|
+
1. Clone this repository:
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/ChanceAroundYou/hbs_python.git
|
|
36
|
+
```
|
|
37
|
+
2. Install dependencies:
|
|
38
|
+
```bash
|
|
39
|
+
pip install -r requirements.txt
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Main Modules
|
|
43
|
+
|
|
44
|
+
- `hbs.py`: HBS algorithm for computing HBS and recontructing shape from HBS
|
|
45
|
+
- `boundary.py`: Boundary processing tools
|
|
46
|
+
- `conformal_welding.py`: Conformal welding algorithm implementation
|
|
47
|
+
- `mesh.py`: Mesh generation and processing
|
|
48
|
+
- `utils/`:
|
|
49
|
+
- `geodesic_welding.py`: Geodesic welding
|
|
50
|
+
- `mobius.py`: Möbius transformations
|
|
51
|
+
- `poisson.py`: Poisson integral implementation
|
|
52
|
+
- `tool_functions.py`: Utility functions
|
|
53
|
+
- `zipper.py`: Zipper algorithm implementation
|
|
54
|
+
- `qc/`: Quasiconformal mapping algorithms
|
|
55
|
+
- `bc.py`: Beltrami coefficient computation
|
|
56
|
+
- `lsqc.py`: Least squares quasiconformal mapping algorithm
|
|
57
|
+
|
|
58
|
+
## Usage Example
|
|
59
|
+
|
|
60
|
+
Compute HBS from image
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from boundary import get_boundary
|
|
64
|
+
from hbs import get_hbs
|
|
65
|
+
|
|
66
|
+
img_path = 'img/example.jpg'
|
|
67
|
+
bound = get_boundary(img_path, bound_point_num)
|
|
68
|
+
hbs, he, cw, disk = get_hbs(bound, circle_point_num, density)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Reconstruct shape from HBS
|
|
72
|
+
```python
|
|
73
|
+
from hbs import reconstruct_from_hbs
|
|
74
|
+
|
|
75
|
+
## `disk` must be a DiskMesh corresponding to give `hbs`
|
|
76
|
+
bound, _, _, _ = reconstruct_from_hbs(hbs, disk)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Please also refer to `example.ipynb`
|
|
80
|
+
|
|
81
|
+
## Dependencies
|
|
82
|
+
|
|
83
|
+
- NumPy
|
|
84
|
+
- SciPy
|
|
85
|
+
- Matplotlib
|
|
86
|
+
- Opencv
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Contributions are welcome via pull requests.
|
|
91
|
+
|
|
92
|
+
## Code Author
|
|
93
|
+
- Chenran Lin
|
hbs-0.1.1/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# HBS - Harmonic Beltrami Signature
|
|
2
|
+
|
|
3
|
+
This is a Python library for computing Harmonic Beltrami Signature(HBS). It provides a set of tools for boundary conditions, mesh generation, conformal welding and other mathematical problems, particularly suited for numerical computations in complex analysis.
|
|
4
|
+
|
|
5
|
+
## Reference
|
|
6
|
+
This implementation is based on the paper:
|
|
7
|
+
|
|
8
|
+
**Harmonic Beltrami Signature: A Novel 2D Shape Representation for Object Classification**
|
|
9
|
+
Chenran Lin, Lok Ming Lui
|
|
10
|
+
DOI: [10.1137/22M1470852](https://doi.org/10.1137/22M1470852)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install directly from PyPI:
|
|
15
|
+
```bash
|
|
16
|
+
pip install hbs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install from source:
|
|
20
|
+
1. Clone this repository:
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/ChanceAroundYou/hbs_python.git
|
|
23
|
+
```
|
|
24
|
+
2. Install dependencies:
|
|
25
|
+
```bash
|
|
26
|
+
pip install -r requirements.txt
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Main Modules
|
|
30
|
+
|
|
31
|
+
- `hbs.py`: HBS algorithm for computing HBS and recontructing shape from HBS
|
|
32
|
+
- `boundary.py`: Boundary processing tools
|
|
33
|
+
- `conformal_welding.py`: Conformal welding algorithm implementation
|
|
34
|
+
- `mesh.py`: Mesh generation and processing
|
|
35
|
+
- `utils/`:
|
|
36
|
+
- `geodesic_welding.py`: Geodesic welding
|
|
37
|
+
- `mobius.py`: Möbius transformations
|
|
38
|
+
- `poisson.py`: Poisson integral implementation
|
|
39
|
+
- `tool_functions.py`: Utility functions
|
|
40
|
+
- `zipper.py`: Zipper algorithm implementation
|
|
41
|
+
- `qc/`: Quasiconformal mapping algorithms
|
|
42
|
+
- `bc.py`: Beltrami coefficient computation
|
|
43
|
+
- `lsqc.py`: Least squares quasiconformal mapping algorithm
|
|
44
|
+
|
|
45
|
+
## Usage Example
|
|
46
|
+
|
|
47
|
+
Compute HBS from image
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from boundary import get_boundary
|
|
51
|
+
from hbs import get_hbs
|
|
52
|
+
|
|
53
|
+
img_path = 'img/example.jpg'
|
|
54
|
+
bound = get_boundary(img_path, bound_point_num)
|
|
55
|
+
hbs, he, cw, disk = get_hbs(bound, circle_point_num, density)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Reconstruct shape from HBS
|
|
59
|
+
```python
|
|
60
|
+
from hbs import reconstruct_from_hbs
|
|
61
|
+
|
|
62
|
+
## `disk` must be a DiskMesh corresponding to give `hbs`
|
|
63
|
+
bound, _, _, _ = reconstruct_from_hbs(hbs, disk)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Please also refer to `example.ipynb`
|
|
67
|
+
|
|
68
|
+
## Dependencies
|
|
69
|
+
|
|
70
|
+
- NumPy
|
|
71
|
+
- SciPy
|
|
72
|
+
- Matplotlib
|
|
73
|
+
- Opencv
|
|
74
|
+
|
|
75
|
+
## Contributing
|
|
76
|
+
|
|
77
|
+
Contributions are welcome via pull requests.
|
|
78
|
+
|
|
79
|
+
## Code Author
|
|
80
|
+
- Chenran Lin
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.interpolate import CubicSpline, PchipInterpolator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_boundary(image_path, num_points=250):
|
|
8
|
+
"""
|
|
9
|
+
Process the image to extract boundary, apply anti-aliasing, and resample.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
image_path: Input image path
|
|
13
|
+
num_points: Number of resampled points
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
resampled_points: List of resampled boundary points
|
|
17
|
+
"""
|
|
18
|
+
# Extract boundary points
|
|
19
|
+
boundary_points = extract_boundary_points(image_path)
|
|
20
|
+
# Apply anti-aliasing
|
|
21
|
+
smoothed_points = anti_aliasing(boundary_points)
|
|
22
|
+
# Resample to the specified number of points
|
|
23
|
+
resampled_points = smooth_resample(smoothed_points, num_points)
|
|
24
|
+
return resampled_points
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def extract_boundary_points(image_path):
|
|
28
|
+
"""
|
|
29
|
+
Extract ordered boundary points from a near black-and-white image.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
image_path: Input image path
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
boundary_points: List of ordered boundary points, format [(x1, y1), (x2, y2), ...]
|
|
36
|
+
"""
|
|
37
|
+
# Read the image and convert to grayscale
|
|
38
|
+
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
|
39
|
+
|
|
40
|
+
# Determine if the image is black-on-white or white-on-black
|
|
41
|
+
if np.mean(img) > 127:
|
|
42
|
+
# White-on-black image, invert it
|
|
43
|
+
img = cv2.bitwise_not(img)
|
|
44
|
+
|
|
45
|
+
# Binarize the image
|
|
46
|
+
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
|
|
47
|
+
|
|
48
|
+
# Find contours (using CHAIN_APPROX_NONE to get all boundary points)
|
|
49
|
+
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
|
50
|
+
|
|
51
|
+
# Get the largest contour (assuming there's only one main shape in the image)
|
|
52
|
+
main_contour = max(contours, key=cv2.contourArea)
|
|
53
|
+
|
|
54
|
+
# Return the contour points directly, without sorting or anti-aliasing
|
|
55
|
+
return main_contour.reshape(-1, 2)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def anti_aliasing(points, kernel_size=5):
|
|
59
|
+
"""
|
|
60
|
+
Apply anti-aliasing to boundary points.
|
|
61
|
+
|
|
62
|
+
Parameters:
|
|
63
|
+
points: List of original boundary points
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
smoothed_points: List of smoothed boundary points
|
|
67
|
+
"""
|
|
68
|
+
# Use Gaussian blur for smoothing
|
|
69
|
+
x = cv2.GaussianBlur(points[:, 0].astype("float32"), (kernel_size, kernel_size), 0)
|
|
70
|
+
y = cv2.GaussianBlur(points[:, 1].astype("float32"), (kernel_size, kernel_size), 0)
|
|
71
|
+
return np.column_stack((x, y))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def plot_boundary(boundary, with_line=True, scale=1):
|
|
75
|
+
num_points = boundary.shape[0]
|
|
76
|
+
x_min, x_max = np.min(boundary[:, 0]), np.max(boundary[:, 0])
|
|
77
|
+
y_min, y_max = np.min(boundary[:, 1]), np.max(boundary[:, 1])
|
|
78
|
+
aspect_ratio = (x_max - x_min) / (y_max - y_min)
|
|
79
|
+
|
|
80
|
+
fig_size = (num_points / 60 * scale * aspect_ratio, num_points / 60 * scale)
|
|
81
|
+
plt.figure(figsize=fig_size)
|
|
82
|
+
if with_line:
|
|
83
|
+
plt.plot(
|
|
84
|
+
boundary[:, 0],
|
|
85
|
+
boundary[:, 1],
|
|
86
|
+
linestyle="-",
|
|
87
|
+
linewidth=1 * scale,
|
|
88
|
+
color="white",
|
|
89
|
+
label="Boundary Line",
|
|
90
|
+
)
|
|
91
|
+
plt.scatter(
|
|
92
|
+
boundary[:, 0],
|
|
93
|
+
boundary[:, 1],
|
|
94
|
+
marker="o",
|
|
95
|
+
s=15 * scale,
|
|
96
|
+
edgecolor="lightgreen",
|
|
97
|
+
facecolor="none",
|
|
98
|
+
label="Boundary Points",
|
|
99
|
+
)
|
|
100
|
+
plt.show()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def smooth_resample(points, num_points=None):
|
|
104
|
+
"""
|
|
105
|
+
Smoothly and uniformly resample the boundary points matrix, generating a matrix of m points.
|
|
106
|
+
:param points: nx2 matrix, original boundary point coordinates
|
|
107
|
+
:param num_points: Number of resampled points
|
|
108
|
+
:return: mx2 matrix, resampled boundary points
|
|
109
|
+
"""
|
|
110
|
+
if num_points is None:
|
|
111
|
+
num_points = points.shape[0]
|
|
112
|
+
|
|
113
|
+
# Check if the boundary is closed
|
|
114
|
+
is_closed = np.all(np.abs(points[0, :] - points[-1, :]) < 1e-8)
|
|
115
|
+
if not is_closed:
|
|
116
|
+
points = np.vstack([points, points[0, :]]) # Close the boundary if not closed
|
|
117
|
+
|
|
118
|
+
# Calculate cumulative arc length
|
|
119
|
+
diffs = np.diff(points, axis=0)
|
|
120
|
+
dists = np.sqrt(np.sum(diffs**2, axis=1))
|
|
121
|
+
cum_dists = np.concatenate([[0], np.cumsum(dists)])
|
|
122
|
+
total_length = cum_dists[-1]
|
|
123
|
+
|
|
124
|
+
# Spline interpolation settings
|
|
125
|
+
t = cum_dists
|
|
126
|
+
x = points[:, 0]
|
|
127
|
+
y = points[:, 1]
|
|
128
|
+
|
|
129
|
+
if is_closed:
|
|
130
|
+
# Periodic cubic spline interpolation (closed curve)
|
|
131
|
+
ppx = CubicSpline(t, x, bc_type="periodic")
|
|
132
|
+
ppy = CubicSpline(t, y, bc_type="periodic")
|
|
133
|
+
else:
|
|
134
|
+
# Shape-preserving piecewise cubic interpolation (open curve)
|
|
135
|
+
ppx = PchipInterpolator(t, x)
|
|
136
|
+
ppy = PchipInterpolator(t, y)
|
|
137
|
+
|
|
138
|
+
# Generate uniform parameters
|
|
139
|
+
if is_closed:
|
|
140
|
+
t_new = np.linspace(0, total_length, num_points + 2)
|
|
141
|
+
t_new = t_new[:-1] # Avoid duplicate start and end points
|
|
142
|
+
else:
|
|
143
|
+
t_new = np.linspace(0, total_length, num_points + 1)
|
|
144
|
+
|
|
145
|
+
# Calculate new points
|
|
146
|
+
x_new = ppx(t_new)
|
|
147
|
+
y_new = ppy(t_new)
|
|
148
|
+
resampled_points = np.column_stack([x_new, y_new])
|
|
149
|
+
return resampled_points[:-1, :]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
# 示例用法
|
|
154
|
+
boundary_points = extract_boundary_points("test_image.png")
|
|
155
|
+
print(f"提取的边界点数量:{len(boundary_points)}")
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.interpolate import interp1d
|
|
4
|
+
|
|
5
|
+
from utils.mobius import mobius, mobius_inv
|
|
6
|
+
from utils.tool_functions import to_complex
|
|
7
|
+
from utils.zipper import zipper, zipper_params
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConformalWelding:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
x: np.ndarray[np.complexfloating],
|
|
14
|
+
y: np.ndarray[np.complexfloating],
|
|
15
|
+
params: np.ndarray[np.complexfloating],
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the ConformalWelding class.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
x (np.ndarray): Complex ndarray of shape (n,) representing initial x points.
|
|
22
|
+
y (np.ndarray): Complex ndarray of shape (n,) representing initial y points.
|
|
23
|
+
params (np.ndarray): Complex ndarray of shape (n+1,) representing parameters.
|
|
24
|
+
"""
|
|
25
|
+
assert (
|
|
26
|
+
x.ndim == y.ndim == params.ndim == 1
|
|
27
|
+
and np.issubdtype(x.dtype, np.complexfloating)
|
|
28
|
+
and np.issubdtype(y.dtype, np.complexfloating)
|
|
29
|
+
and np.issubdtype(params.dtype, np.complexfloating)
|
|
30
|
+
), "Input must be 1D complex array"
|
|
31
|
+
assert len(x) == len(y) == len(params) - 1, (
|
|
32
|
+
"Length of x, y, params must be n, n, n+1"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.params = params
|
|
36
|
+
self._set_init_x(x)
|
|
37
|
+
self._set_init_y(y)
|
|
38
|
+
|
|
39
|
+
def _set_init_x(self, x: np.ndarray[np.complexfloating]):
|
|
40
|
+
self.init_x = x
|
|
41
|
+
self.init_x_angle = np.angle(x)
|
|
42
|
+
self.x = x
|
|
43
|
+
|
|
44
|
+
def _set_init_y(self, y: np.ndarray[np.complexfloating]):
|
|
45
|
+
self.init_y = y
|
|
46
|
+
self.y = y
|
|
47
|
+
|
|
48
|
+
def uniquify(self):
|
|
49
|
+
x_angle, idx = np.unique(np.angle(self.init_x), return_index=True)
|
|
50
|
+
x = np.exp(x_angle * 1j)
|
|
51
|
+
y = self.init_y[idx]
|
|
52
|
+
y = np.exp(np.angle(y) * 1j)
|
|
53
|
+
|
|
54
|
+
self._set_init_x(x)
|
|
55
|
+
self._set_init_y(y)
|
|
56
|
+
|
|
57
|
+
def linear_interp(self, num: int):
|
|
58
|
+
x_angle_regular = np.arange(0, 2 * np.pi, 2 * np.pi / num)
|
|
59
|
+
self.x = np.exp(x_angle_regular * 1j)
|
|
60
|
+
|
|
61
|
+
inter_func = interp1d(
|
|
62
|
+
np.concatenate(
|
|
63
|
+
[
|
|
64
|
+
self.init_x_angle - 2 * np.pi,
|
|
65
|
+
self.init_x_angle,
|
|
66
|
+
self.init_x_angle + 2 * np.pi,
|
|
67
|
+
]
|
|
68
|
+
),
|
|
69
|
+
np.concatenate([self.init_y, self.init_y, self.init_y]),
|
|
70
|
+
kind="linear",
|
|
71
|
+
)
|
|
72
|
+
y = inter_func(x_angle_regular)
|
|
73
|
+
self.y = np.exp(np.angle(y / y[0]) * 1j)
|
|
74
|
+
|
|
75
|
+
def rotate_x(self, r):
|
|
76
|
+
x_angle = np.mod(self.init_x_angle - r, 2 * np.pi)
|
|
77
|
+
x = np.exp(x_angle * 1j)
|
|
78
|
+
self._set_init_x(x)
|
|
79
|
+
|
|
80
|
+
def x_post_norm(self):
|
|
81
|
+
"""
|
|
82
|
+
对外部点进行后归一化
|
|
83
|
+
:param z: 输入点
|
|
84
|
+
:param params: 参数
|
|
85
|
+
:return: 归一化后的点,a 和 theta 参数
|
|
86
|
+
"""
|
|
87
|
+
zinf = zipper_params([np.inf], self.params)
|
|
88
|
+
a = -1 / np.conj(zinf)
|
|
89
|
+
theta = 0
|
|
90
|
+
x = mobius_inv(self.x, a, theta)
|
|
91
|
+
x = np.flipud(x)
|
|
92
|
+
self._set_init_x(x)
|
|
93
|
+
|
|
94
|
+
def y_post_norm(self):
|
|
95
|
+
"""
|
|
96
|
+
对内部点进行后归一化
|
|
97
|
+
:param z: 输入点
|
|
98
|
+
:return: 归一化后的点,a 和 theta 参数
|
|
99
|
+
"""
|
|
100
|
+
p = np.append(self.y, [0, 1])
|
|
101
|
+
while True:
|
|
102
|
+
center = np.mean(p[:-2])
|
|
103
|
+
if np.abs(center) <= np.finfo(float).eps:
|
|
104
|
+
break
|
|
105
|
+
p = mobius(p, center)
|
|
106
|
+
|
|
107
|
+
y = p[:-2]
|
|
108
|
+
# y = np.flipud(y)
|
|
109
|
+
self._set_init_y(y)
|
|
110
|
+
|
|
111
|
+
def plot_x(self):
|
|
112
|
+
plt.gca().set_aspect("equal", adjustable="box")
|
|
113
|
+
plt.scatter(self.x.real, self.x.imag)
|
|
114
|
+
plt.show()
|
|
115
|
+
|
|
116
|
+
def plot_y(self):
|
|
117
|
+
plt.gca().set_aspect("equal", adjustable="box")
|
|
118
|
+
plt.scatter(self.y.real, self.y.imag)
|
|
119
|
+
plt.show()
|
|
120
|
+
|
|
121
|
+
def plot(self, is_interp=True):
|
|
122
|
+
plt.gca().set_aspect("equal", adjustable="box")
|
|
123
|
+
x_angle = np.angle(self.x)
|
|
124
|
+
y_angle = np.angle(self.y)
|
|
125
|
+
x_angle = np.mod(x_angle, 2 * np.pi)
|
|
126
|
+
y_angle = np.mod(y_angle, 2 * np.pi)
|
|
127
|
+
if is_interp:
|
|
128
|
+
plt.plot(x_angle, y_angle, linestyle="-", linewidth=2)
|
|
129
|
+
else:
|
|
130
|
+
plt.scatter(x_angle, y_angle, s=2)
|
|
131
|
+
plt.show()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_conformal_welding(bound: np.ndarray[np.floating]) -> ConformalWelding:
|
|
135
|
+
assert (
|
|
136
|
+
bound.ndim == 2
|
|
137
|
+
and bound.shape[1] == 2
|
|
138
|
+
and np.issubdtype(bound.dtype, np.floating)
|
|
139
|
+
), "bound must be n x 2 real array with float type"
|
|
140
|
+
|
|
141
|
+
bound = to_complex(bound)
|
|
142
|
+
x, _, x_params = zipper(bound)
|
|
143
|
+
# x = np.flipud(x)
|
|
144
|
+
y, _, _ = zipper(np.flipud(bound))
|
|
145
|
+
|
|
146
|
+
cw = ConformalWelding(x, y, x_params)
|
|
147
|
+
cw.x_post_norm()
|
|
148
|
+
cw.y_post_norm()
|
|
149
|
+
cw.uniquify()
|
|
150
|
+
return cw
|
hbs-0.1.1/hbs/hbs.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from hbs.conformal_welding import ConformalWelding, get_conformal_welding
|
|
4
|
+
from utils.geodesic_welding import geodesic_welding
|
|
5
|
+
from hbs.mesh import DiskMesh, get_unit_disk
|
|
6
|
+
from qc import get_beltrami_coefficient, lsqc_solver
|
|
7
|
+
from utils.poisson import integral as poisson_integral
|
|
8
|
+
from utils.tool_functions import to_complex, to_real
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_hbs(
|
|
12
|
+
bound: np.ndarray[np.floating],
|
|
13
|
+
circle_point_num: int = 500,
|
|
14
|
+
density: float = 0.01,
|
|
15
|
+
disk: DiskMesh = None,
|
|
16
|
+
) -> tuple[
|
|
17
|
+
np.ndarray[np.complexfloating],
|
|
18
|
+
np.ndarray[np.floating],
|
|
19
|
+
ConformalWelding,
|
|
20
|
+
DiskMesh,
|
|
21
|
+
]:
|
|
22
|
+
"""
|
|
23
|
+
Get Beltrami coefficients from boundary points
|
|
24
|
+
:param `bound`: `n x 2` array, boundary points
|
|
25
|
+
:param `circle_point_num`: number of points on the circle
|
|
26
|
+
:param `density`: density of the mesh
|
|
27
|
+
:param `disk`: DiskMesh object
|
|
28
|
+
:return:
|
|
29
|
+
`hbs`: `n` x 1 complex array, Beltrami coefficients defined on triangles
|
|
30
|
+
`he`: `n` x 1 float array, harmonice extension of `hbs`
|
|
31
|
+
`cw`: ConformalWelding object
|
|
32
|
+
`disk`: DiskMesh object
|
|
33
|
+
"""
|
|
34
|
+
if disk is None:
|
|
35
|
+
disk = get_unit_disk(density, circle_point_num)
|
|
36
|
+
|
|
37
|
+
cw = get_conformal_welding(bound)
|
|
38
|
+
|
|
39
|
+
r = 0
|
|
40
|
+
while True:
|
|
41
|
+
cw.rotate_x(r / 2)
|
|
42
|
+
cw.linear_interp(circle_point_num)
|
|
43
|
+
he = poisson_integral(disk.in_vert, cw.x, cw.y)
|
|
44
|
+
hbs = get_beltrami_coefficient(he, disk)
|
|
45
|
+
|
|
46
|
+
excluded_idx = np.isnan(hbs) + np.linalg.norm(disk.face_center, axis=1) == 0
|
|
47
|
+
r = np.angle(np.sum(hbs[~excluded_idx]))
|
|
48
|
+
|
|
49
|
+
if abs(r) <= 5e-3:
|
|
50
|
+
he_angle = np.angle(np.sum(hbs * to_complex(disk.face_center)))
|
|
51
|
+
if he_angle < 0 or he_angle == np.pi:
|
|
52
|
+
r += 2 * np.pi
|
|
53
|
+
else:
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
return hbs, he, cw, disk
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def reconstruct_from_hbs(
|
|
60
|
+
hbs: np.ndarray[np.complexfloating], disk: DiskMesh
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Reconstruct original shape from HBS
|
|
64
|
+
:param `hbs`: complex array with length `disk.face_num`, Beltrami coefficients defined on triangles
|
|
65
|
+
:param `disk`: DiskMesh object
|
|
66
|
+
:return:
|
|
67
|
+
`bound_points`: `disk.circle_num` x 2 array, boundary points
|
|
68
|
+
`in_points`: `disk.in_vert_num` x 2 array, inner points
|
|
69
|
+
`out_points`: `disk.out_vert_num` x 2 array, outer points
|
|
70
|
+
`mapping`: `disk.vert_num` x 2 array, vertex coordinates of the solved mapping
|
|
71
|
+
"""
|
|
72
|
+
assert isinstance(disk, DiskMesh), "`disk` must be DiskMesh object"
|
|
73
|
+
|
|
74
|
+
assert isinstance(hbs, np.ndarray) and np.issubdtype(
|
|
75
|
+
hbs.dtype, np.complexfloating
|
|
76
|
+
), "`hbs` must be complex array"
|
|
77
|
+
assert hbs.ndim == 1 and hbs.shape[0] == disk.face_num, (
|
|
78
|
+
"the length of `hbs` is `n` and `n` is the number of faces"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
target = np.array([[1, 0], [0, 0]], dtype=np.float64)
|
|
82
|
+
|
|
83
|
+
one_pos = np.all(disk.vert == target[0], axis=1)
|
|
84
|
+
zero_pos = np.all(disk.vert == target[1], axis=1)
|
|
85
|
+
landmark = np.where(one_pos | zero_pos)[0]
|
|
86
|
+
|
|
87
|
+
mapping = lsqc_solver(hbs, landmark, target, disk)
|
|
88
|
+
bound_points = mapping[: disk.circle_num]
|
|
89
|
+
in_points = mapping[: disk.in_vert_num + disk.circle_num]
|
|
90
|
+
out_points = mapping[disk.in_vert_num + disk.circle_num :]
|
|
91
|
+
|
|
92
|
+
in_points, out_points = geodesic_welding(
|
|
93
|
+
to_complex(in_points),
|
|
94
|
+
to_complex(out_points),
|
|
95
|
+
to_complex(bound_points),
|
|
96
|
+
to_complex(disk.circle),
|
|
97
|
+
)
|
|
98
|
+
in_points = to_real(in_points)
|
|
99
|
+
out_points = to_real(out_points)
|
|
100
|
+
|
|
101
|
+
bound_points = in_points[: disk.circle_num]
|
|
102
|
+
in_points = in_points[disk.circle_num :]
|
|
103
|
+
return bound_points, in_points, out_points, mapping
|