diff --git a/common/pyproject.toml b/common/pyproject.toml
index a61a7c41..e586c797 100644
--- a/common/pyproject.toml
+++ b/common/pyproject.toml
@@ -8,7 +8,7 @@ dependencies = [
"pydantic==2.11.4",
"numpy==2.2.5",
"jfjoch_client==1.0.0rc51",
- "aareDB==0.1.1a9"
+ "aareDB==0.1.1a11"
]
[lint]
diff --git a/common/src/aaredaqlib/raster_grid.py b/common/src/aaredaqlib/raster_grid.py
index b3692cf2..655d9377 100644
--- a/common/src/aaredaqlib/raster_grid.py
+++ b/common/src/aaredaqlib/raster_grid.py
@@ -1,4 +1,4 @@
-from typing import Annotated, List
+from typing import Annotated
from jfjoch_client.models.scan_result import ScanResult
from pydantic import Field, AfterValidator, BaseModel
@@ -6,6 +6,7 @@ from pydantic import Field, AfterValidator, BaseModel
from aaredaqlib.coordinate import Coordinate, positive_coords, SmargonCoordinate
from aaredaqlib.sample_geometry import SampleGeometryModel
+
class RasterGridRequest(BaseModel):
dtz: float | None = None
transmission: float | None = None
diff --git a/common/src/aaredaqlib/rotation_scan.py b/common/src/aaredaqlib/rotation_scan.py
index a6c60f62..13885d12 100644
--- a/common/src/aaredaqlib/rotation_scan.py
+++ b/common/src/aaredaqlib/rotation_scan.py
@@ -1,3 +1,4 @@
+from jfjoch_client.models.scan_result import ScanResult
from pydantic import BaseModel
from aaredaqlib.coordinate import SmargonCoordinate
@@ -18,3 +19,7 @@ class RotationScanRequest(BaseModel):
screening: bool = False
wedge_omega_deg: float | None = None
+
+class CompletedRotationScan(BaseModel):
+ request: RotationScanRequest
+ result: ScanResult
\ No newline at end of file
diff --git a/daq/src/aaredaq/aaredb.py b/daq/src/aaredaq/aaredb.py
index 18178a6c..d5c57f44 100644
--- a/daq/src/aaredaq/aaredb.py
+++ b/daq/src/aaredaq/aaredb.py
@@ -67,47 +67,19 @@ class AareWrapper:
)
print(ret)
- def create_manual_sample(
- self,
- s: SampleShortInfo,
- p: SessionStatus,
- cellparameters: Optional[str] = None,
- spacegroupnumber: Optional[int] = None,
- ) -> Optional[object]:
- """
- Insert a new manual sample into the database using a SampleShortInfo object.
-
- Args:
- s (SampleShortInfo): The sample info object to extract data from.
- p (SessionStatus): The session status object to extract data from.
- cellparameters (Optional[str]): Unit cell parameters as a string.
- spacegroupnumber (Optional[int]): Space group number.
-
- Returns:
- API response object or None if there was an error.
- """
- from aareDBclient.models import ManualSampleCreate, DataCollectionParameters
-
- data_collection_parameters = None
- if cellparameters is not None and spacegroupnumber is not None:
- data_collection_parameters = DataCollectionParameters(
- cellparameters=cellparameters,
- spacegroupnumber=spacegroupnumber
- )
+ def create_manual_sample(self, s: SampleShortInfo):
+ from aareDBclient.models import ManualSampleCreate
manual_sample = ManualSampleCreate(
- pgroup=p.current_pgroup,
+ pgroup=s.user,
sample_name=s.sample_name,
- data_collection_parameters=data_collection_parameters
+ data_collection_parameters=s.data_collection_parameters
)
try:
- result = self.__sample_api.insert_sample(manual_sample)
- print(f"Sample created: {result}")
- return result
+ s.db_id = self.__sample_api.insert_sample(manual_sample).id
except Exception as e:
print(f"Error inserting sample: {e}")
- return None
def sample_mounted(self, s: Optional[SampleShortInfo]):
if s is not None:
diff --git a/daq/src/aaredaq/daq.py b/daq/src/aaredaq/daq.py
index 73785bd6..90cd6246 100644
--- a/daq/src/aaredaq/daq.py
+++ b/daq/src/aaredaq/daq.py
@@ -23,7 +23,7 @@ from aaredaqlib.models import (
SampleShortInfoList,
DAQStatusModel, BeamlineStatus, SessionStatus, SampleCameraSettings, AutofocusSettings, )
from aaredaqlib.raster_grid import RasterGridRequest, CompletedRasterGrid
-from aaredaqlib.rotation_scan import RotationScanRequest
+from aaredaqlib.rotation_scan import RotationScanRequest, CompletedRotationScan
from aaredaqlib.sample_geometry import SampleGeometryModel
from mxlibs3.jfjoch import JFJochWrapper
@@ -176,6 +176,16 @@ class AareDAQ:
self.__cfg.state_busy = False
raise
+ def create_sample(self, target: SampleShortInfo):
+ self.__aare.create_manual_sample(target)
+ self.__cfg.current_sample = target
+
+ def clear_current_sample(self):
+ curr_sample = self.__cfg.current_sample
+ if curr_sample is not None:
+ self.__aare.sample_unmounted(curr_sample)
+ self.__cfg.current_sample = None
+
def __mount(self, target: SampleShortInfo | None):
self.__set_state(BeamlineStateEnum.RobotSampleExchange)
self.__devs.smargon.move_home(wait=True)
@@ -192,8 +202,11 @@ class AareDAQ:
curr_sample = self.__cfg.current_sample
self.__devs.tell.wait_ready()
-
- if target is None:
+ if curr_sample is not None and curr_sample.location is None:
+ # TODO: Smarter way to know manual sample has be removed from goniometer
+ self.__aare.sample_unmounted(curr_sample)
+ self.__cfg.current_sample = None
+ elif target is None:
self.__devs.tell.unmount(wait=True, timeout=360)
self.__aare.sample_unmounted(curr_sample)
self.__cfg.current_sample = None
@@ -300,7 +313,7 @@ class AareDAQ:
self.__cfg.state_busy = False
raise e
- def __rotation(self, request: RotationScanRequest):
+ def __rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
self.__cfg.dtz = request.dtz
self.__set_state(BeamlineStateEnum.DataCollection)
@@ -343,18 +356,20 @@ class AareDAQ:
time.sleep(smargon_time_step)
self.__devs.aerotech.wait_scan_done(total_time + 60)
- self.__jfjoch.wait_till_done(60)
self.__aare.sample_collected(self.sample)
self.__devs.aerotech.reset()
+ result = self.__jfjoch.wait_till_done(60)
+ return CompletedRotationScan(request=copy.deepcopy(request), result=result)
- def measure_rotation(self, request: RotationScanRequest):
+ def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
total_time = request.exp_time_s * request.steps
self.__cfg.try_set_busy(timeout=ceil(total_time + 360))
try:
- self.__rotation(request)
+ result = self.__rotation(request)
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__cfg.state_busy = False
+ return result
except Exception as e:
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__cfg.state_busy = False
diff --git a/daq/src/aaredaq/server.py b/daq/src/aaredaq/server.py
index 74c8bf53..ecc176b3 100644
--- a/daq/src/aaredaq/server.py
+++ b/daq/src/aaredaq/server.py
@@ -9,7 +9,7 @@ from aaredaqlib.coordinate import SmargonCoordinate, Coordinate
from aaredaqlib.models import SampleShortInfo, DAQStatusModel, BeamlineStateEnum, BeamlineSettingsModel, \
SampleShortInfoList, SessionStatus, SampleCameraSettings, AutofocusSettings, TokenData
from aaredaqlib.raster_grid import RasterGridRequest, CompletedRasterGrid
-from aaredaqlib.rotation_scan import RotationScanRequest
+from aaredaqlib.rotation_scan import RotationScanRequest, CompletedRotationScan
from aaredaqlib.sample_geometry import SampleGeometryModel
from fastapi import FastAPI, Depends
from fastapi import HTTPException
@@ -162,7 +162,7 @@ async def get_image(token: str = Depends(oauth2_scheme)):
# TELL procedures
-@app.get("/tell/sample")
+@app.get("/sample/curr_sample")
async def sample(token: str = Depends(oauth2_scheme)) -> SampleShortInfo:
token_data = auth.parse_token(token)
auth.check_jwt_ro(cfg, auth.parse_token(token))
@@ -175,12 +175,12 @@ async def sample(token: str = Depends(oauth2_scheme)) -> SampleShortInfo:
puck_name="",
dewar_name="",
db_id=-1,
- pin=sample.pin,
- location=sample.location
+ pin=s.pin,
+ location=s.location
)
-@app.post("/tell/mount")
+@app.post("/sample/mount")
async def mount(dbid: int, token: str = Depends(oauth2_scheme)):
token_data = auth.parse_token(token)
auth.check_jwt_rw(cfg, auth.parse_token(token))
@@ -204,12 +204,25 @@ async def mount(dbid: int, token: str = Depends(oauth2_scheme)):
)
-@app.post("/tell/unmount")
+@app.post("/sample/unmount")
async def unmount(token: str = Depends(oauth2_scheme)):
auth.check_jwt_rw(cfg, auth.parse_token(token))
daq.sample = None
return "OK"
+@app.post("/sample/clear")
+async def sample_clear(token: str = Depends(oauth2_scheme)):
+ auth.check_jwt_rw(cfg, auth.parse_token(token))
+ daq.clear_current_sample()
+ return "OK"
+
+@app.post("/sample/manual")
+async def manual(s: SampleShortInfo, token: str = Depends(oauth2_scheme)):
+ auth.check_jwt_rw(cfg, auth.parse_token(token))
+ print(f"DB ID prior creating {s.db_id}")
+ daq.create_sample(s)
+ print(f"DB ID after creating {s.db_id}")
+
def get_spreadsheet(data: TokenData) -> SampleShortInfoList:
if data.staff:
@@ -242,7 +255,7 @@ async def spreadsheet_sse(token: str = Depends(oauth2_scheme)):
)
-@app.get("/tell/spreadsheet")
+@app.get("/sample/spreadsheet")
async def spreadsheet(token: str = Depends(oauth2_scheme)) -> SampleShortInfoList:
return get_spreadsheet(auth.parse_token(token))
@@ -280,10 +293,9 @@ async def raster(val: RasterGridRequest, token: str = Depends(oauth2_scheme)) ->
@app.post("/scan/rotation")
-async def rotation(val: RotationScanRequest, token: str = Depends(oauth2_scheme)):
+async def rotation(val: RotationScanRequest, token: str = Depends(oauth2_scheme)) -> CompletedRotationScan:
auth.check_jwt_rw(cfg, auth.parse_token(token))
- daq.measure_rotation(val)
- return "OK"
+ return daq.measure_rotation(val)
@app.post("/scan/auto")
async def auto(s: SampleShortInfo, token: str = Depends(oauth2_scheme)):
diff --git a/gui/src/aaregui/main_window.py b/gui/src/aaregui/main_window.py
index 9910bb4e..c68e0cdf 100644
--- a/gui/src/aaregui/main_window.py
+++ b/gui/src/aaregui/main_window.py
@@ -21,6 +21,7 @@ from aaregui.panels.data_collection_settings import DataCollectionSettings
from aaregui.panels.sample_queue_panel import SampleQueuePanel
from aaregui.panels.tell_sample_panel import TellSamplePanel
from aaregui.scan_logic.raster_grid_manager import RasterGridManager
+from aaregui.scan_logic.rotation_scan_manager import RotationScanManager
from aaregui.scan_logic.sample_mount_logic import SampleMountLogic
from aaregui.threads.camera_thread import SampleCameraThread
from aaregui.threads.daq_worker import DAQWorker
@@ -73,6 +74,7 @@ class MainWindow(QMainWindow):
)
self.raster = RasterGridManager(geom=geom)
+ self.rotation = RotationScanManager()
collection_controls_scroll = NoWheelScrollArea(top_widget)
@@ -130,8 +132,6 @@ class MainWindow(QMainWindow):
self.daq = DAQWorker(base_url=self.__base_url, token=self.__token)
self.daq.spreadsheet.connect(self.tell_samples.new_sample_list)
- self.daq.raster_scan_completed.connect(self.raster.grid_scan_completed)
-
self.beamline.samcam.changed.connect(self.daq.samcam_settings)
self.beamline.loopctr.background.clicked.connect(self.daq.alc_background)
self.beamline.loopctr.find_tip.clicked.connect(self.daq.center_loop)
@@ -179,7 +179,7 @@ class MainWindow(QMainWindow):
self.daq.run_number_incremented.connect(self.data_collection.file_path_panel.increment_run_number)
self.job_list_panel.auto_scan.connect(self.daq.automated_scan)
- self.daq.automated_scan_done.connect(self.job_list_panel.automated_scan_done)
+
self.job_list_panel.unmount.connect(self.daq.unmount)
self.tell_samples.mount.connect(self.daq.mount)
@@ -208,6 +208,7 @@ class MainWindow(QMainWindow):
self.status_bar.close_shutter.connect(self.daq.close_shutter)
self.status_bar.open_shutter.connect(self.daq.open_shutter)
self.raster.file_ready.connect(self.viewer.load_file)
+ self.rotation.file_ready.connect(self.viewer.load_file)
self.raster.image_selected.connect(self.viewer.load_image)
self.sample_logic.sample_changed.connect(self.data_collection.file_path_panel.update_sample)
@@ -227,7 +228,10 @@ class MainWindow(QMainWindow):
self.daq.update.connect(self.beamline.zoom_panel.update_daq_status)
self.daq.update.connect(self.beamline.beam_mark.update_daq_status)
self.daq.update.connect(self.sample_logic.update_daq_status)
- self.daq.standard_scan_completed.connect(self.viewer.load_file)
+
+ self.daq.standard_scan_completed.connect(self.rotation.scan_completed)
+ self.daq.automated_scan_done.connect(self.job_list_panel.automated_scan_done)
+ self.daq.raster_scan_completed.connect(self.raster.grid_scan_completed)
def create_menu_bar(self):
"""Create a menu bar with File->Quit and Help->About."""
diff --git a/gui/src/aaregui/panels/manual_sample_panel.py b/gui/src/aaregui/panels/manual_sample_panel.py
new file mode 100644
index 00000000..8ba9a9e0
--- /dev/null
+++ b/gui/src/aaregui/panels/manual_sample_panel.py
@@ -0,0 +1,41 @@
+from PySide6.QtCore import Signal, Slot
+from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QTextEdit, QPushButton
+
+from aaredaqlib.models import SampleShortInfo
+from aaregui.widgets.title_label import TitleLabel
+
+
+class OmegaPanel(QWidget):
+ sample_manual = Signal(SampleShortInfo)
+ sample_clear = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.__sample = SampleShortInfo(
+ db_id = -1,
+ puck_name="",
+ dewar_name="",
+ sample_name="Manual",
+ pin=0
+ )
+
+ grid_layout = QGridLayout(self)
+
+ grid_layout.addWidget(TitleLabel("Manual sample", self), 0, 0, 1, 2)
+
+ grid_layout.addWidget(QLabel("Name"), 1, 0)
+ self._text_name = QTextEdit(self.__sample.sample_name)
+ grid_layout.addWidget(self._text_name, 1, 1)
+ self._text_name.textChanged.connect(self._name_changed)
+ self._add_button = QPushButton("Add")
+ grid_layout.addWidget(self._add_button, 2, 0, 1 ,2)
+ self._add_button.clicked.connect(self._add_clicked)
+
+ @Slot(str)
+ def _name_vhanged(self, s: str):
+ self.__sample.sample_name = s
+
+ @Slot()
+ def _add_clicked(self):
+ self.sample_manual.emit(self.__sample)
+
diff --git a/gui/src/aaregui/panels/rotation_data_collection.py b/gui/src/aaregui/panels/rotation_data_collection.py
index 16c24042..f03cc32f 100644
--- a/gui/src/aaregui/panels/rotation_data_collection.py
+++ b/gui/src/aaregui/panels/rotation_data_collection.py
@@ -37,7 +37,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
self._layout.addWidget(QLabel("