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.

Files changed (40) hide show
  1. py3DCal/__init__.py +14 -0
  2. py3DCal/data_collection/Calibrator.py +300 -0
  3. py3DCal/data_collection/__init__.py +0 -0
  4. py3DCal/data_collection/printers/Ender3/Ender3.py +82 -0
  5. py3DCal/data_collection/printers/Ender3/__init__.py +0 -0
  6. py3DCal/data_collection/printers/Printer.py +63 -0
  7. py3DCal/data_collection/printers/__init__.py +0 -0
  8. py3DCal/data_collection/sensors/DIGIT/DIGIT.py +47 -0
  9. py3DCal/data_collection/sensors/DIGIT/__init__.py +0 -0
  10. py3DCal/data_collection/sensors/DIGIT/default.csv +1222 -0
  11. py3DCal/data_collection/sensors/GelsightMini/GelsightMini.py +45 -0
  12. py3DCal/data_collection/sensors/GelsightMini/__init__.py +0 -0
  13. py3DCal/data_collection/sensors/GelsightMini/default.csv +1210 -0
  14. py3DCal/data_collection/sensors/Sensor.py +44 -0
  15. py3DCal/data_collection/sensors/__init__.py +0 -0
  16. py3DCal/model_training/__init__.py +0 -0
  17. py3DCal/model_training/datasets/DIGIT_dataset.py +77 -0
  18. py3DCal/model_training/datasets/GelSightMini_dataset.py +75 -0
  19. py3DCal/model_training/datasets/__init__.py +3 -0
  20. py3DCal/model_training/datasets/split_dataset.py +38 -0
  21. py3DCal/model_training/datasets/tactile_sensor_dataset.py +83 -0
  22. py3DCal/model_training/lib/__init__.py +0 -0
  23. py3DCal/model_training/lib/add_coordinate_embeddings.py +29 -0
  24. py3DCal/model_training/lib/annotate_dataset.py +422 -0
  25. py3DCal/model_training/lib/depthmaps.py +82 -0
  26. py3DCal/model_training/lib/fast_poisson.py +51 -0
  27. py3DCal/model_training/lib/get_gradient_map.py +39 -0
  28. py3DCal/model_training/lib/precompute_gradients.py +61 -0
  29. py3DCal/model_training/lib/train_model.py +96 -0
  30. py3DCal/model_training/lib/validate_parameters.py +87 -0
  31. py3DCal/model_training/models/__init__.py +1 -0
  32. py3DCal/model_training/models/touchnet.py +211 -0
  33. py3DCal/utils/__init__.py +0 -0
  34. py3DCal/utils/utils.py +32 -0
  35. py3dcal-1.0.5.dist-info/LICENSE +21 -0
  36. py3dcal-1.0.5.dist-info/METADATA +29 -0
  37. py3dcal-1.0.5.dist-info/RECORD +40 -0
  38. py3dcal-1.0.5.dist-info/WHEEL +5 -0
  39. py3dcal-1.0.5.dist-info/entry_points.txt +3 -0
  40. 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,3 @@
1
+ from .tactile_sensor_dataset import TactileSensorDataset
2
+ from .DIGIT_dataset import DIGIT
3
+ from .GelSightMini_dataset import GelSightMini
@@ -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