├── pynta ├── model │ ├── __init__.py │ ├── daqs │ │ ├── __init__.py │ │ ├── skeleton.py │ │ └── daq_dummy.py │ ├── cameras │ │ ├── __init__.py │ │ ├── exceptions.py │ │ ├── decorators.py │ │ └── psi.py │ ├── experiment │ │ ├── dispertech │ │ │ ├── __init__.py │ │ │ ├── util.py │ │ │ ├── database.py │ │ │ └── fiber_tracking.py │ │ ├── nanoparticle_tracking │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── waterfall_worker.py │ │ │ ├── motor_test.py │ │ │ └── decorators.py │ │ ├── __init__.py │ │ ├── config.py │ │ └── subscriber.py │ ├── exceptions.py │ └── motors │ │ └── arduino_base.py ├── tests │ ├── __init__.py │ ├── test_models.py │ ├── test_examples.py │ ├── test_controllers.py │ └── test_zmq.py ├── controller │ ├── __init__.py │ ├── data_sources │ │ └── __init__.py │ └── devices │ │ ├── arduino │ │ ├── __init__.py │ │ ├── arduino.py │ │ └── arduino_driver │ │ │ └── arduino_driver.ino │ │ ├── hamamatsu │ │ └── __init__.py │ │ ├── keysight │ │ └── __init__.py │ │ ├── photonicscience │ │ └── __init__.py │ │ └── __init__.py ├── exceptions │ ├── __init__.py │ └── exceptions.py ├── view │ ├── GUI │ │ ├── old │ │ │ ├── __init__.py │ │ │ ├── Monitor │ │ │ │ ├── __init__.py │ │ │ │ ├── resources.xml │ │ │ │ ├── clearQueueThread.py │ │ │ │ ├── crossCut.py │ │ │ │ ├── popOut.py │ │ │ │ ├── specialTaskTrack.py │ │ │ │ ├── LocateParticle.py │ │ │ │ └── cameraViewer.py │ │ │ ├── waterfallWidget.py │ │ │ ├── workerThread.py │ │ │ └── trajectoryWidget.py │ │ ├── Icons │ │ │ ├── arrow_left.png │ │ │ ├── arrow_right.png │ │ │ ├── arrow_top.png │ │ │ ├── arrow_bottom.png │ │ │ ├── LICENSE.txt │ │ │ ├── science-and-fiction_upload.svg │ │ │ ├── pictogram_save.svg │ │ │ ├── pictogram_crop.svg │ │ │ ├── pictogram_close.svg │ │ │ ├── science-and-fiction_capture.svg │ │ │ ├── pictogram_link.svg │ │ │ ├── pictogram_open.svg │ │ │ ├── duotone_edit.svg │ │ │ ├── hatch_download.svg │ │ │ ├── pictogram_play.svg │ │ │ ├── duotone_chart.svg │ │ │ ├── pictogram_record.svg │ │ │ ├── avocado_calibrate.svg │ │ │ ├── hatch_enlarge.svg │ │ │ └── hatch_ask.svg │ │ ├── histogram_tracks_widget.py │ │ ├── resources.qrc │ │ ├── tracks_widget.py │ │ ├── camera_focusing.py │ │ ├── histogram_widget.py │ │ ├── designer │ │ │ └── histogram_tracks.ui │ │ ├── __init__.py │ │ └── config_tracking_widget.py │ ├── __init__.py │ ├── subscriber_thread.py │ └── main.py ├── tools │ ├── __init__.py │ └── worker_thread.py ├── util │ ├── __init__.py │ ├── importer.py │ ├── circular_buffer.py │ ├── log.py │ └── example_config.yml ├── __init__.py ├── config.py └── __main__.py ├── docs ├── list_todo.rst ├── media │ ├── screenshot_01.png │ ├── screenshot_snap.png │ ├── screenshot_toolbar.png │ ├── screenshot_free_run.png │ ├── screenshot_particles.png │ ├── screenshot_save_image.png │ ├── screenshot_tracking.png │ └── example_config.yml ├── _static │ ├── screenshot_01.png │ ├── screenshot_snap.png │ ├── screenshot_free_run.png │ ├── screenshot_toolbar.png │ ├── screenshot_tracking.png │ ├── screenshot_particles.png │ ├── screenshot_save_image.png │ └── example_config.yml ├── developers │ ├── model │ │ ├── cameras │ │ │ ├── psi.rst │ │ │ ├── basler.rst │ │ │ ├── hamamatsu.rst │ │ │ ├── base_camera.rst │ │ │ ├── dummy_camera.rst │ │ │ ├── simulate_brownian.rst │ │ │ └── index.rst │ │ ├── experiments │ │ │ ├── fiber_tracking.rst │ │ │ ├── np_tracking.rst │ │ │ └── index.rst │ │ ├── daqs │ │ │ └── index.rst │ │ └── index.rst │ ├── pynta.exceptions.rst │ ├── controller │ │ ├── devices │ │ │ ├── hamamatsu.rst │ │ │ ├── photonicscience.rst │ │ │ ├── keysight.rst │ │ │ └── index.rst │ │ └── index.rst │ ├── pynta.rst │ ├── view │ │ ├── index.rst │ │ └── GUI.rst │ ├── pynta.tools.rst │ ├── pynta.util.rst │ └── pynta.tests.rst ├── contribute_codebase.rst ├── Makefile ├── make.bat ├── requirements_docs.txt ├── making_virtual_environment.rst ├── example_config.rst ├── index.rst.bak ├── index.rst ├── installing.rst └── getting_started.rst ├── AUTHORS ├── requirements.txt ├── .gitignore ├── MANIFEST.in ├── examples ├── simple_mp.py ├── test_tracking_speed.py ├── start_motor_example.py ├── window_with_logging.py ├── start_nanocet.py ├── test_zmq.py ├── config │ ├── nanocet.yml │ └── dispertech.yml ├── test_basler.ipynb └── testing_basler.py ├── .editorconfig ├── requirements_docs.txt ├── setup.py ├── environment.yml ├── environment_docs.yml └── conda_pkgs.txt /pynta/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/model/daqs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/model/cameras/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/data_sources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/devices/arduino/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/devices/hamamatsu/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/devices/keysight/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/model/experiment/dispertech/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/controller/devices/photonicscience/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/model/experiment/nanoparticle_tracking/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pynta/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .worker_thread import WorkerThread -------------------------------------------------------------------------------- /pynta/exceptions/exceptions.py: -------------------------------------------------------------------------------- 1 | class PublisherNotStarted(Exception): 2 | pass -------------------------------------------------------------------------------- /docs/list_todo.rst: -------------------------------------------------------------------------------- 1 | .. _todo: 2 | 3 | List of Todo's 4 | ============== 5 | 6 | .. todolist:: -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Authors 2 | ------- 3 | 4 | Aquiles Carattino 5 | Sanli Faez 6 | -------------------------------------------------------------------------------- /docs/media/screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_01.png -------------------------------------------------------------------------------- /docs/_static/screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_01.png -------------------------------------------------------------------------------- /docs/media/screenshot_snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_snap.png -------------------------------------------------------------------------------- /docs/_static/screenshot_snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_snap.png -------------------------------------------------------------------------------- /docs/media/screenshot_toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_toolbar.png -------------------------------------------------------------------------------- /docs/_static/screenshot_free_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_free_run.png -------------------------------------------------------------------------------- /docs/_static/screenshot_toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_toolbar.png -------------------------------------------------------------------------------- /docs/_static/screenshot_tracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_tracking.png -------------------------------------------------------------------------------- /docs/media/screenshot_free_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_free_run.png -------------------------------------------------------------------------------- /docs/media/screenshot_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_particles.png -------------------------------------------------------------------------------- /docs/media/screenshot_save_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_save_image.png -------------------------------------------------------------------------------- /docs/media/screenshot_tracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/media/screenshot_tracking.png -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/pynta/view/GUI/Icons/arrow_left.png -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/pynta/view/GUI/Icons/arrow_right.png -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/arrow_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/pynta/view/GUI/Icons/arrow_top.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | scipy 3 | trackpy 4 | h5py 5 | numpy 6 | sphinx-rtd-theme 7 | sphinx 8 | pyqt5 9 | pyqtgraph -------------------------------------------------------------------------------- /docs/_static/screenshot_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_particles.png -------------------------------------------------------------------------------- /docs/_static/screenshot_save_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/docs/_static/screenshot_save_image.png -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/arrow_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoepics/pynta/HEAD/pynta/view/GUI/Icons/arrow_bottom.png -------------------------------------------------------------------------------- /pynta/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .log import get_logger, log_to_file, log_to_screen 2 | 3 | __all__ = ['get_logger', 'log_to_file', 'log_to_screen'] -------------------------------------------------------------------------------- /pynta/model/exceptions.py: -------------------------------------------------------------------------------- 1 | class ModelException(Exception): 2 | pass 3 | 4 | 5 | class OutOfRange(ModelException): 6 | pass 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/psi.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.psi 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /pynta/util/importer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def from_here(*args): 5 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), *args) 6 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/basler.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.basler 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/hamamatsu.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.hamamatsu 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/base_camera.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.base_camera 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/dummy_camera.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.dummy_camera 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/simulate_brownian.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.cameras.simulate_brownian 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/pynta.exceptions.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | .. automodule:: pynta.exceptions.exceptions 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/developers/controller/devices/hamamatsu.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.controller.devices.hamamatsu.hamamatsu_camera 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/controller/devices/photonicscience.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.controller.devices.photonicscience.scmoscam 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/model/experiments/fiber_tracking.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.experiment.dispertech.fiber_tracking 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/contribute_codebase.rst: -------------------------------------------------------------------------------- 1 | .. _improving: 2 | 3 | How to Contribute Code 4 | ====================== 5 | If you want to collaborate with PyNTA, you can start by reading the :ref:`for_developers`. -------------------------------------------------------------------------------- /docs/developers/model/experiments/np_tracking.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.model.experiment.nanoparticle_tracking.np_tracking 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | -------------------------------------------------------------------------------- /docs/developers/controller/devices/keysight.rst: -------------------------------------------------------------------------------- 1 | Keysight 2 | ======== 3 | 4 | .. automodule:: pynta.controller.devices.keysight.infiniivision 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject 3 | .pyc 4 | .settings/ 5 | .log 6 | __pycache__/ 7 | myenv 8 | *.exe 9 | *.pyc 10 | .idea 11 | build/ 12 | pynta.egg-info 13 | dist/ 14 | venv/ 15 | *.egg-info/ 16 | _build/ 17 | .ipynb_checkpoints/ 18 | -------------------------------------------------------------------------------- /pynta/model/cameras/exceptions.py: -------------------------------------------------------------------------------- 1 | from pynta.model.exceptions import ModelException 2 | 3 | class CameraException(ModelException): 4 | pass 5 | 6 | class CameraNotFound(CameraException): 7 | pass 8 | 9 | class WrongCameraState(CameraException): 10 | pass -------------------------------------------------------------------------------- /docs/developers/controller/index.rst: -------------------------------------------------------------------------------- 1 | Pynta Controllers 2 | ================= 3 | 4 | .. automodule:: pynta.controller 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | :caption: Contents 12 | 13 | devices/index 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include README.md 3 | include setup.py 4 | include LICENSE 5 | include AUTHORS 6 | include examples/* 7 | include pynta/view/GUI/Icons/* 8 | include pynta/view/GUI/designer/* 9 | include doc/ 10 | include pynta/util/example_config.yml 11 | exlude pynta/view/old/ 12 | -------------------------------------------------------------------------------- /docs/developers/pynta.rst: -------------------------------------------------------------------------------- 1 | .. _PyNTA: 2 | 3 | PyNTA API 4 | ========= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | controller/index 11 | model/index 12 | view/index 13 | pynta.tests 14 | pynta.tools 15 | pynta.util 16 | pynta.exceptions 17 | -------------------------------------------------------------------------------- /docs/developers/controller/devices/index.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pynta.controller.devices 2 | :members: 3 | :undoc-members: 4 | :show-inheritance: 5 | 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :caption: List of available devices 10 | 11 | hamamatsu 12 | keysight 13 | photonicscience 14 | -------------------------------------------------------------------------------- /docs/developers/model/cameras/index.rst: -------------------------------------------------------------------------------- 1 | Camera Models 2 | ============= 3 | .. automodule:: pynta.model.cameras 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | base_camera 12 | simulate_brownian 13 | dummy_camera 14 | basler 15 | hamamatsu 16 | psi 17 | -------------------------------------------------------------------------------- /pynta/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.4' 2 | import os 3 | from pint import UnitRegistry 4 | from multiprocessing import Event 5 | 6 | ureg = UnitRegistry() 7 | Q_ = ureg.Quantity 8 | 9 | general_stop_event = Event() # This event is the last resource to stop threads and processes 10 | 11 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 12 | -------------------------------------------------------------------------------- /pynta/model/cameras/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from pynta.util import get_logger 4 | 5 | logger = get_logger(__name__) 6 | 7 | def not_implemented(func): 8 | @wraps(func) 9 | def func_wrapper(cls, *args, **kwargs): 10 | logger.warning(f'{func} Not Implemented') 11 | return func(cls, *args, **kwargs) 12 | return func_wrapper -------------------------------------------------------------------------------- /pynta/tests/test_models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Models can be checked for structure, but also models which depend on dummy devices can be tested 4 | thoroughly. 5 | 6 | .. TODO:: Define minimum structure for each kind of model 7 | .. TODO:: Define tests for dummy-based models. 8 | 9 | :copyright: Aquiles Carattino 10 | :license: AGPLv3, see LICENSE for more details. 11 | """ -------------------------------------------------------------------------------- /docs/developers/view/index.rst: -------------------------------------------------------------------------------- 1 | View 2 | ==== 3 | .. automodule:: pynta.view 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | :caption: Contents 11 | 12 | GUI 13 | 14 | .. automodule:: pynta.view.main 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | .. automodule:: pynta.view.subscriber_thread 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/developers/pynta.tools.rst: -------------------------------------------------------------------------------- 1 | pynta.tools package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pynta.tools.worker\_thread module 8 | --------------------------------- 9 | 10 | .. automodule:: pynta.tools.worker_thread 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: pynta.tools 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /pynta/tests/test_examples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Check whether the examples are able to perform an experiment with dummy devices. 4 | 5 | .. TODO:: Find a way of testing the UI, is it possible within Travis? 6 | may be useful to check what `PyQtGraph `_ is doing for the tests. 7 | 8 | :copyright: Aquiles Carattino 9 | :license: AGPLv3, see LICENSE for more details 10 | """ -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The contents of these files are licensed under a Creative Commons Attribution 4.0 International License. 2 | 3 | This means you are free to use these icons as you would like, but must provide attribution and, at minimum, a link to http://www.toicon.com/. Please review the legal code reachable at http://creativecommons.org/licenses/by/4.0/ if you have any questions. 4 | 5 | Permissions beyond the scope of this license may be available. Contact The Artificial at info@theartificial.nl to request details. -------------------------------------------------------------------------------- /pynta/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | PyNTA Config 4 | ============ 5 | This file is used to keep track of parameters that are used later in the code and that may be useful to have them 6 | defined instead of hardcoded. For instance, how NI cards organize data (channel or timestamps), whether is raising 7 | or falling edge of the trigger, etc. 8 | 9 | 10 | :copyright: Aquiles Carattino 11 | :license: GPLv3, see LICENSE for more details 12 | """ 13 | 14 | class config: 15 | pass -------------------------------------------------------------------------------- /pynta/tests/test_controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test the available controllers for the proper structure. 4 | Since controllers are inherently impossible to test on all systems, 5 | the only reasonable thing to do is to check for consistency in methods. 6 | 7 | .. TODO:: Define the minimum structure that makes a controller 8 | .. TODO:: Implement tests to assert whether the controllers have the needed methods 9 | 10 | :copyright: Aquiles Carattino 11 | :license: AGPLv3, see LICENSE for more details 12 | """ 13 | 14 | 15 | -------------------------------------------------------------------------------- /pynta/model/experiment/nanoparticle_tracking/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Nano CET exceptions 3 | =================== 4 | Collection of custom exceptions for the nanoCET experiment. 5 | 6 | """ 7 | 8 | class NanoCETException(Exception): 9 | pass 10 | 11 | class CameraNotInitialized(NanoCETException): 12 | pass 13 | 14 | class StreamSavingRunning(NanoCETException): 15 | pass 16 | 17 | class TrackpyNotInstalled(NanoCETException): 18 | pass 19 | 20 | class DiameterNotDefined(NanoCETException): 21 | pass 22 | 23 | class LinkException(NanoCETException): 24 | pass -------------------------------------------------------------------------------- /pynta/model/experiment/nanoparticle_tracking/waterfall_worker.py: -------------------------------------------------------------------------------- 1 | def calculate_waterfall(in_queue, out_queue): 2 | """ 3 | Convenience function for calculating the vertical binning of each frame of an image. It needs an input and output queue, 4 | the first one holds the images while the latter accumulates the 1D arrays. The second queue has to be consumed by another 5 | process, either for saving or for displaying. 6 | 7 | :param in_queue: 8 | :type in_queue: 9 | :param out_queue: 10 | :type out_queue: 11 | :return: 12 | :rtype: 13 | """ 14 | pass -------------------------------------------------------------------------------- /docs/developers/model/daqs/index.rst: -------------------------------------------------------------------------------- 1 | Data Acquisition Cards Models 2 | ============================= 3 | .. automodule:: pynta.model.daqs 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | 8 | Available DAQS 9 | --------------- 10 | 11 | .. automodule:: pynta.model.daqs.NI 12 | :members: 13 | :undoc-members: 14 | :show-inheritance: 15 | 16 | .. automodule:: pynta.model.daqs.daq_dummy 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | 22 | .. automodule:: pynta.model.daqs.skeleton 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /examples/simple_mp.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from multiprocessing import freeze_support 3 | from time import sleep 4 | 5 | from pynta.model.experiment.base_experiment import BaseExperiment 6 | from pynta.util import get_logger 7 | 8 | logger = get_logger()#'nanoparticle_tracking.model.experiment.nanoparticle_tracking.worker_saver') 9 | logger.setLevel(logging.DEBUG) 10 | ch = logging.StreamHandler() 11 | ch.setLevel(logging.DEBUG) 12 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 13 | ch.setFormatter(formatter) 14 | logger.addHandler(ch) 15 | 16 | 17 | if __name__ == "__main__": 18 | freeze_support() 19 | with BaseExperiment() as exp: 20 | 21 | sleep(5) -------------------------------------------------------------------------------- /pynta/view/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | View 4 | ==== 5 | In the broad definition, the View should provide all the tools for the interaction between the user and the 6 | computer. These could be both through the command line or through a Graphical User Interface. In practice, however, 7 | command-line interfaces appear naturally once the models and controllers are properly developed. It is also possible 8 | to use Jupyter notebooks to interface with devices and perform experiments. Therefore, the View will be fundamentally 9 | responsible for generating graphical applications. 10 | 11 | :copyright: Aquiles Carattino 12 | :license: GPLv3, see LICENSE for more details 13 | """ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | charset = utf-8 12 | 13 | # Docstrings and comments use max_line_length = 79 14 | [*.py] 15 | max_line_length = 119 16 | 17 | [*.rst] 18 | indent_size = 3 19 | 20 | # Use 2 spaces for the HTML files 21 | [*.html] 22 | indent_size = 2 23 | 24 | # The JSON files contain newlines inconsistently 25 | [*.json] 26 | indent_size = 2 27 | insert_final_newline = false 28 | 29 | # Makefiles always use tabs for indentation 30 | [Makefile] 31 | indent_style = tab 32 | 33 | # Batch files use tabs for indentation 34 | [*.bat] 35 | indent_style = tab 36 | -------------------------------------------------------------------------------- /pynta/view/GUI/histogram_tracks_widget.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt5 import uic 4 | from PyQt5.QtWidgets import QWidget 5 | 6 | from pynta.view.GUI.histogram_widget import HistogramWidget 7 | from pynta.view.GUI.tracks_widget import TracksWidget 8 | 9 | 10 | class HistogramTracksWidget(QWidget): 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'designer', 'histogram_tracks.ui'), self) 14 | 15 | self.histogram_widget = HistogramWidget(self) 16 | self.tracks_widget = TracksWidget(self) 17 | 18 | self.tabWidget.addTab(self.histogram_widget, 'Histogram') 19 | self.tabWidget.addTab(self.tracks_widget, 'Trajectories') 20 | -------------------------------------------------------------------------------- /docs/developers/pynta.util.rst: -------------------------------------------------------------------------------- 1 | pynta.util package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pynta.util.circular\_buffer module 8 | ---------------------------------- 9 | 10 | .. automodule:: pynta.util.circular_buffer 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pynta.util.importer module 16 | -------------------------- 17 | 18 | .. automodule:: pynta.util.importer 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pynta.util.log module 24 | --------------------- 25 | 26 | .. automodule:: pynta.util.log 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pynta.util 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /pynta/view/GUI/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Icons/hatch_enlarge.svg 4 | Icons/duotone_edit.svg 5 | Icons/pictogram_open.svg 6 | Icons/avocado_calibrate.svg 7 | Icons/duotone_chart.svg 8 | Icons/hatch_ask.svg 9 | Icons/hatch_download.svg 10 | Icons/pictogram_close.svg 11 | Icons/pictogram_crop.svg 12 | Icons/pictogram_link.svg 13 | Icons/pictogram_play.svg 14 | Icons/pictogram_record.svg 15 | Icons/pictogram_save.svg 16 | Icons/science-and-fiction_capture.svg 17 | Icons/science-and-fiction_upload.svg 18 | Icons/sketchy_create.svg 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/test_tracking_speed.py: -------------------------------------------------------------------------------- 1 | import os 2 | from multiprocessing import freeze_support 3 | from time import sleep 4 | 5 | from pynta import BASE_DIR 6 | from pynta.model.experiment.nanoparticle_tracking.np_tracking import NPTracking 7 | 8 | 9 | 10 | if __name__ == "__main__": 11 | freeze_support() 12 | config_file = os.path.join(BASE_DIR, 'util', 'example_config.yml') 13 | with NPTracking(config_file) as exp: 14 | exp.initialize_camera() 15 | exp.start_free_run() 16 | while exp.camera.sb.current_frame 2 | 3 | Icons/snap.png 4 | Icons/Blue-Waterfall-icon.png 5 | Icons/Delete-Database-icon.png 6 | Icons/disk-save.png 7 | Icons/Download-Database-icon.png 8 | Icons/floppy-icon.png 9 | Icons/Pause-icon.png 10 | Icons/pinion-icon.png 11 | Icons/play-icon.png 12 | Icons/power-icon.png 13 | Icons/Ruler-icon.png 14 | Icons/Signal-stop-icon.png 15 | Icons/tools-icon.png 16 | Icons/video-icon.png 17 | Icons/Zoom-In-icon.png 18 | Icons/Zoom-Out-icon.png 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pynta/util/circular_buffer.py: -------------------------------------------------------------------------------- 1 | class circularlist(object): 2 | def __init__(self, size): 3 | """Initialization""" 4 | self.index = 0 5 | self.size = size 6 | self._data = [] 7 | 8 | def append(self, value): 9 | """Append an element""" 10 | if len(self._data) == self.size: 11 | self._data[self.index] = value 12 | else: 13 | self._data.append(value) 14 | self.index = (self.index + 1) % self.size 15 | 16 | def __getitem__(self, key): 17 | """Get element by index, relative to the current index""" 18 | if len(self._data) == self.size: 19 | return(self._data[(key + self.index) % self.size]) 20 | else: 21 | return(self._data[key]) 22 | 23 | def __repr__(self): 24 | """Return string representation""" 25 | return self._data.__repr__() + ' (' + str(len(self._data))+' items)' -------------------------------------------------------------------------------- /pynta/model/experiment/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Experiment Models 4 | ================= 5 | Experiment models make explicit the different steps needed to perform a measurement. The Base Experiment class 6 | defines some methods that are transversal to all experiments (such as loading a configuration file) but individual 7 | experiment models can overwrite this methods to develop custom solutions. 8 | 9 | Moreover, PyNTA introduces the PUB/SUB pattern in order to exchange information between different parts of the 10 | program in a flexible and efficient way. You can find more information on :mod:`~pynta.model.experiment.publisher` 11 | and :mod:`~pynta.model.experiment.subscriber`. 12 | 13 | :copyright: Aquiles Carattino 14 | :license: GPLv3, see LICENSE for more details 15 | """ 16 | from pynta.model.experiment.config import Config 17 | 18 | config = Config() -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /examples/start_motor_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from time import sleep 3 | 4 | from PyQt5.QtWidgets import QApplication 5 | 6 | from pynta.model.experiment.nanoparticle_tracking.motor_test import MotorTestExperiment 7 | from pynta.util import get_logger 8 | from pynta.view.GUI.camera_focusing import CameraFocusing 9 | 10 | logger = get_logger()#'nanoparticle_tracking.model.experiment.nanoparticle_tracking.worker_saver') 11 | logger.setLevel(logging.DEBUG) 12 | ch = logging.StreamHandler() 13 | ch.setLevel(logging.DEBUG) 14 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 15 | ch.setFormatter(formatter) 16 | logger.addHandler(ch) 17 | 18 | if __name__ == '__main__': 19 | with MotorTestExperiment('config/nanocet.yml') as exp: 20 | sleep(1) 21 | exp.initialize_arduino() 22 | sleep(1) 23 | app = QApplication([]) 24 | window = CameraFocusing(exp) 25 | window.show() 26 | app.exec() -------------------------------------------------------------------------------- /pynta/model/experiment/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Experiment Configuration 4 | ======================== 5 | Class which holds some parameters that need to be used throughout the lifetime of a program. Keeping them in a 6 | separate class gives great flexibility, because it allows to overwrite values at run time. 7 | 8 | .. TODO:: Changes to config at runtime will have no effect on other processes. Find a way in which config can 9 | broadcast itself to all the instances of the class 10 | 11 | :copyright: Aquiles Carattino 12 | :license: GPLv3, see LICENSE for more details 13 | """ 14 | from pynta.util import get_logger 15 | 16 | 17 | logger = get_logger(__name__) 18 | 19 | class Config: 20 | def __init__(self): 21 | self.zmq_port = 5555 22 | 23 | def __setattr__(self, key, value): 24 | logger.debug(f'Setting {key} to {value}') 25 | super().__setattr__(key, value) -------------------------------------------------------------------------------- /pynta/model/experiment/nanoparticle_tracking/motor_test.py: -------------------------------------------------------------------------------- 1 | from pynta.model.experiment.base_experiment import BaseExperiment 2 | from pynta.model.motors.arduino_base import Arduino 3 | 4 | 5 | class MotorTestExperiment(BaseExperiment): 6 | def __init__(self, filename=None): 7 | super().__init__() 8 | self.load_configuration(filename) 9 | self.arduino = None 10 | 11 | def initialize_arduino(self): 12 | self.arduino = Arduino(self.config['arduino']['port']) 13 | 14 | def motor_right(self): 15 | self.arduino.set_speed(1, 1, 50) 16 | self.arduino.set_speed(1, 1, 0) 17 | 18 | def motor_left(self): 19 | self.arduino.set_speed(1, 0, 50) 20 | self.arduino.set_speed(1, 0, 0) 21 | 22 | def motor_top(self): 23 | self.arduino.set_speed(2, 1, 50) 24 | self.arduino.set_speed(2, 1, 0) 25 | 26 | def motor_bottom(self): 27 | self.arduino.set_speed(2, 0, 50) 28 | self.arduino.set_speed(2, 0, 0) 29 | -------------------------------------------------------------------------------- /pynta/controller/devices/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Devices 3 | ======= 4 | Collection of drivers for different devices which are useful to perform measurements. Drivers may depend on 5 | external libraries. Thus, be aware that you may need to install some dependencies in order to make the 6 | controllers work correctly. A common example is the requirement of pyvisa to communicate with serial devices, 7 | or the DLL's from Hamamatsu in order to use their cameras. 8 | 9 | For several devices, drivers in Python are provided by the manufacturers and thus they won't be found in this 10 | module. A clear example is the Python wrapper of Pylon to work with Basler cameras, known as PyPylon. In that case, 11 | PyNTA only provides a model which relies on that package. For DAQ devices such as those from National Instruments, 12 | PyNTA depends on PyDAQmx, although NI has released its own wrapper, 13 | `NIDAQmx-Python `_ which may be worth exploring. 14 | """ 15 | 16 | -------------------------------------------------------------------------------- /docs/developers/pynta.tests.rst: -------------------------------------------------------------------------------- 1 | pynta.tests package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pynta.tests.test\_controllers module 8 | ------------------------------------ 9 | 10 | .. automodule:: pynta.tests.test_controllers 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pynta.tests.test\_examples module 16 | --------------------------------- 17 | 18 | .. automodule:: pynta.tests.test_examples 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pynta.tests.test\_models module 24 | ------------------------------- 25 | 26 | .. automodule:: pynta.tests.test_models 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pynta.tests.test\_zmq module 32 | ---------------------------- 33 | 34 | .. automodule:: pynta.tests.test_zmq 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: pynta.tests 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/developers/model/index.rst: -------------------------------------------------------------------------------- 1 | PyNTA Models 2 | ============ 3 | 4 | Models are the place to develop the logic of how the devices are used and how the experiment is performed. You will find models for cameras and daqs. Cameras are heavily used in PyNTA, while DAQs where inherited from a previous incarnation and were left here as a reference and to speed up future developments in which not only a camera has to be controlled. 5 | 6 | The model for the experiment is the easiest place where the developer can have a sense of what is going on under-the-hood. You can check, for example, :mod:`~pynta.model.experiment.nanoparticle_tracking.np_tracking.NPTracking` in order to see the steps that make a tracking measurement. Remember that the program runs with different threads and processes in parallel, and therefore the behavior may not be trivial to understand. 7 | 8 | .. automodule:: pynta.model 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | :caption: Available Models 17 | 18 | cameras/index 19 | daqs/index 20 | experiments/index 21 | 22 | 23 | -------------------------------------------------------------------------------- /pynta/view/subscriber_thread.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Subscriber Thread 4 | ================= 5 | Allows to transform topics coming from a socket (ZMQ) to Qt signals that can be connected to 6 | any method, etc. 7 | 8 | :copyright: Aquiles Carattino 9 | :license: GPLv3, see LICENSE for more details 10 | """ 11 | import numpy as np 12 | from PyQt5.QtCore import QThread, pyqtSignal 13 | 14 | from pynta.model.experiment.subscriber import subscribe 15 | 16 | 17 | class SubscriberThread(QThread): 18 | data_received = pyqtSignal(list) 19 | 20 | def __init__(self, port, topic): 21 | super().__init__() 22 | self.topic = topic 23 | self.port = port 24 | self.keep_receiving = True 25 | 26 | def __del__(self): 27 | self.keep_receiving = False 28 | self.wait() 29 | 30 | def run(self): 31 | socket = subscribe(self.port, self.topic) 32 | while self.keep_receiving: 33 | socket.recv_string() 34 | data = socket.recv_pyobj() # flags=0, copy=True, track=False) 35 | self.data_received.emit(data) 36 | -------------------------------------------------------------------------------- /pynta/util/log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | nanoparticle_tracking.util.log.py 4 | ================================= 5 | 6 | Adding log capacities to PyNTA 7 | 8 | 9 | :copyright: Aquiles Carattino 10 | :license: AGPLv3, see LICENSE for more details 11 | """ 12 | import logging 13 | 14 | 15 | DEFAULT_FMT = "[%(levelname)8s]%(asctime)s %(name)s: %(message)s" 16 | 17 | def get_logger(name='nanoparticle_tracking', add_null_handler=True): 18 | logger = logging.getLogger(name) #, add_null_handler=add_null_handler) 19 | return logger 20 | 21 | 22 | PYNTA_LOGGER = get_logger() 23 | 24 | 25 | def log_to_screen(level=logging.INFO, fmt=None): 26 | fmt = fmt or DEFAULT_FMT 27 | handler = logging.StreamHandler() 28 | handler.setLevel(level) 29 | handler.setFormatter(fmt) 30 | PYNTA_LOGGER.addHandler(handler) 31 | return 32 | 33 | def log_to_file(filename, level=logging.INFO, fmt=None): 34 | fmt = fmt or DEFAULT_FMT 35 | handler = logging.FileHandler(filename) 36 | handler.setLevel(level) 37 | handler.setFormatter(fmt) 38 | PYNTA_LOGGER.addHandler(handler) 39 | return 40 | -------------------------------------------------------------------------------- /requirements_docs.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | asn1crypto==0.24.0 3 | Babel==2.7.0 4 | certifi==2019.6.16 5 | cffi==1.12.3 6 | chardet==3.0.4 7 | cryptography==3.3.2 8 | cycler==0.10.0 9 | docutils==0.14 10 | h5py==2.9.0 11 | idna==2.8 12 | imagesize==1.1.0 13 | Jinja2==2.10.1 14 | kiwisolver==1.1.0 15 | llvmlite==0.29.0 16 | MarkupSafe==1.1.1 17 | matplotlib==3.1.1 18 | numba==0.45.0 19 | numpy==1.16.4 20 | packaging==19.0 21 | pandas==0.24.2 22 | Pint==0.9 23 | pycparser==2.19 24 | Pygments==2.4.2 25 | pyOpenSSL==19.0.0 26 | pyqt5==5.13.0 27 | pyqt5-sip==4.19.18 28 | pyqtgraph==0.10.0 29 | pyreadline==2.1 30 | PySocks==1.7.0 31 | python-dateutil==2.8.0 32 | pytz==2019.1 33 | PyVISA==1.9.1 34 | PyYAML==5.1.1 35 | pyzmq==18.0.0 36 | requests==2.22.0 37 | scipy==1.2.1 38 | six==1.12.0 39 | snowballstemmer==1.9.0 40 | Sphinx==2.1.2 41 | sphinx-rtd-theme==0.4.3 42 | sphinxcontrib-applehelp==1.0.1 43 | sphinxcontrib-devhelp==1.0.1 44 | sphinxcontrib-htmlhelp==1.0.2 45 | sphinxcontrib-jsmath==1.0.1 46 | sphinxcontrib-qthelp==1.0.2 47 | sphinxcontrib-serializinghtml==1.1.3 48 | tornado==6.0.3 49 | trackpy==0.4.1 50 | urllib3==1.24.2 51 | win-inet-pton==1.1.0 52 | wincertstore==0.2 53 | -------------------------------------------------------------------------------- /pynta/view/GUI/tracks_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | In this example we draw two different kinds of histogram. 4 | """ 5 | 6 | import pyqtgraph as pg 7 | from PyQt5.QtWidgets import QWidget, QGridLayout 8 | import numpy as np 9 | 10 | from pynta.model.experiment.nanoparticle_tracking.decorators import make_async_thread 11 | 12 | 13 | class TracksWidget(QWidget): 14 | def __init__(self, parent=None): 15 | super().__init__(parent) 16 | self.plot = pg.PlotWidget() 17 | self.layout = QGridLayout() 18 | self.layout.addWidget(self.plot) 19 | self.setLayout(self.layout) 20 | self._threads = [] 21 | 22 | @make_async_thread 23 | def plot_trajectories(self, locations): 24 | """ 25 | 26 | :param locations: Dataframe of locations as given by trackpy 27 | """ 28 | unstacked = locations.set_index(['particle', 'frame'])[['x', 'y']].unstack() 29 | for i, trajectory in unstacked.iterrows(): 30 | x = trajectory['x'].values 31 | y = trajectory['y'].values 32 | x = x[~np.isnan(x)] 33 | y = y[~np.isnan(y)] 34 | self.plot.plot(x, y) 35 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/clearQueueThread.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.clearQueueThread.py 3 | ======================================= 4 | If one desires to clear the Queue without saving it, a thread should read all the elements of the Queue until it is empty. The documentation on Queues and Processes is a bit obscure as to know if there is a better way of deleting a Queue preserving its integrity. 5 | 6 | .. warning:: If this Thread is run while the save thread is running there will be data loss without warning. Moreover, of the clear queue destroys the last element of the Queue before the saver arrives to it, te saver will not stop. 7 | 8 | .. sectionauthor:: Aquiles Carattino 9 | """ 10 | 11 | from pyqtgraph.Qt import QtCore 12 | from time import sleep 13 | 14 | class clearQueueThread(QtCore.QThread): 15 | """Clears the Queue. 16 | """ 17 | def __init__(self,q): 18 | QtCore.QThread.__init__(self) 19 | self.q = q 20 | 21 | def __del__(self): 22 | self.wait() 23 | 24 | def run(self): 25 | """Clears the queue. 26 | """ 27 | while self.q.qsize()>0: 28 | self.q.get() 29 | -------------------------------------------------------------------------------- /docs/developers/view/GUI.rst: -------------------------------------------------------------------------------- 1 | GUI modules 2 | =========== 3 | .. automodule:: pynta.view.GUI 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | 8 | .. automodule:: pynta.view.GUI.camera_focusing 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | 13 | .. automodule:: pynta.view.GUI.camera_viewer_widget 14 | :members: 15 | :undoc-members: 16 | :show-inheritance: 17 | 18 | .. automodule:: pynta.view.GUI.config_tracking_widget 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | .. automodule:: pynta.view.GUI.config_widget 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | .. automodule:: pynta.view.GUI.histogram_tracks_widget 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | .. automodule:: pynta.view.GUI.histogram_widget 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | .. automodule:: pynta.view.GUI.main_window 39 | :members: 40 | :undoc-members: 41 | :show-inheritance: 42 | 43 | .. automodule:: pynta.view.GUI.tracks_widget 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | with open('pynta/__init__.py', 'r') as f: 6 | version_line = f.readline() 7 | 8 | version = version_line.split('=')[1].strip().replace("'", "") 9 | 10 | with open('README.md', 'r') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name='PyNTA', 15 | version=version, 16 | description='Python Nanoparticle Tracking Analysis', 17 | packages=find_packages(), 18 | url='https://github.com/nanoepics/pynta', 19 | license='GPLv3', 20 | author='Aquiles Carattino', 21 | author_email='aquiles@uetke.com', 22 | classifiers=[ 23 | 'Intended Audience :: End Users/Desktop', 24 | 'Operating System :: Microsoft :: Windows', 25 | 'Programming Language :: Python', 26 | ], 27 | include_package_data=True, 28 | install_requires=['pyqt5', 'numpy', 'pyqtgraph', 'pint', 'h5py', 'trackpy', 'pandas', 'pyyaml', 29 | 'pyzmq', 'numba'], 30 | long_description=long_description, 31 | long_description_content_type="text/markdown", 32 | entry_points={ 33 | "console_scripts": [ 34 | "pynta=pynta.__main__:main" 35 | ] 36 | } 37 | ) 38 | 39 | -------------------------------------------------------------------------------- /pynta/model/experiment/dispertech/util.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from pynta.util import get_logger 4 | 5 | logger = get_logger(name=__name__) 6 | 7 | 8 | def load_camera_module(name): 9 | try: 10 | logger.info('Importing camera model {}'.format(name)) 11 | logger.debug('pynta.model.cameras.' + name) 12 | camera_model_to_import = 'pynta.model.cameras.' + name 13 | cam_module = importlib.import_module(camera_model_to_import) 14 | except ModuleNotFoundError: 15 | logger.error('The model {} for the camera was not found'.format(name)) 16 | raise 17 | return cam_module 18 | 19 | 20 | def instantiate_camera(config: dict): 21 | cam_module = load_camera_module(config['model']) 22 | cam_init_arguments = config['init'] 23 | if 'extra_args' in config: 24 | logger.info('Initializing camera with extra arguments') 25 | logger.debug('cam_module.camera({}, {})'.format(cam_init_arguments, config['extra_args'])) 26 | camera = cam_module.Camera(cam_init_arguments, *config['extra_args']) 27 | else: 28 | logger.info('Initializing camera without extra arguments') 29 | logger.debug('cam_module.camera({})'.format(cam_init_arguments)) 30 | camera = cam_module.Camera(cam_init_arguments) 31 | 32 | return camera 33 | -------------------------------------------------------------------------------- /pynta/view/GUI/camera_focusing.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyqtgraph as pg 3 | 4 | from PyQt5 import uic 5 | from PyQt5.QtWidgets import QMainWindow, QAction 6 | from pyqtgraph import GraphicsLayoutWidget 7 | 8 | from pynta.view.GUI.camera_viewer_widget import CameraViewerWidget 9 | 10 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | class CameraFocusing(QMainWindow): 14 | def __init__(self, experiment=None, parent=None): 15 | super(CameraFocusing, self).__init__(parent) 16 | uic.loadUi(os.path.join(BASE_DIR, 'designer/focusing_window.ui'), self) 17 | self.experiment = experiment 18 | 19 | self.camera_viewer = CameraViewerWidget(self) 20 | self.layout = self.camera_widget.layout() 21 | self.layout.addWidget(self.camera_viewer) 22 | 23 | self.button_right.clicked.connect(self.experiment.motor_right) 24 | self.button_left.clicked.connect(self.experiment.motor_left) 25 | self.button_up.clicked.connect(self.experiment.motor_top) 26 | self.button_down.clicked.connect(self.experiment.motor_bottom) 27 | 28 | 29 | 30 | 31 | if __name__ == '__main__': 32 | from PyQt5.QtWidgets import QApplication 33 | 34 | app = QApplication([]) 35 | win = CameraFocusing() 36 | win.show() 37 | app.exit(app.exec()) -------------------------------------------------------------------------------- /pynta/view/GUI/histogram_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | In this example we draw two different kinds of histogram. 4 | """ 5 | 6 | import pyqtgraph as pg 7 | from PyQt5.QtWidgets import QWidget, QGridLayout 8 | import numpy as np 9 | 10 | pg.setConfigOption('background', 'w') 11 | pg.setConfigOption('foreground', 'k') 12 | 13 | class HistogramWidget(QWidget): 14 | def __init__(self, parent=None): 15 | super().__init__(parent) 16 | self.plot = pg.PlotWidget() 17 | self.plot.setLabel('left', '# Of Particles') 18 | self.plot.setLabel('bottom', 'Diffusion Coefficient', 'μ m2/s') 19 | self.layout = QGridLayout() 20 | self.layout.addWidget(self.plot) 21 | self.setLayout(self.layout) 22 | 23 | def update_distribution(self, values): 24 | values = np.abs(values) 25 | y, x = np.histogram(values, bins=30) 26 | self.plot.plot(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 50), clear=True) 27 | 28 | 29 | if __name__ == '__main__': 30 | import sys 31 | from PyQt5.QtWidgets import QApplication 32 | 33 | app = QApplication([]) 34 | win = HistogramWidget() 35 | win.show() 36 | vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)]) 37 | win.update_distribution(vals) 38 | sys.exit(app.exec()) 39 | -------------------------------------------------------------------------------- /pynta/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from argparse import ArgumentParser 3 | import logging 4 | from PyQt5.QtWidgets import QApplication 5 | 6 | from pynta.model.experiment.nanoparticle_tracking.np_tracking import NPTracking 7 | from pynta.util.log import get_logger 8 | from pynta.view.main import MainWindow 9 | 10 | 11 | 12 | def main(): 13 | logger = get_logger() # 'nanoparticle_tracking.model.experiment.nanoparticle_tracking.saver' 14 | logger.setLevel(logging.DEBUG) 15 | ch = logging.StreamHandler() 16 | ch.setLevel(logging.DEBUG) 17 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 18 | ch.setFormatter(formatter) 19 | logger.addHandler(ch) 20 | 21 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 22 | parser = ArgumentParser(description='Start the pyNTA software') 23 | parser.add_argument("-c", dest="config_file", required=False, 24 | help="Path to the configuration file") 25 | args = parser.parse_args() 26 | 27 | if args.config_file is None: 28 | config_file = os.path.join(BASE_DIR, 'util', 'example_config.yml') 29 | else: 30 | config_file = args.config_file 31 | exp = NPTracking(config_file) 32 | exp.initialize_camera() 33 | app = QApplication([]) 34 | window = MainWindow(exp) 35 | window.show() 36 | app.exec() 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /examples/window_with_logging.py: -------------------------------------------------------------------------------- 1 | import os 2 | from argparse import ArgumentParser 3 | import logging 4 | from PyQt5.QtWidgets import QApplication 5 | 6 | from pynta.model.experiment.nanoparticle_tracking.np_tracking import NPTracking 7 | from pynta.util.log import get_logger 8 | from pynta.view.main import MainWindow 9 | 10 | 11 | 12 | def main(): 13 | logger = get_logger() # 'nanoparticle_tracking.model.experiment.nanoparticle_tracking.saver' 14 | logger.setLevel(logging.DEBUG) 15 | ch = logging.StreamHandler() 16 | ch.setLevel(logging.DEBUG) 17 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 18 | ch.setFormatter(formatter) 19 | logger.addHandler(ch) 20 | 21 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 22 | parser = ArgumentParser(description='Start the pyNTA software') 23 | parser.add_argument("-c", dest="config_file", required=False, 24 | help="Path to the configuration file") 25 | args = parser.parse_args() 26 | 27 | if args.config_file is None: 28 | config_file = os.path.join(BASE_DIR, 'config', 'nanocet.yml') 29 | else: 30 | config_file = args.config_file 31 | exp = NPTracking(config_file) 32 | exp.initialize_camera() 33 | app = QApplication([]) 34 | window = MainWindow(exp) 35 | window.show() 36 | app.exec() 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /examples/start_nanocet.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from time import sleep 3 | 4 | from pynta.model.experiment.nanoparticle_tracking.np_tracking import NPTracking 5 | from pynta.util.log import get_logger 6 | 7 | 8 | logger = get_logger() # 'nanoparticle_tracking.model.experiment.nanoparticle_tracking.saver' 9 | logger.setLevel(logging.DEBUG) 10 | ch = logging.StreamHandler() 11 | ch.setLevel(logging.DEBUG) 12 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 13 | ch.setFormatter(formatter) 14 | logger.addHandler(ch) 15 | 16 | 17 | if __name__ == '__main__': 18 | with NPTracking('config/nanocet.yml') as exp: 19 | sleep(1) 20 | # exp.connect(calculate_positions_image, 'free_run', exp.publisher_queue, **exp.config['tracking']['locate']) 21 | # exp.connect(add_to_save_queue, 'free_run', exp.saver_queue) 22 | # exp.connect(add_linking_queue, 'trackpy_locations', exp.locations_queue) 23 | # exp.connect(add_links_to_queue, 'particle_links', exp.tracks_queue) 24 | exp.initialize_camera() 25 | # exp.link_particles() 26 | exp.save_stream() 27 | sleep(1) 28 | exp.start_free_run() 29 | logger.info('Going to sleep for 5 seconds') 30 | sleep(5) 31 | logger.info('Keep acquiring set to false') 32 | exp.keep_acquiring = False 33 | logger.info('Sleeping for 2 seconds') 34 | sleep(2) 35 | # exp.plot_histogram() -------------------------------------------------------------------------------- /docs/requirements_docs.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | asn1crypto==0.24.0 3 | Babel==2.7.0 4 | certifi==2019.6.16 5 | cffi==1.12.3 6 | chardet==3.0.4 7 | cryptography==3.3.2 8 | cycler==0.10.0 9 | docutils==0.14 10 | h5py==2.9.0 11 | idna==2.8 12 | imagesize==1.1.0 13 | Jinja2==2.10.1 14 | kiwisolver==1.1.0 15 | lantz-core==0.5.3 16 | lantz-drivers==0.5.2 17 | lantz-ino==0.5.2 18 | lantz-qt==0.5.3 19 | lantz-sims==0.5.2 20 | lantzdev==0.5.2 21 | llvmlite==0.29.0 22 | MarkupSafe==1.1.1 23 | matplotlib==3.1.1 24 | mkl-fft==1.0.12 25 | mkl-random==1.0.2 26 | numba==0.45.0 27 | numpy==1.16.4 28 | packaging==19.0 29 | pandas==0.24.2 30 | pimpmyclass==0.4.3 31 | Pint==0.9 32 | pycparser==2.19 33 | PyDAQmx==1.4.2 34 | Pygments==2.4.2 35 | pyOpenSSL==19.0.0 36 | pyparsing==2.4.1 37 | pypylon==1.4.0 38 | pyqt5==5.13.0 39 | pyqt5-sip==4.19.18 40 | pyqtgraph==0.10.0 41 | pyreadline==2.1 42 | PySignal==1.1.1 43 | PySocks==1.7.0 44 | python-dateutil==2.8.0 45 | pytz==2019.1 46 | PyVISA==1.9.1 47 | PyYAML==5.1.1 48 | pyzmq==18.0.0 49 | requests==2.22.0 50 | scipy==1.2.1 51 | Serialize==0.1 52 | six==1.12.0 53 | snowballstemmer==1.9.0 54 | Sphinx==2.1.2 55 | sphinx-rtd-theme==0.4.3 56 | sphinxcontrib-applehelp==1.0.1 57 | sphinxcontrib-devhelp==1.0.1 58 | sphinxcontrib-htmlhelp==1.0.2 59 | sphinxcontrib-jsmath==1.0.1 60 | sphinxcontrib-qthelp==1.0.2 61 | sphinxcontrib-serializinghtml==1.1.3 62 | stringparser==0.5 63 | tornado==6.0.3 64 | trackpy==0.4.1 65 | urllib3==1.24.2 66 | win-inet-pton==1.1.0 67 | wincertstore==0.2 68 | -------------------------------------------------------------------------------- /pynta/controller/devices/arduino/arduino.py: -------------------------------------------------------------------------------- 1 | """ 2 | arduino.py 3 | ========== 4 | 5 | Base driver for communicating with Arduino devices. In principle, Arduino's can be programmed in very different 6 | ways, and therefore the flow of information may be very different. This driver is thought to interface with 7 | an Arduino which is in control of two DC motors and which is able to read values from some devices, such as a 8 | DHT22, and a DS18B20. 9 | """ 10 | 11 | import pyvisa 12 | # TODO: Make more flexible which bacend will be used for PyVisa 13 | rm = pyvisa.ResourceManager('@py') 14 | 15 | 16 | class Arduino: 17 | def __init__(self, port=None): 18 | """ 19 | 20 | :param port: Serial port where the Arduino is connected, can be none and in order to look for devices 21 | automatically 22 | """ 23 | self.rsc = None 24 | self.port = port 25 | if port: 26 | if not port.startswith('ASRL'): 27 | port = 'ASRL' + port 28 | self.port = port 29 | self.rsc = rm.open_resource(self.port) 30 | 31 | def write(self, command): 32 | self.rsc.write(command) 33 | 34 | def close(self): 35 | self.rsc.close() 36 | 37 | @staticmethod 38 | def list_devices(): 39 | return rm.list_resources() 40 | 41 | if __name__ == '__main__': 42 | print(Arduino.list_devices()) 43 | 44 | inst = Arduino('COM3') 45 | inst.close() 46 | inst.list_devices() -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/science-and-fiction_upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to uploadAn icon for "upload" from the Science and Fiction series on to [icon]. Downloaded from http://www.toicon.com/icons/science-and-fiction_upload by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | image/svg+xmlKamila Piątkowskahttp://www.toicon.com/icons/science-and-fiction_upload 8 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to saveAn icon for "save" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_save by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_save 11 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_crop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to cropAn icon for "crop" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_crop by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_crop 11 | -------------------------------------------------------------------------------- /pynta/tools/worker_thread.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | work_thread.py 4 | ============== 5 | Running the acquisition on a separate thread gives a lot of flexibility when designing the program. It comes, 6 | however with some potential risks. First, threads are still running on the same Python interpreter. Therefore they 7 | do not overcome the GIL limitations. They are able to share memory, which makes them transparent to less experienced 8 | users. Potentially, different threads will access the same resources (i.e. devices) creating a clash. It is hard to 9 | implement semaphores or locks for every possible scenario, especially with devices such as cameras which can run 10 | without user intervention for long periods of time. 11 | 12 | 13 | :copyright: Aquiles Carattino 14 | :license: AGPLv3, see LICENSE for more details 15 | """ 16 | from threading import Thread 17 | 18 | 19 | class WorkerThread(Thread): 20 | """ Thread for acquiring from the camera. If the exposure time is long, running on a separate thread will enable 21 | to perform other tasks. It also allows to acquire continuously without freezing the rest of the program. 22 | 23 | .. TODO:: QThreads are much handier than Python threads. Should we put Qt as a requirement regardless of whether 24 | the program runs on CLI or UI mode? 25 | """ 26 | def __init__(self, camera, keep_alive=False): 27 | super().__init__() 28 | self.camera = camera 29 | self.keep_alive = keep_alive 30 | 31 | def run(self): 32 | self.camera.trigger_camera() 33 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/waterfallWidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.waterfallWidget.py 3 | ======================================== 4 | Widget for displaying a 2D image. It shouldn't do much, but is thought for displaying a waterfall kind of image. 5 | 6 | .. todo:: displaying 2D data is ubiquitous in this program; there should be a unified widget to such ends. 7 | 8 | .. todo:: Unify the 2D displaying of :mod:`camera Main ` and :mod:`camera Viewer `. 9 | 10 | .. sectionauthor:: Aquiles Carattino 11 | """ 12 | 13 | import pyqtgraph as pg 14 | from pyqtgraph import GraphicsLayoutWidget 15 | from pyqtgraph.Qt import QtGui 16 | 17 | 18 | class waterfallWidget(QtGui.QWidget): 19 | """Widget for plotting a waterfall plot. 20 | """ 21 | def __init__(self,parent=None): 22 | QtGui.QWidget.__init__(self, parent) 23 | 24 | self.layout = QtGui.QHBoxLayout(self) 25 | self.viewport = GraphicsLayoutWidget() 26 | self.view = self.viewport.addViewBox(colspan=3, rowspan=3, lockAspect = False, enableMenu=True) 27 | self.img = pg.ImageItem() 28 | self.view.addItem(self.img) 29 | 30 | self.h = self.viewport.addViewBox(enableMenu=False, colspan=3) 31 | self.hist = pg.HistogramLUTItem(image=self.img, fillHistogram=False) 32 | self.hist.setImageItem(self.img) 33 | self.h.addItem(self.hist) 34 | 35 | self.imv = pg.ImageView(view=self.view, imageItem=self.img) 36 | 37 | self.layout.addWidget(self.imv) 38 | self.setLayout(self.layout) 39 | 40 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/workerThread.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.workerThread 3 | ================================ 4 | 5 | Thread that acquires continuously data until a variable is changed. This enables to acquire at any frame rate without freezing the GUI or overloading it with data being acquired too fast. 6 | 7 | """ 8 | 9 | from pyqtgraph.Qt import QtCore 10 | 11 | 12 | class workThread(QtCore.QThread): 13 | """Thread for acquiring from the camera. If the exposure time is long, this is 14 | needed to avoid freezing the GUI. 15 | """ 16 | def __init__(self,_session,camera): 17 | QtCore.QThread.__init__(self) 18 | self._session = _session 19 | self.camera = camera 20 | self.origin = None 21 | self.keep_acquiring = True 22 | 23 | def __del__(self): 24 | self.wait() 25 | 26 | def run(self): 27 | """ Triggers the Monitor to acquire a new Image. 28 | the QThread defined .start() method is a special method that sets up the thread and 29 | calls our implementation of the run() method. 30 | """ 31 | first = True 32 | while self.keep_acquiring: 33 | if self.origin == 'snap': 34 | self.keep_acquiring = False 35 | if first: 36 | self.camera.set_acquisition_mode(self.camera.MODE_CONTINUOUS) 37 | self.camera.trigger_camera() # Triggers the camera only once 38 | first = False 39 | img = self.camera.read_camera() 40 | 41 | self.emit(QtCore.SIGNAL('image'), img, self.origin) 42 | self.camera.stopAcq() 43 | return 44 | -------------------------------------------------------------------------------- /pynta/view/GUI/designer/histogram_tracks.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 821 10 | 639 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | -1 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 40 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Compute Histogram 43 | 44 | 45 | 46 | 47 | 48 | 49 | Compute Tracks 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /pynta/view/GUI/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | GUI 4 | === 5 | Developing a GUI for an application is a delicated task. On the one hand, developers want to deliver quick solutions. 6 | On the other, users are exposed to programs developed over decades, by large teams, including designers. Therefore, 7 | it is very hard for a single developer to obtain user interfaces as beautiful as the ones you can get from commercial 8 | suppliers. 9 | 10 | However, it is possible to build a collection of tools that can be reused and that can generate a much more 11 | consistent result throughout different applications. The approach PyNTA follows is to develop GUI's using Qt5. There 12 | are different ports of Qt for Python, such as PySide and PyQt. For the time being, PyNTA is based on PyQt5, but this 13 | can change without previous notice. 14 | 15 | To develop a Graphical User Interface (GUI), we have opted to use Qt Designer, and the files are loaded directly to 16 | the class through ``uic.loadUi`` instead of compiling the files into a Python class. On the one hand, this 17 | simplifies the cycle for updating the interface, on the other it does not make explicit which methods are available. 18 | Compiling the files is up to the developer, but the *official* approach is to use only the UI files generated by 19 | Qt Designer. 20 | 21 | .. TODO:: Different wrappers of Qt for Python expose the same API in different ways. We should explore using an 22 | intermediate package to unify the use of Qt. 23 | 24 | :copyright: Aquiles Carattino 25 | :license: GPLv3, see LICENSE for more details 26 | """ -------------------------------------------------------------------------------- /pynta/model/daqs/skeleton.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Base Model for DAQs 4 | =================== 5 | Base model that makes explicit the API for working with DAQ cards. Every new DAQ card should inherit this model. 6 | This allows to check the argument type, for example, but they also guarantee forward-compatibility in case new 7 | methods are developed. 8 | 9 | .. note:: **IMPORTANT** Whatever new function is implemented in a specific model, it should be first declared in the 10 | laserBase class. In this way the other models will have access to the method and the program will keep running 11 | (perhaps with non intended behavior though). 12 | 13 | :copyright: Aquiles Carattino 14 | :license: AGPLv3, see LICENSE for more details 15 | """ 16 | 17 | 18 | class DaqBase: 19 | def __init__(self, dev_number): 20 | self.dev_number = dev_number 21 | 22 | def analog_input_setup(self, conditions): 23 | """ 24 | conditions -- a dictionary with the needed parameters for an analog acquisition. 25 | """ 26 | pass 27 | 28 | def trigger_analog(self, task_number): 29 | """ 30 | Triggers an analog measurement. It does not read the value. 31 | 32 | conditions -- dictionary with the number of points ot be read 33 | """ 34 | pass 35 | 36 | def analog_output_setup(self, conditions): 37 | """ 38 | Sets up an analog output task. 39 | 40 | :param conditions: 41 | :return: 42 | """ 43 | 44 | def read_analog(self, task_number, conditions): 45 | """ 46 | Gets the analog values acquired with the triggerAnalog function. 47 | """ 48 | pass 49 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: pynta 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - blas=1.0=mkl 7 | - ca-certificates=2019.6.16=hecc5488_0 8 | - certifi=2019.6.16=py36_1 9 | - cycler=0.10.0=py_1 10 | - freetype=2.10.0=h5db478b_0 11 | - h5py=2.9.0=py36h5e291fa_0 12 | - hdf5=1.10.4=h7ebc959_0 13 | - icc_rt=2019.0.0=h0cc432a_1 14 | - icu=58.2=ha66f8fd_1 15 | - intel-openmp=2019.4=245 16 | - jpeg=9b=hb83a4c4_2 17 | - kiwisolver=1.1.0=py36he980bc4_0 18 | - libpng=1.6.37=h2a8f88b_0 19 | - libsodium=1.0.16=h9d3ae62_0 20 | - llvmlite=0.29.0=py36ha925a31_0 21 | - matplotlib=3.1.1=py36_0 22 | - matplotlib-base=3.1.1=py36h2852a4a_0 23 | - mkl=2019.4=245 24 | - mkl_fft=1.0.12=py36h14836fe_0 25 | - mkl_random=1.0.2=py36h343c172_0 26 | - numba=0.45.0=py36hf9181ef_0 27 | - numpy=1.16.4=py36h19fb1c0_0 28 | - numpy-base=1.16.4=py36hc3f5095_0 29 | - openssl=1.1.1c=hfa6e2cd_0 30 | - pandas=0.24.2=py36ha925a31_0 31 | - pint=0.9=py36_2 32 | - pip=19.1.1=py36_0 33 | - pyparsing=2.4.1=py_0 34 | - pyqt=5.9.2=py36h6538335_2 35 | - pyqtgraph=0.10.0=py36h28b3542_3 36 | - pyreadline=2.1=py36_1 37 | - python=3.6.8=h9f7ef89_7 38 | - python-dateutil=2.8.0=py36_0 39 | - pytz=2019.1=py_0 40 | - pyvisa=1.9.1=py36_1000 41 | - pyyaml=5.1.1=py36he774522_0 42 | - pyzmq=18.0.0=py36ha925a31_0 43 | - qt=5.9.7=vc14h73c81de_0 44 | - scipy=1.2.1=py36h29ff71c_0 45 | - setuptools=41.0.1=py36_0 46 | - sip=4.19.8=py36h6538335_0 47 | - six=1.12.0=py36_0 48 | - sqlite=3.29.0=he774522_0 49 | - tornado=6.0.3=py36hfa6e2cd_0 50 | - trackpy=0.4.1=py_1 51 | - vc=14.1=h0510ff6_4 52 | - vs2015_runtime=14.15.26706=h3a45250_4 53 | - wheel=0.33.4=py36_0 54 | - wincertstore=0.2=py36h7fe50ca_0 55 | - yaml=0.1.7=hc54c509_2 56 | - zeromq=4.3.1=h33f27b4_3 57 | - zlib=1.2.11=h62dcd97_3 58 | - pip: 59 | - pypylon==1.4.0 60 | prefix: C:\Users\aquic\.conda\envs\pynta 61 | 62 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to closeAn icon for "close" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_close by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_close 11 | -------------------------------------------------------------------------------- /examples/test_zmq.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from time import sleep, time 3 | 4 | import numpy as np 5 | from multiprocessing import Process, Queue 6 | 7 | from pynta.model.experiment.publisher import publisher 8 | from pynta.model.experiment.subscriber import subscriber 9 | from pynta.util import get_logger 10 | 11 | logger = get_logger(name='nanoparticle_tracking.model.experiment.subscriber') # 'nanoparticle_tracking.model.experiment.nanoparticle_tracking.saver' 12 | logger.setLevel(logging.DEBUG) 13 | ch = logging.StreamHandler() 14 | ch.setLevel(logging.DEBUG) 15 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 16 | ch.setFormatter(formatter) 17 | logger.addHandler(ch) 18 | 19 | num_data_frames = 1000 20 | 21 | data = np.random.randint(0, high=2**8, size=(1000, 1000), dtype=np.uint8) 22 | recv_q = Queue() # Queue where data will be received 23 | send_q = Queue() 24 | for i in range(num_data_frames): 25 | send_q.put({'topic': 'test', 'data': data}) 26 | send_q.put({'topic': 'test', 'data': 'stop'}) 27 | send_q.put({'topic': '', 'stop_pub': 'stop_pub'}) 28 | 29 | 30 | def test_func(data, queue): 31 | logger = get_logger(name=__name__) 32 | logger.info('Putting data to queue') 33 | queue.put(data) 34 | 35 | 36 | def empty_func(data): 37 | pass 38 | 39 | 40 | if __name__ == '__main__': 41 | sub1 = Process(target=subscriber, args=[test_func, 'test', recv_q]) 42 | sub1.start() 43 | t0 = time() 44 | publisher = Process(target=publisher, args=[send_q]) 45 | publisher.start() 46 | publisher.join() 47 | t1 = time()-2 # The publishers sleeps for 2 seconds, 1 at the beginning, 1 at the end 48 | print('Total ellapsed time: {:2.1f}s'.format(t1-t0)) 49 | bandwidth = num_data_frames*data.nbytes/(t1-t0) 50 | print('Bandwidth: {:4.1f}MB/s'.format(bandwidth/1024/1024)) 51 | i = 0 52 | while not recv_q.empty() or recv_q.qsize() > 0: 53 | d = recv_q.get() 54 | i += 1 55 | 56 | print('Total received points: {}/{}'.format(i, num_data_frames)) 57 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/science-and-fiction_capture.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to captureAn icon for "capture" from the Science and Fiction series on to [icon]. Downloaded from http://www.toicon.com/icons/science-and-fiction_capture by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | image/svg+xmlKamila Piątkowskahttp://www.toicon.com/icons/science-and-fiction_capture 8 | -------------------------------------------------------------------------------- /docs/making_virtual_environment.rst: -------------------------------------------------------------------------------- 1 | .. _python_virtual_environment: 2 | 3 | Setting up a Python Virtual Environment 4 | ======================================= 5 | 6 | This guide is thought for users on Windows that want to use virtual environments on their machines. 7 | 8 | 1. 9 | Run:: 10 | 11 | pip.exe install virtualenv 12 | 13 | At this point you have a working installation of virtual environment that will allow you to isolate your development from your computer, ensuring no mistakes on versions will happen. 14 | Let's create a new working environment called Testing 15 | 16 | 7. 17 | Run:: 18 | 19 | virtualenv.exe Testing 20 | 21 | This command will create a folder called Testing, in which all the packages you are going to install are going to 22 | be kept. 23 | 24 | 8. 25 | To activate the Virtual Environment, run:: 26 | 27 | .\Testing\Scripts\activate 28 | 29 | (The ``.`` at the beginning is very important). If an error happens (most likely) follow the instructions below. 30 | Windows has a weird way of handling execution policies and we are going to change that. 31 | 32 | Open PowerShell with administrator rights (normally, just right click on it and select run as administrator) 33 | Run the following command:: 34 | 35 | Set-ExecutionPolicy RemoteSigned 36 | 37 | This will allow to run local scripts. 38 | Go back to the PowerShell without administrative rights and run again the script activate. 39 | 40 | 9. 41 | When you are inside a virtual environment, you should see the name between ``()`` appearing at the beginning of the command line. 42 | 43 | Now you are working on a safe development environment. If you run:: 44 | 45 | pip freeze 46 | 47 | You will see a list of all the packages currently installed in your environment. The list should be empty. 48 | 49 | 10. 50 | To deactivate the virtual environment just run:: 51 | 52 | deactivate 53 | 54 | 11. 55 | If you run ``freeze`` again, you will see all the packages installed in the computer:: 56 | 57 | pip freeze 58 | 59 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to linkAn icon for "link" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_link by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_link 12 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to openAn icon for "open" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_open by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_open 12 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/duotone_edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to editAn icon for "edit" from the Duotone series on to [icon]. Downloaded from http://www.toicon.com/icons/duotone_edit by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 6 | 7 | 8 | 9 | 10 | image/svg+xmlCarol Liaohttp://www.toicon.com/icons/duotone_edit 11 | -------------------------------------------------------------------------------- /pynta/model/daqs/daq_dummy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Dummy DAQ 4 | ========= 5 | DAQ model for testing GUI and other functionalities. Based on the skeleton. It does not interact with any real 6 | device, it just generates random data and accepts inputs which have no effect. 7 | 8 | :copyright: Aquiles Carattino 9 | :license: GPLv3, see LICENSE for more details 10 | """ 11 | from pynta.util import get_logger 12 | from .skeleton import DaqBase 13 | 14 | logger = get_logger(__name__) 15 | 16 | 17 | class DAQDummy(DaqBase): 18 | def __init__(self, dev_number=None): 19 | super().__init__(dev_number) 20 | logger.info('Initialized device with number: %s' % dev_number) 21 | self.test_value = 0 22 | 23 | def triggerAnalog(self, conditions): 24 | """Triggers an analog measurement. It does not read the value. 25 | conditions -- a dictionary with the needed parameters for an analog acquisition. 26 | """ 27 | pass 28 | 29 | def getAnalog(self,conditions): 30 | """Gets the analog values acquired with the triggerAnalog function. 31 | conditions -- dictionary with the number of points ot be read 32 | """ 33 | pass 34 | 35 | 36 | def startMonitor(self,conditions): 37 | """Starts continuous acquisition of the specified channels with the specified timing interval. 38 | conditions['devs'] -- list of devices to monitor 39 | conditions['accuracy'] -- accuracy for the monitor. If not defined defaults to 0.1s 40 | """ 41 | pass 42 | 43 | def readMonitor(self): 44 | """Reads the monitor values of all the channels specified. 45 | """ 46 | pass 47 | 48 | def stopMonitor(self): 49 | """Stops all the tasks related to the monitor. 50 | """ 51 | pass 52 | 53 | def fastTimetrace(self,conditions): 54 | """ Acquires a fast timetrace of the selected devices. 55 | conditions['devs'] -- list of devices to monitor 56 | conditions['accuracy'] -- accuracy in milliseconds. 57 | conditions['time'] -- total time of acquisition for each channel in seconds. 58 | """ 59 | pass 60 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/hatch_download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to downloadAn icon for "download" from the Hatch series on to [icon]. Downloaded from http://www.toicon.com/icons/hatch_download by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 6 | 7 | 8 | 9 | 10 | image/svg+xmlCarol Liaohttp://www.toicon.com/icons/hatch_download 11 | -------------------------------------------------------------------------------- /pynta/model/experiment/subscriber.py: -------------------------------------------------------------------------------- 1 | """ 2 | Subscriber 3 | ========== 4 | Example script on how to run separate processes to process the data coming from a publisher like the one on 5 | ``publisher.py``. The first process just grabs the frame and puts it in a Queue. The Queue is then used by 6 | another process in order to analyse, process, save, etc. It has to be noted that on UNIX systems, getting 7 | from a queue with ``Queue.get()`` is particularly slow, much slower than serializing a numpy array with 8 | cPickle. 9 | """ 10 | from time import sleep 11 | 12 | import zmq 13 | from pynta.util import get_logger 14 | 15 | 16 | def subscribe(port, topic): 17 | context = zmq.Context() 18 | socket = context.socket(zmq.SUB) 19 | socket.connect("tcp://localhost:%s" % port) 20 | sleep(1) # Takes a while for TCP connections to propagate 21 | topic_filter = topic.encode('ascii') 22 | socket.setsockopt(zmq.SUBSCRIBE, topic_filter) 23 | return socket 24 | 25 | 26 | def subscriber(func, topic, event, *args, **kwargs): 27 | port = 5555 28 | if 'port' in kwargs: 29 | port = kwargs['port'] 30 | del kwargs['port'] 31 | 32 | logger = get_logger(name=__name__) 33 | context = zmq.Context() 34 | socket = context.socket(zmq.SUB) 35 | socket.connect("tcp://localhost:%s" % port) 36 | sleep(1) # Takes a while for TCP connections to propagate 37 | topic_filter = topic.encode('ascii') 38 | socket.setsockopt(zmq.SUBSCRIBE, topic_filter) 39 | logger.info('Subscribing {} to {}'.format(func.__name__, topic)) 40 | while not event.is_set(): 41 | topic = socket.recv_string() 42 | data = socket.recv_pyobj() # flags=0, copy=True, track=False) 43 | logger.debug('Got data of type {} on topic: {}'.format(type(data), topic)) 44 | if isinstance(data, str): 45 | logger.debug('Data: {}'.format(data)) 46 | if data == 'stop': 47 | logger.debug('Stopping subscriber on method {}'.format(func.__name__)) 48 | break 49 | 50 | func(data, *args, **kwargs) 51 | sleep(1) # Gives enough time for the publishers to finish sending data before closing the socket 52 | socket.close() 53 | logger.debug('Stopped subscriber {}'.format(func.__name__)) 54 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to playAn icon for "play" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_play by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_play 20 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/crossCut.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.crossCut.py 3 | =================================== 4 | Window that displays a 1D plot of a cross cut on the main window. 5 | 6 | .. sectionauthor:: Aquiles Carattino 7 | """ 8 | 9 | import pyqtgraph as pg 10 | import numpy as np 11 | import copy 12 | from pyqtgraph.Qt import QtGui 13 | 14 | 15 | class crossCutWindow(QtGui.QMainWindow): 16 | """ 17 | Simple window that relies on its parent for updating a 1-D plot. 18 | """ 19 | def __init__(self, parent=None): 20 | super(crossCutWindow, self).__init__(parent=parent) 21 | self.cc = pg.PlotWidget() 22 | self.setCentralWidget(self.cc) 23 | self.parent = parent 24 | y = np.random.random(100) 25 | self.p = self.cc.plot() 26 | changingLabel = QtGui.QLabel() 27 | font = changingLabel.font() 28 | font.setPointSize(16) 29 | self.text = pg.TextItem(text='', color=(200, 200, 200), border='w', fill=(0, 0, 255, 100)) 30 | self.text.setFont(font) 31 | self.cc.addItem(self.text) 32 | self.cc.setRange(xRange=(0,100), yRange=(-20,500)) 33 | 34 | def update(self): 35 | """ Updates the 1-D plot. It is called externally from the main window. 36 | """ 37 | if self.parent != None: 38 | if len(self.parent.tempimage) > 0: 39 | s = self.parent.camWidget.crossCut.value() 40 | (w,h) = np.shape(self.parent.tempimage) 41 | self.cc.setXRange(0,w) 42 | if s= 1): 45 | bg = self.parent.bgimage[:, s] 46 | d = d - bg 47 | self.p.setData(d) 48 | if np.mean(d) > 0: 49 | self.text.setText('Line %d\t Average: %d\t Max: %d\t' %(s, np.mean(d), np.max(d))) 50 | else: 51 | self.text.setText("Blank image") 52 | 53 | 54 | 55 | if __name__ == '__main__': 56 | import numpy as np 57 | app = QtGui.QApplication([]) 58 | win = crossCutWindow() 59 | x = np.random.normal(size=100) 60 | y = np.random.normal(size=100) 61 | win.cc.plot(x,y) 62 | win.show() 63 | app.instance().exec_() 64 | -------------------------------------------------------------------------------- /docs/media/example_config.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # Default parameters for the Tracking program 4 | # All parameters can be changed to accommodate user needs. 5 | # All parameters can be changed at runtime with the appropriate config window 6 | user: 7 | name: Aquiles 8 | 9 | saving: 10 | auto_save: False 11 | auto_save_waterfall: True 12 | directory: C:\Users\carat002\Data 13 | filename_video: Video # Can be the same filename for video and photo 14 | filename_photo: Snap 15 | filename_tracks: Tracks 16 | filename_waterfall: Waterfall 17 | filename_trajectory: Trajectory 18 | filename_log: Log 19 | max_memory: 200 # In megabytes 20 | 21 | GUI: 22 | length_waterfall: 20 # Total length of the Waterfall (lines) 23 | refresh_time: 50 # Refresh rate of the GUI (in ms) 24 | 25 | camera: 26 | model: dummy_camera # Should be a python file in model/cameras 27 | init: 0 # Initial arguments to pass when creating the camera 28 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 29 | model_camera: Orca Flash # To keep a registry of which camera was used in the experiment 30 | exposure_time: 30ms # Initial exposure time (in ms) 31 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 32 | binning_x: 1 # Binning 33 | binning_y: 1 34 | roi_x1: 0 35 | roi_x2: 599 36 | roi_y1: 0 37 | roi_y2: 399 38 | background: '' # Full path to background file, or empty for none. 39 | background_method: [Method1, Method2] 40 | 41 | waterfall: # Parameters for calculating the waterfall plot 42 | length: 20 # The total length of the waterfall (lines) 43 | vertical_bin: 10 # Total number of lines of the CCD to integrate 44 | 45 | movie: 46 | buffer_length: 1000 # Frames 47 | 48 | tracking: 49 | locate: 50 | diameter: 11 # Diameter of the particles (in pixels) to track, has to be an odd number 51 | invert: False 52 | minmass: 100 53 | link: 54 | memory: 3 55 | search_range: 4 56 | filter: # Filter spurious trajectories 57 | min_length: 25 58 | process: 59 | compute_drift: False 60 | um_pixel: 0.01 # Microns per pixel (calibration of the microscope) 61 | min_traj_length: 2 62 | min_mass: 0.05 63 | max_size: 50.0 64 | max_ecc: 1 65 | fps: 30 66 | param_1: 0. 67 | param_2: 0 68 | 69 | debug: 70 | logging_level: Nothing # One of Nothing, Debug, Info, Warning, Error 71 | queue_memory: False 72 | to_screen: True -------------------------------------------------------------------------------- /docs/_static/example_config.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # Default parameters for the Tracking program 4 | # All parameters can be changed to accommodate user needs. 5 | # All parameters can be changed at runtime with the appropriate config window 6 | user: 7 | name: Aquiles 8 | 9 | saving: 10 | auto_save: False 11 | auto_save_waterfall: True 12 | directory: C:\Users\carat002\Data 13 | filename_video: Video # Can be the same filename for video and photo 14 | filename_photo: Snap 15 | filename_tracks: Tracks 16 | filename_waterfall: Waterfall 17 | filename_trajectory: Trajectory 18 | filename_log: Log 19 | max_memory: 200 # In megabytes 20 | 21 | GUI: 22 | length_waterfall: 20 # Total length of the Waterfall (lines) 23 | refresh_time: 50 # Refresh rate of the GUI (in ms) 24 | 25 | camera: 26 | model: dummy_camera # Should be a python file in model/cameras 27 | init: 0 # Initial arguments to pass when creating the camera 28 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 29 | model_camera: Orca Flash # To keep a registry of which camera was used in the experiment 30 | exposure_time: 30ms # Initial exposure time (in ms) 31 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 32 | binning_x: 1 # Binning 33 | binning_y: 1 34 | roi_x1: 0 35 | roi_x2: 599 36 | roi_y1: 0 37 | roi_y2: 399 38 | background: '' # Full path to background file, or empty for none. 39 | background_method: [Method1, Method2] 40 | 41 | waterfall: # Parameters for calculating the waterfall plot 42 | length: 20 # The total length of the waterfall (lines) 43 | vertical_bin: 10 # Total number of lines of the CCD to integrate 44 | 45 | movie: 46 | buffer_length: 1000 # Frames 47 | 48 | tracking: 49 | locate: 50 | diameter: 11 # Diameter of the particles (in pixels) to track, has to be an odd number 51 | invert: False 52 | minmass: 100 53 | link: 54 | memory: 3 55 | search_range: 4 56 | filter: # Filter spurious trajectories 57 | min_length: 25 58 | process: 59 | compute_drift: False 60 | um_pixel: 0.01 # Microns per pixel (calibration of the microscope) 61 | min_traj_length: 2 62 | min_mass: 0.05 63 | max_size: 50.0 64 | max_ecc: 1 65 | fps: 30 66 | param_1: 0. 67 | param_2: 0 68 | 69 | debug: 70 | logging_level: Nothing # One of Nothing, Debug, Info, Warning, Error 71 | queue_memory: False 72 | to_screen: True -------------------------------------------------------------------------------- /examples/config/nanocet.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # Default parameters for the Tracking program 4 | # All parameters can be changed to accommodate user needs. 5 | # All parameters can be changed at runtime with the appropriate config window 6 | user: 7 | name: Aquiles 8 | 9 | arduino: 10 | port: COM3 11 | 12 | saving: 13 | auto_save: False 14 | auto_save_waterfall: True 15 | directory: D:\Data 16 | filename_video: Video # Can be the same filename for video and photo 17 | filename_photo: Snap 18 | filename_tracks: Tracks 19 | filename_waterfall: Waterfall 20 | filename_trajectory: Trajectory 21 | filename_log: Log 22 | max_memory: 200 # In megabytes 23 | 24 | GUI: 25 | length_waterfall: 20 # Total length of the Waterfall (lines) 26 | refresh_time: 50 # Refresh rate of the GUI (in ms) 27 | 28 | camera: 29 | model: basler # Should be a python file in model/cameras 30 | init: 0 # Initial arguments to pass when creating the camera 31 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 32 | model_camera: ACE # To keep a registry of which camera was used in the experiment 33 | exposure_time: 30ms # Initial exposure time (in ms) 34 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 35 | binning_x: 1 # Binning 36 | binning_y: 1 37 | roi_x1: 0 38 | roi_x2: 1932 39 | roi_y1: 0 40 | roi_y2: 1200 41 | background: '' # Full path to background file, or empty for none. 42 | background_method: [Method1, Method2] 43 | 44 | waterfall: # Parameters for calculating the waterfall plot 45 | length: 20 # The total length of the waterfall (lines) 46 | vertical_bin: 10 # Total number of lines of the CCD to integrate 47 | 48 | movie: 49 | buffer_length: 1000 # Frames 50 | 51 | tracking: 52 | locate: 53 | diameter: 5 # Diameter of the particles (in pixels) to track, has to be an odd number 54 | invert: False 55 | minmass: 100 56 | link: 57 | memory: 3 58 | search_range: 5 59 | filter: # Filter spurious trajectories 60 | min_length: 25 61 | process: 62 | compute_drift: False 63 | um_pixel: 0.15 # Microns per pixel (calibration of the microscope) 64 | min_traj_length: 2 65 | min_mass: 0.05 66 | max_size: 50.0 67 | max_ecc: 1 68 | fps: 30 69 | param_1: 0. 70 | param_2: 0 71 | 72 | debug: 73 | logging_level: Nothing # One of Nothing, Debug, Info, Warning, Error 74 | queue_memory: False 75 | to_screen: True 76 | 77 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/trajectoryWidget.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.trajectoryWidget.py 3 | ======================================== 4 | This widget only displays the output of the special worker. It is mainly for prototyping purposes. It displays a scatter 2D plot because it is the current output of the special task worker, but in principle it can be adapted to any other need. 5 | 6 | .. todo:: adapt this widget for a useful case. 7 | 8 | .. sectionauthor:: Aquiles Carattino 9 | """ 10 | 11 | import pyqtgraph as pg 12 | from pyqtgraph.Qt import QtGui 13 | 14 | 15 | class trajectoryWidget(pg.GraphicsView): 16 | """Simple plot class for showing the 2D trajectory""" 17 | def __init__(self,parent=None): 18 | # QtGui.QWidget.__init__(self, parent) 19 | super(trajectoryWidget, self).__init__() 20 | #self.layout = QtGui.QHBoxLayout(self) 21 | 22 | #gv = pg.GraphicsView() 23 | self.l = QtGui.QGraphicsGridLayout() 24 | self.l.setHorizontalSpacing(0) 25 | self.l.setVerticalSpacing(0) 26 | 27 | self.vb = pg.ViewBox() 28 | 29 | self.plot = pg.PlotDataItem() 30 | self.vb.addItem(self.plot) 31 | self.l.addItem(self.vb, 0, 1) 32 | self.centralWidget.setLayout(self.l) 33 | self.xScale = pg.AxisItem(orientation='bottom', linkView=self.vb) 34 | self.l.addItem(self.xScale, 1, 1) 35 | self.yScale = pg.AxisItem(orientation='left', linkView=self.vb) 36 | self.l.addItem(self.yScale, 0, 0) 37 | 38 | self.xScale.setLabel('X Axis', units='px') 39 | self.yScale.setLabel('Y Axis', units='px') 40 | #self.view = pg.GraphicsLayoutWidget() 41 | # 42 | # self.vb = pg.ViewBox() 43 | # self.plot = pg.PlotItem() 44 | # self.vb.addItem(self.plot) 45 | # self.layout.addWidget(self.vb) 46 | # self.setLayout(self.layout) 47 | 48 | if __name__ == '__main__': 49 | from PyQt4.Qt import QApplication 50 | import sys 51 | import numpy as np 52 | 53 | app = QApplication(sys.argv) 54 | t = trajectoryWidget() 55 | def rand(n): 56 | data = np.random.random(n) 57 | data[int(n*0.1):int(n*0.13)] += .5 58 | data[int(n*0.18)] += 2 59 | data[int(n*0.1):int(n*0.13)] *= 5 60 | data[int(n*0.18)] *= 20 61 | return data, np.arange(n, n+len(data)) / float(n) 62 | 63 | yd, xd = rand(10000) 64 | t.plot.setData(y=yd, x=xd) 65 | 66 | t.show() 67 | sys.exit(app.exec_()) 68 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/duotone_chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to chartAn icon for "chart" from the Duotone series on to [icon]. Downloaded from http://www.toicon.com/icons/duotone_chart by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | image/svg+xmlCarol Liaohttp://www.toicon.com/icons/duotone_chart 19 | -------------------------------------------------------------------------------- /pynta/util/example_config.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # Default parameters for the Tracking program 4 | # All parameters can be changed to accommodate user needs. 5 | # All parameters can be changed at runtime with the appropriate config window 6 | user: 7 | name: Aquiles 8 | 9 | saving: 10 | auto_save: False 11 | auto_save_waterfall: True 12 | directory: C:\Users\carat002\Data 13 | filename_video: Video # Can be the same filename for video and photo 14 | filename_photo: Snap 15 | filename_tracks: Tracks 16 | filename_waterfall: Waterfall 17 | filename_trajectory: Trajectory 18 | filename_log: Log 19 | max_memory: 200 # In megabytes 20 | 21 | GUI: 22 | length_waterfall: 20 # Total length of the Waterfall (lines) 23 | refresh_time: 50 # Refresh rate of the GUI (in ms) 24 | 25 | camera: 26 | model: dummy_camera # Should be a python file in model/cameras 27 | init: 0 # Initial arguments to pass when creating the camera 28 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 29 | model_camera: Orca Flash # To keep a registry of which camera was used in the experiment 30 | exposure_time: 30ms # Initial exposure time (in ms) 31 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 32 | binning_x: 1 # Binning 33 | binning_y: 1 34 | roi_x1: 0 35 | roi_x2: 599 36 | roi_y1: 0 37 | roi_y2: 399 38 | background: '' # Full path to background file, or empty for none. 39 | background_method: [Method1, Method2] 40 | 41 | waterfall: # Parameters for calculating the waterfall plot 42 | length: 20 # The total length of the waterfall (lines) 43 | vertical_bin: 10 # Total number of lines of the CCD to integrate 44 | 45 | movie: 46 | buffer_length: 1000 # Frames 47 | 48 | tracking: 49 | locate: 50 | diameter: 11 # Diameter of the particles (in pixels) to track, has to be an odd number 51 | invert: False 52 | minmass: 100 53 | preprocess: False # Avoid bandpass filtering step 54 | max_iterations: 3 # Number of iterations to refine center of mass 55 | link: 56 | memory: 3 57 | search_range: 4 58 | filter: # Filter spurious trajectories 59 | min_length: 25 60 | process: 61 | compute_drift: False 62 | um_pixel: 0.01 # Microns per pixel (calibration of the microscope) 63 | min_traj_length: 2 64 | min_mass: 0.05 65 | max_size: 50.0 66 | max_ecc: 1 67 | fps: 30 68 | param_1: 0. 69 | param_2: 0 70 | 71 | debug: 72 | logging_level: Nothing # One of Nothing, Debug, Info, Warning, Error 73 | queue_memory: False 74 | to_screen: True 75 | 76 | -------------------------------------------------------------------------------- /docs/example_config.rst: -------------------------------------------------------------------------------- 1 | .. _example-config: 2 | 3 | The Config File 4 | =============== 5 | To start the program, it is necessary to define a configuration file. You can :download:`get a config file here `. However, the best place to find the latest examples of config files is on the 6 | `Github Repository `_. The idea behind the config file is that it makes it 7 | transparent both to the end user and to the developer the different settings available throughout the program. 8 | 9 | The example config files only show the minimum possible contents. You are free to add as many entries as you would like. 10 | However, they are not going to be displayed in the GUI, nor will be used automatically. They will, however, be stored as 11 | metadata together with all the files. You could use the config file in order to annotate your experiments, for example. 12 | 13 | The Format 14 | ---------- 15 | The config file is formatted as a YAML file. These files are very easy to transform into python dictionaries and are very 16 | easy to type. So, for example, to change how the tracking algorithm works, one would change the following lines: 17 | 18 | .. code-block:: yaml 19 | 20 | tracking: 21 | locate: 22 | diameter: 11 # Diameter of the particles (in pixels) to track, has to be an odd number 23 | invert: False 24 | minmass: 100 25 | 26 | Note that for the file to make sense, it has to be indexed with 2 spaces. When reading it, it automatically converts some 27 | data types. For example, diameter will be available as ``config['tracking']['locate']['diameter']`` and will be of type 28 | integer. ``invert`` will be a boolean, etc. If the data type is not clear, the default is a string. So, for example: 29 | 30 | .. code-block:: yaml 31 | 32 | camera: 33 | exposure_time: 30ms # Initial exposure time (in ms) 34 | 35 | Will generate a ``config['camera']['exposure_time']`` of type string, that will need to be transformed to a quantity 36 | later on. Note also that comments are ignored (after the ``#`` nothing is read). 37 | 38 | Real Cameras 39 | ------------ 40 | Currently Pynta supports a handful of cameras. If you would like to load a hamamatsu camera, you should change the following line: 41 | 42 | .. code-block:: yaml 43 | 44 | camera: 45 | model: dummy_camera 46 | 47 | with the following: 48 | 49 | .. code-block:: yaml 50 | :emphasize-lines: 2 51 | 52 | camera: 53 | model: hamamatsu 54 | 55 | If you would like to understand how the loading of the camera works, in order to add your own, you can check :func:`~pynta.model.experiment.nano_cet.win_nanocet.NanoCET.initialize_camera`. 56 | 57 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/pictogram_record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to recordAn icon for "record" from the Pictogram series on to [icon]. Downloaded from http://www.toicon.com/icons/pictogram_record by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | image/svg+xmlJo Szczepanskahttp://www.toicon.com/icons/pictogram_record 13 | -------------------------------------------------------------------------------- /environment_docs.yml: -------------------------------------------------------------------------------- 1 | name: pynta_docs 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - alabaster=0.7.12=py36_0 7 | - asn1crypto=0.24.0=py36_0 8 | - babel=2.7.0=py_0 9 | - blas=1.0=mkl 10 | - ca-certificates=2019.5.15=0 11 | - certifi=2019.6.16=py36_0 12 | - cffi=1.12.3=py36h7a1dbc1_0 13 | - chardet=3.0.4=py36_1 14 | - cryptography=2.7=py36h7a1dbc1_0 15 | - docutils=0.14=py36h6012d8f_0 16 | - freetype=2.10.0=h5db478b_0 17 | - h5py=2.9.0=py36h5e291fa_0 18 | - hdf5=1.10.4=h7ebc959_0 19 | - icc_rt=2019.0.0=h0cc432a_1 20 | - icu=58.2=ha66f8fd_1 21 | - idna=2.8=py36_0 22 | - imagesize=1.1.0=py36_0 23 | - intel-openmp=2019.4=245 24 | - jinja2=2.10.1=py36_0 25 | - jpeg=9b=hb83a4c4_2 26 | - kiwisolver=1.1.0=py36he980bc4_0 27 | - libpng=1.6.37=h2a8f88b_0 28 | - libsodium=1.0.16=h9d3ae62_0 29 | - llvmlite=0.29.0=py36ha925a31_0 30 | - markupsafe=1.1.1=py36he774522_0 31 | - matplotlib=3.1.1=py36_0 32 | - matplotlib-base=3.1.1=py36h2852a4a_0 33 | - mkl=2019.4=245 34 | - mkl_fft=1.0.12=py36h14836fe_0 35 | - mkl_random=1.0.2=py36h343c172_0 36 | - numba=0.45.0=py36hf9181ef_0 37 | - numpy=1.16.4=py36h19fb1c0_0 38 | - numpy-base=1.16.4=py36hc3f5095_0 39 | - openssl=1.1.1c=he774522_1 40 | - packaging=19.0=py36_0 41 | - pandas=0.24.2=py36ha925a31_0 42 | - pint=0.9=py36_2 43 | - pip=19.1.1=py36_0 44 | - pycparser=2.19=py36_0 45 | - pygments=2.4.2=py_0 46 | - pyopenssl=19.0.0=py36_0 47 | - pyparsing=2.4.1=py_0 48 | - pyqt=5.9.2=py36h6538335_2 49 | - pyqtgraph=0.10.0=py36h28b3542_3 50 | - pyreadline=2.1=py36_1 51 | - pysocks=1.7.0=py36_0 52 | - python=3.6.8=h9f7ef89_7 53 | - python-dateutil=2.8.0=py36_0 54 | - pytz=2019.1=py_0 55 | - pyvisa=1.9.1=py36_1000 56 | - pyyaml=5.1.1=py36he774522_0 57 | - pyzmq=18.0.0=py36ha925a31_0 58 | - qt=5.9.7=vc14h73c81de_0 59 | - requests=2.22.0=py36_0 60 | - scipy=1.2.1=py36h29ff71c_0 61 | - setuptools=41.0.1=py36_0 62 | - sip=4.19.8=py36h6538335_0 63 | - six=1.12.0=py36_0 64 | - snowballstemmer=1.9.0=py_0 65 | - sphinx=2.1.2=py_0 66 | - sphinx_rtd_theme=0.4.3=py_0 67 | - sphinxcontrib-applehelp=1.0.1=py_0 68 | - sphinxcontrib-devhelp=1.0.1=py_0 69 | - sphinxcontrib-htmlhelp=1.0.2=py_0 70 | - sphinxcontrib-jsmath=1.0.1=py_0 71 | - sphinxcontrib-qthelp=1.0.2=py_0 72 | - sphinxcontrib-serializinghtml=1.1.3=py_0 73 | - sqlite=3.29.0=he774522_0 74 | - tornado=6.0.3=py36hfa6e2cd_0 75 | - trackpy=0.4.1=py_1 76 | - urllib3=1.24.2=py36_0 77 | - vc=14.1=h0510ff6_4 78 | - vs2015_runtime=14.15.26706=h3a45250_4 79 | - wheel=0.33.4=py36_0 80 | - win_inet_pton=1.1.0=py36_0 81 | - wincertstore=0.2=py36h7fe50ca_0 82 | - yaml=0.1.7=hc54c509_2 83 | - zeromq=4.3.1=h33f27b4_3 84 | - zlib=1.2.11=h62dcd97_3 85 | - pip: 86 | - cycler==0.10.0 87 | - pyqt5==5.13.0 88 | - pyqt5-sip==4.19.18 89 | prefix: C:\Users\aquic\.conda\envs\pynta_docs 90 | 91 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/avocado_calibrate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to calibrateAn icon for "calibrate" from the Avocado series on to [icon]. Downloaded from http://www.toicon.com/icons/avocado_calibrate by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | image/svg+xmlShannon E Thomashttp://www.toicon.com/icons/avocado_calibrate 20 | -------------------------------------------------------------------------------- /docs/index.rst.bak: -------------------------------------------------------------------------------- 1 | PyNTA: Python Nanoparticle Tracking Analysis 2 | ============================================= 3 | 4 | Nanoparticle tracking analysis refers to a technique used for characterizing small objects optically. The base principle 5 | is that by following the movement of nanoparticles over time, it is possible to calculate their diffusion properties and 6 | thus derive their size. 7 | 8 | PyNTA aims at bridging the gap between experiments and results by combining data acquisition and analysis in one simple 9 | to use program. 10 | 11 | PyNTA is shipped as a package that can be installed into a virtual environment with the use of pip. It can be both 12 | triggered with a built in function or can be included into larger projects. 13 | 14 | Installing 15 | ---------- 16 | The best place to look for the code of the program is the repository at 17 | `https://github.com/nanoepics/pynta `_. In short, if you want to install PyNTA you 18 | can run the following command:: 19 | 20 | pip install git+https://github.com/nanoepics/pynta 21 | 22 | If you need further assistance with the installation of the code, please check :ref:`installing`. 23 | 24 | Start the program 25 | ----------------- 26 | After installing, the program can be started from the command line by running the following: 27 | 28 | python -m pynta -c config.yml 29 | 30 | Remember that config.yml needs to exist. To create your own configuration file, you can start with the example provided 31 | in the `examples folder `_. Once the program starts, it will 32 | look like the following: 33 | 34 | .. figure:: media/screenshot_01.png 35 | :scale: 50 % 36 | :alt: screenshot 37 | 38 | Contributing to the Program 39 | --------------------------- 40 | The program is open source and therefore you can modify it in any way that you see fit. You have to remember that the 41 | code was written with a specific experiment in mind and therefore it may not fulfill or the requirements of more 42 | advanced imaging software. 43 | 44 | However the design of the program is such that would allow its expansion to meet future needs. In case you are wondering 45 | how the code can be improved you can start by reading :ref:`improving`, or directly submerge yourself in the 46 | documentation of the different classes :ref:`PyNTA`. 47 | 48 | If you want to start right away to improve the code, you can always look at the :ref:`todo`. 49 | 50 | Acknowledgements 51 | ---------------- 52 | This program was developed by Aquiles Carattino with the support of funding from NWO, The Netherlands Scientific 53 | Organization, under a Vici grant from Prof. Allard Mosk. This work was carried on at Utrecht University in the months 54 | between June 2018 and December 2018. 55 | 56 | .. toctree:: 57 | :hidden: 58 | :maxdepth: 2 59 | :caption: Contents: 60 | 61 | installing 62 | modules 63 | config 64 | python_working 65 | installing 66 | starting 67 | improving 68 | UUTrack 69 | todo 70 | 71 | 72 | Indices and tables 73 | ================== 74 | 75 | * :ref:`genindex` 76 | * :ref:`modindex` 77 | * :ref:`search` 78 | -------------------------------------------------------------------------------- /examples/config/dispertech.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # Default parameters for the Tracking program 4 | # All parameters can be changed to accommodate user needs. 5 | # All parameters can be changed at runtime with the appropriate config window 6 | user: 7 | name: Aquiles 8 | 9 | arduino: 10 | port: COM3 11 | 12 | saving: 13 | auto_save: False 14 | auto_save_waterfall: True 15 | directory: D:\Data 16 | filename_video: Video # Can be the same filename for video and photo 17 | filename_photo: Snap 18 | filename_tracks: Tracks 19 | filename_waterfall: Waterfall 20 | filename_trajectory: Trajectory 21 | filename_log: Log 22 | max_memory: 200 # In megabytes 23 | 24 | GUI: 25 | length_waterfall: 20 # Total length of the Waterfall (lines) 26 | refresh_time: 50 # Refresh rate of the GUI (in ms) 27 | 28 | camera_fiber: 29 | model: basler # Should be a python file in model/cameras 30 | init: daA1280 # Initial arguments to pass when creating the camera 31 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 32 | model_camera: Dart # To keep a registry of which camera was used in the experiment 33 | exposure_time: 30ms # Initial exposure time (in ms) 34 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 35 | binning_x: 1 # Binning 36 | binning_y: 1 37 | roi_x1: 0 38 | roi_x2: 1280 39 | roi_y1: 0 40 | roi_y2: 960 41 | background: '' # Full path to background file, or empty for none. 42 | background_method: [Method1, Method2] 43 | 44 | camera_microscope: 45 | model: basler # Should be a python file in model/cameras 46 | init: acA1920 # Initial arguments to pass when creating the camera 47 | #extra_args: [extra, arguments] # Extra arguments that can be passed when constructing the model 48 | model_camera: ACE # To keep a registry of which camera was used in the experiment 49 | exposure_time: 30ms # Initial exposure time (in ms) 50 | fps: 30 # Frames per second, should either be defined by the camera or within the model based on timing 51 | binning_x: 1 # Binning 52 | binning_y: 1 53 | roi_x1: 0 54 | roi_x2: 1920 55 | roi_y1: 0 56 | roi_y2: 1200 57 | background: '' # Full path to background file, or empty for none. 58 | background_method: [Method1, Method2] 59 | 60 | waterfall: # Parameters for calculating the waterfall plot 61 | length: 20 # The total length of the waterfall (lines) 62 | vertical_bin: 10 # Total number of lines of the CCD to integrate 63 | 64 | movie: 65 | buffer_length: 1000 # Frames 66 | 67 | tracking: 68 | locate: 69 | diameter: 5 # Diameter of the particles (in pixels) to track, has to be an odd number 70 | invert: False 71 | minmass: 100 72 | link: 73 | memory: 3 74 | search_range: 5 75 | filter: # Filter spurious trajectories 76 | min_length: 25 77 | process: 78 | compute_drift: False 79 | um_pixel: 0.15 # Microns per pixel (calibration of the microscope) 80 | min_traj_length: 2 81 | min_mass: 0.05 82 | max_size: 50.0 83 | max_ecc: 1 84 | fps: 30 85 | param_1: 0. 86 | param_2: 0 87 | 88 | debug: 89 | logging_level: Nothing # One of Nothing, Debug, Info, Warning, Error 90 | queue_memory: False 91 | to_screen: True 92 | 93 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PyNTA: Python Nanoparticle Tracking Analysis 2 | ============================================= 3 | 4 | Nanoparticle tracking analysis refers to a technique used for characterizing small objects optically. The base principle 5 | is that by following the movement of nanoparticles over time, it is possible to calculate their diffusion properties and 6 | thus derive their size. 7 | 8 | PyNTA aims at bridging the gap between experiments and results by combining data acquisition and analysis in one simple 9 | to use program. 10 | 11 | PyNTA is shipped as a package that can be installed into a virtual environment with the use of pip. It can be both 12 | triggered with a built in function or can be included into larger projects. 13 | 14 | Installing 15 | ---------- 16 | PyNTA can be easily installed by running:: 17 | 18 | pip install pynta 19 | 20 | However, it is also possible to install the latest development version. The source code of the program is hosted at 21 | `https://github.com/nanoepics/pynta `_. If you want to install the development 22 | version of PyNTA you can run the following command:: 23 | 24 | pip install git+https://github.com/nanoepics/pynta 25 | 26 | If you need further assistance with the installation of the code, please check :ref:`installing`. 27 | 28 | Start the program 29 | ----------------- 30 | After installing, the program can be started from the command line by running the following: 31 | 32 | python -m pynta -c config.yml 33 | 34 | Remember that config.yml needs to exist. To create your own configuration file, you can start with the example provided 35 | in the `examples folder `_. Once the program starts, it will 36 | look like the following: 37 | 38 | .. figure:: media/screenshot_01.png 39 | :scale: 50 % 40 | :alt: screenshot 41 | 42 | Contributing to the Program 43 | --------------------------- 44 | The program is open source and therefore you can modify it in any way that you see fit. You have to remember that the 45 | code was written with a specific experiment in mind and therefore it may not fulfill or the requirements of more 46 | advanced imaging software. 47 | 48 | However the design of the program is such that would allow its expansion to meet future needs. In case you are wondering 49 | how the code can be improved you can start by reading :ref:`improving`, or directly submerge yourself in the 50 | documentation of the different classes :ref:`PyNTA`. 51 | 52 | If you want to start right away to improve the code, you can always look at the :ref:`todo`. 53 | 54 | Acknowledgements 55 | ---------------- 56 | This program was developed by Aquiles Carattino with the support of funding from NWO, The Netherlands Scientific 57 | Organization, under VICI grant (PI: Prof. Allard Mosk) and Projectruimte FOM.PR1.005 grant (PI: Dr. Sanli Faez) . This work was carried on at Utrecht University in the months between June 2018 and June 2019. 58 | 59 | .. toctree:: 60 | :maxdepth: 2 61 | :caption: Contents 62 | 63 | installing 64 | example_config 65 | getting_started 66 | contribute_codebase 67 | making_virtual_environment 68 | developers/index 69 | list_todo 70 | 71 | 72 | Indices and tables 73 | ================== 74 | 75 | * :ref:`genindex` 76 | * :ref:`modindex` 77 | * :ref:`search` 78 | -------------------------------------------------------------------------------- /pynta/model/experiment/nanoparticle_tracking/decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Nano CET decorators 3 | =================== 4 | 5 | Useful decorators for the Nano CET experiment. 6 | For example, to check whether the camera was initialized already or not before calling a method that needs an active 7 | camera. 8 | 9 | """ 10 | import warnings 11 | from functools import wraps 12 | from multiprocessing import Process 13 | from threading import Thread 14 | 15 | from pynta.model.experiment.nanoparticle_tracking.exceptions import CameraNotInitialized 16 | from pynta.util.log import get_logger 17 | 18 | 19 | def check_camera(func): 20 | """Decorator to check whether the camera has been already initialized. 21 | It raises an error if it has not been.""" 22 | @wraps(func) 23 | def func_wrapper(cls, *args, **kwargs): 24 | if hasattr(cls, 'camera'): 25 | if cls.camera is not None: 26 | return func(cls, *args, **kwargs) 27 | 28 | if hasattr(cls, 'logger'): 29 | cls.logger.error('Trying to run {} before initializing a camera'.format(func.__name__)) 30 | 31 | raise CameraNotInitialized('At least one camera has to be initialized before calling {}'.format(func.__name__)) 32 | 33 | return func_wrapper 34 | 35 | 36 | def check_not_acquiring(func): 37 | """Decorator to check that the camera is not acquiring before running the function. This prevents, for example, 38 | changing the ROI while a movie is in progress. 39 | This decorator works in conjuction with ``check_camera``, i.e., it will not double check whether the camera was 40 | initialized or not. 41 | 42 | """ 43 | @wraps(func) 44 | def func_wrapper(cls, *args, **kwargs): 45 | if cls.camera.running: 46 | warnings.warn('Trying to run {} while the camera is still running'.format(func.__name__)) 47 | return 48 | 49 | return func(cls, *args, **kwargs) 50 | 51 | return func_wrapper 52 | 53 | 54 | def make_async_thread(func): 55 | """ Simple decorator to make a method run on a separated thread. It requires that the class has a property 56 | called ``_threads`` which is a list and holds all the running threads. 57 | """ 58 | @wraps(func) 59 | def func_wrapper(*args, **kwargs): 60 | logger = get_logger(name=__name__) 61 | logger.info('Starting new thread for {}'.format(func.__name__)) 62 | args[0]._threads.append([func.__name__, Thread(target=func, args=args, kwargs=kwargs)]) 63 | args[0]._threads[-1][1].start() 64 | logger.debug('In total there are {} threads'.format(len(args[0]._threads))) 65 | 66 | return func_wrapper 67 | 68 | 69 | def make_async_process(func): 70 | """ Simple decorator to start a method as a separated process. It requires that the class has a property 71 | called ``_processes`` which is a list and holds all the running processes. 72 | """ 73 | @wraps(func) 74 | def func_wrapper(*args, **kwargs): 75 | logger = get_logger(name=__name__) 76 | logger.info('Starting a new thread for {}'.format(func.__name__)) 77 | args[0]._processes.append([func.__name__, Process(target=func, args=args, kwargs=kwargs)]) 78 | args[0]._processes[-1][1].start() 79 | logger.debug('In total there are {} processes'.format(len(args[0]._threads))) 80 | 81 | return func_wrapper 82 | 83 | -------------------------------------------------------------------------------- /pynta/model/experiment/dispertech/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from pathlib import Path 3 | from typing import Tuple 4 | 5 | import yaml 6 | 7 | from pynta.util import get_logger 8 | 9 | logger = get_logger(__name__) 10 | 11 | 12 | def initialize_database() -> Tuple[sqlite3.Connection, sqlite3.Cursor]: 13 | """ We are using a simple SQLite database to store some information regarding the use of the program. The most 14 | important feature is being able to store the latest set parameters in such a way the they can be recovered when 15 | restarting the program. If the database does not exist, it should be created. 16 | """ 17 | home = Path.home() 18 | pynta_path = Path(home, '.pynta') 19 | if not pynta_path.is_dir(): 20 | try: 21 | pynta_path.mkdir() 22 | except PermissionError: 23 | logger.error(f'This user does not have access to {pynta_path}. Consider another directory') 24 | raise 25 | 26 | database_path = Path(pynta_path, 'config.sqlite') 27 | if not database_path.is_file(): 28 | logger.info('Going to create a new database') 29 | conn = sqlite3.connect(str(database_path)) 30 | cur = conn.cursor() 31 | sql_command = """CREATE TABLE configs ( 32 | id INTEGER PRIMARY KEY AUTOINCREMENT, 33 | timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, 34 | name VARCHAR, 35 | description VARCHAR) 36 | """ 37 | cur.execute(sql_command) 38 | sql_command = """CREATE TABLE users ( 39 | id INTEGER PRIMARY KEY AUTOINCREMENT, 40 | name VARCHAR 41 | timestamp DATETIME DEFAULT CURRENT_TIMESTAMP) 42 | """ 43 | cur.execute(sql_command) 44 | sql_command = """CREATE TABLE samples ( 45 | id INTEGER PRIMARY KEY AUTOINCREMENT, 46 | name VARCHAR, 47 | description TEXT 48 | timestamp DATETIME DEFAULT CURRENT_TIMESTAMP) 49 | """ 50 | cur.execute(sql_command) 51 | sql_command = """CREATE TABLE experiments ( 52 | id INTEGER PRIMARY KEY AUTOINCREMENT, 53 | name VARCHAR, 54 | user_id INTEGER NOT NULL, 55 | FOREIGN KEY (user_id) REFERENCES users(id)) 56 | """ 57 | cur.execute(sql_command) 58 | sql_command = """INSERT INTO configs (name, description) 59 | VALUES 60 | ('Initial Database', 'Starting Fiber Tracking for the first time') 61 | """ 62 | cur.execute(sql_command) 63 | conn.commit() 64 | else: 65 | logger.info('Database found') 66 | conn = sqlite3.connect(str(database_path)) 67 | cur = conn.cursor() 68 | return conn, cur 69 | 70 | 71 | def store_config(db: Tuple[sqlite3.Connection, sqlite3.Cursor], config: dict) -> None: 72 | conn = db[0] 73 | cur = db[1] 74 | data = yaml.dump(config) 75 | sql_command = f"""INSERT INTO configs (name, description) VALUES 76 | ('Saved Config', '{data}') 77 | """ 78 | cur.execute(sql_command) 79 | conn.commit() 80 | 81 | -------------------------------------------------------------------------------- /pynta/model/experiment/dispertech/fiber_tracking.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tracking in Hollow Optical Fibers 4 | ================================= 5 | 6 | This experiment is very similar to the nanoparticle tracking experiment, but everything happens inside a hollow 7 | optical fiber. Some steps are different; for example, the user first has to focus the image of the camera on the 8 | top part of the setup in order to couple the laser to the optical fiber. Then, the user needs to maximise the 9 | background signal on the microscope's camera in order to fine-tune the coupling. 10 | 11 | The measurement is essentially a 1-D measurement of diffusion, in which equations need to be adapted for including 12 | diffusion in a cylinder. 13 | 14 | :copyright: Aquiles Carattino 15 | :license: GPLv3, see LICENSE for more details 16 | """ 17 | import importlib 18 | import sqlite3 19 | from pathlib import Path 20 | 21 | from pynta.model.experiment.base_experiment import BaseExperiment 22 | from pynta.model.experiment.dispertech.util import load_camera_module, instantiate_camera 23 | from pynta.util import get_logger 24 | 25 | 26 | logger = get_logger(name=__name__) 27 | 28 | 29 | class FiberTracking(BaseExperiment): 30 | """ Experiment class for performing nanoparticle tracking analysis inside a hollow optical fiber. 31 | """ 32 | SINGLE_SNAP_BKG = 0 33 | """Uses only one image to correct the background""" 34 | 35 | ROLLING_AVERAGE = 1 36 | """Uses a window of averages to correct the background""" 37 | 38 | BACKGROUND_CORRECTION = ( 39 | ('single_snap', SINGLE_SNAP_BKG), 40 | ('rolling_avg', ROLLING_AVERAGE) 41 | ) 42 | 43 | def __init__(self, filename=None): 44 | super().__init__(filename) 45 | self.cameras = { 46 | 'fiber': None, 47 | 'microscope': None 48 | } 49 | self.initialize_threads = [] 50 | 51 | def initialize_cameras(self): 52 | """ The experiment requires two cameras, and they need to be initialized before we can proceed with the 53 | measurement. This requires two entries in the config file with names ``camera_fiber``, which refers to the 54 | camera which monitors the end of the fiber and ``camera_microscope``, which is the one that is used to do the 55 | real measurement. 56 | 57 | """ 58 | 59 | self.cameras['fiber'] = instantiate_camera(config=self.config['camera_fiber']) 60 | self.cameras['microscope'] = instantiate_camera(config=self.config['camera_microscope']) 61 | 62 | logger.info('Initializing the cameras...') 63 | for k, camera in self.cameras.items(): 64 | logger.info(f'Initializing {k} camera: {camera}') 65 | camera.initialize() 66 | 67 | def initialize_mirror(self): 68 | """ Routine to initialize the movable mirror. The steps in this method should be those needed for having the 69 | mirror in a known position (i.e. the homing procedure). 70 | """ 71 | logger.info('Homing mirror') 72 | 73 | def initialize_electronics(self): 74 | """ Routine to initialize the rest of the electronics. For example, the LED's can be set to a default on/off 75 | state. This is also used to measure the temperature. 76 | """ 77 | logger.info('Initializing electronics') 78 | 79 | def initialize(self): 80 | """ Initializes all the devices at the same time using threads. 81 | """ 82 | self.initialize_threads -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/popOut.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Monitor.popOut.py 3 | =================================== 4 | Pop-out window that can show predefined messages 5 | 6 | .. sectionauthor:: Sanli Faez 7 | """ 8 | 9 | from pyqtgraph.Qt import QtGui 10 | 11 | class popOutWindow(QtGui.QWidget): 12 | """ 13 | Simple window that contains the message text. 14 | """ 15 | def __init__(self, parent=None): 16 | QtGui.QWidget.__init__(self, parent) 17 | 18 | # General layout of the widget 19 | self.layout = QtGui.QVBoxLayout(self) 20 | 21 | # Displays a textbox to the user with either information or a simple log. 22 | self.message = QtGui.QTextEdit() 23 | self.layout.addWidget(self.message) 24 | self.setupStyles() 25 | self.setupMessage() 26 | 27 | 28 | def setupStyles(self): 29 | """ Setups three styles for the bars: Red, Yellow and Default. It is meant to be more graphical to the user regarding 30 | the availability of resources.""" 31 | self.RED_STYLE = """ 32 | QProgressBar{ 33 | border: 2px solid grey; 34 | border-radius: 5px; 35 | text-align: center 36 | } 37 | 38 | QProgressBar::chunk { 39 | background-color: red; 40 | width: 10px; 41 | margin: 1px; 42 | } 43 | """ 44 | 45 | self.DEFAULT_STYLE = """ 46 | QProgressBar{ 47 | border: 2px solid grey; 48 | border-radius: 5px; 49 | text-align: center 50 | } 51 | 52 | QProgressBar::chunk { 53 | background-color: green; 54 | width: 10px; 55 | margin: 1px; 56 | } 57 | """ 58 | 59 | self.YELLOW_STYLE = """ 60 | QProgressBar{ 61 | border: 2px solid grey; 62 | border-radius: 5px; 63 | text-align: center 64 | } 65 | 66 | QProgressBar::chunk { 67 | background-color: yellow; 68 | width: 10px; 69 | margin: 1px; 70 | } 71 | """ 72 | 73 | def setupMessage(self): 74 | """Starts the message box with a title that will always be displayed.""" 75 | messagetitle = "UUTrack Assistant" 76 | shortcuts = """ 77 | F1, Show cheatsheet
78 | F5, Snap image
79 | F6, Continuous run
80 | Alt+mouse: Select line
81 | Ctrl+mouse: Crosshair
82 | Ctrl+B: Toggle buffering
83 | Ctrl+G: Toggle background subtraction
84 | Ctrl+F: Empty buffer
85 | Ctrl+C: Start tracking
86 | Ctrl+V: Stop tracking
87 | Ctrl+M: Autosave on
88 | Ctrl+N: Autosave off
89 | Ctrl+S: Save image
90 | Ctrl+W: Start waterfall
91 | Ctrl+Q: Exit application
92 | Ctrl+Shift+W: Save waterfall data
93 | Ctrl+Shift+T: Save trajectory
94 | """ 95 | self.message.setHtml('

%s

%s' % (messagetitle, shortcuts)) 96 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/specialTaskTrack.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.specialTaskWorker.py 3 | ======================================== 4 | Similar to the :ref:`UUTrack.View.Camera.workerThread`, the special task worker is designed for running in a separate thread a task other than just acquiring from the camera. 5 | For example, one can use this to activate some feedback loop. 6 | In order to see this in action, check :meth:`startSpecialTask ` 7 | 8 | .. todo:: Make something out of this class more than just extracting the centroid. 9 | """ 10 | 11 | import numpy as np 12 | from pyqtgraph.Qt import QtCore 13 | from .LocateParticle import LocatingParticle 14 | 15 | class specialTaskTracking(QtCore.QThread): 16 | """Thread for performing a specific task, for example tracking of a particle in 'real-time'. 17 | It takes as an input variables _session, camera, and inipos: x, y, particle position. 18 | What the coordinates are, depends on specific applications. 19 | """ 20 | def __init__(self, _session, camera, noiselvl, imgsize, iniloc): 21 | QtCore.QThread.__init__(self) 22 | self._session = _session 23 | self.camera = camera 24 | self.keep_running = True 25 | self.loc = iniloc 26 | self.psize = _session.Tracking['particle_size'] 27 | self.step = _session.Tracking['step_size'] 28 | self.locator = LocatingParticle(self.psize, self.step, noiselvl, imgsize, iniloc) 29 | 30 | def __del__(self): 31 | self.wait() 32 | 33 | def run(self): 34 | """ Performs a task after calling the start() method, for example acquires an image and computes the centroid. 35 | """ 36 | first = True 37 | while self.keep_running: 38 | if first: 39 | self.camera.set_acquisition_mode(self.camera.MODE_CONTINUOUS) 40 | self.camera.trigger_camera() # Triggers the camera only once 41 | first = False 42 | img = self.camera.read_camera() 43 | if isinstance(img, list): 44 | tracktag = np.zeros((len(img),5)) # 5 Columns correspond to [mass, cx, cy, sx, sy] 45 | if self.psize == 0: 46 | self.psize = self.locator.findParticleSize(img[0], self.loc) 47 | if self.psize == 0: #if locator does not find a particle, it returns zero for psize 48 | print('Failed to locate the particle!') 49 | self.keep_running = False 50 | else: 51 | n=0 52 | for i in img: 53 | tracktag[n,:] = self.locator.Locate(i) 54 | n+=1 55 | else: 56 | if self.psize == 0: 57 | self.psize = self.locator.findParticleSize(img, self.loc) 58 | if self.psize == 0: #if locator does not find a particle, it returns zero for psize 59 | print('Failed to locate the particle!') 60 | self.keep_running = False 61 | else: 62 | tracktag = self.locator.Locate(img) 63 | # print(X) 64 | # print('Special task running... Coordinate X: %sCoordinate Y: %s'%(X[0], X[1])) 65 | self.emit(QtCore.SIGNAL('image'), img, 'SpecialTaskTracking') 66 | self.emit(QtCore.SIGNAL('coordinates'), tracktag) 67 | self.camera.stopAcq() 68 | print('Live tracking stopped! Set particle size to %s \n' %(self.psize)) 69 | return 70 | -------------------------------------------------------------------------------- /pynta/controller/devices/arduino/arduino_driver/arduino_driver.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define ONE_WIRE_BUS 2 5 | #define DHTPIN 7 // what pin we're connected to 6 | #define DHTTYPE DHT22 // DHT 22 (AM2302) 7 | #define motor1_Pin1 3 // H-bridge pin2 8 | #define motor1_Pin2 4 // H-bridge pin 7 9 | #define motor2_Pin1 11 // H-bridge pin 10 10 | #define motor2_Pin2 12 // H-bridge pin 15 11 | #define motor1_enable 9 // H-bridge enable pin 1 12 | #define motor2_enable 10 // H-bridge enable pin 9 13 | #define motor_delay 5 // Delay when moving the motor 14 | DHT dht(DHTPIN, DHTTYPE); //// Initialize DHT sensor for normal 16mhz Arduino 15 | OneWire oneWire(ONE_WIRE_BUS); 16 | DallasTemperature sensors(&oneWire); 17 | DeviceAddress insideThermometer; 18 | 19 | String serialString; 20 | 21 | int motor; 22 | int direction; 23 | float temp; 24 | float hum_dht; 25 | int temp_channel; 26 | 27 | void setup() { 28 | // Load serial monitor 29 | Serial.begin (19200); 30 | // set all the other pins you're using as outputs: 31 | pinMode(motor1_Pin1, OUTPUT); 32 | pinMode(motor1_Pin2, OUTPUT); 33 | pinMode(motor2_Pin1, OUTPUT); 34 | pinMode(motor2_Pin2, OUTPUT); 35 | pinMode(motor1_enable, OUTPUT); 36 | pinMode(motor2_enable, OUTPUT); 37 | sensors.begin(); 38 | dht.begin(); 39 | sensors.getAddress(insideThermometer, 0); 40 | sensors.setResolution(insideThermometer, 11); 41 | } 42 | 43 | void loop() { 44 | serialString = ""; 45 | while (Serial.available()) { 46 | char c = Serial.read(); 47 | serialString += c; 48 | } 49 | Serial.flush(); 50 | delay(100); 51 | if (serialString.length() > 0) { 52 | if (serialString.startsWith("t")) { // Read the temp 53 | temp_channel = serialString.substring(4, 5).toInt(); 54 | if (temp_channel == 0) { 55 | temp = dht.readTemperature(); 56 | Serial.println(temp); 57 | } else if (temp_channel == 1) { 58 | sensors.requestTemperatures(); // Send the command to get temperature readings 59 | delay(1000); 60 | temp = sensors.getTempCByIndex(0); 61 | Serial.println(temp); 62 | } 63 | } else { // Move the motor 64 | 65 | motor = serialString.substring(0, 1).toInt(); // Number of motor to move 66 | direction = serialString.substring(1, 2).toInt(); // Direction to move the motor 67 | 68 | if (direction == 0) { 69 | if (motor == 1) { 70 | digitalWrite(motor1_Pin1, LOW); 71 | digitalWrite(motor1_Pin2, HIGH); 72 | digitalWrite(motor1_enable, HIGH); 73 | delay(motor_delay); 74 | digitalWrite(motor1_enable, LOW); 75 | } else if (motor == 2) { 76 | digitalWrite(motor2_Pin1, LOW); 77 | digitalWrite(motor2_Pin2, HIGH); 78 | digitalWrite(motor2_enable, HIGH); 79 | delay(motor_delay); 80 | digitalWrite(motor2_enable, LOW); 81 | } 82 | } else if (direction == 1) { 83 | if (motor == 1) { 84 | digitalWrite(motor1_Pin1, HIGH); 85 | digitalWrite(motor1_Pin2, LOW); 86 | digitalWrite(motor1_enable, HIGH); 87 | delay(motor_delay); 88 | digitalWrite(motor1_enable, LOW); 89 | } else if (motor == 2) { 90 | digitalWrite(motor2_Pin1, HIGH); 91 | digitalWrite(motor2_Pin2, LOW); 92 | digitalWrite(motor2_enable, HIGH); 93 | delay(motor_delay); 94 | digitalWrite(motor2_enable, LOW); 95 | } 96 | } 97 | delay(1); 98 | } 99 | } 100 | delay(2); 101 | } 102 | -------------------------------------------------------------------------------- /pynta/view/GUI/config_tracking_widget.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt5 import uic 4 | from PyQt5.QtCore import pyqtSignal, Qt 5 | from PyQt5.QtWidgets import QWidget 6 | 7 | 8 | class ConfigTrackingWidget(QWidget): 9 | 10 | apply_config = pyqtSignal(dict) 11 | flags = Qt.WindowStaysOnTopHint 12 | 13 | def __init__(self, parent=None): 14 | super().__init__(parent, flags=self.flags) 15 | uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'designer', 'tracking_config.ui'), self) 16 | self._config = None 17 | 18 | self.button_apply.clicked.connect(self.get_config) 19 | self.button_cancel.clicked.connect(self.revert_changes) 20 | 21 | def update_config(self, config): 22 | """ 23 | :param config: Dictionary with the new values 24 | :type config: dict 25 | """ 26 | self.line_diameter.setText(str(config['locate']['diameter'])) 27 | self.line_min_mass.setText(str(config['locate']['minmass'])) 28 | self.check_invert.setChecked(config['locate']['invert']) 29 | 30 | self.line_memory.setText(str(config['link']['memory'])) 31 | self.line_search_range.setText(str(config['link']['search_range'])) 32 | 33 | self.line_min_length.setText(str(config['process']['min_traj_length'])) 34 | self.line_calibration.setText(str(config['process']['um_pixel'])) 35 | self.line_min_mass_2.setText(str(config['process']['min_mass'])) 36 | self.line_max_size.setText(str(config['process']['max_size'])) 37 | self.line_max_ecc.setText(str(config['process']['max_ecc'])) 38 | self.check_drift.setChecked(config['process']['compute_drift']) 39 | self._config = config 40 | 41 | def get_config(self): 42 | config = dict( 43 | locate=dict( 44 | diameter=int(self.line_diameter.text()), 45 | minmass=float(self.line_min_mass.text()), 46 | invert=self.check_invert.isChecked(), 47 | ), 48 | link=dict( 49 | memory=int(self.line_memory.text()), 50 | search_range=float(self.line_search_range.text()) 51 | 52 | ), 53 | process=dict( 54 | min_traj_length=int(self.line_min_length.text()), 55 | um_pixel=float(self.line_calibration.text()), 56 | min_mass=float(self.line_min_mass_2.text()), 57 | max_size=float(self.line_max_size.text()), 58 | max_ecc=float(self.line_max_ecc.text()), 59 | compute_drift=self.check_drift.isChecked(), 60 | ) 61 | ) 62 | self._config = config 63 | self.apply_config.emit(config) 64 | return self._config 65 | 66 | def revert_changes(self): 67 | self.update_config(self._config) 68 | 69 | def print_config(self, config): 70 | print(config) 71 | 72 | 73 | if __name__ == "__main__": 74 | from PyQt5.QtWidgets import QApplication 75 | 76 | config = {'locate': 77 | {'diameter': 10, 78 | 'minmass': 100, 79 | 'invert': True}, 80 | 'link': 81 | {'memory': 3, 82 | 'search_range': 4 83 | }, 84 | 'process': 85 | {'min_traj_length': 20, 86 | 'um_pixel': 1, 87 | 'min_mass': 200, 88 | 'max_size': 10, 89 | 'max_ecc': 0.2, 90 | 'compute_drift': False, 91 | } 92 | } 93 | 94 | app = QApplication([]) 95 | win = ConfigTrackingWidget() 96 | win.apply_config.connect(win.print_config) 97 | win.update_config(config) 98 | win.show() 99 | app.exec() 100 | -------------------------------------------------------------------------------- /docs/installing.rst: -------------------------------------------------------------------------------- 1 | .. _installing: 2 | 3 | Installing 4 | ========== 5 | 6 | Pynta can be installed directly with pip. The first step is to create a virtual environment on your machine in order to 7 | avoid clashes with the versions of the dependencies. Virtual Environments are must-have tool, regardless of what you are 8 | doing. You can read a discussion about them directly on 9 | `Python For The Lab `_. You can also see 10 | how to create a virtual environment :ref:`python_virtual_environment`. 11 | 12 | To install PyNTA you can run the following command:: 13 | 14 | pip install git+https://github.com/nanoepics/pynta 15 | 16 | This will get you the latest stable version of Pynta. If you, however, would like to test new features, you can download 17 | the development branch of the program:: 18 | 19 | pip install git+https://github.com/nanoepics/pynta@develop 20 | 21 | Especially if you want to try the development version, you should install it in a virtual environment. We can't guarantee 22 | that the development will be future compatible, i.e. that the program stays compatible with itself over time. One of the 23 | highest risks is that an upgrade on the development branch may brake your config files or customizations you have done. 24 | 25 | Moreover there is the risk of requiring dependencies that are not fully supported or that are later dropped. 26 | 27 | Dependencies 28 | ------------ 29 | By default, when you install PyNTA, the following dependencies will be installed on your computer: 30 | 31 | * trackpy 32 | * pyqt5<5.11 33 | * numpy 34 | * pyqtgraph 35 | * pint 36 | * h5py 37 | * pandas 38 | * pyyaml 39 | * pyzmq 40 | * numba 41 | 42 | However, not all dependencies are mandatory for the program to work. For instance, if you are not interested in the GUI 43 | but are planning to run the program from the command line, you are free to skip PyQt5, Pyqtgraph. 44 | 45 | **Numba** is used because it accelerates the tracking of particles with **trackpy**. But if it is not available on the 46 | computer, the program will run anyways. 47 | 48 | Trackpy 49 | ~~~~~~~ 50 | Trackpy is instrumental for the program to work correctly. This package is able to detect particles on an image based on 51 | few parameters but also to link the particles together, building up single-particle traces. PyNTA uses trackpy to perform 52 | all the detection and analysis in real time. 53 | 54 | One of the constrains of trackpy is that it depends heavily on Pandas, which is a great tool while working in combination 55 | with Jupyter notebooks, but is not that great for user interfaces. Which require to transform from Pandas Data Frames to 56 | numpy arrays all the time. 57 | 58 | PyQt5 and Pyqtgraph 59 | ~~~~~~~~~~~~~~~~~~~ 60 | The user interface is built on PyQt5 in combination with PyQtGraph. PyQt5 versions newer than 5.11 fail at installing 61 | through the setup process (but they do work if installed directly with pip). If this bug is resolved, the constrain on the 62 | version of PyQt to use should be lifted. Moreover, it is desirable to switch to PySide2 as soon as the project is mature. 63 | 64 | Operating System Support 65 | ------------------------ 66 | PyNTA was tested both on Linux and Windows machines. However, the main environment for PyNTA to run is Windows 10. There 67 | are some very fundamental differences on how processes are started between Linux and Windows that have mutual drawbacks. 68 | For example, on Windows processes are spawned, meaning that classes are re-imported and not instantiated. Therefore, 69 | processes don't start with a shared state. This prevents, for example, to start a new process for a method of a class. 70 | 71 | This forced the architecture of the program to rely heavily on functions and not methods, making the code slightly more 72 | convoluted than what was desirable. The approach works on Linux also, but the performance may not be optimal. -------------------------------------------------------------------------------- /pynta/tests/test_zmq.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the limits of the pyZMQ library 3 | ===================================== 4 | 5 | Example on how to test the bandwidth limits for pyZMQ. We create a Queue with a given number of frames and an empty 6 | queue. The first is fed to the publisher, while the second is used by the subscriber to append the data. In this way 7 | we can check whether all the frames arrived in order and the bandwidth limitation for ZMQ. 8 | 9 | If any attempts at improve the architecture of publisher/subscriber are tried, this test is going to be useful to 10 | determine whether there is a real improvement in performance. 11 | 12 | .. note:: Replacing send_pyobj by the nocopy option is faster, however it also requires to reshape the data, which 13 | adds an overhead and eventually reaches equivalent bandwidths. 14 | 15 | .. warning:: This script consumes a lot of memory because it allocates a queue with thousands of elements in it. If 16 | you have a more limited system, consider lowering the amount of elements or changing the data type. 17 | 18 | """ 19 | from time import time 20 | 21 | import numpy as np 22 | from multiprocessing import Process, Queue 23 | 24 | from pynta.model.experiment.publisher import publisher 25 | from pynta.model.experiment.subscriber import subscriber 26 | from pynta.util import get_logger 27 | 28 | 29 | def test_func(data, queue): 30 | logger = get_logger(name=__name__) 31 | logger.info('Putting data to queue') 32 | queue.put(data) 33 | 34 | 35 | def main(num_data_frames = 1000, dtype=np.uint16): 36 | data = np.random.randint(0, high=2**8, size=(1000, 1000), dtype=dtype) 37 | recv_q = Queue() # Queue where data will be received 38 | send_q = Queue() 39 | for i in range(num_data_frames): 40 | send_q.put({'topic': 'test', 'data': data}) 41 | send_q.put({'topic': 'test', 'data': 'stop'}) 42 | send_q.put({'topic': '', 'stop_pub': 'stop_pub'}) 43 | sub1 = Process(target=subscriber, args=[test_func, 'test', recv_q]) 44 | sub1.start() 45 | t0 = time() 46 | pub = Process(target=publisher, args=[send_q]) 47 | pub.start() 48 | pub.join() 49 | t1 = time() - 2 - t0 # The publishers sleeps for 2 seconds, 1 at the beginning, 1 at the end 50 | bandwidth = num_data_frames * data.nbytes / t1 51 | i = 0 52 | while not recv_q.empty() or recv_q.qsize() > 0: 53 | d = recv_q.get() 54 | i += 1 55 | return bandwidth, t1, i 56 | 57 | 58 | if __name__ == '__main__': 59 | from argparse import ArgumentParser 60 | 61 | parser = ArgumentParser(description='Test the limits of ZMQ on this system') 62 | parser.add_argument("--data-frame", dest="number_frames", default=1000, 63 | help="Number of data frames to send") 64 | parser.add_argument("--data-type", dest="dtype", 65 | default="uint8", 66 | help="Data type to use, uint8, uint16, uint32") 67 | 68 | args = parser.parse_args() 69 | 70 | if args.dtype == 'uint8': 71 | dtype = np.uint8 72 | elif args.dtype == 'uint16': 73 | dtype = np.uint16 74 | elif args.dtype == 'uint32': 75 | dtype = np.uint32 76 | else: 77 | raise ValueError('Dtype must be either uint8, 16 or 32') 78 | 79 | num_frames = int(args.number_frames) 80 | print('Starting the test. It may take a couple of seconds to complete...') 81 | bandwidth, t, rcv_num_frames = main(num_data_frames=num_frames, dtype=dtype) 82 | print('Recevied {} frames out of {} sent'.format(num_frames, rcv_num_frames)) 83 | print('Total execution time: {:3.1f}s'.format(t)) 84 | print('Estimated bandwidth: {:3.1f}MB/s'.format(bandwidth/1024/1024)) 85 | 86 | # 87 | # print('Total ellapsed time: {:2.1f}s'.format(t1-t0)) 88 | # 89 | # print('Bandwidth: {:4.1f}MB/s'.format(bandwidth/1024/1024)) 90 | # 91 | # 92 | # print('Total received points: {}/{}'.format(i, num_data_frames)) 93 | -------------------------------------------------------------------------------- /conda_pkgs.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: win-64 4 | @EXPLICIT 5 | https://repo.anaconda.com/pkgs/main/win-64/blas-1.0-mkl.conda 6 | https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2019.6.16-hecc5488_0.tar.bz2 7 | https://repo.anaconda.com/pkgs/main/win-64/icc_rt-2019.0.0-h0cc432a_1.conda 8 | https://repo.anaconda.com/pkgs/main/win-64/intel-openmp-2019.4-245.conda 9 | https://repo.anaconda.com/pkgs/main/win-64/vs2015_runtime-14.15.26706-h3a45250_4.conda 10 | https://repo.anaconda.com/pkgs/main/win-64/mkl-2019.4-245.conda 11 | https://repo.anaconda.com/pkgs/main/win-64/vc-14.1-h0510ff6_4.conda 12 | https://repo.anaconda.com/pkgs/main/win-64/icu-58.2-ha66f8fd_1.conda 13 | https://repo.anaconda.com/pkgs/main/win-64/jpeg-9b-hb83a4c4_2.conda 14 | https://repo.anaconda.com/pkgs/main/win-64/libsodium-1.0.16-h9d3ae62_0.conda 15 | https://conda.anaconda.org/conda-forge/win-64/openssl-1.1.1c-hfa6e2cd_0.tar.bz2 16 | https://repo.anaconda.com/pkgs/main/win-64/sqlite-3.29.0-he774522_0.tar.bz2 17 | https://repo.anaconda.com/pkgs/main/win-64/yaml-0.1.7-hc54c509_2.conda 18 | https://repo.anaconda.com/pkgs/main/win-64/zlib-1.2.11-h62dcd97_3.conda 19 | https://repo.anaconda.com/pkgs/main/win-64/hdf5-1.10.4-h7ebc959_0.conda 20 | https://repo.anaconda.com/pkgs/main/win-64/libpng-1.6.37-h2a8f88b_0.conda 21 | https://repo.anaconda.com/pkgs/main/win-64/python-3.6.8-h9f7ef89_7.conda 22 | https://repo.anaconda.com/pkgs/main/win-64/zeromq-4.3.1-h33f27b4_3.conda 23 | https://conda.anaconda.org/conda-forge/win-64/certifi-2019.6.16-py36_1.tar.bz2 24 | https://conda.anaconda.org/conda-forge/win-64/freetype-2.10.0-h5db478b_0.tar.bz2 25 | https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.1.0-py36he980bc4_0.tar.bz2 26 | https://repo.anaconda.com/pkgs/main/win-64/llvmlite-0.29.0-py36ha925a31_0.conda 27 | https://repo.anaconda.com/pkgs/main/win-64/numpy-base-1.16.4-py36hc3f5095_0.conda 28 | https://conda.anaconda.org/conda-forge/noarch/pyparsing-2.4.1-py_0.tar.bz2 29 | https://repo.anaconda.com/pkgs/main/win-64/pyreadline-2.1-py36_1.conda 30 | https://repo.anaconda.com/pkgs/main/noarch/pytz-2019.1-py_0.tar.bz2 31 | https://repo.anaconda.com/pkgs/main/win-64/pyyaml-5.1.1-py36he774522_0.conda 32 | https://repo.anaconda.com/pkgs/main/win-64/pyzmq-18.0.0-py36ha925a31_0.conda 33 | https://repo.anaconda.com/pkgs/main/win-64/qt-5.9.7-vc14h73c81de_0.conda 34 | https://repo.anaconda.com/pkgs/main/win-64/sip-4.19.8-py36h6538335_0.conda 35 | https://repo.anaconda.com/pkgs/main/win-64/six-1.12.0-py36_0.conda 36 | https://conda.anaconda.org/conda-forge/win-64/tornado-6.0.3-py36hfa6e2cd_0.tar.bz2 37 | https://repo.anaconda.com/pkgs/main/win-64/wincertstore-0.2-py36h7fe50ca_0.conda 38 | https://conda.anaconda.org/conda-forge/noarch/cycler-0.10.0-py_1.tar.bz2 39 | https://repo.anaconda.com/pkgs/main/win-64/mkl_random-1.0.2-py36h343c172_0.conda 40 | https://repo.anaconda.com/pkgs/main/win-64/pyqt-5.9.2-py36h6538335_2.conda 41 | https://repo.anaconda.com/pkgs/main/win-64/python-dateutil-2.8.0-py36_0.conda 42 | https://repo.anaconda.com/pkgs/main/win-64/setuptools-41.0.1-py36_0.conda 43 | https://conda.anaconda.org/conda-forge/win-64/pint-0.9-py36_2.tar.bz2 44 | https://conda.anaconda.org/conda-forge/win-64/pyvisa-1.9.1-py36_1000.tar.bz2 45 | https://repo.anaconda.com/pkgs/main/win-64/wheel-0.33.4-py36_0.conda 46 | https://repo.anaconda.com/pkgs/main/win-64/pip-19.1.1-py36_0.conda 47 | https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.1.1-py36_0.tar.bz2 48 | https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.1.1-py36h2852a4a_0.tar.bz2 49 | https://repo.anaconda.com/pkgs/main/win-64/h5py-2.9.0-py36h5e291fa_0.conda 50 | https://repo.anaconda.com/pkgs/main/win-64/mkl_fft-1.0.12-py36h14836fe_0.conda 51 | https://repo.anaconda.com/pkgs/main/win-64/numpy-1.16.4-py36h19fb1c0_0.conda 52 | https://repo.anaconda.com/pkgs/main/win-64/numba-0.45.0-py36hf9181ef_0.tar.bz2 53 | https://repo.anaconda.com/pkgs/main/win-64/pandas-0.24.2-py36ha925a31_0.conda 54 | https://repo.anaconda.com/pkgs/main/win-64/pyqtgraph-0.10.0-py36h28b3542_3.conda 55 | https://repo.anaconda.com/pkgs/main/win-64/scipy-1.2.1-py36h29ff71c_0.conda 56 | https://conda.anaconda.org/conda-forge/noarch/trackpy-0.4.1-py_1.tar.bz2 57 | -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/LocateParticle.py: -------------------------------------------------------------------------------- 1 | """ 2 | openCET.View.LocateParticle.py 3 | ================================== 4 | The LocateParticle class contains necessary methods for localizing centroid of a particle and following its track. 5 | the idea is to make the output suitable for analysis with TrackPy of similar open-source tracking packages 6 | 7 | .. lastedit:: 10/5/2017 8 | .. sectionauthor:: Sanli Faez 9 | """ 10 | import numpy as np 11 | import copy 12 | #from scipy.ndimage.measurements import center_of_mass as cenmass 13 | 14 | class LocatingParticle: 15 | """ 16 | initiate the localization requirements 17 | :param psize: expected particle size (e.g. point spread function) 18 | :param step: expected step size for searching for next location 19 | :param noiselvl: noise level below which all image data will be set to zero 20 | :param iniloc: starting position [row, column] of the track passed by the monitoring routine 21 | :param imgsize: [number of rows, number of columns] in the analyzed image array 22 | [not implemented] consistently return success/failure messages regarding locating the particle 23 | """ 24 | def __init__(self, psize, step, noiselvl, imgsize, iniloc): 25 | self.noise = noiselvl 26 | self.psize = psize 27 | self.step = step 28 | self.locx = iniloc[0] 29 | self.locy = iniloc[1] 30 | self.imgwidth = imgsize[0] 31 | self.imgheight = imgsize[1] 32 | print(imgsize, iniloc) 33 | 34 | def findParticleSize(self, data, location): 35 | """Estimates size of the PSF of the particle at the specified :location: in the :image:""" 36 | x, y = [int(location[0]), int(location[1])] 37 | w = 40 # distance of the expected peak from the crosshair pointer 38 | imgpart = data[x - w:x + w + 1, y - w:y + w + 1] 39 | imgpart[imgpartself.noise: 41 | cx, cy = self.centroid(imgpart) 42 | self.locx, self.locy = [x-w+cx, y-w+cy] 43 | self.psize = np.int(np.sqrt(np.sum(imgpart)/np.amax(imgpart))) # very rough estimate, Gaussian fit can be better 44 | else: 45 | self.psize = 0 46 | return self.psize 47 | 48 | def centroid(self, data): 49 | h, w = np.shape(data) 50 | x = np.arange(0, w) 51 | y = np.arange(0, h) 52 | 53 | X, Y = np.meshgrid(x, y) 54 | 55 | cy = np.sum(X * data) / np.sum(data) 56 | cx = np.sum(Y * data) / np.sum(data) 57 | 58 | return [cx, cy] 59 | 60 | def pointspread(self, data, cx, cy): 61 | h, w = np.shape(data) 62 | x = np.arange(w) - cy 63 | y = np.arange(h) - cx 64 | 65 | X, Y = np.meshgrid(x, y) 66 | mass = np.sum(data) 67 | sy = np.sqrt(np.sum(np.square(X) * data) / mass) 68 | sx = np.sqrt(np.sum(np.square(Y) * data) / mass) 69 | 70 | return [sx, sy] 71 | 72 | def Locate(self, data_o): 73 | """extracts the particle localization information close the lastly known location (self.loc) and updates it""" 74 | data = copy.copy(data_o) 75 | x, y = [int(self.locx), int(self.locy)] 76 | w = np.int((self.psize + self.step)*2) 77 | if (x in np.arange(self.imgwidth-2*w-1)+w) and (y in np.arange(self.imgheight-2*w-1)+w): 78 | imgpart = data[x - w:x + w + 1, y - w:y + w + 1] 79 | imgpart[imgpart2*self.noise: 82 | cx, cy = self.centroid(imgpart) 83 | self.locx, self.locy = [x-w+cx, y-w+cy] 84 | mass = np.sum(imgpart) 85 | sx, sy = self.pointspread(imgpart, cx, cy) 86 | tracktag = np.array([[mass, self.locx, self.locy, sx, sy]]) 87 | else: 88 | tracktag = np.zeros((1, 5)) 89 | 90 | else: 91 | tracktag = np.zeros((1,5)) 92 | self.locx, self.locy = [0,0] 93 | 94 | #np.set_printoptions(precision=3) 95 | #print(tracktag) #for debugging purposes 96 | return tracktag -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/hatch_enlarge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | to enlargeAn icon for "enlarge" from the Hatch series on to [icon]. Downloaded from http://www.toicon.com/icons/hatch_enlarge by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | image/svg+xmlCarol Liaohttp://www.toicon.com/icons/hatch_enlarge 15 | -------------------------------------------------------------------------------- /examples/test_basler.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from pynta.model.cameras.basler import Camera\n", 10 | "from pynta import Q_\n", 11 | "\n", 12 | "import matplotlib.pyplot as plt" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "camera = Camera(0)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "camera.initialize()\n", 31 | "camera.clearROI()" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "camera.set_acquisition_mode(camera.MODE_SINGLE_SHOT)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "camera.set_exposure(Q_('0.1s'))" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "camera.trigger_camera()" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "imgs = camera.read_camera()\n", 68 | "plt.imshow(imgs[-1])" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "camera.setROI([0, 1500],[400, 500])" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "camera.max_width" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "camera.camera.OffsetX.SetValue(8)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "camera.camera.Width.Value" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "camera.camera.Width.SetValue(52)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "camera.camera.OffsetY.Max" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "camera.camera.Height.GetMin()" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "camera.camera.Height.SetValue(50)" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "(50%4)*4" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "50-50%4" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [] 167 | } 168 | ], 169 | "metadata": { 170 | "kernelspec": { 171 | "display_name": "Python 3", 172 | "language": "python", 173 | "name": "python3" 174 | }, 175 | "language_info": { 176 | "codemirror_mode": { 177 | "name": "ipython", 178 | "version": 3 179 | }, 180 | "file_extension": ".py", 181 | "mimetype": "text/x-python", 182 | "name": "python", 183 | "nbconvert_exporter": "python", 184 | "pygments_lexer": "ipython3", 185 | "version": "3.6.8" 186 | } 187 | }, 188 | "nbformat": 4, 189 | "nbformat_minor": 2 190 | } 191 | -------------------------------------------------------------------------------- /pynta/model/motors/arduino_base.py: -------------------------------------------------------------------------------- 1 | """ 2 | arduino.py 3 | ========== 4 | 5 | Base model for communicating with Arduino devices. In principle, Arduino's can be programmed in very different 6 | ways, and therefore the flow of information may be very different. This model is thought to interface with 7 | an Arduino which is in control of two DC motors and which is able to read values from some devices, such as a 8 | DHT22, and a DS18B20. It relies on PyVisa with pyvisa-py as backend in order to establish the communication with the 9 | device. 10 | """ 11 | from time import sleep 12 | 13 | import pyvisa 14 | from pynta.util.log import get_logger 15 | # TODO: Make more flexible which backend will be used for PyVisa 16 | from pynta.model.exceptions import OutOfRange 17 | 18 | rm = pyvisa.ResourceManager('@py') 19 | logger = get_logger(name=__name__) 20 | 21 | 22 | class Arduino: 23 | def __init__(self, port=None): 24 | """ 25 | 26 | :param port: Serial port where the Arduino is connected, can be none and in order to look for devices 27 | automatically 28 | """ 29 | logger.info(f'Starting Arduino class on port {port}') 30 | self.rsc = None 31 | self.port = port 32 | if port: 33 | if not port.startswith('ASRL'): 34 | port = 'ASRL' + port 35 | self.port = port 36 | self.rsc = rm.open_resource(self.port, baud_rate=19200) 37 | sleep(5) 38 | self.rsc.read_termination = '\r\n' 39 | self.rsc.timeout = 2500 40 | 41 | def move_motor(self, motor: int, direction: int) -> None: 42 | """ Moves a motor in a given direction. It is designed to work with two connected motors 43 | through a quadrupole half-H driver(SN754410). The arduino is programmed to move the motors a short 44 | amount of time. 45 | 46 | Parameters 47 | ---------- 48 | motor : int 49 | Either 1 or 2, which motor to control. It can be extended to control more motors provided that the 50 | Arduino has enough digital channels available. 51 | direction : int 52 | Direction of the movement. Either 0 or 1. 53 | 54 | Raises 55 | ------ 56 | OutOfRange 57 | In case any of the values is outside the allowed range of options. 58 | 59 | """ 60 | logger.info(f'Setting motor {motor} to direction {direction}') 61 | if motor not in (1, 2): 62 | raise OutOfRange('Motor must be either 1 or 2') 63 | if direction not in (0, 1): 64 | raise OutOfRange('Direction must be either 0 or 1') 65 | 66 | command = f'{motor}{direction}' 67 | logger.debug(command) 68 | logger.info(self.rsc.write(command)) 69 | 70 | def read_temperature(self, channel): 71 | """ Reads the temperature from connected sensors 72 | 73 | Parameters 74 | ---------- 75 | channel : int 76 | In principle the Arduino can have more than one temperature sensor connected to it. Specifying the 77 | channel allows the user to know which temperature is being read. 78 | 79 | Returns 80 | ------- 81 | temperature : float 82 | The temperature in degree C. 83 | 84 | """ 85 | command = f'temp{channel}' 86 | logger.debug(command) 87 | return self.rsc.query(command) 88 | 89 | def close(self): 90 | self.move_motor(1, 0, 0) 91 | self.move_motor(2, 0, 0) 92 | self.rsc.close() 93 | 94 | @staticmethod 95 | def list_devices(): 96 | return rm.list_resources() 97 | 98 | def __del__(self): 99 | self.close() 100 | 101 | if __name__ == '__main__': 102 | import logging 103 | 104 | logger = get_logger() # 'pynta.model.experiment.nanoparticle_tracking.saver' 105 | logger.setLevel(logging.DEBUG) 106 | ch = logging.StreamHandler() 107 | ch.setLevel(logging.DEBUG) 108 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 109 | ch.setFormatter(formatter) 110 | logger.addHandler(ch) 111 | 112 | inst = Arduino('COM3') 113 | print(inst.read_temperature(0)) 114 | inst.move_motor(1, 0, 155) 115 | sleep(1) 116 | inst.move_motor(1, 1, 155) 117 | sleep(1) 118 | inst.move_motor(1, 0, 0) 119 | inst.close() 120 | inst.list_devices() -------------------------------------------------------------------------------- /pynta/view/GUI/old/Monitor/cameraViewer.py: -------------------------------------------------------------------------------- 1 | """ 2 | UUTrack.View.Camera.cameraViewer.py 3 | =================================== 4 | Independent window for viewing the camera. It is useful since it allows to quickly check the full CCD without changing the parameters of the main window. 5 | It could have been design in such a way that can be run independently, for fast visualization without the need of controlling. 6 | 7 | .. todo:: The viewer should inherit from :mod:`UUTrack.Viewer.Camera.cameraMainWidget` in order to have the same interface and not replicate code. 8 | 9 | .. todo:: Unify the main viewer with this one. 10 | 11 | .. sectionauthor:: Aquiles Carattino 12 | """ 13 | 14 | import pyqtgraph as pg 15 | from pyqtgraph import GraphicsLayoutWidget 16 | from pyqtgraph.Qt import QtGui, QtCore 17 | 18 | from pynta.view.GUI.old.workerThread import workThread 19 | 20 | 21 | class cameraViewer(QtGui.QMainWindow): 22 | """Main window for the viewer. 23 | """ 24 | def __init__(self,session,camera,parent=None): 25 | super(cameraViewer,self).__init__() 26 | 27 | self._session = session 28 | self.camera = camera 29 | self.parent = parent 30 | self.setWindowTitle('On-Demand Camera Terminal') 31 | self.viewerWidget = viewerWidget() 32 | self.setCentralWidget(self.viewerWidget) 33 | 34 | QtCore.QObject.connect(self.viewerWidget.startButton,QtCore.SIGNAL('clicked()'),self.startCamera) 35 | QtCore.QObject.connect(self.viewerWidget.stopButton,QtCore.SIGNAL('clicked()'),self.startCamera) 36 | self.acquiring = False 37 | 38 | self.tempImage = [] 39 | 40 | self.refreshTimer = QtCore.QTimer() 41 | self.refreshTimer.start(self._session.GUI['refresh_time']) # In milliseconds 42 | 43 | self.connect(self.refreshTimer,QtCore.SIGNAL("timeout()"),self.updateGUI) 44 | 45 | 46 | def startCamera(self): 47 | """Starts a continuous acquisition of the camera. 48 | """ 49 | self.emit(QtCore.SIGNAL('stopMainAcquisition')) 50 | if self.acquiring: 51 | self.stopCamera() 52 | else: 53 | self.acquiring = True 54 | self.workerThread = workThread(self._session,self.camera) 55 | self.connect(self.workerThread,QtCore.SIGNAL('image'),self.getData) 56 | self.workerThread.start() 57 | 58 | def stopCamera(self): 59 | """Stops the acquisition. 60 | """ 61 | if self.acquiring: 62 | self.workerThread.keep_acquiring = False 63 | self.acquiring = False 64 | 65 | def getData(self,data,origin): 66 | """Gets the data that is being gathered by the working thread. 67 | """ 68 | self.tempImage = data 69 | 70 | def updateGUI(self): 71 | """Updates the GUI at regular intervals. 72 | """ 73 | if len(self.tempImage) >= 1: 74 | self.viewerWidget.img.setImage(self.tempImage) 75 | 76 | def closeViewer(self): 77 | """What to do when the viewer is triggered to close from outside. 78 | """ 79 | self.stopCamera() 80 | self.close() 81 | 82 | def closeEvent(self,evnt): 83 | """Triggered at closing. If it is running as main window or not. 84 | """ 85 | if self.parent == None: 86 | self.emit(QtCore.SIGNAL('closeAll')) 87 | self.camera.stop_camera() 88 | self.workerThread.terminate() 89 | self.close() 90 | else: 91 | self.closeViewer() 92 | 93 | class viewerWidget(QtGui.QWidget): 94 | """Widget for holding the GUI elements of the viewer. 95 | """ 96 | def __init__(self, parent=None): 97 | QtGui.QWidget.__init__(self, parent) 98 | 99 | self.layout = QtGui.QVBoxLayout(self) 100 | 101 | self.viewport = GraphicsLayoutWidget() 102 | self.view = self.viewport.addViewBox(enableMenu=True) 103 | self.img = pg.ImageItem() 104 | self.view.addItem(self.img) 105 | 106 | self.buttons = QtGui.QHBoxLayout() 107 | self.startButton = QtGui.QPushButton('Start') 108 | self.stopButton = QtGui.QPushButton('Stop') 109 | self.buttons.addWidget(self.startButton) 110 | self.buttons.addWidget(self.stopButton) 111 | 112 | self.setLayout(self.layout) 113 | self.layout.addWidget(self.viewport) 114 | self.layout.addLayout(self.buttons) 115 | -------------------------------------------------------------------------------- /pynta/view/GUI/Icons/hatch_ask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | to askAn icon for "ask" from the Hatch series on to [icon]. Downloaded from http://www.toicon.com/icons/hatch_ask by 131.211.54.239 on 2018-12-04. Licensed CC-BY, see http://toicon.com/license/ for details. 6 | 7 | 8 | 9 | 10 | image/svg+xmlCarol Liaohttp://www.toicon.com/icons/hatch_ask 11 | -------------------------------------------------------------------------------- /examples/testing_basler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import faulthandler 3 | 4 | faulthandler.enable() 5 | import sys 6 | 7 | sys.settrace 8 | import os 9 | 10 | os.environ["PYLON_CAMEMU"] = "3" 11 | from pypylon import pylon 12 | from pypylon import genicam 13 | import time 14 | import threading 15 | import numpy 16 | 17 | AVAILABLE_CAMERAS = [None, None] 18 | # Camera default values: 19 | EXPOSURE_TIME = 1000 20 | GAIN = 0.0 21 | CAMERA_BUFFER_SIZE = 1 22 | ThreadLock = [threading.Lock(), threading.Lock()] 23 | 24 | 25 | class AcquisitionThread(threading.Thread): 26 | def __init__(self, AppVars, Camera, FrateAvg): 27 | threading.Thread.__init__(self) 28 | self.AppVars = AppVars 29 | self.Camera = Camera 30 | self.Navg = FrateAvg 31 | self.Go = True 32 | self._run_event = threading.Event() 33 | self._run_event.set() 34 | 35 | def Resume(self): 36 | self._run_event.set() 37 | 38 | def Pause(self): 39 | self._run_event.clear() 40 | 41 | def Join(self): 42 | self.Go = False 43 | self.join(0.1) 44 | 45 | def run(self): 46 | try: 47 | Cam = self.Camera 48 | AvgFR = [0] * self.Navg 49 | Fcount = int(0) 50 | dT = 0 51 | T = time.time() 52 | Frame = self.AppVars.available_cameras[Cam].RetrieveResult(1000, pylon.TimeoutHandling_ThrowException) 53 | FrameID = Frame.GetID() 54 | Frame.Release() 55 | while self.Go: 56 | self._run_event.wait() 57 | Frame = self.AppVars.available_cameras[Cam].RetrieveResult(1000, pylon.TimeoutHandling_ThrowException) 58 | if not Frame.GrabSucceeded(): 59 | print('Grab failed') 60 | continue 61 | if Frame.GetID() == FrameID: 62 | print('Frame ID unchanged' + str(FrameID)) 63 | continue 64 | FrameID = Frame.GetID() 65 | frm = Frame.GetArray() 66 | Frame.Release() 67 | dT = time.time() - T 68 | T = time.time() 69 | AvgFR[Fcount % self.Navg] = dT 70 | # ThreadLock[Cam].acquire() 71 | # self.AppVars.FrameID[Cam] = FrameID 72 | # self.AppVars.frame[Cam] = frm.copy() 73 | # self.AppVars.FrameRate[Cam] = 1 / (sum(AvgFR) / len(AvgFR)) 74 | # ThreadLock[Cam].release() 75 | Fcount += 1 76 | except genicam.GenericException as e: 77 | print("An exception occurred.", e.GetDescription()) 78 | finally: 79 | print("AcquisitionThread error camera " + str(Cam)) 80 | 81 | 82 | class Application: 83 | def __init__(self): 84 | self.FrameRate = [0, 0] 85 | self.frame = [None, None] 86 | self.FrameID = [None, None] 87 | self.available_cameras = [None, None] 88 | self.CameraList_str = [None, None] 89 | self.ExposureT = EXPOSURE_TIME 90 | self.CamThread = [None, None] 91 | self.Ncams = 0 92 | 93 | def find_cameras(self): 94 | tlFactory = pylon.TlFactory.GetInstance() 95 | devices = tlFactory.EnumerateDevices() 96 | for i, d in enumerate(devices): 97 | dev = pylon.InstantCamera(tlFactory.CreateDevice(devices[i])) 98 | if dev.IsUsb(): 99 | self.available_cameras[self.Ncams] = dev 100 | self.CameraList_str.append(str(self.Ncams) + ": " + dev.GetDeviceInfo().GetFriendlyName()) 101 | self.Ncams += 1 102 | if self.Ncams == 2: 103 | break 104 | 105 | def camera_init(self): 106 | for ic in range(self.Ncams): 107 | self.available_cameras[ic].Open() 108 | self.available_cameras[ic].OutputQueueSize = CAMERA_BUFFER_SIZE 109 | self.available_cameras[ic].ExposureTime.SetValue(self.ExposureT) 110 | self.available_cameras[ic].StartGrabbing() 111 | 112 | # self.available_cameras[ic].StartGrabbing(pylon.GrabStrategy_LatestImages) 113 | 114 | def start_acuisition(self): 115 | self.find_cameras() 116 | self.camera_init() 117 | for ic in range(self.Ncams): 118 | self.CamThread[ic] = AcquisitionThread(self, ic, 10) 119 | self.CamThread[ic].start() 120 | i = 0 121 | while self.available_cameras[0].IsGrabbing(): 122 | None 123 | 124 | 125 | App = Application() 126 | App.start_acuisition() 127 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | Getting Started 4 | =============== 5 | In order to familiarize yourself with the program, the best idea is to start with simulated data. In this way you avoid 6 | all the problems arising from interfacing with a real instrument and you can see the limitations of the program. The 7 | example config available on the repository is already configured to work with a simulated camera. It is recommended that 8 | you copy the contents of the file into a folder on your own computer. 9 | 10 | Opening the Program 11 | ------------------- 12 | After :ref:`installing` PyNTA, you can trigger it from the command line. You can simply run the following command: 13 | 14 | .. code-block:: bash 15 | 16 | pynta 17 | 18 | After a few moments, a screen like the one below will welcome you to the program. 19 | 20 | .. figure:: media/screenshot_01.png 21 | :alt: screenshot 22 | 23 | This will use synthetic data by default, i.e. you can snap or acquire a movie and test the capabilities of the program without the need of connecting with a real camera. Once you are confident with the program and you would like to start using real hardware, you need to develop :ref:`a proper config file `. Once you have it, you can run the following command: 24 | 25 | .. code-block:: bash 26 | 27 | python -m pynta -c config.yml 28 | 29 | You can reproduce the config file that gives you the synthetic data and then move to real devices. 30 | 31 | The Tools 32 | --------- 33 | Most of the options were designed to be self-explanatory. However it is important to give a short discussion in order to 34 | speed the introduction to the tool. After initializing, normally one would like to snap a photo in order to see what is being recorded by the camera. You can achieve it by clicking the button as shown in the image below: 35 | 36 | .. figure:: media/screenshot_snap.png 37 | :alt: Snap an Image 38 | 39 | This will record a single image from the camera and will be displayed on the space right below: 40 | 41 | .. figure:: media/screenshot_particles.png 42 | :alt: Simulated particles on the camera 43 | 44 | The image can be zoomed-in and out by scrolling with the central wheel of the mouse. Dragging allows to move around the image. In order to return to the full view, it is possible to right-click on the image and select ``View All``. The histogram on the right of the image shows the levels for displaying. You can adjust the minimum and maximum as well as the color scale. Right clicking on the image allows you to do an ``Auto Range``, i.e. to adjust the levels such that the maximum and minimum correspond to those of the data being displayed. 45 | 46 | If you want to save the image you can click on the icon for saving, as shown below: 47 | 48 | .. figure:: media/screenshot_save_image.png 49 | :alt: Simulated particles on the camera 50 | 51 | PyNTA also allows you to acquire continuous images, by clicking on the icon highlighted below. The exact behaviour will depend on the camera employed. For example, if a frame-grabber is available, the exact timing between frames can be guaranteed. Cameras without a buffer, however, will have a timing that depends on the computer ability to read from them. The communication with the camera happens in a separate thread, trying to guarantee the maximum reliability of the timing. 52 | 53 | .. figure:: media/screenshot_free_run.png 54 | :alt: Start a Free Run 55 | 56 | Another feature is the continuous saves option, which is right next to the start movie button. The continuous saves streams all the available frames to a file on the hard rive. The location of the file is determined in the config file or, as we will see later, can be set in the configuration on the User Interface. In case of acquiring at high frame rates, not all frames are displayed to the user, but all of them will be saved. 57 | 58 | Tracking and Linking 59 | -------------------- 60 | .. figure:: media/screenshot_tracking.png 61 | :alt: Screenshot tracking options 62 | 63 | The feature that really makes PyNTA unique is the ability to identify and track nanoparticles on a video in real time. 64 | The procedure for tracking and analysis requires of two steps. First, you have to start identifying the particles, with 65 | the button called ``start tracking``. You will see red crosses appearing on the particles in the image. It takes a few instants to setup the linking procedure, during which the movie may seem to freeze. 66 | 67 | If you are satisfied with how the identification of particles works, you can start linking the positions. Linking is a procedure that identifies whether locations in consecutive frames belong to the same particle or not. This procedure can be computationally expensive and requires fine tuning of the parameters. Linking also happens in a separate process, and in parallel to the acquisition and identification of particles. -------------------------------------------------------------------------------- /pynta/model/cameras/psi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Photonic Science GEV Model 4 | ========================== 5 | 6 | Model for Photonic Science GEV Cameras. The model just implements the basic methods defined in the 7 | :meth:`~pynta.model.cameras.base_camera.BaseCamera` using a Photonic Sicence camera. The controller for this 8 | camera is :mod:`~pynta.controller.devices.photonicscience` 9 | 10 | :copyright: Aquiles Carattino 11 | :license: GPLv3, see LICENSE for more details 12 | """ 13 | import numpy as np 14 | 15 | from pynta.controller.devices.photonicscience.scmoscam import GEVSCMOS 16 | from .base_camera import BaseCamera 17 | 18 | NUMPY_MODES = {"L": np.uint8, "I;16": np.uint16} 19 | 20 | 21 | class Camera(BaseCamera): 22 | def __init__(self, camera): 23 | self.cam_num = camera 24 | self.camera = GEVSCMOS(camera, 'SCMOS') 25 | self.running = False 26 | 27 | def initialize(self): 28 | """ 29 | Initializes the camera. 30 | 31 | .. todo:: :meth:`pynta.controller.devices.photonicscience.scmoscam.GEVSCMOS.SetGainMode` behaves unexpectedly. 32 | One is forced to set the gain mode twice to have it right. So far, this is the only way to prevent the 33 | *weird lines* from appearing. Checking the meaning of the gains is a **must**. 34 | 35 | """ 36 | self.camera.Open() 37 | self.maxSize = self.camera.UpdateSizeMax() 38 | self.camera.SetClockSpeed('50MHz') 39 | self.camera.SetGainMode( 40 | 'gain1+30_Hardware') # Do not change! It is needed for avoiding weird lines in the images. The gain can be changed at the end of this method 41 | self.camera.SetTrigger("FreeRunning") 42 | self.camera.EnableAutoLevel(0) 43 | self.camera.SetExposure(10, "Millisec") 44 | self.trigger_camera() 45 | size = self.get_size() 46 | self.max_width = size[0] 47 | self.max_height = size[1] 48 | self.camera.SetGainMode("gain30") # Change the gain here! Check scmoscam.py for information 49 | 50 | def trigger_camera(self): 51 | """Triggers the camera. 52 | """ 53 | self.camera.Snap() 54 | 55 | def set_exposure(self, exposure): 56 | """Sets the exposure of the camera. 57 | 58 | .. todo:: Include units for ensuring the proper exposure time is being set. 59 | """ 60 | exposure = exposure * 1000 # in order to always use microseconds 61 | # while self.camera.GetStatus(): # Wait until exposure is finished. 62 | self.camera.SetExposure(np.int(exposure), 'Microsec') 63 | 64 | def read_camera(self): 65 | """Reads the camera 66 | """ 67 | if self.get_acquisition_mode() == self.MODE_CONTINUOUS: 68 | self.trigger_camera() 69 | size, data = self.camera.GetImage() 70 | w, h = size 71 | mode = self.camera.GetMode() 72 | img = np.frombuffer(data, NUMPY_MODES[mode]).reshape((h, w)) 73 | img = np.array(img) 74 | return np.transpose(img) 75 | 76 | def set_ROI(self, X, Y): 77 | """Sets up the ROI. 78 | """ 79 | X -= 1 80 | Y -= 1 81 | # Left, top, right, bottom 82 | l = np.int(X[0]) 83 | t = np.int(Y[0]) 84 | r = np.int(X[1]) 85 | b = np.int(Y[1]) 86 | self.camera.SetSubArea(l, t, r, b) 87 | return self.camera.GetSize() 88 | 89 | def get_size(self): 90 | """Returns the size in pixels of the image being acquired. 91 | """ 92 | return self.camera.GetSize() 93 | 94 | def setupCamera(self, params): 95 | """Setups the camera with the given parameters. 96 | 97 | - params['exposureTime'] 98 | - params['binning'] 99 | - params['gain'] 100 | - params['frequency'] 101 | - params['ROI'] 102 | 103 | .. todo:: not implemented 104 | """ 105 | pass 106 | 107 | def getParameters(self): 108 | """Returns all the parameters passed to the camera, such as exposure time, 109 | ROI, etc. Not necessarily the parameters go to the hardware, it may be 110 | that some are just software related. 111 | 112 | :return dict: keyword => value. 113 | 114 | .. todo:: Implement this method 115 | """ 116 | pass 117 | 118 | def GetCCDWidth(self): 119 | """Gets the CCD width.""" 120 | return self.get_size()[0] 121 | 122 | def GetCCDHeight(self): 123 | """Gets the CCD height.""" 124 | return self.get_size()[1] 125 | 126 | def stopAcq(self): 127 | """Stop the acquisition even if ongoing.""" 128 | self.camera.AbortSnap() 129 | 130 | def stop_camera(self): 131 | """Stops the acquisition and closes the camera. This has to be called before quitting the program. 132 | """ 133 | self.camera.AbortSnap() 134 | self.camera.Close() -------------------------------------------------------------------------------- /pynta/view/main.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | import numpy as np 4 | 5 | from pynta.view.GUI.main_window import MainWindowGUI 6 | from pynta.view.subscriber_thread import SubscriberThread 7 | 8 | 9 | class MainWindow(MainWindowGUI): 10 | def __init__(self, experiment): 11 | """ 12 | 13 | :param nanoparticle_tracking.model.experiment.win_nanocet.NPTracking experiment: Experiment class 14 | """ 15 | super().__init__(experiment.config['GUI']['refresh_time']) 16 | 17 | self.experiment = experiment 18 | self.camera_viewer_widget.setup_roi_lines([self.experiment.max_width, self.experiment.max_height]) 19 | self.config_tracking_widget.update_config(self.experiment.config['tracking']) 20 | self.config_widget.update_config(self.experiment.config) 21 | self.tracking = False 22 | 23 | self.update_histogram_worker = SubscriberThread(self.experiment.publisher.port, 'histogram') 24 | self.update_histogram_worker.data_received.connect(self.update_histogram) 25 | self.update_histogram_worker.start() 26 | 27 | def initialize_camera(self): 28 | self.experiment.initialize_camera() 29 | 30 | def snap(self): 31 | self.experiment.snap() 32 | 33 | def update_gui(self): 34 | if self.experiment.temp_image is not None: 35 | self.camera_viewer_widget.update_image(self.experiment.temp_image) 36 | if self.experiment.tracking: 37 | locations = self.experiment.temp_locations 38 | self.camera_viewer_widget.draw_target_pointer(locations) 39 | 40 | def start_movie(self): 41 | if self.experiment.free_run_running: 42 | self.stop_movie() 43 | return 44 | self.experiment.start_free_run() 45 | self.actionStart_Movie.setToolTip('Stop Movie') 46 | 47 | def stop_movie(self): 48 | self.experiment.stop_free_run() 49 | self.actionStart_Movie.setToolTip('Start Movie') 50 | 51 | def set_roi(self): 52 | self.refresh_timer.stop() 53 | X, Y = self.camera_viewer_widget.get_roi_values() 54 | self.experiment.set_roi(X, Y) 55 | X, Y = self.experiment.camera.X, self.experiment.camera.Y 56 | X[1] += 1 57 | Y[1] += 1 58 | self.camera_viewer_widget.set_roi_lines(X, Y) 59 | self.refresh_timer.start() 60 | 61 | def clear_roi(self): 62 | self.refresh_timer.stop() 63 | self.experiment.clear_roi() 64 | self.camera_viewer_widget.set_roi_lines([0, self.experiment.max_width], [0, self.experiment.max_height]) 65 | self.refresh_timer.start() 66 | 67 | def save_image(self): 68 | self.experiment.save_image() 69 | 70 | def start_continuous_saves(self): 71 | if self.experiment.save_stream_running: 72 | self.stop_continuous_saves() 73 | return 74 | 75 | self.experiment.save_stream() 76 | self.actionStart_Continuous_Saves.setToolTip('Stop Continuous Saves') 77 | 78 | def stop_continuous_saves(self): 79 | self.experiment.stop_save_stream() 80 | self.actionStart_Continuous_Saves.setToolTip('Start Continuous Saves') 81 | 82 | def start_tracking(self): 83 | self.experiment.start_tracking() 84 | self.tracking = True 85 | 86 | def start_saving_tracks(self): 87 | self.experiment.start_saving_location() 88 | 89 | def stop_saving_tracks(self): 90 | self.experiment.stop_saving_location() 91 | 92 | def start_linking(self): 93 | if self.experiment.link_particles_running: 94 | self.stop_linking() 95 | return 96 | self.experiment.start_linking_locations() 97 | self.actionStart_Linking.setToolTip('Stop Linking') 98 | 99 | def stop_linking(self): 100 | self.experiment.stop_linking_locations() 101 | self.actionStart_Linking.setToolTip('Start Linking') 102 | 103 | def calculate_histogram(self): 104 | if not self.experiment.location.calculating_histograms: 105 | self.experiment.location.calculate_histogram() 106 | 107 | def update_histogram(self, values): 108 | if len(values) > 0: 109 | vals = np.array(values)[:, 0] 110 | vals = vals[~np.isnan(vals)] 111 | self.histogram_tracks_widget.histogram_widget.update_distribution(vals) 112 | 113 | def update_tracks(self): 114 | locations = self.experiment.location.relevant_tracks() 115 | self.histogram_tracks_widget.tracks_widget.plot_trajectories(locations) 116 | 117 | def update_tracking_config(self, config): 118 | config = dict( 119 | tracking=config 120 | ) 121 | self.experiment.update_config(**config) 122 | 123 | def update_config(self, config): 124 | self.experiment.update_config(**config) 125 | 126 | def closeEvent(self, *args, **kwargs): 127 | self.experiment.finalize() 128 | sleep(1) 129 | super().closeEvent(*args, **kwargs) 130 | 131 | --------------------------------------------------------------------------------