Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
9 changes: 5 additions & 4 deletions src/instamatic/calibrate/calibrate_beamshift.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ def live(
) -> Self:
while True:
c = calibrate_beamshift(ctrl=ctrl, save_images=True, outdir=outdir)
with c.annotate_videostream(vsp) if vsp else nullcontext():
binsize = ctrl.cam.default_binsize
with c.annotate_videostream(vsp, binsize) if vsp else nullcontext():
if input(' >> Accept? [y/n] ') == 'y':
return c

Expand All @@ -127,15 +128,15 @@ def plot(self, to_file: Optional[AnyPath] = None):
plt.show()

@contextmanager
def annotate_videostream(self, vsp: Optional[VideoStreamProcessor] = None) -> None:
def annotate_videostream(self, vsp: VideoStreamProcessor, binsize: int = 1) -> None:
shifts = np.dot(self.shifts, np.linalg.inv(self.transform))
ins: list[DeferredImageDraw.Instruction] = []

vsp.temporary_frame = np.max(self.images, axis=0)
print('Determined (blue) vs calibrated (orange) beam positions:')
for p, s in zip(self.pixels, shifts):
p = (p + self.reference_pixel)[::-1] # xy coords inverted for plot
s = (s + self.reference_pixel)[::-1] # xy coords inverted for plot
p = (p + self.reference_pixel)[::-1] / binsize # xy coords inverted for plot
s = (s + self.reference_pixel)[::-1] / binsize # xy coords inverted for plot
ins.append(vsp.draw.circle(p, radius=3, fill='blue'))
ins.append(vsp.draw.circle(s, radius=3, fill='orange'))
ins.append(vsp.draw.circle(self.reference_pixel[::-1], radius=3, fill='black'))
Expand Down
26 changes: 21 additions & 5 deletions src/instamatic/camera/camera_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import threading
import time
from functools import wraps
from typing import Any, Generator

import numpy as np

Expand Down Expand Up @@ -126,11 +127,7 @@ def _eval_dct(self, dct):
with self._eval_lock:
self.s.send(dumper(dct))

acquiring_image = dct['attr_name'] == 'get_image'
acquiring_movie = dct['attr_name'] == 'get_movie'

if acquiring_movie:
raise NotImplementedError('Acquiring movies over a socket is not supported.')
acquiring_image = dct['attr_name'] in {'get_image', 'get_movie', '__gen_next__'}

if acquiring_image and not self.use_shared_memory:
response = self.s.recv(self._imagebufsize)
Expand All @@ -146,6 +143,8 @@ def _eval_dct(self, dct):
data = self.get_data_from_shared_memory(**data)

if status == 200:
if isinstance(data, dict) and '__generator__' in data:
return self._wrap_remote_generator(data['__generator__'])
return data

elif status == 500:
Expand Down Expand Up @@ -206,3 +205,20 @@ def block(self):

def unblock(self):
raise NotImplementedError('This camera cannot be streamed.')

def _wrap_remote_generator(self, gen_id: str) -> Generator[Any]:
"""Pass a reference to yield from a remote __generator__ with id."""

def generator():
kwargs = {'id': gen_id}
try:
while True:
dct = {'attr_name': '__gen_next__', 'kwargs': kwargs}
value = self._eval_dct(dct)
if value is None:
return
yield value
finally:
self._eval_dct({'attr_name': '__gen_close__', 'kwargs': kwargs})

return generator()
4 changes: 2 additions & 2 deletions src/instamatic/camera/videostream.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def run(self):
if self.acquireInitiateEvent.is_set():
r = self.request
self.acquireInitiateEvent.clear()
e = r.exposure if r.exposure else self.default_exposure
b = r.binsize if r.binsize else self.default_binsize
e = float(r.exposure if r.exposure else self.default_exposure)
b = int(r.binsize if r.binsize else self.default_binsize)
if isinstance(r, ImageRequest):
media = self.cam.get_image(exposure=e, binsize=b)
self.callback(media, request=r)
Expand Down
13 changes: 8 additions & 5 deletions src/instamatic/experiments/fast_adt/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def __init__(
self.flatfield = flatfield
self.fast_adt_frame = experiment_frame
self.beamshift: Optional[CalibBeamShift] = None
self.binsize: int = 1
self.camera_length: int = 0

if videostream_frame is not None:
Expand Down Expand Up @@ -337,6 +338,7 @@ def start_collection(self, **params) -> None:

with self.ctrl.beam.blanked(), self.ctrl.cam.blocked():
if params['tracking_algo'] == 'manual':
self.binsize = self.ctrl.cam.default_binsize
self.runs.tracking = TrackingRun.from_params(params)
self.determine_pathing_manually()
for pathing_run in self.runs.pathing:
Expand Down Expand Up @@ -366,8 +368,8 @@ def displayed_pathing(self, step: Step) -> None:
draw = self.videostream_processor.draw
instructions: list[draw.Instruction] = []
for run_i, p in enumerate(self.runs.pathing):
x = p.table.at[step.Index, 'beampixel_x']
y = p.table.at[step.Index, 'beampixel_y']
x = p.table.at[step.Index, 'beampixel_x'] / self.binsize
y = p.table.at[step.Index, 'beampixel_y'] / self.binsize
instructions.append(draw.circle((x, y), fill='white', radius=5))
instructions.append(draw.circle((x, y), fill=get_color(run_i), radius=3))
try:
Expand All @@ -388,7 +390,7 @@ def determine_pathing_manually(self) -> None:
self.beamshift = self.get_beamshift()
self.msg1('Locate the beam (move it if needed) and click on its center.')
with self.click_listener as cl:
obs_beampixel_xy = np.array(cl.get_click().xy)
obs_beampixel_xy = np.array(cl.get_click().xy) * self.binsize
cal_beampixel_yx = self.beamshift.beamshift_to_pixelcoord(self.ctrl.beamshift.get())

self.ctrl.restore('FastADT_track')
Expand All @@ -401,11 +403,12 @@ def determine_pathing_manually(self) -> None:
self.msg1(f'Click on tracked point: {step.summary}.')
with self.displayed_pathing(step=step), self.click_listener:
click = self.click_listener.get_click()
delta_yx = (np.array(click.xy) - obs_beampixel_xy)[::-1]
click_xy = np.array(click.xy) * self.binsize
delta_yx = (click_xy - obs_beampixel_xy)[::-1]
click_beampixel_yx = cast(Sequence[float], cal_beampixel_yx + delta_yx)
click_beamshift_xy = self.beamshift.pixelcoord_to_beamshift(click_beampixel_yx)
cols = ['beampixel_x', 'beampixel_y', 'beamshift_x', 'beamshift_y']
run.table.loc[step.Index, cols] = *click.xy, *click_beamshift_xy
run.table.loc[step.Index, cols] = *click_xy, *click_beamshift_xy
tracking_frames.append(step.image)
if 'image' not in run.table:
run.table['image'] = tracking_frames
Expand Down
6 changes: 5 additions & 1 deletion src/instamatic/gui/ctrl_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,13 @@ def toggle_rmb_beam(self, _name, _index, _mode) -> None:
self.var_rmb_beam.set(False)
return

binning = self.ctrl.cam.default_binsize

def _callback(click: ClickEvent) -> None:
if click.button == MouseButton.RIGHT:
bs = calib_beamshift.pixelcoord_to_beamshift((click.y, click.x))
pixel_x = click.x * binning
pixel_y = click.y * binning
bs = calib_beamshift.pixelcoord_to_beamshift((pixel_y, pixel_x))
self.ctrl.beamshift.set(*[float(b) for b in bs])

d.add_listener('rmb_beam', _callback, active=True)
Expand Down
21 changes: 20 additions & 1 deletion src/instamatic/server/cam_server.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from __future__ import annotations

import datetime
import inspect
import logging
import queue
import socket
import threading
import traceback
import uuid

import numpy as np

Expand All @@ -19,6 +21,7 @@
if config.settings.cam_use_shared_memory:
from multiprocessing import shared_memory

_generators = {}
condition = threading.Condition()
box = []

Expand Down Expand Up @@ -97,6 +100,10 @@ def run(self):
try:
ret = self.evaluate(attr_name, args, kwargs)
status = 200
if inspect.isgenerator(ret):
gen_id = uuid.uuid4().hex
_generators[gen_id] = ret
ret = {'__generator__': gen_id}
except Exception as e:
traceback.print_exc()
if self.log:
Expand All @@ -121,7 +128,19 @@ def run(self):
def evaluate(self, attr_name: str, args: list, kwargs: dict):
"""Evaluate the function or attribute `attr_name` on `self.cam`, if
`attr_name` refers to a function, call it with *args and **kwargs."""
# print(attr_name, args, kwargs)

if attr_name == '__gen_next__':
gen = _generators[kwargs['id']]
try:
return next(gen)
except StopIteration:
del _generators[kwargs['id']]
return

if attr_name == '__gen_close__':
_generators.pop(kwargs['id'], None)
return

f = getattr(self.cam, attr_name)
return f(*args, **kwargs) if callable(f) else f

Expand Down