openmlcore 1.2.0__tar.gz → 2.0.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.
- {openmlcore-1.2.0 → openmlcore-2.0.1}/PKG-INFO +6 -8
- {openmlcore-1.2.0 → openmlcore-2.0.1}/README.md +5 -7
- {openmlcore-1.2.0 → openmlcore-2.0.1}/pyproject.toml +1 -1
- openmlcore-2.0.1/src/openmlcore/base/data3d.py +174 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/loss/loss.py +14 -4
- openmlcore-2.0.1/src/openmlcore/loss/loss3d.py +95 -0
- openmlcore-2.0.1/src/openmlcore/metrics/metrics3d.py +119 -0
- openmlcore-2.0.1/src/openmlcore/models/lib/tnet.py +43 -0
- openmlcore-2.0.1/src/openmlcore/models/pointNetPP.py +209 -0
- openmlcore-2.0.1/src/openmlcore/models/pointnet.py +62 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore.egg-info/PKG-INFO +6 -8
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore.egg-info/SOURCES.txt +6 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/setup.cfg +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/__init__.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/base/data.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/metrics/metrics.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/MobileUnetR.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/UNets.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/attUnets.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/base_model.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/lib/attention.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/lib/convolutions.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/segformer.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore/models/unetpp.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore.egg-info/dependency_links.txt +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore.egg-info/requires.txt +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/src/openmlcore.egg-info/top_level.txt +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/tests/test_base_model.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/tests/test_loss.py +0 -0
- {openmlcore-1.2.0 → openmlcore-2.0.1}/tests/test_main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openmlcore
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: OpenMLCore is an open source framework (wrapper which simplify the use of PyTorch)
|
|
5
5
|
Author-email: Doyez Sébastien <doyez.sebastien34090@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://sebastien-doyez2812.github.io/openMLCore.github.io/
|
|
@@ -25,23 +25,21 @@ Today's computer vision ecosystem is heavily fragmented. If you want YOLO, Ultra
|
|
|
25
25
|
OpenML bridges this gap by bringing a clean, Hugging Face-inspired Developer Experience (DX) to advanced computer vision tasks—from classical models to SOTA transformers and parameter-efficient fine-tuning.
|
|
26
26
|
## Supported Architectures:
|
|
27
27
|
|
|
28
|
-
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet
|
|
28
|
+
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
|
|
29
29
|
|
|
30
|
-
- Advanced Transformers:
|
|
30
|
+
- Advanced Transformers: MobileUnetR, SegFormer
|
|
31
31
|
|
|
32
|
-
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA)
|
|
32
|
+
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA) (Coming soon)
|
|
33
33
|
## Key features:
|
|
34
34
|
|
|
35
35
|
Unified Object-Oriented API: Every model inherits from a clean ModelBase class supporting standard .train(), .eval(), and .help() workflows.
|
|
36
36
|
|
|
37
|
-
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM.
|
|
37
|
+
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM. (Coming soonn)
|
|
38
38
|
|
|
39
|
-
Hugging Face
|
|
39
|
+
Hugging Face pretrained weights availablle
|
|
40
40
|
|
|
41
41
|
MLflow Tracking Out-of-the-Box: Keep tabs on your losses, metrics, and hyperparameter logs without writing boilerplate monitoring code.
|
|
42
42
|
|
|
43
|
-
Interactive Help System: Running model.help() directly opens up the exact architectural documentation page on the web.
|
|
44
|
-
|
|
45
43
|
## Contributions:
|
|
46
44
|
|
|
47
45
|
Contributions, feature requests, and bug reports are welcome! Feel free to open an Issue or submit a Pull Request on GitHub.
|
|
@@ -7,23 +7,21 @@ Today's computer vision ecosystem is heavily fragmented. If you want YOLO, Ultra
|
|
|
7
7
|
OpenML bridges this gap by bringing a clean, Hugging Face-inspired Developer Experience (DX) to advanced computer vision tasks—from classical models to SOTA transformers and parameter-efficient fine-tuning.
|
|
8
8
|
## Supported Architectures:
|
|
9
9
|
|
|
10
|
-
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet
|
|
10
|
+
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
|
|
11
11
|
|
|
12
|
-
- Advanced Transformers:
|
|
12
|
+
- Advanced Transformers: MobileUnetR, SegFormer
|
|
13
13
|
|
|
14
|
-
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA)
|
|
14
|
+
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA) (Coming soon)
|
|
15
15
|
## Key features:
|
|
16
16
|
|
|
17
17
|
Unified Object-Oriented API: Every model inherits from a clean ModelBase class supporting standard .train(), .eval(), and .help() workflows.
|
|
18
18
|
|
|
19
|
-
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM.
|
|
19
|
+
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM. (Coming soonn)
|
|
20
20
|
|
|
21
|
-
Hugging Face
|
|
21
|
+
Hugging Face pretrained weights availablle
|
|
22
22
|
|
|
23
23
|
MLflow Tracking Out-of-the-Box: Keep tabs on your losses, metrics, and hyperparameter logs without writing boilerplate monitoring code.
|
|
24
24
|
|
|
25
|
-
Interactive Help System: Running model.help() directly opens up the exact architectural documentation page on the web.
|
|
26
|
-
|
|
27
25
|
## Contributions:
|
|
28
26
|
|
|
29
27
|
Contributions, feature requests, and bug reports are welcome! Feel free to open an Issue or submit a Pull Request on GitHub.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Seb Doyez
|
|
3
|
+
|
|
4
|
+
3D dataset should be a folder, where inside we can find subfolder corresponding to a scene
|
|
5
|
+
1 scene = 1 subfolder
|
|
6
|
+
Inside a subfolder, there qre .pcd and ..._label.txt
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
import torch.nn as nn
|
|
12
|
+
from torch.utils.data import Dataset
|
|
13
|
+
import numpy as np
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
class Segmentation3dDataset(Dataset):
|
|
17
|
+
def __init__(self, dataset_path, num_points = 2048, split="train"):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.num_points = num_points
|
|
20
|
+
self.dataset_path = dataset_path
|
|
21
|
+
|
|
22
|
+
self.scene_dirs = self._get_object_paths(self.dataset_path) # Only treat the airplane!
|
|
23
|
+
total_data = len(self.scene_dirs)
|
|
24
|
+
split_idx = int(total_data * 0.8)
|
|
25
|
+
if split.lower() == "train":
|
|
26
|
+
self.scene_dirs = self.scene_dirs[:split_idx]
|
|
27
|
+
else:
|
|
28
|
+
self.scene_dirs = self.scene_dirs[split_idx:]
|
|
29
|
+
|
|
30
|
+
def _get_object_paths(self, dataset_path):
|
|
31
|
+
# Dataset should be like:
|
|
32
|
+
# subfolders, and inside, you will find a .pcd and the _label.txt
|
|
33
|
+
# .pcd is the point cloud and _label.txt is the label
|
|
34
|
+
# Label goes from 1 to n ( number of classes)
|
|
35
|
+
|
|
36
|
+
object_paths = []
|
|
37
|
+
for dirpath, dirnames, filenames in os.walk(dataset_path):
|
|
38
|
+
for file in filenames:
|
|
39
|
+
if file.endswith(".pcd"):
|
|
40
|
+
base_path = os.path.join(dirpath, file.replace(".pcd", ""))
|
|
41
|
+
if os.path.exists(base_path +"_label.txt"):
|
|
42
|
+
object_paths.append(base_path)
|
|
43
|
+
return object_paths
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _load_scene_data(self, scene_base_path):
|
|
47
|
+
label_path = scene_base_path + "_label.txt"
|
|
48
|
+
pcd_path = scene_base_path + ".pcd"
|
|
49
|
+
|
|
50
|
+
if not os.path.exists(label_path):
|
|
51
|
+
raise FileNotFoundError(f"No labels found for {label_path}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if not os.path.exists(pcd_path):
|
|
55
|
+
raise FileNotFoundError(f"No PCD found for {pcd_path}")
|
|
56
|
+
|
|
57
|
+
with open(pcd_path, 'rb') as f:
|
|
58
|
+
header = []
|
|
59
|
+
data_type = 'ascii'
|
|
60
|
+
num_pts = 0
|
|
61
|
+
fields = []
|
|
62
|
+
sizes = []
|
|
63
|
+
types = []
|
|
64
|
+
|
|
65
|
+
# Skip the header
|
|
66
|
+
while True:
|
|
67
|
+
line_bytes = f.readline()
|
|
68
|
+
if not line_bytes:
|
|
69
|
+
break
|
|
70
|
+
line = line_bytes.decode('latin-1')
|
|
71
|
+
header.append(line)
|
|
72
|
+
if line.startswith("DATA"):
|
|
73
|
+
parts = line.strip().split()
|
|
74
|
+
if len(parts) > 1:
|
|
75
|
+
data_type = parts[1]
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
for line in header:
|
|
79
|
+
if line.startswith("POINTS"):
|
|
80
|
+
num_pts = int(line.strip().split()[1])
|
|
81
|
+
elif line.startswith("WIDTH") and num_pts == 0:
|
|
82
|
+
num_pts = int(line.strip().split()[1])
|
|
83
|
+
elif line.startswith("FIELDS"):
|
|
84
|
+
fields = line.strip().split()[1:]
|
|
85
|
+
elif line.startswith("SIZE"):
|
|
86
|
+
sizes = [int(s) for s in line.strip().split()[1:]]
|
|
87
|
+
elif line.startswith("TYPE"):
|
|
88
|
+
types = line.strip().split()[1:]
|
|
89
|
+
|
|
90
|
+
# Loading the data:
|
|
91
|
+
if data_type.lower() == 'ascii':
|
|
92
|
+
points_data = np.loadtxt(f, encoding="latin-1")
|
|
93
|
+
points = points_data[:, :3]
|
|
94
|
+
else:
|
|
95
|
+
binary_data = f.read()
|
|
96
|
+
try:
|
|
97
|
+
dt_list = []
|
|
98
|
+
for field, size, t in zip(fields, sizes, types):
|
|
99
|
+
if t == 'F':
|
|
100
|
+
np_t = np.float32 if size == 4 else np.float64
|
|
101
|
+
elif t in ['I', 'U']:
|
|
102
|
+
np_t = np.int32 if size == 4 else (np.int16 if size == 2 else np.int8)
|
|
103
|
+
else:
|
|
104
|
+
np_t = np.float32
|
|
105
|
+
dt_list.append((field, np_t))
|
|
106
|
+
|
|
107
|
+
dt = np.dtype(dt_list)
|
|
108
|
+
data = np.frombuffer(binary_data, dtype=dt, count=num_pts)
|
|
109
|
+
points = np.stack([data['x'], data['y'], data['z']], axis=-1)
|
|
110
|
+
except Exception:
|
|
111
|
+
raw_data = np.frombuffer(binary_data, dtype=np.float32)
|
|
112
|
+
stride = len(raw_data) // num_pts
|
|
113
|
+
points = raw_data.reshape(num_pts, stride)[:, :3]
|
|
114
|
+
|
|
115
|
+
# Loading the labels
|
|
116
|
+
labels = np.loadtxt(label_path, dtype=np.int64, encoding="latin-1")
|
|
117
|
+
|
|
118
|
+
assert len(points) == len(labels), f"[-] Error, number of points mismatch: {len(points)} pts vs {len(labels)} labels."
|
|
119
|
+
|
|
120
|
+
return points, labels
|
|
121
|
+
|
|
122
|
+
def _normalize_point_cloud(self, points):
|
|
123
|
+
centroid = np.mean(points, axis=0)
|
|
124
|
+
points_centered = points - centroid
|
|
125
|
+
|
|
126
|
+
max_dist = np.max(np.sqrt(np.sum(points_centered ** 2, axis=1)))
|
|
127
|
+
|
|
128
|
+
if max_dist > 0:
|
|
129
|
+
points_normalized = points_centered / max_dist
|
|
130
|
+
else:
|
|
131
|
+
points_normalized = points_centered
|
|
132
|
+
|
|
133
|
+
return points_normalized
|
|
134
|
+
|
|
135
|
+
def __len__(self):
|
|
136
|
+
return len(self.scene_dirs)
|
|
137
|
+
|
|
138
|
+
def __getitem__(self, idx):
|
|
139
|
+
scene_path = self.scene_dirs[idx]
|
|
140
|
+
points, labels = self._load_scene_data(scene_path)
|
|
141
|
+
|
|
142
|
+
totals_pts = points.shape[0]
|
|
143
|
+
if totals_pts >= self.num_points:
|
|
144
|
+
# Block_sampling:
|
|
145
|
+
center_idx = np.random.choice(points.shape[0])
|
|
146
|
+
center = points[center_idx]
|
|
147
|
+
|
|
148
|
+
radius = 1.5
|
|
149
|
+
distances = np.sqrt(np.sum((points - center) ** 2, axis = 1))
|
|
150
|
+
|
|
151
|
+
mask = distances < radius
|
|
152
|
+
block_points = points[mask]
|
|
153
|
+
block_labels = labels[mask]
|
|
154
|
+
|
|
155
|
+
total_pts = block_points.shape[0]
|
|
156
|
+
if total_pts >= self.num_points:
|
|
157
|
+
choice = np.random.choice(total_pts, self.num_points, replace=False)
|
|
158
|
+
else:
|
|
159
|
+
choice = np.random.choice(total_pts, self.num_points, replace=True)
|
|
160
|
+
|
|
161
|
+
points_sampled = block_points[choice]
|
|
162
|
+
labels_sampled = block_labels[choice]
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
choice = np.random.choice(totals_pts, self.num_points, replace=True)
|
|
166
|
+
points_sampled = points[choice]
|
|
167
|
+
labels_sampled = labels[choice]
|
|
168
|
+
|
|
169
|
+
points_normalized = self._normalize_point_cloud(points_sampled)
|
|
170
|
+
points_tensor = torch.tensor(points_normalized, dtype=torch.float32).transpose(1, 0)
|
|
171
|
+
labels_tensor = torch.tensor(labels_sampled - 1, dtype=torch.long)
|
|
172
|
+
|
|
173
|
+
return points_tensor, labels_tensor
|
|
174
|
+
|
|
@@ -7,12 +7,16 @@ class BaseLoss(nn.Module):
|
|
|
7
7
|
def __init__(self, name: str = "BaseLoss", doc_url: str = ""):
|
|
8
8
|
super().__init__()
|
|
9
9
|
self.name = name
|
|
10
|
+
self.type = "segmetation2d"
|
|
10
11
|
self.smooth = 1e-6
|
|
11
12
|
self.doc_url = doc_url
|
|
12
13
|
|
|
13
14
|
def forward(self, outputs: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
|
|
14
15
|
raise NotImplementedError("Subclasses should implement this method.")
|
|
15
16
|
|
|
17
|
+
def get_type(self):
|
|
18
|
+
return self.type
|
|
19
|
+
|
|
16
20
|
def documentation(self) -> str:
|
|
17
21
|
print(f"Documentation for {self.name} is available here:\n{self.doc_url}")
|
|
18
22
|
try:
|
|
@@ -22,9 +26,9 @@ class BaseLoss(nn.Module):
|
|
|
22
26
|
print(f"[-] {e}")
|
|
23
27
|
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
# Class Loss Functions
|
|
27
|
-
|
|
29
|
+
################################################
|
|
30
|
+
# Class Loss Functions for 2d Segmentation #
|
|
31
|
+
################################################
|
|
28
32
|
|
|
29
33
|
class BCEWithLogitsLoss(BaseLoss):
|
|
30
34
|
def __init__(self, weights=None):
|
|
@@ -189,4 +193,10 @@ class CustomLoss(BaseLoss):
|
|
|
189
193
|
for loss_fn, coef in zip(self.loss_fcns, self.coefficients):
|
|
190
194
|
total_loss += coef * loss_fn(outputs, targets)
|
|
191
195
|
return total_loss
|
|
192
|
-
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
################################################
|
|
201
|
+
# Class Loss Functions for 3d Segmentation #
|
|
202
|
+
################################################
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
|
|
5
|
+
class Base3dLoss(nn.Module):
|
|
6
|
+
def __init__(self, name: str= "BaseLoss3d", doc_url:str= ""):
|
|
7
|
+
super().__init__()
|
|
8
|
+
self.name = name
|
|
9
|
+
self.type = "segmentation3d"
|
|
10
|
+
self.smooth = 1e-6
|
|
11
|
+
self.doc_url = doc_url
|
|
12
|
+
|
|
13
|
+
def get_type(self):
|
|
14
|
+
return self.type
|
|
15
|
+
|
|
16
|
+
def forward(self, outputs: torch.Tensor, targets: torch.Tensor):
|
|
17
|
+
raise NotImplementedError("Subclasses should implement this method.")
|
|
18
|
+
|
|
19
|
+
def documentation(self) -> str:
|
|
20
|
+
print(f"Documentation available here:{self.doc_url}")
|
|
21
|
+
try:
|
|
22
|
+
import webbrowser
|
|
23
|
+
webbrowser.open(self.doc_url)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"[-] {e}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CustomLoss3d(Base3dLoss):
|
|
29
|
+
def __init__(self, loss_fcns: list[Base3dLoss], name: str, coefficients: list[float]):
|
|
30
|
+
assert len(loss_fcns) == len(coefficients), "The number of loss functions must match the number of coefficients."
|
|
31
|
+
super().__init__(name=name, doc_url=None)
|
|
32
|
+
self.loss_fcns = nn.ModuleList(loss_fcns)
|
|
33
|
+
self.coefficients = coefficients
|
|
34
|
+
|
|
35
|
+
def forward(self, outputs: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
|
|
36
|
+
total_loss = 0.0
|
|
37
|
+
for loss_fn, coef in zip(self.loss_fcns, self.coefficients):
|
|
38
|
+
total_loss += coef * loss_fn(outputs, targets)
|
|
39
|
+
return total_loss
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CrossEntropyLoss(Base3dLoss):
|
|
45
|
+
def __init__(self, name = "CrossEntropy", weights = None, doc_url = ""):
|
|
46
|
+
super().__init__(name, doc_url)
|
|
47
|
+
self.weights = weights
|
|
48
|
+
|
|
49
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
50
|
+
|
|
51
|
+
pos_weights = None
|
|
52
|
+
if weights is not None:
|
|
53
|
+
if not isinstance(weights, torch.Tensor):
|
|
54
|
+
pos_weights = torch.tensor(weights, device=device, dtype=torch.float32)
|
|
55
|
+
else:
|
|
56
|
+
pos_weights = weights.to(device)
|
|
57
|
+
|
|
58
|
+
self.loss_fn = nn.CrossEntropyLoss(weight=pos_weights)
|
|
59
|
+
|
|
60
|
+
def forward(self, outputs: torch.Tensor, targets: torch.Tensor):
|
|
61
|
+
return self.loss_fn(outputs, targets.long())
|
|
62
|
+
|
|
63
|
+
class DiceLoss3d(Base3dLoss):
|
|
64
|
+
def __init__(self, weights = None):
|
|
65
|
+
super().__init__(name="DiceLoss3d")
|
|
66
|
+
if weights is None:
|
|
67
|
+
raise ValueError("weights need to be set!")
|
|
68
|
+
|
|
69
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
70
|
+
|
|
71
|
+
if not isinstance(weights, torch.Tensor):
|
|
72
|
+
self.weights = torch.tensor(weights, device=device, dtype=torch.float32)
|
|
73
|
+
else:
|
|
74
|
+
self.weights = weights.to(device)
|
|
75
|
+
|
|
76
|
+
def forward(self, outputs, targets):
|
|
77
|
+
probs = F.softmax(outputs, dim=1)
|
|
78
|
+
B, C, N = probs.shape
|
|
79
|
+
|
|
80
|
+
if targets.min() < -1 or targets.max() > C:
|
|
81
|
+
raise ValueError("target at -1")
|
|
82
|
+
|
|
83
|
+
targets_one_hot = F.one_hot(targets.long(), num_classes=C)
|
|
84
|
+
targets_one_hot = targets_one_hot.permute(0, 2, 1).float()
|
|
85
|
+
|
|
86
|
+
intersection = torch.sum(probs * targets_one_hot, dim=(0, 2))
|
|
87
|
+
union = torch.sum(probs + targets_one_hot, dim=(0, 2))
|
|
88
|
+
|
|
89
|
+
dice_score = (2.0 * intersection) / (self.smooth + union)
|
|
90
|
+
|
|
91
|
+
dice_loss = 0
|
|
92
|
+
for idx_class, weight in enumerate(self.weights):
|
|
93
|
+
dice_loss += weight * (1 - dice_score[idx_class])
|
|
94
|
+
|
|
95
|
+
return dice_loss
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
def iou3d(preds, targets):
|
|
5
|
+
num_batches = preds.shape[0]
|
|
6
|
+
num_classes = preds.shape[1]
|
|
7
|
+
|
|
8
|
+
metric_to_return = [0.0] * num_classes
|
|
9
|
+
valid_batches_per_class = [0] * num_classes
|
|
10
|
+
|
|
11
|
+
for idx_current_batch in range(num_batches):
|
|
12
|
+
for idx_class in range(num_classes):
|
|
13
|
+
pred = preds[idx_current_batch, idx_class]
|
|
14
|
+
gt = targets[idx_current_batch, idx_class]
|
|
15
|
+
|
|
16
|
+
pred_mask = (pred > 0.5)
|
|
17
|
+
gt_mask = (gt > 0.5)
|
|
18
|
+
|
|
19
|
+
intersection = torch.logical_and(pred_mask, gt_mask).sum().item()
|
|
20
|
+
union = torch.logical_or(pred_mask, gt_mask).sum().item()
|
|
21
|
+
|
|
22
|
+
if union > 0:
|
|
23
|
+
iou = intersection / union
|
|
24
|
+
metric_to_return[idx_class] += iou
|
|
25
|
+
valid_batches_per_class[idx_class] += 1
|
|
26
|
+
|
|
27
|
+
final_metric = []
|
|
28
|
+
for i in range(num_classes):
|
|
29
|
+
if valid_batches_per_class[i] > 0:
|
|
30
|
+
final_metric.append(metric_to_return[i] / valid_batches_per_class[i])
|
|
31
|
+
else:
|
|
32
|
+
final_metric.append(float('nan'))
|
|
33
|
+
|
|
34
|
+
return final_metric
|
|
35
|
+
|
|
36
|
+
def accuracy3d(preds, targets):
|
|
37
|
+
num_batches = preds.shape[0]
|
|
38
|
+
num_classes = preds.shape[1]
|
|
39
|
+
|
|
40
|
+
metrics_to_return = [0.0] * num_classes
|
|
41
|
+
valid_batches_per_class = [0] * num_classes
|
|
42
|
+
|
|
43
|
+
for idx_current_batch in range(num_batches):
|
|
44
|
+
pred_batch = preds[idx_current_batch]
|
|
45
|
+
|
|
46
|
+
if targets.ndim == preds.ndim:
|
|
47
|
+
target_batch = targets[idx_current_batch] # Shape: (C, ...)
|
|
48
|
+
else:
|
|
49
|
+
target_indices = targets[idx_current_batch] # Shape: (...)
|
|
50
|
+
|
|
51
|
+
for idx_class in range(num_classes):
|
|
52
|
+
pred = pred_batch[idx_class]
|
|
53
|
+
pred_mask = (pred > 0.5)
|
|
54
|
+
|
|
55
|
+
if targets.ndim == preds.ndim:
|
|
56
|
+
gt_mask = (target_batch[idx_class] > 0.5)
|
|
57
|
+
else:
|
|
58
|
+
gt_mask = (target_indices == idx_class)
|
|
59
|
+
|
|
60
|
+
correct = torch.sum(pred_mask == gt_mask).item()
|
|
61
|
+
total = gt_mask.numel()
|
|
62
|
+
|
|
63
|
+
if total > 0:
|
|
64
|
+
current_acc = correct / total
|
|
65
|
+
metrics_to_return[idx_class] += current_acc
|
|
66
|
+
valid_batches_per_class[idx_class] += 1
|
|
67
|
+
|
|
68
|
+
final_metric = []
|
|
69
|
+
for i in range(num_classes):
|
|
70
|
+
if valid_batches_per_class[i] > 0:
|
|
71
|
+
final_metric.append(metrics_to_return[i] / valid_batches_per_class[i])
|
|
72
|
+
else:
|
|
73
|
+
final_metric.append(float('nan'))
|
|
74
|
+
|
|
75
|
+
return final_metric
|
|
76
|
+
|
|
77
|
+
def Precision3d(preds, labels, nb_classes):
|
|
78
|
+
pred_flatten = preds.flatten()
|
|
79
|
+
label_flatten = labels.flatten()
|
|
80
|
+
|
|
81
|
+
precision_tab = []
|
|
82
|
+
for idx_class in range(1, nb_classes+1):
|
|
83
|
+
TP = np.sum((pred_flatten == idx_class) & (label_flatten == idx_class))
|
|
84
|
+
FP = np.sum((pred_flatten == idx_class) & (label_flatten != idx_class))
|
|
85
|
+
if (TP + FP) == 0:
|
|
86
|
+
precision_tab.append(0.0)
|
|
87
|
+
else:
|
|
88
|
+
precision_tab.append(TP/(TP+FP))
|
|
89
|
+
|
|
90
|
+
return precision_tab
|
|
91
|
+
|
|
92
|
+
def Recall3d(preds, labels, nb_classes):
|
|
93
|
+
pred_flatten = preds.flatten()
|
|
94
|
+
label_flatten = labels.flatten()
|
|
95
|
+
|
|
96
|
+
precision_tab = []
|
|
97
|
+
for idx_class in range(1, nb_classes+1):
|
|
98
|
+
TP = np.sum((pred_flatten == idx_class) & (label_flatten == idx_class))
|
|
99
|
+
FN = np.sum((pred_flatten != idx_class) & (label_flatten == idx_class))
|
|
100
|
+
if (TP + FN) == 0:
|
|
101
|
+
precision_tab.append(0.0)
|
|
102
|
+
else:
|
|
103
|
+
precision_tab.append(TP/(TP+FN))
|
|
104
|
+
|
|
105
|
+
return precision_tab
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def F13d(preds, labels, nb_classes):
|
|
109
|
+
current_prec = Precision3d(preds, labels, nb_classes)
|
|
110
|
+
current_recall = Recall3d(preds, labels, nb_classes)
|
|
111
|
+
|
|
112
|
+
f1_scores = []
|
|
113
|
+
for idx_class in range(nb_classes):
|
|
114
|
+
if current_prec[idx_class] + current_recall[idx_class] == 0:
|
|
115
|
+
f1_scores.append(0.0)
|
|
116
|
+
else:
|
|
117
|
+
f1_scores.appennd(2* (current_prec[idx_class] * current_recall[idx_class])/ (current_prec[idx_class] + current_recall[idx_class]))
|
|
118
|
+
|
|
119
|
+
return f1_scores
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
import torch.nn.functional as F
|
|
4
|
+
|
|
5
|
+
class Tnet(nn.Module):
|
|
6
|
+
def __init__(self, input_dim, output_dim):
|
|
7
|
+
super(Tnet, self).__init__()
|
|
8
|
+
|
|
9
|
+
self.input_dim = input_dim
|
|
10
|
+
self.output_dim = output_dim
|
|
11
|
+
|
|
12
|
+
# Shared MLP layer = con1d
|
|
13
|
+
self.conv1 = nn.Conv1d(self.input_dim, 64, 1)
|
|
14
|
+
self.conv2 = nn.Conv1d(64, 128, 1)
|
|
15
|
+
self.conv3 = nn.Conv1d(128, 256, 1)
|
|
16
|
+
|
|
17
|
+
self.batch_norm1 = nn.BatchNorm1d(64)
|
|
18
|
+
self.batch_norm2 = nn.BatchNorm1d(128)
|
|
19
|
+
self.batch_norm3 = nn.BatchNorm1d(256)
|
|
20
|
+
self.batch_norm4 = nn.BatchNorm1d(128)
|
|
21
|
+
self.batch_norm5 = nn.BatchNorm1d(64)
|
|
22
|
+
|
|
23
|
+
self.fc1 = nn.Linear(256, 128)
|
|
24
|
+
self.fc2 = nn.Linear(128, 64)
|
|
25
|
+
self.fc3 = nn.Linear(64, self.output_dim * self.output_dim)
|
|
26
|
+
|
|
27
|
+
def forward(self, x):
|
|
28
|
+
batch_size = x.shape[0]
|
|
29
|
+
|
|
30
|
+
x = F.relu(self.batch_norm1(self.conv1(x)))
|
|
31
|
+
x = F.relu(self.batch_norm2(self.conv2(x)))
|
|
32
|
+
x = F.relu(self.batch_norm3(self.conv3(x)))
|
|
33
|
+
|
|
34
|
+
x = torch.max(x, dim=2)[0]
|
|
35
|
+
x = x.view(-1, 256)
|
|
36
|
+
|
|
37
|
+
x = F.relu(self.batch_norm4(self.fc1(x)))
|
|
38
|
+
x = F.relu(self.batch_norm5(self.fc2(x)))
|
|
39
|
+
x = self.fc3(x)
|
|
40
|
+
|
|
41
|
+
identity_matrix = torch.eye(self.output_dim, device=x.device).unsqueeze(0).repeat(batch_size, 1, 1) # (B, output_dim, output_dim)
|
|
42
|
+
x = x.view(-1, self.output_dim, self.output_dim) + identity_matrix
|
|
43
|
+
return x
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# PointNet++
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
import torch.nn.functional as F
|
|
6
|
+
from .base_model import BaseModel
|
|
7
|
+
|
|
8
|
+
def furthest_point_sample(xyz, npoint):
|
|
9
|
+
device = xyz.device
|
|
10
|
+
B, N, C = xyz.shape
|
|
11
|
+
|
|
12
|
+
centroids = torch.zeros(B, npoint, dtype=torch.long, device=device)
|
|
13
|
+
distance = torch.ones(B, N, device=device) * 1e10
|
|
14
|
+
farthest = torch.randint(0, N, (B,), dtype=torch.long, device=device)
|
|
15
|
+
batch_indices = torch.arange(B, dtype=torch.long, device=device)
|
|
16
|
+
|
|
17
|
+
for i in range(npoint):
|
|
18
|
+
centroids[:, i] = farthest
|
|
19
|
+
centroid = xyz[batch_indices, farthest, :].view(B, 1, 3)
|
|
20
|
+
dist = torch.sum((xyz - centroid) ** 2, dim=-1)
|
|
21
|
+
mask = dist < distance
|
|
22
|
+
distance = torch.where(mask, dist, distance)
|
|
23
|
+
farthest = torch.max(distance, dim=-1)[1]
|
|
24
|
+
|
|
25
|
+
return centroids
|
|
26
|
+
|
|
27
|
+
def query_ball_points(radius, nsample, xyz, new_xyz):
|
|
28
|
+
device = xyz.device
|
|
29
|
+
B, N, C = xyz.shape
|
|
30
|
+
_, S, _ = new_xyz.shape
|
|
31
|
+
group_idx = torch.arange(N, dtype=torch.long, device=device).view(1, 1, N).repeat([B, S, 1])
|
|
32
|
+
|
|
33
|
+
sqrdists = square_distance(new_xyz, xyz)
|
|
34
|
+
mask = sqrdists > radius ** 2
|
|
35
|
+
group_idx = torch.where(mask, torch.tensor(N, device=device, dtype=torch.long), group_idx)
|
|
36
|
+
|
|
37
|
+
group_idx = group_idx.sort(dim=-1)[0][:, :, :nsample]
|
|
38
|
+
|
|
39
|
+
group_first = group_idx[:, :, 0].view(B, S, 1).repeat([1, 1, nsample])
|
|
40
|
+
mask = group_idx == N
|
|
41
|
+
group_idx = torch.where(mask, group_first, group_idx)
|
|
42
|
+
|
|
43
|
+
return group_idx
|
|
44
|
+
|
|
45
|
+
def square_distance(src, dst):
|
|
46
|
+
B, N, _ = src.shape
|
|
47
|
+
_, M, _ = dst.shape
|
|
48
|
+
|
|
49
|
+
dist = -2 * torch.matmul(src, dst.permute(0, 2, 1))
|
|
50
|
+
head = torch.sum(src ** 2, dim= -1, keepdim=True)
|
|
51
|
+
tail = torch.sum(dst ** 2, dim=-1, keepdim=True).permute(0, 2, 1)
|
|
52
|
+
|
|
53
|
+
return dist + head + tail
|
|
54
|
+
|
|
55
|
+
def index_points(points, idx):
|
|
56
|
+
device = points.device
|
|
57
|
+
B = points.shape[0]
|
|
58
|
+
|
|
59
|
+
view_shape = list(idx.shape)
|
|
60
|
+
view_shape[1:] = [1] * (len(view_shape) - 1)
|
|
61
|
+
repeat_shape = list(idx.shape)
|
|
62
|
+
repeat_shape[0] = 1
|
|
63
|
+
|
|
64
|
+
batch_indices = torch.arange(B, dtype=torch.long, device=device).view(view_shape).repeat(repeat_shape)
|
|
65
|
+
new_points = points[batch_indices, idx, :]
|
|
66
|
+
return new_points
|
|
67
|
+
|
|
68
|
+
class SetAbstraction(nn.Module):
|
|
69
|
+
def __init__(self, npoints, radius, nsample, in_channel, mlp ):
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.npoints = npoints
|
|
72
|
+
self.radius = radius
|
|
73
|
+
self.nsample = nsample
|
|
74
|
+
self.inchannel = in_channel
|
|
75
|
+
|
|
76
|
+
self.mlp_convs = nn.ModuleList()
|
|
77
|
+
self.mlp_bns = nn.ModuleList()
|
|
78
|
+
last_channel = 3 if in_channel == 3 else in_channel + 3
|
|
79
|
+
|
|
80
|
+
for out_channel in mlp:
|
|
81
|
+
self.mlp_convs.append(nn.Conv2d(last_channel, out_channel, 1))
|
|
82
|
+
self.mlp_bns.append(nn.BatchNorm2d(out_channel))
|
|
83
|
+
last_channel = out_channel
|
|
84
|
+
|
|
85
|
+
def forward(self, xyz, points):
|
|
86
|
+
centroid_idx = furthest_point_sample(xyz, self.npoints)
|
|
87
|
+
new_xyz = index_points(xyz, centroid_idx)
|
|
88
|
+
|
|
89
|
+
group_idx = query_ball_points(self.radius, self.nsample, xyz, new_xyz)
|
|
90
|
+
grouped_xyz = index_points(xyz, group_idx)
|
|
91
|
+
grouped_xyz_norm = grouped_xyz - new_xyz.unsqueeze(2)
|
|
92
|
+
|
|
93
|
+
if points is not None:
|
|
94
|
+
grouped_points = index_points(points, group_idx)
|
|
95
|
+
new_points = torch.cat([grouped_xyz_norm, grouped_points], dim=-1)
|
|
96
|
+
else:
|
|
97
|
+
new_points = grouped_xyz_norm
|
|
98
|
+
|
|
99
|
+
new_points = new_points.permute(0, 3, 1, 2)
|
|
100
|
+
for conv, bn in zip(self.mlp_convs, self.mlp_bns):
|
|
101
|
+
new_points = F.relu(bn(conv(new_points)))
|
|
102
|
+
|
|
103
|
+
new_points = torch.max(new_points, dim=-1)[0]
|
|
104
|
+
new_points = new_points.permute(0, 2, 1)
|
|
105
|
+
return new_xyz, new_points
|
|
106
|
+
|
|
107
|
+
class FeaturePropagation(nn.Module):
|
|
108
|
+
def __init__(self, in_channel, mlp):
|
|
109
|
+
super().__init__()
|
|
110
|
+
self.mlp_convs = nn.ModuleList()
|
|
111
|
+
self.mlp_bns = nn.ModuleList()
|
|
112
|
+
|
|
113
|
+
last_channel = in_channel
|
|
114
|
+
for out_channel in mlp:
|
|
115
|
+
self.mlp_convs.append(nn.Conv1d(last_channel, out_channel, 1))
|
|
116
|
+
self.mlp_bns.append(nn.BatchNorm1d(out_channel))
|
|
117
|
+
last_channel = out_channel
|
|
118
|
+
|
|
119
|
+
def forward(self, xyz1, xyz2, points1, points2):
|
|
120
|
+
# xyz2 has the less points/ low resolution
|
|
121
|
+
# xyz1 has a higher resolution
|
|
122
|
+
# points1: skip connection
|
|
123
|
+
# points2: features of the previous layers, shape = B, S, C2
|
|
124
|
+
|
|
125
|
+
B, N, C = xyz1.shape
|
|
126
|
+
_, S, _ = xyz2.shape
|
|
127
|
+
|
|
128
|
+
if S == 1:
|
|
129
|
+
interpolated_points = points2.repeat(1, N, 1)
|
|
130
|
+
else:
|
|
131
|
+
# Search for the 3 closers points in xyz2
|
|
132
|
+
dists = square_distance(xyz1, xyz2) # (B, N, S)
|
|
133
|
+
dists, idx = torch.topk(dists, 3, dim=-1, largest=False, sorted=True)
|
|
134
|
+
|
|
135
|
+
weights = 1.0 / (dists + 1e-8)
|
|
136
|
+
weight = weights / torch.sum(weights, dim=-1, keepdim=True) #(B, N, 3)
|
|
137
|
+
|
|
138
|
+
interpolated_points = torch.sum(index_points(points2, idx) * weight.view(B, N, 3, 1), dim=2)
|
|
139
|
+
if points1 is not None:
|
|
140
|
+
new_points = torch.cat([points1, interpolated_points], dim=-1)
|
|
141
|
+
else:
|
|
142
|
+
new_points = interpolated_points
|
|
143
|
+
|
|
144
|
+
new_points = new_points.permute(0, 2, 1)
|
|
145
|
+
for conv, bn in zip(self.mlp_convs, self.mlp_bns):
|
|
146
|
+
new_points = F.relu(bn(conv(new_points)))
|
|
147
|
+
|
|
148
|
+
return new_points.permute(0, 2, 1)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class PointNetPPSemSegTorch(nn.Module):
|
|
152
|
+
def __init__(self, num_classes):
|
|
153
|
+
super().__init__()
|
|
154
|
+
|
|
155
|
+
# --- ENCODER (Set Abstraction ) ---
|
|
156
|
+
self.sa1 = SetAbstraction(npoints=1024, radius=0.1, nsample=32, in_channel=3, mlp=[32, 32, 64])
|
|
157
|
+
self.sa2 = SetAbstraction(npoints=256, radius=0.2, nsample=32, in_channel=64, mlp=[64, 64, 128])
|
|
158
|
+
self.sa3 = SetAbstraction(npoints=64, radius=0.4, nsample=32, in_channel=128, mlp=[128, 128, 256])
|
|
159
|
+
self.sa4 = SetAbstraction(npoints=16, radius=0.8, nsample=32, in_channel= 256, mlp=[256, 256, 512]) # Version sans features initiales directes (in_channel implicite)
|
|
160
|
+
|
|
161
|
+
# --- DECODER (Feature Propagation) ---
|
|
162
|
+
# Remonte progressivement de 16 -> 64 -> 256 -> 1024 -> N points d'origine
|
|
163
|
+
self.fp4 = FeaturePropagation(in_channel=512 + 256, mlp=[256, 256])
|
|
164
|
+
self.fp3 = FeaturePropagation(in_channel=256 + 128, mlp=[256, 256])
|
|
165
|
+
self.fp2 = FeaturePropagation(in_channel=256 + 64, mlp=[256, 128])
|
|
166
|
+
self.fp1 = FeaturePropagation(in_channel=128 + 3, mlp=[128, 128, 128])
|
|
167
|
+
|
|
168
|
+
# --- TÊTE DE CLASSIFICATION PAR POINT ---
|
|
169
|
+
self.conv1 = nn.Conv1d(128, 128, 1)
|
|
170
|
+
self.bn1 = nn.BatchNorm1d(128)
|
|
171
|
+
self.drop = nn.Dropout(0.5)
|
|
172
|
+
self.conv2 = nn.Conv1d(128, num_classes, 1)
|
|
173
|
+
|
|
174
|
+
def forward(self, xyz):
|
|
175
|
+
if xyz.shape[1] == 3:
|
|
176
|
+
xyz = xyz.permute(0, 2, 1)
|
|
177
|
+
|
|
178
|
+
l0_xyz = xyz
|
|
179
|
+
l0_points = None
|
|
180
|
+
|
|
181
|
+
# 1. Encoder
|
|
182
|
+
l1_xyz, l1_points = self.sa1(l0_xyz, l0_points)
|
|
183
|
+
l2_xyz, l2_points = self.sa2(l1_xyz, l1_points)
|
|
184
|
+
l3_xyz, l3_points = self.sa3(l2_xyz, l2_points)
|
|
185
|
+
l4_xyz, l4_points = self.sa4(l3_xyz, l3_points)
|
|
186
|
+
|
|
187
|
+
# 2. Décoder (Propagation of features + Skip Connections)
|
|
188
|
+
l3_points = self.fp4(l3_xyz, l4_xyz, l3_points, l4_points)
|
|
189
|
+
l2_points = self.fp3(l2_xyz, l3_xyz, l2_points, l3_points)
|
|
190
|
+
l1_points = self.fp2(l1_xyz, l2_xyz, l1_points, l2_points)
|
|
191
|
+
l0_points = self.fp1(l0_xyz, l1_xyz, l0_xyz, l1_points) # Skip to the origin cloud l0_xyz
|
|
192
|
+
|
|
193
|
+
# 3. Final classification
|
|
194
|
+
net = l0_points.permute(0, 2, 1) # (B, C, N) for Conv1d
|
|
195
|
+
net = F.relu(self.bn1(self.conv1(net)))
|
|
196
|
+
net = self.drop(net)
|
|
197
|
+
logits = self.conv2(net) # (B, num_classes, N)
|
|
198
|
+
|
|
199
|
+
return logits #.permute(0, 2, 1) # return (B, N, num_classes)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class PointNetPP(BaseModel):
|
|
203
|
+
def __init__(self, output_channels=1, **kwargs):
|
|
204
|
+
super().__init__(name="PointNet++", **kwargs)
|
|
205
|
+
self.output_channels = output_channels
|
|
206
|
+
|
|
207
|
+
def create_model(self):
|
|
208
|
+
self.model = PointNetPPSemSegTorch( num_classes= self.output_channels )
|
|
209
|
+
return self.model
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from .lib.tnet import Tnet
|
|
2
|
+
import torch
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
import torch.nn.functional as F
|
|
5
|
+
from .base_model import BaseModel
|
|
6
|
+
|
|
7
|
+
class PointNetTorch(nn.Module):
|
|
8
|
+
def __init__(self, input_dims= 3, output_dims=13):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.input_dims = input_dims
|
|
11
|
+
self.output_dims = output_dims
|
|
12
|
+
self.tnet1 = Tnet(3, 3)
|
|
13
|
+
self.tnet2 = Tnet(64, 64)
|
|
14
|
+
|
|
15
|
+
self.conv1 = nn.Conv1d(3, 64, kernel_size=1)
|
|
16
|
+
self.conv2 = nn.Conv1d(64, 1024, kernel_size=1)
|
|
17
|
+
self.conv3 = nn.Conv1d(1088, 128, kernel_size=1) # 1024 (global) + 64 (local)
|
|
18
|
+
self.conv4 = nn.Conv1d(128, self.output_dims, kernel_size=1)
|
|
19
|
+
|
|
20
|
+
self.bn1 = nn.BatchNorm1d(64)
|
|
21
|
+
self.bn2 = nn.BatchNorm1d(1024)
|
|
22
|
+
self.bn3 = nn.BatchNorm1d(128)
|
|
23
|
+
|
|
24
|
+
def forward(self, x):
|
|
25
|
+
batch_size, _, num_pts = x.shape
|
|
26
|
+
|
|
27
|
+
trans1 = self.tnet1(x) # (B, 3, 3)
|
|
28
|
+
skipped_x1 = x.transpose(2, 1) # (B, N, 3)
|
|
29
|
+
x = torch.bmm(skipped_x1, trans1) # (B, N, 3) x (B, 3, 3) = (B, N, 3)
|
|
30
|
+
x = x.transpose(2, 1) # (B, 3, N)
|
|
31
|
+
x = F.relu(self.bn1(self.conv1(x))) # (B, 64, N)
|
|
32
|
+
|
|
33
|
+
local_feat = x # (B, 64, N)
|
|
34
|
+
trans2 = self.tnet2(x) # (B, 64, 64)
|
|
35
|
+
skipped_x2 = x.transpose(2, 1) # (B, N, 64)
|
|
36
|
+
|
|
37
|
+
x = torch.bmm(skipped_x2, trans2) # (B, N, 64) x (B, 64, 64) = (B, N, 64)
|
|
38
|
+
x = x.transpose(2, 1) # (B, 64, N)
|
|
39
|
+
x = F.relu(self.bn2(self.conv2(x))) # (B, 1064, N)
|
|
40
|
+
|
|
41
|
+
# Max pool:
|
|
42
|
+
global_feat = torch.max(x, dim=2, keepdim=True) [0] # (B, 1024, 1)
|
|
43
|
+
global_feat = global_feat.repeat(1, 1, num_pts) # (B, 1024, N)
|
|
44
|
+
x = torch.cat([global_feat, local_feat], dim=1) # (B, 1088, N)
|
|
45
|
+
x = F.relu(self.bn3(self.conv3(x))) # (B, 128, N)
|
|
46
|
+
x = self.conv4(x) # (B, output_dims, N)
|
|
47
|
+
return x
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PointNet(BaseModel):
|
|
52
|
+
def __init__(self, input_channels=1, output_channels=1, **kwargs):
|
|
53
|
+
super().__init__(name="UNet", **kwargs)
|
|
54
|
+
self.input_channels = input_channels
|
|
55
|
+
self.output_channels = output_channels
|
|
56
|
+
|
|
57
|
+
def create_model(self):
|
|
58
|
+
self.model = PointNetTorch(
|
|
59
|
+
input_dims = self.input_channels,
|
|
60
|
+
output_dims= self.output_channels
|
|
61
|
+
)
|
|
62
|
+
return self.model
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openmlcore
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: OpenMLCore is an open source framework (wrapper which simplify the use of PyTorch)
|
|
5
5
|
Author-email: Doyez Sébastien <doyez.sebastien34090@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://sebastien-doyez2812.github.io/openMLCore.github.io/
|
|
@@ -25,23 +25,21 @@ Today's computer vision ecosystem is heavily fragmented. If you want YOLO, Ultra
|
|
|
25
25
|
OpenML bridges this gap by bringing a clean, Hugging Face-inspired Developer Experience (DX) to advanced computer vision tasks—from classical models to SOTA transformers and parameter-efficient fine-tuning.
|
|
26
26
|
## Supported Architectures:
|
|
27
27
|
|
|
28
|
-
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet
|
|
28
|
+
- Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
|
|
29
29
|
|
|
30
|
-
- Advanced Transformers:
|
|
30
|
+
- Advanced Transformers: MobileUnetR, SegFormer
|
|
31
31
|
|
|
32
|
-
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA)
|
|
32
|
+
- SOTA & Edge: Vision Transformers, SAM 2 adaptations via efficient Low-Rank Adaptation (LoRA) (Coming soon)
|
|
33
33
|
## Key features:
|
|
34
34
|
|
|
35
35
|
Unified Object-Oriented API: Every model inherits from a clean ModelBase class supporting standard .train(), .eval(), and .help() workflows.
|
|
36
36
|
|
|
37
|
-
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM.
|
|
37
|
+
Native LoRA Wrappers: Fine-tune massive vision models and transformers on modest hardware (like a single RTX 3070) without blowing up your VRAM. (Coming soonn)
|
|
38
38
|
|
|
39
|
-
Hugging Face
|
|
39
|
+
Hugging Face pretrained weights availablle
|
|
40
40
|
|
|
41
41
|
MLflow Tracking Out-of-the-Box: Keep tabs on your losses, metrics, and hyperparameter logs without writing boilerplate monitoring code.
|
|
42
42
|
|
|
43
|
-
Interactive Help System: Running model.help() directly opens up the exact architectural documentation page on the web.
|
|
44
|
-
|
|
45
43
|
## Contributions:
|
|
46
44
|
|
|
47
45
|
Contributions, feature requests, and bug reports are welcome! Feel free to open an Issue or submit a Pull Request on GitHub.
|
|
@@ -7,16 +7,22 @@ src/openmlcore.egg-info/dependency_links.txt
|
|
|
7
7
|
src/openmlcore.egg-info/requires.txt
|
|
8
8
|
src/openmlcore.egg-info/top_level.txt
|
|
9
9
|
src/openmlcore/base/data.py
|
|
10
|
+
src/openmlcore/base/data3d.py
|
|
10
11
|
src/openmlcore/loss/loss.py
|
|
12
|
+
src/openmlcore/loss/loss3d.py
|
|
11
13
|
src/openmlcore/metrics/metrics.py
|
|
14
|
+
src/openmlcore/metrics/metrics3d.py
|
|
12
15
|
src/openmlcore/models/MobileUnetR.py
|
|
13
16
|
src/openmlcore/models/UNets.py
|
|
14
17
|
src/openmlcore/models/attUnets.py
|
|
15
18
|
src/openmlcore/models/base_model.py
|
|
19
|
+
src/openmlcore/models/pointNetPP.py
|
|
20
|
+
src/openmlcore/models/pointnet.py
|
|
16
21
|
src/openmlcore/models/segformer.py
|
|
17
22
|
src/openmlcore/models/unetpp.py
|
|
18
23
|
src/openmlcore/models/lib/attention.py
|
|
19
24
|
src/openmlcore/models/lib/convolutions.py
|
|
25
|
+
src/openmlcore/models/lib/tnet.py
|
|
20
26
|
tests/test_base_model.py
|
|
21
27
|
tests/test_loss.py
|
|
22
28
|
tests/test_main.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|