├── .github └── workflows │ ├── docs.yml │ └── python-package.yml ├── .gitignore ├── CHANGES.md ├── INSTALLATION.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── example ├── autolamella.py ├── example.ipynb ├── example.py ├── example_imaging.py ├── example_milling.py ├── example_movement.py ├── example_notebook.ipynb ├── lithography.py ├── profile.npy ├── protocol_autolamella.yaml ├── protocol_lithography.yaml ├── protocol_slice_and_view.yaml └── slice_and_view.py ├── external └── application_files │ ├── autolamella.xml │ └── cryo_Pt_dep.xml ├── fibsem ├── __init__.py ├── acquire.py ├── alignment.py ├── calibration.py ├── chat │ ├── .gitignore │ ├── main.py │ └── requirements.txt ├── config.py ├── config │ ├── deposition.dbp │ ├── microscope-configuration.yaml │ ├── odemis-configuration.yaml │ ├── positions.yaml │ ├── protocol.yaml │ ├── tescan-configuration.yaml │ ├── tescan_manipulator.yaml │ ├── tfs-aquilos2-configuration.yaml │ ├── tfs-arctis-configuration.yaml │ └── tfs-hydra-configuration.yaml ├── configuration.py ├── constants.py ├── conversions.py ├── db │ ├── app.py │ ├── config.yaml │ ├── notebook.ipynb │ ├── util.py │ ├── v2 │ │ ├── app.py │ │ ├── models.py │ │ ├── notebook.ipynb │ │ └── util.py │ └── v3 │ │ ├── app.py │ │ ├── models.py │ │ ├── notebook.ipynb │ │ └── util.py ├── detection │ ├── __init__.py │ ├── config-autolamella-liftout-hf-mega.yaml │ ├── config-autolamella-liftout.yaml │ ├── config-autolamella-serial-liftout-hf-mega.yaml │ ├── config-autolamella-serial-liftout.yaml │ ├── config-autolamella-waffle-v2-hf-mega.yaml │ ├── config-autolamella-waffle-v2.yaml │ ├── config-autolamella-waffle.yml │ ├── config-autoliftout-dm-embryo.yml │ ├── detection.py │ ├── evaluation.py │ ├── run_evaluation.py │ ├── test_image.tif │ ├── test_image_2.tif │ ├── test_images │ │ ├── serial │ │ │ ├── serial_liftout_mask_00.tif │ │ │ ├── serial_liftout_mask_01.tif │ │ │ ├── serial_liftout_mask_02.tif │ │ │ ├── serial_liftout_mask_03.tif │ │ │ ├── serial_liftout_mask_04.tif │ │ │ ├── serial_liftout_mask_05.tif │ │ │ ├── serial_liftout_mask_06.tif │ │ │ └── serial_liftout_mask_07.tif │ │ ├── test_needle_mask.tif │ │ ├── test_needle_mask2.tif │ │ └── test_needle_mask3.tif │ └── utils.py ├── gis.py ├── imaging │ ├── .gitkeep │ ├── __init__.py │ ├── autogamma.py │ ├── masks.py │ ├── spot.py │ ├── tiled.py │ └── utils.py ├── microscope.py ├── microscopes │ ├── notebook.ipynb │ ├── odemis_microscope.py │ ├── simulator.py │ └── tescan.py ├── milling │ ├── __init__.py │ ├── base.py │ ├── config.py │ ├── core.py │ ├── milling_notebook.ipynb │ ├── patterning │ │ ├── patterns2.py │ │ └── plotting.py │ └── strategy │ │ ├── __init__.py │ │ ├── overtilt.py │ │ └── standard.py ├── movement.py ├── segmentation │ ├── README.md │ ├── __init__.py │ ├── _nnunet.py │ ├── adaptive_model.py │ ├── augmentation.ipynb │ ├── config-autolamella-mega-v4-xl.yml │ ├── config-autolamella-mega-v4.yml │ ├── config-autolamella-mega-v5.yml │ ├── config-autolamella-waffle4.yml │ ├── config.py │ ├── config.yml │ ├── dataset.py │ ├── docs │ │ ├── example_napari.png │ │ └── imgs │ │ │ ├── combined │ │ │ └── combined.jpg │ │ │ ├── labelled │ │ │ └── label.tif │ │ │ └── raw │ │ │ └── image.tif │ ├── example.ipynb │ ├── hf_segmentation_model.py │ ├── huggingface.ipynb │ ├── inference.py │ ├── model.py │ ├── models │ │ └── .gitkeep │ ├── nnunet_model.py │ ├── onnx_model.py │ ├── requirements.txt │ ├── sam_model.py │ ├── segmentation_config.yaml │ ├── test_image.tif │ ├── train.py │ └── utils.py ├── structures.py ├── tools │ ├── _parser.py │ ├── _streamlit.py │ ├── run_manipulator_calibration.py │ ├── run_split_dataset.py │ └── telemetry.py ├── transformations.py ├── ui │ ├── .gitkeep │ ├── FibsemCryoDepositionWidget.py │ ├── FibsemCryoDepositionWidget_qt.py │ ├── FibsemEmbeddedDetectionWidget.py │ ├── FibsemFeatureLabellingUI.py │ ├── FibsemImageSettingsWidget.py │ ├── FibsemImageViewer.py │ ├── FibsemLabellingUI.py │ ├── FibsemManipulatorWidget.py │ ├── FibsemMicroscopeConfigurationWidget.py │ ├── FibsemMicroscopeConfigurationWidgetBase.py │ ├── FibsemMillingStageEditorWidget.py │ ├── FibsemMillingWidget.py │ ├── FibsemMinimapWidget.py │ ├── FibsemModelTrainingWidget.py │ ├── FibsemMovementWidget.py │ ├── FibsemSegmentationModelWidget.py │ ├── FibsemSpotBurnWidget.py │ ├── FibsemSystemSetupWidget.py │ ├── FibsemUI.py │ ├── __init__.py │ ├── napari │ │ ├── patterns.py │ │ ├── properties.py │ │ └── utilities.py │ ├── qtdesigner_files │ │ ├── FibsemCryoDepositionWidget.py │ │ ├── FibsemCryoDepositionWidget.ui │ │ ├── FibsemEmbeddedDetectionWidget.py │ │ ├── FibsemEmbeddedDetectionWidget.ui │ │ ├── FibsemExperimentWidget.ui │ │ ├── FibsemFeatureDetectionUI.py │ │ ├── FibsemFeatureDetectionUI.ui │ │ ├── FibsemLabellingUI.py │ │ ├── FibsemLabellingUI.ui │ │ ├── FibsemManipulatorWidget.py │ │ ├── FibsemManipulatorWidget.ui │ │ ├── FibsemMicroscopeConfigurationWidget.py │ │ ├── FibsemMicroscopeConfigurationWidget.ui │ │ ├── FibsemMicroscopeConfigurationWidget2.ui │ │ ├── FibsemMicroscopeConfigurationWidgetBase.py │ │ ├── FibsemMicroscopeConfigurationWidgetBase.ui │ │ ├── FibsemMillingWidget.py │ │ ├── FibsemMillingWidget.ui │ │ ├── FibsemMinimapWidget.py │ │ ├── FibsemMinimapWidget.ui │ │ ├── FibsemModelTrainingWidge.ui │ │ ├── FibsemModelTrainingWidget.py │ │ ├── FibsemModelTrainingWidget.ui │ │ ├── FibsemMovementWidget.py │ │ ├── FibsemMovementWidget.ui │ │ ├── FibsemSegmentationModelWidget.py │ │ ├── FibsemSegmentationModelWidget.ui │ │ ├── FibsemSpotBurnWidget.py │ │ ├── FibsemSpotBurnWidget.ui │ │ ├── FibsemSystemSetupWidget.py │ │ ├── FibsemSystemSetupWidget.ui │ │ ├── FibsemUI.py │ │ ├── FibsemUI.ui │ │ ├── ImageSettingsWidget.py │ │ ├── ImageSettingsWidget.ui │ │ ├── detection_dialog.py │ │ ├── detection_dialog.ui │ │ ├── image_viewer.py │ │ └── image_viewer.ui │ ├── stylesheets.py │ └── utils.py ├── util │ ├── __init__.py │ └── filename.py ├── utils.py └── validation.py ├── mkdocs.yml ├── pyproject.toml ├── requirements-3.8.txt ├── requirements-headless.txt ├── requirements.txt ├── scripts ├── .gitkeep ├── arctis_notebook.ipynb ├── compat-notebook.ipynb ├── convert_to_nnunet_dataset.py ├── convert_to_onnx.py ├── export_nnunet_checkpoint.py ├── generate_segmentation_objects.py ├── install.bat ├── install.sh ├── notebook.ipynb ├── onnx_notebook.ipynb ├── onnx_pred.py ├── run.sh ├── run_ui.bat ├── shortcut.py ├── stack_Arctis_Script.ipynb ├── test-alignment-script.py └── test_installation.py └── tests ├── milling ├── test_base.py └── test_patterns.py ├── tescan-test-notebook.ipynb ├── test_acquire.py ├── test_alignment.py ├── test_example.py ├── test_microscope.py ├── test_movement.py ├── test_structures.py └── test_tescan.py /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - v0.2-stable 6 | pull_request: 7 | branches: 8 | - v0.2-release 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.x 17 | - run: pip install mkdocs-material mkdocstrings-python 18 | - run: mkdocs gh-deploy --force 19 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | pull_request: 8 | branches: [ "v0.2-release" ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: ["3.9", "3.10"] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | python -m pip install -e . 29 | - name: Test with pytest 30 | run: | 31 | pytest 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info/* 3 | .vscode/* 4 | .coverage 5 | *.code-workspace 6 | *.log 7 | scratch/test_image.tif 8 | *.zarr/* 9 | 10 | fibsem/wandb/* 11 | 12 | *.json 13 | fibsem/segmentation/wandb/* 14 | fibsem/segmentation/models/*.pt* 15 | wandb/* 16 | 17 | build/* 18 | fibsem/segmentation/training/* 19 | scratch/training/* 20 | fibsem/testing.ipynb 21 | fibsem/_version.py 22 | demo_*/* 23 | scratch/figure/wd*/* 24 | fibsem/log/data/* 25 | test_images/test.tif 26 | 27 | fibsem/detection/notebook.ipynb 28 | 29 | media/* 30 | .DS_Store 31 | fibsem/segmentation/models/**/*.pt* 32 | fibsem/segmentation/models/* 33 | example/**/*.tif 34 | example/demo/* 35 | fibsem/chat/secret.txt 36 | 37 | dist/* 38 | scratch/tile-images/* 39 | fibsem/log/* 40 | fibsem/db/fibsem.db 41 | fibsem/notebook.ipynb 42 | scratch/health-monitor/* 43 | example/notebook.ipynb 44 | fibsem/segmentation/*config*.y*ml 45 | !fibsem/segmentation/segementation_config.yaml 46 | 47 | fibsem/config/*.yaml 48 | !fibsem/config/microscope-configuration.yaml 49 | !fibsem/config/tescan_manipulator.yaml 50 | !fibsem/config/tfs*.yaml 51 | !fibsem/config/odemis*.yaml 52 | !fibsem/config/tescan-*.yaml 53 | 54 | **/wandb/* 55 | tests/notebook.ipynb 56 | fibsem/db/v2/notebook.ipynb 57 | scripts/segformer-*/* 58 | *.onnx 59 | scripts/**/*.jpeg 60 | scripts/**/*.png 61 | fibsem/ui/test_qt.py 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DeMarcoLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | 4 | recursive-exclude * __pycache__ 5 | recursive-exclude * *.py[co] 6 | recursive-exclude * *.ui -------------------------------------------------------------------------------- /example/autolamella.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import logging 4 | import os 5 | from dataclasses import dataclass 6 | from pathlib import Path 7 | from pprint import pprint 8 | 9 | import numpy as np 10 | from fibsem import acquire, alignment, milling, utils 11 | from fibsem.milling.base import get_milling_stages 12 | from fibsem.structures import BeamType, MicroscopeState, FibsemImage, FibsemStagePosition 13 | from typing import List 14 | 15 | @dataclass 16 | class Lamella: 17 | state: MicroscopeState 18 | reference_image: FibsemImage 19 | path: Path 20 | 21 | def main(): 22 | 23 | PROTOCOL_PATH = os.path.join(os.path.dirname(__file__), "protocol_autolamella.yaml") 24 | microscope, settings = utils.setup_session(protocol_path=PROTOCOL_PATH) 25 | 26 | # move to the milling angle 27 | stage_position = FibsemStagePosition( 28 | r=np.deg2rad(settings.protocol["stage_rotation"]), 29 | t=np.deg2rad(settings.protocol["stage_tilt"]) 30 | ) 31 | microscope.move_stage_absolute(stage_position) # do need a safe version? 32 | 33 | # take a reference image 34 | settings.image.filename = "grid_reference" 35 | settings.image.beam_type = BeamType.ION 36 | settings.image.hfw = 900e-6 37 | settings.image.save = True 38 | acquire.take_reference_images(microscope, settings.image) 39 | 40 | # select positions 41 | experiment: List[Lamella] = [] 42 | lamella_no = 1 43 | settings.image.hfw = 80e-6 44 | base_path = settings.image.path 45 | 46 | while True: 47 | response = input(f"""Move to the desired position. 48 | Do you want to select another lamella? [y]/n {len(experiment)} selected so far.""") 49 | 50 | # store lamella information 51 | if response.lower() in ["", "y", "yes"]: 52 | 53 | # set filepaths 54 | path = os.path.join(base_path, f"{lamella_no:02d}") 55 | settings.image.path = path 56 | settings.image.filename = f"ref_lamella" 57 | acquire.take_reference_images(microscope, settings.image) 58 | 59 | lamella = Lamella( 60 | state=microscope.get_microscope_state(), 61 | reference_image=acquire.new_image(microscope, settings.image), 62 | path = path 63 | ) 64 | experiment.append(lamella) 65 | lamella_no += 1 66 | else: 67 | break 68 | 69 | # sanity check 70 | if len(experiment) == 0: 71 | logging.info(f"No lamella positions selected. Exiting.") 72 | return 73 | 74 | # mill (rough, thin, polish) 75 | workflow_stages = ["rough", "thin", "polish"] 76 | for stage_no, stage_name in enumerate(workflow_stages): 77 | 78 | logging.info(f"Starting milling stage {stage_no}") 79 | 80 | lamella: Lamella 81 | for lamella_no, lamella in enumerate(experiment): 82 | 83 | logging.info(f"Starting lamella {lamella_no:02d}") 84 | 85 | # return to lamella 86 | microscope.set_microscope_state(lamella.state) 87 | 88 | # realign 89 | alignment.beam_shift_alignment_v2(microscope, lamella.reference_image) 90 | 91 | if stage_no == 0: 92 | microexpansion_stage = get_milling_stages("microexpansion", settings.protocol) 93 | milling.mill_stage(microscope, microexpansion_stage[0]) 94 | 95 | # get trench milling pattern, and mill 96 | trench_stage = get_milling_stages("lamella", settings.protocol)[stage_no] 97 | milling.mill_stage(microscope, trench_stage) 98 | 99 | # retake reference image 100 | settings.image.path = lamella.path 101 | settings.image.filename = f"ref_mill_stage_{stage_no:02d}" 102 | lamella.reference_image = acquire.new_image(microscope, settings.image) 103 | 104 | if stage_no == 3: 105 | # take final reference images 106 | settings.image.filename = f"ref_final" 107 | acquire.take_reference_images(microscope, settings.image) 108 | 109 | logging.info(f"Finished autolamella: {settings.protocol['name']}") 110 | 111 | 112 | if __name__ == "__main__": 113 | main() 114 | -------------------------------------------------------------------------------- /example/example.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from fibsem import utils, acquire 4 | 5 | import matplotlib.pyplot as plt 6 | import matplotlib 7 | matplotlib.use('TkAgg', force=True) # Activate 'agg' backend for off-screen plotting. 8 | 9 | 10 | def main(): 11 | 12 | # connect to microscope 13 | microscope, settings = utils.setup_session(manufacturer="Demo", ip_address="localhost") 14 | 15 | # take image with both beams 16 | eb_image, ib_image = acquire.take_reference_images(microscope, settings.image) 17 | 18 | # show images 19 | fig, ax = plt.subplots(1, 2, figsize=(10, 5)) 20 | ax[0].imshow(eb_image.data, cmap="gray") 21 | ax[0].set_title("Electron Beam Image") 22 | ax[0].axis("off") 23 | ax[1].imshow(ib_image.data, cmap="gray") 24 | ax[1].set_title("Ion Beam Image") 25 | ax[1].axis("off") 26 | plt.show() 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /example/example_imaging.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | import matplotlib.pyplot as plt 3 | 4 | from fibsem import acquire, utils 5 | from fibsem.structures import BeamType 6 | import logging 7 | 8 | matplotlib.use('TkAgg', force=True) # Activate 'agg' backend for off-screen plotting. 9 | 10 | 11 | """ 12 | This script will take an image with the electron beam, an image with the ion beam, and an image with both beams. 13 | The images are then displayed in a matplotlib figure. 14 | 15 | The settings for images are stored in the settings.image struct, and can be modified before taking an image. 16 | 17 | For more detail on the settings, see the documentation for the ImageSettings class. 18 | 19 | """ 20 | 21 | def main(): 22 | 23 | # connect to the microscope 24 | microscope, settings = utils.setup_session(manufacturer="Demo", ip_address="localhost") 25 | 26 | # info about ImageSettings 27 | logging.info(f"\nAcquiring Images Example:") 28 | logging.info(f"The current image settings are: \n{settings.image}") 29 | 30 | # take an image with the electron beam 31 | settings.image.beam_type = BeamType.ELECTRON 32 | eb_image = acquire.new_image(microscope, settings.image) 33 | 34 | # take an image with the ion beam 35 | settings.image.beam_type = BeamType.ION 36 | ib_image = acquire.new_image(microscope, settings.image) 37 | 38 | # take an image with both beams with increased hfw 39 | settings.image.hfw = 400e-6 40 | ref_eb_image, ref_ib_image = acquire.take_reference_images(microscope, settings.image) 41 | 42 | # show images 43 | 44 | fig, ax = plt.subplots(2, 2, figsize=(10, 7)) 45 | ax[0][0].imshow(eb_image.data, cmap="gray") 46 | ax[0][0].set_title("Electron Image 01") 47 | ax[0][1].imshow(ib_image.data, cmap="gray") 48 | ax[0][1].set_title("Ion Image 01") 49 | ax[1][0].imshow(ref_eb_image.data, cmap="gray") 50 | ax[1][0].set_title("Electron Image 02 (Reference)") 51 | ax[1][1].imshow(ref_ib_image.data, cmap="gray") 52 | ax[1][1].set_title("Ion Image 02 (Reference)") 53 | plt.show() 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /example/example_milling.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from fibsem import milling, utils 4 | from fibsem.structures import ( 5 | FibsemCircleSettings, 6 | FibsemLineSettings, 7 | FibsemMillingSettings, 8 | FibsemRectangleSettings, 9 | ) 10 | from fibsem.milling import FibsemMillingStage, MillingAlignment 11 | from fibsem.milling.patterning.patterns2 import RectanglePattern, Point 12 | 13 | """ 14 | This script demonstrates how to use the milling module to mill a rectangle and two lines. 15 | 16 | The script will: 17 | - connect to the microscope 18 | - setup milling 19 | - draw a rectangle and two lines 20 | - run milling 21 | - finish milling (restore ion beam current) 22 | 23 | """ 24 | 25 | def main(): 26 | 27 | # connect to microscope 28 | microscope, settings = utils.setup_session(manufacturer="Demo", ip_address="localhost") 29 | 30 | 31 | # setup milling stage (settings and alignment) 32 | stage = FibsemMillingStage( 33 | milling=FibsemMillingSettings( 34 | milling_current=2.0e-9, 35 | milling_voltage=30.0e3, 36 | hfw=100e-6, 37 | application_file="Si", 38 | patterning_mode="Serial", 39 | ), 40 | alignment=MillingAlignment( 41 | enabled=False 42 | ) 43 | ) 44 | 45 | # rectangle 46 | rectangle_shape = FibsemRectangleSettings( 47 | width = 10.0e-6, 48 | height = 10.0e-6, 49 | depth = 2.0e-6, 50 | rotation = 0.0, 51 | centre_x = 0.0, 52 | centre_y = 0.0, 53 | ) 54 | 55 | # circle 56 | circle_shape = FibsemCircleSettings( 57 | radius = 10.0e-6, 58 | depth = 2.0e-6, 59 | centre_x = 10e-6, 60 | centre_y = 10e-6, 61 | ) 62 | 63 | # line pattern 64 | line_shape = FibsemLineSettings( 65 | start_x = 0.0, 66 | start_y = 0.0, 67 | end_x = 10.0e-6, 68 | end_y = 10.0e-6, 69 | depth = 1.0e-6, 70 | ) 71 | 72 | logging.info(f"""\nMilling Pattern Example: """) 73 | logging.info(f"The current milling settings are: \n{settings.milling}") 74 | logging.info(f"The current rectangle pattern is \n{rectangle_shape}") 75 | logging.info(f"The current circle pattern ins is \n{circle_shape}") 76 | logging.info(f"The current line pattern is \n{line_shape}") 77 | logging.info("---------------------------------- Milling ----------------------------------\n") 78 | # setup patterns in a list 79 | patterns = [rectangle_shape, circle_shape, line_shape] 80 | 81 | milling.setup_milling(microscope, stage) 82 | 83 | # draw patterns 84 | milling.draw_patterns(microscope, patterns) 85 | 86 | # run milling 87 | milling.run_milling(microscope, stage.milling.milling_current, milling_voltage=stage.milling.milling_voltage) 88 | 89 | # finish milling 90 | milling.finish_milling(microscope, microscope.system.ion.beam.beam_current) 91 | 92 | 93 | rect = RectanglePattern( 94 | width=10e-6, 95 | height=10e-6, 96 | depth=2e-6, 97 | rotation=0, 98 | point=Point(0, 0) 99 | ) 100 | stage.pattern = rect 101 | 102 | milling.mill_stages(microscope, stage) 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /example/example_movement.py: -------------------------------------------------------------------------------- 1 | from fibsem import utils 2 | from fibsem.structures import FibsemStagePosition 3 | import numpy as np 4 | import logging 5 | 6 | 7 | """ 8 | This script demonstrates how to get the current stage position, and how to move the stage to a new position. 9 | 10 | The basic movement methods are absolute_move and relative_move. 11 | - Relative move moves the stage by a certain amount in the current coordinate system. 12 | - Absolute move moves the stage to a new position in the absolute coordinate system. 13 | 14 | This script will move the stage by 20um in the x direction (relative move), and then move back to the original position (absolute move). 15 | 16 | Additional movement methods are available in the core api: 17 | - Stable Move: the stage moves along the sample plane, accounting for stage tilt, and shuttle pre-tilt 18 | - Vertical Move: the stage moves vertically in the chamber, regardless of tilt orientation 19 | 20 | """ 21 | 22 | def main(): 23 | 24 | # connect to microscope 25 | microscope, settings = utils.setup_session(manufacturer="Demo", ip_address="localhost") 26 | 27 | # info about ImageSettings 28 | logging.info("---------------------------------- Current Position ----------------------------------\n") 29 | 30 | # get current position 31 | intial_position = microscope.get_stage_position() 32 | logging.info(f"\nStage Movement Example:") 33 | logging.info(f"Current stage position: {intial_position}") 34 | 35 | 36 | logging.info("\n---------------------------------- Relative Movement ----------------------------------\n") 37 | 38 | #### Moving to a relative position #### 39 | relative_move = FibsemStagePosition(x=20e-6, # metres 40 | y=0, # metres 41 | z=0.0, # metres 42 | r=np.deg2rad(0), # radians 43 | t=np.deg2rad(0)) # radians 44 | 45 | input(f"Press Enter to move by: {relative_move} (Relative)") 46 | 47 | # move by relative position 48 | microscope.move_stage_relative(relative_move) 49 | current_position = microscope.get_stage_position() 50 | logging.info(f"After move stage position: {current_position}") 51 | 52 | 53 | logging.info("\n---------------------------------- Absolute Movement ----------------------------------\n") 54 | 55 | #### Moving to an absolute position #### 56 | stage_position = intial_position # move back to initial position 57 | 58 | # uncomment this if you want to move to a different position 59 | # be careful to define a safe position to move too 60 | # relative_move = FibsemStagePosition(x=0, # metres 61 | # y=0, # metres 62 | # z=0.0, # metres 63 | # r=np.deg2rad(0), # radians 64 | # t=np.deg2rad(0)) # radians 65 | 66 | input(f"Press Enter to move to: {stage_position} (Absolute)") 67 | 68 | # move to absolute position 69 | microscope.move_stage_absolute(stage_position) 70 | current_position = microscope.get_stage_position() 71 | logging.info(f"After move stage position: {current_position}") 72 | 73 | 74 | logging.info("---------------------------------- End Example ----------------------------------") 75 | 76 | 77 | if __name__ == "__main__": 78 | main() -------------------------------------------------------------------------------- /example/lithography.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pprint import pprint 3 | 4 | import numpy as np 5 | from autoscript_sdb_microscope_client.structures import ( 6 | BitmapPatternDefinition, StagePosition) 7 | from fibsem import acquire, milling, movement, utils 8 | # from fibsem.ui import windows 9 | from fibsem.structures import BeamType 10 | from PIL import Image 11 | 12 | BASE_PATH = os.path.dirname(__file__) 13 | 14 | def save_profile_to_bmp(arr: np.ndarray, fname: str = "profile.bmp"): 15 | 16 | # scale values to int 17 | arr = (arr / np.max(arr)) * 255 18 | arr = arr.astype(np.uint8) 19 | 20 | # save profile 21 | Image.fromarray(arr).convert("RGB").save(fname) 22 | 23 | 24 | def main(): 25 | 26 | PROTOCOL_PATH = os.path.join(BASE_PATH, "protocol_lithography.yaml") 27 | microscope, settings = utils.setup_session(protocol_path=PROTOCOL_PATH) 28 | 29 | # lens plane 30 | microscope.system.electron.column_tilt = 0 31 | 32 | # move to the milling angle 33 | # stage_position = StagePosition( 34 | # r=np.deg2rad(settings.protocol["stage_rotation"]), 35 | # t=np.deg2rad(settings.protocol["stage_tilt"]), 36 | # ) 37 | # movement.safe_absolute_stage_movement(microscope, stage_position) 38 | 39 | microscope.move_flat_to_beam(BeamType.ION) 40 | 41 | # eucentric, select position 42 | # windows.ask_user_movement( 43 | # microscope, 44 | # settings, 45 | # msg_type="eucentric", 46 | # msg="Select a position to mill the pattern.", 47 | # ) 48 | 49 | # lens profile files 50 | npy_path = os.path.join(BASE_PATH, settings.protocol["profile"]) 51 | bmp_path = os.path.join(BASE_PATH, "profile.bmp") 52 | 53 | # load milling properties 54 | profile = np.load(npy_path) 55 | pixel_size = settings.protocol["pixelsize"] 56 | lens_height = settings.protocol["milling"]["height"] 57 | lens_width = settings.protocol["milling"]["width"] 58 | 59 | # save profile to bmp 60 | save_profile_to_bmp(profile, bmp_path) 61 | 62 | # load bmp pattern 63 | bitmap_pattern = BitmapPatternDefinition() 64 | bitmap_pattern.load(bmp_path) 65 | 66 | # milling setup 67 | milling.setup_milling( 68 | microscope, application_file=settings.protocol["application_file"] 69 | ) 70 | 71 | # surface milling 72 | microscope.patterning.create_bitmap( 73 | center_x=0, 74 | centre_y=0, 75 | width=lens_width, 76 | height=lens_height, 77 | depth=settings.protocol["initial_depth"], 78 | bitmap_pattern_definition=bitmap_pattern, 79 | ) 80 | 81 | # mill bitmap 82 | microscope.patterning.create_bitmap( 83 | center_x=0, 84 | centre_y=0, 85 | width=lens_width, 86 | height=lens_height, 87 | depth=settings.protocol["milling"]["milling_depth"], 88 | bitmap_pattern_definition=bitmap_pattern, 89 | ) 90 | milling.run_milling(microscope, settings.protocol["milling"]["milling_current"]) 91 | milling.finish_milling(microscope) 92 | 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /example/profile.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/example/profile.npy -------------------------------------------------------------------------------- /example/protocol_autolamella.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: autolamella_demo 3 | stage_rotation: 50 4 | stage_tilt: 20 5 | 6 | 7 | fiducial: 8 | length: 10.e-6 9 | width: 2.e-6 10 | depth: 2.e-6 11 | milling_voltage: 30.0e+3 12 | milling_current: 2.e-9 13 | hfw: 150.0e-6 14 | application_file: autolamella 15 | 16 | lamella: 17 | stages: 18 | - lamella_width: 10.e-6 19 | lamella_height: 5.e-6 20 | trench_height: 10.e-6 21 | depth: 2.e-6 22 | offset: 5.e-6 23 | size_ratio: 1.0 24 | milling_voltage: 30.0e+3 25 | milling_current: 2.e-9 26 | cleaning_cross_section: True 27 | hfw: 150.0e-6 28 | application_file: autolamella 29 | - lamella_width: 10.e-6 30 | lamella_height: 5.e-6 31 | trench_height: 1.e-6 32 | depth: 2.e-6 33 | offset: 0.5e-6 34 | size_ratio: 1.0 35 | hfw: 80.0e-6 36 | milling_voltage: 30.0e+3 37 | milling_current: 0.2e-9 38 | cleaning_cross_section: True 39 | application_file: autolamella 40 | - lamella_width: 10.e-6 41 | lamella_height: 5.e-6 42 | trench_height: 0.5e-6 43 | depth: 0.4e-6 44 | offset: 0.e-6 45 | size_ratio: 1.0 46 | milling_current: 60.e-12 47 | milling_voltage: 30.0e+3 48 | hfw: 80.0e-6 49 | cleaning_cross_section: True 50 | application_file: autolamella 51 | 52 | microexpansion: 53 | height: 10.0e-6 54 | width: 2.0e-6 55 | depth: 5.0e-6 56 | distance: 10.0e-6 57 | hfw: 150.0e-6 58 | milling_voltage: 30.0e+3 59 | milling_current: 2.e-9 60 | application_file: autolamella 61 | -------------------------------------------------------------------------------- /example/protocol_lithography.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: lens_milling_demo 3 | stage_rotation: 230 4 | stage_tilt: 52 5 | application_file: Si 6 | plasma_gas: Xenon 7 | 8 | profile: profile.npy 9 | pixelsize: 1.e-6 10 | milling: 11 | width: 50.e-6 12 | height: 30.0e-6 13 | milling_current: 60.e-9 14 | initial_depth: 600.e-9 15 | milling_depth: 5.0e-6 16 | 17 | -------------------------------------------------------------------------------- /example/protocol_slice_and_view.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: slice_and_view_demo 3 | steps: 10 4 | step_size: 150.e-9 5 | milling: 6 | width: 100.e-6 7 | height: 1.e-6 8 | depth: 5.e-6 9 | milling_current: 2.e-9 10 | scan_direction: BottomToTop 11 | cross_section: Rectangle 12 | -------------------------------------------------------------------------------- /example/slice_and_view.py: -------------------------------------------------------------------------------- 1 | # slice and view 2 | 3 | import os 4 | import logging 5 | from pprint import pprint 6 | 7 | import fibsem 8 | from fibsem import acquire, milling, utils, alignment 9 | from fibsem.structures import BeamType, ImageSettings, FibsemRectangleSettings 10 | import numpy as np 11 | 12 | def main(): 13 | 14 | PROTOCOL_PATH = os.path.join(os.path.dirname(__file__), "protocol_slice_and_view.yaml") 15 | microscope, settings = utils.setup_session(protocol_path = PROTOCOL_PATH) 16 | 17 | # setup for milling 18 | milling.setup_milling(microscope = microscope, 19 | patterning_mode = "Serial", 20 | mill_settings = settings.milling) 21 | 22 | pattern_settings = FibsemRectangleSettings( 23 | width = settings.protocol["milling"]["width"], 24 | height = settings.protocol["milling"]["height"], 25 | depth = settings.protocol["milling"]["depth"], 26 | scan_direction= settings.protocol["milling"]["scan_direction"], 27 | cross_section= settings.protocol["milling"]["cross_section"] 28 | ) 29 | 30 | # angle correction 31 | microscope.set("angular_correction_tilt_correction", True) 32 | microscope.set("angular_correction_angle", np.deg2rad(-38)) 33 | 34 | # update image settings 35 | settings.image.filename = "reference" 36 | settings.image.save = True 37 | settings.image.beam_type = BeamType.ELECTRON 38 | 39 | eb_image, ib_image = acquire.take_reference_images(microscope, settings.image) 40 | ref_eb, ref_ib = None, None 41 | 42 | for slice_idx in range(int(settings.protocol["steps"])): 43 | 44 | # slice 45 | logging.info("------------------------ SLICE ------------------------") 46 | milling_settings = settings.milling 47 | 48 | patterns = milling.draw_pattern(microscope, pattern_settings) 49 | # estimated_milling_time = milling.estimate_milling_time_in_seconds([patterns]) 50 | # logging.info(f"Estimated milling time: {estimated_milling_time}") 51 | milling.run_milling(microscope, milling_current=milling_settings.milling_current) 52 | milling.finish_milling(microscope, settings.system.ion.current) 53 | 54 | # view 55 | logging.info("------------------------ VIEW ------------------------") 56 | settings.image.filename = f"slice_{slice_idx:04d}" 57 | eb_image = acquire.new_image(microscope, settings.image) 58 | 59 | 60 | # align 61 | if ref_eb is not None: 62 | alignment.align_using_reference_images(microscope, ref_eb, eb_image) 63 | ref_eb = eb_image 64 | 65 | 66 | # update patterns 67 | pattern_settings.centre_y += settings.protocol["step_size"] 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /external/application_files/autolamella.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | XTUI, iFast 10 | 11 | 12 | 13 | 14 | 15 | Ion 16 | 17 | 18 | 19 | 22 | 23 | Line, Circle, Rectangle, Bitmap, Streamfile, Polygon, CCS, RCS 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 1.000E-6 35 | 36 | 37 | 38 | 39 | 40 | 98 41 | 42 | 43 | 44 | 46 | 47 | 0.00000000038 48 | 49 | 50 | 51 | 62 | 63 | 0 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /external/application_files/cryo_Pt_dep.xml: -------------------------------------------------------------------------------- 1 | 2 | Line 3 | Electron 4 | Pt cryo 5 | 1:0.2 6 | 1:cryo 7 | 1E-07 8 | -90 9 | 5E-11 10 | 0 11 | 0 12 | 0 13 | 0 14 | 0 15 | 1 16 | -------------------------------------------------------------------------------- /fibsem/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import importlib.metadata 4 | __version__ = importlib.metadata.version('fibsem') 5 | except ModuleNotFoundError: 6 | __version__ = "unknown" 7 | 8 | -------------------------------------------------------------------------------- /fibsem/chat/.gitignore: -------------------------------------------------------------------------------- 1 | secret.txt 2 | documents/* 3 | .chroma/* 4 | *.pdf 5 | *.mp4 6 | *.docx -------------------------------------------------------------------------------- /fibsem/chat/main.py: -------------------------------------------------------------------------------- 1 | # 2 | from langchain.document_loaders import UnstructuredPDFLoader 3 | from langchain.indexes import VectorstoreIndexCreator 4 | from langchain.llms import OpenAI 5 | import os 6 | 7 | # import OpenAI API key from secret.txt 8 | with open("secret.txt", "r") as f: 9 | os.environ["OPENAI_API_KEY"] = f.read() 10 | 11 | def create_index(filenames: list[str]): 12 | # TODO: make a list of files to index 13 | llm = OpenAI(openai_api_key="OPENAI_API_KEY") 14 | loaders = [UnstructuredPDFLoader(fname) for fname in filenames] 15 | index = VectorstoreIndexCreator().from_loaders(loaders) 16 | 17 | return index 18 | 19 | 20 | # TODO: select files 21 | # TODO: tool use 22 | 23 | # persist 24 | # https://python.langchain.com/en/latest/modules/indexes/vectorstores/examples/chroma.html 25 | 26 | # enter an endless loop with user input 27 | def main(): 28 | print("Welcome to FIBSEM Chat! Enter your question, or type 'quit' to exit.") 29 | 30 | # load user manual, index 31 | filenames = ["documents/OnlineManualHeliosHydra.pdf"] 32 | # filenames = ["documents/autoliftout.pdf", "documents/supplementary.pdf"] 33 | 34 | print(f"Creating index from {filenames}.") 35 | index = create_index(filenames=filenames) 36 | print(f"Index created from {filenames}.\n") 37 | 38 | while True: 39 | 40 | q = input("Enter a question: ") 41 | 42 | if q in ["quit", "exit"]: 43 | print("\nGoodbye!") 44 | break 45 | 46 | print("Thinking...") 47 | 48 | # run query, show result 49 | ret = index.query(q) 50 | 51 | print("\n\nResponse: " + ret) 52 | print("-"*50) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /fibsem/chat/requirements.txt: -------------------------------------------------------------------------------- 1 | langchain 2 | openai 3 | jupyter 4 | unstructured 5 | chromadb 6 | Cython 7 | tiktoken 8 | pdf2image -------------------------------------------------------------------------------- /fibsem/config/deposition.dbp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/config/deposition.dbp -------------------------------------------------------------------------------- /fibsem/config/positions.yaml: -------------------------------------------------------------------------------- 1 | - coordinate_system: RAW 2 | name: grid02-pre-tilt-35-deg-electron 3 | r: 0.8551927515811997 4 | t: 0.6108583337307305 5 | x: 0.0028004166666666668 6 | y: 0.004053833333333333 7 | z: 0.03175649434156379 8 | - coordinate_system: RAW 9 | name: grid02-pre-tilt-35-deg-ion 10 | r: -2.286298510029761 11 | t: 0.29669765745887133 12 | x: -0.00368525 13 | y: -0.0023315 14 | z: 0.03184661136831276 15 | - coordinate_system: RAW 16 | name: grid01-pre-tilt-35-deg-ion 17 | r: -2.286298510029761 18 | t: 0.29669765745887133 19 | x: 0.0024960833333333332 20 | y: -0.002562333333333333 21 | z: 0.03186551568930041 22 | - coordinate_system: RAW 23 | name: grid01-pre-tilt-35-deg-electron 24 | r: 0.8551927515811997 25 | t: 0.6108470164082682 26 | x: -0.003231 27 | y: 0.00415 28 | z: 0.03186721965020576 29 | - coordinate_system: Raw 30 | name: autoliftout-pre-tilt-35-deg-grid-01-lamella 31 | r: -2.28636643635382 32 | t: 0.2967058527613453 33 | x: 0.0023149166666666665 34 | y: -0.0019699166666666667 35 | z: 0.031954218106995884 36 | - coordinate_system: Raw 37 | name: autoliftout-pre-tilt-35-deg-grid-02-landing 38 | r: -2.286298510029761 39 | t: 0.5585004974916625 40 | x: -0.003933166666666666 41 | y: -0.0023545 42 | z: 0.031787004886831276 43 | - coordinate_system: RAW 44 | name: autoliftout-serial-pre-tilt-35-deg-grid-01-lamella 45 | r: -2.2863324731917913 46 | t: 0.2967019502363577 47 | x: 0.00238175 48 | y: -0.0025256666666666665 49 | z: 0.031839988425925926 50 | - coordinate_system: RAW 51 | name: autoliftout-serial-pre-tilt-35-deg-grid-02-lamella 52 | r: -2.2863324731917913 53 | t: 0.2967019502363577 54 | x: -0.00364925 55 | y: -0.0025468333333333332 56 | z: 0.031784593621399175 57 | - coordinate_system: RAW 58 | name: autoliftout-serial-pre-tilt-35-deg-grid-01-landing 59 | r: 0.8551927515811997 60 | t: 0.45378482501352835 61 | x: -0.0034078333333333335 62 | y: 0.004113083333333333 63 | z: 0.031839988425925926 64 | - coordinate_system: RAW 65 | name: autoliftout-serial-pre-tilt-35-deg-grid-02-landing 66 | r: 0.8551927515811997 67 | t: 0.4537750687010593 68 | x: 0.0029505833333333333 69 | y: 0.004088416666666666 70 | z: 0.03178491512345679 71 | - coordinate_system: RAW 72 | name: arctis-grid-01-electron 73 | r: 0 74 | t: -3.141592653589793 75 | x: 0 76 | y: 0 77 | z: 0 78 | - coordinate_system: RAW 79 | name: arctis-grid-01-ion 80 | r: 0 81 | t: -2.234021442552742 82 | x: 0 83 | y: 0 84 | z: 0 85 | -------------------------------------------------------------------------------- /fibsem/config/protocol.yaml: -------------------------------------------------------------------------------- 1 | # default protocol 2 | 3 | name: fibsem 4 | version: 0.1.0 5 | description: protocol for fibsem 6 | 7 | 8 | milling: 9 | milling_current: 2.e-9 10 | milling_voltage: 30000 11 | application_file: Si 12 | hfw: 150.0e-6 13 | spot_size: 5.0e-8 14 | rate: 3.0e-3 15 | dwell_time: 1.e-6 16 | preset: "30 keV; 2.5 nA" # TESCAN only 17 | 18 | patterns: 19 | Rectangle: 20 | width: 10.0e-6 21 | height: 5.0e-6 22 | depth: 1.0e-6 23 | rotation: 0.0 24 | scan_direction: TopToBottom 25 | passes: 0 # means auto 26 | time: 0 # means auto 27 | Line: 28 | start_x: -10.0e-6 29 | start_y: 0.0 30 | end_x: 10.0e-6 31 | end_y: 0 32 | depth: 1.0e-6 33 | Circle: 34 | radius: 5.0e-6 35 | depth: 1.0e-6 36 | Trench: 37 | width: 10.0e-6 38 | spacing: 5.0e-6 39 | upper_trench_height: 5.0e-6 40 | lower_trench_height: 5.0e-6 41 | depth: 2.0e-6 42 | time: 0 # means auto 43 | fillet: 0 # no fillet radius 44 | Horseshoe: 45 | width: 40.0e-6 46 | spacing: 10.0e-6 47 | upper_trench_height: 10.0e-6 48 | lower_trench_height: 10.0e-6 49 | side_width: 5.0e-6 50 | side_offset: 0.0 51 | depth: 10.0e-6 52 | scan_direction: TopToBottom 53 | HorseshoeVertical: 54 | depth: 4.0e-6 55 | height: 5.0e-05 56 | width: 2.0e-05 57 | scan_direction: TopToBottom 58 | side_trench_width: 5.0e-06 59 | top_trench_height: 10.0e-6 60 | inverted: False 61 | SerialSection: 62 | section_thickness: 4.0e-6 63 | section_width: 50.0e-6 64 | section_depth: 20.0e-6 65 | side_width: 10.0e-6 66 | side_depth: 40.0e-6 67 | side_height: 10.0e-6 68 | inverted: false 69 | RectangleOffset: 70 | width: 10.0e-6 71 | height: 5.0e-6 72 | depth: 1.0e-6 73 | scan_direction: TopToBottom 74 | cross_section: Rectangle 75 | offset: 0.0e-6 76 | inverted: false 77 | Undercut: 78 | height: 10.0e-6 79 | width: 10.0e-6 80 | depth: 5.0e-6 81 | trench_width: 2.0e-6 82 | rhs_height: 10.0e-6 83 | h_offset: 5.0e-6 84 | Fiducial: 85 | height: 10.0e-6 86 | width: 1.0e-6 87 | depth: 5.0e-6 88 | rotation: 45.0 89 | ArrayPattern: 90 | height: 2.0e-6 91 | width: 2.0e-6 92 | depth: 5.0e-6 93 | n_columns: 5 94 | n_rows: 5 95 | pitch_vertical: 5.0e-6 96 | pitch_horizontal: 5.0e-6 97 | rotation: 0.0 98 | passes: 1 99 | scan_direction: "TopToBottom" 100 | MicroExpansion: 101 | height: 15.0e-6 102 | width: 0.5e-6 103 | depth: 1.0e-6 104 | distance: 10.0e-6 105 | WaffleNotch: 106 | vheight: 2.0e-6 107 | vwidth: 0.5e-6 108 | hwidth: 2.0e-6 109 | hheight: 0.5e-6 110 | depth: 1.0e-6 111 | distance: 2.0e-6 112 | Clover: 113 | radius: 10.0e-6 114 | depth: 5.0e-6 115 | TriForce: 116 | height: 10.0e-6 117 | width: 1.0e-6 118 | depth: 5.0e-6 119 | BitmapPattern: 120 | width: 10.0e-6 121 | height: 10.0e-6 122 | depth: 1.0e-6 123 | rotation: 0.0 124 | path: 125 | 126 | Annulus: 127 | thickness: 2.0e-6 128 | radius: 10.0e-6 129 | depth: 1.0e-6 130 | Trapezoid: 131 | inner_width: 10.0e-6 132 | outer_width: 20.0e-6 133 | trench_height: 5.0e-6 134 | depth: 1.0e-6 135 | distance: 1.0e-6 136 | n_rectangles: 10 137 | overlap: 0.0 138 | scan_direction: "TopToBottom" 139 | type: "Trapezoid" 140 | -------------------------------------------------------------------------------- /fibsem/config/tescan_manipulator.yaml: -------------------------------------------------------------------------------- 1 | positions: 2 | calibrated: false 3 | parking: 4 | x: -0.008918395 5 | y: 0.0006548000000000001 6 | z: -0.004848865 7 | standby: 8 | x: 0.0 9 | y: 0.0 10 | z: 0.00545 11 | working: 12 | x: 0.0 13 | y: 0.0 14 | z: 0.00585 15 | # nb: these were for the AMBER simulator, real numbers might be different 16 | 17 | # TODO: refactor this to match Retract, Park, Eucentric terminology -------------------------------------------------------------------------------- /fibsem/constants.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | MICRON_TO_METRE = 1e-6 3 | METRE_TO_MICRON = 1.0 / MICRON_TO_METRE 4 | 5 | MILLIMETRE_TO_METRE = 1e-3 6 | METRE_TO_MILLIMETRE = 1.0 / MILLIMETRE_TO_METRE 7 | 8 | SI_TO_KILO = 1e-3 9 | KILO_TO_SI = 1/ SI_TO_KILO 10 | 11 | SI_TO_MILLI = 1e3 12 | MILLI_TO_SI = 1 / SI_TO_MILLI 13 | 14 | SI_TO_MICRO = 1e6 15 | MICRO_TO_SI = 1 / SI_TO_MICRO 16 | 17 | SI_TO_NANO = 1e9 18 | NANO_TO_SI = 1 / SI_TO_NANO 19 | 20 | SI_TO_PICO = 1e12 21 | PICO_TO_SI = 1 / SI_TO_PICO 22 | 23 | RADIANS_TO_DEGREES = 180.0 / np.pi 24 | 25 | DEGREES_TO_RADIANS = np.pi / 180.0 26 | 27 | TO_PERCENTAGES = 100.0 28 | 29 | FROM_PERCENTAGES = 1.0 / TO_PERCENTAGES 30 | 31 | DEGREE_SYMBOL = "°" -------------------------------------------------------------------------------- /fibsem/db/config.yaml: -------------------------------------------------------------------------------- 1 | name: fibsem.db 2 | 3 | tables: 4 | - projects 5 | - experiments 6 | - users 7 | - samples 8 | - -------------------------------------------------------------------------------- /fibsem/db/v2/app.py: -------------------------------------------------------------------------------- 1 | 2 | from fastapi import FastAPI 3 | from fastapi.responses import HTMLResponse 4 | 5 | from fibsem.db.v2.models import Base, User, Project, Sample, Instrument, Experiment, Lamella, Step, Detection, Interaction 6 | from fibsem.db.v2 import util 7 | from fibsem import config as cfg 8 | 9 | 10 | app = FastAPI() 11 | 12 | @app.get("/") 13 | async def root(): 14 | return {"message": "Hello World"} 15 | 16 | # create a connection to the database 17 | engine = util.create_connection() 18 | 19 | # create a session 20 | session = util.create_session(engine) 21 | 22 | # return all users 23 | @app.get("/users") 24 | async def get_users(): 25 | users = session.query(User).all() 26 | return users 27 | 28 | @app.get("/users/{user_id}") 29 | async def get_user(user_id: int): 30 | user = session.query(User).filter(User.id == user_id).first() 31 | return user 32 | 33 | # return all projects 34 | @app.get("/projects") 35 | async def get_projects(): 36 | projects = session.query(Project).all() 37 | return projects 38 | 39 | @app.get("/projects/{project_id}") 40 | async def get_project(project_id: int): 41 | project = session.query(Project).filter(Project.id == project_id).first() 42 | return project 43 | 44 | 45 | # return all samples 46 | @app.get("/samples") 47 | async def get_samples(): 48 | samples = session.query(Sample).all() 49 | return samples 50 | 51 | @app.get("/samples/{sample_id}") 52 | async def get_sample(sample_id: int): 53 | sample = session.query(Sample).filter(Sample.id == sample_id).first() 54 | return sample 55 | 56 | 57 | @app.get("/experiments") 58 | async def get_experiments(): 59 | experiments = session.query(Experiment).all() 60 | 61 | # drop the .data attribute 62 | for experiment in experiments: 63 | experiment.data = None 64 | 65 | return experiments 66 | 67 | @app.get("/instruments") 68 | async def get_instruments(): 69 | instruments = session.query(Instrument).all() 70 | return instruments 71 | 72 | @app.post("/user") 73 | async def create_user(user: dict): 74 | user = User.from_dict(user) 75 | user = await util.create_user(session, user) 76 | return {"status": "ok", "data": user} 77 | 78 | 79 | @app.post("/project") 80 | async def create_project(project: dict): 81 | project = Project.from_dict(project) 82 | project = await util.create_project(session, project) 83 | return {"status": "ok", "data": project} 84 | 85 | 86 | @app.post("/sample") 87 | async def create_sample(sample: dict): 88 | sample = Sample.from_dict(sample) 89 | sample = await util.create_sample(session, sample) 90 | return {"status": "ok", "data": sample} 91 | 92 | 93 | @app.put("/sample/{sample_id}") 94 | async def update_sample(sample_id: int, sample: dict): 95 | sample = Sample.from_dict(sample) 96 | sample = await util.update_sample(session, sample_id, sample) 97 | return {"status": "ok", "data": sample} 98 | 99 | 100 | # https://fastapi.tiangolo.com/tutorial/sql-databases/#__tabbed_1_2 -------------------------------------------------------------------------------- /fibsem/db/v2/util.py: -------------------------------------------------------------------------------- 1 | 2 | from sqlalchemy import create_engine 3 | from sqlalchemy.orm import sessionmaker 4 | 5 | from fibsem import config as cfg 6 | from fibsem.db.v2.models import Base, User, Project, Sample, Instrument, Experiment, Lamella, Step, Detection, Interaction 7 | 8 | def create_connection(database_path: str = cfg.DATABASE_PATH): 9 | # create a connection to the database 10 | engine = create_engine(f'sqlite:///{database_path}') 11 | 12 | return engine 13 | 14 | def create_database(engine): 15 | # create the database 16 | Base.metadata.create_all(engine) 17 | 18 | def create_session(engine): 19 | # create a session 20 | Session = sessionmaker(bind=engine) 21 | session = Session() 22 | return session 23 | 24 | async def create_user(session, user: User): 25 | session.add(user) 26 | session.commit() 27 | return user 28 | 29 | async def create_project(session, project: Project): 30 | session.add(project) 31 | session.commit() 32 | return project 33 | 34 | async def create_sample(session, sample: Sample): 35 | session.add(sample) 36 | session.commit() 37 | return sample 38 | 39 | async def create_instrument(session, instrument: Instrument): 40 | session.add(instrument) 41 | session.commit() 42 | return instrument 43 | 44 | 45 | async def create_experiment(session, experiment: Experiment): 46 | session.add(experiment) 47 | session.commit() 48 | return experiment 49 | 50 | async def create_lamella(session, lamella: Lamella): 51 | session.add(lamella) 52 | session.commit() 53 | return lamella 54 | 55 | def create_step(session, step: Step): 56 | session.add(step) 57 | session.commit() 58 | return step 59 | 60 | def create_detection(session, detection: Detection): 61 | session.add(detection) 62 | session.commit() 63 | return detection 64 | 65 | def create_interaction(session, interaction: Interaction): 66 | session.add(interaction) 67 | session.commit() 68 | return interaction 69 | 70 | 71 | def add_steps(session, steps: list): 72 | session.add_all(steps) 73 | session.commit() 74 | return steps 75 | 76 | def add_detections(session, detections: list): 77 | session.add_all(detections) 78 | session.commit() 79 | return detections 80 | 81 | def add_interactions(session, interactions: list): 82 | session.add_all(interactions) 83 | session.commit() 84 | return interactions 85 | 86 | 87 | 88 | def get_user(session, username: str): 89 | user = session.query(User).filter(User.username == username).first() 90 | session.close() 91 | return user 92 | 93 | def get_project(session, name: str): 94 | project = session.query(Project).filter(Project.name == name).first() 95 | session.close() 96 | return project 97 | 98 | def get_sample(session, name: str): 99 | sample = session.query(Sample).filter(Sample.name == name).first() 100 | session.close() 101 | return sample 102 | 103 | def get_instrument(session, name: str): 104 | instrument = session.query(Instrument).filter(Instrument.name == name).first() 105 | session.close() 106 | return instrument 107 | 108 | def get_projects(session): 109 | projects = session.query(Project).all() 110 | session.close() 111 | return projects 112 | 113 | def get_samples(session): 114 | samples = session.query(Sample).all() 115 | session.close() 116 | return samples 117 | 118 | def get_instruments(session): 119 | instruments = session.query(Instrument).all() 120 | session.close() 121 | return instruments 122 | 123 | def get_experiments(session): 124 | experiments = session.query(Experiment).all() 125 | session.close() 126 | return experiments 127 | 128 | def get_users(session): 129 | """Query the User table""" 130 | users = session.query(User).all() 131 | session.close() 132 | return users 133 | -------------------------------------------------------------------------------- /fibsem/db/v3/app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | 3 | from fibsem import config as cfg 4 | from fibsem.db.v3.util import get_session, create_connection 5 | 6 | import pandas as pd 7 | st.set_page_config(page_title='Fibsem DB v3', page_icon=":microscope:", layout="wide") 8 | 9 | st.title('Fibsem DB v3') 10 | 11 | 12 | # connectto the database 13 | conn = st.connection("fibsem.db", type="sql", url=f'sqlite:///{cfg.DATABASE_PATH}', ttl=0) 14 | 15 | 16 | 17 | @st.cache_data(show_spinner=True) 18 | def get_user_data(): 19 | return conn.query("SELECT * FROM user") 20 | 21 | 22 | # force refresh 23 | if st.button('Refresh'): 24 | get_user_data.clear() 25 | 26 | df = None 27 | 28 | df = get_user_data() 29 | st.data_editor(df) 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | # sidebar 40 | st.sidebar.title('Navigation') 41 | page = st.sidebar.radio('Go to', ('Home', 'Users', 'Samples', 'Instruments', 'Configurations')) 42 | 43 | @st.cache_resource 44 | def get_user_model(): 45 | from fibsem.db.v3.models import User 46 | return User 47 | 48 | User = get_user_model() 49 | 50 | # create a form for adding users (using sqlmodel) 51 | if page == 'Users': 52 | st.header('Users') 53 | with st.form(key='add_user'): 54 | username = st.text_input('Username') 55 | name = st.text_input('Name') 56 | email = st.text_input('Email') 57 | password = st.text_input('Password', type='password') 58 | role = st.selectbox('Role', ['admin', 'user']) 59 | submit = st.form_submit_button('Add User') 60 | 61 | 62 | if submit: 63 | user = User(username=username, name=name, email=email, password=password, 64 | role=role) 65 | engine = create_connection(echo=True) 66 | with get_session(engine) as session: 67 | session.add(user) 68 | session.commit() 69 | st.toast(f'User {username} added successfully') 70 | 71 | # rerun 72 | st.rerun() 73 | 74 | # add a form to delete users 75 | with st.form(key='delete_user'): 76 | user_id = st.text_input('User ID') 77 | submit = st.form_submit_button('Delete User') 78 | 79 | if submit: 80 | engine = create_connection(echo=True) 81 | with get_session(engine) as session: 82 | user = session.get(User, user_id) 83 | session.delete(user) 84 | session.commit() 85 | st.toast(f'User {user_id} deleted successfully') 86 | 87 | # rerun 88 | st.rerun() 89 | -------------------------------------------------------------------------------- /fibsem/db/v3/notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## FIBSEM Database (SQLModel Version)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%load_ext autoreload\n", 17 | "%autoreload 2\n", 18 | "\n", 19 | "from fibsem.db.v3 import models\n", 20 | "\n", 21 | "user = models.User(username='patrick', \n", 22 | " name='Patrick Cleeve',\n", 23 | " email = 'patrick@openfibsem.org',\n", 24 | " password = 'password',\n", 25 | " role = 'admin')\n", 26 | "\n", 27 | "print(user.username)\n", 28 | "\n", 29 | "\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "from sqlmodel import Session, SQLModel, create_engine, select\n", 39 | "\n", 40 | "from fibsem import config as cfg\n", 41 | "\n", 42 | "def create_connection(database_path: str = cfg.DATABASE_PATH, echo: bool = False):\n", 43 | " # create a connection to the database\n", 44 | " engine = create_engine(f'sqlite:///{database_path}', echo=echo)\n", 45 | "\n", 46 | " return engine\n", 47 | "\n", 48 | "def create_database(engine):\n", 49 | " # create the database\n", 50 | " SQLModel.metadata.create_all(engine)\n", 51 | "\n", 52 | "engine = create_connection(echo=True)\n", 53 | "create_database(engine)\n", 54 | "\n", 55 | "# with Session(engine) as session:\n", 56 | " # session.add(user)\n", 57 | " # session.commit()" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "with Session(engine) as session:\n", 67 | " statement = select(models.User)\n", 68 | " users = session.exec(statement)\n", 69 | " for user in users:\n", 70 | " print(user)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [] 79 | } 80 | ], 81 | "metadata": { 82 | "kernelspec": { 83 | "display_name": "fibsem", 84 | "language": "python", 85 | "name": "python3" 86 | }, 87 | "language_info": { 88 | "codemirror_mode": { 89 | "name": "ipython", 90 | "version": 3 91 | }, 92 | "file_extension": ".py", 93 | "mimetype": "text/x-python", 94 | "name": "python", 95 | "nbconvert_exporter": "python", 96 | "pygments_lexer": "ipython3", 97 | "version": "3.9.18" 98 | } 99 | }, 100 | "nbformat": 4, 101 | "nbformat_minor": 2 102 | } 103 | -------------------------------------------------------------------------------- /fibsem/db/v3/util.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import Session, SQLModel, create_engine, select 2 | from fibsem import config as cfg 3 | 4 | def create_connection(database_path: str = cfg.DATABASE_PATH, echo: bool = False): 5 | # create a connection to the database 6 | engine = create_engine(f'sqlite:///{database_path}', echo=echo) 7 | 8 | return engine 9 | 10 | def create_database(engine): 11 | # create the database 12 | SQLModel.metadata.create_all(engine) 13 | 14 | 15 | def get_session(engine): 16 | # create a session 17 | return Session(engine) 18 | 19 | 20 | def get_all(model, session): 21 | # get all the records from the model 22 | return session.exec(select(model)).all() 23 | 24 | 25 | def get_by_id(model, session, id): 26 | return session.get(model, id) 27 | 28 | 29 | -------------------------------------------------------------------------------- /fibsem/detection/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/__init__.py -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-liftout-hf-mega.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/liftout/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/liftout/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/hf-mega-eval/liftout/keypoint-eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-liftout-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | - checkpoint: patrickcleeve/segformer-b1-autolamella-mega-1 9 | 10 | # plot 11 | thresholds: 12 | - 250 13 | - 100 14 | - 50 15 | - 25 16 | - 10 17 | 18 | # options 19 | run_eval: True 20 | plot_eval: True 21 | 22 | show_det_plot: False 23 | save_det_plot: True 24 | show_eval_plot: False 25 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-liftout.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/liftout/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/liftout/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/liftout-v1/keypoint-eval-v2" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-liftout-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | 9 | # plot 10 | thresholds: 11 | - 250 12 | - 100 13 | - 50 14 | - 25 15 | - 10 16 | 17 | # options 18 | run_eval: True 19 | plot_eval: True 20 | 21 | show_det_plot: False 22 | save_det_plot: True 23 | show_eval_plot: False 24 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-serial-liftout-hf-mega.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/hf-mega-eval/sl/keypoint-eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-serial-liftout-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | - checkpoint: patrickcleeve/segformer-b1-autolamella-mega-1 9 | 10 | 11 | # plot 12 | thresholds: 13 | - 250 14 | - 100 15 | - 50 16 | - 25 17 | - 10 18 | 19 | # options 20 | run_eval: True 21 | plot_eval: True 22 | 23 | show_det_plot: False 24 | save_det_plot: True 25 | show_eval_plot: False 26 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-serial-liftout.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/serial-liftout-v1/keypoint-eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-serial-liftout-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | 9 | 10 | # plot 11 | thresholds: 12 | - 250 13 | - 100 14 | - 50 15 | - 25 16 | - 10 17 | 18 | # options 19 | run_eval: True 20 | plot_eval: True 21 | 22 | show_det_plot: False 23 | save_det_plot: True 24 | show_eval_plot: False 25 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-waffle-v2-hf-mega.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/waffle/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/waffle/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/hf-mega-eval/waffle/keypoint-eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-waffle-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | - checkpoint: patrickcleeve/segformer-b1-autolamella-mega-1 9 | 10 | 11 | # plot 12 | thresholds: 13 | - 250 14 | - 100 15 | - 50 16 | - 25 17 | - 10 18 | 19 | # options 20 | run_eval: True 21 | plot_eval: True 22 | 23 | show_det_plot: False 24 | save_det_plot: True 25 | show_eval_plot: False 26 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-waffle-v2.yaml: -------------------------------------------------------------------------------- 1 | data_path: "/home/patrick/github/data/autolamella-paper/model-development/train/waffle/test/keypoints.csv" # test data csv 2 | images_path: "/home/patrick/github/data/autolamella-paper/model-development/train/waffle/test" # test data image directory 3 | save_path: "/home/patrick/github/data/autolamella-paper/model-development/eval/waffle-v1/keypoint-eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: autolamella-waffle-20240107.pt 7 | - checkpoint: autolamella-mega-20240107.pt 8 | 9 | 10 | # plot 11 | thresholds: 12 | - 250 13 | - 100 14 | - 50 15 | - 25 16 | - 10 17 | 18 | # options 19 | run_eval: True 20 | plot_eval: True 21 | 22 | show_det_plot: False 23 | save_det_plot: True 24 | show_eval_plot: False 25 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autolamella-waffle.yml: -------------------------------------------------------------------------------- 1 | data_path: "eval/autolamella-waffle/data.csv" # test data csv 2 | images_path: "eval/autolamella-waffle/images" # test images 3 | save_path: "eval/autolamella-waffle/eval" # save path for evaluation results 4 | 5 | checkpoints: 6 | - checkpoint: "openfibsem-baseline-34.pt" 7 | encoder: "resnet34" 8 | nc: 3 9 | - checkpoint: "autolamella-02-34.pt" 10 | encoder: "resnet34" 11 | nc: 3 12 | - checkpoint: "autolamella-04-34.pt" 13 | encoder: "resnet34" 14 | nc: 3 15 | - checkpoint: "autolamella-05-34.pt" 16 | encoder: "resnet34" 17 | nc: 3 18 | 19 | # plot 20 | thresholds: 21 | - 250 22 | - 100 23 | - 50 24 | - 25 25 | - 10 26 | 27 | # options 28 | run_eval: True 29 | plot_eval: True 30 | 31 | show_det_plot: False 32 | save_det_plot: True 33 | show_eval_plot: False 34 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/config-autoliftout-dm-embryo.yml: -------------------------------------------------------------------------------- 1 | data_path: "eval/dm-embryo/data.csv" # test data csv 2 | images_path: "eval/dm-embryo/images" # test data image directory 3 | save_path: "eval/dm-embryo/eval" # save path for evaluation results 4 | 5 | checkpoints: # list of checkpoints to evaluate 6 | - checkpoint: "openfibsem-01-18.pt" 7 | encoder: "resnet18" 8 | nc: 3 9 | - checkpoint: "openfibsem-02-18.pt" 10 | encoder: "resnet18" 11 | nc: 3 12 | - checkpoint: "openfibsem-03-18.pt" 13 | encoder: "resnet18" 14 | nc: 3 15 | - checkpoint: "openfibsem-baseline-34.pt" 16 | encoder: "resnet34" 17 | nc: 3 18 | 19 | # plot 20 | thresholds: 21 | - 250 22 | - 100 23 | - 50 24 | - 25 25 | - 10 26 | 27 | # options 28 | run_eval: False 29 | plot_eval: True 30 | 31 | show_det_plot: False 32 | save_det_plot: True 33 | show_eval_plot: False 34 | save_eval_plot: True -------------------------------------------------------------------------------- /fibsem/detection/run_evaluation.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | import pandas as pd 5 | import yaml 6 | 7 | from fibsem.detection import evaluation 8 | 9 | def main(config: dict): 10 | 11 | os.makedirs(config["save_path"], exist_ok=True) 12 | 13 | if config["run_eval"]: 14 | df_eval = evaluation.run_evaluation_v2(path=config["data_path"], 15 | image_path = config["images_path"], 16 | checkpoints=config["checkpoints"], 17 | plot=config["show_det_plot"], 18 | save=config["save_det_plot"], 19 | save_path=config["save_path"]) 20 | else: 21 | df_eval = pd.read_csv(os.path.join(config["save_path"], "eval.csv")) 22 | 23 | if config["plot_eval"]: 24 | category_orders = {"checkpoint": df_eval["checkpoint"].unique().tolist(), 25 | "feature": sorted(df_eval["feature"].unique().tolist())} 26 | evaluation.plot_evaluation_data(df=df_eval, 27 | category_orders=category_orders, 28 | thresholds=config["thresholds"], 29 | show=config["show_eval_plot"], 30 | save=config["save_eval_plot"], 31 | save_path=config["save_path"]) 32 | 33 | 34 | 35 | if __name__ == "__main__": 36 | # command line arguments 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument( 39 | "--config", 40 | help="the directory containing the config file to use", 41 | dest="config", 42 | action="store", 43 | default=os.path.join(os.path.join(os.path.dirname(__file__), "config.yml")), 44 | ) 45 | args = parser.parse_args() 46 | config_dir = args.config 47 | 48 | # NOTE: Setup your config.yml file 49 | with open(config_dir, "r") as f: 50 | config = yaml.safe_load(f) 51 | 52 | main(config=config) -------------------------------------------------------------------------------- /fibsem/detection/test_image.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_image.tif -------------------------------------------------------------------------------- /fibsem/detection/test_image_2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_image_2.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_00.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_00.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_01.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_01.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_02.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_02.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_03.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_03.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_04.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_04.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_05.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_05.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_06.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_06.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/serial/serial_liftout_mask_07.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/serial/serial_liftout_mask_07.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/test_needle_mask.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/test_needle_mask.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/test_needle_mask2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/test_needle_mask2.tif -------------------------------------------------------------------------------- /fibsem/detection/test_images/test_needle_mask3.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/detection/test_images/test_needle_mask3.tif -------------------------------------------------------------------------------- /fibsem/imaging/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/imaging/.gitkeep -------------------------------------------------------------------------------- /fibsem/imaging/__init__.py: -------------------------------------------------------------------------------- 1 | from fibsem.imaging.spot import * 2 | from fibsem.imaging.autogamma import * 3 | from fibsem.imaging.masks import * 4 | from fibsem.imaging.tiled import * 5 | from fibsem.imaging.utils import * -------------------------------------------------------------------------------- /fibsem/imaging/spot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from typing import List, Optional 4 | 5 | from fibsem.microscope import FibsemMicroscope 6 | from fibsem.structures import BeamType, Point 7 | 8 | SLEEP_TIME = 1 9 | 10 | def run_spot_burn(microscope: FibsemMicroscope, 11 | coordinates: List[Point], 12 | exposure_time: float, 13 | milling_current: float, 14 | beam_type: BeamType = BeamType.ION, 15 | parent_ui: Optional['FibsemSpotBurnWidget'] = None)-> None: 16 | """Run a spot burner job on the microscope. Exposes the specified coordinates for a the specified 17 | time at the specified current. 18 | Args: 19 | microscope: The microscope object. 20 | coordinates: List of points to burn. (0 - 1 in image coordinates) 21 | exposure_time: Time to expose each point in seconds. 22 | milling_current: Current to use for the spot. 23 | beam_type: The type of beam to use. (Default: BeamType.ION) 24 | parent_ui: The parent UI object to emit progress signals. (Default: None) 25 | Returns: 26 | None 27 | """ 28 | # - TODO: support cancelling the task 29 | # - QUERY: do we need to set the full frame scanning mode each time, or only at the end? 30 | 31 | total_estimated_time = len(coordinates) * exposure_time 32 | total_remaining_time = total_estimated_time 33 | 34 | # emit initial progress signal 35 | if parent_ui is not None: 36 | parent_ui.spot_burn_progress_signal.emit( 37 | { 38 | "current_point": 0, 39 | "total_points": len(coordinates), 40 | "remaining_time": exposure_time, 41 | "total_remaining_time": total_remaining_time, 42 | "total_estimated_time": total_estimated_time, 43 | } 44 | ) 45 | 46 | # set the beam current to the milling current 47 | imaging_current = microscope.get_beam_current(beam_type=beam_type) 48 | microscope.set_beam_current(current=milling_current, beam_type=beam_type) 49 | 50 | for i, pt in enumerate(coordinates, 1): 51 | logging.info(f'burning spot {i}: {pt}, exposure time: {exposure_time}, milling current: {milling_current}') 52 | 53 | microscope.blank(beam_type=beam_type) 54 | microscope.set_spot_scanning_mode(point=pt, beam_type=beam_type) 55 | microscope.unblank(beam_type=beam_type) 56 | 57 | # countdown for the exposure time, emit progress signal 58 | remaining_time = exposure_time 59 | while remaining_time > 0: 60 | time.sleep(SLEEP_TIME) 61 | remaining_time -= SLEEP_TIME 62 | total_remaining_time -= SLEEP_TIME 63 | if parent_ui is not None: 64 | parent_ui.spot_burn_progress_signal.emit( 65 | { 66 | "current_point": i, 67 | "total_points": len(coordinates), 68 | "remaining_time": remaining_time, 69 | "total_remaining_time": total_remaining_time, 70 | "total_estimated_time": total_estimated_time, 71 | } 72 | ) 73 | microscope.set_full_frame_scanning_mode(beam_type=beam_type) 74 | 75 | # emit finished signal 76 | if parent_ui is not None: 77 | parent_ui.spot_burn_progress_signal.emit({"finished": True}) 78 | 79 | microscope.set_beam_current(current=imaging_current, beam_type=beam_type) 80 | return 81 | -------------------------------------------------------------------------------- /fibsem/imaging/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from fibsem.structures import Point, FibsemImage 3 | from PIL import Image 4 | 5 | 6 | def create_distance_map_px(w: int, h: int) -> np.ndarray: 7 | x = np.arange(0, w) 8 | y = np.arange(0, h) 9 | 10 | X, Y = np.meshgrid(x, y) 11 | distance = np.sqrt(((w / 2) - X) ** 2 + ((h / 2) - Y) ** 2) 12 | 13 | return distance 14 | 15 | 16 | def measure_brightness(img: FibsemImage) -> float: 17 | 18 | return np.mean(img.data) 19 | 20 | 21 | def rotate_image(image: FibsemImage): 22 | """Rotate the AdornedImage 180 degrees.""" 23 | data = np.rot90(np.rot90(np.copy(image.data))) 24 | reference = FibsemImage(data=data, metadata=image.metadata) 25 | return reference 26 | 27 | 28 | def normalise_image(img: FibsemImage) -> np.ndarray: 29 | """Normalise the image""" 30 | return (img.data - np.mean(img.data)) / np.std(img.data) 31 | 32 | 33 | def cosine_stretch(img: FibsemImage, tilt_degrees: float): 34 | """Apply a cosine stretch to an image based on the relative tilt. 35 | 36 | This is required when aligning images with different tilts to ensure features are the same size. 37 | 38 | Args: 39 | img (AdornedImage): _description_ 40 | tilt_degrees (float): _description_ 41 | 42 | Returns: 43 | _type_: _description_ 44 | """ 45 | # note: do smaller version for negative tilt?? 46 | 47 | tilt = np.deg2rad(tilt_degrees) 48 | 49 | shape = int(img.data.shape[0] / np.cos(tilt)), int(img.data.shape[1] / np.cos(tilt)) 50 | 51 | # cosine stretch 52 | # larger 53 | resized_img = np.asarray( 54 | Image.fromarray(img.data).resize(size=(shape[1], shape[0])) 55 | ) 56 | 57 | # crop centre out? 58 | c = Point(resized_img.shape[1] // 2, resized_img.shape[0] // 2) 59 | dy, dx = img.data.shape[0] // 2, img.data.shape[1] // 2 60 | scaled_img = resized_img[c.y - dy : c.y + dy, c.x - dx : c.x + dx] 61 | 62 | return FibsemImage(data=scaled_img, metadata=img.metadata) 63 | 64 | 65 | def apply_image_mask(img: FibsemImage, mask: np.ndarray) -> np.ndarray: 66 | 67 | return normalise_image(img) * mask 68 | -------------------------------------------------------------------------------- /fibsem/milling/__init__.py: -------------------------------------------------------------------------------- 1 | from fibsem.milling.base import ( 2 | FibsemMillingStage, 3 | MillingStrategy, 4 | MillingAlignment, 5 | get_milling_stages, 6 | get_protocol_from_stages, 7 | get_strategy, 8 | estimate_milling_time, 9 | estimate_total_milling_time, 10 | ) 11 | from fibsem.milling.core import ( 12 | draw_pattern, 13 | draw_patterns, 14 | finish_milling, 15 | mill_stages, 16 | run_milling, 17 | setup_milling, 18 | ) 19 | from fibsem.milling.patterning.plotting import draw_milling_patterns as plot_milling_patterns 20 | -------------------------------------------------------------------------------- /fibsem/milling/config.py: -------------------------------------------------------------------------------- 1 | # sputtering rates, from microscope application files 2 | MILLING_SPUTTER_RATE = { 3 | 20e-12: 6.85e-3, # 30kv 4 | 0.2e-9: 6.578e-2, # 30kv 5 | 0.74e-9: 3.349e-1, # 30kv 6 | 0.89e-9: 3.920e-1, # 20kv 7 | 2.0e-9: 9.549e-1, # 30kv 8 | 2.4e-9: 1.309, # 20kv 9 | 6.2e-9: 2.907, # 20kv 10 | 7.6e-9: 3.041, # 30kv 11 | 28.0e-9: 1.18e1, # 30 kv 12 | } 13 | -------------------------------------------------------------------------------- /fibsem/milling/strategy/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import typing 3 | from functools import cache 4 | 5 | from fibsem.milling.base import MillingStrategy 6 | from fibsem.milling.strategy.standard import StandardMillingStrategy 7 | from fibsem.milling.strategy.overtilt import OvertiltTrenchMillingStrategy 8 | 9 | 10 | DEFAULT_STRATEGY = StandardMillingStrategy 11 | DEFAULT_STRATEGY_NAME = DEFAULT_STRATEGY.name 12 | BUILTIN_STRATEGIES: typing.Dict[str, type[MillingStrategy]] = { 13 | StandardMillingStrategy.name: StandardMillingStrategy, 14 | OvertiltTrenchMillingStrategy.name: OvertiltTrenchMillingStrategy, 15 | } 16 | REGISTERED_STRATEGIES: typing.Dict[str, type[MillingStrategy]] = {} 17 | 18 | 19 | def get_strategies() -> typing.Dict[str, type[MillingStrategy]]: 20 | # This order means that builtins > registered > plugins if there are any name clashes 21 | return {**_get_plugin_strategies(), **REGISTERED_STRATEGIES, **BUILTIN_STRATEGIES} 22 | 23 | 24 | def get_strategy_names() -> typing.List[str]: 25 | return list(get_strategies().keys()) 26 | 27 | 28 | def register_strategy(strategy_cls: type[MillingStrategy]) -> None: 29 | global REGISTERED_STRATEGIES 30 | REGISTERED_STRATEGIES[strategy_cls.name] = strategy_cls 31 | 32 | 33 | @cache 34 | def _get_plugin_strategies() -> typing.Dict[str, type[MillingStrategy]]: 35 | """ 36 | Import new strategies and append them to the list here 37 | 38 | The plugin logic is based on: 39 | https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata 40 | """ 41 | import sys 42 | 43 | if sys.version_info < (3, 10): 44 | from importlib_metadata import entry_points 45 | else: 46 | from importlib.metadata import entry_points 47 | 48 | strategies: typing.Dict[str, type[MillingStrategy]] = {} 49 | for strategy_entry_point in entry_points(group="fibsem.strategies"): 50 | try: 51 | strategy = strategy_entry_point.load() 52 | if not issubclass(strategy, MillingStrategy): 53 | raise TypeError( 54 | f"'{strategy_entry_point.value}' is not a subclass of MillingStrategy" 55 | ) 56 | logging.info("Loaded strategy '%s'", strategy.name) 57 | strategies[strategy.name] = strategy 58 | except TypeError as e: 59 | logging.warning("Invalid strategy found: %s", str(e)) 60 | except Exception: 61 | logging.error( 62 | "Unexpected error raised while attempting to import strategy from '%s'", 63 | strategy_entry_point.value, 64 | exc_info=True, 65 | ) 66 | return strategies 67 | -------------------------------------------------------------------------------- /fibsem/milling/strategy/standard.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | 4 | from fibsem.microscope import FibsemMicroscope 5 | from fibsem.milling import (draw_patterns, run_milling, 6 | setup_milling) 7 | from fibsem.milling.base import (FibsemMillingStage, MillingStrategy, 8 | MillingStrategyConfig) 9 | 10 | import time 11 | 12 | @dataclass 13 | class StandardMillingConfig(MillingStrategyConfig): 14 | """Configuration for standard milling strategy""" 15 | pass 16 | 17 | 18 | @dataclass 19 | class StandardMillingStrategy(MillingStrategy): 20 | """Basic milling strategy that mills continuously until completion""" 21 | name: str = "Standard" 22 | fullname: str = "Standard Milling" 23 | 24 | def __init__(self, config: StandardMillingConfig = None): 25 | self.config = config or StandardMillingConfig() 26 | 27 | def to_dict(self): 28 | return {"name": self.name, "config": self.config.to_dict()} 29 | 30 | @staticmethod 31 | def from_dict(d: dict) -> "StandardMillingStrategy": 32 | config=StandardMillingConfig.from_dict(d.get("config", {})) 33 | return StandardMillingStrategy(config=config) 34 | 35 | def run( 36 | self, 37 | microscope: FibsemMicroscope, 38 | stage: FibsemMillingStage, 39 | asynch: bool = False, 40 | parent_ui = None, 41 | ) -> None: 42 | logging.info(f"Running {self.name} Milling Strategy for {stage.name}") 43 | setup_milling(microscope, milling_stage=stage, ref_image=stage.ref_image) 44 | 45 | draw_patterns(microscope, stage.pattern.define()) 46 | 47 | estimated_time = microscope.estimate_milling_time() 48 | logging.info(f"Estimated time for {stage.name}: {estimated_time:.2f} seconds") 49 | 50 | if parent_ui: 51 | parent_ui.milling_progress_signal.emit({"msg": f"Running {stage.name}...", 52 | "progress": 53 | {"started": True, 54 | "start_time": time.time(), 55 | "estimated_time": estimated_time, 56 | "name": stage.name} 57 | }) 58 | 59 | run_milling( 60 | microscope=microscope, 61 | milling_current=stage.milling.milling_current, 62 | milling_voltage=stage.milling.milling_voltage, 63 | asynch=asynch, 64 | ) 65 | -------------------------------------------------------------------------------- /fibsem/movement.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | 5 | def rotation_angle_is_larger(angle1: float, angle2: float, atol: float = 90) -> bool: 6 | """Check the rotation angles are large 7 | 8 | Args: 9 | angle1 (float): angle1 (radians) 10 | angle2 (float): angle2 (radians) 11 | atol : tolerance (degrees) 12 | 13 | Returns: 14 | bool: rotation angle is larger than atol 15 | """ 16 | 17 | return angle_difference(angle1, angle2) > (np.deg2rad(atol)) 18 | 19 | 20 | def rotation_angle_is_smaller(angle1: float, angle2: float, atol: float = 5) -> bool: 21 | """Check the rotation angles are large 22 | 23 | Args: 24 | angle1 (float): angle1 (radians) 25 | angle2 (float): angle2 (radians) 26 | atol : tolerance (degrees) 27 | 28 | Returns: 29 | bool: rotation angle is smaller than atol 30 | """ 31 | 32 | return angle_difference(angle1, angle2) < (np.deg2rad(atol)) 33 | 34 | 35 | def angle_difference(angle1: float, angle2: float) -> float: 36 | """Return the difference between two angles, accounting for greater than 360, less than 0 angles 37 | 38 | Args: 39 | angle1 (float): angle1 (radians) 40 | angle2 (float): angle2 (radians) 41 | 42 | Returns: 43 | float: _description_ 44 | """ 45 | angle1 %= 2 * np.pi 46 | angle2 %= 2 * np.pi 47 | 48 | large_angle = np.max([angle1, angle2]) 49 | small_angle = np.min([angle1, angle2]) 50 | 51 | return min((large_angle - small_angle), ((2 * np.pi + small_angle - large_angle))) 52 | -------------------------------------------------------------------------------- /fibsem/segmentation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/__init__.py -------------------------------------------------------------------------------- /fibsem/segmentation/adaptive_model.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | import torch 4 | from adaptive_polish.dl_segmentation import sem_lamella_segmentor as sgm 5 | from fibsem.segmentation.utils import decode_segmap_v2, download_checkpoint 6 | 7 | 8 | class AdaptiveSegmentationModel: 9 | def __init__( 10 | self, 11 | checkpoint: str = None, 12 | mode: str = "eval", 13 | num_classes: int = 4, 14 | ) -> None: 15 | super().__init__() 16 | self.checkpoint: str = checkpoint 17 | self.mode = mode 18 | self.num_classes = num_classes 19 | self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 20 | self.model = self.load_model_v2(checkpoint=checkpoint) 21 | self.device = self.model._device 22 | 23 | def load_model_v2(self, checkpoint: str): 24 | model = sgm.cSEMLamellaSegmentor(model_path=checkpoint) 25 | logging.debug(f"Loaded {self.__class__}, model from {checkpoint}") 26 | return model 27 | 28 | def pre_process(self, img: np.ndarray) -> torch.Tensor: 29 | """Pre-process the image for inference""" 30 | return img 31 | 32 | def inference(self, img: np.ndarray, rgb: bool = True) -> np.ndarray: 33 | """Run model inference on the input image""" 34 | 35 | # NOTE: currently all pre-processing is done in adaptive_polish.sem_lamella_segmentor.cSEMLamellaSegmentor 36 | # TODO: migrate pre-processing to this class 37 | masks = self.model.get_prediction(img) 38 | # decode to rgb 39 | if rgb: 40 | masks = self.postprocess(masks) 41 | return masks 42 | 43 | def postprocess(self, masks: np.ndarray) -> np.ndarray: 44 | return decode_segmap_v2(masks) 45 | -------------------------------------------------------------------------------- /fibsem/segmentation/config-autolamella-mega-v4-xl.yml: -------------------------------------------------------------------------------- 1 | data_paths: [ 2 | /home/patrick/github/data/autolamella/train, 3 | /home/patrick/github/data/liftout/training/train-relabelled/images, 4 | /home/patrick/github/data/liftout/train-new/train, 5 | /home/patrick/github/data/liftout/train-waffle/train, 6 | /home/patrick/github/data/liftout/serial-liftout/data/train 7 | ] 8 | label_paths: [ 9 | /home/patrick/github/data/autolamella/train/labels, 10 | /home/patrick/github/data/liftout/training/train-relabelled/labels, 11 | /home/patrick/github/data/liftout/train-new/train/labels, 12 | /home/patrick/github/data/liftout/train-waffle/train/labels, 13 | /home/patrick/github/data/liftout/serial-liftout/data/train/labels 14 | ] 15 | save_path: /home/patrick/github/fibsem/fibsem/segmentation/models/autolamella/mega/base/v4/xl 16 | checkpoint: null 17 | encoder: "resnet50" 18 | epochs: 50 19 | batch_size: 2 20 | num_classes: 5 21 | lr: 3.0e-4 22 | wandb: true 23 | wandb_project: "autolamella-mega" 24 | wandb_entity: "demarcolab" 25 | # re-labelled data, fixed numerical bug in pre-processing -------------------------------------------------------------------------------- /fibsem/segmentation/config-autolamella-mega-v4.yml: -------------------------------------------------------------------------------- 1 | data_paths: [ 2 | /home/patrick/github/data/autolamella/train, 3 | /home/patrick/github/data/liftout/training/train-relabelled/images, 4 | /home/patrick/github/data/liftout/train-new/train, 5 | /home/patrick/github/data/liftout/train-waffle/train, 6 | /home/patrick/github/data/liftout/serial-liftout/data/train 7 | ] 8 | label_paths: [ 9 | /home/patrick/github/data/autolamella/train/labels, 10 | /home/patrick/github/data/liftout/training/train-relabelled/labels, 11 | /home/patrick/github/data/liftout/train-new/train/labels, 12 | /home/patrick/github/data/liftout/train-waffle/train/labels, 13 | /home/patrick/github/data/liftout/serial-liftout/data/train/labels 14 | ] 15 | save_path: /home/patrick/github/fibsem/fibsem/segmentation/models/autolamella/mega/base/v4 16 | checkpoint: null 17 | encoder: "resnet34" 18 | epochs: 50 19 | batch_size: 4 20 | num_classes: 5 21 | lr: 3.0e-4 22 | wandb: true 23 | wandb_project: "autolamella-mega" 24 | wandb_entity: "demarcolab" 25 | # re-labelled data, fixed numerical bug in pre-processing -------------------------------------------------------------------------------- /fibsem/segmentation/config-autolamella-mega-v5.yml: -------------------------------------------------------------------------------- 1 | data_paths: [ 2 | /home/patrick/github/data/autolamella-paper/model-development/train/autolamella-waffle/train, 3 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-relabelled/images, 4 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-new/train, 5 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-waffle/train, 6 | /home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/train 7 | ] 8 | label_paths: [ 9 | /home/patrick/github/data/autolamella-paper/model-development/train/autolamella-waffle/train/labels, 10 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-relabelled/labels, 11 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-new/train/labels, 12 | /home/patrick/github/data/autolamella-paper/model-development/train/autoliftout-04/train-waffle/train/labels, 13 | /home/patrick/github/data/autolamella-paper/model-development/train/serial-liftout/train/labels 14 | ] 15 | save_path: /home/patrick/github/fibsem/fibsem/segmentation/models/autolamella/mega/base/v5 16 | checkpoint: null 17 | encoder: "resnet34" 18 | epochs: 50 19 | split: 0.1 20 | batch_size: 2 21 | num_classes: 6 22 | lr: 3.0e-4 23 | train_log_freq: 32 24 | val_log_freq: 32 25 | wandb: true 26 | wandb_project: "autolamella-mega" 27 | wandb_entity: "openfibsem" 28 | 29 | 30 | -------------------------------------------------------------------------------- /fibsem/segmentation/config-autolamella-waffle4.yml: -------------------------------------------------------------------------------- 1 | data_path: /home/patrick/github/data/autolamella/train 2 | label_path: /home/patrick/github/data/autolamella/train/labels 3 | save_path: /home/patrick/github/fibsem/fibsem/segmentation/models/autolamella/v4 4 | checkpoint: null 5 | encoder: "resnet34" 6 | epochs: 20 7 | batch_size: 4 8 | num_classes: 3 9 | lr: 3.0e-4 10 | wandb: true 11 | wandb_project: "autolamella" 12 | wandb_entity: "demarcolab" 13 | -------------------------------------------------------------------------------- /fibsem/segmentation/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import List 3 | CLASS_CONFIG_PATH = os.path.join(os.path.dirname(__file__), "segmentation_config.yaml") 4 | 5 | import yaml 6 | with open(CLASS_CONFIG_PATH) as f: 7 | CLASS_CONFIG = yaml.load(f, Loader=yaml.FullLoader) 8 | 9 | CLASS_COLORS = CLASS_CONFIG["CLASS_COLORS"] 10 | CLASS_LABELS = CLASS_CONFIG["CLASS_LABELS"] 11 | 12 | import matplotlib.colors as mcolors 13 | def convert_color_names_to_rgb(color_names: List[str]): 14 | if isinstance(color_names, dict): 15 | color_names = color_names.values() 16 | rgb_colors = [mcolors.to_rgb(color) for color in color_names] 17 | # Convert to 0-255 scale 18 | rgb_colors = [(int(r*255), int(g*255), int(b*255)) for r, g, b in rgb_colors] 19 | return rgb_colors 20 | 21 | # map color names to rgb values 22 | CLASS_COLORS_RGB = convert_color_names_to_rgb(CLASS_COLORS.values()) 23 | 24 | 25 | def get_colormap(): 26 | return CLASS_COLORS_RGB 27 | 28 | def get_class_labels(): 29 | return CLASS_LABELS 30 | 31 | CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.yml") -------------------------------------------------------------------------------- /fibsem/segmentation/config.yml: -------------------------------------------------------------------------------- 1 | # data 2 | data_paths: [/path/to/data, /path/to/second/data] # paths to image data (multiple supported) 3 | label_paths: [/path/to/data/labels, /path/to/second/data/labels] # paths to label data (multiple supported) 4 | save_path: /path/to/save/checkpoints # path to save checkpoints (checkpointed each epoch) 5 | checkpoint: null # checkpoint to resume from 6 | 7 | # model 8 | encoder: "resnet34" # segmentation model encoder (imagenet) 9 | num_classes: 6 # number of classes 10 | 11 | # training 12 | epochs: 50 # number of epochs 13 | split: 0.1 # train / val split 14 | batch_size: 4 # batch size 15 | lr: 3.0e-4 # initial learning rate 16 | apply_transforms: true # apply data augmentation 17 | 18 | # logging 19 | train_log_freq: 32 # frequency to log training images 20 | val_log_freq: 32 # frequency to log validation images 21 | 22 | # wandb 23 | wandb: true # enable wandb logging 24 | wandb_project: "autolamella-mega" # wandb project 25 | wandb_entity: "openfibsem" # wandb user / org 26 | model_type: "mega-model" # model type note (descriptive only) 27 | note: "notes about this specific training run" # additional trianing note (descriptive only) -------------------------------------------------------------------------------- /fibsem/segmentation/docs/example_napari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/docs/example_napari.png -------------------------------------------------------------------------------- /fibsem/segmentation/docs/imgs/combined/combined.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/docs/imgs/combined/combined.jpg -------------------------------------------------------------------------------- /fibsem/segmentation/docs/imgs/labelled/label.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/docs/imgs/labelled/label.tif -------------------------------------------------------------------------------- /fibsem/segmentation/docs/imgs/raw/image.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/docs/imgs/raw/image.tif -------------------------------------------------------------------------------- /fibsem/segmentation/hf_segmentation_model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | 4 | from PIL import Image 5 | from transformers import SegformerImageProcessor, SegformerForSemanticSegmentation 6 | from torch import nn 7 | from fibsem.segmentation import config as scfg 8 | from fibsem.segmentation.utils import decode_segmap_v2 9 | 10 | class SegmentationModelHuggingFace: 11 | """HuggingFace model for semantic segmentation""" 12 | def __init__( 13 | self, 14 | checkpoint: str = None, 15 | ) -> None: 16 | super().__init__() 17 | 18 | self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 19 | self.colormap = scfg.get_colormap() 20 | 21 | self.checkpoint = checkpoint 22 | if checkpoint is not None: 23 | self.load_model(checkpoint=checkpoint) 24 | self.num_classes = 6 # TODO: tmp required, remove 25 | 26 | def load_model(self, checkpoint: str) -> None: 27 | """Load the model, and optionally load a checkpoint""" 28 | 29 | self.processor = SegformerImageProcessor.from_pretrained(checkpoint) 30 | self.model = SegformerForSemanticSegmentation.from_pretrained(checkpoint) 31 | 32 | return self.model 33 | 34 | def pre_process(self, img: np.ndarray) -> np.ndarray: 35 | """Pre-process the image for model inference""" 36 | 37 | # assume image is 2D grayscale 38 | image = np.asarray(Image.fromarray(np.asarray(img)).convert("RGB")) 39 | inputs = self.processor(images=image, return_tensors="pt") 40 | 41 | return inputs 42 | 43 | def inference(self, img: np.ndarray, rgb: bool = True) -> np.ndarray: 44 | """Run model inference on the input image""" 45 | 46 | inputs = self.pre_process(img) 47 | outputs = self.model(**inputs) 48 | logits = outputs.logits # shape (batch_size, num_labels, height/4, width/4) 49 | 50 | # First, rescale logits to original image size 51 | upsampled_logits = nn.functional.interpolate( 52 | logits, 53 | size=img.shape, # (height, width) 54 | mode='bilinear', 55 | align_corners=False 56 | ) 57 | 58 | # Second, apply argmax on the class dimension 59 | masks = upsampled_logits.argmax(dim=1).detach().cpu().numpy() 60 | 61 | # convert to rgb image 62 | if rgb: 63 | masks = decode_segmap_v2(masks[0], self.colormap) # 2d only 64 | else: 65 | if masks.ndim>=3: 66 | masks = masks[0] # return 2d 67 | return masks 68 | 69 | def postprocess(self, masks): 70 | """Convert the model output to a rgb class map""" 71 | if masks.ndim == 3: 72 | masks = masks[0] 73 | return decode_segmap_v2(masks, self.colormap) 74 | 75 | -------------------------------------------------------------------------------- /fibsem/segmentation/inference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | # import matplotlib.pyplot as plt 6 | import numpy as np 7 | import segmentation_models_pytorch as smp 8 | from fibsem.segmentation import dataset, utils, validate_config 9 | import torch 10 | import os 11 | import wandb 12 | from PIL import Image 13 | import yaml 14 | import zarr 15 | import glob 16 | import tifffile as tff 17 | import time 18 | 19 | def inference(images, output_dir, model, model_path, device, WANDB=False): 20 | """Helper function for performing inference with the model""" 21 | # Load the model 22 | model.load_state_dict(torch.load(model_path)) 23 | model = model.to(device) 24 | # model.eval() 25 | 26 | # Inference 27 | with torch.no_grad(): # TODO: move this down to only wrap the model inference 28 | vol = tff.imread(os.path.join(images, "*.tif*"), aszarr=True) # loading folder of .tif into zarr array) 29 | zarr_set = zarr.open(vol) 30 | 31 | filenames = sorted(glob.glob(os.path.join(images, "*.tif*"))) 32 | 33 | for img, fname in zip(zarr_set, filenames): 34 | img = torch.tensor(np.asarray(img)).unsqueeze(0) 35 | img = img.to(device) 36 | outputs = model(img[None, :, :, :].float()) 37 | output_mask = utils.decode_output(outputs) 38 | 39 | output = Image.fromarray(output_mask) 40 | path = os.path.join(output_dir, os.path.basename(fname).split(".")[0]) 41 | 42 | # if not os.path.exists(path): 43 | os.makedirs(path, exist_ok=True) 44 | 45 | input_img = Image.fromarray(img.detach().cpu().squeeze().numpy()) 46 | input_img.save(os.path.join(path, "input.tif")) 47 | output.save(os.path.join(path, "output.tif")) # or 'test.tif' 48 | 49 | if WANDB: 50 | img_base = img.detach().cpu().squeeze().numpy() 51 | img_rgb = np.dstack((img_base, img_base, img_base)) 52 | 53 | wb_img = wandb.Image(img_rgb, caption="Input Image") 54 | wb_mask = wandb.Image(output_mask, caption="Output Mask") 55 | wandb.log({"image": wb_img, "mask": wb_mask}) 56 | 57 | return outputs, output_mask 58 | 59 | if __name__ == "__main__": 60 | # command line arguments 61 | parser = argparse.ArgumentParser() 62 | parser.add_argument( 63 | "--config", 64 | help="the directory containing the config file to use", 65 | dest="config", 66 | action="store", 67 | default=os.path.join("fibsem", "segmentation", "config.yml"), 68 | ) 69 | args = parser.parse_args() 70 | config_dir = args.config 71 | 72 | # NOTE: Setup your config.yml file 73 | with open(config_dir, 'r') as f: 74 | config = yaml.safe_load(f) 75 | 76 | print("Validating config file.") 77 | validate_config.validate_config(config, "inference") 78 | 79 | # directories 80 | data_path = config["inference"]["data_dir"] 81 | model_weights = config["inference"]["model_dir"] 82 | output_dir = config["inference"]["output_dir"] 83 | 84 | # other parameters 85 | cuda = config["inference"]["cuda"] 86 | WANDB = config["inference"]["wandb"] 87 | 88 | 89 | model = smp.Unet( 90 | encoder_name=config["inference"]["encoder"], 91 | encoder_weights="imagenet", 92 | in_channels=1, # grayscale images 93 | classes=config["inference"]["num_classes"], # background, needle, lamella 94 | ) 95 | 96 | if WANDB: 97 | # weights and biases setup 98 | wandb.init(project=config["inference"]["wandb_project"], entity=config["inference"]["wandb_entity"]) 99 | 100 | device = torch.device("cuda:0" if torch.cuda.is_available() and cuda else "cpu") 101 | 102 | inference(data_path, output_dir, model, model_weights, device, WANDB) 103 | -------------------------------------------------------------------------------- /fibsem/segmentation/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/models/.gitkeep -------------------------------------------------------------------------------- /fibsem/segmentation/nnunet_model.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Optional, List 3 | 4 | import numpy as np 5 | import torch 6 | 7 | from fibsem.segmentation.utils import decode_segmap_v2 8 | 9 | from pathlib import Path 10 | from fibsem.segmentation import config as scfg 11 | import os 12 | from huggingface_hub import hf_hub_download 13 | 14 | 15 | from fibsem.segmentation import _nnunet as nnunet 16 | 17 | # TODO: actually implement this 18 | from abc import ABC 19 | def SegmentationModelBase(ABC): 20 | 21 | def __init__(self, checkpoint: str): 22 | pass 23 | 24 | 25 | def load_model(self, checkpoint: str) -> SegmentationModelBase: 26 | """Load the model, and optionally load a checkpoint""" 27 | raise NotImplementedError 28 | 29 | def inference(self, img: np.ndarray, rgb: bool = True) -> np.ndarray: 30 | """Run model inference on the input image""" 31 | raise NotImplementedError 32 | 33 | class SegmentationModelNNUnet: 34 | def __init__( 35 | self, 36 | checkpoint: str = None, 37 | ) -> None: 38 | super().__init__() 39 | 40 | self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 41 | self.colormap = scfg.get_colormap() 42 | 43 | self.checkpoint = checkpoint 44 | if checkpoint is not None: 45 | self.load_model(checkpoint=checkpoint) 46 | 47 | def load_model(self, checkpoint: str) -> None: 48 | """Load the model, and optionally load a checkpoint""" 49 | 50 | self.model = nnunet.load_model(path=checkpoint, device=self.device) 51 | 52 | return self.model 53 | 54 | def pre_process(self, img: np.ndarray) -> np.ndarray: 55 | """Pre-process the image for model inference""" 56 | 57 | # convert to 4D 58 | if img.ndim == 2: 59 | img = img[np.newaxis, np.newaxis, :, :] 60 | elif img.ndim == 3: 61 | img = img[np.newaxis, :, :, :] 62 | elif img.ndim == 4: 63 | img = img[:, :, :, :] 64 | else: 65 | raise ValueError(f"Invalid image shape: {img.shape}") 66 | 67 | # TODO: also do dtype conversions 68 | if not isinstance(img.dtype, np.float32): 69 | img = img.astype(np.float32) 70 | 71 | return img 72 | 73 | def inference(self, img: np.ndarray, rgb: bool = True) -> np.ndarray: 74 | """Run model inference on the input image""" 75 | 76 | img = self.pre_process(img) 77 | 78 | masks, scores = nnunet.inference(self.model, img) 79 | 80 | # convert to rgb image 81 | if rgb: 82 | masks = decode_segmap_v2(masks[0], self.colormap) # 2d only 83 | else: 84 | if masks.ndim>=3: 85 | masks = masks[0] # return 2d 86 | return masks 87 | 88 | def postprocess(self, masks): 89 | """Convert the model output to a rgb class map""" 90 | if masks.ndim == 3: 91 | masks = masks[0] 92 | return decode_segmap_v2(masks, self.colormap) 93 | 94 | -------------------------------------------------------------------------------- /fibsem/segmentation/requirements.txt: -------------------------------------------------------------------------------- 1 | segment-anything @ git+https://github.com/facebookresearch/segment-anything.git 2 | opencv-python 3 | pycocotools 4 | matplotlib 5 | onnxruntime 6 | onnx -------------------------------------------------------------------------------- /fibsem/segmentation/sam_model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | from transformers import SamModel, SamProcessor 4 | from typing import List, Tuple 5 | 6 | class SamModelWrapper: 7 | def __init__(self, checkpoint: str = "facebook/sam-vit-base", device: str = None): 8 | if device is None: 9 | device = "cuda" if torch.cuda.is_available() else "cpu" 10 | self.device = device 11 | 12 | self.checkpoint = checkpoint 13 | self.model: SamModel = SamModel.from_pretrained(checkpoint).to(self.device) 14 | self.processor: SamProcessor = SamProcessor.from_pretrained(checkpoint) 15 | 16 | def __call__( 17 | self, 18 | image: np.ndarray, 19 | points: List[List[List[int]]] = None, 20 | labels: List[List[bool]] = None, 21 | input_masks=None, 22 | multimask_output: bool = False, 23 | ): 24 | inputs = self.processor( 25 | image, 26 | input_points=points, 27 | input_labels=labels, 28 | return_tensors="pt", 29 | input_masks=input_masks, 30 | multimask_output=multimask_output, 31 | ).to(self.device) 32 | # TODO: multi-mask output doesn't seem to work? 33 | with torch.no_grad(): 34 | outputs = self.model(**inputs) 35 | return outputs, inputs 36 | 37 | # to mimic the functionality of sam predictor 38 | def predict( 39 | self, 40 | image: np.ndarray, 41 | points: List[List[List[int]]] = None, 42 | labels: List[List[bool]] = None, 43 | input_masks: np.ndarray = None, 44 | multimask_output: bool = False, 45 | ) -> Tuple[np.ndarray, float, np.ndarray]: 46 | outputs, inputs = self(image, points, labels, input_masks, multimask_output) 47 | masks: torch.Tensor = self.processor.image_processor.post_process_masks( 48 | outputs.pred_masks.cpu(), 49 | inputs["original_sizes"].cpu(), 50 | inputs["reshaped_input_sizes"].cpu(), 51 | ) 52 | scores: torch.Tensor = outputs.iou_scores 53 | logits: torch.Tensor = outputs.pred_masks 54 | 55 | # get best mask 56 | idx = np.argmax(scores.detach().cpu().numpy()) 57 | mask = masks[0][0][idx].detach().cpu().numpy() 58 | score = scores[0][0][idx].detach().cpu().numpy() 59 | logits = logits[0][0][idx].detach().cpu().unsqueeze(0).numpy() 60 | 61 | return mask, score, logits 62 | 63 | def __repr__(self): 64 | return f"SamModelWrapper({self.checkpoint}, {self.device})" 65 | -------------------------------------------------------------------------------- /fibsem/segmentation/segmentation_config.yaml: -------------------------------------------------------------------------------- 1 | CLASS_COLORS: 2 | 0: "black" 3 | 1: "red" 4 | 2: "green" 5 | 3: "cyan" 6 | 4: "yellow" 7 | 5: "magenta" 8 | 6: "purple" 9 | 7: "white" 10 | 8: "orange" 11 | 9: "blue" 12 | 10: "pink" 13 | 11: "gold" 14 | 12: "salmon" 15 | 13: "olive" 16 | 14: "maroon" 17 | 15: "navy" 18 | 16: "teal" 19 | 17: "lime" 20 | 18: "aqua" 21 | 19: "silver" 22 | 20: "brown" 23 | 21: "indigo" 24 | 22: "violet" 25 | 23: "turquoise" 26 | 24: "tan" 27 | 25: "orchid" 28 | 26: "gray" 29 | 27: "khaki" 30 | 28: "coral" 31 | 29: "crimson" 32 | 30: "plum" 33 | 31: "lavender" 34 | 32: "darkgreen" 35 | 33: "darkblue" 36 | 34: "darkred" 37 | 35: "darkgray" 38 | 36: "darkorange" 39 | 40 | CLASS_LABELS: # autolamella 41 | 0: "background" 42 | 1: "lamella" 43 | 2: "manipulator" 44 | 3: "landing_post" 45 | 4: "copper_adaptor" 46 | 5: "volume_block" 47 | 48 | 49 | # CLASS_LABELS: # spacetomo 50 | # 0: "background" 51 | # 1: "white" 52 | # 2: "black" 53 | # 3: "crack" 54 | # 4: "coating" 55 | # 5: "cell" 56 | # 6: "cellwall" 57 | # 7: "nucleus" 58 | # 8: "vacuole" 59 | # 9: "mitos" 60 | # 10: "lipiddroplets" 61 | # 11: "vesicles" 62 | # 12: "multivesicles" 63 | # 13: "membranes" 64 | # 14: "dynabeads" 65 | # 15: "ice" 66 | # 16: "cryst" 67 | -------------------------------------------------------------------------------- /fibsem/segmentation/test_image.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/segmentation/test_image.tif -------------------------------------------------------------------------------- /fibsem/tools/_parser.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | 5 | def _parse_health_monitor_data(path: str) -> pd.DataFrame: 6 | # PATH = "health-monitor/data2.csv" 7 | 8 | df = pd.read_csv(path, skiprows=5) 9 | 10 | # display(df) 11 | 12 | 13 | # header = system 14 | # row 0 = subsystem 15 | # row 1 = component 16 | # row 2 = parameter 17 | # row 3 = unit 18 | # row 4 = type 19 | 20 | # col 0 = date 21 | # col 1 = timestamp 22 | 23 | df_headers = df.iloc[0:5, 2:] 24 | df_data = df.iloc[6:, :] 25 | 26 | systems = df_headers.columns.values 27 | subsystems = df_headers.iloc[0, :].values 28 | components = df_headers.iloc[1, :].values 29 | parameters = df_headers.iloc[2, :].values 30 | units = df_headers.iloc[3, :].values 31 | types = df_headers.iloc[4, :].values 32 | 33 | type_map = { 34 | "Int": np.uint8, 35 | "Float": np.float32, 36 | "String": str, 37 | "Boolean": bool, 38 | "DateTime": np.datetime64, 39 | } 40 | 41 | new_columns = ["Date", "Time"] 42 | new_columns_type = ["datetime64", "datetime64"] 43 | for subsystem, component, parameter, unit, type in zip(subsystems, components, parameters, units, types): 44 | new_columns.append(f"{subsystem}.{component}.{parameter} ({unit})") 45 | new_columns_type.append(type_map[type]) 46 | 47 | df_data.columns = new_columns 48 | 49 | # replace all values of "NaN" with np.nan 50 | df_data = df_data.replace("NaN", np.nan) 51 | 52 | # drop columns with all NaN values 53 | # df_data = df_data.dropna(axis=0, how="all") 54 | # df_data = df_data.astype(dict(zip(new_columns, new_columns_type))) 55 | 56 | # combine date and time columns 57 | df_data["datetime"] = pd.to_datetime(df_data["Date"] + " " + df_data["Time"]) 58 | 59 | # set timezone to Aus/Sydney for datetime column 60 | # df_data["datetime"] = df_data["datetime"].dt.tz_localize("UTC").dt.tz_convert("Australia/Sydney") 61 | 62 | # drop Date and Time columns 63 | df_data = df_data.drop(columns=["Date", "Time"]) 64 | 65 | 66 | # print duplicate columns 67 | # drop duplicate columns 68 | df_data = df_data.loc[:,~df_data.columns.duplicated()] 69 | print(df_data.columns[df_data.columns.duplicated()]) 70 | 71 | 72 | return df_data 73 | -------------------------------------------------------------------------------- /fibsem/tools/_streamlit.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import threading 3 | import time 4 | 5 | def run_streamlit_simple(script_path: str, port: int = 8501, streamlit_args: list = None) -> subprocess.Popen: 6 | """Simple version to run Streamlit app in background thread.""" 7 | 8 | def start_streamlit(): 9 | cmd = [ 10 | "streamlit", "run", script_path, 11 | "--server.port", str(port), 12 | "--server.headless", "false", 13 | "--browser.gatherUsageStats", "false", 14 | ] 15 | 16 | # Add any additional arguments for the streamlit script 17 | if streamlit_args: 18 | cmd.append("--") # Separator between streamlit args and script args 19 | cmd.extend(streamlit_args) 20 | 21 | subprocess.run(cmd) 22 | 23 | # Start in daemon thread so it stops when main program exits 24 | thread = threading.Thread(target=start_streamlit, daemon=True) 25 | thread.start() 26 | 27 | # Give it time to start 28 | time.sleep(2) 29 | 30 | return thread 31 | 32 | # Usage 33 | def main(): 34 | 35 | script_path = "/home/patrick/github/autolamella/autolamella/tools/review.py" 36 | experiment_path = "/home/patrick/github/autolamella/autolamella/log/AutoLamella-2025-05-30-17-56" 37 | 38 | # Start Streamlit app 39 | streamlit_args = ["--experiment_path", experiment_path] 40 | thread = run_streamlit_simple(script_path=script_path, 41 | port=8502, 42 | streamlit_args=streamlit_args) 43 | 44 | # Your main application continues here 45 | print("Streamlit is running in background...") 46 | 47 | # Keep main thread alive 48 | try: 49 | while True: 50 | time.sleep(1) 51 | except KeyboardInterrupt: 52 | print("Exiting...") 53 | 54 | if __name__ == "__main__": 55 | main() -------------------------------------------------------------------------------- /fibsem/tools/run_manipulator_calibration.py: -------------------------------------------------------------------------------- 1 | from fibsem import utils, calibration, acquire 2 | 3 | from fibsem.structures import BeamType, FibsemManipulatorPosition 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import sys 7 | 8 | def main(): 9 | microscope, settings = utils.setup_session() 10 | 11 | # if argv 1 is "debug", run in debug mode 12 | _DEBUG = False 13 | if len(sys.argv) > 1: 14 | if sys.argv[1] == "debug": 15 | _DEBUG = True 16 | 17 | if _DEBUG: 18 | microscope.set("scan_rotation", np.deg2rad(0), beam_type=BeamType.ION) 19 | microscope.move_manipulator_to_position_offset(offset=FibsemManipulatorPosition(), name="EUCENTRIC") 20 | microscope.move_manipulator_corrected(dx=-50e-6, dy=-150e-6, beam_type=BeamType.ELECTRON) 21 | 22 | settings.image.autocontrast = True 23 | settings.image.hfw = 900e-6 24 | eb_image, ib_image = acquire.take_reference_images(microscope, settings.image) 25 | 26 | fig, ax = plt.subplots(1, 2, figsize=(10, 5)) 27 | ax[0].imshow(eb_image.data, cmap="gray") 28 | ax[1].imshow(ib_image.data, cmap="gray") 29 | 30 | plt.show() 31 | 32 | calibration._calibrate_manipulator_thermo(microscope, settings, None) 33 | 34 | if __name__ == "__main__": 35 | main() -------------------------------------------------------------------------------- /fibsem/tools/run_split_dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import pandas as pd 4 | from pathlib import Path 5 | import argparse 6 | 7 | def _split_dataset(DATA_PATH: Path, OUTPUT_PATH: Path): 8 | 9 | TRAIN_PATH = os.path.join(OUTPUT_PATH, "train" ) 10 | TEST_PATH = os.path.join(OUTPUT_PATH, "test") 11 | 12 | # make dirs 13 | os.makedirs(os.path.join(TRAIN_PATH, "labels"), exist_ok=True) 14 | os.makedirs(os.path.join(TEST_PATH, "labels"), exist_ok=True) 15 | 16 | df = pd.read_csv(os.path.join(DATA_PATH, "data.csv")) 17 | df["path"] = df["image"].apply(lambda x: os.path.join(DATA_PATH, f"{x}.tif")) 18 | df["mask_path"] = df["image"].apply(lambda x: os.path.join(DATA_PATH, "mask", f"{x}.tif")) 19 | 20 | print(f"total: {len(df)} images") 21 | 22 | # corrected: did the user overwrite the model prediction 23 | # False: the model prediction was correct -> test set 24 | # True: the model prediction was incorrect -> train set 25 | 26 | # split the data into train and test 27 | df_train = df[df["corrected"] == True] 28 | df_test = df[df["corrected"] == False] 29 | 30 | print(f"train: {len(df_train)} images") 31 | print(f"test: {len(df_test)} images") 32 | 33 | response = input(f"Move data to {OUTPUT_PATH}? [y/n]") 34 | 35 | if response == "y": 36 | # move the images and masks to the correct folder 37 | for path, mask_path in zip(df_train["path"].unique(), df_train["mask_path"].unique()): 38 | os.rename(path, os.path.join(TRAIN_PATH, os.path.basename(path))) 39 | os.rename(mask_path, os.path.join(TRAIN_PATH, "labels", os.path.basename(mask_path))) 40 | 41 | for path, mask_path in zip(df_test["path"].unique(), df_test["mask_path"].unique()): 42 | os.rename(path, os.path.join(TEST_PATH, os.path.basename(path))) 43 | os.rename(mask_path, os.path.join(TEST_PATH, "labels", os.path.basename(mask_path))) 44 | 45 | 46 | def main(): 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument("--data_path", type=str, dest="data_path", action="store", help="Path to the data folder") 49 | parser.add_argument("--output_path", type=str, dest="output_path", action="store", help="Path to the output folder") 50 | 51 | args = parser.parse_args() 52 | 53 | _split_dataset(args.data_path, args.output_path) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() -------------------------------------------------------------------------------- /fibsem/transformations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional 3 | 4 | import numpy as np 5 | 6 | from fibsem.microscope import FibsemMicroscope 7 | from fibsem.structures import FibsemStagePosition 8 | 9 | 10 | def convert_milling_angle_to_stage_tilt( 11 | milling_angle: float, pretilt: float, column_tilt: float = np.deg2rad(52) 12 | ) -> float: 13 | """Convert the milling angle to the stage tilt angle, based on pretilt and column tilt. 14 | milling_angle = 90 - column_tilt + stage_tilt - pretilt 15 | stage_tilt = milling_angle - 90 + pretilt + column_tilt 16 | Args: 17 | milling_angle: milling angle (radians) 18 | pretilt: pretilt angle (radians) 19 | column_tilt: column tilt angle (radians) 20 | Returns: 21 | stage_tilt: stage tilt (radians)""" 22 | 23 | stage_tilt = milling_angle + column_tilt + pretilt - np.deg2rad(90) 24 | 25 | return stage_tilt 26 | 27 | 28 | def convert_stage_tilt_to_milling_angle( 29 | stage_tilt: float, pretilt: float, column_tilt: float = np.deg2rad(52) 30 | ) -> float: 31 | """Convert the stage tilt angle to the milling angle, based on pretilt and column tilt. 32 | milling_angle = 90 - column_tilt + stage_tilt - pretilt 33 | Args: 34 | stage_tilt: stage tilt (radians) 35 | pretilt: pretilt angle (radians) 36 | column_tilt: column tilt angle (radians) 37 | Returns: 38 | milling_angle: milling angle (radians)""" 39 | 40 | milling_angle = np.deg2rad(90) - column_tilt + stage_tilt - pretilt 41 | 42 | return milling_angle 43 | 44 | 45 | def get_stage_tilt_from_milling_angle( 46 | microscope: FibsemMicroscope, milling_angle: float 47 | ) -> float: 48 | """Get the stage tilt angle from the milling angle, based on pretilt and column tilt. 49 | Args: 50 | microscope (FibsemMicroscope): microscope connection 51 | milling_angle (float): milling angle (radians) 52 | Returns: 53 | float: stage tilt angle (radians) 54 | """ 55 | pretilt = np.deg2rad(microscope.system.stage.shuttle_pre_tilt) 56 | column_tilt = np.deg2rad(microscope.system.ion.column_tilt) 57 | stage_tilt = convert_milling_angle_to_stage_tilt( 58 | milling_angle, pretilt, column_tilt 59 | ) 60 | logging.debug( 61 | f"milling_angle: {np.rad2deg(milling_angle):.2f} deg, " 62 | f"pretilt: {np.rad2deg(pretilt)} deg, " 63 | f"stage_tilt: {np.rad2deg(stage_tilt):.2f} deg" 64 | ) 65 | return stage_tilt 66 | 67 | def is_close_to_milling_angle( 68 | microscope: FibsemMicroscope, milling_angle: float, atol: float = np.deg2rad(2) 69 | ) -> bool: 70 | """Check if the stage tilt is close to the milling angle, within a tolerance. 71 | Args: 72 | microscope (FibsemMicroscope): microscope connection 73 | milling_angle (float): milling angle (radians) 74 | atol (float): tolerance in radians 75 | Returns: 76 | bool: True if the stage tilt is within the tolerance of the milling angle 77 | """ 78 | current_stage_tilt = microscope.get_stage_position().t 79 | pretilt = np.deg2rad(microscope.system.stage.shuttle_pre_tilt) 80 | column_tilt = np.deg2rad(microscope.system.ion.column_tilt) 81 | stage_tilt = convert_milling_angle_to_stage_tilt( 82 | milling_angle, pretilt=pretilt, column_tilt=column_tilt 83 | ) 84 | logging.info( 85 | f"The current stage tilt is {np.rad2deg(stage_tilt):.2f} deg, " 86 | f"the stage tilt for the milling angle is {np.rad2deg(stage_tilt):.2f} deg" 87 | ) 88 | return np.isclose(stage_tilt, current_stage_tilt, atol=atol) 89 | 90 | # TODO: move inside the microscope class 91 | def move_to_milling_angle( 92 | microscope: FibsemMicroscope, 93 | milling_angle: float, 94 | rotation: Optional[float] = None, 95 | ) -> bool: 96 | """Move the stage to the milling angle, based on the current pretilt and column tilt.""" 97 | 98 | if rotation is None: 99 | rotation = microscope.system.stage.rotation_reference 100 | 101 | # calculate the stage tilt from the milling angle 102 | stage_tilt = get_stage_tilt_from_milling_angle(microscope, milling_angle) 103 | stage_position = FibsemStagePosition(t=stage_tilt, r=rotation) 104 | microscope.safe_absolute_stage_movement(stage_position) 105 | 106 | is_close = is_close_to_milling_angle(microscope, milling_angle) 107 | return is_close -------------------------------------------------------------------------------- /fibsem/ui/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/ui/.gitkeep -------------------------------------------------------------------------------- /fibsem/ui/FibsemCryoDepositionWidget.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import napari 3 | import napari.utils.notifications 4 | from PyQt5 import QtWidgets 5 | from fibsem import config as cfg 6 | from fibsem import constants, conversions, utils 7 | from fibsem.microscope import FibsemMicroscope, ThermoMicroscope 8 | from fibsem.microscopes.tescan import TescanMicroscope 9 | from fibsem.microscopes.simulator import DemoMicroscope 10 | from fibsem.structures import MicroscopeSettings, FibsemGasInjectionSettings 11 | 12 | from fibsem.ui.qtdesigner_files import FibsemCryoDepositionWidget 13 | from fibsem import gis 14 | 15 | 16 | class FibsemCryoDepositionWidget(FibsemCryoDepositionWidget.Ui_Dialog, QtWidgets.QDialog): 17 | def __init__( 18 | self, 19 | microscope: FibsemMicroscope = None, 20 | settings: MicroscopeSettings = None, 21 | parent=None, 22 | ): 23 | super(FibsemCryoDepositionWidget, self).__init__(parent=parent) 24 | self.setupUi(self) 25 | self.setWindowTitle("Cryo Deposition") 26 | 27 | self.microscope = microscope 28 | self.settings = settings 29 | 30 | self.setup_connections() 31 | 32 | def setup_connections(self): 33 | 34 | self.pushButton_run_sputter.clicked.connect(self.run_sputter) 35 | 36 | positions = utils.load_yaml(cfg.POSITION_PATH) 37 | self.comboBox_stage_position.addItems(["Current Position"] + [p["name"] for p in positions]) 38 | available_ports = self.microscope.get_available_values("gis_ports") 39 | self.comboBox_port.addItems([str(p) for p in available_ports]) 40 | 41 | 42 | # TODO: show / hide based on gis / multichem available 43 | multichem_available = self.microscope.is_available("gis_multichem") 44 | self.lineEdit_gas.setVisible(multichem_available) 45 | self.label_gas.setVisible(multichem_available) 46 | self.lineEdit_insert_position.setVisible(multichem_available) 47 | self.label_insert_position.setVisible(multichem_available) 48 | self.comboBox_port.setVisible(not multichem_available) # gis only 49 | self.label_port.setVisible(not multichem_available) # gis only 50 | 51 | def _get_protocol_from_ui(self): 52 | 53 | protocol = { 54 | "port": self.comboBox_port.currentText(), 55 | "gas": self.lineEdit_gas.text(), 56 | "insert_position": self.lineEdit_insert_position.text(), 57 | "duration": self.doubleSpinBox_duration.value(), 58 | "name": self.comboBox_stage_position.currentText(), 59 | 60 | } 61 | 62 | return protocol 63 | 64 | # TODO: thread this, add progress bar, feedback 65 | def run_sputter(self): 66 | 67 | gdict = self._get_protocol_from_ui() 68 | gis_settings = FibsemGasInjectionSettings.from_dict(gdict) 69 | 70 | if gdict["name"] == "Current Position": 71 | gdict["name"] = None 72 | 73 | gis.cryo_deposition_v2(self.microscope, gis_settings, name=gdict["name"]) 74 | 75 | 76 | def main(): 77 | 78 | viewer = napari.Viewer(ndisplay=2) 79 | microscope, settings = utils.setup_session() 80 | cryo_sputter_widget = FibsemCryoDepositionWidget(microscope, settings) 81 | viewer.window.add_dock_widget( 82 | cryo_sputter_widget, area="right", add_vertical_stretch=False 83 | ) 84 | napari.run() 85 | 86 | 87 | if __name__ == "__main__": 88 | main() -------------------------------------------------------------------------------- /fibsem/ui/FibsemMicroscopeConfigurationWidget.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import napari 3 | import napari.utils.notifications 4 | import fibsem 5 | from PyQt5 import QtWidgets 6 | from fibsem import config as cfg 7 | from fibsem.ui.qtdesigner_files import FibsemMicroscopeConfigurationWidget as FibsemMicroscopeConfigurationWidgetUI 8 | from fibsem.ui.FibsemMicroscopeConfigurationWidgetBase import FibsemMicroscopeConfigurationWidgetBase 9 | 10 | 11 | class FibsemMicroscopeConfigurationWidget(FibsemMicroscopeConfigurationWidgetUI.Ui_MainWindow, QtWidgets.QMainWindow): 12 | def __init__( 13 | self, 14 | path: str = None, 15 | viewer: napari.Viewer = None, 16 | parent=None, 17 | ): 18 | super().__init__(parent=parent) 19 | self.setupUi(self) 20 | self.setWindowTitle("Microscope Configuration") 21 | 22 | self.viewer = viewer 23 | 24 | self.configuration_widget = FibsemMicroscopeConfigurationWidgetBase(path) 25 | # add to layout 26 | self.gridLayout.addWidget(self.configuration_widget) 27 | 28 | self.setup_connections() 29 | 30 | def setup_connections(self): 31 | 32 | # actions 33 | self.actionSave_Configuration.triggered.connect(self.configuration_widget.save_configuration) 34 | self.actionLoad_Configuration.triggered.connect(self.configuration_widget.load_configuration_from_file) 35 | 36 | 37 | def main(): 38 | 39 | # parse arguments 40 | parser = argparse.ArgumentParser(f"Microscope Configuration UI") 41 | parser.add_argument("--config", type=str, default=cfg.DEFAULT_CONFIGURATION_PATH, help="Path to microscope configuration file") 42 | args = parser.parse_args() 43 | 44 | # widget viewer 45 | viewer = napari.Viewer(ndisplay=2) 46 | microscope_configuration = FibsemMicroscopeConfigurationWidget(path=args.config, viewer=viewer) 47 | viewer.window.add_dock_widget( 48 | microscope_configuration, 49 | area="right", 50 | add_vertical_stretch=False, 51 | name=f"OpenFIBSEM v{fibsem.__version__} Microscope Configuration", 52 | ) 53 | napari.run() 54 | 55 | 56 | if __name__ == "__main__": 57 | main() -------------------------------------------------------------------------------- /fibsem/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from fibsem.ui.FibsemImageSettingsWidget import FibsemImageSettingsWidget 2 | from fibsem.ui.FibsemMovementWidget import FibsemMovementWidget 3 | from fibsem.ui.FibsemSystemSetupWidget import FibsemSystemSetupWidget 4 | from fibsem.ui.FibsemMillingWidget import FibsemMillingWidget 5 | from fibsem.ui.FibsemCryoDepositionWidget import FibsemCryoDepositionWidget 6 | from fibsem.ui.FibsemMinimapWidget import FibsemMinimapWidget 7 | from fibsem.ui.FibsemManipulatorWidget import FibsemManipulatorWidget 8 | from fibsem.ui.FibsemSpotBurnWidget import FibsemSpotBurnWidget 9 | try: 10 | from fibsem.ui.FibsemEmbeddedDetectionWidget import FibsemEmbeddedDetectionUI 11 | DETECTION_AVAILABLE = True 12 | except ImportError: 13 | DETECTION_AVAILABLE = False 14 | import logging 15 | logging.debug("Could not import FibsemEmbeddedDetectionWidget") 16 | 17 | -------------------------------------------------------------------------------- /fibsem/ui/napari/properties.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # IMAGING 4 | 5 | 6 | ALIGNMENT_LAYER_PROPERTIES = { 7 | "name": "alignment_area", 8 | "shape_type": "rectangle", 9 | "edge_color": "lime", 10 | "edge_width": 20, 11 | "face_color": "transparent", 12 | "opacity": 0.5, 13 | "metadata": {"type": "alignment"}, 14 | } 15 | 16 | IMAGE_TEXT_LAYER_PROPERTIES = { 17 | "name": "label", 18 | "text": { 19 | "string": ["ELECTRON BEAM", "ION BEAM"], 20 | "color": "white" 21 | }, 22 | "size": 20, 23 | "edge_width": 7, 24 | "edge_width_is_relative": False, 25 | "edge_color": "transparent", 26 | "face_color": "transparent", 27 | } 28 | 29 | IMAGING_CROSSHAIR_LAYER_PROPERTIES = { 30 | "name": "crosshair", 31 | "shape_type": "line", 32 | "edge_width": 5, 33 | "edge_color": "yellow", 34 | "face_color": "yellow", 35 | "opacity": 0.8, 36 | "blending": "translucent", 37 | } 38 | 39 | IMAGING_SCALEBAR_LAYER_PROPERTIES = { 40 | "name": "scalebar", 41 | "shape_type": "line", 42 | "edge_width": 5, 43 | "edge_color": "yellow", 44 | "face_color": "yellow", 45 | "opacity": 0.8, 46 | "blending": "translucent", 47 | "text": { 48 | "color":"white", 49 | "translation": np.array([-20, 0]), 50 | "opacity": 1, 51 | "sze": 20, 52 | }, 53 | } 54 | 55 | IMAGING_RULER_LAYER_PROPERTIES = { 56 | "name": "ruler", 57 | "size": 20, 58 | "face_color": "lime", 59 | "edge_color": "white", 60 | "text": { 61 | "color": "white", 62 | "translation": np.array([-20, 0]), 63 | "size": 10, 64 | }, 65 | "line-layer": { 66 | "name": "ruler_line", 67 | "shape_type": "line", 68 | "edge_width": 5, 69 | "edge_color": "lime", 70 | }, 71 | } 72 | 73 | RULER_LAYER_NAME = IMAGING_RULER_LAYER_PROPERTIES["name"] 74 | RULER_LINE_LAYER_NAME = IMAGING_RULER_LAYER_PROPERTIES["line-layer"]["name"] 75 | 76 | # MILLING 77 | 78 | 79 | ## MINIMAP 80 | 81 | OVERVIEW_IMAGE_LAYER_PROPERTIES = { 82 | "name": "overview-image", 83 | "colormap": "gray", 84 | "blending": "additive", 85 | "median_filter_size": 3, 86 | } 87 | 88 | GRIDBAR_IMAGE_LAYER_PROPERTIES = { 89 | "name": "gridbar-image", 90 | "spacing": 100, 91 | "width": 20, 92 | } 93 | 94 | CORRELATION_IMAGE_LAYER_PROPERTIES = { 95 | "name": "correlation-image", 96 | "colormap": "green", 97 | "blending": "translucent", 98 | "opacity": 0.2, 99 | "colours": ["green", "cyan", "magenta", "red", "yellow"], 100 | } 101 | 102 | STAGE_POSITION_SHAPE_LAYER_PROPERTIES = { 103 | "name": "stage-positions", 104 | "shape_type": "line", 105 | "edge_width": 5, 106 | "edge_color": "yellow", 107 | "face_color": "yellow", 108 | "opacity": 0.8, 109 | "blending": "translucent", 110 | "text": { 111 | "string": [], 112 | "color": "white", 113 | "size": 15, 114 | "translation": np.array([-50, 0]), # text shown 50px above the point 115 | }, 116 | "saved_color": "lime", 117 | } -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemCryoDepositionWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemCryoDepositionWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(416, 221) 18 | self.gridLayout = QtWidgets.QGridLayout(Dialog) 19 | self.gridLayout.setObjectName("gridLayout") 20 | self.label_duration = QtWidgets.QLabel(Dialog) 21 | self.label_duration.setObjectName("label_duration") 22 | self.gridLayout.addWidget(self.label_duration, 5, 0, 1, 1) 23 | self.comboBox_stage_position = QtWidgets.QComboBox(Dialog) 24 | self.comboBox_stage_position.setObjectName("comboBox_stage_position") 25 | self.gridLayout.addWidget(self.comboBox_stage_position, 1, 1, 1, 1) 26 | self.label_title = QtWidgets.QLabel(Dialog) 27 | font = QtGui.QFont() 28 | font.setPointSize(12) 29 | font.setBold(True) 30 | font.setWeight(75) 31 | self.label_title.setFont(font) 32 | self.label_title.setObjectName("label_title") 33 | self.gridLayout.addWidget(self.label_title, 0, 0, 1, 2) 34 | self.label_stage_position = QtWidgets.QLabel(Dialog) 35 | self.label_stage_position.setObjectName("label_stage_position") 36 | self.gridLayout.addWidget(self.label_stage_position, 1, 0, 1, 1) 37 | self.lineEdit_gas = QtWidgets.QLineEdit(Dialog) 38 | self.lineEdit_gas.setObjectName("lineEdit_gas") 39 | self.gridLayout.addWidget(self.lineEdit_gas, 4, 1, 1, 1) 40 | self.doubleSpinBox_duration = QtWidgets.QDoubleSpinBox(Dialog) 41 | self.doubleSpinBox_duration.setMaximum(1000.0) 42 | self.doubleSpinBox_duration.setProperty("value", 30.0) 43 | self.doubleSpinBox_duration.setObjectName("doubleSpinBox_duration") 44 | self.gridLayout.addWidget(self.doubleSpinBox_duration, 5, 1, 1, 1) 45 | self.label_port = QtWidgets.QLabel(Dialog) 46 | self.label_port.setObjectName("label_port") 47 | self.gridLayout.addWidget(self.label_port, 3, 0, 1, 1) 48 | self.pushButton_run_sputter = QtWidgets.QPushButton(Dialog) 49 | self.pushButton_run_sputter.setObjectName("pushButton_run_sputter") 50 | self.gridLayout.addWidget(self.pushButton_run_sputter, 7, 0, 1, 2) 51 | self.label_gas = QtWidgets.QLabel(Dialog) 52 | self.label_gas.setObjectName("label_gas") 53 | self.gridLayout.addWidget(self.label_gas, 4, 0, 1, 1) 54 | self.label_insert_position = QtWidgets.QLabel(Dialog) 55 | self.label_insert_position.setObjectName("label_insert_position") 56 | self.gridLayout.addWidget(self.label_insert_position, 6, 0, 1, 1) 57 | self.lineEdit_insert_position = QtWidgets.QLineEdit(Dialog) 58 | self.lineEdit_insert_position.setObjectName("lineEdit_insert_position") 59 | self.gridLayout.addWidget(self.lineEdit_insert_position, 6, 1, 1, 1) 60 | self.comboBox_port = QtWidgets.QComboBox(Dialog) 61 | self.comboBox_port.setObjectName("comboBox_port") 62 | self.gridLayout.addWidget(self.comboBox_port, 3, 1, 1, 1) 63 | 64 | self.retranslateUi(Dialog) 65 | QtCore.QMetaObject.connectSlotsByName(Dialog) 66 | 67 | def retranslateUi(self, Dialog): 68 | _translate = QtCore.QCoreApplication.translate 69 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 70 | self.label_duration.setText(_translate("Dialog", "Duration (s)")) 71 | self.label_title.setText(_translate("Dialog", "Cryo Deposition")) 72 | self.label_stage_position.setText(_translate("Dialog", "Stage Position")) 73 | self.lineEdit_gas.setText(_translate("Dialog", "Pt cryo")) 74 | self.label_port.setText(_translate("Dialog", "Port")) 75 | self.pushButton_run_sputter.setText(_translate("Dialog", "Run Cryo Deposition")) 76 | self.label_gas.setText(_translate("Dialog", "Gas")) 77 | self.label_insert_position.setText(_translate("Dialog", "Insert Position")) 78 | self.lineEdit_insert_position.setText(_translate("Dialog", "cryo")) 79 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemCryoDepositionWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 416 10 | 221 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | Duration (s) 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 12 32 | 75 33 | true 34 | 35 | 36 | 37 | Cryo Deposition 38 | 39 | 40 | 41 | 42 | 43 | 44 | Stage Position 45 | 46 | 47 | 48 | 49 | 50 | 51 | Pt cryo 52 | 53 | 54 | 55 | 56 | 57 | 58 | 1000.000000000000000 59 | 60 | 61 | 30.000000000000000 62 | 63 | 64 | 65 | 66 | 67 | 68 | Port 69 | 70 | 71 | 72 | 73 | 74 | 75 | Run Cryo Deposition 76 | 77 | 78 | 79 | 80 | 81 | 82 | Gas 83 | 84 | 85 | 86 | 87 | 88 | 89 | Insert Position 90 | 91 | 92 | 93 | 94 | 95 | 96 | cryo 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemEmbeddedDetectionWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemEmbeddedDetectionWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.11 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(472, 525) 18 | self.gridLayout = QtWidgets.QGridLayout(Form) 19 | self.gridLayout.setObjectName("gridLayout") 20 | self.label_model = QtWidgets.QLabel(Form) 21 | self.label_model.setObjectName("label_model") 22 | self.gridLayout.addWidget(self.label_model, 1, 0, 1, 2) 23 | self.label_title = QtWidgets.QLabel(Form) 24 | font = QtGui.QFont() 25 | font.setPointSize(9) 26 | font.setBold(True) 27 | font.setWeight(75) 28 | self.label_title.setFont(font) 29 | self.label_title.setObjectName("label_title") 30 | self.gridLayout.addWidget(self.label_title, 0, 0, 1, 2) 31 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 32 | self.gridLayout.addItem(spacerItem, 4, 0, 1, 2) 33 | self.label_info = QtWidgets.QLabel(Form) 34 | self.label_info.setMinimumSize(QtCore.QSize(0, 0)) 35 | self.label_info.setText("") 36 | self.label_info.setWordWrap(True) 37 | self.label_info.setObjectName("label_info") 38 | self.gridLayout.addWidget(self.label_info, 2, 0, 1, 2) 39 | self.label_instructions = QtWidgets.QLabel(Form) 40 | self.label_instructions.setWordWrap(True) 41 | self.label_instructions.setObjectName("label_instructions") 42 | self.gridLayout.addWidget(self.label_instructions, 3, 0, 1, 2) 43 | 44 | self.retranslateUi(Form) 45 | QtCore.QMetaObject.connectSlotsByName(Form) 46 | 47 | def retranslateUi(self, Form): 48 | _translate = QtCore.QCoreApplication.translate 49 | Form.setWindowTitle(_translate("Form", "Form")) 50 | self.label_model.setText(_translate("Form", "Model:")) 51 | self.label_title.setText(_translate("Form", "Feature Detection")) 52 | self.label_instructions.setText(_translate("Form", "Drag to move the features, when finished press confirm.")) 53 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemEmbeddedDetectionWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 472 10 | 525 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Model: 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 9 29 | 75 30 | true 31 | 32 | 33 | 34 | Feature Detection 35 | 36 | 37 | 38 | 39 | 40 | 41 | Qt::Vertical 42 | 43 | 44 | 45 | 20 46 | 40 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 0 56 | 0 57 | 58 | 59 | 60 | 61 | 62 | 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | Drag to move the features, when finished press confirm. 71 | 72 | 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemExperimentWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 345 10 | 179 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Name 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Horizontal 34 | 35 | 36 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 37 | 38 | 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 75 48 | true 49 | 50 | 51 | 52 | Experiment 53 | 54 | 55 | 56 | 57 | 58 | 59 | Sample 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | User 70 | 71 | 72 | 73 | 74 | 75 | 76 | Project 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | lineEdit_exp_name 87 | lineEdit_exp_user 88 | lineEdit_exp_sample 89 | lineEdit_exp_project 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemFeatureDetectionUI.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 429 10 | 648 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 12 26 | 75 27 | true 28 | 29 | 30 | 31 | OpenFIBSEM Keypoint Labelling 32 | 33 | 34 | 35 | 36 | 37 | 38 | Edit 39 | 40 | 41 | 42 | 43 | 44 | 45 | Features 46 | 47 | 48 | 49 | 50 | 51 | 52 | Add 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::Vertical 60 | 61 | 62 | 63 | 20 64 | 40 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Instructions 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 81 | 82 | Qt::AlignCenter 83 | 84 | 85 | 999999999 86 | 87 | 88 | 89 | 90 | 91 | 92 | Current Image (Total: N) 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 0 102 | 0 103 | 429 104 | 22 105 | 106 | 107 | 108 | 109 | File 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Load Image Directory 120 | 121 | 122 | 123 | 124 | Load CSV 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemMicroscopeConfigurationWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemMicroscopeConfigurationWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.resize(632, 930) 18 | self.centralwidget = QtWidgets.QWidget(MainWindow) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) 21 | self.gridLayout.setObjectName("gridLayout") 22 | self.label_configuration_title = QtWidgets.QLabel(self.centralwidget) 23 | font = QtGui.QFont() 24 | font.setPointSize(12) 25 | font.setBold(True) 26 | font.setWeight(75) 27 | self.label_configuration_title.setFont(font) 28 | self.label_configuration_title.setObjectName("label_configuration_title") 29 | self.gridLayout.addWidget(self.label_configuration_title, 0, 0, 1, 1) 30 | MainWindow.setCentralWidget(self.centralwidget) 31 | self.menubar = QtWidgets.QMenuBar(MainWindow) 32 | self.menubar.setGeometry(QtCore.QRect(0, 0, 632, 22)) 33 | self.menubar.setObjectName("menubar") 34 | self.menuFile = QtWidgets.QMenu(self.menubar) 35 | self.menuFile.setObjectName("menuFile") 36 | MainWindow.setMenuBar(self.menubar) 37 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 38 | self.statusbar.setObjectName("statusbar") 39 | MainWindow.setStatusBar(self.statusbar) 40 | self.actionLoad_Configuration = QtWidgets.QAction(MainWindow) 41 | self.actionLoad_Configuration.setObjectName("actionLoad_Configuration") 42 | self.actionSave_Configuration = QtWidgets.QAction(MainWindow) 43 | self.actionSave_Configuration.setObjectName("actionSave_Configuration") 44 | self.menuFile.addAction(self.actionLoad_Configuration) 45 | self.menuFile.addAction(self.actionSave_Configuration) 46 | self.menubar.addAction(self.menuFile.menuAction()) 47 | 48 | self.retranslateUi(MainWindow) 49 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 50 | 51 | def retranslateUi(self, MainWindow): 52 | _translate = QtCore.QCoreApplication.translate 53 | MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) 54 | self.label_configuration_title.setText(_translate("MainWindow", "OpenFIBSEM: Microscope Configuration")) 55 | self.menuFile.setTitle(_translate("MainWindow", "File")) 56 | self.actionLoad_Configuration.setText(_translate("MainWindow", "Load Configuration")) 57 | self.actionSave_Configuration.setText(_translate("MainWindow", "Save Configuration")) 58 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemMicroscopeConfigurationWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 632 10 | 930 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 12 23 | 75 24 | true 25 | 26 | 27 | 28 | OpenFIBSEM: Microscope Configuration 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 0 39 | 632 40 | 22 41 | 42 | 43 | 44 | 45 | File 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Load Configuration 56 | 57 | 58 | 59 | 60 | Save Configuration 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemModelTrainingWidge.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Data Path 21 | 22 | 23 | 24 | 25 | 26 | 27 | Num Classes 28 | 29 | 30 | 31 | 32 | 33 | 34 | Encoder 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 10 43 | 44 | 45 | 46 | Training 47 | 48 | 49 | 50 | 51 | 52 | 53 | Label Path 54 | 55 | 56 | 57 | 58 | 59 | 60 | Checkpoint 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Train Model 71 | 72 | 73 | 74 | 75 | 76 | 77 | Info 78 | 79 | 80 | 81 | 82 | 83 | 84 | Qt::Vertical 85 | 86 | 87 | 88 | 20 89 | 40 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSegmentationModelWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemSegmentationModelWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(456, 352) 18 | self.gridLayout = QtWidgets.QGridLayout(Form) 19 | self.gridLayout.setObjectName("gridLayout") 20 | self.checkpoint_seg_button = QtWidgets.QToolButton(Form) 21 | self.checkpoint_seg_button.setObjectName("checkpoint_seg_button") 22 | self.gridLayout.addWidget(self.checkpoint_seg_button, 2, 2, 1, 1) 23 | self.label_model_type = QtWidgets.QLabel(Form) 24 | self.label_model_type.setObjectName("label_model_type") 25 | self.gridLayout.addWidget(self.label_model_type, 1, 0, 1, 1) 26 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 27 | self.gridLayout.addItem(spacerItem, 4, 0, 1, 2) 28 | self.label_header_model = QtWidgets.QLabel(Form) 29 | font = QtGui.QFont() 30 | font.setBold(True) 31 | font.setWeight(75) 32 | self.label_header_model.setFont(font) 33 | self.label_header_model.setObjectName("label_header_model") 34 | self.gridLayout.addWidget(self.label_header_model, 0, 0, 1, 2) 35 | self.label_checkpoint = QtWidgets.QLabel(Form) 36 | self.label_checkpoint.setObjectName("label_checkpoint") 37 | self.gridLayout.addWidget(self.label_checkpoint, 2, 0, 1, 1) 38 | self.comboBox_model_type = QtWidgets.QComboBox(Form) 39 | self.comboBox_model_type.setObjectName("comboBox_model_type") 40 | self.gridLayout.addWidget(self.comboBox_model_type, 1, 1, 1, 1) 41 | self.lineEdit_checkpoint = QtWidgets.QLineEdit(Form) 42 | self.lineEdit_checkpoint.setObjectName("lineEdit_checkpoint") 43 | self.gridLayout.addWidget(self.lineEdit_checkpoint, 2, 1, 1, 1) 44 | self.pushButton_load_model = QtWidgets.QPushButton(Form) 45 | self.pushButton_load_model.setObjectName("pushButton_load_model") 46 | self.gridLayout.addWidget(self.pushButton_load_model, 3, 0, 1, 2) 47 | 48 | self.retranslateUi(Form) 49 | QtCore.QMetaObject.connectSlotsByName(Form) 50 | 51 | def retranslateUi(self, Form): 52 | _translate = QtCore.QCoreApplication.translate 53 | Form.setWindowTitle(_translate("Form", "Form")) 54 | self.checkpoint_seg_button.setText(_translate("Form", "...")) 55 | self.label_model_type.setText(_translate("Form", "Model")) 56 | self.label_header_model.setText(_translate("Form", "Segmentation Model")) 57 | self.label_checkpoint.setText(_translate("Form", "Checkpoint")) 58 | self.pushButton_load_model.setText(_translate("Form", "Load Model")) 59 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSegmentationModelWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 456 10 | 352 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | ... 21 | 22 | 23 | 24 | 25 | 26 | 27 | Model 28 | 29 | 30 | 31 | 32 | 33 | 34 | Qt::Vertical 35 | 36 | 37 | 38 | 20 39 | 40 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 75 49 | true 50 | 51 | 52 | 53 | Segmentation Model 54 | 55 | 56 | 57 | 58 | 59 | 60 | Checkpoint 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Load Model 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSpotBurnWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemSpotBurnWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.11 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(332, 202) 18 | self.gridLayout_2 = QtWidgets.QGridLayout(Form) 19 | self.gridLayout_2.setObjectName("gridLayout_2") 20 | self.label_information = QtWidgets.QLabel(Form) 21 | self.label_information.setText("") 22 | self.label_information.setObjectName("label_information") 23 | self.gridLayout_2.addWidget(self.label_information, 4, 1, 1, 2) 24 | self.label_beam_current = QtWidgets.QLabel(Form) 25 | self.label_beam_current.setObjectName("label_beam_current") 26 | self.gridLayout_2.addWidget(self.label_beam_current, 3, 1, 1, 1) 27 | self.label_exposure_time = QtWidgets.QLabel(Form) 28 | self.label_exposure_time.setObjectName("label_exposure_time") 29 | self.gridLayout_2.addWidget(self.label_exposure_time, 2, 1, 1, 1) 30 | self.doubleSpinBox_exposure_time = QtWidgets.QDoubleSpinBox(Form) 31 | self.doubleSpinBox_exposure_time.setObjectName("doubleSpinBox_exposure_time") 32 | self.gridLayout_2.addWidget(self.doubleSpinBox_exposure_time, 2, 2, 1, 1) 33 | self.comboBox_beam_current = QtWidgets.QComboBox(Form) 34 | self.comboBox_beam_current.setObjectName("comboBox_beam_current") 35 | self.gridLayout_2.addWidget(self.comboBox_beam_current, 3, 2, 1, 1) 36 | self.pushButton_run_spot_burn = QtWidgets.QPushButton(Form) 37 | self.pushButton_run_spot_burn.setObjectName("pushButton_run_spot_burn") 38 | self.gridLayout_2.addWidget(self.pushButton_run_spot_burn, 6, 1, 1, 2) 39 | self.progressBar = QtWidgets.QProgressBar(Form) 40 | self.progressBar.setProperty("value", 24) 41 | self.progressBar.setObjectName("progressBar") 42 | self.gridLayout_2.addWidget(self.progressBar, 5, 1, 1, 2) 43 | self.label_title = QtWidgets.QLabel(Form) 44 | self.label_title.setObjectName("label_title") 45 | self.gridLayout_2.addWidget(self.label_title, 1, 1, 1, 2) 46 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 47 | self.gridLayout_2.addItem(spacerItem, 7, 1, 1, 2) 48 | 49 | self.retranslateUi(Form) 50 | QtCore.QMetaObject.connectSlotsByName(Form) 51 | 52 | def retranslateUi(self, Form): 53 | _translate = QtCore.QCoreApplication.translate 54 | Form.setWindowTitle(_translate("Form", "Form")) 55 | self.label_beam_current.setText(_translate("Form", "Beam Current")) 56 | self.label_exposure_time.setText(_translate("Form", "Exposure Time")) 57 | self.pushButton_run_spot_burn.setText(_translate("Form", "Run Spot Burn")) 58 | self.label_title.setText(_translate("Form", "Spot Burn")) 59 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSpotBurnWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 332 10 | 202 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Beam Current 28 | 29 | 30 | 31 | 32 | 33 | 34 | Exposure Time 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Run Spot Burn 48 | 49 | 50 | 51 | 52 | 53 | 54 | 24 55 | 56 | 57 | 58 | 59 | 60 | 61 | Spot Burn 62 | 63 | 64 | 65 | 66 | 67 | 68 | Qt::Vertical 69 | 70 | 71 | 72 | 20 73 | 40 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSystemSetupWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemSystemSetupWidget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(570, 883) 18 | Form.setMinimumSize(QtCore.QSize(0, 450)) 19 | font = QtGui.QFont() 20 | font.setPointSize(10) 21 | Form.setFont(font) 22 | self.gridLayout = QtWidgets.QGridLayout(Form) 23 | self.gridLayout.setObjectName("gridLayout") 24 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 25 | self.gridLayout.addItem(spacerItem, 4, 0, 1, 3) 26 | self.comboBox_configuration = QtWidgets.QComboBox(Form) 27 | self.comboBox_configuration.setObjectName("comboBox_configuration") 28 | self.gridLayout.addWidget(self.comboBox_configuration, 0, 1, 1, 1) 29 | self.pushButton_connect_to_microscope = QtWidgets.QPushButton(Form) 30 | font = QtGui.QFont() 31 | font.setPointSize(10) 32 | self.pushButton_connect_to_microscope.setFont(font) 33 | self.pushButton_connect_to_microscope.setObjectName("pushButton_connect_to_microscope") 34 | self.gridLayout.addWidget(self.pushButton_connect_to_microscope, 1, 0, 1, 3) 35 | self.toolButton_import_configuration = QtWidgets.QToolButton(Form) 36 | self.toolButton_import_configuration.setObjectName("toolButton_import_configuration") 37 | self.gridLayout.addWidget(self.toolButton_import_configuration, 0, 2, 1, 1) 38 | self.pushButton_apply_configuration = QtWidgets.QPushButton(Form) 39 | font = QtGui.QFont() 40 | font.setPointSize(10) 41 | self.pushButton_apply_configuration.setFont(font) 42 | self.pushButton_apply_configuration.setObjectName("pushButton_apply_configuration") 43 | self.gridLayout.addWidget(self.pushButton_apply_configuration, 3, 0, 1, 3) 44 | self.label_configuration = QtWidgets.QLabel(Form) 45 | self.label_configuration.setObjectName("label_configuration") 46 | self.gridLayout.addWidget(self.label_configuration, 0, 0, 1, 1) 47 | self.label_connection_information = QtWidgets.QLabel(Form) 48 | font = QtGui.QFont() 49 | font.setPointSize(10) 50 | self.label_connection_information.setFont(font) 51 | self.label_connection_information.setObjectName("label_connection_information") 52 | self.gridLayout.addWidget(self.label_connection_information, 5, 0, 1, 3) 53 | 54 | self.retranslateUi(Form) 55 | QtCore.QMetaObject.connectSlotsByName(Form) 56 | 57 | def retranslateUi(self, Form): 58 | _translate = QtCore.QCoreApplication.translate 59 | Form.setWindowTitle(_translate("Form", "Form")) 60 | self.pushButton_connect_to_microscope.setText(_translate("Form", "Connect to Microscope")) 61 | self.toolButton_import_configuration.setText(_translate("Form", "...")) 62 | self.pushButton_apply_configuration.setText(_translate("Form", "Apply Microscope Configuration")) 63 | self.label_configuration.setText(_translate("Form", "Configuration")) 64 | self.label_connection_information.setText(_translate("Form", "No Connected")) 65 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemSystemSetupWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 570 10 | 883 11 | 12 | 13 | 14 | 15 | 0 16 | 450 17 | 18 | 19 | 20 | 21 | 10 22 | 23 | 24 | 25 | Form 26 | 27 | 28 | 29 | 30 | 31 | Qt::Vertical 32 | 33 | 34 | 35 | 20 36 | 40 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 10 49 | 50 | 51 | 52 | Connect to Microscope 53 | 54 | 55 | 56 | 57 | 58 | 59 | ... 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 10 68 | 69 | 70 | 71 | Apply Microscope Configuration 72 | 73 | 74 | 75 | 76 | 77 | 78 | Configuration 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 10 87 | 88 | 89 | 90 | No Connected 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemUI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'FibsemUI.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.resize(378, 531) 18 | self.centralwidget = QtWidgets.QWidget(MainWindow) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) 21 | self.gridLayout.setObjectName("gridLayout") 22 | self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) 23 | self.tabWidget.setDocumentMode(False) 24 | self.tabWidget.setTabsClosable(False) 25 | self.tabWidget.setTabBarAutoHide(False) 26 | self.tabWidget.setObjectName("tabWidget") 27 | self.gridLayout.addWidget(self.tabWidget, 1, 0, 1, 1) 28 | self.label_title = QtWidgets.QLabel(self.centralwidget) 29 | font = QtGui.QFont() 30 | font.setPointSize(16) 31 | font.setBold(True) 32 | font.setWeight(75) 33 | self.label_title.setFont(font) 34 | self.label_title.setObjectName("label_title") 35 | self.gridLayout.addWidget(self.label_title, 0, 0, 1, 1) 36 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 37 | self.gridLayout.addItem(spacerItem, 2, 0, 1, 1) 38 | MainWindow.setCentralWidget(self.centralwidget) 39 | self.menubar = QtWidgets.QMenuBar(MainWindow) 40 | self.menubar.setGeometry(QtCore.QRect(0, 0, 378, 22)) 41 | self.menubar.setObjectName("menubar") 42 | self.menuTools = QtWidgets.QMenu(self.menubar) 43 | self.menuTools.setObjectName("menuTools") 44 | MainWindow.setMenuBar(self.menubar) 45 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 46 | self.statusbar.setObjectName("statusbar") 47 | MainWindow.setStatusBar(self.statusbar) 48 | self.actionCurrent_alignment = QtWidgets.QAction(MainWindow) 49 | self.actionCurrent_alignment.setObjectName("actionCurrent_alignment") 50 | self.actionManipulator_Positions_Calibration = QtWidgets.QAction(MainWindow) 51 | self.actionManipulator_Positions_Calibration.setObjectName("actionManipulator_Positions_Calibration") 52 | self.actionMinimap = QtWidgets.QAction(MainWindow) 53 | self.actionMinimap.setObjectName("actionMinimap") 54 | self.actionOpen_Minimap = QtWidgets.QAction(MainWindow) 55 | self.actionOpen_Minimap.setObjectName("actionOpen_Minimap") 56 | self.actionImage_Viewer = QtWidgets.QAction(MainWindow) 57 | self.actionImage_Viewer.setObjectName("actionImage_Viewer") 58 | self.menuTools.addAction(self.actionManipulator_Positions_Calibration) 59 | self.menuTools.addAction(self.actionOpen_Minimap) 60 | self.menubar.addAction(self.menuTools.menuAction()) 61 | 62 | self.retranslateUi(MainWindow) 63 | self.tabWidget.setCurrentIndex(-1) 64 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 65 | 66 | def retranslateUi(self, MainWindow): 67 | _translate = QtCore.QCoreApplication.translate 68 | MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) 69 | self.label_title.setText(_translate("MainWindow", "OpenFIBSEM")) 70 | self.menuTools.setTitle(_translate("MainWindow", "Tools")) 71 | self.actionCurrent_alignment.setText(_translate("MainWindow", "Beam Current Alignment")) 72 | self.actionManipulator_Positions_Calibration.setText(_translate("MainWindow", "Manipulator Callibration")) 73 | self.actionMinimap.setText(_translate("MainWindow", "Minimap")) 74 | self.actionOpen_Minimap.setText(_translate("MainWindow", "Open Minimap")) 75 | self.actionImage_Viewer.setText(_translate("MainWindow", "Image Viewer")) 76 | -------------------------------------------------------------------------------- /fibsem/ui/qtdesigner_files/FibsemUI.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 378 10 | 531 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | -1 22 | 23 | 24 | false 25 | 26 | 27 | false 28 | 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 16 39 | 75 40 | true 41 | 42 | 43 | 44 | OpenFIBSEM 45 | 46 | 47 | 48 | 49 | 50 | 51 | Qt::Vertical 52 | 53 | 54 | 55 | 20 56 | 40 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 0 68 | 378 69 | 22 70 | 71 | 72 | 73 | 74 | Tools 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Beam Current Alignment 85 | 86 | 87 | 88 | 89 | Manipulator Callibration 90 | 91 | 92 | 93 | 94 | Minimap 95 | 96 | 97 | 98 | 99 | Open Minimap 100 | 101 | 102 | 103 | 104 | Image Viewer 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /fibsem/ui/stylesheets.py: -------------------------------------------------------------------------------- 1 | # style sheets 2 | # TODO: TEMPLATE THIS CSS 3 | GREEN_PUSHBUTTON_STYLE = """ 4 | QPushButton { 5 | background-color: green; 6 | } 7 | QPushButton:hover { 8 | background-color: rgba(0, 255, 0, 125); 9 | }""" 10 | 11 | RED_PUSHBUTTON_STYLE = """ 12 | QPushButton { 13 | background-color: red; 14 | } 15 | QPushButton:hover { 16 | background-color: rgba(255, 0, 0, 125); 17 | }""" 18 | 19 | BLUE_PUSHBUTTON_STYLE = """ 20 | QPushButton { 21 | background-color: blue; 22 | } 23 | QPushButton:hover { 24 | background-color: rgba(0, 0, 255, 125); 25 | }""" 26 | 27 | YELLOW_PUSHBUTTON_STYLE = """ 28 | QPushButton { 29 | background-color: yellow; 30 | } 31 | QPushButton:hover { 32 | background-color: rgba(255, 255, 0, 125); 33 | }""" 34 | 35 | WHITE_PUSHBUTTON_STYLE = """ 36 | QPushButton { 37 | background-color: white; 38 | color: black; 39 | } 40 | QPushButton:hover { 41 | background-color: rgba(255, 255, 255, 125); 42 | }""" 43 | 44 | GRAY_PUSHBUTTON_STYLE = """ 45 | QPushButton { 46 | background-color: gray; 47 | } 48 | QPushButton:hover { 49 | background-color: rgba(125, 125, 125, 125); 50 | }""" 51 | 52 | ORANGE_PUSHBUTTON_STYLE = """ 53 | QPushButton { 54 | background-color: orange; 55 | color: black; 56 | } 57 | QPushButton:hover { 58 | background-color: rgba(255, 125, 0, 125); 59 | }""" 60 | 61 | DISABLED_PUSHBUTTON_STYLE = """ 62 | QPushButton { 63 | background-color: none; 64 | } 65 | """ 66 | 67 | PROGRESS_BAR_GREEN_STYLE = "QProgressBar::chunk {background-color: green;}" 68 | PROGRESS_BAR_BLUE_STYLE = "QProgressBar::chunk {background-color: blue;}" 69 | -------------------------------------------------------------------------------- /fibsem/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/fibsem/util/__init__.py -------------------------------------------------------------------------------- /fibsem/util/filename.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def _get_extension(filename: str) -> str: 4 | if filename.endswith(".ome.tiff"): # special case for OME-TIFF files (double extension) 5 | return ".ome.tiff" 6 | else: 7 | return os.path.splitext(filename)[1] 8 | 9 | def _get_basename(filename: str) -> str: 10 | return filename.removesuffix(_get_extension(filename)) 11 | 12 | def _get_basename_and_extension(filename: str) -> tuple: 13 | return _get_basename(filename), _get_extension(filename) 14 | 15 | def get_unique_filename(filename): 16 | if not os.path.exists(filename): 17 | return filename 18 | 19 | basename, ext = _get_basename_and_extension(filename) 20 | idx = 1 21 | while os.path.exists(filename): 22 | filename = f"{basename}-{idx}{ext}" 23 | idx += 1 24 | 25 | return filename -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: FIBSEM Docs 2 | 3 | theme: 4 | name: "material" 5 | 6 | plugins: 7 | - mkdocstrings 8 | 9 | nav: 10 | - index.md 11 | - started.md 12 | - automation.md 13 | - ml.md 14 | - examples.md 15 | - roadmap.md 16 | - reference.md 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "fibsem" 7 | version = "0.4.1a0" 8 | 9 | description = "a universal api for fibsem control" 10 | authors = [ 11 | {name = "Patrick Cleeve", email = "patrick@openfibsem.org"}, 12 | ] 13 | readme = "README.md" 14 | requires-python = ">=3.8" 15 | license = {text = "MIT License"} 16 | classifiers = [ 17 | "Programming Language :: Python :: 3.8", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | "Framework :: napari", 21 | ] 22 | 23 | dependencies = [ 24 | "tifffile>=2021.7.2", 25 | "numpy>=1.23.5,<2.0.0", 26 | "scipy>=1.10.0", 27 | "opencv-python-headless>=4.7.0.72", # TODO: test headless dependency 28 | "scikit-image>=0.19.3", 29 | "matplotlib>=3.7.0", 30 | "tqdm>=4.65.0", 31 | "pytest>=7.2.2", 32 | "petname>=2.6", 33 | "pandas>=2.0.0", 34 | "pyyaml>=6.0", 35 | "psygnal" 36 | ] 37 | 38 | [project.urls] 39 | Homepage = "https://github.com/DeMarcoLab/fibsem" 40 | "Bug Tracker" = "https://github.com/DeMarcoLab/fibsem/issues" 41 | 42 | [project.optional-dependencies] 43 | ml = [ 44 | "zarr>=2.13.6", 45 | "dask>=2023.3.0", 46 | "torch>=2.0.0,<=2.1.2", 47 | "torchvision>=0.15.1", 48 | "segmentation-models-pytorch>=0.3.2", 49 | "plotly>=5.14.1", 50 | "kaleido==0.2.0", 51 | "transformers>=4.36.2", 52 | ] 53 | ui = [ 54 | "napari>=0.4.17,<=0.5.3", 55 | "pyqt5>=5.15.9", 56 | "matplotlib_scalebar>=0.8.1", 57 | ] 58 | odemis = [ 59 | "pylibtiff" 60 | ] 61 | 62 | reporting = [ 63 | "plotly>=5.14.1", 64 | "reportlab", 65 | "kaleido==0.2.0" 66 | ] 67 | 68 | [project.scripts] 69 | fibsem_ui = "fibsem.ui.FibsemUI:main" 70 | fibsem_label = "fibsem.ui.FibsemLabellingUI:main" 71 | fibsem-generate-config = "fibsem.configuration:gen_config_cli" 72 | fibsem-config-ui = "fibsem.ui.FibsemMicroscopeConfigurationWidget:main" 73 | 74 | [tool.setuptools] 75 | packages = ["fibsem"] 76 | 77 | [tool.setuptools.package-data] 78 | "*" = ["*.yaml"] -------------------------------------------------------------------------------- /requirements-3.8.txt: -------------------------------------------------------------------------------- 1 | # zarr>=2.13.6 2 | # dask>=2023.3.0 3 | tifffile>=2021.7.2 4 | numpy>=1.23.5 5 | scipy>=1.10.0 6 | opencv-python>=4.7.0.72 7 | scikit-image>=0.19.3 8 | matplotlib>=3.7.0 9 | napari>=0.4.17 10 | pyqt5>=5.15.9 11 | torch>=2.0.0 12 | torchvision>=0.15.1 13 | segmentation-models-pytorch>=0.3.2 14 | tqdm>=4.65.0 15 | pytest>=7.2.2 16 | petname>=2.6 17 | # plotly>=5.14.1 18 | # kaleido==0.2.0 19 | matplotlib_scalebar>=0.8.1 20 | # transformers>=4.36.2 -------------------------------------------------------------------------------- /requirements-headless.txt: -------------------------------------------------------------------------------- 1 | tifffile>=2021.7.2 2 | numpy>=1.23.5,<2.0.0 3 | scipy>=1.10.0 4 | opencv-python>=4.7.0.72 5 | scikit-image>=0.19.3 6 | matplotlib>=3.7.0 7 | pytest>=7.2.2 8 | petname>=2.6 9 | pandas>=2.2.3 10 | pyyaml>=6.0 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zarr>=2.13.6 2 | dask>=2023.3.0 3 | tifffile>=2021.7.2 4 | numpy>=1.23.5 5 | scipy>=1.10.0 6 | opencv-python>=4.7.0.72 7 | scikit-image>=0.19.3 8 | matplotlib>=3.7.0 9 | napari>=0.4.17 10 | pyqt5>=5.15.9 11 | torch>=2.0.0 12 | torchvision>=0.15.1 13 | segmentation-models-pytorch>=0.3.2 14 | tqdm>=4.65.0 15 | pytest>=7.2.2 16 | petname>=2.6 17 | plotly>=5.14.1 18 | kaleido==0.2.0 19 | matplotlib_scalebar>=0.8.1 20 | transformers>=4.36.2 -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeMarcoLab/fibsem/abfa2efafaba76dd24756afa5a2bdbe88158226f/scripts/.gitkeep -------------------------------------------------------------------------------- /scripts/compat-notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Python 3.8 Compatibility (Headless)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%load_ext autoreload\n", 17 | "%autoreload 2\n", 18 | "\n", 19 | "\n", 20 | "from fibsem import utils, acquire\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "from fibsem.structures import BeamType\n", 23 | "\n", 24 | "from fibsem.milling.patterning.plotting import draw_milling_patterns\n", 25 | "from fibsem.milling import get_milling_stages\n", 26 | "\n", 27 | "\n", 28 | "PROTOCOL_PATH = \"/home/patrick/github/autolamella/autolamella/protocol/protocol-odemis-on-grid.yaml\"\n", 29 | "\n", 30 | "microscope, settings = utils.setup_session(protocol_path=PROTOCOL_PATH)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "settings.image.hfw = 80e-6\n", 47 | "settings.image.beam_type = BeamType.ELECTRON\n", 48 | "\n", 49 | "image = acquire.acquire_image(microscope, settings.image)\n", 50 | "\n", 51 | "stages = get_milling_stages(\"lamella\", settings.protocol[\"milling\"])\n", 52 | "\n", 53 | "fig = draw_milling_patterns(image, stages)\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "## v0.4.0 Refactor " 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "%load_ext autoreload\n", 77 | "%autoreload 2\n", 78 | "\n", 79 | "# convert milling tasks to milling stages\n", 80 | "import os\n", 81 | "import time\n", 82 | "from fibsem.microscopes.odemis_microscope import add_odemis_path\n", 83 | "add_odemis_path()\n", 84 | "\n", 85 | "from odemis.acq.milling.tasks import load_milling_tasks\n", 86 | "from odemis.acq.milling.tasks import __file__ as milling_tasks_file\n", 87 | "from odemis.acq.milling.openfibsem import run_milling_tasks_openfibsem\n", 88 | "\n", 89 | "MILLING_TASKS_PATH = os.path.join(os.path.dirname(milling_tasks_file), \"milling_tasks.yaml\")\n", 90 | "milling_tasks = load_milling_tasks(MILLING_TASKS_PATH)\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "tasks = list(milling_tasks.values())[:2]\n", 100 | "f = run_milling_tasks_openfibsem(tasks)\n", 101 | "# time.sleep(10)\n", 102 | "# f.cancel()\n", 103 | "f.result()" 104 | ] 105 | } 106 | ], 107 | "metadata": { 108 | "kernelspec": { 109 | "display_name": "fibsem-headless", 110 | "language": "python", 111 | "name": "python3" 112 | }, 113 | "language_info": { 114 | "codemirror_mode": { 115 | "name": "ipython", 116 | "version": 3 117 | }, 118 | "file_extension": ".py", 119 | "mimetype": "text/x-python", 120 | "name": "python", 121 | "nbconvert_exporter": "python", 122 | "pygments_lexer": "ipython3", 123 | "version": "3.9.19" 124 | } 125 | }, 126 | "nbformat": 4, 127 | "nbformat_minor": 2 128 | } 129 | -------------------------------------------------------------------------------- /scripts/convert_to_nnunet_dataset.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from fibsem.segmentation._nnunet import convert_to_nnunet_dataset 3 | 4 | # Convert a fibsem dataset to nnunet format 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser(description="Convert a dataset to nnunet format") 8 | parser.add_argument("--data_path", type=str, help="path to data") 9 | parser.add_argument("--label_path", type=str, help="path to labels") 10 | parser.add_argument("--nnunet_data_path", type=str, help="path to nnunet data") 11 | parser.add_argument( 12 | "--label_map", type=str, help="label map", required=False, default=None 13 | ) 14 | parser.add_argument( 15 | "--filetype", type=str, help="filetype", required=False, default=".tif" 16 | ) 17 | 18 | args = parser.parse_args() 19 | 20 | if args.label_map is not None: 21 | print(args.label_map) 22 | # open text file, read each line, and add to list of labels, remove newline character 23 | with open(args.label_map, "r") as f: 24 | labels = [line.rstrip("\n") for line in f] 25 | args.label_map = labels 26 | 27 | # print(args.data_path) 28 | # print(args.label_path) 29 | # print(args.nnunet_data_path) 30 | # print(args.label_map) 31 | # print(args.filetype) 32 | 33 | # return 34 | convert_to_nnunet_dataset( 35 | data_path=args.data_path, 36 | label_path=args.label_path, 37 | nnunet_data_path=args.nnunet_data_path, 38 | label_map=args.label_map, 39 | filetype=args.filetype, 40 | ) 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /scripts/convert_to_onnx.py: -------------------------------------------------------------------------------- 1 | from fibsem.segmentation.onnx_model import export_model_to_onnx 2 | import argparse 3 | 4 | 5 | def main(): 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument( 9 | "--checkpoint", 10 | type=str, 11 | help="path to openfibsem model checkpoint", 12 | required=True, 13 | ) 14 | parser.add_argument( 15 | "--output", 16 | type=str, 17 | help="path to save onnx model", 18 | required=True, 19 | ) 20 | args = parser.parse_args() 21 | export_model_to_onnx(args.checkpoint, args.output) 22 | 23 | 24 | 25 | 26 | if __name__ == "__main__": 27 | main() -------------------------------------------------------------------------------- /scripts/export_nnunet_checkpoint.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import os 4 | import torch 5 | 6 | import argparse 7 | 8 | 9 | def export_model_checkpoint( 10 | path: str, 11 | checkpoint_path: str = None, 12 | checkpoint_name: str = "model_checkpoint.pth", 13 | ) -> None: 14 | """Save NNUNet model checkpoint as a single .pth file 15 | args: 16 | path: path to the nnunet model directory 17 | 18 | """ 19 | # nnunet model directory structure for ensemble: 20 | # model 21 | # dataset.json 22 | # plans.json 23 | # fold_n: 24 | # checkpoint_best.pth 25 | # checkpoint_final.pth 26 | 27 | # we want to convert it to a single .pth file with the following structure: 28 | # model_checkpoint.pth 29 | # dataset: dataset.json 30 | # plans: plans.json 31 | # fold_n: 32 | # best: checkpoint_best.pth 33 | # final: checkpoint_final.pth 34 | 35 | # this makes it more portable and easier to load 36 | 37 | def load_json(path: str): 38 | with open(path, "r") as f: 39 | return json.load(f) 40 | 41 | # confirm that the path is a nnunet model directory 42 | if not os.path.isdir(path): 43 | raise ValueError(f"{path} is not a directory") 44 | if not os.path.exists(os.path.join(path, "dataset.json")): 45 | raise ValueError(f"{path} does not contain a dataset.json file") 46 | if not os.path.exists(os.path.join(path, "plans.json")): 47 | raise ValueError(f"{path} does not contain a plans.json file") 48 | 49 | print(f"Exporting model checkpoint from {path}...") 50 | 51 | MODEL_CHECKPOINT = {} 52 | 53 | # paths 54 | DATASET_JSON_PATH = os.path.join(path, "dataset.json") 55 | PLAN_JSON_PATH = os.path.join(path, "plans.json") 56 | 57 | # load the dataset and plans 58 | print("Loading dataset and plans configurations...") 59 | MODEL_CHECKPOINT["dataset"] = load_json(DATASET_JSON_PATH) 60 | MODEL_CHECKPOINT["plans"] = load_json(PLAN_JSON_PATH) 61 | 62 | # load the folds 63 | MODEL_CHECKPOINT["folds"] = {} 64 | 65 | # get all the fold directories, 66 | FOLD_DIRS = sorted(glob.glob(os.path.join(path, "fold_*"))) 67 | print(f"Found {len(FOLD_DIRS)} folds...") 68 | for fold_dir in FOLD_DIRS: 69 | fold_name = os.path.basename(fold_dir) 70 | print(f"Processing fold {fold_name}...") 71 | 72 | # load the best/ final checkpoint 73 | BEST_CHECKPOINT_PATH = os.path.join(fold_dir, "checkpoint_best.pth") 74 | FINAL_CHECKPOINT_PATH = os.path.join(fold_dir, "checkpoint_final.pth") 75 | 76 | MODEL_CHECKPOINT["folds"][fold_name] = { 77 | "best": torch.load(BEST_CHECKPOINT_PATH, map_location=torch.device("cpu")), 78 | "final": torch.load( 79 | FINAL_CHECKPOINT_PATH, map_location=torch.device("cpu") 80 | ), 81 | } 82 | 83 | # save as single torch checkpoint 84 | if checkpoint_path is None: 85 | checkpoint_path = os.path.join(path, checkpoint_name) 86 | torch.save(MODEL_CHECKPOINT, checkpoint_path) 87 | print(f"Saved model checkpoint to {checkpoint_path}") 88 | 89 | 90 | def main(): 91 | parser = argparse.ArgumentParser( 92 | description="Export nnunet model checkpoint as a single .pth file" 93 | ) 94 | parser.add_argument("--path", type=str, help="path to nnunet model directory") 95 | parser.add_argument( 96 | "--checkpoint_path", 97 | type=str, 98 | help="path to save the checkpoint", 99 | required=False, 100 | default=None, 101 | ) 102 | parser.add_argument( 103 | "--checkpoint_name", 104 | type=str, 105 | help="name of the checkpoint", 106 | required=False, 107 | default="model_checkpoint.pth", 108 | ) 109 | 110 | args = parser.parse_args() 111 | 112 | export_model_checkpoint( 113 | path=args.path, 114 | checkpoint_path=args.checkpoint_path, 115 | checkpoint_name=args.checkpoint_name, 116 | ) 117 | 118 | 119 | if __name__ == "__main__": 120 | main() 121 | -------------------------------------------------------------------------------- /scripts/generate_segmentation_objects.py: -------------------------------------------------------------------------------- 1 | # 2 | import argparse 3 | import os 4 | 5 | from fibsem.detection.detection import generate_segmentation_objects 6 | 7 | def main(): 8 | 9 | parser = argparse.ArgumentParser("Generate segmentation objects from segmentation labels") 10 | parser.add_argument("--data_path", type=str, help="Path to data directory") 11 | parser.add_argument("--labels_path", type=str, help="Path to labels directory") 12 | parser.add_argument("--dataset_json_path", type=str, default=None, help="Path to save dataset json") 13 | parser.add_argument("--min_pixels", type=int, default=100, help="Minimum number of pixels for an object to be considered") 14 | 15 | args = parser.parse_args() 16 | 17 | if args.dataset_json_path is None: 18 | args.dataset_json_path = os.path.join(args.data_path, "data.json") 19 | 20 | generate_segmentation_objects( 21 | data_path=args.data_path, 22 | labels_path=args.labels_path, 23 | dataset_json_path=args.dataset_json_path, 24 | min_pixels=args.min_pixels, 25 | save=True 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | main() -------------------------------------------------------------------------------- /scripts/install.bat: -------------------------------------------------------------------------------- 1 | CALL conda.bat create -n fibsem python=3.9 pip 2 | CALL activate fibsem 3 | pip install -e . 4 | python shortcut.py -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install fibsem using pip 4 | pip install -e . 5 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/onnx_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### ONNX Windowed Model Integration" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%load_ext autoreload\n", 17 | "%autoreload 2\n", 18 | "import glob\n", 19 | "import os\n", 20 | "import numpy as np\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "import PIL.Image\n", 23 | "\n", 24 | "from fibsem.segmentation.model import load_model\n", 25 | "from fibsem.structures import FibsemImage\n", 26 | "\n", 27 | "# image filenames\n", 28 | "PATH = \"example_imgs/input\"\n", 29 | "filenames = glob.glob(PATH + \"/*.jpeg\")\n", 30 | "\n", 31 | "# PATH = \"/home/patrick/github/data/autolamella-paper/model-development/train/waffle/test\"\n", 32 | "# filenames = glob.glob(PATH + \"/*.tif\")\n", 33 | "\n", 34 | "# load model\n", 35 | "MODEL_PATH = \"ppliteseg_fibsem_07022024_512x512_128k.onnx\"\n", 36 | "model = load_model(checkpoint=MODEL_PATH)\n", 37 | "\n", 38 | "os.makedirs(\"example_imgs/output/test\", exist_ok=True)\n", 39 | "\n", 40 | "for i, filename in enumerate(filenames):\n", 41 | " print(f\"Processing {i+1}/{len(filenames)}: {filename}\")\n", 42 | "\n", 43 | " # load image\n", 44 | " if \"tif\" in filename:\n", 45 | " image = FibsemImage.load(filename)\n", 46 | " else:\n", 47 | " image = FibsemImage(data=np.asarray(PIL.Image.open(filename)))\n", 48 | " \n", 49 | " # inference\n", 50 | " rgb = model.inference(image.data)\n", 51 | "\n", 52 | " fig = plt.figure(figsize=(10, 10))\n", 53 | " plt.title(f\"Predicted: {os.path.basename(filename)}\", fontsize=10)\n", 54 | " plt.imshow(image.data, cmap=\"gray\")\n", 55 | " plt.imshow(rgb, alpha=0.5)\n", 56 | " plt.axis(\"off\")\n", 57 | " plt.show()\n", 58 | "\n", 59 | " # save figure\n", 60 | " fig.savefig(f\"example_imgs/output/test/{os.path.basename(filename)}\".replace(\".tif\", \".png\"), bbox_inches=\"tight\")\n", 61 | " plt.close(fig)" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [] 77 | } 78 | ], 79 | "metadata": { 80 | "kernelspec": { 81 | "display_name": "fibsem", 82 | "language": "python", 83 | "name": "python3" 84 | }, 85 | "language_info": { 86 | "codemirror_mode": { 87 | "name": "ipython", 88 | "version": 3 89 | }, 90 | "file_extension": ".py", 91 | "mimetype": "text/x-python", 92 | "name": "python", 93 | "nbconvert_exporter": "python", 94 | "pygments_lexer": "ipython3", 95 | "version": "3.9.18" 96 | } 97 | }, 98 | "nbformat": 4, 99 | "nbformat_minor": 2 100 | } 101 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | fibsem_ui -------------------------------------------------------------------------------- /scripts/run_ui.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | CALL conda.bat activate fibsem 3 | fibsem_ui 4 | pause -------------------------------------------------------------------------------- /scripts/shortcut.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import win32com.client 4 | from fibsem.config import BASE_PATH 5 | # Get the user's desktop folder 6 | desktop = os.path.join(os.path.expanduser('~'), 'Desktop') 7 | 8 | # Specify the target file (e.g., a script or program you want to create a shortcut for) 9 | target_file = os.path.join(BASE_PATH, "run_ui.bat") # Replace with your program's path 10 | 11 | # Create a shortcut name 12 | shortcut_name = 'FibsemUI.lnk' 13 | 14 | # Create a shortcut on the desktop 15 | shell = win32com.client.Dispatch("WScript.Shell") 16 | shortcut = shell.CreateShortCut(os.path.join(desktop, shortcut_name)) 17 | shortcut.TargetPath = target_file 18 | shortcut.save() 19 | 20 | print(f"Shortcut to '{target_file}' created on the desktop as '{shortcut_name}'") -------------------------------------------------------------------------------- /scripts/test-alignment-script.py: -------------------------------------------------------------------------------- 1 | 2 | from fibsem import utils, acquire, alignment 3 | from fibsem.structures import BeamType, FibsemRectangle 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | def main(): 9 | microscope, settings = utils.setup_session() 10 | 11 | # parameters 12 | beam_type = BeamType.ION 13 | scan_rotation = 180 # deg 14 | 15 | # set scan rotation 16 | microscope.set("scan_rotation", np.radians(scan_rotation), beam_type=beam_type) 17 | 18 | # reset beam shifts 19 | microscope.reset_beam_shifts() 20 | 21 | # acquire FIB Image 22 | settings.image.hfw = 150e-6 23 | settings.image.beam_type = beam_type 24 | settings.image.reduced_area = FibsemRectangle(0.25, 0.25, 0.5, 0.5) 25 | image1 = acquire.acquire_image(microscope=microscope, settings=settings.image) 26 | 27 | # shift beam shift 28 | microscope.beam_shift(dx=10e-6, dy=5e-6, beam_type=beam_type) 29 | 30 | # acquire FIB image 31 | image2 = acquire.acquire_image(microscope=microscope, settings=settings.image) 32 | 33 | # align beam shift 34 | alignment.multi_step_alignment_v2(microscope, ref_image=image1, beam_type=beam_type, use_autocontrast=True) 35 | 36 | # acquire FIB Image 37 | image3 = acquire.acquire_image(microscope=microscope, settings=settings.image) 38 | 39 | # plot 40 | fig, ax = plt.subplots(1, 3, figsize=(15, 7)) 41 | fig.suptitle(f"Test Alignment: {beam_type.name} - Scan Rotation: {scan_rotation}deg") 42 | ax[0].imshow(image1.data, cmap='gray') 43 | ax[0].set_title('Previous Image') 44 | ax[0].axis('off') 45 | ax[1].imshow(image2.data, cmap='gray') 46 | ax[1].set_title('Shifted Image') 47 | ax[1].axis('off') 48 | ax[2].imshow(image3.data, cmap='gray') 49 | ax[2].set_title('Aligned Image') 50 | ax[2].axis('off') 51 | 52 | # plot center crosshair 53 | ax[0].plot([image1.data.shape[1] // 2, image1.data.shape[1] // 2], [0, image1.data.shape[0]], color='yellow', lw=1) 54 | ax[0].plot([0, image1.data.shape[1]], [image1.data.shape[0] // 2, image1.data.shape[0] // 2], color='yellow', lw=1) 55 | ax[1].plot([image2.data.shape[1] // 2, image2.data.shape[1] // 2], [0, image2.data.shape[0]], color='yellow', lw=1) 56 | ax[1].plot([0, image2.data.shape[1]], [image2.data.shape[0] // 2, image2.data.shape[0] // 2], color='yellow', lw=1) 57 | ax[2].plot([image3.data.shape[1] // 2, image3.data.shape[1] // 2], [0, image3.data.shape[0]], color='yellow', lw=1) 58 | ax[2].plot([0, image3.data.shape[1]], [image3.data.shape[0] // 2, image3.data.shape[0] // 2], color='yellow', lw=1) 59 | plt.show() 60 | 61 | 62 | if __name__ == "__main__": 63 | main() -------------------------------------------------------------------------------- /scripts/test_installation.py: -------------------------------------------------------------------------------- 1 | from fibsem.microscope import THERMO_API_AVAILABLE 2 | from fibsem.microscopes.tescan import TESCAN_API_AVAILABLE 3 | 4 | def main(): 5 | 6 | # OpenFIBSEM API 7 | print(f"\n\nOpenFIBSEM API:\n") 8 | try: 9 | import fibsem 10 | FIBSEM_AVAILABLE = True 11 | except ImportError: 12 | FIBSEM_AVAILABLE = False 13 | if FIBSEM_AVAILABLE: 14 | print(f"OpenFIBSEM v{fibsem.__version__}") 15 | print(f"Installed at: {fibsem.__path__}") 16 | 17 | print(f"-" * 80) 18 | 19 | print(f"Applications:\n") 20 | try: 21 | import autolamella 22 | AUTOLAMELLA_AVAILABLE = True 23 | except ImportError: 24 | AUTOLAMELLA_AVAILABLE = False 25 | if AUTOLAMELLA_AVAILABLE: 26 | print(f"AutoLamella v{autolamella.__version__}") 27 | print(f"Installed at: {autolamella.__path__}") 28 | 29 | try: 30 | import salami 31 | SALAMI_AVAILABLE = True 32 | except ImportError: 33 | SALAMI_AVAILABLE = False 34 | if SALAMI_AVAILABLE: 35 | print(f"SALAMI v{salami.__version__}") 36 | print(f"Installed at: {salami.__path__}") 37 | print(f"-" * 80) 38 | 39 | # Hardware APIs 40 | print(f"Hardware APIs:\n") 41 | 42 | # Thermo Fisher API 43 | print(f"ThermoFisher API {'Available' if THERMO_API_AVAILABLE else 'Not Available'}") 44 | if THERMO_API_AVAILABLE: 45 | from fibsem.microscope import version as autoscript_version 46 | print(f"AutoScript v{autoscript_version}") 47 | print(f"-" * 80) 48 | 49 | # Tescan API 50 | print(f"Tescan API {'Available' if TESCAN_API_AVAILABLE else 'Not Available'}") 51 | if TESCAN_API_AVAILABLE: 52 | from fibsem.microscopes.tescan import tescanautomation 53 | print(f"TescanAutomation v{tescanautomation.__version__}") 54 | 55 | if __name__ == "__main__": 56 | main() -------------------------------------------------------------------------------- /tests/milling/test_base.py: -------------------------------------------------------------------------------- 1 | from fibsem.milling.base import ( 2 | FibsemMillingStage, 3 | MillingStrategy, 4 | get_strategy, 5 | ) 6 | from fibsem.milling.patterning.patterns2 import RectanglePattern 7 | from fibsem.milling.strategy import DEFAULT_STRATEGY, get_strategies 8 | from fibsem.structures import FibsemMillingSettings, MillingAlignment, Point 9 | 10 | def test_milling_stage(): 11 | 12 | milling_settings = FibsemMillingSettings() 13 | pattern = RectanglePattern(width=10, height=5, depth=1) 14 | strategy = get_strategy("Standard") 15 | alignment = MillingAlignment(enabled=True) 16 | 17 | # Create a FibsemMillingStage instance 18 | milling_stage = FibsemMillingStage( 19 | name="Test Stage", 20 | num=1, 21 | milling=milling_settings, 22 | pattern=pattern, 23 | strategy=strategy, 24 | alignment=alignment, 25 | ) 26 | 27 | # Check the attributes 28 | assert milling_stage.name == "Test Stage" 29 | assert milling_stage.num == 1 30 | assert isinstance(milling_stage.milling, FibsemMillingSettings) 31 | assert isinstance(milling_stage.pattern, RectanglePattern) 32 | assert isinstance(milling_stage.strategy, MillingStrategy) 33 | assert isinstance(milling_stage.alignment, MillingAlignment) 34 | assert milling_stage.pattern.width == 10 35 | assert milling_stage.pattern.height == 5 36 | assert milling_stage.pattern.depth == 1 37 | assert milling_stage.strategy.name == DEFAULT_STRATEGY.name 38 | assert milling_stage.alignment.enabled is True 39 | 40 | # test to_dict method 41 | dict_repr = milling_stage.to_dict() 42 | assert dict_repr["name"] == "Test Stage" 43 | assert dict_repr["num"] == 1 44 | assert isinstance(dict_repr["milling"], dict) 45 | assert isinstance(dict_repr["pattern"], dict) 46 | assert isinstance(dict_repr["strategy"], dict) 47 | assert isinstance(dict_repr["alignment"], dict) 48 | 49 | # test from_dict method 50 | dict_repr = { 51 | "name": "Test Stage", 52 | "num": 1, 53 | "milling": milling_settings.to_dict(), 54 | "pattern": pattern.to_dict(), 55 | "strategy": strategy.to_dict(), 56 | "alignment": alignment.to_dict(), 57 | } 58 | new_milling_stage = FibsemMillingStage.from_dict(dict_repr) 59 | 60 | 61 | assert new_milling_stage.name == "Test Stage" 62 | assert new_milling_stage.num == 1 63 | assert isinstance(new_milling_stage.milling, FibsemMillingSettings) 64 | assert isinstance(new_milling_stage.pattern, RectanglePattern) 65 | assert isinstance(new_milling_stage.strategy, MillingStrategy) 66 | assert isinstance(new_milling_stage.alignment, MillingAlignment) 67 | assert new_milling_stage.imaging.path is None 68 | 69 | def test_get_strategy(): 70 | # Test with default strategy 71 | strategy = get_strategy() 72 | assert isinstance(strategy, MillingStrategy) 73 | assert strategy.name == DEFAULT_STRATEGY.name 74 | 75 | # Test with a specific strategy name 76 | strategy_name = "Standard" 77 | strategy = get_strategy(name=strategy_name) 78 | assert isinstance(strategy, MillingStrategy) 79 | assert strategy.name == strategy_name 80 | 81 | # Test with a non-existent strategy name 82 | strategy = get_strategy(name="NonExistentStrategy") 83 | assert isinstance(strategy, MillingStrategy) 84 | assert strategy.name == DEFAULT_STRATEGY.name 85 | -------------------------------------------------------------------------------- /tests/test_acquire.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from fibsem import acquire, utils 5 | from fibsem.structures import ( 6 | FibsemImage, 7 | FibsemRectangle, 8 | ) 9 | 10 | def test_reduced_area_acquisition(): 11 | """Test the reduced area acquisition functionality of the acquire module.""" 12 | # setup a demo microscope session 13 | microscope, settings = utils.setup_session(manufacturer="Demo") 14 | 15 | resolution = settings.image.resolution 16 | 17 | # acquire a full frame image 18 | image = acquire.acquire_image(microscope, settings.image) 19 | assert isinstance(image, FibsemImage) 20 | assert image.data.shape == (resolution[1], resolution[0]) 21 | 22 | # acquire a reduced area image 23 | settings.image.reduced_area = FibsemRectangle(0.25, 0.25, 0.5, 0.5) 24 | image = acquire.acquire_image(microscope, settings.image) 25 | assert isinstance(image, FibsemImage) 26 | assert image.data.shape == (resolution[1] // 2, resolution[0] // 2) -------------------------------------------------------------------------------- /tests/test_alignment.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from fibsem import utils, acquire, alignment 3 | import random 4 | import numpy as np 5 | 6 | def test_align_from_crosscorrelation(): 7 | 8 | microscope, settings = utils.setup_session(debug=False) 9 | 10 | # create random images 11 | ref_image = acquire.acquire_image(microscope, settings.image) 12 | new_image = acquire.acquire_image(microscope, settings.image) 13 | 14 | ref_image.data[:] = 0 15 | new_image.data[:] = 0 16 | 17 | # crop a random square out 18 | w = h = 150 19 | offset_limits = (-50, 50) 20 | x = random.randint( 21 | 0 - offset_limits[0], ref_image.data.shape[1] - offset_limits[1] - 2 * w 22 | ) 23 | y = random.randint( 24 | 0 - offset_limits[0], ref_image.data.shape[0] - offset_limits[1] - 2 * h 25 | ) 26 | ref_image.data[y:y+h, x:x+w] = 255 27 | 28 | # new image should be offset by 250 pixels in x and y 29 | offset = random.randint(offset_limits[0], offset_limits[1]) 30 | new_image.data[y+offset:y+h+offset, x+offset:x+w+offset] = 255 31 | 32 | dx, dy, xcorr = alignment.shift_from_crosscorrelation( 33 | ref_image, new_image, lowpass=50, highpass=4, sigma=5, use_rect_mask=True 34 | ) 35 | 36 | # write a test case with pytest for this case 37 | # test that the shift is within 1 pixel of the offset 38 | 39 | pixel_size = ref_image.metadata.pixel_size.x 40 | assert np.isclose(dx, offset*pixel_size, atol=pixel_size), f"dx: {dx}, offset: {offset*pixel_size}" 41 | assert np.isclose(dy, offset*pixel_size, atol=pixel_size), f"dy: {dy}, offset: {offset*pixel_size}" -------------------------------------------------------------------------------- /tests/test_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_example(): 5 | 6 | assert 1 + 1 == 2 -------------------------------------------------------------------------------- /tests/test_microscope.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from fibsem import utils 4 | from fibsem.structures import BeamType 5 | 6 | 7 | def test_microscope(): 8 | """Test get/set microscope functions.""" 9 | 10 | microscope, settings = utils.setup_session(manufacturer="Demo") 11 | 12 | hfw = 150e-6 13 | microscope.set_field_of_view(hfw, BeamType.ELECTRON) 14 | assert microscope.get_field_of_view(BeamType.ELECTRON) == hfw 15 | 16 | beam_current = 1e-9 17 | microscope.set_beam_current(beam_current, BeamType.ION) 18 | assert microscope.get_beam_current(BeamType.ION) == beam_current -------------------------------------------------------------------------------- /tests/test_movement.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from fibsem import movement 5 | 6 | def test_angle_difference(): 7 | assert np.isclose(movement.angle_difference(np.deg2rad(0), np.deg2rad(0)), 0) 8 | assert np.isclose(movement.angle_difference(np.deg2rad(0), np.deg2rad(360)), 0) 9 | assert np.isclose(movement.angle_difference(np.deg2rad(0), np.deg2rad(180)), np.pi) 10 | assert np.isclose(movement.angle_difference(np.deg2rad(2), np.deg2rad(358)), np.deg2rad(4)) 11 | assert np.isclose(movement.angle_difference(np.deg2rad(-45), np.deg2rad(45)), np.pi / 2) 12 | assert np.isclose(movement.angle_difference(np.deg2rad(-360), np.deg2rad(360)), 0) 13 | assert np.isclose(movement.angle_difference(-4*np.pi, 4*np.pi), 0) 14 | 15 | def test_rotation_angle_is_larger(): 16 | assert movement.rotation_angle_is_larger(np.deg2rad(0), np.deg2rad(0)) == False 17 | assert movement.rotation_angle_is_larger(np.deg2rad(0), np.deg2rad(360)) == False 18 | assert movement.rotation_angle_is_larger(np.deg2rad(-45), np.deg2rad(45)) == False 19 | assert movement.rotation_angle_is_larger(np.deg2rad(0), np.deg2rad(180)) == True 20 | assert movement.rotation_angle_is_larger(np.deg2rad(-90), np.deg2rad(90)) == True 21 | assert movement.rotation_angle_is_larger(np.deg2rad(0), np.deg2rad(720)) == False 22 | 23 | 24 | def test_rotation_angle_is_smaller(): 25 | assert movement.rotation_angle_is_smaller(np.deg2rad(0), np.deg2rad(0), 5) == True 26 | assert movement.rotation_angle_is_smaller(np.deg2rad(0), np.deg2rad(360), 5) == True 27 | assert movement.rotation_angle_is_smaller(np.deg2rad(0), np.deg2rad(180)) == False 28 | assert movement.rotation_angle_is_smaller(np.deg2rad(-90), np.deg2rad(90), 180) == False 29 | assert movement.rotation_angle_is_smaller(np.deg2rad(0), np.deg2rad(-360), 1) == True 30 | -------------------------------------------------------------------------------- /tests/test_tescan.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_tescan(): 5 | 6 | assert True --------------------------------------------------------------------------------