py3dcal 1.0.5__py3-none-any.whl
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.
Potentially problematic release.
This version of py3dcal might be problematic. Click here for more details.
- py3DCal/__init__.py +14 -0
- py3DCal/data_collection/Calibrator.py +300 -0
- py3DCal/data_collection/__init__.py +0 -0
- py3DCal/data_collection/printers/Ender3/Ender3.py +82 -0
- py3DCal/data_collection/printers/Ender3/__init__.py +0 -0
- py3DCal/data_collection/printers/Printer.py +63 -0
- py3DCal/data_collection/printers/__init__.py +0 -0
- py3DCal/data_collection/sensors/DIGIT/DIGIT.py +47 -0
- py3DCal/data_collection/sensors/DIGIT/__init__.py +0 -0
- py3DCal/data_collection/sensors/DIGIT/default.csv +1222 -0
- py3DCal/data_collection/sensors/GelsightMini/GelsightMini.py +45 -0
- py3DCal/data_collection/sensors/GelsightMini/__init__.py +0 -0
- py3DCal/data_collection/sensors/GelsightMini/default.csv +1210 -0
- py3DCal/data_collection/sensors/Sensor.py +44 -0
- py3DCal/data_collection/sensors/__init__.py +0 -0
- py3DCal/model_training/__init__.py +0 -0
- py3DCal/model_training/datasets/DIGIT_dataset.py +77 -0
- py3DCal/model_training/datasets/GelSightMini_dataset.py +75 -0
- py3DCal/model_training/datasets/__init__.py +3 -0
- py3DCal/model_training/datasets/split_dataset.py +38 -0
- py3DCal/model_training/datasets/tactile_sensor_dataset.py +83 -0
- py3DCal/model_training/lib/__init__.py +0 -0
- py3DCal/model_training/lib/add_coordinate_embeddings.py +29 -0
- py3DCal/model_training/lib/annotate_dataset.py +422 -0
- py3DCal/model_training/lib/depthmaps.py +82 -0
- py3DCal/model_training/lib/fast_poisson.py +51 -0
- py3DCal/model_training/lib/get_gradient_map.py +39 -0
- py3DCal/model_training/lib/precompute_gradients.py +61 -0
- py3DCal/model_training/lib/train_model.py +96 -0
- py3DCal/model_training/lib/validate_parameters.py +87 -0
- py3DCal/model_training/models/__init__.py +1 -0
- py3DCal/model_training/models/touchnet.py +211 -0
- py3DCal/utils/__init__.py +0 -0
- py3DCal/utils/utils.py +32 -0
- py3dcal-1.0.5.dist-info/LICENSE +21 -0
- py3dcal-1.0.5.dist-info/METADATA +29 -0
- py3dcal-1.0.5.dist-info/RECORD +40 -0
- py3dcal-1.0.5.dist-info/WHEEL +5 -0
- py3dcal-1.0.5.dist-info/entry_points.txt +3 -0
- py3dcal-1.0.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class Sensor(ABC):
|
|
4
|
+
"""
|
|
5
|
+
Sensor: An abstract base class for tactile sensors.
|
|
6
|
+
"""
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.name = ""
|
|
9
|
+
self.x_offset = 5
|
|
10
|
+
self.y_offset = 5
|
|
11
|
+
self.z_offset = 5
|
|
12
|
+
self.z_clearance = 2
|
|
13
|
+
self.max_penetration = 0
|
|
14
|
+
self.default_calibration_file = "calibration_procs/digit/default.csv"
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def connect(self):
|
|
18
|
+
""" Connects to the sensor
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def disconnect(self):
|
|
24
|
+
""" Disconnects from the sensor
|
|
25
|
+
"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def capture_image(self):
|
|
30
|
+
""" Captures an image from the sensor
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
numpy.ndarray: The image from the sensor.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def flush_frames(self, n: int = 5):
|
|
38
|
+
"""Discards the next n frames to clear camera buffer.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
n (int): Number of frames to discard. Default is 5.
|
|
42
|
+
"""
|
|
43
|
+
for _ in range(n):
|
|
44
|
+
self.capture_image()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import tarfile
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from typing import Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .tactile_sensor_dataset import TactileSensorDataset
|
|
8
|
+
from ..lib.validate_parameters import validate_root
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DIGIT(TactileSensorDataset):
|
|
12
|
+
"""
|
|
13
|
+
DIGIT: A Dataset Class for the DIGIT sensor
|
|
14
|
+
Args:
|
|
15
|
+
root (str or pathlib.Path): The root directory containing digit_calibration_data.
|
|
16
|
+
download (bool, optional): If True, downloads the dataset for the specified sensor type. Defaults to False.
|
|
17
|
+
add_coordinate_embeddings (bool, optional): If True, adds xy coordinate embeddings to each image. Defaults to True.
|
|
18
|
+
subtract_blank (bool, optional): If True, subtracts a blank image from each input image. Defaults to False.
|
|
19
|
+
transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default: ``transforms.ToTensor()``
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, root: Union[str, Path] = Path("."), download=False, add_coordinate_embeddings=True, subtract_blank=True, transform=None):
|
|
22
|
+
validate_root(root)
|
|
23
|
+
|
|
24
|
+
self.root = root
|
|
25
|
+
|
|
26
|
+
self.dataset_path = os.path.join(self.root, "digit_calibration_data")
|
|
27
|
+
|
|
28
|
+
if download:
|
|
29
|
+
self._download_dataset()
|
|
30
|
+
|
|
31
|
+
super().__init__(root=self.dataset_path, add_coordinate_embeddings=add_coordinate_embeddings, subtract_blank=subtract_blank, transform=transform)
|
|
32
|
+
|
|
33
|
+
def _download_dataset(self):
|
|
34
|
+
"""
|
|
35
|
+
Downloads the dataset for either the DIGIT sensor.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
# Check if self.dataset_path exists
|
|
39
|
+
if not os.path.exists(self.dataset_path):
|
|
40
|
+
os.makedirs(self.root, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
tar_path = os.path.join(self.root, "digit_calibration_data.tar.gz")
|
|
43
|
+
|
|
44
|
+
print(f"Downloading DIGIT dataset ...")
|
|
45
|
+
response = requests.get('https://zenodo.org/records/17517028/files/digit_calibration_data.tar.gz?download=1', stream=True)
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
|
|
48
|
+
total_size = int(response.headers.get('content-length', 0))
|
|
49
|
+
block_size = 1024
|
|
50
|
+
|
|
51
|
+
# Save file in chunks to handle large datasets
|
|
52
|
+
with open(tar_path, 'wb') as f, tqdm(
|
|
53
|
+
total=total_size,
|
|
54
|
+
unit='B',
|
|
55
|
+
unit_scale=True,
|
|
56
|
+
desc="Downloading",
|
|
57
|
+
ncols=80
|
|
58
|
+
) as progress_bar:
|
|
59
|
+
for chunk in response.iter_content(chunk_size=block_size):
|
|
60
|
+
if chunk:
|
|
61
|
+
f.write(chunk)
|
|
62
|
+
progress_bar.update(len(chunk))
|
|
63
|
+
|
|
64
|
+
print(f"Download complete!")
|
|
65
|
+
|
|
66
|
+
# Extract .tar.gz file
|
|
67
|
+
print("Extracting files ...")
|
|
68
|
+
with tarfile.open(tar_path, "r:gz") as tar:
|
|
69
|
+
tar.extractall(path=self.root)
|
|
70
|
+
|
|
71
|
+
os.remove(tar_path)
|
|
72
|
+
|
|
73
|
+
print(f"Extraction complete! Files are in: {self.root}/")
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
print(f"DIGIT dataset already exists at: {self.dataset_path}/")
|
|
77
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import tarfile
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from typing import Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .tactile_sensor_dataset import TactileSensorDataset
|
|
8
|
+
from ..lib.validate_parameters import validate_root
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GelSightMini(TactileSensorDataset):
|
|
12
|
+
"""
|
|
13
|
+
GelSight Mini: A Dataset Class for the GelSight Mini sensor
|
|
14
|
+
Args:
|
|
15
|
+
root (str or pathlib.Path): The root directory containing gsmini_calibration_data.
|
|
16
|
+
download (bool, optional): If True, downloads the dataset for the specified sensor type. Defaults to False.
|
|
17
|
+
add_coordinate_embeddings (bool, optional): If True, adds xy coordinate embeddings to each image. Defaults to True.
|
|
18
|
+
subtract_blank (bool, optional): If True, subtracts a blank image from each input image. Defaults to False.
|
|
19
|
+
transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default: ``transforms.ToTensor()``
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, root: Union[str, Path] = Path("."), download=False, add_coordinate_embeddings=True, subtract_blank=True, transform=None):
|
|
22
|
+
validate_root(root)
|
|
23
|
+
|
|
24
|
+
self.root = root
|
|
25
|
+
|
|
26
|
+
self.dataset_path = os.path.join(self.root, "gsmini_calibration_data")
|
|
27
|
+
|
|
28
|
+
if download:
|
|
29
|
+
self._download_dataset()
|
|
30
|
+
|
|
31
|
+
super().__init__(root=self.dataset_path, add_coordinate_embeddings=add_coordinate_embeddings, subtract_blank=subtract_blank, transform=transform)
|
|
32
|
+
|
|
33
|
+
def _download_dataset(self):
|
|
34
|
+
"""
|
|
35
|
+
Downloads the dataset for the GelSight Mini sensor.
|
|
36
|
+
"""
|
|
37
|
+
# Check if self.dataset_path exists
|
|
38
|
+
if not os.path.exists(self.dataset_path):
|
|
39
|
+
os.makedirs(self.root, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
tar_path = os.path.join(self.root, "gsmini_calibration_data.tar.gz")
|
|
42
|
+
|
|
43
|
+
print(f"Downloading GelSight Mini dataset ...")
|
|
44
|
+
response = requests.get('https://zenodo.org/records/17517028/files/gsmini_calibration_data.tar.gz?download=1', stream=True)
|
|
45
|
+
response.raise_for_status()
|
|
46
|
+
|
|
47
|
+
total_size = int(response.headers.get('content-length', 0))
|
|
48
|
+
block_size = 1024
|
|
49
|
+
|
|
50
|
+
# Save file in chunks to handle large datasets
|
|
51
|
+
with open(tar_path, 'wb') as f, tqdm(
|
|
52
|
+
total=total_size,
|
|
53
|
+
unit='B',
|
|
54
|
+
unit_scale=True,
|
|
55
|
+
desc="Downloading",
|
|
56
|
+
ncols=80
|
|
57
|
+
) as progress_bar:
|
|
58
|
+
for chunk in response.iter_content(chunk_size=block_size):
|
|
59
|
+
if chunk:
|
|
60
|
+
f.write(chunk)
|
|
61
|
+
progress_bar.update(len(chunk))
|
|
62
|
+
|
|
63
|
+
print(f"Download complete!")
|
|
64
|
+
|
|
65
|
+
# Extract .tar.gz file
|
|
66
|
+
print("Extracting files ...")
|
|
67
|
+
with tarfile.open(tar_path, "r:gz") as tar:
|
|
68
|
+
tar.extractall(path=self.root)
|
|
69
|
+
|
|
70
|
+
os.remove(tar_path)
|
|
71
|
+
|
|
72
|
+
print(f"Extraction complete! Files are in: {self.root}/")
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
print(f"GelSight Mini dataset already exists at: {self.dataset_path}/")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from sklearn.model_selection import train_test_split
|
|
4
|
+
from .tactile_sensor_dataset import TactileSensorDataset
|
|
5
|
+
|
|
6
|
+
def split_dataset(dataset, train_ratio=0.8):
|
|
7
|
+
"""
|
|
8
|
+
Splits a dataset into training and validation sets.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
dataset (py3DCal.datasets.TactileSensorDataset): The dataset to split.
|
|
12
|
+
train_ratio (float): The proportion of the dataset to include in the training set. Default is 0.8.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
tuple: A tuple containing the training and validation datasets.
|
|
16
|
+
"""
|
|
17
|
+
if not isinstance(dataset, TactileSensorDataset):
|
|
18
|
+
raise TypeError("Expected dataset to be an instance of py3DCal.datasets.TactileSensorDataset")
|
|
19
|
+
|
|
20
|
+
df = dataset.data.copy()
|
|
21
|
+
|
|
22
|
+
unique_coords = df[['x_mm', 'y_mm']].drop_duplicates().reset_index(drop=True)
|
|
23
|
+
|
|
24
|
+
train_df, val_df = train_test_split(unique_coords, train_size=train_ratio, random_state=42)
|
|
25
|
+
|
|
26
|
+
# Merge back to get full rows
|
|
27
|
+
train_data = pd.merge(df, train_df, on=['x_mm', 'y_mm'])
|
|
28
|
+
val_data = pd.merge(df, val_df, on=['x_mm', 'y_mm'])
|
|
29
|
+
|
|
30
|
+
# Create two copies of the original dataset
|
|
31
|
+
train_dataset = copy.deepcopy(dataset)
|
|
32
|
+
val_dataset = copy.deepcopy(dataset)
|
|
33
|
+
|
|
34
|
+
# Update the data attribute of each dataset
|
|
35
|
+
train_dataset.data = train_data
|
|
36
|
+
val_dataset.data = val_data
|
|
37
|
+
|
|
38
|
+
return train_dataset, val_dataset
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import torch
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from typing import Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from torch.utils.data import Dataset
|
|
8
|
+
from torchvision import transforms
|
|
9
|
+
from ..lib.precompute_gradients import precompute_gradients
|
|
10
|
+
from ..lib.get_gradient_map import get_gradient_map
|
|
11
|
+
from ..lib.add_coordinate_embeddings import add_coordinate_embeddings
|
|
12
|
+
from ..lib.validate_parameters import validate_root, validate_dataset
|
|
13
|
+
|
|
14
|
+
class TactileSensorDataset(Dataset):
|
|
15
|
+
"""
|
|
16
|
+
Tactile Sensor Dataset.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
root (str or pathlib.Path): The root directory that contains the dataset folder.
|
|
20
|
+
add_coordinate_embeddings (bool, optional): If True, adds xy coordinate embeddings to each image. Defaults to True.
|
|
21
|
+
subtract_blank (bool, optional): If True, subtracts a blank image from each input image. Defaults to False.
|
|
22
|
+
transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default: ``transforms.ToTensor()``
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, root: Union[str, Path], add_coordinate_embeddings=True, subtract_blank=True, transform=None):
|
|
25
|
+
validate_dataset(root, subtract_blank)
|
|
26
|
+
|
|
27
|
+
self.root = root
|
|
28
|
+
self.annotation_path = os.path.join(root, "annotations", "annotations.csv")
|
|
29
|
+
self.metadata_path = os.path.join(root, "annotations", "metadata.json")
|
|
30
|
+
if subtract_blank:
|
|
31
|
+
self.blank_image_path = os.path.join(root, "blank_images", "blank.png")
|
|
32
|
+
self.add_coordinate_embeddings = add_coordinate_embeddings
|
|
33
|
+
self.subtract_blank = subtract_blank
|
|
34
|
+
|
|
35
|
+
if transform is None:
|
|
36
|
+
self.transform = transforms.ToTensor()
|
|
37
|
+
else:
|
|
38
|
+
self.transform = transform
|
|
39
|
+
|
|
40
|
+
# Load the CSV data
|
|
41
|
+
self.data = pd.read_csv(self.annotation_path)
|
|
42
|
+
|
|
43
|
+
# Get probe radius (in px) from metadata
|
|
44
|
+
metadata = pd.read_json(self.metadata_path, typ="series")
|
|
45
|
+
radius = metadata["probe_radius_mm"] * metadata["px_per_mm"]
|
|
46
|
+
|
|
47
|
+
# Precompute gradients
|
|
48
|
+
self.precomputed_gradients = precompute_gradients(dataset_path=self.root, annotation_path=self.annotation_path, r=radius)
|
|
49
|
+
|
|
50
|
+
# Load and transform blank image
|
|
51
|
+
if subtract_blank:
|
|
52
|
+
self.blank_image = self.transform(Image.open(self.blank_image_path).convert("RGB"))
|
|
53
|
+
|
|
54
|
+
def __len__(self):
|
|
55
|
+
return len(self.data) # Use the DataFrame length
|
|
56
|
+
|
|
57
|
+
def __getitem__(self, idx):
|
|
58
|
+
# Check if index is valid
|
|
59
|
+
if idx < 0 or idx >= len(self.data):
|
|
60
|
+
raise IndexError("Index out of range")
|
|
61
|
+
|
|
62
|
+
if torch.is_tensor(idx):
|
|
63
|
+
idx = idx.tolist()
|
|
64
|
+
|
|
65
|
+
image_name = os.path.join(self.root, "probe_images", self.data.iloc[idx, 0])
|
|
66
|
+
image = Image.open(image_name).convert("RGB")
|
|
67
|
+
|
|
68
|
+
target = get_gradient_map(idx, annotation_path=self.annotation_path, precomputed_gradients=self.precomputed_gradients)
|
|
69
|
+
|
|
70
|
+
image = self.transform(image)
|
|
71
|
+
target = self.transform(target)
|
|
72
|
+
|
|
73
|
+
if self.subtract_blank:
|
|
74
|
+
# Subtract pre-transformed blank tensor
|
|
75
|
+
image = image - self.blank_image
|
|
76
|
+
|
|
77
|
+
if self.add_coordinate_embeddings:
|
|
78
|
+
# Add coordinate embeddings
|
|
79
|
+
image = add_coordinate_embeddings(image)
|
|
80
|
+
|
|
81
|
+
sample = (image, target)
|
|
82
|
+
|
|
83
|
+
return sample
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
def add_coordinate_embeddings(image):
|
|
4
|
+
"""
|
|
5
|
+
Add coordinate embeddings to the input image.
|
|
6
|
+
- X channel: column indices (1s in first column, 2s in second column, etc.)
|
|
7
|
+
- Y channel: row indices (1s in first row, 2s in second row, etc.)
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
image (torch.Tensor): Input image tensor of shape (C, H, W).
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
torch.Tensor: Image tensor with added coordinate embeddings of shape (C+2, H, W).
|
|
14
|
+
"""
|
|
15
|
+
# Get image dimensions
|
|
16
|
+
_, height, width = image.shape
|
|
17
|
+
|
|
18
|
+
# Create x coordinate channel (column indices)
|
|
19
|
+
x_embedding = torch.arange(1, width + 1, dtype=torch.float32).unsqueeze(0).repeat(height, 1)
|
|
20
|
+
x_channel = x_embedding.unsqueeze(0) # Add channel dimension
|
|
21
|
+
|
|
22
|
+
# Create y coordinate channel (row indices)
|
|
23
|
+
y_embedding = torch.arange(1, height + 1, dtype=torch.float32).unsqueeze(1).repeat(1, width)
|
|
24
|
+
y_channel = y_embedding.unsqueeze(0) # Add channel dimension
|
|
25
|
+
|
|
26
|
+
# Concatenate original image with position embeddings
|
|
27
|
+
image_with_embeddings = torch.cat([image, x_channel, y_channel], dim=0)
|
|
28
|
+
|
|
29
|
+
return image_with_embeddings
|