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
py3DCal/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ from .data_collection.Calibrator import Calibrator
2
+ from .data_collection.printers.Printer import Printer
3
+ from .data_collection.printers.Ender3.Ender3 import Ender3
4
+ from .data_collection.sensors.Sensor import Sensor
5
+ from .data_collection.sensors.DIGIT.DIGIT import DIGIT
6
+ from .data_collection.sensors.GelsightMini.GelsightMini import GelsightMini
7
+ from .model_training import datasets, models
8
+ from .model_training.datasets.split_dataset import split_dataset
9
+ from .model_training.models.touchnet import SensorType
10
+ from .model_training.lib.annotate_dataset import annotate
11
+ from .model_training.lib.train_model import train_model
12
+ from .model_training.lib.depthmaps import get_depthmap, save_2d_depthmap, show_2d_depthmap
13
+ from .model_training.lib.fast_poisson import fast_poisson
14
+ from .utils.utils import list_com_ports
@@ -0,0 +1,300 @@
1
+ import numpy as np
2
+ import time
3
+ import csv
4
+ import os
5
+ from typing import Union
6
+ from pathlib import Path
7
+ from PIL import Image
8
+ from tqdm import tqdm
9
+ from .printers.Printer import Printer
10
+ from .sensors.Sensor import Sensor
11
+
12
+ class Calibrator:
13
+ """ Calibrator class to automatically probe a tactile sensor.
14
+ Args:
15
+ printer (Printer): An instance of a Printer class.
16
+ sensor (Sensor): An instance of a Sensor class.
17
+ """
18
+ def __init__(self, printer: Printer, sensor: Sensor):
19
+ self.printer = printer
20
+ self.sensor = sensor
21
+
22
+ self.printer_connected = False
23
+ self.sensor_connected = False
24
+
25
+ try:
26
+ self.printer_name = self.printer.name
27
+ except:
28
+ self.printer_name = "printer"
29
+ try:
30
+ self.sensor_name = self.sensor.name
31
+ except:
32
+ self.sensor_name = "tactile"
33
+
34
+ def connect_printer(self):
35
+ """ Connects to the 3D Printer
36
+
37
+ Returns:
38
+ bool: Returns True if connection was successful.
39
+ """
40
+ print("Connecting to " + str(self.printer_name) + "...")
41
+
42
+ try:
43
+ self.printer.connect()
44
+ self.printer_connected = True
45
+
46
+ print("Connected to " + str(self.printer_name) + "!")
47
+ print("")
48
+ return True
49
+ except:
50
+ self.printer_connected = False
51
+ print("Error connecting to " + str(self.printer_name) + ".")
52
+ print("")
53
+ return False
54
+
55
+ def disconnect_printer(self):
56
+ """ Disconnects from the 3D Printer
57
+
58
+ Returns:
59
+ bool: Returns True if disconnection was successful.
60
+ """
61
+ print("Disconnecting from " + str(self.printer_name) + "...")
62
+
63
+ try:
64
+ self.printer.disconnect()
65
+ self.printer_connected = False
66
+ print("Disconnected from " + str(self.printer_name) + "!")
67
+ print("")
68
+ return True
69
+ except:
70
+ print("Error disconnecting from " + str(self.printer_name) + ".")
71
+ print("")
72
+ return False
73
+
74
+ def initialize_printer(self):
75
+ """ Sends gcode to configure and home 3D Printer
76
+
77
+ Returns:
78
+ bool: Returns True if initialization was successful.
79
+ """
80
+ # Probe must be detached to home printer
81
+ print("Make sure probe is not attached to print head. ", end="")
82
+ input("Press Enter to continue...")
83
+ print("")
84
+
85
+ if not self.printer_connected:
86
+ self.connect_printer()
87
+
88
+ try:
89
+ print("Initializing printer...")
90
+
91
+ self.printer.initialize()
92
+
93
+ print("Printer initialization complete!")
94
+ print("")
95
+ return True
96
+ except:
97
+ print("Error sending initialization gcode to printer.")
98
+ print("")
99
+ return False
100
+
101
+ def connect_sensor(self):
102
+ """ Connects to the sensor
103
+
104
+ Returns:
105
+ bool: Returns True if connection was successful.
106
+ """
107
+ print("Connecting to " + self.sensor_name + " sensor...")
108
+
109
+ try:
110
+ # Connect to sensor
111
+ self.sensor.connect()
112
+ print("Connected to sensor!")
113
+ print("")
114
+ except:
115
+ print("Error connecting to sensor.")
116
+ print("")
117
+
118
+ def disconnect_sensor(self):
119
+ """ Disconnects from the sensor
120
+
121
+ Returns:
122
+ bool: Returns True if disconnection was successful.
123
+ """
124
+ print("Disconnecting from " + self.sensor.name + " sensor...")
125
+
126
+ self.sensor.connect()
127
+ try:
128
+ self.printer.disconnect()
129
+ self.printer_connected = False
130
+ print("Disconnected from sensor!")
131
+ print("")
132
+ return True
133
+ except:
134
+ print("Error disconnecting from sensor.")
135
+ print("")
136
+ return False
137
+
138
+ def probe(self, home_printer: bool = True, save_images: bool = True, calibration_file_path: Union[str, Path] = None, data_save_path: str = "."):
139
+ """ Executes the probing procedure on 3D printer
140
+
141
+ Args:
142
+ home_printer (bool, optional): Determines whether to home the printer prior to probing. Defaults to True.
143
+ save_images (bool, optional): Determines whether sensor images are saved. Defaults to True.
144
+ calibration_file_path (str, optional): The path of the calibration file. For the DIGIT and GelSight Mini,
145
+ if no file is specified, a default calibration file will be used.
146
+ data_save_path (str, optional): The folder in which the data should be saved. If no folder is specified,
147
+ data will be stored in a directory named "sensor_calibration_data" within the current working directory.
148
+
149
+ Returns:
150
+ bool: Returns True when the probing procedure is complete.
151
+ """
152
+ # Connect to 3D printer if not already connected
153
+ if not self.printer_connected:
154
+ self.connect_printer()
155
+ self.printer.send_gcode("M117 Calibrating sensor...")
156
+
157
+ # Connect to sensor
158
+ if save_images == True:
159
+ self.connect_sensor()
160
+
161
+ for i in range(30):
162
+ self.sensor.capture_image()
163
+
164
+ # Send initialization gcode to printer
165
+ if home_printer == True:
166
+ self.initialize_printer()
167
+
168
+ # If no data path was provided, set default path to a folder called "sensor_calibration_data" in the Downloads folder
169
+ if data_save_path is not None:
170
+ data_save_path = os.path.join(data_save_path, "sensor_calibration_data")
171
+
172
+ # Create necessary directories if they don't exist
173
+ Path(data_save_path).mkdir(parents=True, exist_ok=True)
174
+ Path(os.path.join(data_save_path, "annotations")).mkdir(parents=True, exist_ok=True)
175
+ Path(os.path.join(data_save_path, "blank_images")).mkdir(parents=True, exist_ok=True)
176
+ Path(os.path.join(data_save_path, "probe_images")).mkdir(parents=True, exist_ok=True)
177
+
178
+ # Open a csv file to write calibration data
179
+ with open(os.path.join(data_save_path, "annotations", "probe_data.csv"), 'w', newline='') as csv_file:
180
+ csv_writer = csv.writer(csv_file)
181
+ csv_writer.writerow(['img_name', 'x_mm', 'y_mm', 'penetration_depth_mm'])
182
+
183
+ # Save blank image
184
+ if save_images == True:
185
+ blank_img = self.sensor.capture_image()
186
+ img = Image.fromarray(blank_img)
187
+ img.save(os.path.join(data_save_path, "blank_images", "blank.png"))
188
+
189
+ # If no calibration file path was provided, use the default calibration file for the specified sensor
190
+ if calibration_file_path == None:
191
+ calibration_file_path = self.sensor.default_calibration_file
192
+
193
+ # Load CSV file into numpy array
194
+ self.calibration_points = np.genfromtxt(calibration_file_path, delimiter=',', skip_header=1)
195
+
196
+ # Get number of rows (i.e. calibration points)
197
+ N = self.calibration_points.shape[0]
198
+
199
+ # Variable to keep track of image number
200
+ img_idx = 0
201
+
202
+ # Move to offset Z position
203
+ self.printer.send_gcode("G0 Z" + str(self.sensor.z_offset + self.sensor.z_clearance))
204
+
205
+ time.sleep(1 + (self.sensor.z_offset + self.sensor.z_clearance) / 4)
206
+
207
+ print("Attach probe to printer head. ", end="")
208
+ input("Press Enter to continue...")
209
+ print("")
210
+
211
+ # Move to offset XY position
212
+ self.printer.send_gcode("G0 X" + str(self.sensor.x_offset) + " Y" + str(self.sensor.y_offset))
213
+
214
+ time.sleep(1 + max(self.sensor.x_offset, self.sensor.y_offset) / 10)
215
+
216
+ print("Beginning sensor calibration procedure...")
217
+ print("")
218
+
219
+ x_prev = self.sensor.x_offset
220
+ y_prev = self.sensor.y_offset
221
+ z_prev = self.sensor.z_offset + self.sensor.z_clearance
222
+
223
+ # Loop through every calibration point
224
+ for i in tqdm(range(N), desc="Sensor Calibration Progress"):
225
+ # If specified penetration depth exceeds maximum value, print message
226
+ if abs(self.calibration_points[i][2]) > self.sensor.max_penetration:
227
+ print("Line " + str(i+1) + ": Maximum penetration depth for sensor exceeded. Skipping calibration point.")
228
+ # If penetration depth does not exceed maximum value, move to calibration point
229
+ else:
230
+ # Get absolute XYZ coordinates
231
+ x = self.sensor.x_offset + self.calibration_points[i][0]
232
+ y = self.sensor.y_offset + self.calibration_points[i][1]
233
+ z = self.sensor.z_offset - abs(self.calibration_points[i][2])
234
+
235
+ # Move to Z clearance height
236
+ self.printer.send_gcode("G0 Z" + str(self.sensor.z_offset + self.sensor.z_clearance))
237
+
238
+ # Move to desired XY location
239
+ self.printer.send_gcode("G0 X" + str(x) + " Y" + str(y))
240
+
241
+ # Move to desired Z penetration
242
+ self.printer.send_gcode("G0 Z" + str(z))
243
+
244
+ # Calculate time required to reach position
245
+ # Assumes x and y speed of 10 mm/s, z speed of 4 mm/s
246
+ travel_time = abs(self.sensor.z_offset + self.sensor.z_clearance - z_prev) / 4 + max(abs(x - x_prev), abs(y - y_prev)) / 10 + abs(z - (self.sensor.z_offset + self.sensor.z_clearance)) / 4
247
+ time.sleep(travel_time + 1)
248
+
249
+ # Update variables
250
+ x_prev = x
251
+ y_prev = y
252
+ z_prev = z
253
+
254
+ # Take desired number of images
255
+ if save_images == True:
256
+ with open(os.path.join(data_save_path, "annotations", "probe_data.csv"), 'a', newline='') as csv_file:
257
+ csv_writer = csv.writer(csv_file)
258
+
259
+ # Flush frames to clear camera buffer
260
+ self.sensor.flush_frames(n=5)
261
+
262
+ for _ in range(int(self.calibration_points[i][3])):
263
+ frame = self.sensor.capture_image()
264
+
265
+ img_name = str(img_idx) + "_" + "X" + str(self.calibration_points[i][0]) + "Y" + str(self.calibration_points[i][1]) + "Z" + str(self.calibration_points[i][2]) + ".png"
266
+ img_path = os.path.join(data_save_path, "probe_images", img_name)
267
+
268
+ img = Image.fromarray(frame)
269
+ img.save(img_path)
270
+
271
+ csv_writer.writerow([img_name, self.calibration_points[i][0], self.calibration_points[i][1], self.calibration_points[i][2]])
272
+
273
+ img_idx += 1
274
+
275
+ time.sleep(0.5)
276
+
277
+ else:
278
+ with open(os.path.join(data_save_path, "annotations", "probe_data.csv"), 'a', newline='') as csv_file:
279
+ csv_writer = csv.writer(csv_file)
280
+ csv_writer.writerow(["---", self.calibration_points[i][0], self.calibration_points[i][1], self.calibration_points[i][2]])
281
+
282
+ # Move to Z clearance height
283
+ self.printer.send_gcode("G0 Z" + str(self.sensor.z_offset + self.sensor.z_clearance))
284
+
285
+ print("")
286
+
287
+ # Update printer display
288
+ self.printer.send_gcode("M117 Calibration Done!")
289
+
290
+ # Disconnect from 3D printer
291
+ self.disconnect_printer()
292
+
293
+ # Disconnect from sensor
294
+ if save_images == True:
295
+ self.disconnect_sensor()
296
+
297
+ print("Calibration procedure complete!")
298
+ print("")
299
+
300
+ return True
File without changes
@@ -0,0 +1,82 @@
1
+ from ..Printer import Printer
2
+ import serial
3
+ from typing import Union
4
+ from pathlib import Path
5
+
6
+ class Ender3(Printer):
7
+ """
8
+ Ender3: A Printer Class for the Ender 3
9
+ Args:
10
+ port (str or pathlib.Path): The COM port the printer is connected to.
11
+ """
12
+ def __init__(self, port: Union[str, Path]):
13
+ self.port = port
14
+ self.name = "Ender 3"
15
+
16
+ def connect(self):
17
+ """
18
+ Connects to the Ender 3 printer.
19
+ """
20
+ # Code to connect to the printer
21
+ self.ser = serial.Serial(self.port, 115200)
22
+
23
+ def disconnect(self):
24
+ """
25
+ Disconnects from the Ender 3 printer.
26
+ """
27
+ # Code to disconnect from the printer
28
+ self.ser.close()
29
+
30
+ def send_gcode(self, command: str):
31
+ """
32
+ Sends a G-code command to the Ender 3 printer.
33
+ Args:
34
+ command (str): The G-code command to be sent to the 3D printer.
35
+ """
36
+ # Code to execute gcode command on the printer
37
+ self.ser.write(str.encode(command + "\r\n"))
38
+
39
+ def get_response(self):
40
+ """
41
+ Gets messages sent by the Ender 3 printer.
42
+
43
+ Returns:
44
+ response (str): The message sent by the printer.
45
+ """
46
+ # Code to return message from the printer
47
+ response = self.ser.readline().decode('utf-8')
48
+
49
+ return response
50
+
51
+ def initialize(self, xy_only: bool = False):
52
+ """
53
+ Initializes the Ender 3 printer (homes the printer, sets units, adjusts fans, etc).
54
+ Args:
55
+ xy_only (bool): If True, only homes the X and Y axes.
56
+ """
57
+ # Code to initialize printer (home, set units, set absolute/relative movements, adjust fan speeds, etc.)
58
+
59
+ # Use Metric Values
60
+ self.send_gcode("G21")
61
+
62
+ # Absolute Positioning
63
+ self.send_gcode("G90")
64
+
65
+ # Fan Off
66
+ self.send_gcode("M107")
67
+
68
+ if xy_only:
69
+ # Home Printer X Y
70
+ self.send_gcode("G28 X Y")
71
+ else:
72
+ # Home Printer X Y Z
73
+ self.send_gcode("G28")
74
+
75
+ # Check if homing is complete
76
+ ok_count = 0
77
+
78
+ while ok_count < 4:
79
+ if "ok" in self.get_response():
80
+ ok_count += 1
81
+
82
+ return True
File without changes
@@ -0,0 +1,63 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ class Printer(ABC):
4
+ """
5
+ Printer: An abstract base class for 3D printers.
6
+ """
7
+ def __init__(self):
8
+ self.name = ""
9
+
10
+ @abstractmethod
11
+ def connect(self):
12
+ """ Connects to the 3D printer.
13
+ """
14
+ pass
15
+
16
+ @abstractmethod
17
+ def disconnect(self):
18
+ """ Disconnects from the 3D printer.
19
+ """
20
+ pass
21
+
22
+ @abstractmethod
23
+ def send_gcode(self, command: str):
24
+ """ Sends a G-code command to the 3D printer.
25
+
26
+ Args:
27
+ command (str): The G-code command to be sent to the 3D printer.
28
+ """
29
+ pass
30
+
31
+ @abstractmethod
32
+ def get_response(self):
33
+ """ Gets messages sent by the 3D printer.
34
+
35
+ Returns:
36
+ str: The message sent by the printer.
37
+ """
38
+ pass
39
+
40
+ @abstractmethod
41
+ def initialize(self):
42
+ """ Initializes the 3D printer (homes the printer, sets units, adjusts fans, etc).
43
+ """
44
+ pass
45
+
46
+ def go_to(self, x: float = None, y: float = None, z: float = None):
47
+ """
48
+ Moves the printer to a specific position.
49
+ Args:
50
+ x (float, optional): The X coordinate to move to.
51
+ y (float, optional): The Y coordinate to move to.
52
+ z (float, optional): The Z coordinate to move to.
53
+ """
54
+ # Code to move the printer to a specific position
55
+ command = "G0"
56
+ if x is not None:
57
+ command += f" X{x}"
58
+ if y is not None:
59
+ command += f" Y{y}"
60
+ if z is not None:
61
+ command += f" Z{z}"
62
+
63
+ self.send_gcode(command)
File without changes
@@ -0,0 +1,47 @@
1
+ from ..Sensor import Sensor
2
+ import cv2
3
+ import os
4
+
5
+ try:
6
+ from digit_interface import Digit
7
+ except:
8
+ pass
9
+
10
+ class DIGIT(Sensor):
11
+ """
12
+ DIGIT: A Sensor Class for the DIGIT sensor
13
+ Args:
14
+ serial_number (str): The serial number of the DIGIT sensor.
15
+ """
16
+ def __init__(self, serial_number: str):
17
+ self.serial_number = serial_number
18
+ self.name = "DIGIT"
19
+ self.x_offset = 110
20
+ self.y_offset = 111.5
21
+ self.z_offset = 137
22
+ self.z_clearance = 2
23
+ self.max_penetration = 4
24
+ self.default_calibration_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "default.csv")
25
+
26
+ def connect(self):
27
+ """
28
+ Connects to the DIGIT sensor.
29
+ """
30
+ # Code to connect to the sensor
31
+ self.sensor = Digit(self.serial_number)
32
+ self.sensor.connect()
33
+ self.sensor.set_fps(30)
34
+
35
+ def disconnect(self):
36
+ """
37
+ Disconnects from the DIGIT sensor.
38
+ """
39
+ # Code to disconnect from the sensor
40
+ self.sensor.disconnect()
41
+
42
+ def capture_image(self):
43
+ """
44
+ Captures an image from the DIGIT sensor.
45
+ """
46
+ # Code to return an image from the sensor
47
+ return cv2.flip(self.sensor.get_frame(), 1)
File without changes