Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions src/odemis/acq/milling/fibsemos.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,24 @@ def convert_pattern_to_fibsemos(p: MillingPatternParameters) -> 'BasePattern':
else:
raise NotImplementedError(f"Conversion not implemented for pattern type: {type(p)}")


def _apply_spot_size_correction(dimension: float, correction: float, dimension_name: str) -> float:
"""Reduce a scan dimension by the measured opening excess."""
corrected_dimension = dimension - correction
if corrected_dimension <= 0:
raise ValueError(
f"Spot size correction {correction} m makes the {dimension_name} non-positive "
f"({corrected_dimension} m)."
)
return corrected_dimension


def _convert_rectangle_pattern(p: RectanglePatternParameters) -> 'RectanglePattern':
"""Convert an Odemis rectangle pattern to a fibsemOS RectanglePattern."""
correction = p.spot_size_correction.value
return RectanglePattern(
width=p.width.value,
height=p.height.value,
width=_apply_spot_size_correction(p.width.value, correction, "rectangle width"),
height=_apply_spot_size_correction(p.height.value, correction, "rectangle height"),
depth=p.depth.value,
rotation=p.rotation.value,
scan_direction=p.scan_direction.value,
Expand All @@ -216,20 +229,24 @@ def _convert_rectangle_pattern(p: RectanglePatternParameters) -> 'RectanglePatte

def _convert_trench_pattern(p: TrenchPatternParameters) -> 'TrenchPattern':
"""Convert an Odemis trench pattern to a fibsemOS TrenchPattern."""
correction = p.spot_size_correction.value
return TrenchPattern(
width=p.width.value,
upper_trench_height=p.height.value,
lower_trench_height=p.height.value,
spacing=p.spacing.value,
width=_apply_spot_size_correction(p.width.value, correction, "trench width"),
upper_trench_height=_apply_spot_size_correction(p.height.value, correction, "upper trench height"),
lower_trench_height=_apply_spot_size_correction(p.height.value, correction, "lower trench height"),
# fibsemOS derives trench centers from height and spacing. Increasing
# spacing keeps the smaller sent rectangles centered on the displayed ones.
spacing=p.spacing.value + correction,
depth=p.depth.value,
point=Point(x=p.center.value[0], y=p.center.value[1])
)

def _convert_microexpansion_pattern(p: MicroexpansionPatternParameters) -> 'MicroExpansionPattern':
"""Convert an Odemis microexpansion pattern to a fibsemOS MicroExpansionPattern."""
correction = p.spot_size_correction.value
return MicroExpansionPattern(
width=p.width.value,
height=p.height.value,
width=_apply_spot_size_correction(p.width.value, correction, "microexpansion width"),
height=_apply_spot_size_correction(p.height.value, correction, "microexpansion height"),
depth=p.depth.value,
distance=p.spacing.value,
point=Point(x=p.center.value[0], y=p.center.value[1])
Expand Down
7 changes: 7 additions & 0 deletions src/odemis/acq/milling/milling_tasks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
# task_name:
# milling: Milling parameters
# patterns: List of patterns (and parameters) to mill
# spot_size_correction is the total measured excess in the milled opening size.
# It must be nonnegative and is subtracted once from each dimension sent to fibsemOS.
'Microexpansion':
name: 'Microexpansion'
milling:
Expand All @@ -20,6 +22,7 @@
height: 1.5e-05
depth: 1.0e-06
spacing: 1.0e-05
spot_size_correction: 0.0
center_x: 0
center_y: 0
pattern: 'microexpansion'
Expand All @@ -37,6 +40,7 @@
height: 6.0e-06
depth: 1.0e-06
spacing: 3.0e-06
spot_size_correction: 0.0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
center_x: 0
center_y: 0
pattern: 'trench'
Expand All @@ -54,6 +58,7 @@
height: 4.0e-06
depth: 0.8e-06
spacing: 1.5e-06
spot_size_correction: 0.0
center_x: 0
center_y: 0
pattern: 'trench'
Expand All @@ -71,6 +76,7 @@
height: 1.0e-06
depth: 0.6e-06
spacing: 600.0e-09
spot_size_correction: 0.0
center_x: 0
center_y: 0
pattern: 'trench'
Expand All @@ -88,6 +94,7 @@
height: 0.6e-06
depth: 0.5e-06
spacing: 300.0e-09
spot_size_correction: 0.0
center_x: 0
center_y: 0
pattern: 'trench'
37 changes: 31 additions & 6 deletions src/odemis/acq/milling/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@ def generate(self) -> List['MillingPatternParameters']:
class RectanglePatternParameters(MillingPatternParameters):
"""Represents rectangle pattern parameters"""

def __init__(self, width: float, height: float, depth: float, rotation: float = 0.0, center = (0, 0), scan_direction: str = "TopToBottom", name: str = "Rectangle"):
def __init__(self, width: float, height: float, depth: float, rotation: float = 0.0,
center=(0, 0), scan_direction: str = "TopToBottom", name: str = "Rectangle",
spot_size_correction: float = 0.0):
self.name = model.StringVA(name)
self.width = model.FloatContinuous(width, unit="m", range=(1e-9, 900e-6))
self.height = model.FloatContinuous(height, unit="m", range=(1e-9, 900e-6))
self.depth = model.FloatContinuous(depth, unit="m", range=(1e-9, 100e-6))
self.rotation = model.FloatContinuous(rotation, unit="rad", range=(0, 2 * math.pi))
self.center = model.TupleContinuous(center, unit="m", range=((-1e3, -1e3), (1e3, 1e3)), cls=(int, float))
self.scan_direction = model.StringEnumerated(scan_direction, choices=set(["TopToBottom", "BottomToTop", "LeftToRight", "RightToLeft"]))
self.spot_size_correction = model.FloatContinuous(
spot_size_correction, unit="m", range=(0, 900e-6)
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def to_dict(self) -> dict:
"""Convert the parameters to a json object"""
Expand All @@ -75,6 +80,7 @@ def to_dict(self) -> dict:
"center_x": self.center.value[0],
"center_y": self.center.value[1],
"scan_direction": self.scan_direction.value,
"spot_size_correction": self.spot_size_correction.value,
"pattern": "rectangle"
}

Expand All @@ -87,7 +93,8 @@ def from_dict(data: dict) -> 'RectanglePatternParameters':
rotation=data.get("rotation", 0),
center=(data.get("center_x", 0), data.get("center_y", 0)),
scan_direction=data.get("scan_direction", "TopToBottom"),
name=data.get("name", "Rectangle"))
name=data.get("name", "Rectangle"),
spot_size_correction=data.get("spot_size_correction", 0.0))

def __repr__(self) -> str:
return f"{self.to_dict()}"
Expand All @@ -101,13 +108,17 @@ def generate(self) -> List[MillingPatternParameters]:
class TrenchPatternParameters(MillingPatternParameters):
"""Represents trench pattern parameters"""

def __init__(self, width: float, height: float, depth: float, spacing: float, center = (0, 0), name: str = "Trench"):
def __init__(self, width: float, height: float, depth: float, spacing: float,
center=(0, 0), name: str = "Trench", spot_size_correction: float = 0.0):
self.name = model.StringVA(name)
self.width = model.FloatContinuous(width, unit="m", range=(1e-9, 900e-6))
self.height = model.FloatContinuous(height, unit="m", range=(1e-9, 900e-6))
self.depth = model.FloatContinuous(depth, unit="m", range=(1e-9, 100e-6))
self.spacing = model.FloatContinuous(spacing, unit="m", range=(1e-9, 900e-6))
self.center = model.TupleContinuous(center, unit="m", range=((-1e3, -1e3), (1e3, 1e3)), cls=(int, float))
self.spot_size_correction = model.FloatContinuous(
spot_size_correction, unit="m", range=(0, 900e-6)
)

def to_dict(self) -> dict:
"""Convert the parameters to a json object"""
Expand All @@ -118,6 +129,7 @@ def to_dict(self) -> dict:
"spacing": self.spacing.value,
"center_x": self.center.value[0],
"center_y": self.center.value[1],
"spot_size_correction": self.spot_size_correction.value,
"pattern": "trench"
}

Expand All @@ -129,7 +141,8 @@ def from_dict(data: dict) -> 'TrenchPatternParameters':
depth=data["depth"],
spacing=data["spacing"],
center=(data.get("center_x", 0), data.get("center_y", 0)),
name=data.get("name", "Trench"))
name=data.get("name", "Trench"),
spot_size_correction=data.get("spot_size_correction", 0.0))
Comment thread
ilyushkin marked this conversation as resolved.

def __repr__(self) -> str:
return f"{self.to_dict()}"
Expand All @@ -141,6 +154,7 @@ def generate(self) -> List[MillingPatternParameters]:
height = self.height.value
depth = self.depth.value
spacing = self.spacing.value
spot_size_correction = self.spot_size_correction.value
center = self.center.value

# pattern center
Expand All @@ -157,6 +171,7 @@ def generate(self) -> List[MillingPatternParameters]:
rotation=0,
center = (center_x, upper_center_y), # x, y
scan_direction="TopToBottom",
spot_size_correction=spot_size_correction,
),
RectanglePatternParameters(
name=f"{name} (Lower)",
Expand All @@ -166,6 +181,7 @@ def generate(self) -> List[MillingPatternParameters]:
rotation=0,
center = (center_x, lower_center_y), # x, y
scan_direction="BottomToTop",
spot_size_correction=spot_size_correction,
),
]

Expand All @@ -175,13 +191,17 @@ def generate(self) -> List[MillingPatternParameters]:
class MicroexpansionPatternParameters(MillingPatternParameters):
"""Represents microexpansion pattern parameters"""

def __init__(self, width: float, height: float, depth: float, spacing: float, center = (0, 0), name: str = "Trench"):
def __init__(self, width: float, height: float, depth: float, spacing: float,
center=(0, 0), name: str = "Trench", spot_size_correction: float = 0.0):
self.name = model.StringVA(name)
self.width = model.FloatContinuous(width, unit="m", range=(1e-9, 900e-6))
self.height = model.FloatContinuous(height, unit="m", range=(1e-9, 900e-6))
self.depth = model.FloatContinuous(depth, unit="m", range=(1e-9, 100e-6))
self.spacing = model.FloatContinuous(spacing, unit="m", range=(1e-9, 900e-6))
self.center = model.TupleContinuous(center, unit="m", range=((-1e3, -1e3), (1e3, 1e3)), cls=(int, float))
self.spot_size_correction = model.FloatContinuous(
spot_size_correction, unit="m", range=(0, 900e-6)
)

def to_dict(self) -> dict:
"""Convert the parameters to a json object"""
Expand All @@ -192,6 +212,7 @@ def to_dict(self) -> dict:
"spacing": self.spacing.value,
"center_x": self.center.value[0],
"center_y": self.center.value[1],
"spot_size_correction": self.spot_size_correction.value,
"pattern": "microexpansion"
}

Expand All @@ -204,7 +225,8 @@ def from_dict(data: dict) -> 'MicroexpansionPatternParameters':
depth=data["depth"],
spacing=data["spacing"],
center=(data.get("center_x", 0), data.get("center_y", 0)),
name=data.get("name", "Microexpansion"))
name=data.get("name", "Microexpansion"),
spot_size_correction=data.get("spot_size_correction", 0.0))

def __repr__(self) -> str:
return f"{self.to_dict()}"
Expand All @@ -216,6 +238,7 @@ def generate(self) -> List[MillingPatternParameters]:
height = self.height.value
depth = self.depth.value
spacing = self.spacing.value
spot_size_correction = self.spot_size_correction.value
center_x, center_y = self.center.value

patterns = [
Expand All @@ -227,6 +250,7 @@ def generate(self) -> List[MillingPatternParameters]:
rotation=0,
center = (center_x - spacing, center_y),
scan_direction="TopToBottom",
spot_size_correction=spot_size_correction,
),
RectanglePatternParameters(
name=f"{name} (Right)",
Expand All @@ -236,6 +260,7 @@ def generate(self) -> List[MillingPatternParameters]:
rotation=0,
center = (center_x + spacing, center_y),
scan_direction="TopToBottom",
spot_size_correction=spot_size_correction,
),
]

Expand Down
48 changes: 46 additions & 2 deletions src/odemis/acq/milling/test/fibsemos_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""
import logging
import unittest
from unittest import mock
import numpy

from odemis.acq.milling import fibsemos # to load the fibsemOS module
Expand Down Expand Up @@ -55,7 +56,7 @@
logging.getLogger().setLevel(logging.DEBUG)

# Create dummy parameter objects to pass into converter functions.
def create_rectangle_pattern_params():
def create_rectangle_pattern_params(spot_size_correction=0.0):
return RectanglePatternParameters(
name="Rectangle-1",
width=10e-6,
Expand All @@ -64,16 +65,18 @@ def create_rectangle_pattern_params():
rotation=0,
center=(100, 150),
scan_direction="TopToBottom",
spot_size_correction=spot_size_correction,
)

def create_trench_pattern_params():
def create_trench_pattern_params(spot_size_correction=0.0):
return TrenchPatternParameters(
name="Trench-1",
width=12e-6,
height=8e-6,
depth=4e-6,
spacing=3e-6,
center=(50, 75),
spot_size_correction=spot_size_correction,
)

def create_microexpansion_pattern_params():
Expand Down Expand Up @@ -134,6 +137,47 @@ def test_convert_microexpansion_pattern(self):
self.assertEqual(converted.point, Point(x=pattern_param.center.value[0],
y=pattern_param.center.value[1]))


class _FakeFibsemPattern:
def __init__(self, **kwargs):
vars(self).update(kwargs)


class _FakePoint:
def __init__(self, x, y):
self.x = x
self.y = y


class TestSpotSizeCorrectionConversion(unittest.TestCase):
def setUp(self):
patcher = mock.patch.multiple(
fibsemos,
RectanglePattern=_FakeFibsemPattern,
TrenchPattern=_FakeFibsemPattern,
Point=_FakePoint,
create=True,
)
patcher.start()
self.addCleanup(patcher.stop)

def test_spot_size_correction_changes_sent_dimensions_only(self):
correction = 1e-6
rectangle_param = create_rectangle_pattern_params(correction)
rectangle = fibsemos.convert_pattern_to_fibsemos(rectangle_param)
self.assertAlmostEqual(rectangle.width, rectangle_param.width.value - correction)
self.assertAlmostEqual(rectangle.height, rectangle_param.height.value - correction)
self.assertAlmostEqual(rectangle_param.width.value, 10e-6)
self.assertAlmostEqual(rectangle_param.height.value, 15e-6)

def test_trench_centers_stay_on_displayed_positions(self):
correction = 0.1e-6
trench_param = create_trench_pattern_params(correction)
trench = fibsemos.convert_pattern_to_fibsemos(trench_param)
sent_offset = (trench.spacing + trench.upper_trench_height) / 2
displayed_offset = (trench_param.spacing.value + trench_param.height.value) / 2
self.assertAlmostEqual(sent_offset, displayed_offset)

class TestConvertMillingSettings(unittest.TestCase):

@classmethod
Expand Down
Loading
Loading