prefab 1.1.2__py3-none-any.whl → 1.1.3__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.
- prefab/__init__.py +3 -2
- prefab/device.py +34 -166
- prefab/models.py +1 -1
- prefab/predict.py +260 -0
- {prefab-1.1.2.dist-info → prefab-1.1.3.dist-info}/METADATA +1 -1
- prefab-1.1.3.dist-info/RECORD +13 -0
- prefab-1.1.2.dist-info/RECORD +0 -12
- {prefab-1.1.2.dist-info → prefab-1.1.3.dist-info}/WHEEL +0 -0
- {prefab-1.1.2.dist-info → prefab-1.1.3.dist-info}/licenses/LICENSE +0 -0
prefab/__init__.py
CHANGED
|
@@ -5,9 +5,9 @@ Usage:
|
|
|
5
5
|
import prefab as pf
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.1.
|
|
8
|
+
__version__ = "1.1.3"
|
|
9
9
|
|
|
10
|
-
from . import compare, geometry, read, shapes
|
|
10
|
+
from . import compare, geometry, predict, read, shapes
|
|
11
11
|
from .device import BufferSpec, Device
|
|
12
12
|
from .models import models
|
|
13
13
|
|
|
@@ -15,6 +15,7 @@ __all__ = [
|
|
|
15
15
|
"Device",
|
|
16
16
|
"BufferSpec",
|
|
17
17
|
"geometry",
|
|
18
|
+
"predict",
|
|
18
19
|
"read",
|
|
19
20
|
"shapes",
|
|
20
21
|
"compare",
|
prefab/device.py
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
"""Provides the Device class for representing photonic devices."""
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
|
-
import io
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
3
|
from typing import Optional
|
|
8
4
|
|
|
9
5
|
import cv2
|
|
10
6
|
import gdstk
|
|
11
7
|
import matplotlib.pyplot as plt
|
|
12
8
|
import numpy as np
|
|
13
|
-
import requests
|
|
14
|
-
import toml
|
|
15
9
|
from matplotlib.axes import Axes
|
|
16
10
|
from matplotlib.patches import Rectangle
|
|
17
11
|
from PIL import Image
|
|
18
12
|
from pydantic import BaseModel, Field, conint, root_validator, validator
|
|
19
13
|
from scipy.ndimage import distance_transform_edt
|
|
20
14
|
from skimage import measure
|
|
21
|
-
from tqdm import tqdm
|
|
22
15
|
|
|
23
16
|
from . import compare, geometry
|
|
24
17
|
from .models import Model
|
|
18
|
+
from .predict import predict_array
|
|
25
19
|
|
|
26
20
|
Image.MAX_IMAGE_PIXELS = None
|
|
27
21
|
|
|
@@ -210,147 +204,6 @@ class Device(BaseModel):
|
|
|
210
204
|
or np.array_equal(unique_values, [1])
|
|
211
205
|
)
|
|
212
206
|
|
|
213
|
-
def _encode_array(self, array):
|
|
214
|
-
image = Image.fromarray(np.uint8(array * 255))
|
|
215
|
-
buffered = io.BytesIO()
|
|
216
|
-
image.save(buffered, format="PNG")
|
|
217
|
-
encoded_png = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
218
|
-
return encoded_png
|
|
219
|
-
|
|
220
|
-
def _decode_array(self, encoded_png):
|
|
221
|
-
binary_data = base64.b64decode(encoded_png)
|
|
222
|
-
image = Image.open(io.BytesIO(binary_data))
|
|
223
|
-
return np.array(image) / 255
|
|
224
|
-
|
|
225
|
-
def _predict_array(
|
|
226
|
-
self,
|
|
227
|
-
model: Model,
|
|
228
|
-
model_type: str,
|
|
229
|
-
binarize: bool,
|
|
230
|
-
gpu: bool = False,
|
|
231
|
-
) -> "Device":
|
|
232
|
-
try:
|
|
233
|
-
with open(os.path.expanduser("~/.prefab.toml")) as file:
|
|
234
|
-
content = file.readlines()
|
|
235
|
-
access_token = None
|
|
236
|
-
refresh_token = None
|
|
237
|
-
for line in content:
|
|
238
|
-
if "access_token" in line:
|
|
239
|
-
access_token = line.split("=")[1].strip().strip('"')
|
|
240
|
-
if "refresh_token" in line:
|
|
241
|
-
refresh_token = line.split("=")[1].strip().strip('"')
|
|
242
|
-
break
|
|
243
|
-
if not access_token or not refresh_token:
|
|
244
|
-
raise ValueError("Token not found in the configuration file.")
|
|
245
|
-
except FileNotFoundError:
|
|
246
|
-
raise FileNotFoundError(
|
|
247
|
-
"Could not validate user.\n"
|
|
248
|
-
"Please update prefab using: pip install --upgrade prefab.\n"
|
|
249
|
-
"Signup/login and generate a new token.\n"
|
|
250
|
-
"See https://www.prefabphotonics.com/docs/guides/quickstart."
|
|
251
|
-
) from None
|
|
252
|
-
|
|
253
|
-
headers = {
|
|
254
|
-
"Authorization": f"Bearer {access_token}",
|
|
255
|
-
"X-Refresh-Token": refresh_token,
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
predict_data = {
|
|
259
|
-
"device_array": self._encode_array(self.device_array[:, :, 0]),
|
|
260
|
-
"model": model.to_json(),
|
|
261
|
-
"model_type": model_type,
|
|
262
|
-
"binary": binarize,
|
|
263
|
-
}
|
|
264
|
-
json_data = json.dumps(predict_data)
|
|
265
|
-
|
|
266
|
-
endpoint_url = (
|
|
267
|
-
"https://prefab-photonics--predict-gpu-v1.modal.run"
|
|
268
|
-
if gpu
|
|
269
|
-
else "https://prefab-photonics--predict-v1.modal.run"
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
try:
|
|
273
|
-
with requests.post(
|
|
274
|
-
endpoint_url, data=json_data, headers=headers, stream=True
|
|
275
|
-
) as response:
|
|
276
|
-
response.raise_for_status()
|
|
277
|
-
event_type = None
|
|
278
|
-
model_descriptions = {
|
|
279
|
-
"p": "Prediction",
|
|
280
|
-
"c": "Correction",
|
|
281
|
-
"s": "SEMulate",
|
|
282
|
-
}
|
|
283
|
-
progress_bar = tqdm(
|
|
284
|
-
total=100,
|
|
285
|
-
desc=f"{model_descriptions[model_type]}",
|
|
286
|
-
unit="%",
|
|
287
|
-
colour="green",
|
|
288
|
-
bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
for line in response.iter_lines():
|
|
292
|
-
if line:
|
|
293
|
-
decoded_line = line.decode("utf-8").strip()
|
|
294
|
-
if decoded_line.startswith("event:"):
|
|
295
|
-
event_type = decoded_line.split(":")[1].strip()
|
|
296
|
-
elif decoded_line.startswith("data:"):
|
|
297
|
-
try:
|
|
298
|
-
data_content = json.loads(
|
|
299
|
-
decoded_line.split("data: ")[1]
|
|
300
|
-
)
|
|
301
|
-
if event_type == "progress":
|
|
302
|
-
progress = round(100 * data_content["progress"])
|
|
303
|
-
progress_bar.update(progress - progress_bar.n)
|
|
304
|
-
elif event_type == "result":
|
|
305
|
-
results = []
|
|
306
|
-
for key in sorted(data_content.keys()):
|
|
307
|
-
if key.startswith("result"):
|
|
308
|
-
decoded_image = self._decode_array(
|
|
309
|
-
data_content[key]
|
|
310
|
-
)
|
|
311
|
-
results.append(decoded_image)
|
|
312
|
-
|
|
313
|
-
if results:
|
|
314
|
-
prediction = np.stack(results, axis=-1)
|
|
315
|
-
if binarize:
|
|
316
|
-
prediction = geometry.binarize_hard(
|
|
317
|
-
prediction
|
|
318
|
-
)
|
|
319
|
-
progress_bar.close()
|
|
320
|
-
return prediction
|
|
321
|
-
elif event_type == "end":
|
|
322
|
-
print("Stream ended.")
|
|
323
|
-
progress_bar.close()
|
|
324
|
-
break
|
|
325
|
-
elif event_type == "auth":
|
|
326
|
-
if "new_refresh_token" in data_content["auth"]:
|
|
327
|
-
prefab_file_path = os.path.expanduser(
|
|
328
|
-
"~/.prefab.toml"
|
|
329
|
-
)
|
|
330
|
-
with open(
|
|
331
|
-
prefab_file_path, "w", encoding="utf-8"
|
|
332
|
-
) as toml_file:
|
|
333
|
-
toml.dump(
|
|
334
|
-
{
|
|
335
|
-
"access_token": data_content[
|
|
336
|
-
"auth"
|
|
337
|
-
]["new_access_token"],
|
|
338
|
-
"refresh_token": data_content[
|
|
339
|
-
"auth"
|
|
340
|
-
]["new_refresh_token"],
|
|
341
|
-
},
|
|
342
|
-
toml_file,
|
|
343
|
-
)
|
|
344
|
-
elif event_type == "error":
|
|
345
|
-
raise ValueError(f"{data_content['error']}")
|
|
346
|
-
except json.JSONDecodeError:
|
|
347
|
-
raise ValueError(
|
|
348
|
-
"Failed to decode JSON:",
|
|
349
|
-
decoded_line.split("data: ")[1],
|
|
350
|
-
) from None
|
|
351
|
-
except requests.RequestException as e:
|
|
352
|
-
raise RuntimeError(f"Request failed: {e}") from e
|
|
353
|
-
|
|
354
207
|
def predict(
|
|
355
208
|
self,
|
|
356
209
|
model: Model,
|
|
@@ -393,7 +246,8 @@ class Device(BaseModel):
|
|
|
393
246
|
If the prediction service returns an error or if the response from the
|
|
394
247
|
service cannot be processed correctly.
|
|
395
248
|
"""
|
|
396
|
-
prediction_array =
|
|
249
|
+
prediction_array = predict_array(
|
|
250
|
+
device_array=self.device_array,
|
|
397
251
|
model=model,
|
|
398
252
|
model_type="p",
|
|
399
253
|
binarize=binarize,
|
|
@@ -445,7 +299,8 @@ class Device(BaseModel):
|
|
|
445
299
|
If the correction service returns an error or if the response from the
|
|
446
300
|
service cannot be processed correctly.
|
|
447
301
|
"""
|
|
448
|
-
correction_array =
|
|
302
|
+
correction_array = predict_array(
|
|
303
|
+
device_array=self.device_array,
|
|
449
304
|
model=model,
|
|
450
305
|
model_type="c",
|
|
451
306
|
binarize=binarize,
|
|
@@ -487,7 +342,8 @@ class Device(BaseModel):
|
|
|
487
342
|
A new instance of the Device class with its geometry transformed to simulate
|
|
488
343
|
an SEM image style.
|
|
489
344
|
"""
|
|
490
|
-
semulated_array =
|
|
345
|
+
semulated_array = predict_array(
|
|
346
|
+
device_array=self.device_array,
|
|
491
347
|
model=model,
|
|
492
348
|
model_type="s",
|
|
493
349
|
binarize=False,
|
|
@@ -550,6 +406,7 @@ class Device(BaseModel):
|
|
|
550
406
|
cell_name: str = "prefab_device",
|
|
551
407
|
gds_layer: tuple[int, int] = (1, 0),
|
|
552
408
|
contour_approx_mode: int = 2,
|
|
409
|
+
origin: tuple[float, float] = (0.0, 0.0),
|
|
553
410
|
):
|
|
554
411
|
"""
|
|
555
412
|
Exports the device geometry as a GDSII file.
|
|
@@ -572,11 +429,15 @@ class Device(BaseModel):
|
|
|
572
429
|
The mode of contour approximation used during the conversion. Defaults to 2,
|
|
573
430
|
which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
|
|
574
431
|
horizontal, vertical, and diagonal segments and leaves only their endpoints.
|
|
432
|
+
origin : tuple[float, float], optional
|
|
433
|
+
The x and y coordinates of the origin for the GDSII export. Defaults to
|
|
434
|
+
(0.0, 0.0).
|
|
575
435
|
"""
|
|
576
436
|
gdstk_cell = self.flatten()._device_to_gdstk(
|
|
577
437
|
cell_name=cell_name,
|
|
578
438
|
gds_layer=gds_layer,
|
|
579
439
|
contour_approx_mode=contour_approx_mode,
|
|
440
|
+
origin=origin,
|
|
580
441
|
)
|
|
581
442
|
print(f"Saving GDS to '{gds_path}'...")
|
|
582
443
|
gdstk_library = gdstk.Library()
|
|
@@ -588,6 +449,7 @@ class Device(BaseModel):
|
|
|
588
449
|
cell_name: str = "prefab_device",
|
|
589
450
|
gds_layer: tuple[int, int] = (1, 0),
|
|
590
451
|
contour_approx_mode: int = 2,
|
|
452
|
+
origin: tuple[float, float] = (0.0, 0.0),
|
|
591
453
|
):
|
|
592
454
|
"""
|
|
593
455
|
Converts the device geometry to a GDSTK cell object.
|
|
@@ -607,6 +469,9 @@ class Device(BaseModel):
|
|
|
607
469
|
The mode of contour approximation used during the conversion. Defaults to 2,
|
|
608
470
|
which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
|
|
609
471
|
horizontal, vertical, and diagonal segments and leaves only their endpoints.
|
|
472
|
+
origin : tuple[float, float], optional
|
|
473
|
+
The x and y coordinates of the origin for the GDSTK cell. Defaults to
|
|
474
|
+
(0.0, 0.0).
|
|
610
475
|
|
|
611
476
|
Returns
|
|
612
477
|
-------
|
|
@@ -618,6 +483,7 @@ class Device(BaseModel):
|
|
|
618
483
|
cell_name=cell_name,
|
|
619
484
|
gds_layer=gds_layer,
|
|
620
485
|
contour_approx_mode=contour_approx_mode,
|
|
486
|
+
origin=origin,
|
|
621
487
|
)
|
|
622
488
|
return gdstk_cell
|
|
623
489
|
|
|
@@ -626,6 +492,7 @@ class Device(BaseModel):
|
|
|
626
492
|
cell_name: str,
|
|
627
493
|
gds_layer: tuple[int, int],
|
|
628
494
|
contour_approx_mode: int,
|
|
495
|
+
origin: tuple[float, float],
|
|
629
496
|
) -> gdstk.Cell:
|
|
630
497
|
approx_mode_mapping = {
|
|
631
498
|
1: cv2.CHAIN_APPROX_NONE,
|
|
@@ -662,8 +529,21 @@ class Device(BaseModel):
|
|
|
662
529
|
polygons_to_process = hierarchy_polygons[level]
|
|
663
530
|
|
|
664
531
|
if polygons_to_process:
|
|
532
|
+
center_x_nm = self.device_array.shape[1] / 2
|
|
533
|
+
center_y_nm = self.device_array.shape[0] / 2
|
|
534
|
+
|
|
535
|
+
center_x_um = center_x_nm / 1000
|
|
536
|
+
center_y_um = center_y_nm / 1000
|
|
537
|
+
|
|
538
|
+
adjusted_polygons = [
|
|
539
|
+
[
|
|
540
|
+
(x - center_x_um + origin[0], y - center_y_um + origin[1])
|
|
541
|
+
for x, y in polygon
|
|
542
|
+
]
|
|
543
|
+
for polygon in polygons_to_process
|
|
544
|
+
]
|
|
665
545
|
processed_polygons = gdstk.boolean(
|
|
666
|
-
|
|
546
|
+
adjusted_polygons,
|
|
667
547
|
processed_polygons,
|
|
668
548
|
operation,
|
|
669
549
|
layer=gds_layer[0],
|
|
@@ -1313,13 +1193,6 @@ class Device(BaseModel):
|
|
|
1313
1193
|
"""
|
|
1314
1194
|
Trim the device geometry by removing empty space around it.
|
|
1315
1195
|
|
|
1316
|
-
Parameters
|
|
1317
|
-
----------
|
|
1318
|
-
buffer_thickness : dict, optional
|
|
1319
|
-
A dictionary specifying the thickness of the buffer to leave around the
|
|
1320
|
-
non-zero elements of the array. Should contain keys 'top', 'bottom', 'left',
|
|
1321
|
-
'right'. Defaults to None, which means no buffer is added.
|
|
1322
|
-
|
|
1323
1196
|
Returns
|
|
1324
1197
|
-------
|
|
1325
1198
|
Device
|
|
@@ -1414,15 +1287,10 @@ class Device(BaseModel):
|
|
|
1414
1287
|
Flatten the device geometry by summing the vertical layers and normalizing the
|
|
1415
1288
|
result.
|
|
1416
1289
|
|
|
1417
|
-
Parameters
|
|
1418
|
-
----------
|
|
1419
|
-
device_array : np.ndarray
|
|
1420
|
-
The input array to be flattened.
|
|
1421
|
-
|
|
1422
1290
|
Returns
|
|
1423
1291
|
-------
|
|
1424
|
-
|
|
1425
|
-
|
|
1292
|
+
Device
|
|
1293
|
+
A new instance of the Device with the flattened geometry.
|
|
1426
1294
|
"""
|
|
1427
1295
|
flattened_device_array = geometry.flatten(device_array=self.device_array)
|
|
1428
1296
|
return self.model_copy(update={"device_array": flattened_device_array})
|
prefab/models.py
CHANGED
prefab/predict.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import requests
|
|
8
|
+
import toml
|
|
9
|
+
from PIL import Image
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
from .geometry import binarize_hard
|
|
13
|
+
from .models import Model
|
|
14
|
+
|
|
15
|
+
BASE_URL = "https://prefab-photonics--predict"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def predict_array(
|
|
19
|
+
device_array: np.ndarray,
|
|
20
|
+
model: Model,
|
|
21
|
+
model_type: str,
|
|
22
|
+
binarize: bool,
|
|
23
|
+
gpu: bool = False,
|
|
24
|
+
) -> np.ndarray:
|
|
25
|
+
"""
|
|
26
|
+
Predicts the output array for a given device array using a specified model.
|
|
27
|
+
|
|
28
|
+
This function sends the device array to a prediction service, which uses a machine
|
|
29
|
+
learning model to predict the outcome of the nanofabrication process. The prediction
|
|
30
|
+
can be performed on a GPU if specified.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
device_array : np.ndarray
|
|
35
|
+
The input device array to be predicted.
|
|
36
|
+
model : Model
|
|
37
|
+
The model to use for prediction.
|
|
38
|
+
model_type : str
|
|
39
|
+
The type of model to use (e.g., 'p', 'c', 's').
|
|
40
|
+
binarize : bool
|
|
41
|
+
Whether to binarize the output.
|
|
42
|
+
gpu : bool, optional
|
|
43
|
+
Whether to use GPU for prediction. Defaults to False.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
np.ndarray
|
|
48
|
+
The predicted output array.
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
RuntimeError
|
|
53
|
+
If the request to the prediction service fails.
|
|
54
|
+
"""
|
|
55
|
+
headers = _prepare_headers()
|
|
56
|
+
predict_data = _prepare_predict_data(device_array, model, model_type, binarize)
|
|
57
|
+
endpoint_url = f"{BASE_URL}-gpu-v1.modal.run" if gpu else f"{BASE_URL}-v1.modal.run"
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with requests.post(
|
|
61
|
+
endpoint_url,
|
|
62
|
+
data=json.dumps(predict_data),
|
|
63
|
+
headers=headers,
|
|
64
|
+
stream=True,
|
|
65
|
+
) as response:
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
return _process_response(response, model_type, binarize)
|
|
68
|
+
except requests.RequestException as e:
|
|
69
|
+
raise RuntimeError(f"Request failed: {e}") from e
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def predict_array_with_grad(
|
|
73
|
+
device_array: np.ndarray, model: Model, model_type: str
|
|
74
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
75
|
+
"""
|
|
76
|
+
Predicts the output array and its gradient for a given device array using a
|
|
77
|
+
specified model.
|
|
78
|
+
|
|
79
|
+
This function sends the device array to a prediction service, which uses a machine
|
|
80
|
+
learning model to predict both the outcome and the gradient of the nanofabrication
|
|
81
|
+
process.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
device_array : np.ndarray
|
|
86
|
+
The input device array to be predicted.
|
|
87
|
+
model : Model
|
|
88
|
+
The model to use for prediction.
|
|
89
|
+
model_type : str
|
|
90
|
+
The type of model to use (e.g., 'p', 'c', 's').
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
tuple[np.ndarray, np.ndarray]
|
|
95
|
+
A tuple containing the predicted output array and its gradient.
|
|
96
|
+
|
|
97
|
+
Raises
|
|
98
|
+
------
|
|
99
|
+
RuntimeError
|
|
100
|
+
If the request to the prediction service fails.
|
|
101
|
+
"""
|
|
102
|
+
headers = _prepare_headers()
|
|
103
|
+
predict_data = _prepare_predict_data(device_array, model, model_type, False)
|
|
104
|
+
endpoint_url = f"{BASE_URL}-with-grad-v1.modal.run"
|
|
105
|
+
|
|
106
|
+
response = requests.post(
|
|
107
|
+
endpoint_url, data=json.dumps(predict_data), headers=headers
|
|
108
|
+
)
|
|
109
|
+
prediction_array = _decode_array(response.json()["prediction_array"])
|
|
110
|
+
gradient_array = _decode_array(response.json()["gradient_array"])
|
|
111
|
+
gradient_min = response.json()["gradient_min"]
|
|
112
|
+
gradient_max = response.json()["gradient_max"]
|
|
113
|
+
gradient_range = gradient_max - gradient_min
|
|
114
|
+
gradient_array = gradient_array * gradient_range + gradient_min
|
|
115
|
+
|
|
116
|
+
return (prediction_array, gradient_array)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _encode_array(array):
|
|
120
|
+
"""Encode a numpy array as a PNG image and return the base64 encoded string."""
|
|
121
|
+
image = Image.fromarray(np.uint8(array * 255))
|
|
122
|
+
buffered = io.BytesIO()
|
|
123
|
+
image.save(buffered, format="PNG")
|
|
124
|
+
encoded_png = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
125
|
+
return encoded_png
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _decode_array(encoded_png):
|
|
129
|
+
"""Decode a base64 encoded PNG image and return a numpy array."""
|
|
130
|
+
binary_data = base64.b64decode(encoded_png)
|
|
131
|
+
image = Image.open(io.BytesIO(binary_data))
|
|
132
|
+
return np.array(image) / 255
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _read_tokens():
|
|
136
|
+
"""Read access and refresh tokens from the configuration file."""
|
|
137
|
+
token_file_path = os.path.expanduser("~/.prefab.toml")
|
|
138
|
+
try:
|
|
139
|
+
with open(token_file_path) as file:
|
|
140
|
+
tokens = toml.load(file)
|
|
141
|
+
access_token = tokens.get("access_token")
|
|
142
|
+
refresh_token = tokens.get("refresh_token")
|
|
143
|
+
if not access_token or not refresh_token:
|
|
144
|
+
raise ValueError("Tokens not found in the configuration file.")
|
|
145
|
+
return access_token, refresh_token
|
|
146
|
+
except FileNotFoundError:
|
|
147
|
+
raise FileNotFoundError(
|
|
148
|
+
"Could not validate user.\n"
|
|
149
|
+
"Please update prefab using: pip install --upgrade prefab.\n"
|
|
150
|
+
"Signup/login and generate a new token.\n"
|
|
151
|
+
"See https://www.prefabphotonics.com/docs/guides/quickstart."
|
|
152
|
+
) from None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _prepare_headers():
|
|
156
|
+
"""Prepare HTTP headers for the request."""
|
|
157
|
+
access_token, refresh_token = _read_tokens()
|
|
158
|
+
return {
|
|
159
|
+
"Authorization": f"Bearer {access_token}",
|
|
160
|
+
"X-Refresh-Token": refresh_token,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _prepare_predict_data(device_array, model, model_type, binarize):
|
|
165
|
+
"""Prepare the data payload for the prediction request."""
|
|
166
|
+
return {
|
|
167
|
+
"device_array": _encode_array(np.squeeze(device_array)),
|
|
168
|
+
"model": model.to_json(),
|
|
169
|
+
"model_type": model_type,
|
|
170
|
+
"binary": binarize,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _process_response(response, model_type, binarize):
|
|
175
|
+
"""Process the streaming response from the prediction request."""
|
|
176
|
+
event_type = None
|
|
177
|
+
model_descriptions = {
|
|
178
|
+
"p": "Prediction",
|
|
179
|
+
"c": "Correction",
|
|
180
|
+
"s": "SEMulate",
|
|
181
|
+
}
|
|
182
|
+
progress_bar = tqdm(
|
|
183
|
+
total=100,
|
|
184
|
+
desc=model_descriptions.get(model_type, "Processing"),
|
|
185
|
+
unit="%",
|
|
186
|
+
colour="green",
|
|
187
|
+
bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
for line in response.iter_lines():
|
|
191
|
+
if line:
|
|
192
|
+
decoded_line = line.decode("utf-8").strip()
|
|
193
|
+
if decoded_line.startswith("event:"):
|
|
194
|
+
event_type = decoded_line.split(":", 1)[1].strip()
|
|
195
|
+
elif decoded_line.startswith("data:"):
|
|
196
|
+
data_content = _parse_data_line(decoded_line)
|
|
197
|
+
result = _handle_event(event_type, data_content, progress_bar, binarize)
|
|
198
|
+
if result is not None:
|
|
199
|
+
progress_bar.close()
|
|
200
|
+
return result
|
|
201
|
+
progress_bar.close()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_data_line(decoded_line):
|
|
205
|
+
"""Parse a data line from the response stream."""
|
|
206
|
+
data_line = decoded_line.split(":", 1)[1].strip()
|
|
207
|
+
try:
|
|
208
|
+
return json.loads(data_line)
|
|
209
|
+
except json.JSONDecodeError:
|
|
210
|
+
raise ValueError(f"Failed to decode JSON: {data_line}") from None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _handle_event(event_type, data_content, progress_bar, binarize):
|
|
214
|
+
"""Handle different types of events received from the server."""
|
|
215
|
+
if event_type == "progress":
|
|
216
|
+
_update_progress(progress_bar, data_content)
|
|
217
|
+
elif event_type == "result":
|
|
218
|
+
return _process_result(data_content, binarize)
|
|
219
|
+
elif event_type == "end":
|
|
220
|
+
print("Stream ended.")
|
|
221
|
+
elif event_type == "auth":
|
|
222
|
+
_update_tokens(data_content.get("auth", {}))
|
|
223
|
+
elif event_type == "error":
|
|
224
|
+
raise ValueError(f"{data_content['error']}")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _update_progress(progress_bar, data_content):
|
|
228
|
+
"""Update the progress bar based on the progress event."""
|
|
229
|
+
progress = round(100 * data_content.get("progress", 0))
|
|
230
|
+
progress_bar.update(progress - progress_bar.n)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _process_result(data_content, binarize):
|
|
234
|
+
"""Process the result event and return the prediction."""
|
|
235
|
+
results = [
|
|
236
|
+
_decode_array(data_content[key])
|
|
237
|
+
for key in sorted(data_content.keys())
|
|
238
|
+
if key.startswith("result")
|
|
239
|
+
]
|
|
240
|
+
if results:
|
|
241
|
+
prediction = np.stack(results, axis=-1)
|
|
242
|
+
if binarize:
|
|
243
|
+
prediction = binarize_hard(prediction)
|
|
244
|
+
return prediction
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _update_tokens(auth_data):
|
|
248
|
+
"""Update tokens if new tokens are provided in the auth event."""
|
|
249
|
+
new_access_token = auth_data.get("new_access_token")
|
|
250
|
+
new_refresh_token = auth_data.get("new_refresh_token")
|
|
251
|
+
if new_access_token and new_refresh_token:
|
|
252
|
+
prefab_file_path = os.path.expanduser("~/.prefab.toml")
|
|
253
|
+
with open(prefab_file_path, "w", encoding="utf-8") as toml_file:
|
|
254
|
+
toml.dump(
|
|
255
|
+
{
|
|
256
|
+
"access_token": new_access_token,
|
|
257
|
+
"refresh_token": new_refresh_token,
|
|
258
|
+
},
|
|
259
|
+
toml_file,
|
|
260
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: prefab
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Artificial nanofabrication of integrated photonic circuits using deep learning
|
|
5
5
|
Project-URL: Homepage, https://prefabphotonics.com
|
|
6
6
|
Project-URL: Repository, https://github.com/PreFab-Photonics/PreFab
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
prefab/__init__.py,sha256=DuARo1QC4MGMw-yzCBLEoRlOU3MsKZTbWDCAqUpQjWM,425
|
|
2
|
+
prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
|
|
3
|
+
prefab/compare.py,sha256=2MKUT7N2A639tUGCnJHpfF9MmS-v3oARDkTqHbWJ9OM,3239
|
|
4
|
+
prefab/device.py,sha256=l0etqB0559xa9Frb0IKl5N3kU56vWHlwHc-qehINTlw,52292
|
|
5
|
+
prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
|
|
6
|
+
prefab/models.py,sha256=JpBqNFIqbo1ymKEl0NWWF4ZkhvK23rEVVBEFl05TXCI,3671
|
|
7
|
+
prefab/predict.py,sha256=AuCh_vOMP0dD68u75WQaOTog6TMi1FG3nLQxv6UgIkA,8579
|
|
8
|
+
prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
|
|
9
|
+
prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
|
|
10
|
+
prefab-1.1.3.dist-info/METADATA,sha256=TGbeM7u0kktbeGeOwGIFS43dwp8rWvLSwHDYlXEcsfI,34824
|
|
11
|
+
prefab-1.1.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
12
|
+
prefab-1.1.3.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
|
13
|
+
prefab-1.1.3.dist-info/RECORD,,
|
prefab-1.1.2.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
prefab/__init__.py,sha256=fbGTDuVA76ol5cM6fXbPAZMB0BCT2zmHD7Pgr9I-zV0,401
|
|
2
|
-
prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
|
|
3
|
-
prefab/compare.py,sha256=2MKUT7N2A639tUGCnJHpfF9MmS-v3oARDkTqHbWJ9OM,3239
|
|
4
|
-
prefab/device.py,sha256=dpx4dDspruImy__bp3X11y_s-CBJZr6dKpxn8wGtmSA,58314
|
|
5
|
-
prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
|
|
6
|
-
prefab/models.py,sha256=UMzYZzKouroxlwkXCMKIYozmQCMhNhvt8kQrZmwmZB4,3671
|
|
7
|
-
prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
|
|
8
|
-
prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
|
|
9
|
-
prefab-1.1.2.dist-info/METADATA,sha256=wZOFUncUh7ZmZRoJ8AxH_SfpKwd2KdxDnVvmTyiLSR0,34824
|
|
10
|
-
prefab-1.1.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
-
prefab-1.1.2.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
|
|
12
|
-
prefab-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|