diff --git a/.github/workflows/installation.yml b/.github/workflows/installation.yml index e4a9f29b..15015c5d 100644 --- a/.github/workflows/installation.yml +++ b/.github/workflows/installation.yml @@ -7,65 +7,357 @@ on: branches: [ master ] jobs: - build-2004: - runs-on: ubuntu-20.04 + # Ubuntu 22.04 + build-2204: + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 + + # create log files + - name: Set up Logging + run: | + touch ${{ runner.temp }}/log + touch tigger_installer.log + touch tigger_installer.err + touch /tmp/result-file.txt + + # setup compatible with 'act' - name: Set up runner VM + id: setup-runner run: | - sudo apt update - sudo apt install xvfb + sudo apt update > ${{ runner.temp }}/log 2>&1 + # sudo apt -y upgrade > ${{ runner.temp }}/log 2>&1 + sudo apt -y install xvfb > ${{ runner.temp }}/log 2>&1 + sudo apt -y install python3-pip > ${{ runner.temp }}/log 2>&1 + sudo apt -y install psmisc > ${{ runner.temp }}/log 2>&1 + sudo apt -y install python3.10-venv > ${{ runner.temp }}/log 2>&1 + - name: Install Python dependencies + id: install-deps + run: | + pip3 install -q --upgrade testresources mypy setuptools wheel pip > ${{ runner.temp }}/log 2>&1 + pip3 install -q flake8 pytest > ${{ runner.temp }}/log 2>&1 + + - name: Lint with flake8 + id: lint run: | - pip3 install --upgrade testresources mypy setuptools wheel pip - pip3 install flake8 pytest + # stop the build if there are Python syntax errors or undefined names + flake8 . -q --count --select=E9,F63,F7,F82,F821 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . -q --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Installation script + id: install + run: | + ./install_tigger_ubuntu.sh + + - name: Check installation successful + id: check-install + run: | + grep -q 'Tigger installation complete' tigger_installer.log || exit 2 + + - name: Test Tigger loads FITS file + id: test-fits + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/star_model_image.fits & sleep 10" /tmp/result-file.txt + grep -q "Loaded FITS image test-files/star_model_image.fits" /tmp/result-file.txt || exit 2 + + - name: Test Tigger loads model file + id: test-model + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/cat.gaul & sleep 10" /tmp/result-file.txt + grep -q "Loaded 17 sources from 'Gaul' file test-files/cat.gaul" /tmp/result-file.txt || exit 2 + + - name: VENV installation script + id: venv-install run: | + python3 -m venv ~/venv > ${{ runner.temp }}/log 2>&1 + source ~/venv/bin/activate ./install_tigger_ubuntu.sh - - name: Check installer log file + + - name: Check VENV installation successful + id: venv-check-install + run: | + grep -q 'Tigger installation complete' tigger_installer.log || exit 2 + + - name: Test VENV Tigger loads FITS file + id: venv-test-fits + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/venv/bin/tigger test-files/star_model_image.fits & sleep 10" /tmp/result-file.txt + grep -q "Loaded FITS image test-files/star_model_image.fits" /tmp/result-file.txt || exit 2 + + - name: Test VENV Tigger loads model file + id: venv-test-model + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/venv/bin/tigger test-files/cat.gaul & sleep 10" /tmp/result-file.txt + grep -q "Loaded 17 sources from 'Gaul' file test-files/cat.gaul" /tmp/result-file.txt || exit 2 + + # provide summary and ouput log files upon failure + - name: Actions Failed + if: failure() + run: | + if [[ -f "tigger_installer.log" ]] + then + echo "==== Installer log output ====" + cat tigger_installer.log + echo "==== End installer log output ====" + echo "" + echo "==== Installer error log output ====" + cat tigger_installer.err + echo "==== End installer error log output ====" + echo "" + fi + if [[ ${{ steps.setup-runner.outcome }} == 'failure' ]] || [[ ${{ steps.install-deps.outcome }} == 'failure' ]] + then + echo "==== System log output ====" + cat ${{ runner.temp }}/log + echo "==== End system log output ====" + echo "" + elif [[ ${{ steps.test-fits.outcome }} == 'failure' ]] || [[ ${{ steps.test-model.outcome }} == 'failure' ]] || [[ ${{ steps.venv-test-fits.outcome }} == 'failure' ]] || [[ ${{ steps.venv-test-model.outcome }} == 'failure' ]] + then + echo "==== Test log output ====" + cat /tmp/result-file.txt + echo "==== End test log output ====" + echo "" + fi + echo "==== Step Outcomes ====" + echo "setup-runner: ${{ steps.setup-runner.outcome }}" + echo "install-deps: ${{ steps.install-deps.outcome }}" + echo "lint: ${{ steps.lint.outcome }}" + echo "install: ${{ steps.install.outcome }}" + echo "check-install: ${{ steps.check-install.outcome }}" + echo "test-fits: ${{ steps.test-fits.outcome }}" + echo "test-model: ${{ steps.test-model.outcome }}" + echo "venv-install: ${{ steps.venv-install.outcome }}" + echo "venv-check-install: ${{ steps.venv-check-install.outcome }}" + echo "venv-test-fits: ${{ steps.venv-test-fits.outcome }}" + echo "venv-test-model: ${{ steps.venv-test-model.outcome }}" + echo "" + exit 2 + + # Ubuntu 20.04 + build-2004: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + # create log files + - name: Set up Logging + run: | + touch ${{ runner.temp }}/log + touch tigger_installer.log + touch tigger_installer.err + touch /tmp/result-file.txt + + # setup compatible with 'act' + - name: Set up runner VM + id: setup-runner run: | - cat tigger_installer.log - - name: Check installer error log file + sudo apt update > ${{ runner.temp }}/log 2>&1 + # sudo apt -y upgrade > ${{ runner.temp }}/log 2>&1 + sudo apt -y install xvfb > ${{ runner.temp }}/log 2>&1 + sudo apt -y install python3-pip > ${{ runner.temp }}/log 2>&1 + sudo apt -y install psmisc > ${{ runner.temp }}/log 2>&1 + sudo apt -y install python3.8-venv > ${{ runner.temp }}/log 2>&1 + + - name: Install Python dependencies + id: install-deps run: | - cat tigger_installer.err + pip3 install -q --upgrade testresources mypy setuptools wheel pip > ${{ runner.temp }}/log 2>&1 + pip3 install -q flake8 pytest > ${{ runner.temp }}/log 2>&1 + - name: Lint with flake8 + id: lint run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82,F821 --show-source --statistics + flake8 . -q --count --select=E9,F63,F7,F82,F821 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test Tigger loads + flake8 . -q --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Installation script + id: install + run: | + ./install_tigger_ubuntu.sh + + - name: Check installation successful + id: check-install + run: | + grep -q 'Tigger installation complete' tigger_installer.log || exit 2 + + - name: Test Tigger loads FITS file + id: test-fits + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/star_model_image.fits & sleep 10" /tmp/result-file.txt + grep -q "Loaded FITS image test-files/star_model_image.fits" /tmp/result-file.txt || exit 2 + + - name: Test Tigger loads model file + id: test-model + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/cat.gaul & sleep 10" /tmp/result-file.txt + grep -q "Loaded 17 sources from 'Gaul' file test-files/cat.gaul" /tmp/result-file.txt || exit 2 + + - name: VENV installation script + id: venv-install + run: | + python3 -m venv ~/venv > ${{ runner.temp }}/log 2>&1 + source ~/venv/bin/activate + ./install_tigger_ubuntu.sh + + - name: Check VENV installation successful + id: venv-check-install run: | - xvfb-run -a $HOME/.local/bin/tigger & sleep 10 - killall tigger + grep -q 'Tigger installation complete' tigger_installer.log || exit 2 + + - name: Test VENV Tigger loads FITS file + id: venv-test-fits + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/venv/bin/tigger test-files/star_model_image.fits & sleep 10" /tmp/result-file.txt + grep -q "Loaded FITS image test-files/star_model_image.fits" /tmp/result-file.txt || exit 2 + + - name: Test VENV Tigger loads model file + id: venv-test-model + run: | + /usr/bin/script -qfc "xvfb-run -a $HOME/venv/bin/tigger test-files/cat.gaul & sleep 10" /tmp/result-file.txt + grep -q "Loaded 17 sources from 'Gaul' file test-files/cat.gaul" /tmp/result-file.txt || exit 2 + + # provide summary and ouput log files upon failure + - name: Actions Failed + if: failure() + run: | + if [[ -f "tigger_installer.log" ]] + then + echo "==== Installer log output ====" + cat tigger_installer.log + echo "==== End installer log output ====" + echo "" + echo "==== Installer error log output ====" + cat tigger_installer.err + echo "==== End installer error log output ====" + echo "" + fi + if [[ ${{ steps.setup-runner.outcome }} == 'failure' ]] || [[ ${{ steps.install-deps.outcome }} == 'failure' ]] + then + echo "==== System log output ====" + cat ${{ runner.temp }}/log + echo "==== End system log output ====" + echo "" + elif [[ ${{ steps.test-fits.outcome }} == 'failure' ]] || [[ ${{ steps.test-model.outcome }} == 'failure' ]] || [[ ${{ steps.venv-test-fits.outcome }} == 'failure' ]] || [[ ${{ steps.venv-test-model.outcome }} == 'failure' ]] + then + echo "==== Test log output ====" + cat /tmp/result-file.txt + echo "==== End test log output ====" + echo "" + fi + echo "==== Step Outcomes ====" + echo "setup-runner: ${{ steps.setup-runner.outcome }}" + echo "install-deps: ${{ steps.install-deps.outcome }}" + echo "lint: ${{ steps.lint.outcome }}" + echo "install: ${{ steps.install.outcome }}" + echo "check-install: ${{ steps.check-install.outcome }}" + echo "test-fits: ${{ steps.test-fits.outcome }}" + echo "test-model: ${{ steps.test-model.outcome }}" + echo "venv-install: ${{ steps.venv-install.outcome }}" + echo "venv-check-install: ${{ steps.venv-check-install.outcome }}" + echo "venv-test-fits: ${{ steps.venv-test-fits.outcome }}" + echo "venv-test-model: ${{ steps.venv-test-model.outcome }}" + echo "" + exit 2 + + # Ubuntu 18.04 build-1804: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 + + # create log files + - name: Set up Logging + run: | + touch ${{ runner.temp }}/log + touch tigger_installer.log + touch tigger_installer.err + touch /tmp/result-file.txt + + # setup compatible with 'act' - name: Set up runner VM + id: setup-runner run: | - sudo apt update - sudo apt install xvfb + sudo apt update > ${{ runner.temp }}/log 2>&1 + # sudo apt -y upgrade > ${{ runner.temp }}/log 2>&1 + sudo apt -y install xvfb > ${{ runner.temp }}/log 2>&1 + sudo apt -y install python3-pip > ${{ runner.temp }}/log 2>&1 + sudo apt -y install psmisc > ${{ runner.temp }}/log 2>&1 + - name: Install Python dependencies + id: install-deps run: | - pip3 install --upgrade testresources mypy setuptools wheel pip - pip3 install flake8 pytest + pip3 install -q --upgrade testresources mypy setuptools wheel pip > ${{ runner.temp }}/log 2>&1 + pip3 install -q flake8 pytest > ${{ runner.temp }}/log 2>&1 + + - name: Lint with flake8 + id: lint + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . -q --count --select=E9,F63,F7,F82,F821 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . -q --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Installation script + id: install run: | ./install_tigger_ubuntu.sh - - name: Check installer log file + + - name: Check installation successful + id: check-install run: | - cat tigger_installer.log - - name: Check installer error log file + grep -q 'Tigger installation complete' tigger_installer.log || exit 2 + + - name: Test Tigger loads FITS file + id: test-fits run: | - cat tigger_installer.err - - name: Lint with flake8 + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/star_model_image.fits & sleep 10" /tmp/result-file.txt + grep -q "Loaded FITS image test-files/star_model_image.fits" /tmp/result-file.txt || exit 2 + + - name: Test Tigger loads model file + id: test-model run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82,F821 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test Tigger loads + /usr/bin/script -qfc "xvfb-run -a $HOME/.local/bin/tigger test-files/cat.gaul & sleep 10" /tmp/result-file.txt + grep -q "Loaded 17 sources from 'Gaul' file test-files/cat.gaul" /tmp/result-file.txt || exit 2 + + # provide summary and ouput log files upon failure + - name: Actions Failed + if: failure() run: | - xvfb-run -a $HOME/.local/bin/tigger & sleep 10 - killall tigger + if [[ -f "tigger_installer.log" ]] + then + echo "==== Installer log output ====" + cat tigger_installer.log + echo "==== End installer log output ====" + echo "" + echo "==== Installer error log output ====" + cat tigger_installer.err + echo "==== End installer error log output ====" + echo "" + fi + if [[ ${{ steps.setup-runner.outcome }} == 'failure' ]] || [[ ${{ steps.install-deps.outcome }} == 'failure' ]] + then + echo "==== System log output ====" + cat ${{ runner.temp }}/log + echo "==== End system log output ====" + echo "" + elif [[ ${{ steps.test-fits.outcome }} == 'failure' ]] || [[ ${{ steps.test-model.outcome }} == 'failure' ]] + then + echo "==== Test log output ====" + cat /tmp/result-file.txt + echo "==== End test log output ====" + echo "" + fi + echo "==== Step Outcomes ====" + echo "setup-runner: ${{ steps.setup-runner.outcome }}" + echo "install-deps: ${{ steps.install-deps.outcome }}" + echo "lint: ${{ steps.lint.outcome }}" + echo "install: ${{ steps.install.outcome }}" + echo "check-install: ${{ steps.check-install.outcome }}" + echo "test-fits: ${{ steps.test-fits.outcome }}" + echo "test-model: ${{ steps.test-model.outcome }}" + echo "" + exit 2 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 432e65cc..cdb89157 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,26 @@ Tigger Changelog ================ +1.6.1 +===== + +* Version bump in preparation for release +* Supports Ubuntu 22.04 +* Beta support for Ubuntu 22.04 ARM64 +* Added FITS header preview pane to file dialog +* Dependent on the latest tigger-lsm (1.7.1) +* Fixed float errors with updated library API's +* Fixed dockable widgets and window sizing +* Refactored plot zooming +* Re-enabled splash screen +* Fixes venv pip3 bug +* Fixes issue #131 +* Install script 'setup.py install' has been replaced by pip +* Updated URL's +* GitHub Actions are now compatible with 'act' +* Actions now test VENV installations on Ubuntu 20.04 and 22.04 +* Improved Action tests and logging + 1.6.0 ===== diff --git a/Makefile b/Makefile index f99a4d54..3a1342d1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -DOCKER_REPO=radioastro/tigger:1.6.0 +DOCKER_REPO=radioastro/tigger:1.6.1 .PHONY: build clean diff --git a/README.rst b/README.rst index f4c257c8..13cbdb54 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,16 @@ Tigger ====== +|actionsbadge| |actionspypibadge| |versionbadge| + +.. |actionsbadge| image:: https://github.com/ratt-ru/tigger/workflows/Tigger%20Ubuntu%20CI/badge.svg + :target: https://github.com/ratt-ru/tigger/actions +.. |actionspypibadge| image:: https://github.com/ratt-ru/tigger/workflows/Deploy%20Python%20Package/badge.svg + :target: https://github.com/ratt-ru/tigger/actions +.. |versionbadge| image:: https://img.shields.io/github/v/release/ratt-ru/tigger + :target: https://github.com/ratt-ru/tigger/releases + :alt: GitHub release (latest by date) + .. image:: https://user-images.githubusercontent.com/7116312/113705452-5ac51d00-96d5-11eb-8087-5d2a8ccad99a.png Installing Tigger @@ -18,7 +28,7 @@ From source with Ubuntu LTS Python dependencies ^^^^^^^^^^^^^^^^^^^ -* Tigger-LSM v1.7.0 - if you are not installing Tigger via the KERN repository or using the ``install_tigger_ubuntu.sh`` script provided, please go here and install this first. +* Tigger-LSM v1.7.1 - if you are not installing Tigger via the KERN repository or using the ``install_tigger_ubuntu.sh`` script provided, please go here and install this first. Automatically installed Python dependencies: @@ -42,9 +52,9 @@ Install on Ubuntu LTS with the installation script Download the Tigger repository:: - git clone https://github.com/ska-sa/tigger.git + git clone https://github.com/ratt-ru/tigger.git -The installation script works on Ubuntu 18.04, 20.04 and 21.04. +The installation script works on Ubuntu 18.04, 20.04 and 22.04 (including ARM64). Run the installation script and enter ``sudo`` password when prompted:: @@ -53,13 +63,13 @@ Run the installation script and enter ``sudo`` password when prompted:: Manual installation from source ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -After the Tigger repository has been downloaded with ``git clone https://github.com/ska-sa/tigger.git``, please run the following:: +After the Tigger repository has been downloaded with ``git clone https://github.com/ratt-ru/tigger.git``, please run the following:: sudo apt -y install python3-pyqt5.qtsvg python3-pyqt5.qtopengl libqwt-qt5-6 - sudo dpkg -i debian_pkgs/ubuntu_20_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb - python3 setup.py install --user + sudo dpkg -i debian_pkgs/ubuntu_22_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb + pip3 install . --user -Please note that the above commands are for installing on Ubuntu 20.04, Debian packages for 18.04 and 21.04 are located in the ``ubuntu_18_04_deb_pkg`` and ``ubuntu_21_04_deb_pkg`` directories respectively. +Please note that the above commands are for installing on Ubuntu 22.04, Debian packages for 18.04, 20.04 and 22.04 ARM64 are located in the ``ubuntu_18_04_deb_pkg``, ``ubuntu_20_04_deb_pkg`` and ``ubuntu_20_04__arm64_deb_pkg`` directories respectively. Running Tigger ============== @@ -71,11 +81,13 @@ Questions or problems Open an issue on github -https://github.com/ska-sa/tigger +https://github.com/ratt-ru/tigger/issues +Contributors +============ -Travis -====== +Thank you to the people who have contributed to this project. + +.. image:: https://contrib.rocks/image?repo=ratt-ru/tigger + :target: https://github.com/ratt-ru/tigger/graphs/contributors -.. image:: https://travis-ci.org/ska-sa/tigger.svg?branch=master - :target: https://travis-ci.org/ska-sa/tigger diff --git a/TigGUI/AboutDialog.py b/TigGUI/AboutDialog.py index f5c8fee7..37f71b97 100644 --- a/TigGUI/AboutDialog.py +++ b/TigGUI/AboutDialog.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -89,8 +89,8 @@ def languageChange(self): self.setWindowTitle(self.__tr("About Tigger")) self.title_label.setText(self.__tr( \ """

Tigger %s

-

\u00a92010-2021 Oleg Smirnov & Rhodes University & SKA SA
-
Please direct feedback and bug reports at https://github.com/ska-sa/tigger

+

\u00a92010-2022 Oleg Smirnov & Rhodes University & SKA SA
+
Please direct feedback and bug reports at https://github.com/ratt-ru/tigger

""" % (release_string) \ )) diff --git a/TigGUI/Images/Colormaps.py b/TigGUI/Images/Colormaps.py index 3a0a51b4..d2eed180 100644 --- a/TigGUI/Images/Colormaps.py +++ b/TigGUI/Images/Colormaps.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Images/ControlDialog.py b/TigGUI/Images/ControlDialog.py index d01ffc6f..d682d0f6 100644 --- a/TigGUI/Images/ControlDialog.py +++ b/TigGUI/Images/ControlDialog.py @@ -1,5 +1,4 @@ - -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -25,7 +24,7 @@ import numpy from PyQt5.Qt import QWidget, QHBoxLayout, QComboBox, QLabel, QLineEdit, QDialog, QToolButton, QVBoxLayout, \ Qt, QSize, QSizePolicy, QApplication, QColor, QBrush, QTimer, QFrame, QCheckBox, QStackedWidget, QIcon, QMenu, \ - QGridLayout, QPen, QRect + QGridLayout, QPen, QRectF from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QDockWidget from PyQt5.Qwt import QwtPlot, QwtText, QwtPlotItem, QwtPlotCurve, QwtSymbol, QwtLinearScaleEngine, QwtLogScaleEngine, \ @@ -86,9 +85,9 @@ def __init__(self, parent, rc, imgman): # lo0.setContentsMargins(0,0,0,0) # histogram plot - whide = self.makeButton("Hide", self.hide, width=128) - whide.setShortcut(Qt.Key_F9) - lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide])) + self.whide = self.makeButton("Hide", self.hide, width=128) + self.whide.setShortcut(Qt.Key_F9) + lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[self.whide])) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) self._histplot = QwtPlot(self) @@ -544,7 +543,7 @@ def draw(self, painter, xmap, ymap, rect): # remap into an Nx1 image qimg = self.cmap.colorize(self.imap.remap(xp.reshape((len(xp), 1)))) # plot image - painter.drawImage(QRect(xp1, y0, xdp, dy), qimg) + painter.drawImage(QRectF(xp1, y0, xdp, dy), qimg) class HistogramLineMarker: """Helper class implementing a line marker for a histogram plot""" diff --git a/TigGUI/Images/Controller.py b/TigGUI/Images/Controller.py index 2177a767..5c7ee356 100644 --- a/TigGUI/Images/Controller.py +++ b/TigGUI/Images/Controller.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -28,7 +28,7 @@ from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QDockWidget, QSizePolicy, QWidget, QPushButton, QStyle from PyQt5.Qwt import QwtText, QwtPlotCurve, QwtPlotMarker, QwtScaleMap, QwtPlotItem -from PyQt5.QtCore import pyqtSignal, QPointF, QSize +from PyQt5.QtCore import pyqtSignal, QPoint, QPointF, QSize import TigGUI.kitties.utils from TigGUI.Images.SkyImage import FITSImagePlotItem @@ -345,16 +345,22 @@ def showRenderControls(self): if not self._control_dialog.isVisible(): dprint(1, "showing control dialog") self._control_dialog.show() - self._dockable_colour_ctrl.setVisible(True) - self.addDockWidgetToTab() - self._dockable_colour_ctrl.show() - self._dockable_colour_ctrl.raise_() + if self._dockable_colour_ctrl is not None: + self._dockable_colour_ctrl.setVisible(True) + self.addDockWidgetToTab() + self._dockable_colour_ctrl.show() + self._dockable_colour_ctrl.raise_() + if not self.get_docked_widget_size(self._dockable_colour_ctrl): + geo = self.parent().mainwin.geometry() + geo.setWidth(self.parent().mainwin.width() + self._dockable_colour_ctrl.width()) + self.parent().mainwin.setGeometry(geo) else: self._control_dialog.hide() self._dockable_colour_ctrl.setVisible(False) - if self.parent().mainwin.windowState() != Qt.WindowMaximized: - self.parent().mainwin.setMaximumWidth( - self.parent().mainwin.width() + self._dockable_colour_ctrl.width()) + if not self.get_docked_widget_size(self._dockable_colour_ctrl): + geo = self.parent().mainwin.geometry() + geo.setWidth(self.parent().mainwin.width() - self._dockable_colour_ctrl.width()) + self.parent().mainwin.setGeometry(geo) def addDockWidgetToTab(self): # Add dockable widget to main window. @@ -396,17 +402,43 @@ def removeDockWidget(self): def colourctrl_dockwidget_closed(self): self._dockable_colour_ctrl.setVisible(False) if self.parent().mainwin.windowState() != Qt.WindowMaximized: - self.parent().mainwin.setMaximumWidth(self.parent().mainwin.width() + self._dockable_colour_ctrl.width()) + if not self.get_docked_widget_size(self._dockable_colour_ctrl): + if not self._dockable_colour_ctrl.isFloating(): + geo = self.parent().mainwin.geometry() + geo.setWidth(self.parent().mainwin.width() - self._dockable_colour_ctrl.width()) + self.parent().mainwin.setGeometry(geo) def colourctrl_dockwidget_toggled(self): - if self._dockable_colour_ctrl.isVisible(): - if self._dockable_colour_ctrl.isWindow(): - self._dockable_colour_ctrl.setFloating(False) - else: - self._dockable_colour_ctrl.setFloating(True) - if self.parent().mainwin.windowState() != Qt.WindowMaximized: - self.parent().mainwin.setMaximumWidth( - self.parent().mainwin.width() + self._dockable_colour_ctrl.width()) + if not self._dockable_colour_ctrl.isVisible(): + return + if self._dockable_colour_ctrl.isWindow(): + self._dockable_colour_ctrl.setFloating(False) + if self.parent().mainwin.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self._dockable_colour_ctrl): + geo = self.parent().mainwin.geometry() + geo.setWidth(self.parent().mainwin.width() + self._dockable_colour_ctrl.width()) + self.parent().mainwin.setGeometry(geo) + else: + self._dockable_colour_ctrl.setFloating(True) + if self.parent().mainwin.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self._dockable_colour_ctrl): + geo = self.parent().mainwin.geometry() + geo.setWidth(self.parent().mainwin.width() - self._dockable_colour_ctrl.width()) + self.parent().mainwin.setGeometry(geo) + + def get_docked_widget_size(self, _dockable): + widget_list = self.parent().mainwin.findChildren(QDockWidget) + size_list = [] + if _dockable: + for widget in widget_list: + if isinstance(widget.bind_widget, ImageControlDialog): + if widget is not _dockable: + if not widget.isWindow() and not widget.isFloating() and widget.isVisible(): + size_list.append(widget.bind_widget.width()) + if size_list: + return max(size_list) + else: + return size_list def _changeDisplayRangeToPercent(self, percent): if not self._control_dialog: diff --git a/TigGUI/Images/Manager.py b/TigGUI/Images/Manager.py index 740efa21..6e7ceeed 100644 --- a/TigGUI/Images/Manager.py +++ b/TigGUI/Images/Manager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -20,15 +20,16 @@ # import os.path +from re import split import sys import time import traceback import numpy -from PyQt5.Qt import (QWidget, QFileDialog, QVBoxLayout, QApplication, QMenu, QClipboard, QInputDialog, QActionGroup) +from PyQt5.Qt import (QWidget, QFileDialog, QVBoxLayout, QApplication, QMenu, QClipboard, QInputDialog, QActionGroup, QTextOption, QFont) from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDockWidget +from PyQt5.QtWidgets import QDockWidget, QLabel, QPlainTextEdit from astropy.io import fits as pyfits from TigGUI.Images import FITS_ExtensionList @@ -89,6 +90,8 @@ def __init__(self, *args): self._repopulateMenu() self.signalShowMessage = None self.signalShowErrorMessage = None + # FITS header preview pane + self.fits_info = QPlainTextEdit() def close(self): dprint(1, "closing Manager") @@ -105,6 +108,25 @@ def setShowErrorMessageSignal(self, _signal): def setMainWindow(self, _mainwin): self.mainwin = _mainwin + def FITSHeaderPreview(self, fname): + """Loads information for the FITS header preview pane. + Connected via the QFileDialog currentChanged signal. + """ + if os.path.isfile(fname): + name = os.path.basename(fname) + split_name = os.path.splitext(name) + if split_name[1].startswith(tuple(FITS_ExtensionList)): + try: + with pyfits.open(fname) as hdu: + hdu.verify('silentfix') + hdr = hdu[0].header + self.fits_info.setPlainText( + "[File size: " + str(round(hdu._file.tell()/1024/1024, 2)) + " MiB]\n" + hdr.tostring(sep='\n', padding=True)) + except: + self.fits_info.setPlainText("Error Reading FITS file") + else: + self.fits_info.clear() + def loadImage(self, filename=None, duplicate=True, to_top=True, model=None): """Loads image. Returns ImageControlBar object. If image is already loaded: returns old ICB if duplicate=False (raises to top if to_top=True), @@ -116,10 +138,31 @@ def loadImage(self, filename=None, duplicate=True, to_top=True, model=None): if not self._load_image_dialog: dialog = self._load_image_dialog = QFileDialog(self, "Load FITS image", ".", "FITS images (%s);;All files (*)" % (" ".join( - ["*" + ext for ext in FITS_ExtensionList]))) + ["*" + ext for ext in FITS_ExtensionList])), + options=QFileDialog.DontUseNativeDialog) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setModal(True) dialog.filesSelected['QStringList'].connect(self.loadImage) + layout = dialog.layout() + if layout: + # FITS header preview pane + dialog.currentChanged.connect(self.FITSHeaderPreview) + self.fits_info.setMinimumWidth(263) + dialog.setMinimumWidth(dialog.width() + self.fits_info.minimumWidth()) + self.fits_info.setWordWrapMode(QTextOption.NoWrap) + self.fits_info.setLineWrapMode(QPlainTextEdit.NoWrap) + self.fits_info.setTabStopWidth(40) + f = QFont() + f.setFamily("Monospace") + f.setPointSize(10) + f.setFixedPitch(True) + self.fits_info.setFont(f) + self.fits_info.setReadOnly(True) + _flabel = QLabel("FITS File Information") + _flabel.setAlignment(Qt.AlignHCenter) + layout.addWidget(_flabel, 0, 3) + layout.addWidget(self.fits_info, 1, 3, 3, 1) + dialog.setLayout(layout) self._load_image_dialog.exec_() return None if isinstance(filename, QStringList): diff --git a/TigGUI/Images/RenderControl.py b/TigGUI/Images/RenderControl.py index f1a181ca..b8cc19ad 100644 --- a/TigGUI/Images/RenderControl.py +++ b/TigGUI/Images/RenderControl.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Images/SkyImage.py b/TigGUI/Images/SkyImage.py index f04a90c3..017b4b34 100644 --- a/TigGUI/Images/SkyImage.py +++ b/TigGUI/Images/SkyImage.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -239,7 +239,7 @@ def draw(self, painter, xmap, ymap, rect, use_cache=True): yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist() dprint(5, "draw:", rect, xinfo, yinfo) self._current_rect = QRectF(QPointF(xs2, ys1), QSizeF(xds, yds)) - self._current_rect_pix = QRect(QPoint(*self.lmToPix(xs1, ys1)), QPoint(*self.lmToPix(xs2, ys2))).intersected( + self._current_rect_pix = QRectF(QPointF(*self.lmToPix(xs1, ys1)), QPointF(*self.lmToPix(xs2, ys2))).toRect().intersected( self._bounding_rect_pix) dprint(5, "draw:", self._current_rect_pix) # put together tuple describing current mapping @@ -338,7 +338,7 @@ def draw(self, painter, xmap, ymap, rect, use_cache=True): self._cache_qimage[self._image_key] = self.qimg.copy() # now draw the image t0 = time.time() - painter.drawImage(xp1, yp2, self.qimg) + painter.drawImage(QPointF(xp1, yp2), self.qimg) dprint(2, "drawing took", time.time() - t0, "secs") # when exporting images to PNG cache needs to be cleared if not use_cache: diff --git a/TigGUI/Images/__init__.py b/TigGUI/Images/__init__.py index 8a6616ce..7d76b5d2 100644 --- a/TigGUI/Images/__init__.py +++ b/TigGUI/Images/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/MainWindow.py b/TigGUI/MainWindow.py index ada80313..35fc5713 100644 --- a/TigGUI/MainWindow.py +++ b/TigGUI/MainWindow.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -326,7 +326,6 @@ def setLayout(self, layout): self.skyplot._liveprofile.setVisible(True) self.skyplot._dockable_liveprofile.setVisible(True) self.addDockWidget(Qt.LeftDockWidgetArea, self.skyplot._dockable_liveprofile) - # resize dock areas widget_list = self.findChildren(QDockWidget) size_list = [] @@ -342,6 +341,9 @@ def setLayout(self, layout): widget_list = result # resize dock areas self.resizeDocks(widget_list, size_list, Qt.Horizontal) + geo = self.geometry() + geo.setWidth(self.width() + max(size_list)) + self.setGeometry(geo) elif layout is self.LayoutImageModel: self.tw.show() self.grouptab.show() diff --git a/TigGUI/Plot/MouseModes.py b/TigGUI/Plot/MouseModes.py index a1c32eb2..c09f589b 100644 --- a/TigGUI/Plot/MouseModes.py +++ b/TigGUI/Plot/MouseModes.py @@ -1,3 +1,24 @@ +# Copyright (C) 2002-2022 +# The MeqTree Foundation & +# ASTRON (Netherlands Foundation for Research in Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see , +# or write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + from PyQt5.Qt import QObject, Qt, QActionGroup from PyQt5.QtCore import pyqtSignal diff --git a/TigGUI/Plot/SkyModelPlot.py b/TigGUI/Plot/SkyModelPlot.py index 6607c07f..392dfb77 100644 --- a/TigGUI/Plot/SkyModelPlot.py +++ b/TigGUI/Plot/SkyModelPlot.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -61,6 +56,7 @@ from Tigger.Models.SkyModel import SkyModel from TigGUI.Widgets import TiggerPlotCurve, TiggerPlotMarker, TDockWidget, TigToolTip from TigGUI.Plot import MouseModes +from TigGUI.Images.ControlDialog import ImageControlDialog # plot Z depths for various classes of objects Z_Image = 1000 @@ -160,7 +156,7 @@ def _setupMarker(self, style, label): if style.symbol == "dot": self._symbol.setSize(2) else: - self._symbol.setSize(self._size) + self._symbol.setSize(int(self._size)) self._symbol.setPen(QPen(symbol_color, style.symbol_linewidth)) self._symbol.setBrush(QBrush(Qt.NoBrush)) lab_pen = QPen(Qt.NoPen) @@ -322,9 +318,36 @@ def setVisible(self, visible, emit=True): if visible and not self.parent().isVisible(): self.parent().setGeometry(self.geometry()) self.parent().setVisible(True) + if self.parent().main_win.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self.parent()): + geo = self.parent().main_win.geometry() + geo.setWidth(self.parent().main_win.width() + self.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() - self.width(), geo.y())) + self.parent().main_win.setGeometry(geo) elif not visible and self.parent().isVisible(): + if self.parent().main_win.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self.parent()): + geo = self.parent().main_win.geometry() + geo.setWidth(self.parent().main_win.width() - self.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() + self.width(), geo.y())) + self.parent().main_win.setGeometry(geo) self.parent().setVisible(False) + def get_docked_widget_size(self, _dockable): + widget_list = self.parent().main_win.findChildren(QDockWidget) + size_list = [] + if _dockable: + for widget in widget_list: + if not isinstance(widget.bind_widget, ImageControlDialog): + if widget.bind_widget != _dockable.bind_widget: + if not widget.isWindow() and not widget.isFloating() and widget.isVisible(): + size_list.append(widget.bind_widget.width()) + if size_list: + return max(size_list) + else: + return size_list class LiveImageZoom(ToolDialog): livezoom_resize_signal = pyqtSignal(QSize) @@ -458,8 +481,8 @@ def setPlotSize(self, radius, factor): self._zoomplot.setMinimumHeight(height + 80) self._zoomplot.setMinimumWidth(width + 80) # set data array - self._data = numpy.ma.masked_array(numpy.zeros((int(self._npix), int(self._npix)), float), - numpy.zeros((int(self._npix), int(self._npix)), bool)) + self._data = numpy.ma.masked_array(numpy.zeros((self._npix, self._npix), float), + numpy.zeros((self._npix, self._npix), bool)) # reset window size self._lo0.update() self.resize(self._lo0.minimumSize()) @@ -486,7 +509,8 @@ def setImage(self, qimg): def draw(self, painter, xmap, ymap, rect): """Implements QwtPlotItem.draw(), to render the image on the given painter.""" - self._qimg and painter.drawImage(QRect(xmap.p1(), ymap.p2(), xmap.pDist(), ymap.pDist()), self._qimg) + # drawImage expects QRectF + self._qimg and painter.drawImage(QRectF(xmap.p1(), ymap.p2(), xmap.pDist(), ymap.pDist()), self._qimg) def trackImage(self, image, ix, iy): if not self.isVisible(): @@ -501,13 +525,14 @@ def trackImage(self, image, ix, iy): # There was an error here when using zoom window zoom buttons # (TypeError: slice indices must be integers or None or have an __index__ method). # Therefore indexes have been cast as int() + # 16/05/2022: the error no longer occurs, therefore code has been reverted. self._data.mask[...] = False - self._data.mask[:int(zx0), ...] = True - self._data.mask[int(zx1):, ...] = True - self._data.mask[..., :int(zy0)] = True - self._data.mask[..., int(zy1):] = True + self._data.mask[:zx0, ...] = True + self._data.mask[zx1:, ...] = True + self._data.mask[..., :zy0] = True + self._data.mask[..., zy1:] = True # copy & colorize region - self._data[int(zx0):int(zx1), int(zy0):int(zy1)] = image.image()[int(ix0):int(ix1), int(iy0):int(iy1)] + self._data[zx0:zx1, zy0:zy1] = image.image()[ix0:ix1, iy0:iy1] intensity = image.intensityMap().remap(self._data) self._zi.setImage( image.colorMap().colorize(image.intensityMap().remap(self._data)).transformed(self._xform)) @@ -515,7 +540,8 @@ def trackImage(self, image, ix, iy): # set cross-sections if self._showcs.isChecked(): if iy >= 0 and iy < ny and ix1 > ix0: - xcs = [float(x) for x in image.image()[int(ix0):int(ix1), int(iy)]] + # added fix for masked arrays and mosaic images + xcs = [float(x) for x in numpy.ma.filled(image.image()[ix0:ix1, iy], fill_value=0.0)] self._xcs.setData(numpy.arange(ix0 - 1, ix1) + .5, [xcs[0]] + xcs) self._xcs.setVisible(True) self._zoomplot.setAxisAutoScale(QwtPlot.yRight) @@ -524,8 +550,8 @@ def trackImage(self, image, ix, iy): self._xcs.setVisible(False) self._zoomplot.setAxisScale(QwtPlot.yRight, 0, 1) if ix >= 0 and ix < nx and iy1 > iy0: - ycs = [float(y) for y in image.image()[int(ix), int(iy0):int(iy1)]] - # self._ycs.setData([ycs[0]] + ycs, numpy.arange(iy0 - 1, iy1) + .5) + # added fix for masked arrays and mosaic images + ycs = [float(y) for y in numpy.ma.filled(image.image()[ix, iy0:iy1], fill_value=0.0)] self._ycs.setData([ycs[0]] + ycs, numpy.arange(iy0 - 1, iy1) + .5) self._ycs.setVisible(True) self._zoomplot.setAxisAutoScale(QwtPlot.xTop) @@ -675,7 +701,10 @@ def trackImage(self, image, ix, iy): i0 = rect.topLeft().y() i1 = i0 + rect.height() self._profplot.setAxisScale(QwtPlot.xBottom, xval[i0], xval[i1 - 1]) - self._profcurve.setData(xval[i0:i1], yval[i0:i1]) + # added fix for masked arrays and mosaic images + yval = numpy.ma.filled(yval[i0:i1], fill_value=0.0) + xval = numpy.ma.filled(xval[i0:i1], fill_value=0.0) + self._profcurve.setData(xval, yval) self._profcurve.setVisible(inrange) # update plots self._profplot.replot() @@ -726,19 +755,23 @@ def dropEvent(self, event): return self._mainwin.dropEvent(event) def lmPosToScreen(self, fpos): - return QPoint(self.transform(QwtPlot.xBottom, fpos.x()), self.transform(QwtPlot.yLeft, fpos.y())) + # transform -> float + return QPointF(self.transform(QwtPlot.xBottom, fpos.x()), self.transform(QwtPlot.yLeft, fpos.y())) - def lmRectToScreen(self, frect): - return QRect(self.lmPosToScreen(frect.topLeft()), self.lmPosToScreen(frect.bottomRight())) + def lmRectToScreen(self, frect): # seemingly unused + # lmPosToScreen -> float + return QRectF(self.lmPosToScreen(frect.topLeft()), self.lmPosToScreen(frect.bottomRight())) def screenPosToLm(self, pos): + # invtransform -> float return QPointF(self.invTransform(QwtPlot.xBottom, pos.x()), self.invTransform(QwtPlot.yLeft, pos.y())) def screenRectToLm(self, rect): + # screenPosToLm -> float return QRectF(self.screenPosToLm(rect.topLeft()), self.screenPosToLm(rect.bottomRight())) def getMarkerPosition(self, marker): - """Returns QPoint associated with the given marker. Caches coordinate conversion by marker ID.""" + """Returns QPointF associated with the given marker. Caches coordinate conversion by marker ID.""" mid = id(marker) pos = self._coord_cache.get(mid) if pos is None: @@ -800,7 +833,10 @@ def updatePlot(self): self.replot() class PlotZoomer(QwtPlotZoomer): - provisionalZoom = pyqtSignal(float, float, int, int) + # draws the zoom box overlay and selects zoom area + provisionalZoom = pyqtSignal(float, float, int) + # renders the zoom overlay box + replotProvisionalZoom = pyqtSignal() def __init__(self, canvas, updateLayoutEvent, track_callback=None, label=None): QwtPlotZoomer.__init__(self, canvas) @@ -822,6 +858,8 @@ def __init__(self, canvas, updateLayoutEvent, track_callback=None, label=None): # watch plot for changes: if resized, aspect ratios need to be checked self._updateLayoutEvent = updateLayoutEvent self._updateLayoutEvent.connect(self._checkAspects) + self._zoom_in_process = False # zoom wheel lock + self._zoom_wheel_threshold = 0 # zoom wheel 1/8th rotaions def isFixedAspect(self): return self._fixed_aspect @@ -891,7 +929,8 @@ def zoom(self, rect): x2 = self.plot().transform(self.xAxis(), x2) y2 = self.plot().transform(self.yAxis(), y2) dprint(2, "zoom by", abs(x1 - x2), abs(y1 - y2)) - if abs(x1 - x2) <= 20 and abs(y1 - y2) <= 20: + if abs(x1 - x2) <= 40 and abs(y1 - y2) <= 40: + self._zoom_in_process = False # zoom wheel lock return if isinstance(rect, int) or rect.isValid(): dprint(2, "zoom", rect) @@ -915,13 +954,34 @@ def widgetMouseDoubleClickEvent(self, ev): x = self.plot().invTransform(self.xAxis(), ev.x()) y = self.plot().invTransform(self.yAxis(), ev.y()) if int(ev.button()) == self._dczoom_button and int(ev.modifiers()) == self._dczoom_modifiers: - self.provisionalZoom.emit(x, y, 1, 10) + self.provisionalZoom.emit(x, y, 1) def widgetWheelEvent(self, ev): x = self.plot().invTransform(self.xAxis(), ev.x()) y = self.plot().invTransform(self.yAxis(), ev.y()) - if self._use_wheel: - self.provisionalZoom.emit(x, y, (1 if ev.angleDelta().y() > 0 else -1), 200) + if self._use_wheel and not self._zoom_in_process: + # angleDelta is the relative amount the wheel was rotated, + # in eighths of a degree. Therefore, + # 120 / 8 = 15 which is 1 wheel increment. + n_deg = ev.angleDelta().y() / 8 + self._zoom_wheel_threshold += n_deg # collect 1/8th rotaions + # process trackpad or mouse wheel + if abs(self._zoom_wheel_threshold / 15) < 1: + # process trackpad scroll + n_deg = (self._zoom_wheel_threshold / 15) * 10 + if n_deg > 7.5: + self.provisionalZoom.emit(x, y, 1) + self._zoom_wheel_threshold = 0 + elif n_deg < -7.5: + self.provisionalZoom.emit(x, y, -1) + self._zoom_wheel_threshold = 0 + # process mouse wheel + elif self._zoom_wheel_threshold >= 15: + self.provisionalZoom.emit(x, y, 1) + self._zoom_wheel_threshold = 0 + elif self._zoom_wheel_threshold <= -15: + self.provisionalZoom.emit(x, y, -1) + self._zoom_wheel_threshold = 0 QwtPlotPicker.widgetWheelEvent(self, ev) class PlotPicker(QwtPlotPicker): @@ -1200,7 +1260,13 @@ def livezoom_dockwidget_closed(self): if ea_action.text() == 'Show live zoom && cross-sections': self._dockable_livezoom.setVisible(False) if self._mainwin.windowState() != Qt.WindowMaximized: - self._mainwin.setMaximumWidth(self._mainwin.width() + self._dockable_livezoom.width()) + if not self.get_docked_widget_size(self._dockable_livezoom): + if not self._dockable_livezoom.isFloating(): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() - self._dockable_livezoom.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() + self._dockable_livezoom.width(), geo.y())) + self._mainwin.setGeometry(geo) ea_action.setChecked(False) def liveprofile_dockwidget_closed(self): @@ -1209,26 +1275,70 @@ def liveprofile_dockwidget_closed(self): if ea_action.text() == 'Show profiles': self._dockable_liveprofile.setVisible(False) if self._mainwin.windowState() != Qt.WindowMaximized: - self._mainwin.setMaximumWidth(self._mainwin.width() + self._dockable_liveprofile.width()) + if not self.get_docked_widget_size(self._dockable_liveprofile): + if not self._dockable_liveprofile.isFloating(): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() - self._dockable_liveprofile.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() + self._dockable_liveprofile.width(), geo.y())) + self._mainwin.setGeometry(geo) ea_action.setChecked(False) def liveprofile_dockwidget_toggled(self): if self._dockable_liveprofile.isVisible(): if self._dockable_liveprofile.isWindow(): self._dockable_liveprofile.setFloating(False) + if self._mainwin.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self._dockable_liveprofile): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() + self._dockable_liveprofile.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() - self._dockable_liveprofile.width(), geo.y())) + self._mainwin.setGeometry(geo) else: self._dockable_liveprofile.setFloating(True) if self._mainwin.windowState() != Qt.WindowMaximized: - self._mainwin.setMaximumWidth(self._mainwin.width() + self._dockable_liveprofile.width()) + if not self.get_docked_widget_size(self._dockable_liveprofile): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() - self._dockable_liveprofile.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() + self._dockable_liveprofile.width(), geo.y())) + self._mainwin.setGeometry(geo) def livezoom_dockwidget_toggled(self): if self._dockable_livezoom.isVisible(): if self._dockable_livezoom.isWindow(): self._dockable_livezoom.setFloating(False) + if self._mainwin.windowState() != Qt.WindowMaximized: + if not self.get_docked_widget_size(self._dockable_livezoom): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() + self._dockable_livezoom.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() - self._dockable_livezoom.width(), geo.y())) + self._mainwin.setGeometry(geo) else: self._dockable_livezoom.setFloating(True) if self._mainwin.windowState() != Qt.WindowMaximized: - self._mainwin.setMaximumWidth(self._mainwin.width() + self._dockable_livezoom.width()) + if not self.get_docked_widget_size(self._dockable_livezoom): + geo = self._mainwin.geometry() + geo.setWidth(self._mainwin.width() - self._dockable_livezoom.width()) + center = geo.center() + geo.moveCenter(QPoint(center.x() + self._dockable_livezoom.width(), geo.y())) + self._mainwin.setGeometry(geo) + + def get_docked_widget_size(self, _dockable): + widget_list = self._mainwin.findChildren(QDockWidget) + size_list = [] + if _dockable: + for widget in widget_list: + if not isinstance(widget.bind_widget, ImageControlDialog): + if widget.bind_widget != _dockable.bind_widget: + if not widget.isWindow() and not widget.isFloating() and widget.isVisible(): + size_list.append(widget.bind_widget.width()) + if size_list: + return max(size_list) + else: + return size_list def setupShowMessages(self, _signal): self.plotShowMessage = _signal @@ -1312,10 +1422,11 @@ def _initPickers(self): self._zoomer_label.setLabelAlignment(Qt.AlignBottom | Qt.AlignRight) for item in self._zoomer_label, self._zoomer_box: item.setZ(Z_Markup) - self._provisional_zoom_timer = QTimer(self) + self._provisional_zoom_timer = QTimer(self) # does the zooming self._provisional_zoom_timer.setSingleShot(True) self._provisional_zoom_timer.timeout.connect(self._finalizeProvisionalZoom) self._provisional_zoom = None + self._zoomer.replotProvisionalZoom.connect(self._replot) # previous version of Qwt had Rect or Drag selection modes. # self._zoomer.setSelectionFlags(QwtPicker.RectSelection | QwtPicker.DragSelection) @@ -1677,7 +1788,11 @@ def _updatePsfMarker(self, rect=None, replot=False): def _replot(self): dprint(1, "replot") self.plot.clearDrawCache() - self.plot.replot() + # render the zoom box overlay + self.plot.updateCurrentPlot.emit() + # delay the processing of the actual zooming + # to allow the zoom box overlay to be rendered + self._provisional_zoom_timer.start(200) def _addPlotMarkup(self, items): """Adds a list of QwtPlotItems to the markup""" @@ -1863,9 +1978,12 @@ def _selectRect(self, rect, world=True, mode=SelectionClear | SelectionAdd): def _finalizeProvisionalZoom(self): if self._provisional_zoom is not None: + self._zoomer._zoom_in_process = True # zoom wheel lock self._zoomer.zoom(self._provisional_zoom) - - def _plotProvisionalZoom(self, x, y, level, timeout=200): + else: + self._zoomer._zoom_in_process = False # zoom wheel lock + + def _plotProvisionalZoom(self, x, y, level): """Called when mouse wheel is used to zoom in our out""" self._provisional_zoom_level += level self._zoomer_box.setVisible(False) @@ -1875,7 +1993,17 @@ def _plotProvisionalZoom(self, x, y, level, timeout=200): x1, y1, x2, y2 = self._zoomer.zoomRect().getCoords() w = (x2 - x1) / 2 ** self._provisional_zoom_level h = (y2 - y1) / 2 ** self._provisional_zoom_level - self._provisional_zoom = QRectF(x - w / 2, y - h / 2, w, h) + # check that it's not too small, ignore if it is + new_zoom = QRectF(x - w / 2, y - h / 2, w, h) + x1, y1, x2, y2 = new_zoom.getCoords() + x1 = self.plot.transform(self._zoomer.xAxis(), x1) + y1 = self.plot.transform(self._zoomer.yAxis(), y1) + x2 = self.plot.transform(self._zoomer.xAxis(), x2) + y2 = self.plot.transform(self._zoomer.yAxis(), y2) + if abs(x1 - x2) <= 40 and abs(y1 - y2) <= 40: + self._zoom_in_process = False # zoom wheel lock + return + self._provisional_zoom = new_zoom x1, y1, x2, y2 = self._provisional_zoom.getCoords() self._zoomer_box.setData([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1]) self._zoomer_label.setValue(max(x1, x2), max(y1, y2)) @@ -1883,7 +2011,7 @@ def _plotProvisionalZoom(self, x, y, level, timeout=200): self._zoomer_label.setLabel(self._zoomer_label_text) self._zoomer_box.setVisible(True) self._zoomer_label.setVisible(True) - else: + elif self._provisional_zoom_level < 0: maxout = -self._zoomer.zoomRectIndex() self._provisional_zoom_level = level = max(self._provisional_zoom_level, maxout) if self._provisional_zoom_level < 0: @@ -1894,8 +2022,8 @@ def _plotProvisionalZoom(self, x, y, level, timeout=200): self._provisional_zoom = int(self._provisional_zoom_level) else: self._provisional_zoom = None - QTimer.singleShot(5, self._replot) - self._provisional_zoom_timer.start(timeout) + # signal the rendering of the zoom overlay box + self._zoomer.replotProvisionalZoom.emit() def _plotZoomed(self, rect): dprint(2, "zoomed to", rect) @@ -1906,6 +2034,7 @@ def _plotZoomed(self, rect): self._zoomrect = QRectF(rect) # make copy self._qa_unzoom.setEnabled(rect != self._zoomer.zoomBase()) self._updatePsfMarker(rect, replot=True) + self._zoom_in_process = False # zoom wheel lock def _setGridCircleStepping(self, arcsec=DefaultGridStep_ArcSec): """Changes the visible grid circles. None to disable.""" diff --git a/TigGUI/SkyModelTreeWidget.py b/TigGUI/SkyModelTreeWidget.py index cf7f4b9f..0883ac45 100644 --- a/TigGUI/SkyModelTreeWidget.py +++ b/TigGUI/SkyModelTreeWidget.py @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/__init__.py b/TigGUI/Tools/__init__.py index 2d09b357..b01f4ae2 100644 --- a/TigGUI/Tools/__init__.py +++ b/TigGUI/Tools/__init__.py @@ -1,8 +1,4 @@ -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/add_brick.py b/TigGUI/Tools/add_brick.py index 10855ad3..e10b2787 100644 --- a/TigGUI/Tools/add_brick.py +++ b/TigGUI/Tools/add_brick.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/dependency_check.py b/TigGUI/Tools/dependency_check.py index 59627bee..e0375f17 100644 --- a/TigGUI/Tools/dependency_check.py +++ b/TigGUI/Tools/dependency_check.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2021 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -83,7 +78,7 @@ def close_btn(btn): import sys error_msg = f"Error: Dependencies have not been met {missing}, please check your installation. \n" \ - "See https://github.com/ska-sa/tigger for further information." + "See https://github.com/ratt-ru/tigger for further information." # load GUI error message if possible if 'PyQt5' not in missing: @@ -119,7 +114,7 @@ def close_btn(btn): msg_box.setWindowTitle("Tigger") msg_box.setText(f"Error

Dependencies have not been met:

{missing}

" f"Please check your installation. " - f"

See https://github.com/ska-sa/tigger" + f"

See https://github.com/ratt-ru/tigger" f" for further information.") msg_box.setStandardButtons(QMessageBox.Close) msg_box.buttonClicked.connect(close_btn) diff --git a/TigGUI/Tools/export_karma.py b/TigGUI/Tools/export_karma.py index 992ba999..4863f3a5 100644 --- a/TigGUI/Tools/export_karma.py +++ b/TigGUI/Tools/export_karma.py @@ -1,8 +1,4 @@ -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/make_brick.py b/TigGUI/Tools/make_brick.py index 78b16a4d..e9c2807f 100644 --- a/TigGUI/Tools/make_brick.py +++ b/TigGUI/Tools/make_brick.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/restore_image.py b/TigGUI/Tools/restore_image.py index 3f882fc9..edb9c8d5 100644 --- a/TigGUI/Tools/restore_image.py +++ b/TigGUI/Tools/restore_image.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Tools/source_selector.py b/TigGUI/Tools/source_selector.py index de9d3896..ff7a347c 100644 --- a/TigGUI/Tools/source_selector.py +++ b/TigGUI/Tools/source_selector.py @@ -1,8 +1,4 @@ -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/Widgets.py b/TigGUI/Widgets.py index fad07ac4..09a613b4 100644 --- a/TigGUI/Widgets.py +++ b/TigGUI/Widgets.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -432,6 +427,8 @@ def __init__(self, title="", parent=None, flags=Qt.WindowFlags(), bind_widget=No bind_widget.livezoom_resize_signal.connect(self._resizeDockWidget) if close_slot is not None: self.close_button.clicked.connect(close_slot) + if isinstance(bind_widget, ImageControlDialog): + bind_widget.whide.clicked.connect(close_slot) if toggle_slot is not None: self.toggle_button.clicked.connect(toggle_slot) diff --git a/TigGUI/__init__.py b/TigGUI/__init__.py index 923f8a85..1a908005 100644 --- a/TigGUI/__init__.py +++ b/TigGUI/__init__.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- -# -# % $Id$ -# -# -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/kitties/config.py b/TigGUI/kitties/config.py index 2debaff0..a2534cfc 100644 --- a/TigGUI/kitties/config.py +++ b/TigGUI/kitties/config.py @@ -1,11 +1,6 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 -# -# % $Id$ -# -# -# Copyright (C) 2002-2007 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/kitties/pixmaps.py b/TigGUI/kitties/pixmaps.py index b6f9f4ed..5d8afb13 100644 --- a/TigGUI/kitties/pixmaps.py +++ b/TigGUI/kitties/pixmaps.py @@ -1,10 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# -# % $Id$ -# -# -# Copyright (C) 2002-2007 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/kitties/utils.py b/TigGUI/kitties/utils.py index 2e287a38..c6ab4d63 100644 --- a/TigGUI/kitties/utils.py +++ b/TigGUI/kitties/utils.py @@ -1,6 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Copyright (C) 2002-2007 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands diff --git a/TigGUI/kitties/widgets.py b/TigGUI/kitties/widgets.py index 1764a5e5..3142ff1f 100644 --- a/TigGUI/kitties/widgets.py +++ b/TigGUI/kitties/widgets.py @@ -1,4 +1,24 @@ -# -*- coding: utf-8 -*- +# Copyright (C) 2002-2022 +# The MeqTree Foundation & +# ASTRON (Netherlands Foundation for Research in Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see , +# or write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + from PyQt5 import QtWidgets from PyQt5.Qt import QCursor, Qt, QWidgetAction, QLabel, QFrame, QTreeWidget, QObject, QApplication, \ QTreeWidgetItemIterator, QListWidget diff --git a/TigGUI/tigger b/TigGUI/tigger index 697dbc22..eecf8b57 100755 --- a/TigGUI/tigger +++ b/TigGUI/tigger @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright (C) 2002-2011 +# Copyright (C) 2002-2022 # The MeqTree Foundation & # ASTRON (Netherlands Foundation for Research in Astronomy) # P.O.Box 2, 7990 AA Dwingeloo, The Netherlands @@ -20,11 +20,13 @@ # or write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import faulthandler +import time + from TigGUI.Tools import dependency_check # checks dependencies are available from PyQt5.QtCore import Qt from PyQt5.QtGui import QPalette -from PyQt5.QtWidgets import QStyleFactory, QSizePolicy, QStyle +from PyQt5.QtWidgets import QStyleFactory, QSizePolicy, QStyle, QSplashScreen faulthandler.enable() import sys @@ -127,9 +129,11 @@ def main(): pass dprint(1, "created QApplication") - # splash = QSplashScreen(TigGUI.pixmaps.tigger_splash.pm()) - # splash.showMessage("Welcome to TigGUI!",Qt.AlignHCenter|Qt.AlignBottom) - # splash.show() + splash = QSplashScreen(pixmaps.tigger_splash.pm(), Qt.WindowStaysOnTopHint) + splash.show() + splash.showMessage("Welcome to TigGUI!", Qt.AlignBottom) + time.sleep(2) + app.processEvents() import TigGUI.Images import TigGUI.MainWindow @@ -145,6 +149,10 @@ def main(): # centre on screen centre = QStyle.alignedRect(Qt.LeftToRight, Qt.AlignHCenter, mainwin.size(), QApplication.desktop().availableGeometry()) mainwin.setGeometry(centre) + # set minimum size as the height of image control dialog + if usable_screen.width() >= 895 <= usable_screen.height(): + mainwin.setMinimumHeight(895) + mainwin.setMinimumWidth(895) # set main window size constraints and policy dprint(1, "created main window") @@ -162,10 +170,15 @@ def main(): # load images first for img in images: - # splash.showMessage("Loading image %s"%img,Qt.AlignHCenter|Qt.AlignBottom) + file_loading = os.path.basename(img) + splash.showMessage(f"Loading image {file_loading}", Qt.AlignBottom) + app.processEvents() mainwin.loadImage(img) dprint(1, "loaded image", img) + splash.showMessage(f"Loaded images", Qt.AlignBottom) + app.processEvents() + # load model, if specified for mod in models: dprint(2, "Loading model" + mod) @@ -176,14 +189,20 @@ def main(): print("Error loading model %s" % mod) exit(1) - # splash.showMessage("Loading model %s"%mod,Qt.AlignHCenter|Qt.AlignBottom) + model_loading = os.path.basename(mod) + splash.showMessage(f"Loading model {model_loading}", Qt.AlignBottom) + app.processEvents() dprint(1, "loaded model", mod) + splash.showMessage(f"Loaded model", Qt.AlignBottom) + app.processEvents() + # start updating the plot mainwin.enableUpdates() dprint(1, "started plot updates") # flush app event queue, so windows get resized , etc. + splash.showMessage(f"Starting up", Qt.AlignBottom) app.processEvents() # handle SIGINT @@ -195,7 +214,8 @@ def main(): signal.signal(signal.SIGINT, sigint_handler) # TODO -check this is still valid/used dprint(1, "added signal handler") - # splash.finish(mainwin) + splash.finish(mainwin) + mainwin.raise_() app.exec_() diff --git a/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb b/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb new file mode 100644 index 00000000..d27335ea Binary files /dev/null and b/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb differ diff --git a/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_arm64.deb b/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_arm64.deb new file mode 100644 index 00000000..104df2c8 Binary files /dev/null and b/debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_arm64.deb differ diff --git a/debian_pkgs/ubuntu_22_04_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb b/debian_pkgs/ubuntu_22_04_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb new file mode 100644 index 00000000..d27335ea Binary files /dev/null and b/debian_pkgs/ubuntu_22_04_deb_pkg/python-pyqt5.qwt-doc_2.00.00-1build1_all.deb differ diff --git a/debian_pkgs/ubuntu_22_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb b/debian_pkgs/ubuntu_22_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb new file mode 100644 index 00000000..5420f055 Binary files /dev/null and b/debian_pkgs/ubuntu_22_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb differ diff --git a/install_tigger_ubuntu.sh b/install_tigger_ubuntu.sh index f086be24..0ba1a7c7 100755 --- a/install_tigger_ubuntu.sh +++ b/install_tigger_ubuntu.sh @@ -1,4 +1,6 @@ #!/bin/bash +# add .local to $PATH +export PATH=$PATH:~/.local/bin log_file=tigger_installer.log error_file=tigger_installer.err @@ -15,10 +17,10 @@ echo() { command echo "$@" >&3 } -echo "==== Tigger v1.6.0 - Ubuntu install script ====" +echo "==== Tigger v1.6.1 - Ubuntu install script ====" echo "==== Log file: $log_file ====" echo "==== Error log: $error_file ====" -printf "==== Tigger v1.6.0 - Ubuntu install script ====\n" +printf "==== Tigger v1.6.1 - Ubuntu install script ====\n" # sudo runner by default sudo_runner="sudo" @@ -93,6 +95,18 @@ then printf "==== Error: Ubuntu Linux not detected, stopping installation ====\n" exception fi + + if [[ -n "$(command -v arch)" ]] + then + # lookup for aarch64 + arch=$(arch) + if [[ $arch == "aarch64" ]] && [[ $distro_version != "2204" ]] + then + echo "==== Error: ARM64 installation requires Ubuntu 22.04. Installer has detected Linux distribution as $distro_name $distro_version ====" + printf "==== Error: ARM64 installation requires Ubuntu 22.04. Installer has detected Linux distribution as $distro_name $distro_version ====\n" + exception + fi + fi else echo "==== Error: Unable to detect Linux distribution, stopping installation ====" printf "==== Error: Unable to detect Linux distribution, stopping installation ====\n" @@ -107,18 +121,27 @@ if command -v pip3 > /dev/null then if [[ $VIRTUAL_ENV == "" ]] then - echo "==== Installer found pip3... ====" - printf "==== Installer found pip3... ====\n" + echo "==== Installer found pip3 (no VENV)... ====" + printf "==== Installer found pip3 (no VENV)... ====\n" else - echo "==== Installer did not find pip3... ====" - printf "==== Installer did not find pip3... ====\n" - $sudo_runner $apt_runner python3-setuptools python3-pip 2>>$error_file || exception + if command -v $VIRTUAL_ENV/bin/pip3 > /dev/null + then + echo "==== Installer found pip3 (VENV)... ====" + printf "==== Installer found pip3 (VENV)... ====\n" + $VIRTUAL_ENV/bin/pip3 install -U pip setuptools wheel + else + echo "==== Installer did not find pip3 (VENV)... ====" + printf "==== Installer did not find pip3 (VENV)... ====\n" + $sudo_runner $apt_runner python3-setuptools python3-pip 2>>$error_file || exception + pip3 install -U pip setuptools wheel --user + fi fi else - echo "==== Installer did not find pip3... ====" - printf "==== Installer did not find pip3... ====\n" + echo "==== Installer did not find pip3 (no VENV)... ====" + printf "==== Installer did not find pip3 (no VENV)... ====\n" install_type="fullstack" $sudo_runner $apt_runner python3-setuptools python3-pip 2>>$error_file || exception + pip3 install -U pip setuptools wheel --user fi # check for astro-tigger-lsm @@ -129,10 +152,10 @@ then printf "==== Installer found astro-tigger-lsm dependency... ====\n" tigger_lsm_version=`pip3 list|grep astro-tigger-lsm|awk '{print $2}'|sed -e 's/\.//g'` - if [[ "$tigger_lsm_version" -lt "170" ]] + if [[ "$tigger_lsm_version" -lt "171" ]] then - echo "==== Installer fullstack mode - astro-tigger-lsm version is less than 1.7.0... ====" - printf "==== Installer fullstack mode - astro-tigger-lsm version is less than 1.7.0... ====\n" + echo "==== Installer fullstack mode - astro-tigger-lsm version is less than 1.7.1... ====" + printf "==== Installer fullstack mode - astro-tigger-lsm version is less than 1.7.1... ====\n" pip3 uninstall -y astro_tigger_lsm 2>>$error_file || exception install_type="fullstack" fi @@ -149,27 +172,41 @@ then then echo "==== Installing Tigger-LSM dependency from source... ====" printf "==== Installing Tigger-LSM dependency from source... ====\n" - $sudo_runner $apt_runner git 2>>$error_file || exception - cd /tmp || exception - rm -rf tigger-lsm - git clone https://github.com/ska-sa/tigger-lsm.git 1>>$log_file 2>>$error_file || exception - cd tigger-lsm || exception - - if [[ $distro_version == "1804" ]] + if [[ $distro_version == "2204" ]] + then + $sudo_runner $apt_runner libboost-python-dev python3-casacore casacore* libcfitsio-dev wcslib-dev 2>>$error_file || exception + elif [[ $distro_version == "1804" ]] then $sudo_runner $apt_runner libboost-python-dev casacore* 2>>$error_file || exception pip3 install -q astropy==4.1 || exception pip3 install -q scipy==1.5.2 || exception fi - - python3 setup.py install --user 1>>$log_file 2>>$error_file || exception + $sudo_runner $apt_runner git 2>>$error_file || exception + cd /tmp || exception + rm -rf tigger-lsm + git clone https://github.com/ratt-ru/tigger-lsm.git 1>>$log_file 2>>$error_file || exception + cd tigger-lsm || exception + pip3 install . 1>>$log_file 2>>$error_file || exception cd /tmp || exception cd "${tigger_pwd}" || exception elif [[ $build_type == "package" ]] then - echo "==== Installing Tigger-LSM dependency from pip3... ====" - printf "==== Installing Tigger-LSM dependency from pip3... ====\n" - pip3 install -q astro_tigger_lsm==1.7.0 || exception + echo "==== Installing Tigger-LSM dependencies... ====" + printf "==== Installing Tigger-LSM dependencies... ====\n" + if [[ $distro_version == "2204" ]] + then + if [[ $VIRTUAL_ENV == "" ]] + then + echo "==== Installing Tigger-LSM dependencies libboost-python and python3-casacore... ====" + printf "==== Installing Tigger-LSM dependencies libboost-python and python3-casacore... ====\n" + $sudo_runner $apt_runner libboost-python-dev python3-casacore 2>>$error_file || exception + else + echo "==== Installing Tigger-LSM dependencies libboost-python, casacore, python3-casacore, libcfitsio and wcslib... ====" + printf "==== Installing Tigger-LSM dependencies libboost-python casacore, python3-casacore, libcfitsio and wcslib... ====\n" + $sudo_runner $apt_runner libboost-python-dev python3-casacore casacore* libcfitsio-dev wcslib-dev 2>>$error_file || exception + fi + fi + pip3 install -q astro_tigger_lsm==1.7.1 || exception fi fi @@ -179,17 +216,31 @@ printf "==== Installing package dependencies... ====\n" # install Tigger deps $sudo_runner $apt_runner python3-pyqt5.qtsvg python3-pyqt5.qtopengl libqwt-qt5-6 2>>$error_file || exception +# VENV and PyQt-Qwt source based installation not supported (Please compile manually) +if [[ $VIRTUAL_ENV != "" && $build_type == "source" ]] +then + echo "==== PyQt-Qwt source based installation with VENV is not supported. Please install manually, attempting package based installation instead ====" + printf "==== PyQt-Qwt source based installation with VENV is not supported. Please install manually, attempting package based installation instead ====\n" + $sudo_runner $apt_runner sip-tools sip-dev 2>>$error_file || exception + build_type="package" +fi # compile PyQt-Qwt if [[ $build_type == "source" ]] then - # install PyQt-Qwt deps - $sudo_runner $apt_runner pyqt5-dev pyqt5-dev-tools python3-pyqt5 libqwt-qt5-dev libqwt-headers libqt5opengl5-dev libqt5svg5-dev g++ dpkg-dev git 2>>$error_file || exception - if [[ $distro_version == "2104" ]] + # install PyQt-Qwt deps + $sudo_runner $apt_runner pyqt5-dev pyqt5-dev-tools python3-pyqt5 libqwt-qt5-dev libqwt-headers libqt5opengl5-dev libqt5svg5-dev g++ dpkg-dev git 2>>$error_file || exception + if [[ $distro_version == "2104" ]] || [[ $distro_version == "2204" ]] then - echo "==== Compiling PyQt-Qwt for $distro_name $distro_version... ====" + echo "==== Compiling PyQt-Qwt for $distro_name $distro_version... ====" printf "==== Compiling PyQt-Qwt for $distro_name $distro_version... ====\n" - $sudo_runner $apt_runner sip5-tools 2>>$error_file || exception - cd /tmp || exception + if [[ $distro_version == "2104" ]] + then + $sudo_runner $apt_runner sip5-tools 2>>$error_file || exception + elif [[ $distro_version == "2204" ]] + then + $sudo_runner $apt_runner sip-tools sip-dev 2>>$error_file || exception + fi + cd /tmp || exception rm -rf PyQt-Qwt git clone https://github.com/razman786/PyQt-Qwt.git || exception cd PyQt-Qwt || exception @@ -245,7 +296,17 @@ fi # install PyQt-Qwt package if [[ $build_type == "package" ]] then - if [[ $distro_version == "2104" ]] + if [[ $distro_version == "2204" ]] + then + echo "==== Installing PyQwt for $distro_name $distro_version... ====" + printf "==== Installing PyQwt for $distro_name $distro_version... ====\n" + if [[ $arch == "aarch64" ]] + then + $sudo_runner dpkg -i debian_pkgs/ubuntu_22_04_arm64_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_arm64.deb || exception + else + $sudo_runner dpkg -i debian_pkgs/ubuntu_22_04_deb_pkg/python3-pyqt5.qwt_2.00.00-1build1_amd64.deb || exception + fi + elif [[ $distro_version == "2104" ]] then echo "==== Installing PyQwt for $distro_name $distro_version... ====" printf "==== Installing PyQwt for $distro_name $distro_version... ====\n" @@ -301,8 +362,17 @@ fi # install Tigger if [[ $VIRTUAL_ENV == "" ]] then - python3 setup.py install --user 1>>$log_file 2>>$error_file && echo "==== Tigger installation complete! \o/ ====" || exception + echo "==== Installing Tigger... ====" + printf "==== Installing Tigger... ====\n" + pip3 install . --user 1>>$log_file 2>>$error_file && echo "==== Tigger installation complete! \o/ ====" && printf "==== Tigger installation complete! \o/ ====\n" || exception else + echo "==== Installing Tigger (VENV)... ====" + printf "==== Installing Tigger(VENV)... ====\n" + if [[ $distro_version == "2204" ]] + then + $sudo_runner $apt_runner python3-sip-dev 2>>$error_file || exception + fi + pip3 install -q wheel || exception pip3 install -q vext.pyqt5 || exception - pip3 install . 1>>$log_file 2>>$error_file && echo "==== Tigger installation complete! \o/ ====" || exception + pip3 install . 1>>$log_file 2>>$error_file && echo "==== Tigger installation complete! \o/ ====" && printf "==== Tigger installation complete! \o/ ====\n"|| exception fi diff --git a/setup.py b/setup.py index a0920cf6..2ecd888d 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function from setuptools import setup, find_packages from pathlib import Path -__version__ = "1.6.0" +__version__ = "1.6.1" # PyQt (5.15.x) has not been added here are it needs to be installed via apt-get instead to support Qwt. # requirements below do not have versions for upstream packaging processes, but tested and compatible versions are given requirements = [ - 'numpy', # tested with version >=1.19.4 - 'scipy', # tested with versions 1.5.2 for Python 3.6 and >=1.6.2 - 'astlib', # tested with version 0.11.6 - 'astropy', # tested with 4.1 and 4.2 - 'astro_tigger_lsm==1.7.0', # PyQt5 version of astro-tigger-lsm - 'configparser', # tested with version 5.0.1 + 'numpy', # tested with version >=1.19.4 (<= 1.22.3) + 'scipy', # tested with versions 1.5.2 for Python 3.6 and >=1.6.2 (<= 1.8.0) + 'astlib', # tested with version 0.11.6 and 0.11.7 + 'astropy', # tested with 4.1, 4.2 and 5.0.4 + 'astro_tigger_lsm==1.7.1', # PyQt5 version of astro-tigger-lsm + 'configparser', # tested with version 5.0.1 and 5.2.0 ] scripts = [ @@ -37,7 +37,7 @@ description="yet another FITS image viewer", author="Oleg Smirnov", author_email="osmirnov@gmail.com", - url="https://github.com/ska-sa/tigger", + url="https://github.com/ratt-ru/tigger", python_requires='>=3.6', install_requires=requirements, data_files=[