Skip to content
Merged
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
412 changes: 63 additions & 349 deletions docs/source/design/taviclasses.rst

Large diffs are not rendered by default.

32 changes: 7 additions & 25 deletions docs/source/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,17 @@
Graphical user interface
########################

As of version 1.0.0 the graphical user interface for the planning tool looks like
As of version 1.0.0 the graphical user interface for TAVI looks like

.. image:: images/ppt_sc.png
.. image:: images/tavi_placeholder.png
:width: 600

The user is supposed to select the incident energy, detector tank angle, and the angle between polarization and the
beam direction. This will generate a map of the angle between the polarization and momentum transfer :math:`\alpha_s`, or some
relevant derived quantity. Currently we have implemented :math:`\cos^2\alpha_s` and :math:`(1+\cos^2\alpha_s)/2`.

The user can position a crosshair at a certain momentum and energy transfer position, in order to test several possible polarization directions.
In the single crystal mode, the magnitude of momentum transfer :math:`|\vec Q|` is provided via lattice parameters and reciprocal
lattice coordinates. In the powder mode, the user enters this quantity directly.

.. image:: images/ppt_pow.png
:width: 600

One can use the plotting toolbar to
* zoom in/out/pan
* change the color range, by zooming on the colorbar
* change the colormap. Click on edit axes, customize the default plot, click on the Images tab.
One can use TAVI for:
* data visualization
* combining different scans
* performing fitting of the peaks.

Validation
----------

The graphical user interface will provide a visual feedback (red border) if some of the quantities required for calculations are missing
or outside acceptable limits. The tooltips provide this information. In addition, in the single crystal mode, the sum of the lattice angles must be greater than :math:`360^\circ`, and the sum of any two angles must be greater than the remaining one.

Negative energy transfer
------------------------

By default, the energy transfer range for the plot is given by the incident energy :math:`E_i`, from :math:`-E_i` to :math:`E_i`.
If the energy transfer of the crosshair is selected to be less than :math:`-E_i`, the new minimum will be :math:`-1.2\Delta E`.
The graphical user interface will provide a visual feedback as a pop-up window for an errors ursers might encounter.
Binary file removed docs/source/images/ppt_pow.png
Binary file not shown.
Binary file removed docs/source/images/ppt_sc.png
Binary file not shown.
Binary file added docs/source/images/tavi_placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ignore = [
"ARG002",
"ARG004",
"F821",
"D401",
"N801",
"N802",
"N806",
Expand Down
6 changes: 4 additions & 2 deletions src/tavi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from qtpy.QtWidgets import QApplication

from tavi.backend.model.interface.TaviProjectInterface import TaviProjectInterface
from tavi.backend.model.interface.tavi_project_interface import TaviProjectProxy
from tavi.backend.model.tavi_project_model import TaviProjectModel
from tavi.configuration import Configuration
from tavi.frontend.presenter.main_presenter import MainPresenter

Expand All @@ -25,7 +26,8 @@ def execute() -> None:
print(" ".join(msg))
sys.exit(-1)

dict_of_model = {"TaviProjectInterface": TaviProjectInterface()}
tavi_project_model = TaviProjectModel()
dict_of_model = {"TaviProjectProxy": TaviProjectProxy(tavi_project_model)}

presenter = MainPresenter(dict_of_model)
presenter._view.show()
Expand Down
9 changes: 0 additions & 9 deletions src/tavi/backend/model/interface/TaviProjectInterface.py

This file was deleted.

17 changes: 17 additions & 0 deletions src/tavi/backend/model/interface/tavi_project_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Tavi project interface."""

import abc

from tavi.meta.multithreading.proxy import Proxy


class TaviProjectInterface(metaclass=abc.ABCMeta):
"""Tavi project interface."""

@abc.abstractmethod
def load_raw_scan_from_folder(self) -> None:
"""Abstract method to get tavi data."""
pass


TaviProjectProxy = Proxy(TaviProjectInterface)
29 changes: 29 additions & 0 deletions src/tavi/backend/model/tavi_project_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Tavi Project."""

from tavi.backend.model.interface.tavi_project_interface import TaviProjectInterface
from tavi.event_broker.event_broker import EventBroker
from tavi.event_broker.event_type import Event
from tavi.library.data.model_response import ModelResponse, ResponseCode
from tavi.meta.decorators.singleton import Singleton


@Singleton
class TaviProjectModel(TaviProjectInterface):
"""Tavi project class."""

def __init__(self) -> None:
"""Init tavi data."""
self._event_broker = EventBroker()

def send(self, event: Event) -> None:
"""Send pre-register event to event broker."""
self._event_broker.publish(event)

def load_raw_scan_from_folder(self, folder: str) -> None:
"""Load a folder containing raw scans."""
print("folder director received by model:", folder)
# TO DO
# Implement load raw scan from folder logic
# raw_scan_loading_event = RawScanLoadingEvent(raw_scan_uuid = ...)
# self.send(raw_scan_loading_event)
return ModelResponse(code=ResponseCode.OK, message="TODO: implement loading backend")
32 changes: 32 additions & 0 deletions src/tavi/event_broker/event_broker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Event broker class.

Stub implementation, will be updated.

"""

from collections import defaultdict
from typing import Any, Literal

from tavi.meta.decorators.singleton import Singleton


@Singleton
class EventBroker:
"""Event broker class."""

def __init__(self) -> None:
"""Initialize event broker."""
if not hasattr(self, "registry"):
self.registry = defaultdict(list)

def register(self, event_type: Any, callable: Literal["event_type"]) -> None:
"""Register event with the broker."""
self.registry[event_type].append(callable)

def publish(self, event: Any) -> None:
"""Publish event to the broker."""
event_type = type(event)
if callable_list := self.registry.get(event_type):
for callable in callable_list:
callable(event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I was saying at status, there are a couple more features outlined in the EventBroker ticket that the prototype did not implement. You can view my additions here. I still need to write tests for it.

Seeing as the event broker is to be implemented in a different ticket, could you mark this as a stub implementation? I dont see any unit tests written for this explicitly, which is fine, that will be added as part of the event broker ticket.

16 changes: 16 additions & 0 deletions src/tavi/event_broker/event_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Define event type here."""

from attr import dataclass


class Event:
"""Docstring for Event."""

pass


@dataclass
class RawScanLoadingEvent(Event):
"""loading raw data event."""

raw_scan_uuid: list[str]
8 changes: 4 additions & 4 deletions src/tavi/frontend/presenter/file_menu_presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from tavi.frontend.view.file_menu_view import FileMenu

if TYPE_CHECKING:
from tavi.backend.model.interface.TaviProjectInterface import TaviProjectInterface
from tavi.backend.model.interface.tavi_project_interface import TaviProjectInterface


class FileMenuPresenter:
Expand Down Expand Up @@ -36,7 +36,7 @@ def __init__(self, exit_routine: Any, model: TaviProjectInterface) -> None:
self._view.setup_callback_load_folder(self.handle_load_folder)
self._view.setup_callback_exit(self.exit)

def handle_load_folder(self, folder_dir: list[str]) -> None:
def handle_load_folder(self, folder: list[str]) -> None:
"""
Handle folder-loading requests from the view.

Expand All @@ -47,13 +47,13 @@ def handle_load_folder(self, folder_dir: list[str]) -> None:

Parameters
----------
folder_dir : list[str]
folder : list[str]
A list containing one or more filesystem paths. Only the first entry
is used, as Qt's `QFileDialog` returns a list even when selecting a
single folder.

"""
self._model.print()
self._model.load_raw_scan_from_folder(folder)

def exit(self) -> None:
"""Exit in menu."""
Expand Down
44 changes: 44 additions & 0 deletions src/tavi/frontend/presenter/load_raw_scan_presenter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Load raw scan presenter."""

from __future__ import annotations

from typing import TYPE_CHECKING

from tavi.event_broker.event_broker import EventBroker
from tavi.event_broker.event_type import RawScanLoadingEvent

if TYPE_CHECKING:
from tavi.backend.model.interface.tavi_project_interface import TaviProjectInterface
from tavi.frontend.view.load_raw_scan_view import LoadView


class LoadRawScanPresenter:
"""
Presenter responsible for data loading.

Mediating dataloading-related updates between the
model (`TaviProjectInterface`) and the load_raw_scan_view (`LoadView`).

Attributes
----------
_view : LoadView
The load view associated with this presenter.
_model : TaviProjectInterface
The model providing metadata updates.
event_broker : EventBroker
The event system used to subscribe to different loading data update events.

"""

def __init__(self, view: LoadView, model: TaviProjectInterface) -> None:
"""Initialize the metadata presenter and register for `meta_data` events."""
super().__init__()
self._view = view
self._model = model
self.event_broker = EventBroker()
self.event_broker.register(RawScanLoadingEvent, self.update_treeview_data)

def update_treeview_data(self, event: RawScanLoadingEvent) -> None:
"""Update the treeview GUI after loading complete."""
# TODO: implement rules to display tavi data after backend story
print("TODO: Implement rules to display loaded data after backend story.")
6 changes: 5 additions & 1 deletion src/tavi/frontend/presenter/main_presenter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Main presenter for tavi."""

from tavi.frontend.presenter.file_menu_presenter import FileMenuPresenter
from tavi.frontend.presenter.load_raw_scan_presenter import LoadRawScanPresenter
from tavi.frontend.view.main_view import TaviView
from tavi.frontend.view.menubar_view import MainMenuBar

Expand All @@ -12,10 +13,13 @@ def __init__(self, model_dict: dict) -> None:
"""Init main views."""
self._view = TaviView()
self._view.exit_requested.connect(self.exit)
self.file_menu_presenter = FileMenuPresenter(self.exit, model=model_dict["TaviProjectInterface"])
self.file_menu_presenter = FileMenuPresenter(self.exit, model=model_dict["TaviProjectProxy"])
menu_bar = MainMenuBar(self._view, file_menu_view=self.file_menu_presenter._view)
self._view.install_menu_bar(menu_bar)

self.load_raw_scan_view = self._view.main_window.load_view
self.load_raw_scan_presenter = LoadRawScanPresenter(self.load_raw_scan_view, model_dict["TaviProjectProxy"])

def exit(self) -> bool:
"""
Presenter handles dirty-save confirmation.
Expand Down
4 changes: 2 additions & 2 deletions src/tavi/frontend/view/file_menu_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def __init__(self, parent: Any = None) -> None:
self.setTitle("File")
self.new_project_action = QAction("New Project", self)
self.load_project_action = QAction("Load Project", self)
self.load_file_action = QAction("Load File(s)", self)
self.load_folder_action = QAction("Load Folder", self)
self.load_file_action = QAction("Load Data File(s)", self)
self.load_folder_action = QAction("Load Experiment Folder", self)
self.save_action = QAction("Save Project", self)
self.exit_action = QAction("Exit", self)

Expand Down
Loading