From fe30651dd2635fb9702b8157f692ebb5fd9fa5b3 Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Mon, 19 Feb 2024 02:28:27 -0500 Subject: [PATCH 1/3] update styles --- tomopyui/widgets/analysis.py | 27 +++++++++++------------ tomopyui/widgets/center.py | 18 ++++++---------- tomopyui/widgets/dataexplorer.py | 3 +-- tomopyui/widgets/hdf_imports.py | 6 +++--- tomopyui/widgets/prep.py | 37 ++++++++++++++------------------ tomopyui/widgets/styles.py | 9 ++++++++ tomopyui/widgets/tools.py | 25 ++++++--------------- tomopyui/widgets/view.py | 33 ++++++++++++++-------------- 8 files changed, 72 insertions(+), 86 deletions(-) create mode 100644 tomopyui/widgets/styles.py diff --git a/tomopyui/widgets/analysis.py b/tomopyui/widgets/analysis.py index 58b885f..92e87de 100644 --- a/tomopyui/widgets/analysis.py +++ b/tomopyui/widgets/analysis.py @@ -6,7 +6,6 @@ from tomopyui._sharedvars import ( tomopy_algorithm_kwargs, astra_cuda_recon_algorithm_kwargs, - extend_description_style, ) from tomopyui.backend.io import Metadata_Align, Metadata_Recon, Projections_Child @@ -16,6 +15,11 @@ BqImViewer_Projections_Child, BqImViewer_Projections_Parent, ) +from tomopyui.widgets.styles import ( + header_font_style, + button_font_style, + extend_description_style, +) class AnalysisBase(ABC): @@ -53,12 +57,6 @@ def init_attributes(self, Import, Center): key for key in astra_cuda_recon_algorithm_kwargs ] self.run_list = [] - self.header_font_style = { - "font_size": "22px", - "font_weight": "bold", - "font_variant": "small-caps", - # "text_color": "#0F52BA", - } self.accordions_open = False self.plot_output1 = Output() @@ -66,34 +64,33 @@ def init_widgets(self): """ Initializes many of the widgets in the Alignment and Recon tabs. """ - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") # -- Button to turn on tab --------------------------------------------- self.open_accordions_button = Button( icon="lock-open", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) # -- Headers for plotting ------------------------------------- self.import_plot_header = "Imported Projections" self.import_plot_header = Label( - self.import_plot_header, style=self.header_font_style + self.import_plot_header, style=header_font_style ) self.altered_plot_header = "Altered Projections" self.altered_plot_header = Label( - self.altered_plot_header, style=self.header_font_style + self.altered_plot_header, style=header_font_style ) # -- Headers for results ------------------------------------- self.before_analysis_plot_header = "Analysis Projections" self.before_analysis_plot_header = Label( - self.before_analysis_plot_header, style=self.header_font_style + self.before_analysis_plot_header, style=header_font_style ) self.after_analysis_plot_header = "Result" self.after_analysis_plot_header = Label( - self.after_analysis_plot_header, style=self.header_font_style + self.after_analysis_plot_header, style=header_font_style ) # -- Button to load metadata ---------------------------------------------- @@ -451,7 +448,7 @@ def containerize(self): # -- Methods ---------------------------------------------------------- self.tomopy_methods_hbox = VBox( [ - Label("Tomopy", style=self.header_font_style), + Label("Tomopy", style=header_font_style), VBox( self.tomopy_methods_checkboxes, layout=Layout(flex_flow="column wrap", align_content="flex-start"), @@ -462,7 +459,7 @@ def containerize(self): self.astra_methods_hbox = VBox( [ - Label("Astra", style=self.header_font_style), + Label("Astra", style=header_font_style), VBox( self.astra_cuda_methods_checkboxes, layout=Layout(flex_flow="column wrap"), diff --git a/tomopyui/widgets/center.py b/tomopyui/widgets/center.py index 31bf26e..7288710 100644 --- a/tomopyui/widgets/center.py +++ b/tomopyui/widgets/center.py @@ -14,6 +14,7 @@ from tomopyui.backend.util.center import write_center from tomopyui.widgets.helpers import ReactiveIconButton, ReactiveTextButton from tomopyui.widgets.view import BqImViewer_Center, BqImViewer_Center_Recon +from tomopyui.widgets.styles import header_font_style, button_font_style class Center: @@ -71,11 +72,6 @@ def __init__(self, Import): self.rec_viewer = BqImViewer_Center_Recon() self.rec_viewer.create_app() self.reg = None - self.header_font_style = { - "font_size": "22px", - "font_weight": "bold", - "font_variant": "small-caps", - } self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") self._init_widgets() @@ -180,21 +176,21 @@ def _init_widgets(self): ) self.projections_plot_header = "Projections" self.projections_plot_header = Label( - self.projections_plot_header, style=self.header_font_style + self.projections_plot_header, style=header_font_style ) self.reconstructions_plot_header = "Reconstructions" self.reconstructions_plot_header = Label( - self.reconstructions_plot_header, style=self.header_font_style + self.reconstructions_plot_header, style=header_font_style ) # -- Centers display ----------------------------------------------------------- - self.center_select_label = Label("Slice : Center", style=self.header_font_style) + self.center_select_label = Label("Slice : Center", style=header_font_style) self.center_select = Select( disabled=True, rows=10, ) self.all_centers_select_label = Label( - "Centers for reconstruction", style=self.header_font_style + "Centers for reconstruction", style=header_font_style ) self.all_centers_select = Select( disabled=True, @@ -205,7 +201,7 @@ def _init_widgets(self): icon="fa-minus-square", tooltip="Remove selected center.", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.buttons_to_disable = [ self.center_select, @@ -217,7 +213,7 @@ def _init_widgets(self): tooltip="Add center from reconstruction of this slice.", skip_during=True, layout=self.button_layout, - style=self.button_font, + style=button_font_style, disabled=False, ) self.add_center_button.button.disabled = True diff --git a/tomopyui/widgets/dataexplorer.py b/tomopyui/widgets/dataexplorer.py index 973920f..8d46625 100644 --- a/tomopyui/widgets/dataexplorer.py +++ b/tomopyui/widgets/dataexplorer.py @@ -212,7 +212,6 @@ def __init__(self): self.quick_path_search.observe( self.update_filechooser_from_quicksearch, names="value" ) - self.quick_path_label = Label("Quick path search") # subdirectory selector self.subdir_list = [] @@ -369,7 +368,7 @@ def load_metadata(self): def create_app(self): quickpath = VBox( [ - self.quick_path_label, + widgets.Label("Quick path search:"), self.quick_path_search, ], layout=Layout(align_items="center"), diff --git a/tomopyui/widgets/hdf_imports.py b/tomopyui/widgets/hdf_imports.py index 5e7a05c..e29c179 100644 --- a/tomopyui/widgets/hdf_imports.py +++ b/tomopyui/widgets/hdf_imports.py @@ -9,6 +9,7 @@ from tomopyui.widgets.hdf_viewer import * from tomopyui.widgets.helpers import ToggleIconButton from tomopyui.widgets.imports import UploaderBase +from tomopyui.widgets.styles import button_font_style class HDF5_GeneralUploader(UploaderBase): @@ -43,20 +44,19 @@ def init_state(self): self.turn_off_callbacks = False def init_widgets(self): - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") self.files_sel = Select() # Go faster on play button self.home_button: ipywidgets.Button = Button( icon="home", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Speed up play slider.", ) self.reset_button: ipywidgets.Button = Button( icon="redo", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Reset to original.", ) self.tree_output = Output() diff --git a/tomopyui/widgets/prep.py b/tomopyui/widgets/prep.py index b0ab8ed..8450630 100644 --- a/tomopyui/widgets/prep.py +++ b/tomopyui/widgets/prep.py @@ -20,6 +20,9 @@ from ..tomocupy.prep.alignment import shift_prj_cp, batch_cross_correlation from ..tomocupy.prep.sampling import shrink_and_pad_projections +from tomopyui.widgets.styles import header_font_style, button_font_style + + import tomopy.misc.corr as tomocorr @@ -49,12 +52,6 @@ def init_widgets(self): """ Initializes widgets in the Prep tab. """ - self.header_font_style = { - "font_size": "22px", - "font_weight": "bold", - "font_variant": "small-caps", - } - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") # -- Main viewers -------------------------------------------------------------- @@ -68,18 +65,16 @@ def init_widgets(self): # -- Headers for plotting ------------------------------------- self.import_plot_header = "Imported Projections" self.import_plot_header = Label( - self.import_plot_header, style=self.header_font_style + self.import_plot_header, style=header_font_style ) self.altered_plot_header = "Altered Projections" self.altered_plot_header = Label( - self.altered_plot_header, style=self.header_font_style + self.altered_plot_header, style=header_font_style ) # -- Header for methods ------------------------------------- self.prep_list_header = "Methods" - self.prep_list_header = Label( - self.prep_list_header, style=self.header_font_style - ) + self.prep_list_header = Label(self.prep_list_header, style=header_font_style) # -- Prep List ------------------------------------- self.prep_list_select = Select( @@ -93,21 +88,21 @@ def init_widgets(self): icon="arrow-up", tooltip="Move method up.", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.down_button = Button( disabled=True, icon="arrow-down", tooltip="Move method down.", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.remove_method_button = Button( disabled=True, icon="fa-minus-square", tooltip="Remove selected method.", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.start_button = Button( disabled=True, @@ -118,7 +113,7 @@ def init_widgets(self): ), icon="fa-running", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.preview_only_button = Button( disabled=True, @@ -129,7 +124,7 @@ def init_widgets(self): ), icon="glasses", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.save_on_button = Button( disabled=True, @@ -137,7 +132,7 @@ def init_widgets(self): tooltip=("Turn this on to save the data when you click the run button."), icon="fa-file-export", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.methods_button_box = VBox( [ @@ -191,14 +186,14 @@ def init_widgets(self): # -- Shifts uploader -------------------------------------------------------- self.shifts_uploader = ShiftsUploader(self) self.shift_x_header = "Shift in X" - self.shift_x_header = Label(self.shift_x_header, style=self.header_font_style) + self.shift_x_header = Label(self.shift_x_header, style=header_font_style) self.shifts_sx_select = Select( options=[], rows=10, disabled=True, ) self.shift_y_header = "Shift in Y" - self.shift_y_header = Label(self.shift_y_header, style=self.header_font_style) + self.shift_y_header = Label(self.shift_y_header, style=header_font_style) self.shifts_sy_select = Select( options=[], rows=10, @@ -206,7 +201,7 @@ def init_widgets(self): ) self.shifts_filechooser_label = "Filechooser" self.shifts_filechooser_label = Label( - self.shifts_filechooser_label, style=self.header_font_style + self.shifts_filechooser_label, style=header_font_style ) # -- List manipulation --------------------------------------------------------- @@ -560,7 +555,7 @@ def make_tab(self): [ VBox( [ - self.shifts_uploader.quick_path_label, + widgets.Label("Quick path search:"), self.shifts_uploader.quick_path_search, ], ), diff --git a/tomopyui/widgets/styles.py b/tomopyui/widgets/styles.py new file mode 100644 index 0000000..2c84323 --- /dev/null +++ b/tomopyui/widgets/styles.py @@ -0,0 +1,9 @@ +header_font_style = { + "font_size": "22px", + "font_weight": "bold", + "font_variant": "small-caps", +} + +button_font_style = {"font_size": "22px"} + +extend_description_style = {"description_width": "auto", "font_family": "Helvetica"} diff --git a/tomopyui/widgets/tools.py b/tomopyui/widgets/tools.py index 4cab2c1..2d08924 100644 --- a/tomopyui/widgets/tools.py +++ b/tomopyui/widgets/tools.py @@ -16,6 +16,7 @@ from tomopyui.widgets.helpers import import_module_set_env from tomopyui.widgets.imports import TwoEnergyUploader from tomopyui.widgets.view import BqImViewer_TwoEnergy_High, BqImViewer_TwoEnergy_Low +from tomopyui.widgets.styles import header_font_style, button_font_style import_module_set_env(cuda_import_dict) if os.environ["cuda_enabled"] == "True": @@ -36,17 +37,11 @@ def init_attributes(self): self.metadata = Metadata_TwoE() def init_widgets(self): - self.header_font_style = { - "font_size": "22px", - "font_weight": "bold", - "font_variant": "small-caps", - } - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") self.high_e_header = "Shifted High Energy Projections" - self.high_e_header = Label(self.high_e_header, style=self.header_font_style) + self.high_e_header = Label(self.high_e_header, style=header_font_style) self.low_e_header = "Moving Low Energy Projections" - self.low_e_header = Label(self.low_e_header, style=self.header_font_style) + self.low_e_header = Label(self.low_e_header, style=header_font_style) self.high_e_viewer = BqImViewer_TwoEnergy_High() self.low_e_viewer = BqImViewer_TwoEnergy_Low(self.high_e_viewer) self.low_e_viewer.scale_button.on_click(self.scale_low_e) @@ -59,7 +54,7 @@ def init_widgets(self): [ VBox( [ - self.high_e_uploader.quick_path_label, + widgets.Label("Quick path search:"), HBox( [ self.high_e_uploader.quick_path_search, @@ -71,7 +66,7 @@ def init_widgets(self): ), VBox( [ - self.low_e_uploader.quick_path_label, + widgets.Label("Quick path search:"), HBox( [ self.low_e_uploader.quick_path_search, @@ -250,17 +245,11 @@ def init_attributes(self): self.metadata = Metadata_TwoE() def init_widgets(self): - self.header_font_style = { - "font_size": "22px", - "font_weight": "bold", - "font_variant": "small-caps", - } - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") self.high_e_header = "Shifted High Energy Projections" - self.high_e_header = Label(self.high_e_header, style=self.header_font_style) + self.high_e_header = Label(self.high_e_header, style=header_font_style) self.low_e_header = "Moving Low Energy Projections" - self.low_e_header = Label(self.low_e_header, style=self.header_font_style) + self.low_e_header = Label(self.low_e_header, style=header_font_style) self.uploader = HDF5_MultipleEnergyUploader() self.high_e_viewer = self.uploader.viewer1 self.low_e_viewer = self.uploader.viewer2 diff --git a/tomopyui/widgets/view.py b/tomopyui/widgets/view.py index b969767..df71e6a 100644 --- a/tomopyui/widgets/view.py +++ b/tomopyui/widgets/view.py @@ -12,7 +12,9 @@ from bqplot_image_gl.interacts import MouseInteraction, keyboard_events, mouse_events from ipywidgets import * from skimage.transform import rescale # look for better option + from tomopyui._sharedvars import extend_description_style +from tomopyui.widgets.styles import button_font_style class BqImViewerBase(ABC): @@ -111,7 +113,6 @@ def _init_fig(self): def _init_widgets(self): # Styles and layouts - self.button_font = {"font_size": "22px"} self.button_layout = Layout(width="45px", height="40px") # Image index slider @@ -136,7 +137,7 @@ def _init_widgets(self): self.plus_button = Button( icon="plus", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Speed up play slider.", ) @@ -144,7 +145,7 @@ def _init_widgets(self): self.minus_button = Button( icon="minus", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Slow down play slider slider.", ) @@ -157,7 +158,7 @@ def _init_widgets(self): self.swap_axes_button = Button( icon="random", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Swap axes", ) @@ -165,7 +166,7 @@ def _init_widgets(self): self.rm_high_low_int_button = Button( icon="adjust", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Remove high and low intensities from view.", ) @@ -173,7 +174,7 @@ def _init_widgets(self): self.save_movie_button = Button( icon="file-video", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Save a movie of these images.", ) @@ -189,14 +190,14 @@ def _init_widgets(self): self.reset_button = Button( icon="redo", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Reset to original view.", ) self.rectangle_selector_button = Button( icon="far square", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Select a region of interest.", ) # Rectangle selector @@ -597,7 +598,7 @@ def __init__(self, viewer_parent): self.copy_button = Button( icon="file-import", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Copy data from 'Imported Projections'.", ) self.copy_button.on_click(self.copy_parent_projections) @@ -605,7 +606,7 @@ def __init__(self, viewer_parent): self.link_plotted_projections_button = Button( icon="unlink", layout=self.button_layout, - style=self.button_font, + style=button_font_style, disabled=True, tooltip="Link the sliders together.", ) @@ -615,7 +616,7 @@ def __init__(self, viewer_parent): self.range_from_parent_button = Button( icon="object-ungroup", layout=self.button_layout, - style=self.button_font, + style=button_font_style, disabled=True, tooltip="Get range from 'Imported Projections'.", ) @@ -956,7 +957,7 @@ def make_buttons(self): self.diff_button = Button( icon="black-tie", layout=self.button_layout, - style=self.button_font, + style=button_font_style, tooltip="Take the difference of the high and low energies.", disabled=False, ) @@ -964,7 +965,7 @@ def make_buttons(self): icon="unlink", tooltip="Link to the high energy slider.", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.link_plotted_projections_button.on_click(self.link_plotted_projections) self.plots_linked = False @@ -974,7 +975,7 @@ def make_buttons(self): button_style="", disabled=True, layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.start_button = Button( disabled=True, @@ -982,7 +983,7 @@ def make_buttons(self): tooltip="Register low energy to high energy images", icon="fa-running", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) self.save_button = Button( disabled=True, @@ -990,7 +991,7 @@ def make_buttons(self): tooltip=("Click this button to save the shifted lower energy projections."), icon="fa-file-export", layout=self.button_layout, - style=self.button_font, + style=button_font_style, ) def add_buttons(self): From 78fd979b753168b5a21343aca6fe0f25de2c9d16 Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Wed, 21 Feb 2024 09:36:19 -0500 Subject: [PATCH 2/3] [WIP] frontend refactor using solara - adds various components to build up the frontend, as opposed to classes. much easier for managing state. --- environment-dev.yml | 31 ++ sol.py | 440 ++++++++++++++++++ tomopyui/frontend/__init__.py | 0 tomopyui/frontend/components/__init__.py | 0 .../frontend/components/buttons/__init__.py | 0 tomopyui/frontend/components/buttons/onoff.py | 29 ++ .../frontend/components/buttons/reactive.py | 73 +++ .../frontend/components/buttons/simple.py | 27 ++ .../components/filechooser_quicksearch.py | 69 +++ tomopyui/frontend/components/importer.py | 38 ++ .../frontend/components/metadata/__init__.py | 0 .../frontend/components/metadata/metadata.py | 114 +++++ .../components/metadata/metadata_input.py | 0 .../frontend/components/viewer/__init__.py | 0 tomopyui/frontend/components/viewer/bqfig.py | 111 +++++ .../frontend/components/viewer/dropdowns.py | 73 +++ .../frontend/components/viewer/h5viewer.py | 24 + .../frontend/components/viewer/histogram.py | 103 ++++ .../frontend/components/viewer/imageslider.py | 33 ++ .../frontend/components/viewer/plotlyfig.py | 136 ++++++ .../viewer/plotlyfig_withbuttons.py | 185 ++++++++ .../components/viewer/plotlyframes.py | 16 + tomopyui/frontend/components/viewer/slicer.py | 71 +++ .../frontend/components/viewer/statusbar.py | 0 tomopyui/frontend/context/__init__.py | 63 +++ tomopyui/frontend/features/upload.py | 0 tomopyui/frontend/pages/__init__.py | 0 tomopyui/frontend/pages/imports.py | 46 ++ 28 files changed, 1682 insertions(+) create mode 100644 environment-dev.yml create mode 100644 sol.py create mode 100644 tomopyui/frontend/__init__.py create mode 100644 tomopyui/frontend/components/__init__.py create mode 100644 tomopyui/frontend/components/buttons/__init__.py create mode 100644 tomopyui/frontend/components/buttons/onoff.py create mode 100644 tomopyui/frontend/components/buttons/reactive.py create mode 100644 tomopyui/frontend/components/buttons/simple.py create mode 100644 tomopyui/frontend/components/filechooser_quicksearch.py create mode 100644 tomopyui/frontend/components/importer.py create mode 100644 tomopyui/frontend/components/metadata/__init__.py create mode 100644 tomopyui/frontend/components/metadata/metadata.py create mode 100644 tomopyui/frontend/components/metadata/metadata_input.py create mode 100644 tomopyui/frontend/components/viewer/__init__.py create mode 100644 tomopyui/frontend/components/viewer/bqfig.py create mode 100644 tomopyui/frontend/components/viewer/dropdowns.py create mode 100644 tomopyui/frontend/components/viewer/h5viewer.py create mode 100644 tomopyui/frontend/components/viewer/histogram.py create mode 100644 tomopyui/frontend/components/viewer/imageslider.py create mode 100644 tomopyui/frontend/components/viewer/plotlyfig.py create mode 100644 tomopyui/frontend/components/viewer/plotlyfig_withbuttons.py create mode 100644 tomopyui/frontend/components/viewer/plotlyframes.py create mode 100644 tomopyui/frontend/components/viewer/slicer.py create mode 100644 tomopyui/frontend/components/viewer/statusbar.py create mode 100644 tomopyui/frontend/context/__init__.py create mode 100644 tomopyui/frontend/features/upload.py create mode 100644 tomopyui/frontend/pages/__init__.py create mode 100644 tomopyui/frontend/pages/imports.py diff --git a/environment-dev.yml b/environment-dev.yml new file mode 100644 index 0000000..f636cba --- /dev/null +++ b/environment-dev.yml @@ -0,0 +1,31 @@ +name: tomopyui +channels: + - astra-toolbox + - conda-forge + - defaults +dependencies: + - python=3.11 + - matplotlib + - ffmpeg + - dask + - dask-image + - olefile + - git + - pip + - tomopy + - dxchange + - jupyterlab + - astropy + - ipyfilechooser + - ipywidgets + - astra-toolbox + - bqplot + - pip: + - bqplot-image-gl + - pytest + - pytest-mock + - black + - pydantic + - solara + - reacton + - plotly \ No newline at end of file diff --git a/sol.py b/sol.py new file mode 100644 index 0000000..b838c6c --- /dev/null +++ b/sol.py @@ -0,0 +1,440 @@ +import asyncio +from functools import partial + +import numpy as np +import solara +from bqplot import LinearScale +from exceptiongroup import catch +from reacton import ipywidgets as rw +from reacton import use_state +from solara.lab import Tab, Tabs, task, use_task + +from tomopyui.backend.schemas.prenorm_input import PrenormProjectionsMetadataNew +from tomopyui.frontend.components.filechooser_quicksearch import FileBrowserSearch +from tomopyui.frontend.components.metadata.metadata import PydanticForm, PydanticTable +from tomopyui.frontend.components.viewer.bqfig import BqFigure +from tomopyui.frontend.components.viewer.h5viewer import H5Viewer +from tomopyui.frontend.components.viewer.plotlyfig import Plotly + +# from tomopyui.backend.io import Projections_Prenormalized + +# from tomopyui.frontend.pages.imports import ImportPage + +# @solara.component +# def Page(): +# return ImportPage() + +# Page() + + +num_images = 20 +x = np.linspace(-1, 1, 128) +y = np.linspace(-1, 1, 128) +X, Y = np.meshgrid(x, y) + +image_data = np.array( + [np.cos(X**2 + Y**2 + phase) for phase in np.linspace(0, 2 * np.pi, num_images)] +).astype(np.float32) + +types_of_data = ["tiff file", "tiff folder"] + + +@task(prefer_threaded=False) +async def debounce_update(value): + await asyncio.sleep(1) + return value + + +@solara.component +def Page(): + + im_idx, set_im_idx = use_state(0) + plotly_im_idx, set_plotly_im_idx = use_state(0) + px_range, set_px_range = use_state([[0, 1], [0, 1]]) + data_type, set_data_type = use_state("tiff file") + projections, set_projections = use_state(image_data) + metadata, set_metadata = use_state(None) + + def on_slider_change(value): + set_im_idx(value) + debounce_update(value) + + if debounce_update.finished: + new_idx = debounce_update.value + if new_idx == im_idx: + set_plotly_im_idx(new_idx) + + def on_switch_to_movie(): + set_switch_to_movie(not switch_to_movie) + set_movie_button_label( + "Switch to Movie" if switch_to_movie else "Switch to Image" + ) + + movie_button_label, set_movie_button_label = use_state("Switch to Movie") + # switch_to_movie, set_switch_to_movie = use_state(False) + # switch_to_movie_button = solara.Button( + # label=movie_button_label, + # on_click=on_switch_to_movie, + # ) + + entire_directory, set_entire_directory = use_state(False) + + def on_set_px_range(px_range_x, px_range_y): + px_range_x = [int(px_range_x[0]), int(px_range_x[1])] + px_range_y = [int(px_range_y[0]), int(px_range_y[1])] + set_px_range([px_range_x, px_range_y]) + + metadata_instance, set_metadata_instance = use_state(None) + with solara.AppLayout(title="TomopyUI") as main: + + with solara.VBox() as sidebar: + solara.Markdown("## Data Type") + solara.Switch( + label="Entire Directory", + value=entire_directory, + on_value=set_entire_directory, + ) + solara.ToggleButtonsSingle( + value=data_type, values=types_of_data, on_value=set_data_type + ) + solara.Markdown("## File browser") + with solara.Card(): + FileBrowserSearch() + + solara.Button("Upload Data") + + with solara.Card(title="Metadata input") as metadata_input: + with solara.HBox(): + PydanticForm( + PrenormProjectionsMetadataNew, + model_instance=metadata_instance, + on_change=set_metadata_instance, + ) + PydanticTable(metadata_instance) + + with solara.Card(title="Image Viewer") as image_slider: + with rw.HBox( + layout={ + "justify_content": "center", + "align_items": "center", + "flex_wrap": "wrap", + } + ) as image_viewer: + Plotly(image_data, plotly_im_idx, 0, 1, on_set_px_range) + BqFigure( + images=image_data, + index=im_idx, + vmin=0, + vmax=1, + ) + # solara.display(switch_to_movie_button) + + solara.SliderInt( + label="Image Index", + min=0, + max=len(image_data) - 1, + step=1, + value=im_idx, + on_value=on_slider_change, + ) + solara.Info( + icon=False, + label=f"Pixel ranges | x -> {px_range[0]} | y -> {px_range[1]}", + dense=True, + outlined=False, + ) + + return main + + +Page() + + +"""# Image annotation with Solara + +This example displays how to annotate images with different drawing tools in plotly figures. Use the canvas +below to draw shapes and visualize the canvas callback. + + +Check [plotly docs](https://dash.plotly.com/annotations) for more information about image annotation. +""" + +# import numpy as np +# import reacton +# import reacton.bqplot as bqplot +# import solara +# from bqplot import Figure as BqFigure +# from bqplot import LinearScale as BqLinearScale +# from bqplot import Lines as BqLines +# from bqplot import PanZoom as BqPanZoom +# from bqplot.interacts import BrushSelector as BqBrushSelector +# from reacton.bqplot import ( +# Axis, +# ColorAxis, +# ColorScale, +# Figure, +# HeatMap, +# LinearScale, +# PanZoom, +# ) +# from reacton.bqplot_image_gl import ImageGL +# from reacton.core import ComponentWidget, use_state + +# num_images = 20 # Number of images in the stack +# x = np.linspace(-1, 1, 500) +# y = np.linspace(-1, 1, 500) +# X, Y = np.meshgrid(x, y) + +# image_data = np.array( +# [np.cos(X**2 + Y**2 + phase) for phase in np.linspace(0, 2 * np.pi, num_images)] +# ) + +# x = np.linspace(-5, 5, 200) +# y = np.linspace(-5, 5, 200) +# X, Y = np.meshgrid(x, y) +# color = np.cos(X**2 + Y**2) + + +# @solara.component +# def Page(): +# current_image_index, set_current_image_index = use_state(0) + +# # Scales for the HeatMap +# x_sc, y_sc, col_sc = ( +# LinearScale(), +# LinearScale(), +# ColorScale(scheme="RdYlBu"), +# ) + +# x_sc_bqplot, y_sc_bqplot = (BqLinearScale(), BqLinearScale()) +# # Initial HeatMap with the first image in the stack +# heat = ImageGL( +# image=color, +# scales={"x": x_sc, "y": y_sc, "image": col_sc}, +# ) +# ax_x = Axis(scale=x_sc) +# ax_y = Axis(scale=y_sc, orientation="vertical") +# ax_c = ColorAxis(scale=col_sc) + +# panzoom = BqPanZoom(scales={"x": [x_sc_bqplot], "y": [y_sc_bqplot]}) + +# # Create the figure with initial HeatMap +# fig = Figure( +# scale_x=x_sc, +# scale_y=y_sc, +# # scale_x=x_sc, +# # scale_y=y_sc, +# # axes=[ax_x, ax_y, ax_c], +# title="Cosine", +# min_aspect_ratio=1, +# max_aspect_ratio=1, +# padding_y=0, +# marks=[heat], +# ) +# fig.component.widget.interaction = panzoom + +# def on_slider_change(value): +# # Update the current image index state +# set_current_image_index(value) +# # Update the HeatMap's color data with the new image + +# slider = solara.SliderInt( +# label="Image Index", +# min=0, +# max=len(color) - 1, +# step=1, +# value=current_image_index, +# on_value=on_slider_change, +# ) + +# return solara.VBox([slider, fig]) + + +# x0 = np.linspace(0, 2, 100) + +# exponent = solara.reactive(1.0) +# log_scale = solara.reactive(False) + + +# @solara.component +# def Page(x=x0, ymax=5): +# y = x**exponent.value +# color = "red" +# display_legend = True +# label = "bqplot graph" + +# solara.SliderFloat(value=exponent, min=0.1, max=3, label="Exponent") +# solara.Checkbox(value=log_scale, label="Log scale") + +# # x_scale = bqplot.LinearScale(min=-5000, max=5000) +# # if log_scale.value: +# # y_scale = bqplot.LogScale(min=0.1, max=ymax) +# # else: +# # y_scale = bqplot.LinearScale(min=0, max=ymax) + +# # panzoom = bqplot.PanZoom(scales={"x": [x_scale], "y": [y_scale]}) + +# # lines = bqplot.Lines( +# # x=x, +# # y=y, +# # scales={"x": x_scale, "y": y_scale}, +# # stroke_width=3, +# # colors=[color], +# # display_legend=display_legend, +# # labels=[label], +# # ) + +# def on_brushing(change): +# print(change) + +# bqscalex = BqLinearScale(min=-5000, max=5000) +# bqscaley = BqLinearScale(min=0, max=ymax) +# bqbrush = BqBrushSelector( +# x_scale=bqscalex, y_scale=bqscaley, on_selected=on_brushing +# ) +# bqbrush.observe(on_brushing, "brushing") + +# # brush = reacton.bqplot.BrushSelector( +# # x_scale=x_scale, y_scale=y_scale, on_selected=on_brushing +# # ) +# # x_axis = reacton.bqplot.Axis(scale=x_scale) +# # y_axis = reacton.bqplot.Axis(scale=y_scale, orientation="vertical") + +# fig_bq = BqFigure( +# marks=[ +# BqLines( +# x=x, +# y=y, +# scales={"x": bqscalex, "y": bqscaley}, +# stroke_width=3, +# colors=[color], +# display_legend=display_legend, +# labels=[label], +# ) +# ], +# scale_y=bqscaley, +# scale_x=bqscalex, +# interaction=bqbrush, +# ) + +# # fig = reacton.bqplot.Figure( +# # # axes=[x_axis, y_axis], +# # marks=[lines], +# # scale_y=x_scale, +# # scale_x=y_scale, +# # layout={"min_width": "800px"}, +# # interaction=brush, +# # ) + +# with solara.VBox() as main: +# solara.VBox([fig_bq]) +# solara.SliderFloat(value=exponent, min=0.1, max=3, label="Exponent") +# solara.Checkbox(value=log_scale, label="Log scale") + + +# Page() + + +# # class CustomEncoder(json.JSONEncoder): +# # """ +# # Custom JSON encoder for Plotly objects. + +# # Plotly may return objects that the standard JSON encoder can't handle. This +# # encoder converts such objects to str, allowing serialization by json.dumps +# # """ + +# # def default(self, o): +# # if isinstance(o, object): +# # return str(o) +# # return super().default(o) + + +# # @solara.component +# # def Page(): + +# # current_image, set_current_image = use_state(image_data[0]) +# # print(current_image) + +# # def on_relayout(data): +# # if data is None: +# # return +# # print(fig) + +# # relayout_data = data["relayout_data"] + +# # if "shapes" in relayout_data: +# # shapes.value = relayout_data["shapes"] + +# # # Check for xaxis and yaxis range updates in the relayout_data +# # if "xaxis.range[0]" in relayout_data and "xaxis.range[1]" in relayout_data: +# # xaxis_range = [ +# # relayout_data["xaxis.range[0]"], +# # relayout_data["xaxis.range[1]"], +# # ] +# # print("X-axis range after zoom/pan:", xaxis_range) + +# # if "yaxis.range[0]" in relayout_data and "yaxis.range[1]" in relayout_data: +# # yaxis_range = [ +# # relayout_data["yaxis.range[0]"], +# # relayout_data["yaxis.range[1]"], +# # ] +# # print("Y-axis range after zoom/pan:", yaxis_range) + +# # if "shapes" in relayout_data: +# # shapes.value = relayout_data["shapes"] + +# # fig = go.FigureWidget( +# # data=go.Heatmap(z=current_image, colorscale="Viridis"), +# # layout=go.Layout( +# # showlegend=True, +# # autosize=False, +# # width=600, +# # height=600, +# # margin=dict(l=20, r=20, t=20, b=20), +# # xaxis=dict(showgrid=False, zeroline=False, visible=False), +# # yaxis=dict(showgrid=False, zeroline=False, visible=False), +# # dragmode="zoom", +# # modebar={ +# # "add": [ +# # "drawrect", +# # ] +# # }, +# # ), +# # ) + +# # def on_value(value): +# # set_current_image(image_data[value]) + +# # # sol_fig = solara.FigurePlotly( +# # # fig, on_relayout=on_relayout, dependencies=[current_image] +# # # ) +# # slider = solara.SliderInt( +# # label="", +# # min=0, +# # max=image_data.shape[0] - 1, +# # step=1, +# # on_value=on_value, +# # ) + +# # if not shapes.value: +# # solara.Markdown("## Draw on the canvas") +# # else: +# # solara.Markdown("## Data returned by drawing") +# # formatted_shapes = str(json.dumps(shapes.value, indent=2, cls=CustomEncoder)) +# # solara.Preformatted(formatted_shapes) + + +# # df = px.data.iris() +# # arr = np.random.random(100,100,100) +# # @solara.component +# # def Page(): +# # solara.provide_cross_filter() +# # fig = px.histogram(df, "species") +# # fig.update_layout(dragmode="select", selectdirection="h") + +# # with solara.VBox() as main: +# # spx.density_heatmap(df, x="sepal_width", y="sepal_length") +# # spx.scatter(df, x="sepal_width", y="sepal_length", color="species") +# # spx.scatter_3d(df, x="sepal_width", y="sepal_length", z="petal_width") +# # spx.CrossFilteredFigurePlotly(fig) +# # return main diff --git a/tomopyui/frontend/__init__.py b/tomopyui/frontend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/__init__.py b/tomopyui/frontend/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/buttons/__init__.py b/tomopyui/frontend/components/buttons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/buttons/onoff.py b/tomopyui/frontend/components/buttons/onoff.py new file mode 100644 index 0000000..e547a4e --- /dev/null +++ b/tomopyui/frontend/components/buttons/onoff.py @@ -0,0 +1,29 @@ +from typing import Callable + +import reacton +import reacton.ipywidgets as w +from pydantic import ConfigDict +from .simple import ButtonProps + + +class OnOffButtonProps(ButtonProps): + model_config = ConfigDict(arbitrary_types_allowed=True) + on: bool + on_click: Callable[[], None] + + +@reacton.component +def OnOffButton(props: OnOffButtonProps): + button_style = "success" if props.on else "" + + def on_click_wrapper(): + props.on_click() + + return w.Button( + icon=props.icon, + tooltip=props.tooltip, + on_click=on_click_wrapper, + button_style=button_style, + style=props.style, + layout=props.layout, + ) diff --git a/tomopyui/frontend/components/buttons/reactive.py b/tomopyui/frontend/components/buttons/reactive.py new file mode 100644 index 0000000..c7b7efc --- /dev/null +++ b/tomopyui/frontend/components/buttons/reactive.py @@ -0,0 +1,73 @@ +import solara +from typing import Callable, Coroutine, Union +import asyncio + + +@solara.component +def ReactiveButton( + label: str = "", + on_click: Callable[ + [], Union[None, Coroutine] + ] = None, # Expect a coroutine function + icon_name: str = "fa-upload", + disabled: bool = False, + button_style: str = "", + button_style_during: str = "info", + button_style_after: str = "success", + description_during: str = "", + description_after: str = "", + icon_during: str = "fas fa-cog fa-spin fa-lg", + icon_after: str = "fa-check-square", + warning_text: str = "That button click didn't work.", # Avoid name clash with warning module +): + state, set_state = solara.use_state("initial") + click_event, set_click_event = solara.use_state(0) + + async def async_wrapper(): + set_state("during") + try: + if asyncio.iscoroutinefunction(on_click): + await on_click() # Await if on_click is a coroutine function + else: + on_click() # Call directly if on_click is a regular function + set_state("after") + except Exception as e: + set_state("warning") + print("Warning:", warning_text) + + def handle_click_sync(): + asyncio.create_task(async_wrapper()) + + current_label = label + current_icon = icon_name + current_color = button_style + + # Determine the current state of the button for rendering + current_label, current_icon, current_color = label, icon_name, button_style + if state == "during": + current_label, current_icon, current_color = ( + description_during, + icon_during, + button_style_during, + ) + elif state == "after": + current_label, current_icon, current_color = ( + description_after, + icon_after, + button_style_after, + ) + elif state == "warning": + current_label, current_icon, current_color = ( + warning_text, + "exclamation-triangle", + "warning", + ) + + return solara.Button( + label=current_label, + on_click=handle_click_sync, + icon_name=current_icon, + disabled=disabled + or state == "during", # Optionally disable the button during operation + color=current_color, + ) diff --git a/tomopyui/frontend/components/buttons/simple.py b/tomopyui/frontend/components/buttons/simple.py new file mode 100644 index 0000000..44972f2 --- /dev/null +++ b/tomopyui/frontend/components/buttons/simple.py @@ -0,0 +1,27 @@ +from typing import Callable, Any, Optional + +import reacton +import reacton.ipywidgets as w +from pydantic import BaseModel + + +class ButtonProps(BaseModel): + name: Optional[str] = None + icon: str = "far square" + tooltip: str = "" + layout: dict = {"width": "45px", "height": "40px"} + style: dict = {"font_size": "22px"} + + +@reacton.component +def ButtonSimpleCallback(props: ButtonProps, on_click: Callable[[], Any]): + + button = w.Button( + icon=props.icon, + tooltip=props.tooltip, + on_click=on_click, + style=props.style, + layout=props.layout, + ) + + return button diff --git a/tomopyui/frontend/components/filechooser_quicksearch.py b/tomopyui/frontend/components/filechooser_quicksearch.py new file mode 100644 index 0000000..3068ba2 --- /dev/null +++ b/tomopyui/frontend/components/filechooser_quicksearch.py @@ -0,0 +1,69 @@ +from pathlib import Path +from typing import Optional, Union, cast, Callable, List +import solara +from reacton.core import use_state, component +from reacton.core import component + + +@component +def FileBrowserSearch( + start_directory: Union[None, str, Path, solara.Reactive[Path]] = None, + on_file_select: Optional[Callable[[Path], None]] = None, + file_types: Optional[List[str]] = None, # Added parameter for file type filtering +): + directory, set_directory = use_state(start_directory or Path("~/").expanduser()) + selected_path, set_selected_path = use_state(cast(Optional[Path], None)) + search_path, set_search_path = use_state("") + + # Define a filter function based on the file_types list + def file_filter(path: Path) -> bool: + if file_types is None: + return True # Show all files if no filter is provided + if path.is_dir(): + return True # Always show directories + return any(path.name.endswith(file_type) for file_type in file_types) + + def handle_directory_change(new_directory: Path): + set_directory(new_directory) + set_selected_path(None) # Reset selected path when changing directories + + def handle_file_open(file_path: Path): + set_selected_path(file_path) + if on_file_select: + on_file_select(file_path) + + def handle_path_select(path: Optional[Path]): + set_selected_path(path) + if on_file_select: + on_file_select(path) + + def update_search_path(new_path: str): + if new_path: + try: + path_obj = Path(new_path).expanduser().resolve() + if path_obj.exists(): + if path_obj.is_dir(): + set_directory(path_obj) + else: + set_directory(path_obj.parent) + set_selected_path(path_obj) + set_search_path(new_path) + except Exception as e: + print(f"Error updating path: {e}") + + with solara.VBox(): + solara.InputText( + "Search or Enter Path", value=search_path, on_value=update_search_path + ) + solara.Button("Clear", on_click=lambda: set_search_path("")) + + solara.FileBrowser( + directory=directory, + on_directory_change=handle_directory_change, + on_path_select=handle_path_select, + on_file_open=handle_file_open, + can_select=False, + filter=file_filter + ) + if selected_path: + solara.Info(f"You selected: {selected_path}") diff --git a/tomopyui/frontend/components/importer.py b/tomopyui/frontend/components/importer.py new file mode 100644 index 0000000..ceca8b1 --- /dev/null +++ b/tomopyui/frontend/components/importer.py @@ -0,0 +1,38 @@ +from typing import Callable, Coroutine, Optional, Union +import solara +from .filechooser_quicksearch import FileBrowserSearch +from .buttons.reactive import ReactiveButton +import pathlib +from reacton.core import use_state, component + + +import solara + + +@component +def Importer( + on_import: Callable[[pathlib.Path], Union[None, Coroutine]], + file_types: Optional[list] = None, +): + # State for managing the current directory and selected file path + directory, set_directory = use_state(pathlib.Path("~/").expanduser()) + selected_file, set_selected_file = use_state(pathlib.Path()) + + def handle_import(): + if selected_file: + on_import(selected_file) + + with solara.VBox() as vbox: + ReactiveButton( + label="Import", + on_click=handle_import, + icon_name="fas fa-upload", + disabled=selected_file is None, + ) + FileBrowserSearch( + start_directory=directory, + on_file_select=set_selected_file, + file_types=file_types, + ) + + return vbox diff --git a/tomopyui/frontend/components/metadata/__init__.py b/tomopyui/frontend/components/metadata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/metadata/metadata.py b/tomopyui/frontend/components/metadata/metadata.py new file mode 100644 index 0000000..2daf7aa --- /dev/null +++ b/tomopyui/frontend/components/metadata/metadata.py @@ -0,0 +1,114 @@ +from enum import Enum +from typing import Any, Callable, List, Optional, Type, get_args, get_origin + +import pandas as pd +import solara +from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo +from reacton.core import component, use_effect, use_state + + +@component +def PydanticTable(model_instance: BaseModel): + # Convert the Pydantic model instance to a dictionary + if not model_instance: + return solara.Text("No model instance provided") + model_dict = model_instance.model_dump() + + # Create a DataFrame from the model dictionary + # The DataFrame will have two columns: 'Field' and 'Value' + data = {"Field": list(model_dict.keys()), "Value": list(model_dict.values())} + df = pd.DataFrame(data) + + # Use Solara's DataFrame component to display the table + return solara.DataFrame(df, items_per_page=10) + + +@component +def PydanticForm( + model_cls: Type[BaseModel], + model_instance: BaseModel, + on_change: Callable[[BaseModel], None], +): + fields = model_cls.model_fields + + def on_value_change(field_name: str): + def inner(new_value: Any): + on_change(model_cls(**{field_name: new_value})) + + return inner + + widgets = [ + create_widget(field=fields[field_name], on_value=on_value_change(field_name)) + for field_name in fields + ] + + return solara.VBox(children=[widget for widget in widgets if widget is not None]) + + +def create_widget(field: FieldInfo, on_value: Callable[[Any], None]): + field_type, default = get_field_type_and_default(field) + desc = field.description or "" + + # Check if the field is an enum and handle it with ToggleButtonsSingle + if issubclass(field_type, Enum): + return create_enum_widget(field_type, default, desc, on_value) + + # Mapping for other types + type_to_widget = { + int: solara.InputInt, + float: solara.InputFloat, + str: solara.InputText, + } + + widget_creator = type_to_widget.get(field_type) + if widget_creator: + return widget_creator(label=desc, value=default, on_value=on_value) + + return None + + +def create_enum_widget( + enum_type: Type[Enum], + default_value: Any, + description: str, + on_value: Callable[[Any], None], +): + choices = [e.value for e in enum_type] # Assuming Enum values are used directly + default_value = default_value if default_value in choices else choices[0] + + # Use Reacton's use_state to manage the selected enum value state + selected_value, set_selected_value = use_state(default_value) + + # Call the provided on_value callback whenever the selected value changes + def handle_value_change(new_value: Any): + set_selected_value(new_value) + on_value(new_value) + + return solara.VBox( + children=[ + solara.Markdown(f"**{description}**"), + solara.ToggleButtonsSingle( + value=selected_value, + values=choices, + on_value=handle_value_change, + ), + ] + ) + + +def get_field_type_and_default(field_info: FieldInfo) -> tuple[Any, Any]: + base_annotation = get_origin(field_info.annotation) or field_info.annotation + args_annotation = get_args(field_info.annotation) + is_optional = type(None) in args_annotation + field_type = ( + args_annotation[0] + if is_optional and len(args_annotation) > 1 + else base_annotation + ) + + # Determine a sensible default value + default_value = 0 if field_type in [int, float] else "" + return field_type, ( + field_info.default if field_info.default is not None else default_value + ) diff --git a/tomopyui/frontend/components/metadata/metadata_input.py b/tomopyui/frontend/components/metadata/metadata_input.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/viewer/__init__.py b/tomopyui/frontend/components/viewer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/components/viewer/bqfig.py b/tomopyui/frontend/components/viewer/bqfig.py new file mode 100644 index 0000000..109b179 --- /dev/null +++ b/tomopyui/frontend/components/viewer/bqfig.py @@ -0,0 +1,111 @@ +from typing import Any, List, Optional, Type, Union + +import bqplot as bq +import numpy as np +import reacton +import reacton.bqplot as rbq +import reacton.ipywidgets as widgets +import reacton.ipywidgets as w +import reacton.ipywidgets as rw +import solara +from bqplot.scales import ColorScale, LinearScale, Scale +from bqplot_image_gl import ImageGL +from bqplot_image_gl.interacts import MouseInteraction, keyboard_events, mouse_events +from bs4 import BeautifulStoneSoup +from pydantic import BaseModel, ConfigDict, Field +from reacton.bqplot import PanZoom +from reacton.core import Element, use_effect, use_memo, use_ref + +from ...context import ImageContext +from .histogram import Histogram + + +class ImageProps(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + scale_x: Optional[Scale] = Field(default_factory=lambda: LinearScale(min=0, max=1)) + scale_y: Optional[Scale] = Field(default_factory=lambda: LinearScale(min=1, max=0)) + scale_image: Optional[ColorScale] = Field( + default_factory=lambda: ColorScale(min=0, max=1, scheme="viridis") + ) + + def dict(self): + d = { + "x": self.scale_x, + "y": self.scale_y, + "image": self.scale_image, + } + return d + + +class FigureLayoutProps(BaseModel): + width: str = "100%" + height: str = "100%" + fig_margin: dict[str, float] = dict(top=0, bottom=0, left=0, right=0) + + +class DefaultFigureProps(BaseModel): + padding_x: float = 0 + padding_y: float = 0 + pixel_ratio: float = 1.0 + fig_margin: dict[str, float] = dict(top=0, bottom=0, left=0, right=0) + layout: FigureLayoutProps = FigureLayoutProps() + + +class FigureProps(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + index: int = 0 + images: np.ndarray + vmin: float + vmax: float + padding_x: float = 0 + padding_y: float = 0 + fig_margin: dict[str, float] = dict(top=0, bottom=0, left=0, right=0) + layout: FigureLayoutProps = FigureLayoutProps() + + +@reacton.component +def BqFigure(images: np.ndarray, index: int, vmin: float, vmax: float): + vmin_value, _ = reacton.use_state(vmin) + vmax_value, _ = reacton.use_state(vmax) + + image_scale = bq.ColorScale(min=vmin_value, max=vmax_value, scheme="viridis") + + im_props = ImageProps( + scale_image=image_scale, + ) + + # hist = Histogram(images=images) + + im = reacton.use_memo( + lambda: ImageGL(image=images[index], scales=im_props.dict()), [] + ) + figure = reacton.use_memo( + lambda: bq.Figure( + scale_x=im_props.scale_x, + scale_y=im_props.scale_y, + marks=[im], + **DefaultFigureProps().model_dump(), + ), + [], + ) + + def on_change_colors(): + im.scales["image"].min = vmin_value + im.scales["image"].max = vmax_value + + def change_aspect_ratio(): + figure.min_aspect_ratio = images.shape[2] / images.shape[1] + figure.max_aspect_ratio = images.shape[2] / images.shape[1] + + def on_change_index(): + im.image = images[index] + + def on_new_images(): + change_aspect_ratio() + im.image = images[0] + + reacton.use_effect(on_change_colors, [vmin_value, vmax_value]) + reacton.use_effect(on_change_index, [index]) + reacton.use_effect(on_new_images, [images]) + + return rw.Box(children=[figure]) diff --git a/tomopyui/frontend/components/viewer/dropdowns.py b/tomopyui/frontend/components/viewer/dropdowns.py new file mode 100644 index 0000000..9670c17 --- /dev/null +++ b/tomopyui/frontend/components/viewer/dropdowns.py @@ -0,0 +1,73 @@ +from typing import List + +import bqplot as bq +import reacton +import reacton.ipywidgets as widgets +from pydantic import BaseModel + + +class DropdownProps(BaseModel): + name: str + description: str + options: List[str] + value: str + + +@reacton.component +def SchemeDropdown(im_props: ImageProps) -> widgets.Dropdown: + lin_schemes: List[str] = [ + "viridis", + "plasma", + "inferno", + "magma", + "OrRd", + "PuBu", + "BuPu", + "Oranges", + "BuGn", + "YlOrBr", + "YlGn", + "Reds", + "RdPu", + "Greens", + "YlGnBu", + "Purples", + "GnBu", + "Greys", + "YlOrRd", + "PuRd", + "Blues", + "PuBuGn", + ] + + value, set_value = reacton.use_state("viridis") + + def update_scheme(change) -> None: + """ + Updates the scheme based on dropdown value selection. + """ + im_props.scale_image.scheme = change["new"] + + dd: widgets.Dropdown = widgets.Dropdown( + description="Scheme: ", options=lin_schemes, value=value + ) + + def attach_event_handler(): + """ + Attaches the update_scheme function to the dropdown change event. + """ + dd_widget = reacton.get_widget(dd) + + def on_value_change(change) -> None: + update_scheme(change) + + def cleanup() -> None: + dd_widget.unobserve(on_value_change, names="value") + + dd_widget.observe(on_value_change, names="value") + + return cleanup + + reacton.use_effect(attach_event_handler) + + return dd diff --git a/tomopyui/frontend/components/viewer/h5viewer.py b/tomopyui/frontend/components/viewer/h5viewer.py new file mode 100644 index 0000000..848a120 --- /dev/null +++ b/tomopyui/frontend/components/viewer/h5viewer.py @@ -0,0 +1,24 @@ +from enum import Enum +from typing import Any, Callable, List, Optional, Type, get_args, get_origin + +import pandas as pd +import reacton.ipywidgets as rw +import solara +from IPython.display import display +from ipywidgets import VBox +from jupyterlab_h5web import H5Web +from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo +from reacton.core import component, use_effect, use_state + + +@component +def H5Viewer(): + # Convert the Pydantic model instance to a dictionary + return rw.VBox( + children=[ + H5Web( + "/Users/swelborn/Documents/gits/tomopyui/examples/data/ALS_832_Data/fake_als_data.h5" + ) + ] + ) diff --git a/tomopyui/frontend/components/viewer/histogram.py b/tomopyui/frontend/components/viewer/histogram.py new file mode 100644 index 0000000..295184b --- /dev/null +++ b/tomopyui/frontend/components/viewer/histogram.py @@ -0,0 +1,103 @@ +import numpy as np +import reacton +import reacton.bqplot as rbq +from bqplot.interacts import BrushIntervalSelector +from bqplot.scales import LinearScale as BqLinearScale, Scale, ColorScale +from traitlets.utils.bunch import Bunch +from reacton.core import Element + + + +@reacton.component +def Histogram(images: np.ndarray, image_colorscale: Element[ColorScale]): + # Initialize state for vmin, vmax, and selector bounds + vmin, setVmin = reacton.use_state(0.0) + vmax, setVmax = reacton.use_state(1.0) + selector_min, set_selector_min = reacton.use_state(0.0) + selector_max, set_selector_max = reacton.use_state(1.0) + + selector_vmin, set_selector_vmin = reacton.use_state(0.0) + selector_vmax, set_selector_vmax = reacton.use_state(1.0) + + # Compute histogram data (bin centers and frequencies) + bin_centers = np.array([x for x in range(30)]) + bin_freqs = np.array([x for x in range(30)]) + + # Initialize scales + scale_x = rbq.LinearScale(min=vmin, max=vmax) + scale_y = rbq.LinearScale() + # brush_scale = reacton.use_memo( + # lambda: BqLinearScale(min=selector_min, max=selector_max), + # [selector_vmin, selector_vmax], + # ) + + # Create bars for the histogram + bars = rbq.Bars( + x=bin_centers, + y=bin_freqs, + scales={"x": scale_x, "y": scale_y}, + colors=["dodgerblue"], + opacities=[0.75], + orientation="horizontal", + ) + + # Define callback for brush selector changes + def on_brush_select(change: Bunch): + _brush = change["owner"] + if _brush: + sel: list[float] = _brush.selected + print(sel) + if all(sel): + set_selector_vmin(sel[0]) + set_selector_vmax(sel[1]) + + # Initialize brush selector for interaction + # brush = BrushIntervalSelector( + # orientation="vertical", + # scale=brush_scale, + # ) + + # brush.observe(on_brush_select, "selected") + + # Create the figure + figure = rbq.Figure( + fig_margin=dict(top=0, bottom=0, left=0, right=0), + layout={"width": "100px", "height": "100px"}, + marks=[bars], + # interaction=brush, + ) + + # Effect to adjust vmin, vmax, and selector bounds based on image data + def on_image_change(): + new_vmax = np.max(images) + new_vmin = np.min(images) + _min, _max = np.percentile(images, q=(0.5, 99.5)) + # Update state only if the values have changed + if new_vmax != vmax or new_vmin != vmin: + setVmax(new_vmax) + setVmin(new_vmin) + if _min != selector_min or _max != selector_max: + set_selector_min(_min) + set_selector_max(_max) + + reacton.use_effect(on_image_change, [images]) + # reacton.use_effect(on_brush_select, [brush.selected]) + return figure + + # def remove_bins_lower_than_min(): + # ind = bin_centers < vmin + # bin_freqs[ind] = 0 + # reacton.get_widget(scale_y) = float(np.max(bin_freqs)) + + # def update_crange_selector(selected_range): + # if selector.selected is not None: + # image_scale["image"].min = selector.selected[0] + # image_scale["image"].max = selector.selected[1] + # vmin = selector.selected[0] + # vmax = selector.selected[1] + + # Use reacton hooks to manage state and effects + # reacton.use_effect(refresh_histogram, [images]) + + # Render the component using reacton components + # (This is a placeholder; you'll need to replace it with the actual rendering logic) diff --git a/tomopyui/frontend/components/viewer/imageslider.py b/tomopyui/frontend/components/viewer/imageslider.py new file mode 100644 index 0000000..c5b598e --- /dev/null +++ b/tomopyui/frontend/components/viewer/imageslider.py @@ -0,0 +1,33 @@ +from typing import Callable, Optional + +import numpy as np +import reacton +import reacton.ipywidgets as widgets +from pydantic import BaseModel +import solara + +from ...context import ImageContext + + +class ImageSliderProps(BaseModel): + images: list + image_index: int + set_image_index: Callable[[int], None] + + +@reacton.component +def ImageSlider(props: ImageSliderProps): + context = reacton.use_context(ImageContext) + images = context["images"] + image_index = props.image_index + + slider = solara.SliderInt( + label="Image Index:", + value=image_index, + min=0, + max=len(images) - 1, + step=1, + on_value=props.set_image_index, + ) + + return slider diff --git a/tomopyui/frontend/components/viewer/plotlyfig.py b/tomopyui/frontend/components/viewer/plotlyfig.py new file mode 100644 index 0000000..1d6366f --- /dev/null +++ b/tomopyui/frontend/components/viewer/plotlyfig.py @@ -0,0 +1,136 @@ +import asyncio +import json +from turtle import fillcolor +from typing import Callable, Optional + +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +import reacton.ipywidgets as w +import solara +from reacton.core import use_effect, use_state + +from tomopyui.widgets.helpers import debounce + + +class CustomEncoder(json.JSONEncoder): + """ + Custom JSON encoder for Plotly objects. + + Plotly may return objects that the standard JSON encoder can't handle. This + encoder converts such objects to str, allowing serialization by json.dumps + """ + + def default(self, o): + if isinstance(o, object): + return "" + return super().default(o) + + +@solara.component +def Plotly( + images: np.ndarray, + index: int, + vmin: float, + vmax: float, + on_set_px_range: Callable[[list[int], list[int]], None], +): + + current_image, set_current_image = use_state(images[0]) + + def update_image(): + set_current_image(images[index]) + + use_effect(update_image, [index]) + + def on_relayout(data): + if data is None: + return + + relayout_data = data["relayout_data"] + + # Check for xaxis and yaxis range updates in the relayout_data + if "xaxis.range[0]" in relayout_data and "xaxis.range[1]" in relayout_data: + xaxis_range = [ + relayout_data["xaxis.range[0]"], + relayout_data["xaxis.range[1]"], + ] + print("X-axis range after zoom/pan:", xaxis_range) + + if "yaxis.range[0]" in relayout_data and "yaxis.range[1]" in relayout_data: + yaxis_range = [ + relayout_data["yaxis.range[0]"], + relayout_data["yaxis.range[1]"], + ] + print("Y-axis range after zoom/pan:", yaxis_range) + + if "selections" in relayout_data and relayout_data["selections"]: + # Assuming there's only one selection to handle at a time + selection = relayout_data["selections"][0] + x0, x1 = selection["x0"], selection["x1"] + y0, y1 = selection["y0"], selection["y1"] + + # Ensure x0 < x1 and y0 < y1 by swapping if necessary + x0, x1 = sorted([x0, x1]) + y0, y1 = sorted([y0, y1]) + + _, max_height, max_width = images.shape + x_range_corr = [ + max(0, min(x0, max_width - 1)), + max(0, min(x1, max_width - 1)), + ] + y_range_corr = [ + max(0, min(y0, max_height - 1)), + max(0, min(y1, max_height - 1)), + ] + + # Use the corrected ranges + on_set_px_range(x_range_corr, y_range_corr) + + layout, set_layout = use_state( + go.Layout( + showlegend=False, + autosize=False, + width=400, + height=320, + hovermode="closest", + margin=dict(l=20, r=20, t=20, b=20), + xaxis=dict( + showgrid=False, + zeroline=False, + visible=False, + range=[0, images.shape[2] - 1], + # minallowed=0, + # maxallowed=images.shape[2], + ), + yaxis=dict( + showgrid=False, + zeroline=False, + visible=False, + range=[images.shape[1] - 1, 0], + scaleanchor="x", + scaleratio=1, + # minallowed=0, + # maxallowed=images.shape[1], + ), + dragmode="zoom", + modebar=go.layout.Modebar(add=["deselect"], remove=["lasso"]), + ) + ) + + heat = go.FigureWidget( + data=[ + go.Scatter( + x=[0], + y=[0], + ), + go.Heatmap(z=current_image, colorscale="Viridis"), + ], + layout=layout, + ) + + solara.FigurePlotly( + heat, + on_relayout=on_relayout, + dependencies=[current_image], + ) diff --git a/tomopyui/frontend/components/viewer/plotlyfig_withbuttons.py b/tomopyui/frontend/components/viewer/plotlyfig_withbuttons.py new file mode 100644 index 0000000..bed2a1c --- /dev/null +++ b/tomopyui/frontend/components/viewer/plotlyfig_withbuttons.py @@ -0,0 +1,185 @@ +import asyncio +import json +from turtle import fillcolor + +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +import reacton.ipywidgets as w +import solara +from reacton.core import use_effect, use_state + +from tomopyui.widgets.helpers import debounce + + +class CustomEncoder(json.JSONEncoder): + """ + Custom JSON encoder for Plotly objects. + + Plotly may return objects that the standard JSON encoder can't handle. This + encoder converts such objects to str, allowing serialization by json.dumps + """ + + def default(self, o): + if isinstance(o, object): + return "" + return super().default(o) + + +@solara.component +def Plotly(images: np.ndarray, index: int, vmin: float, vmax: float): + + current_image, set_current_image = use_state(images[0]) + + def update_image(): + set_current_image(images[index]) + + use_effect(update_image, [index]) + + def on_relayout(data): + if data is None: + return + + relayout_data = data["relayout_data"] + print(select_data) + + # Check for xaxis and yaxis range updates in the relayout_data + if "xaxis.range[0]" in relayout_data and "xaxis.range[1]" in relayout_data: + xaxis_range = [ + relayout_data["xaxis.range[0]"], + relayout_data["xaxis.range[1]"], + ] + print("X-axis range after zoom/pan:", xaxis_range) + + if "yaxis.range[0]" in relayout_data and "yaxis.range[1]" in relayout_data: + yaxis_range = [ + relayout_data["yaxis.range[0]"], + relayout_data["yaxis.range[1]"], + ] + print("Y-axis range after zoom/pan:", yaxis_range) + + button_layer_1_height = 1.4 + button_layer_2_height = 1.2 + layout, set_layout = use_state( + go.Layout( + showlegend=False, + autosize=False, + width=400, + height=400, + margin=dict(l=20, r=20, t=0, b=20), + xaxis=dict(showgrid=False, zeroline=False, visible=False), + yaxis=dict(showgrid=False, zeroline=False, visible=False), + dragmode="select", + modebar=go.layout.Modebar(add=["deselect"], remove=["lasso"]), + updatemenus=[ + dict( + type="buttons", + direction="left", + buttons=list( + [ + dict( + args=["type", "surface"], + label="3D Surface", + method="restyle", + ), + dict( + args=["type", "heatmap"], + label="Heatmap", + method="restyle", + ), + ] + ), + pad={"r": 10, "t": 10}, + showactive=True, + x=0.11, + xanchor="left", + y=button_layer_2_height, + yanchor="top", + ), + dict( + buttons=list( + [ + dict( + args=["colorscale", "Viridis"], + label="Viridis", + method="restyle", + ), + dict( + args=["colorscale", "Cividis"], + label="Cividis", + method="restyle", + ), + dict( + args=["colorscale", "Blues"], + label="Blues", + method="restyle", + ), + dict( + args=["colorscale", "Greens"], + label="Greens", + method="restyle", + ), + ] + ), + type="buttons", + direction="right", + pad={"r": 2, "t": 2}, + showactive=True, + x=0.1, + xanchor="left", + y=button_layer_1_height, + yanchor="top", + ), + ], + annotations=[ + dict( + text="colorscale", + x=0, + xref="paper", + y=1.1, + yref="paper", + align="left", + showarrow=False, + ), + dict( + text="Reverse
Colorscale", + x=0, + xref="paper", + y=1.06, + yref="paper", + showarrow=False, + ), + dict( + text="Lines", + x=0.47, + xref="paper", + y=1.045, + yref="paper", + showarrow=False, + ), + ], + ) + ) + + select_data, set_select_data = use_state(None) + + heat = go.FigureWidget( + data=[ + go.Heatmap(z=current_image, colorscale="Viridis"), + go.Scatter( + x=[0], + y=[0], + ), + ], + layout=layout, + ) + + with solara.VBox() as main: + solara.FigurePlotly( + heat, + on_relayout=on_relayout, + on_selection=set_select_data, + dependencies=[current_image], + ) + + return main diff --git a/tomopyui/frontend/components/viewer/plotlyframes.py b/tomopyui/frontend/components/viewer/plotlyframes.py new file mode 100644 index 0000000..669d192 --- /dev/null +++ b/tomopyui/frontend/components/viewer/plotlyframes.py @@ -0,0 +1,16 @@ +import numpy as np +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +import solara + + +@solara.component +def PlotlyAnimation(images: np.ndarray): + + # You can use solara.display directly + fig = px.imshow( + images, + animation_frame=0, + ) + solara.display(fig) diff --git a/tomopyui/frontend/components/viewer/slicer.py b/tomopyui/frontend/components/viewer/slicer.py new file mode 100644 index 0000000..de59a17 --- /dev/null +++ b/tomopyui/frontend/components/viewer/slicer.py @@ -0,0 +1,71 @@ +import reacton +import reacton.bqplot as rbq +import solara +from bqplot.scales import LinearScale +from bqplot_image_gl.interacts import BrushSelector, MouseInteraction +import solara + +from ...context import ImageContext +from ..buttons.onoff import OnOffButton, OnOffButtonProps +from ..buttons.simple import ButtonProps, ButtonSimpleCallback +from .bqfig import FigureComponent, FigureProps +from .imageslider import ImageSlider, ImageSliderProps + + +@reacton.component +def ThreeDSlicer(): + image_index, set_image_index = reacton.use_state(0) + context = reacton.use_context(ImageContext) + images = context["images"] + + scale_x = rbq.LinearScale(min=0, max=1) + scale_y = rbq.LinearScale(min=1, max=0) + # scale_x, scale_y = reacton.use_memo( + # lambda: (LinearScale(min=0, max=1), LinearScale(min=1, max=0)), [] + # ) + + play_speed, set_play_speed = reacton.use_state(300) + + # Managing which interaction is currently active + is_rectangle_mode, set_is_rectangle_mode = reacton.use_state(False) + + def toggle_interaction_mode(): + set_is_rectangle_mode(not is_rectangle_mode) + + figure_props = FigureProps( + images=images, + index=image_index, + scale_x=scale_x, + scale_y=scale_y, + ) + + figure = FigureComponent(props=figure_props) + slider = ImageSlider( + ImageSliderProps( + images=images, image_index=image_index, set_image_index=set_image_index + ) + ) + + rectangle_selector_button = OnOffButton( + OnOffButtonProps( + icon="far square", on=is_rectangle_mode, on_click=toggle_interaction_mode + ) + ) + + def on_plus(): + set_play_speed(play_speed + 50) + + def on_minus(): + set_play_speed(play_speed - 50) + + speed_up = ButtonSimpleCallback( + ButtonProps(icon="plus", tooltip="Increase play speed"), on_click=on_plus + ) + slow_down = ButtonSimpleCallback( + ButtonProps(icon="minus", tooltip="Decrease play speed"), on_click=on_minus + ) + + with solara.lab + with solara.VBox(grow=False, align_items="center"): + solara.VBox([figure, slider], grow=False) + solara.HBox([speed_up, slow_down, rectangle_selector_button]) diff --git a/tomopyui/frontend/components/viewer/statusbar.py b/tomopyui/frontend/components/viewer/statusbar.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/context/__init__.py b/tomopyui/frontend/context/__init__.py new file mode 100644 index 0000000..d71150c --- /dev/null +++ b/tomopyui/frontend/context/__init__.py @@ -0,0 +1,63 @@ +from typing import Any + +import numpy as np +import reacton +from pydantic import BaseModel + +global_update_images = None + +ImageContext = reacton.create_context( + {"images": np.random.rand(10, 256, 256), "setImages": None, "updateImages": None} +) + + +@reacton.component +def ImageProvider(children): + images, setImages = reacton.use_state(np.random.rand(10, 256, 256)) + + # This function will be passed down and can be used to update the images + def updateImages(new_images): + setImages(new_images) + + # Set the global reference + global global_update_images + global_update_images = updateImages + + ImageContext.provide( + {"images": images, "setImages": setImages, "updateImages": updateImages} + ) + + return children + + +# @reacton.component +# def PixelRangeProvider(props): +# image_context = reacton.use_context(ImageContext) +# images: np.ndarray = image_context["images"] +# shape: tuple = images.shape + +# px_range, set_px_range = reacton.use_state((0,)) + +# # This function will be passed down and can be used to update the images +# def updateImages(new_images): +# setImages(new_images) + +# # Set the global reference +# global global_update_images +# global_update_images = updateImages + +# value = {"images": images, "setImages": setImages, "updateImages": updateImages} + +# ImageContext.provide( +# {"value": value, "setImages": setImages, "updateImages": updateImages} +# ) + +# return props.children + + +# External function to update the images +def setNewImages(new_images): + if global_update_images: + global_update_images(new_images) + else: + print("Error: The update function is not set.") diff --git a/tomopyui/frontend/features/upload.py b/tomopyui/frontend/features/upload.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/pages/__init__.py b/tomopyui/frontend/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomopyui/frontend/pages/imports.py b/tomopyui/frontend/pages/imports.py new file mode 100644 index 0000000..40148ad --- /dev/null +++ b/tomopyui/frontend/pages/imports.py @@ -0,0 +1,46 @@ +import pathlib + +import numpy as np +import solara +import solara.lab + +from ..components.importer import Importer +from ..components.viewer.slicer import ThreeDSlicer +from ..context import ImageProvider, global_update_images + +# Assuming the Importer component has been properly defined to handle file importation + + +@solara.component +def ImportPage(): + def on_import(path: pathlib.Path): + print(f"Importing data from {path}") + loaded_image_data = load_image_data(path) + setNewImages(loaded_image_data) # Update the global image context + + with solara.lab.Tabs(): + with solara.lab.Tab("Import"): + with solara.HBox(): + with solara.Card(title="Import", style="width: 40%; min-width: 3"): + Importer(on_import=on_import, file_types=[".tif", ".tiff"]) + # with solara.Card(title="Viewer", style="width: 75%; min-width: 3"): + ImageProvider(children=ThreeDSlicer()) + with solara.lab.Tab("Center"): + Importer(on_import=on_import, file_types=[".tif", ".tiff"]) + + +def load_image_data(path: pathlib.Path) -> np.ndarray: + # Placeholder function: Implement the actual logic to load and return image data from the given path + # For demonstration, this just returns a random numpy array + return np.random.rand(10, 256, 256) + + +def setNewImages(new_images: np.ndarray): + if global_update_images: + global_update_images(new_images) + else: + print("Error: The update function is not set.") + + +# Note: Ensure that the `Importer` component is properly implemented to handle the import process, +# including invoking the `on_import` callback with the selected file path. From 6d447df933c6ba3a96182e2310553b7186f7d8ba Mon Sep 17 00:00:00 2001 From: Sam Welborn Date: Wed, 21 Feb 2024 09:48:48 -0500 Subject: [PATCH 3/3] [WIP] add some schemas --- tomopyui/backend/schemas/ALS.py | 31 + tomopyui/backend/schemas/Dxchange.py | 102 ++ tomopyui/backend/schemas/README.md | 55 ++ tomopyui/backend/schemas/SSRL.py | 109 ++ tomopyui/backend/schemas/__init__.py | 3 + tomopyui/backend/schemas/analysis.py | 80 ++ tomopyui/backend/schemas/import_metadata.json | 927 ++++++++++++++++++ tomopyui/backend/schemas/prenorm_input.py | 61 ++ tomopyui/backend/schemas/prep.py | 12 + 9 files changed, 1380 insertions(+) create mode 100644 tomopyui/backend/schemas/ALS.py create mode 100644 tomopyui/backend/schemas/Dxchange.py create mode 100644 tomopyui/backend/schemas/README.md create mode 100644 tomopyui/backend/schemas/SSRL.py create mode 100644 tomopyui/backend/schemas/__init__.py create mode 100644 tomopyui/backend/schemas/analysis.py create mode 100644 tomopyui/backend/schemas/import_metadata.json create mode 100644 tomopyui/backend/schemas/prenorm_input.py create mode 100644 tomopyui/backend/schemas/prep.py diff --git a/tomopyui/backend/schemas/ALS.py b/tomopyui/backend/schemas/ALS.py new file mode 100644 index 0000000..85ab5e7 --- /dev/null +++ b/tomopyui/backend/schemas/ALS.py @@ -0,0 +1,31 @@ +# generated by datamodel-codegen: +# filename: raw_metadata.json +# timestamp: 2024-02-14T20:36:23+00:00 + +from __future__ import annotations + +from typing import List + +from pydantic import BaseModel + + +class ALS832(BaseModel): + metadata_type: str + data_hierarchy_level: int + pxY: int + numslices: int + pxX: int + numrays: int + pxZ: int + num_angles: int + pxsize: float + px_size_units: str + propagation_dist: float + energy_float: float + kev: float + energy_str: str + energy_units: str + angularrange: float + propagation_dist_units: str + angles_deg: List[float] + angles_rad: List[float] diff --git a/tomopyui/backend/schemas/Dxchange.py b/tomopyui/backend/schemas/Dxchange.py new file mode 100644 index 0000000..999b9f7 --- /dev/null +++ b/tomopyui/backend/schemas/Dxchange.py @@ -0,0 +1,102 @@ +# generated by datamodel-codegen: +# filename: metadata.json +# timestamp: 2024-02-14T21:51:12+00:00 + +from __future__ import annotations + +from typing import List, Optional, Union + +from pydantic import BaseModel + + +class Dxchange(BaseModel): + measurement_instrument_attenuator_name: List[Optional[str]] + measurement_instrument_attenuator_setup_user_filter_ds: List[str] + measurement_instrument_attenuator_setup_user_filter_ds_legend: List[Optional[str]] + measurement_instrument_attenuator_setup_user_filter_us: List[str] + measurement_instrument_attenuator_setup_user_filter_us_legend: List[Optional[str]] + measurement_instrument_attenuator_1_description: List[Optional[str]] + measurement_instrument_attenuator_1_name: List[Optional[str]] + measurement_instrument_detection_system_objective_magnification: List[Optional[str]] + measurement_instrument_detection_system_scintillator_name: List[Optional[str]] + measurement_instrument_detection_system_scintillator_scintillating_thickness: List[ + str + ] + measurement_instrument_detector_ADcore_version: List[Optional[str]] + measurement_instrument_detector_HDFplugin_version: List[Optional[str]] + measurement_instrument_detector_SDK_version: List[Optional[str]] + measurement_instrument_detector_acquisition_period: List[Optional[float]] + measurement_instrument_detector_array_counter: List[Optional[str]] + measurement_instrument_detector_binning_x: List[Optional[str]] + measurement_instrument_detector_binning_y: List[Optional[str]] + measurement_instrument_detector_data_type: List[Optional[str]] + measurement_instrument_detector_dimension_x: List[Optional[str]] + measurement_instrument_detector_dimension_y: List[Optional[str]] + measurement_instrument_detector_driver_version: List[Optional[str]] + measurement_instrument_detector_exposure_time: List[Optional[float]] + measurement_instrument_detector_firmware_version: List[Optional[str]] + measurement_instrument_detector_frame_rate: List[Optional[float]] + measurement_instrument_detector_frame_rate_enable: List[Optional[str]] + measurement_instrument_detector_gain: List[Optional[float]] + measurement_instrument_detector_gain_auto: List[Optional[float]] + measurement_instrument_detector_manufacturer: List[Optional[str]] + measurement_instrument_detector_model: List[Optional[str]] + measurement_instrument_detector_roi_min_x: List[Optional[str]] + measurement_instrument_detector_roi_min_y: List[Optional[str]] + measurement_instrument_detector_roi_size_x: List[Optional[str]] + measurement_instrument_detector_roi_size_y: List[Optional[str]] + measurement_instrument_detector_serial_number: List[Optional[str]] + measurement_instrument_detector_temperature: List[Union[float, str]] + measurement_instrument_mirror_name: List[Optional[str]] + measurement_instrument_mirror_setup_mirror_angle: List[Union[float, str]] + measurement_instrument_mirror_setup_mirror_dsy: List[Union[float, str]] + measurement_instrument_mirror_setup_mirror_usy: List[Union[float, str]] + measurement_instrument_mirror_setup_mirror_x: List[Union[float, str]] + measurement_instrument_mirror_setup_mirror_y: List[Union[float, str]] + measurement_instrument_mirror_setup_stripe: List[Optional[str]] + measurement_instrument_monochromator_Energy_list: List[Optional[str]] + measurement_instrument_monochromator_USArm_list: List[Optional[str]] + measurement_instrument_monochromator_name: List[Optional[str]] + measurement_instrument_monochromator_setup_dmm_ds_arm: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_m2_y: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_m2_z: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_table_ds_y: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_table_usy_ib: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_table_usy_ob: List[Union[float, str]] + measurement_instrument_monochromator_setup_dmm_us_arm: List[Union[float, str]] + measurement_instrument_name: List[Optional[str]] + measurement_instrument_sample_detector_distance: List[str] + measurement_instrument_sample_name: List[Optional[str]] + measurement_instrument_sample_setup_rail_z: List[Union[float, str]] + measurement_instrument_sample_setup_sample_pitch: List[Union[float, str]] + measurement_instrument_sample_setup_sample_roll: List[Union[float, str]] + measurement_instrument_sample_setup_sample_rotary: List[Union[float, str]] + measurement_instrument_sample_setup_sample_x: List[Union[float, str]] + measurement_instrument_sample_setup_sample_xx: List[Union[float, str]] + measurement_instrument_sample_setup_sample_y: List[Union[float, str]] + measurement_instrument_sample_setup_sample_zz: List[Union[float, str]] + measurement_instrument_slits_name: List[Optional[str]] + measurement_instrument_slits_setup_hslits_ds_center: List[Union[float, str]] + measurement_instrument_slits_setup_hslits_ds_size: List[Union[float, str]] + measurement_instrument_slits_setup_hslits_us_center: List[Union[float, str]] + measurement_instrument_slits_setup_hslits_us_size: List[Union[float, str]] + measurement_instrument_slits_setup_vslits_ds_center: List[Union[float, str]] + measurement_instrument_slits_setup_vslits_ds_size: List[Union[float, str]] + measurement_instrument_slits_setup_vslits_us_center: List[Union[float, str]] + measurement_instrument_slits_setup_vslits_us_size: List[Union[float, str]] + measurement_instrument_source_beamline: List[Optional[str]] + measurement_instrument_source_current: List[Union[float, str]] + measurement_instrument_source_fill_mode: List[Optional[str]] + measurement_instrument_source_name: List[Optional[str]] + measurement_instrument_source_top_up: List[Optional[str]] + measurement_sample_experiment_proposal: List[Optional[str]] + measurement_sample_experiment_title: List[Optional[str]] + measurement_sample_experimenter_affiliation: List[Optional[str]] + measurement_sample_experimenter_email: List[Optional[str]] + measurement_sample_experimenter_facility_user_id: List[Optional[str]] + measurement_sample_experimenter_name: List[Optional[str]] + measurement_sample_file_path: List[Optional[str]] + measurement_sample_full_file_name: List[Optional[str]] + measurement_sample_name: List[Optional[str]] + process_acquisition_end_date: List[Optional[str]] + process_acquisition_start_date: List[Optional[str]] diff --git a/tomopyui/backend/schemas/README.md b/tomopyui/backend/schemas/README.md new file mode 100644 index 0000000..371869b --- /dev/null +++ b/tomopyui/backend/schemas/README.md @@ -0,0 +1,55 @@ +# Automatic conversion of metadata into schema + +Download datamodel-code-generator + +```bash +pip install datamodel-code-generator +``` + +Put your json file into this folder +``` +cp your-metadata.json tomopyui/backend/schemas/ +``` + +Go to this folder +``` +cd tomopyui/backend/schemas +``` + +Run code generator +``` +datamodel-codegen --input your-metadata.json --input-file-type json --output your-metadata.py +``` + +Inspect the code that has been generated (open in text editor) + +```python +# generated by datamodel-codegen: +# filename: metadata.json +# timestamp: 2024-02-14T21:51:12+00:00 + +from __future__ import annotations + +from typing import List, Optional, Union + +from pydantic import BaseModel + +class Model(BaseModel): + measurement_instrument_attenuator_name: List[Optional[str]] + measurement_instrument_attenuator_setup_user_filter_ds: List[str] + measurement_instrument_attenuator_setup_user_filter_ds_legend: List[Optional[str]] + measurement_instrument_attenuator_setup_user_filter_us: List[str] + measurement_instrument_attenuator_setup_user_filter_us_legend: List[Optional[str]] + measurement_instrument_attenuator_1_description: List[Optional[str]] + measurement_instrument_attenuator_1_name: List[Optional[str]] + measurement_instrument_detection_system_objective_magnification: List[Optional[str]] + measurement_instrument_detection_system_scintillator_name: List[Optional[str]] + measurement_instrument_detection_system_scintillator_scintillating_thickness: List[ + str + ] +... + +``` + +You can + diff --git a/tomopyui/backend/schemas/SSRL.py b/tomopyui/backend/schemas/SSRL.py new file mode 100644 index 0000000..666c32b --- /dev/null +++ b/tomopyui/backend/schemas/SSRL.py @@ -0,0 +1,109 @@ +# from __future__ import annotations +# import pathlib + +# from typing import List, Optional, Union + +# from pydantic import BaseModel + +# # from .importing import Acquisition, PixelUnits + +# class SSRL62C_Image(BaseModel): +# image_width: int +# image_height: int +# number_of_images: int +# ccd_pixel_size: float +# data_type: int +# exposure_time: List[float] +# images_per_projection: List[int] +# images_taken: int +# camera_binning: int +# pixel_size: float +# thetas: List[float] +# x_positions: List[float] +# y_positions: List[float] +# z_positions: List[float] + +# class ScanInfo(BaseModel): +# VERSION: int +# ENERGY: int +# TOMO: int +# MOSAIC: int +# MULTIEXPOSURE: int +# NREPEATSCAN: int +# WAITNSECS: int +# NEXPOSURES: int +# AVERAGEONTHEFLY: int +# IMAGESPERPROJECTION: int +# REFNEXPOSURES: int +# REFEVERYEXPOSURES: int +# REFABBA: int +# REFAVERAGEONTHEFLY: int +# REFDESPECKLEAVERAGE: int +# APPLYREF: int +# MOSAICUP: int +# MOSAICDOWN: int +# MOSAICLEFT: int +# MOSAICRIGHT: int +# MOSAICOVERLAP: int +# MOSAICCENTRALTILE: int +# FILES: List[str] +# PROJECTION_METADATA: List[SSRL62C_Image] +# FLAT_METADATA: List[SSRL62C_Image] + + +# class SSRL62C(Acquisition): +# scan_info: dict +# scan_info_path: pathlib.Path +# run_script_path: pathlib.Path +# flats_filenames: List[pathlib.Path] +# projections_filenames: List[pathlib.Path] +# scan_type: str +# scan_order: Optional[int] +# num_angles: int +# angles_rad: List[float] +# angles_deg: List[float] +# start_angle: float +# end_angle: float +# binning: int +# projections_exposure_time: float +# references_exposure_time: float +# all_raw_energies_float: List[float] +# all_raw_energies_str: List[str] +# all_raw_pixel_sizes: List[float] +# pixel_size_from_scan_info: float +# energy_units: str +# pixel_units: PixelUnits +# raw_projections_dtype: str +# raw_projections_directory: pathlib.Path +# data_hierarchy_level: int + + +# class Model(BaseModel): +# metadata_type: str +# data_hierarchy_level: int +# scan_info: ScanInfo +# scan_info_path: str +# run_script_path: str +# flats_filenames: List[str] +# projections_filenames: List[str] +# scan_type: str +# scan_order: List[List[Union[int, str]]] +# pxX: int +# pxY: int +# pxZ: int +# num_angles: int +# angles_rad: List[float] +# angles_deg: List[float] +# start_angle: float +# end_angle: float +# binning: int +# projections_exposure_time: List[float] +# references_exposure_time: List[float] +# all_raw_energies_float: List[float] +# all_raw_energies_str: List[str] +# all_raw_pixel_sizes: List[float] +# pixel_size_from_scan_info: float +# energy_units: str +# pixel_units: str +# raw_projections_dtype: str +# raw_projections_directory: str diff --git a/tomopyui/backend/schemas/__init__.py b/tomopyui/backend/schemas/__init__.py new file mode 100644 index 0000000..e78f268 --- /dev/null +++ b/tomopyui/backend/schemas/__init__.py @@ -0,0 +1,3 @@ +# from .SSRL import SSRL62C +# from .ALS import ALS832 +# from .Dxchange import Dxchange diff --git a/tomopyui/backend/schemas/analysis.py b/tomopyui/backend/schemas/analysis.py new file mode 100644 index 0000000..cfc0f5a --- /dev/null +++ b/tomopyui/backend/schemas/analysis.py @@ -0,0 +1,80 @@ + +from __future__ import annotations +import datetime + +from typing import Any, Dict, List, Optional +from pydantic import BaseModel, Field + +class AnalysisOpts(BaseModel): + downsample: bool + ds_factor: int + pyramid_level: int + num_iter: int + center: float + pad: List[int] + extra_options: Dict[str, Any] + use_multiple_centers: bool + px_range_x: List[int] + px_range_y: List[int] + + +class ReconOpts(AnalysisOpts): + pass + + +class AlignOpts(AnalysisOpts): + shift_full_dataset_after: bool + upsample_factor: int + pre_alignment_iters: int + num_batches: int + use_subset_correlation: bool + subset_x: List[int] + subset_y: List[int] + + +class Algorithms(BaseModel): + art: Optional[bool] + bart: Optional[bool] + fbp: Optional[bool] + gridrec: Optional[bool] + mlem: Optional[bool] + osem: Optional[bool] + ospml_hybrid: Optional[bool] + ospml_quad: Optional[bool] + pml_hybrid: Optional[bool] + pml_quad: Optional[bool] + sirt: Optional[bool] + tv: Optional[bool] + grad: Optional[bool] + tikh: Optional[bool] + FBP_CUDA: Optional[bool] = Field(..., alias='FBP CUDA') + SIRT_CUDA: Optional[bool] = Field(..., alias='SIRT CUDA') + SART_CUDA: Optional[bool] = Field(..., alias='SART CUDA') + CGLS_CUDA: Optional[bool] = Field(..., alias='CGLS CUDA') + MLEM_CUDA: Optional[bool] = Field(..., alias='MLEM CUDA') + SIRT_Plugin: Optional[bool] = Field(..., alias='SIRT Plugin') + SIRT_3D: Optional[bool] = Field(..., alias='SIRT 3D') + + +class SaveOpts(BaseModel): + Reconstruction: bool + tiff: bool + hdf: bool + + +class AlignSaveOpts(BaseModel): + Projections_Before_Alignment: bool = Field( + ..., alias='Projections Before Alignment' + ) + Projections_After_Alignment: bool = Field(..., alias='Projections After Alignment') + Reconstruction: bool + + +class Align(BaseModel): + opts: AlignOpts + methods: Algorithms + save_opts: SaveOpts + copy_hists_from_parent: bool + data_hierarchy_level: int + analysis_time: datetime.timedelta + convergence: List[float] \ No newline at end of file diff --git a/tomopyui/backend/schemas/import_metadata.json b/tomopyui/backend/schemas/import_metadata.json new file mode 100644 index 0000000..4078601 --- /dev/null +++ b/tomopyui/backend/schemas/import_metadata.json @@ -0,0 +1,927 @@ +{ + "metadata_type": "General_Normalized", + "data_hierarchy_level": 0, + "start_angle": -90, + "end_angle": 90, + "pixel_size": 30, + "pixel_units": "nm", + "energy_float": 8000, + "energy_units": "eV", + "binning": 2, + "angular_resolution": 0.25, + "energy_str": "8000.00", + "num_angles": 451, + "pxX": 77, + "pxY": 77, + "pxZ": 451, + "angles_rad": [ + -1.5707963267948966, + -1.5638150097869192, + -1.556833692778942, + -1.5498523757709646, + -1.5428710587629872, + -1.53588974175501, + -1.5289084247470326, + -1.5219271077390553, + -1.514945790731078, + -1.5079644737231006, + -1.5009831567151233, + -1.494001839707146, + -1.4870205226991686, + -1.4800392056911915, + -1.4730578886832142, + -1.4660765716752369, + -1.4590952546672595, + -1.4521139376592822, + -1.4451326206513049, + -1.4381513036433275, + -1.4311699866353502, + -1.424188669627373, + -1.4172073526193956, + -1.4102260356114182, + -1.403244718603441, + -1.3962634015954636, + -1.3892820845874863, + -1.382300767579509, + -1.3753194505715316, + -1.3683381335635543, + -1.361356816555577, + -1.3543754995475996, + -1.3473941825396223, + -1.340412865531645, + -1.3334315485236679, + -1.3264502315156905, + -1.3194689145077132, + -1.3124875974997359, + -1.3055062804917585, + -1.2985249634837812, + -1.2915436464758039, + -1.2845623294678266, + -1.2775810124598492, + -1.270599695451872, + -1.2636183784438946, + -1.2566370614359172, + -1.24965574442794, + -1.2426744274199626, + -1.2356931104119853, + -1.228711793404008, + -1.2217304763960306, + -1.2147491593880533, + -1.207767842380076, + -1.2007865253720986, + -1.1938052083641213, + -1.186823891356144, + -1.1798425743481666, + -1.1728612573401893, + -1.165879940332212, + -1.1588986233242347, + -1.1519173063162573, + -1.14493598930828, + -1.137954672300303, + -1.1309733552923256, + -1.1239920382843482, + -1.117010721276371, + -1.1100294042683936, + -1.1030480872604163, + -1.096066770252439, + -1.0890854532444616, + -1.0821041362364843, + -1.075122819228507, + -1.0681415022205296, + -1.0611601852125523, + -1.054178868204575, + -1.0471975511965976, + -1.0402162341886205, + -1.0332349171806432, + -1.0262536001726659, + -1.0192722831646885, + -1.0122909661567112, + -1.0053096491487339, + -0.9983283321407564, + -0.9913470151327791, + -0.9843656981248018, + -0.9773843811168245, + -0.9704030641088471, + -0.9634217471008698, + -0.9564404300928926, + -0.9494591130849153, + -0.9424777960769379, + -0.9354964790689606, + -0.9285151620609833, + -0.921533845053006, + -0.9145525280450286, + -0.9075712110370513, + -0.900589894029074, + -0.8936085770210966, + -0.8866272600131193, + -0.8796459430051421, + -0.8726646259971648, + -0.8656833089891874, + -0.8587019919812101, + -0.8517206749732328, + -0.8447393579652555, + -0.8377580409572781, + -0.8307767239493008, + -0.8237954069413235, + -0.8168140899333461, + -0.8098327729253688, + -0.8028514559173915, + -0.7958701389094143, + -0.7888888219014369, + -0.7819075048934596, + -0.7749261878854823, + -0.767944870877505, + -0.7609635538695276, + -0.7539822368615503, + -0.747000919853573, + -0.7400196028455956, + -0.7330382858376183, + -0.726056968829641, + -0.7190756518216637, + -0.7120943348136864, + -0.7051130178057091, + -0.6981317007977318, + -0.6911503837897545, + -0.6841690667817771, + -0.6771877497737998, + -0.6702064327658225, + -0.6632251157578452, + -0.6562437987498678, + -0.6492624817418905, + -0.6422811647339132, + -0.635299847725936, + -0.6283185307179586, + -0.6213372137099813, + -0.614355896702004, + -0.6073745796940266, + -0.6003932626860493, + -0.593411945678072, + -0.5864306286700947, + -0.5794493116621173, + -0.57246799465414, + -0.5654866776461627, + -0.5585053606381853, + -0.551524043630208, + -0.5445427266222307, + -0.5375614096142534, + -0.530580092606276, + -0.5235987755982987, + -0.5166174585903214, + -0.5096361415823443, + -0.5026548245743669, + -0.4956735075663896, + -0.4886921905584123, + -0.48171087355043496, + -0.47472955654245763, + -0.4677482395344803, + -0.460766922526503, + -0.45378560551852565, + -0.4468042885105483, + -0.439822971502571, + -0.43284165449459366, + -0.42586033748661634, + -0.418879020478639, + -0.4118977034706617, + -0.40491638646268435, + -0.397935069454707, + -0.3909537524467297, + -0.38397243543875237, + -0.37699111843077504, + -0.3700098014227977, + -0.3630284844148204, + -0.35604716740684306, + -0.34906585039886595, + -0.3420845333908886, + -0.3351032163829113, + -0.32812189937493397, + -0.32114058236695664, + -0.3141592653589793, + -0.307177948351002, + -0.30019663134302466, + -0.29321531433504733, + -0.28623399732707, + -0.27925268031909267, + -0.27227136331111534, + -0.265290046303138, + -0.2583087292951607, + -0.25132741228718336, + -0.24434609527920603, + -0.2373647782712287, + -0.23038346126325138, + -0.22340214425527405, + -0.21642082724729672, + -0.2094395102393194, + -0.20245819323134207, + -0.19547687622336474, + -0.18849555921538763, + -0.1815142422074103, + -0.17453292519943298, + -0.16755160819145565, + -0.16057029118347832, + -0.153588974175501, + -0.14660765716752366, + -0.13962634015954634, + -0.132645023151569, + -0.12566370614359168, + -0.11868238913561435, + -0.11170107212763702, + -0.1047197551196597, + -0.09773843811168237, + -0.09075712110370504, + -0.08377580409572771, + -0.07679448708775038, + -0.06981317007977306, + -0.06283185307179573, + -0.0558505360638184, + -0.04886921905584107, + -0.041887902047863745, + -0.03490658503988642, + -0.02792526803190909, + -0.020943951023931984, + -0.013962634015954656, + -0.006981317007977328, + 0.0, + 0.006981317007977328, + 0.013962634015954656, + 0.020943951023931984, + 0.02792526803190931, + 0.03490658503988664, + 0.04188790204786397, + 0.048869219055841295, + 0.05585053606381862, + 0.06283185307179595, + 0.06981317007977328, + 0.07679448708775061, + 0.08377580409572793, + 0.09075712110370526, + 0.09773843811168259, + 0.10471975511965992, + 0.11170107212763725, + 0.11868238913561457, + 0.1256637061435919, + 0.13264502315156923, + 0.13962634015954634, + 0.14660765716752366, + 0.153588974175501, + 0.16057029118347832, + 0.16755160819145565, + 0.17453292519943298, + 0.1815142422074103, + 0.18849555921538763, + 0.19547687622336496, + 0.2024581932313423, + 0.20943951023931962, + 0.21642082724729694, + 0.22340214425527427, + 0.2303834612632516, + 0.23736477827122893, + 0.24434609527920625, + 0.2513274122871836, + 0.2583087292951609, + 0.26529004630313824, + 0.27227136331111557, + 0.2792526803190929, + 0.2862339973270702, + 0.29321531433504755, + 0.30019663134302466, + 0.307177948351002, + 0.3141592653589793, + 0.32114058236695664, + 0.32812189937493397, + 0.3351032163829113, + 0.3420845333908886, + 0.34906585039886595, + 0.3560471674068433, + 0.3630284844148206, + 0.37000980142279793, + 0.37699111843077526, + 0.3839724354387526, + 0.3909537524467299, + 0.39793506945470725, + 0.4049163864626846, + 0.4118977034706619, + 0.41887902047863923, + 0.42586033748661656, + 0.4328416544945939, + 0.4398229715025712, + 0.44680428851054854, + 0.45378560551852587, + 0.4607669225265032, + 0.4677482395344805, + 0.47472955654245785, + 0.4817108735504352, + 0.4886921905584125, + 0.49567350756638984, + 0.5026548245743672, + 0.5096361415823445, + 0.5166174585903218, + 0.5235987755982991, + 0.5305800926062765, + 0.5375614096142538, + 0.5445427266222307, + 0.551524043630208, + 0.5585053606381853, + 0.5654866776461627, + 0.57246799465414, + 0.5794493116621173, + 0.5864306286700947, + 0.593411945678072, + 0.6003932626860493, + 0.6073745796940266, + 0.614355896702004, + 0.6213372137099813, + 0.6283185307179586, + 0.635299847725936, + 0.6422811647339133, + 0.6492624817418906, + 0.6562437987498679, + 0.6632251157578453, + 0.6702064327658226, + 0.6771877497737999, + 0.6841690667817772, + 0.6911503837897546, + 0.6981317007977319, + 0.7051130178057092, + 0.7120943348136866, + 0.7190756518216639, + 0.7260569688296412, + 0.7330382858376185, + 0.7400196028455959, + 0.7470009198535732, + 0.7539822368615505, + 0.7609635538695279, + 0.7679448708775052, + 0.7749261878854825, + 0.7819075048934598, + 0.7888888219014372, + 0.7958701389094145, + 0.8028514559173918, + 0.8098327729253691, + 0.8168140899333465, + 0.8237954069413238, + 0.8307767239493011, + 0.8377580409572785, + 0.8447393579652558, + 0.8517206749732331, + 0.8587019919812104, + 0.8656833089891878, + 0.8726646259971647, + 0.879645943005142, + 0.8866272600131193, + 0.8936085770210966, + 0.900589894029074, + 0.9075712110370513, + 0.9145525280450286, + 0.921533845053006, + 0.9285151620609833, + 0.9354964790689606, + 0.9424777960769379, + 0.9494591130849153, + 0.9564404300928926, + 0.9634217471008699, + 0.9704030641088472, + 0.9773843811168246, + 0.9843656981248019, + 0.9913470151327792, + 0.9983283321407566, + 1.0053096491487339, + 1.0122909661567112, + 1.0192722831646885, + 1.0262536001726659, + 1.0332349171806432, + 1.0402162341886205, + 1.0471975511965979, + 1.0541788682045752, + 1.0611601852125525, + 1.0681415022205298, + 1.0751228192285072, + 1.0821041362364845, + 1.0890854532444618, + 1.0960667702524391, + 1.1030480872604165, + 1.1100294042683938, + 1.1170107212763711, + 1.1239920382843485, + 1.1309733552923258, + 1.1379546723003031, + 1.1449359893082804, + 1.1519173063162578, + 1.158898623324235, + 1.1658799403322124, + 1.1728612573401898, + 1.179842574348167, + 1.1868238913561444, + 1.1938052083641213, + 1.2007865253720986, + 1.207767842380076, + 1.2147491593880533, + 1.2217304763960306, + 1.228711793404008, + 1.2356931104119853, + 1.2426744274199626, + 1.24965574442794, + 1.2566370614359172, + 1.2636183784438946, + 1.270599695451872, + 1.2775810124598492, + 1.2845623294678266, + 1.2915436464758039, + 1.2985249634837812, + 1.3055062804917585, + 1.3124875974997359, + 1.3194689145077132, + 1.3264502315156905, + 1.3334315485236679, + 1.3404128655316452, + 1.3473941825396225, + 1.3543754995475998, + 1.3613568165555772, + 1.3683381335635545, + 1.3753194505715318, + 1.3823007675795091, + 1.3892820845874865, + 1.3962634015954638, + 1.4032447186034411, + 1.4102260356114185, + 1.4172073526193958, + 1.4241886696273731, + 1.4311699866353504, + 1.4381513036433278, + 1.445132620651305, + 1.4521139376592824, + 1.4590952546672598, + 1.466076571675237, + 1.4730578886832144, + 1.4800392056911917, + 1.487020522699169, + 1.4940018397071464, + 1.5009831567151237, + 1.507964473723101, + 1.5149457907310784, + 1.5219271077390553, + 1.5289084247470326, + 1.53588974175501, + 1.5428710587629872, + 1.5498523757709646, + 1.556833692778942, + 1.5638150097869192, + 1.5707963267948966 + ], + "angles_deg": [ + -90.0, + -89.6, + -89.2, + -88.8, + -88.39999999999999, + -87.99999999999999, + -87.6, + -87.2, + -86.8, + -86.4, + -86.0, + -85.6, + -85.19999999999999, + -84.80000000000001, + -84.4, + -84.0, + -83.60000000000001, + -83.2, + -82.8, + -82.39999999999999, + -82.0, + -81.60000000000001, + -81.2, + -80.8, + -80.4, + -80.0, + -79.6, + -79.19999999999999, + -78.8, + -78.4, + -78.0, + -77.6, + -77.19999999999999, + -76.8, + -76.4, + -76.0, + -75.60000000000001, + -75.2, + -74.80000000000001, + -74.4, + -74.0, + -73.6, + -73.2, + -72.80000000000001, + -72.4, + -72.0, + -71.6, + -71.19999999999999, + -70.8, + -70.4, + -70.0, + -69.6, + -69.19999999999999, + -68.8, + -68.39999999999999, + -68.0, + -67.6, + -67.19999999999999, + -66.79999999999998, + -66.39999999999999, + -66.0, + -65.6, + -65.2, + -64.80000000000001, + -64.4, + -64.0, + -63.599999999999994, + -63.2, + -62.800000000000004, + -62.4, + -62.0, + -61.599999999999994, + -61.199999999999996, + -60.8, + -60.4, + -59.99999999999999, + -59.60000000000001, + -59.20000000000001, + -58.800000000000004, + -58.400000000000006, + -58.0, + -57.60000000000001, + -57.199999999999996, + -56.8, + -56.39999999999999, + -56.0, + -55.599999999999994, + -55.199999999999996, + -54.800000000000004, + -54.4, + -54.0, + -53.599999999999994, + -53.2, + -52.8, + -52.4, + -51.99999999999999, + -51.599999999999994, + -51.2, + -50.8, + -50.400000000000006, + -50.0, + -49.6, + -49.199999999999996, + -48.8, + -48.400000000000006, + -48.0, + -47.599999999999994, + -47.199999999999996, + -46.79999999999999, + -46.4, + -46.0, + -45.6, + -45.2, + -44.8, + -44.4, + -43.99999999999999, + -43.6, + -43.2, + -42.8, + -42.4, + -41.99999999999999, + -41.599999999999994, + -41.199999999999996, + -40.800000000000004, + -40.4, + -40.0, + -39.599999999999994, + -39.2, + -38.8, + -38.4, + -38.0, + -37.599999999999994, + -37.199999999999996, + -36.8, + -36.400000000000006, + -36.0, + -35.599999999999994, + -35.2, + -34.8, + -34.4, + -34.0, + -33.599999999999994, + -33.199999999999996, + -32.8, + -32.39999999999999, + -31.999999999999996, + -31.599999999999994, + -31.199999999999996, + -30.799999999999994, + -30.39999999999999, + -29.999999999999993, + -29.59999999999999, + -29.200000000000003, + -28.800000000000004, + -28.400000000000002, + -28.0, + -27.6, + -27.2, + -26.799999999999997, + -26.4, + -25.999999999999996, + -25.6, + -25.2, + -24.799999999999997, + -24.4, + -23.999999999999996, + -23.599999999999994, + -23.199999999999996, + -22.799999999999994, + -22.399999999999995, + -21.999999999999993, + -21.59999999999999, + -21.199999999999992, + -20.79999999999999, + -20.399999999999988, + -20.000000000000004, + -19.6, + -19.200000000000003, + -18.8, + -18.4, + -18.0, + -17.6, + -17.2, + -16.799999999999997, + -16.4, + -15.999999999999998, + -15.599999999999998, + -15.199999999999996, + -14.799999999999995, + -14.399999999999995, + -13.999999999999993, + -13.599999999999993, + -13.199999999999994, + -12.799999999999994, + -12.399999999999993, + -11.999999999999991, + -11.59999999999999, + -11.19999999999999, + -10.800000000000002, + -10.400000000000002, + -10.000000000000002, + -9.600000000000001, + -9.2, + -8.8, + -8.399999999999999, + -7.999999999999999, + -7.599999999999998, + -7.1999999999999975, + -6.799999999999996, + -6.399999999999997, + -5.999999999999996, + -5.599999999999995, + -5.199999999999994, + -4.7999999999999945, + -4.399999999999993, + -3.9999999999999933, + -3.599999999999992, + -3.199999999999992, + -2.799999999999991, + -2.3999999999999906, + -1.99999999999999, + -1.5999999999999894, + -1.2000000000000017, + -0.8000000000000012, + -0.4000000000000006, + 0.0, + 0.4000000000000006, + 0.8000000000000012, + 1.2000000000000017, + 1.6000000000000023, + 2.0000000000000027, + 2.4000000000000035, + 2.800000000000004, + 3.2000000000000046, + 3.600000000000005, + 4.000000000000005, + 4.400000000000007, + 4.800000000000007, + 5.200000000000006, + 5.600000000000008, + 6.000000000000009, + 6.400000000000009, + 6.800000000000009, + 7.20000000000001, + 7.600000000000011, + 7.999999999999999, + 8.399999999999999, + 8.8, + 9.2, + 9.600000000000001, + 10.000000000000002, + 10.400000000000002, + 10.800000000000002, + 11.200000000000003, + 11.600000000000005, + 12.000000000000005, + 12.400000000000004, + 12.800000000000004, + 13.200000000000006, + 13.600000000000007, + 14.000000000000007, + 14.400000000000007, + 14.80000000000001, + 15.200000000000008, + 15.600000000000009, + 16.00000000000001, + 16.400000000000013, + 16.80000000000001, + 17.2, + 17.6, + 18.0, + 18.4, + 18.8, + 19.200000000000003, + 19.6, + 20.000000000000004, + 20.400000000000002, + 20.800000000000004, + 21.200000000000003, + 21.600000000000005, + 22.000000000000007, + 22.400000000000006, + 22.800000000000008, + 23.20000000000001, + 23.60000000000001, + 24.00000000000001, + 24.400000000000013, + 24.800000000000008, + 25.20000000000001, + 25.60000000000001, + 26.00000000000001, + 26.400000000000013, + 26.80000000000001, + 27.200000000000014, + 27.600000000000012, + 28.000000000000014, + 28.400000000000016, + 28.800000000000015, + 29.200000000000017, + 29.60000000000002, + 30.000000000000014, + 30.400000000000016, + 30.800000000000022, + 31.199999999999996, + 31.599999999999994, + 31.999999999999996, + 32.39999999999999, + 32.8, + 33.199999999999996, + 33.599999999999994, + 34.0, + 34.4, + 34.8, + 35.2, + 35.599999999999994, + 36.0, + 36.400000000000006, + 36.8, + 37.2, + 37.6, + 38.0, + 38.400000000000006, + 38.800000000000004, + 39.2, + 39.60000000000001, + 40.00000000000001, + 40.400000000000006, + 40.800000000000004, + 41.20000000000001, + 41.60000000000001, + 42.000000000000014, + 42.400000000000006, + 42.800000000000004, + 43.20000000000001, + 43.60000000000001, + 44.000000000000014, + 44.40000000000001, + 44.80000000000001, + 45.20000000000001, + 45.600000000000016, + 46.000000000000014, + 46.40000000000002, + 46.80000000000001, + 47.20000000000002, + 47.600000000000016, + 48.00000000000002, + 48.40000000000002, + 48.800000000000026, + 49.20000000000002, + 49.600000000000016, + 49.99999999999999, + 50.4, + 50.8, + 51.2, + 51.599999999999994, + 51.99999999999999, + 52.4, + 52.8, + 53.2, + 53.599999999999994, + 54.0, + 54.4, + 54.800000000000004, + 55.2, + 55.60000000000001, + 56.0, + 56.400000000000006, + 56.800000000000004, + 57.20000000000001, + 57.60000000000001, + 58.0, + 58.400000000000006, + 58.800000000000004, + 59.20000000000001, + 59.60000000000001, + 60.00000000000001, + 60.400000000000006, + 60.80000000000001, + 61.20000000000001, + 61.600000000000016, + 62.00000000000001, + 62.400000000000006, + 62.80000000000001, + 63.20000000000002, + 63.600000000000016, + 64.00000000000001, + 64.4, + 64.80000000000001, + 65.20000000000002, + 65.60000000000002, + 66.00000000000001, + 66.40000000000002, + 66.80000000000001, + 67.20000000000002, + 67.60000000000002, + 68.00000000000003, + 68.39999999999999, + 68.8, + 69.19999999999999, + 69.6, + 70.0, + 70.4, + 70.8, + 71.19999999999999, + 71.6, + 72.0, + 72.4, + 72.80000000000001, + 73.2, + 73.6, + 74.0, + 74.4, + 74.80000000000001, + 75.2, + 75.60000000000001, + 76.0, + 76.4, + 76.80000000000001, + 77.20000000000002, + 77.60000000000001, + 78.0, + 78.4, + 78.80000000000001, + 79.20000000000002, + 79.60000000000001, + 80.00000000000001, + 80.4, + 80.80000000000001, + 81.20000000000002, + 81.60000000000001, + 82.00000000000001, + 82.40000000000002, + 82.80000000000001, + 83.20000000000002, + 83.60000000000002, + 84.00000000000003, + 84.40000000000002, + 84.80000000000001, + 85.20000000000002, + 85.60000000000001, + 86.00000000000001, + 86.40000000000002, + 86.80000000000003, + 87.2, + 87.6, + 87.99999999999999, + 88.39999999999999, + 88.8, + 89.2, + 89.6, + 90.0 + ], + "import_time": 5.989602585992543, + "normalized_projections_size_gb": 0.010200420379638671, + "normalized_projections_directory": "/src/examples/data/tiff_image/20240216-1850-8000.00eV", + "saved_as_tiff": false +} \ No newline at end of file diff --git a/tomopyui/backend/schemas/prenorm_input.py b/tomopyui/backend/schemas/prenorm_input.py new file mode 100644 index 0000000..6308dd0 --- /dev/null +++ b/tomopyui/backend/schemas/prenorm_input.py @@ -0,0 +1,61 @@ +# generated by datamodel-codegen: +# filename: import_metadata.json +# timestamp: 2024-02-16T18:52:43+00:00 + +from __future__ import annotations +from enum import Enum + +from typing import Optional + +from pydantic import BaseModel, Field + + +class PixelUnits(str, Enum): + nm = "nm" + um = "um" + mm = "mm" + cm = "cm" + + +class EnergyUnits(str, Enum): + eV = "eV" + keV = "keV" + + +class Binning(str, Enum): + none = "None" + two = "2" + four = "4" + eight = "8" + + +class PrenormProjectionsMetadata(BaseModel): + metadata_type: str + data_hierarchy_level: int + start_angle: int + end_angle: int + pixel_size: int + pixel_units: str + energy: float + energy_units: str + binning: Optional[int] = None + pxX: int + pxY: int + pxZ: int + + +class PrenormProjectionsMetadataNew(BaseModel): + start_angle: float = Field(-90, description="Starting angle (\u00b0)") + end_angle: float = Field(90, description="Ending angle (\u00b0)") + pixel_size: float = Field(30, description="Pixel size (no binning)") + pixel_units: PixelUnits = Field(PixelUnits.nm, description="Pixel size units") + energy: float = Field(8000, description="Energy") + energy_units: EnergyUnits = Field(EnergyUnits.eV, description="Energy units") + binning: Optional[Binning] = Field(Binning.two, description="Binning") + + +groupings = [ + ["start_angle", "end_angle"], + ["pixel_size", "pixel_units", "binning"], + ["energy", "energy_units"], +] diff --git a/tomopyui/backend/schemas/prep.py b/tomopyui/backend/schemas/prep.py new file mode 100644 index 0000000..c2aa372 --- /dev/null +++ b/tomopyui/backend/schemas/prep.py @@ -0,0 +1,12 @@ +from __future__ import annotations +import datetime +from enum import Enum +import pathlib + +from typing import Any, Dict, List, Optional, Union + +from pydantic import BaseModel, Field + +class Prep(BaseModel): + data_hierarchy_level: int + prep_list: List[List[Union[str, List[int]]]] \ No newline at end of file