openmlcore 1.2.0__tar.gz → 2.0.0__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.
Files changed (30) hide show
  1. {openmlcore-1.2.0 → openmlcore-2.0.0}/PKG-INFO +6 -8
  2. {openmlcore-1.2.0 → openmlcore-2.0.0}/README.md +5 -7
  3. {openmlcore-1.2.0 → openmlcore-2.0.0}/pyproject.toml +1 -1
  4. openmlcore-2.0.0/src/openmlcore/base/data3d.py +174 -0
  5. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/loss/loss.py +14 -4
  6. openmlcore-2.0.0/src/openmlcore/loss/loss3d.py +92 -0
  7. openmlcore-2.0.0/src/openmlcore/metrics/metrics3d.py +119 -0
  8. openmlcore-2.0.0/src/openmlcore/models/lib/tnet.py +43 -0
  9. openmlcore-2.0.0/src/openmlcore/models/pointNetPP.py +209 -0
  10. openmlcore-2.0.0/src/openmlcore/models/pointnet.py +62 -0
  11. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore.egg-info/PKG-INFO +6 -8
  12. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore.egg-info/SOURCES.txt +6 -0
  13. {openmlcore-1.2.0 → openmlcore-2.0.0}/setup.cfg +0 -0
  14. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/__init__.py +0 -0
  15. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/base/data.py +0 -0
  16. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/metrics/metrics.py +0 -0
  17. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/MobileUnetR.py +0 -0
  18. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/UNets.py +0 -0
  19. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/attUnets.py +0 -0
  20. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/base_model.py +0 -0
  21. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/lib/attention.py +0 -0
  22. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/lib/convolutions.py +0 -0
  23. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/segformer.py +0 -0
  24. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore/models/unetpp.py +0 -0
  25. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore.egg-info/dependency_links.txt +0 -0
  26. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore.egg-info/requires.txt +0 -0
  27. {openmlcore-1.2.0 → openmlcore-2.0.0}/src/openmlcore.egg-info/top_level.txt +0 -0
  28. {openmlcore-1.2.0 → openmlcore-2.0.0}/tests/test_base_model.py +0 -0
  29. {openmlcore-1.2.0 → openmlcore-2.0.0}/tests/test_loss.py +0 -0
  30. {openmlcore-1.2.0 → openmlcore-2.0.0}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openmlcore
3
- Version: 1.2.0
3
+ Version: 2.0.0
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++, Mask R-CNN
28
+ - Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
29
29
 
30
- - Advanced Transformers: Swin Transformer, TransUNet, MobileUNetR
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 Hub Integration: Push and pull checkpoints effortlessly using native .safetensors formatting.
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++, Mask R-CNN
10
+ - Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
11
11
 
12
- - Advanced Transformers: Swin Transformer, TransUNet, MobileUNetR
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 Hub Integration: Push and pull checkpoints effortlessly using native .safetensors formatting.
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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openmlcore"
7
- version = "1.2.0"
7
+ version = "2.0.0"
8
8
  authors = [
9
9
  { name="Doyez Sébastien", email="doyez.sebastien34090@gmail.com" },
10
10
  ]
@@ -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,92 @@
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
+ targets_one_hot = F.one_hot(targets.long(), num_classes=C)
81
+ targets_one_hot = targets_one_hot.permute(0, 2, 1).float()
82
+
83
+ intersection = torch.sum(probs * targets_one_hot, dim=(0, 2))
84
+ union = torch.sum(probs + targets_one_hot, dim=(0, 2))
85
+
86
+ dice_score = (2.0 * intersection) / (self.smooth + union)
87
+
88
+ dice_loss = 0
89
+ for idx_class, weight in enumerate(self.weights):
90
+ dice_loss += weight * (1 - dice_score[idx_class])
91
+
92
+ 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: 1.2.0
3
+ Version: 2.0.0
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++, Mask R-CNN
28
+ - Classic & Deep Segmentation: U-Net, Attention U-Net, UNet++
29
29
 
30
- - Advanced Transformers: Swin Transformer, TransUNet, MobileUNetR
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 Hub Integration: Push and pull checkpoints effortlessly using native .safetensors formatting.
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