summaryrefslogtreecommitdiff
path: root/examples/imageview.py
blob: 40b5dffd255ca882694cb539b942fcac67d8ae18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env python
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""
Example to show the use of :class:`~silx.gui.plot.ImageView.ImageView` widget.

It can be used to open an EDF or TIFF file from the shell command line.

To view an image file with the current installed silx library:
``python examples/imageview.py <file to open>``
To get help:
``python examples/imageview.py -h``
"""
from __future__ import division

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "08/11/2018"

import logging
import numpy
import time
import threading

from silx.gui.utils import concurrent
from silx.gui.plot.ImageView import ImageViewMainWindow
from silx.gui import qt

logging.basicConfig()
logger = logging.getLogger(__name__)


Nx = 150
Ny = 50


class UpdateThread(threading.Thread):
    """Thread updating the image of a :class:`~sil.gui.plot.Plot2D`

    :param plot2d: The Plot2D to update."""

    def __init__(self, imageview):
        self.imageview = imageview
        self.running = False
        self.future_result = None
        super(UpdateThread, self).__init__()

    def start(self):
        """Start the update thread"""
        self.running = True
        super(UpdateThread, self).start()

    def run(self, pos={'x0': 0, 'y0': 0}):
        """Method implementing thread loop that updates the plot

        It produces an image every 10 ms or so, and
        either updates the plot or skip the image
        """
        while self.running:
            time.sleep(0.01)

            # Create image
            # width of peak
            sigma_x = 0.15
            sigma_y = 0.25
            # x and y positions
            x = numpy.linspace(-1.5, 1.5, Nx)
            y = numpy.linspace(-1.0, 1.0, Ny)
            xv, yv = numpy.meshgrid(x, y)
            signal = numpy.exp(- ((xv - pos['x0']) ** 2 / sigma_x ** 2
                                  + (yv - pos['y0']) ** 2 / sigma_y ** 2))
            # add noise
            signal += 0.3 * numpy.random.random(size=signal.shape)
            # random walk of center of peak ('drift')
            pos['x0'] += 0.05 * (numpy.random.random() - 0.5)
            pos['y0'] += 0.05 * (numpy.random.random() - 0.5)

            # If previous frame was not added to the plot yet, skip this one
            if self.future_result is None or self.future_result.done():
                # plot the data asynchronously, and
                # keep a reference to the `future` object
                self.future_result = concurrent.submitToQtMainThread(
                    self.imageview.setImage, signal, resetzoom=False)

    def stop(self):
        """Stop the update thread"""
        self.running = False
        self.join(2)


def main(argv=None):
    """Display an image from a file in an :class:`ImageView` widget.

    :param argv: list of command line arguments or None (the default)
                 to use sys.argv.
    :type argv: list of str
    :return: Exit status code
    :rtype: int
    :raises IOError: if no image can be loaded from the file
    """
    import argparse
    import os.path

    from silx.third_party.EdfFile import EdfFile

    # Command-line arguments
    parser = argparse.ArgumentParser(
        description='Browse the images of an EDF file.')
    parser.add_argument(
        '-o', '--origin', nargs=2,
        type=float, default=(0., 0.),
        help="""Coordinates of the origin of the image: (x, y).
        Default: 0., 0.""")
    parser.add_argument(
        '-s', '--scale', nargs=2,
        type=float, default=(1., 1.),
        help="""Scale factors applied to the image: (sx, sy).
        Default: 1., 1.""")
    parser.add_argument(
        '-l', '--log', action="store_true",
        help="Use logarithm normalization for colormap, default: Linear.")
    parser.add_argument(
        'filename', nargs='?',
        help='EDF filename of the image to open')
    parser.add_argument(
        '--live', action='store_true',
        help='Live update of a generated image')
    args = parser.parse_args(args=argv)

    # Open the input file
    edfFile = None
    if args.live:
        data = None
    elif not args.filename:
        logger.warning('No image file provided, displaying dummy data')
        size = 512
        xx, yy = numpy.ogrid[-size:size, -size:size]
        data = numpy.cos(xx / (size//5)) + numpy.cos(yy / (size//5))
        data = numpy.random.poisson(numpy.abs(data))
        nbFrames = 1

    else:
        if not os.path.isfile(args.filename):
            raise IOError('No input file: %s' % args.filename)

        else:
            edfFile = EdfFile(args.filename)
            data = edfFile.GetData(0)

            nbFrames = edfFile.GetNumImages()
            if nbFrames == 0:
                raise IOError(
                    'Cannot read image(s) from file: %s' % args.filename)

    global app  # QApplication must be global to avoid seg fault on quit
    app = qt.QApplication([])
    sys.excepthook = qt.exceptionHandler

    mainWindow = ImageViewMainWindow()
    mainWindow.setAttribute(qt.Qt.WA_DeleteOnClose)

    if args.log:  # Use log normalization by default
        colormap = mainWindow.getDefaultColormap()
        colormap.setNormalization(colormap.LOGARITHM)

    if data is not None:
        mainWindow.setImage(data,
                            origin=args.origin,
                            scale=args.scale)

    if edfFile is not None and nbFrames > 1:
        # Add a toolbar for multi-frame EDF support
        multiFrameToolbar = qt.QToolBar('Multi-frame')
        multiFrameToolbar.addWidget(qt.QLabel(
            'Frame [0-%d]:' % (nbFrames - 1)))

        spinBox = qt.QSpinBox()
        spinBox.setRange(0, nbFrames - 1)

        def updateImage(index):
            mainWindow.setImage(edfFile.GetData(index),
                                origin=args.origin,
                                scale=args.scale,
                                reset=False)
        spinBox.valueChanged[int].connect(updateImage)
        multiFrameToolbar.addWidget(spinBox)

        mainWindow.addToolBar(multiFrameToolbar)

    mainWindow.show()
    mainWindow.setFocus(qt.Qt.OtherFocusReason)

    if args.live:
        # Start updating the plot
        updateThread = UpdateThread(mainWindow)
        updateThread.start()

    return app.exec()


if __name__ == "__main__":
    import sys
    sys.exit(main(argv=sys.argv[1:]))