Skip to content

Commit 215de3d

Browse files
committed
Ensure one crystal at (0, 0), and add cross at (0, 0)
1 parent f369af9 commit 215de3d

File tree

1 file changed

+25
-5
lines changed

1 file changed

+25
-5
lines changed

src/instamatic/camera/camera_simu.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def __init__(self, name: str = 'simulate'):
8080
"""Simple simulation of the TEM. MUST START IN IMAGE MODE!
8181
8282
Consisting of randomly dispersed crystals.
83+
One crystal is guaranteeed to be at (0, 0) on the stage
8384
Each crystal is perfectly flat, and perfectly round.
8485
All crystals have the same unit cell.
8586
@@ -90,6 +91,7 @@ def __init__(self, name: str = 'simulate'):
9091
Image mode:
9192
Crystals have different "thickness", yielding different gray-values proportional to thickness.
9293
This gray-value never changes, regardless of tilt, orientation ect.
94+
A cross centered at (0, 0) is added for convenience
9395
9496
Diffraction mode:
9597
All crystals visible in image mode contribute to the diffraction pattern.
@@ -108,7 +110,7 @@ def __init__(self, name: str = 'simulate'):
108110
self.unit_cell_alpha = 90 # Degrees
109111
self.unit_cell_beta = 90 # Degrees
110112
self.unit_cell_gamma = 120 # Degrees
111-
self.max_excitation_error = 0.01
113+
self.max_excitation_error = 0.005
112114
self.spot_radius = 3 # pixels
113115
super().__init__(name)
114116

@@ -126,6 +128,11 @@ def __init__(self, name: str = 'simulate'):
126128
self.crystal_euler_angle_psi = np.random.uniform(0, 180, self.n_crystals)
127129
self.crystal_euler_angle_phi_2 = np.random.uniform(0, 360, self.n_crystals)
128130

131+
# Ensure one crystal is always at (0, 0) and 1µm radius
132+
self.crystal_x[0] = 0
133+
self.crystal_y[0] = 0
134+
self.crystal_r[0] = 1000
135+
129136
# Reciprocal-space setup
130137
reciprocal_unit_cell = _get_reciprocal_unit_cell(
131138
self.unit_cell_a,
@@ -193,6 +200,11 @@ def actually_establish_connection(self):
193200
else:
194201
self.mag = self.tem.getMagnification()
195202

203+
# If the TEM is a simulated one (i.e. random beam shift), reset beam shift
204+
# Otherwise, reseting the stage will not get you to (0, 0)
205+
if self.tem.name == 'simulate':
206+
self.tem.setBeamShift(0, 0)
207+
196208
self.ready = True
197209

198210
def release_connection(self):
@@ -237,7 +249,14 @@ def get_realspace_image(
237249
# thickness multiplied by mask of where the crystal is
238250
mask = ((xx - x) ** 2 + (yy - y) ** 2) < r**2
239251
out += t * mask.astype(float)
240-
return out
252+
# Invert and scale
253+
out = 0xF000 * (1 - out)
254+
# Add some noise
255+
out *= np.random.uniform(0.9, 1.1, out.shape)
256+
# Add cross at (0, 0)
257+
width = 50 # nm
258+
out[(np.abs(xx) < width) | (np.abs(yy) < width)] = 0
259+
return out.astype(int)
241260

242261
def get_diffraction_image(
243262
self, shape: Tuple[int, int], crystal_indices: np.ndarray
@@ -303,7 +322,9 @@ def get_diffraction_image(
303322
min_y = round(max(0, y - self.spot_radius))
304323
max_y = round(min(shape[0], y + self.spot_radius))
305324
out[min_y:max_y, min_x:max_x] = intensity
306-
return out
325+
# Scale. Direct beam intensity is 25
326+
out = out * 0x8000 / 25
327+
return out.astype(int)
307328

308329
def get_image(self, exposure: float = None, binsize: int = None, **kwargs) -> np.ndarray:
309330
self.actually_establish_connection()
@@ -371,8 +392,7 @@ def get_image(self, exposure: float = None, binsize: int = None, **kwargs) -> np
371392
c_ind,
372393
)
373394
else:
374-
img = (self.get_realspace_image(xx, yy, c_ind) * 0xFFFF).astype(int)
375-
return img
395+
return self.get_realspace_image(xx, yy, c_ind)
376396

377397
def acquire_image(self) -> int:
378398
"""For TVIPS compatibility."""

0 commit comments

Comments
 (0)