<commit_before># The MIT License (MIT)
#
# Copyright (c) 2014 WUSTL ZPLAB
#
# 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.
#
# Authors: <NAME> <<EMAIL>>

from PyQt5 import Qt
import numpy
import pyagg
import sys

from . import canvas
from . import image_widget
from . import histogram_widget
from .image import Image

class RisWidget(Qt.QMainWindow):
    # The image_changed signal is emitted when a new value is successfully assigned to the
    # RisWidget.image property
    image_changed = Qt.pyqtSignal()

    def __init__(self, window_title='RisWidget', parent=None, window_flags=Qt.Qt.WindowFlags(0)):
        super().__init__(parent, window_flags)
        if sys.platform == 'darwin': # workaround for https://bugreports.qt.io/browse/QTBUG-44230
            hs = Qt.QSlider(Qt.Qt.Horizontal)
            hs.show()
            hs.hide()
            hs.destroy()
            del hs
        if window_title is not None:
            self.setWindowTitle(window_title)
        self.setAcceptDrops(True)
        self._init_views()
#       self._init_actions()
#       self._init_toolbars()
        self._image = None

    def _init_actions(self):
        self._image_view_zoom_to_fit_action = Qt.QAction(self)
        self._image_view_zoom_to_fit_action.setCheckable(True)
        self._image_view_zoom_to_fit_action.setText('Zoom to Fit')

    @staticmethod
    def _format_zoom(zoom):
        if int(zoom) == zoom:
            return '{}'.format(int(zoom))
        else:
            txt = '{:.2f}'.format(zoom)
            if txt[-1] == 0:
                return txt[:-1]

    def _init_toolbars(self):
        self._image_view_toolbar = self.addToolBar('View')
        self._image_view_zoom_combo = Qt.QComboBox(self)
        self._image_view_toolbar.addWidget(self._image_view_zoom_combo)
        self._image_view_zoom_combo.setEditable(True)
        self._image_view_zoom_combo.setInsertPolicy(Qt.QComboBox.NoInsert)
        self._image_view_zoom_combo.setDuplicatesEnabled(True)
        self._image_view_zoom_combo.setSizeAdjustPolicy(Qt.QComboBox.AdjustToContents)
        for zoom in image_widget.ImageWidget._ZOOM_PRESETS:
            self._image_view_zoom_combo.addItem(self._format_zoom(zoom * 100) + '%')
        self._image_view_zoom_combo.setCurrentIndex(image_widget.ImageWidget._ZOOM_DEFAULT_PRESET_IDX)
        self._image_view_zoom_combo.activated[int].connect(self._image_view_zoom_combo_changed)
        self._image_view_zoom_combo.lineEdit().returnPressed.connect(self._image_view_zoom_combo_custom_value_entered)
        self.image_widget.zoom_changed.connect(self._image_view_zoom_changed)
        self._image_view_toolbar.addAction(self._image_view_zoom_to_fit_action)
        self._image_view_zoom_to_fit_action.triggered[bool].connect(self._image_view_zoom_to_fit_action_toggled)
        self.image_widget.zoom_to_fit_changed.connect(self._image_view_zoom_to_fit_changed)

    def _init_views(self):
        self.image_scene = image_widget.ImageScene(self)
        self.image_widget = image_widget.ImageWidget(self.image_scene, self)
        self.setCentralWidget(self.image_widget)
        self.histogram_scene = histogram_widget.HistogramScene(self)
        self._histogram_dock_widget = Qt.QDockWidget('Histogram', self)
        self.histogram_widget, self._histogram_container_widget = histogram_widget.HistogramWidget.make_histogram_and_container_widgets(self.histogram_scene, self._histogram_dock_widget)
        self.image_widget.histogram_widget = self.histogram_widget
        self._histogram_dock_widget.setWidget(self._histogram_container_widget)
        self._histogram_dock_widget.setAllowedAreas(Qt.Qt.BottomDockWidgetArea | Qt.Qt.TopDockWidgetArea)
        self._histogram_dock_widget.setFeatures(Qt.QDockWidget.DockWidgetFloatable | Qt.QDockWidget.DockWidgetMovable | Qt.QDockWidget.DockWidgetVerticalTitleBar)
        self.addDockWidget(Qt.Qt.BottomDockWidgetArea, self._histogram_dock_widget)
        self.histogram_widget.gamma_or_min_max_changed.connect(self.image_widget.update)
        self.histogram_scene.request_mouseover_info_status_text_change.connect(self.histogram_widget._on_request_mouseover_info_status_text_change)
        self.image_scene.request_mouseover_info_status_text_change.connect(self.histogram_widget._on_request_mouseover_info_status_text_change)

    @property
    def image_data(self):
        """image_data property:
        The input assigned to this property may be None, in which case the current image and histogram views are cleared,
        and otherwise must be convertable to a 2D or 3D numpy array of shape (w, h) or (w, h, c), respectively*.  2D input
        is interpreted as grayscale.  3D input, depending on the value of c, is iterpreted as grayscale & alpha (c of 2),
        red & blue & green (c of 3), or red & blue & green & alpha (c of 4).

        The following dtypes are directly supported (data of any other type is converted to 32-bit floating point,
        and an exception is thrown if conversion fails):
        numpy.uint8
        numpy.uint16
        numpy.float32

        Supplying a numpy array of one of the above types as input may avoid an intermediate copy step by allowing RisWidget
        to keep a reference to the supplied array, allowing its data to be accessed directly.


        * IE, the iterable assigned to the image property is interpreted as an iterable of columns (image left to right), each
        containing an iterable of rows (image top to bottom), each of which is either a grayscale intensity value or an
        iterable of color channel intensity values (gray & alpha, or red & green & blue, or red & green & blue & alpha)."""
        return None if self._image is None else self._image.data

    @image_data.setter
    def image_data(self, image_data):
        self.image = Image(image_data)

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, image):
        if image is not None and not issubclass(type(image), Image):
            raise ValueError('The value assigned to the image property must either be derived from ris_widget.image.Image or must be None.  Did you mean to assign to the image_data property?')
        self.image_widget._on_image_changed(image)
        self.histogram_widget._on_image_changed(image)
        self._image = image
        self.image_changed.emit()
#
#   def _image_view_zoom_changed(self, zoom_preset_idx, custom_zoom):
#       assert zoom_preset_idx == -1 and custom_zoom != 0 or zoom_preset_idx != -1 and custom_zoom == 0, 'zoom_preset_idx XOR custom_zoom must be set.'
#       if zoom_preset_idx == -1:
#           self._image_view_zoom_combo.lineEdit().setText(self._format_zoom(custom_zoom * 100) + '%')
#       else:
#           self._image_view_zoom_combo.setCurrentIndex(zoom_preset_idx)
#
#   def _image_view_zoom_to_fit_changed(self, zoom_to_fit):
#       """Handle self.image_widget.zoom_to_fit property change."""
#       if zoom_to_fit != self._image_view_zoom_to_fit_action.isChecked():
#           self._image_view_zoom_to_fit_action.setChecked(zoom_to_fit)
#           self._image_view_zoom_combo.setEnabled(not zoom_to_fit)
#
#   def _image_view_zoom_combo_changed(self, idx):
#       self.image_widget.zoom_preset_idx = idx
#
#   def _image_view_zoom_to_fit_action_toggled(self, zoom_to_fit):
#       """Change self._image_widget.zoom_to_fit property value in response to GUI manipulation."""
#       self._image_view_zoom_combo.setEnabled(not zoom_to_fit)
#       self.image_widget.zoom_to_fit = zoom_to_fit
#
#   def _image_view_zoom_combo_custom_value_entered(self):
#       txt = self._image_view_zoom_combo.lineEdit().text()
#       scale_txt = txt[:txt.find('%')]
#       try:
#           self.image_widget.custom_zoom = float(scale_txt) * 0.01
#       except ValueError:
#           Qt.QMessageBox.information(self, self.windowTitle(), 'Please enter a number between {} and {}.'.format(*map(RisWidget._format_zoom, image_widget.ImageWidget._ZOOM_MIN_MAX)))
#           self._image_view_zoom_combo.setFocus()
#           self._image_view_zoom_combo.lineEdit().selectAll()

if __name__ == '__main__':
    import sys
    app = Qt.QApplication(sys.argv)
    rw = RisWidget()
    rw.show()
    app.exec_()<commit_msg>Investigate sharing resources between OpenGL contexts belonging to different QOpenGLWidgets not necessarily instantiated at the same time.<commit_after># The MIT License (MIT)
#
# Copyright (c) 2014 WUSTL ZPLAB
#
# 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.
#
# Authors: <NAME> <<EMAIL>>

from PyQt5 import Qt
import numpy
import pyagg
import sys

from . import canvas
from . import image_widget
from . import histogram_widget
from .image import Image

class RisWidget(Qt.QMainWindow):
    # The image_changed signal is emitted when a new value is successfully assigned to the
    # RisWidget.image property
    image_changed = Qt.pyqtSignal()

    def __init__(self, window_title='RisWidget', parent=None, window_flags=Qt.Qt.WindowFlags(0)):
        super().__init__(parent, window_flags)
        if sys.platform == 'darwin': # workaround for https://bugreports.qt.io/browse/QTBUG-44230
            hs = Qt.QSlider(Qt.Qt.Horizontal)
            hs.show()
            hs.hide()
            hs.destroy()
            del hs
        if window_title is not None:
            self.setWindowTitle(window_title)
        self.setAcceptDrops(True)
        self.gl_share = GLShare(self)
        self._init_views()
#       self._init_actions()
#       self._init_toolbars()
        self._image = None

    def _init_actions(self):
        self._image_view_zoom_to_fit_action = Qt.QAction(self)
        self._image_view_zoom_to_fit_action.setCheckable(True)
        self._image_view_zoom_to_fit_action.setText('Zoom to Fit')

    @staticmethod
    def _format_zoom(zoom):
        if int(zoom) == zoom:
            return '{}'.format(int(zoom))
        else:
            txt = '{:.2f}'.format(zoom)
            if txt[-1] == 0:
                return txt[:-1]

    def _init_toolbars(self):
        self._image_view_toolbar = self.addToolBar('View')
        self._image_view_zoom_combo = Qt.QComboBox(self)
        self._image_view_toolbar.addWidget(self._image_view_zoom_combo)
        self._image_view_zoom_combo.setEditable(True)
        self._image_view_zoom_combo.setInsertPolicy(Qt.QComboBox.NoInsert)
        self._image_view_zoom_combo.setDuplicatesEnabled(True)
        self._image_view_zoom_combo.setSizeAdjustPolicy(Qt.QComboBox.AdjustToContents)
        for zoom in image_widget.ImageWidget._ZOOM_PRESETS:
            self._image_view_zoom_combo.addItem(self._format_zoom(zoom * 100) + '%')
        self._image_view_zoom_combo.setCurrentIndex(image_widget.ImageWidget._ZOOM_DEFAULT_PRESET_IDX)
        self._image_view_zoom_combo.activated[int].connect(self._image_view_zoom_combo_changed)
        self._image_view_zoom_combo.lineEdit().returnPressed.connect(self._image_view_zoom_combo_custom_value_entered)
        self.image_widget.zoom_changed.connect(self._image_view_zoom_changed)
        self._image_view_toolbar.addAction(self._image_view_zoom_to_fit_action)
        self._image_view_zoom_to_fit_action.triggered[bool].connect(self._image_view_zoom_to_fit_action_toggled)
        self.image_widget.zoom_to_fit_changed.connect(self._image_view_zoom_to_fit_changed)

    def _init_views(self):
        self.image_scene = image_widget.ImageScene(self)
        self.image_widget = image_widget.ImageWidget(self.image_scene, self)
        self.setCentralWidget(self.image_widget)
        self.histogram_scene = histogram_widget.HistogramScene(self)
        self._histogram_dock_widget = Qt.QDockWidget('Histogram', self)
        self.histogram_widget, self._histogram_container_widget = histogram_widget.HistogramWidget.make_histogram_and_container_widgets(self.histogram_scene, self._histogram_dock_widget)
        self.image_widget.histogram_widget = self.histogram_widget
        self._histogram_dock_widget.setWidget(self._histogram_container_widget)
        self._histogram_dock_widget.setAllowedAreas(Qt.Qt.BottomDockWidgetArea | Qt.Qt.TopDockWidgetArea)
        self._histogram_dock_widget.setFeatures(Qt.QDockWidget.DockWidgetFloatable | Qt.QDockWidget.DockWidgetMovable | Qt.QDockWidget.DockWidgetVerticalTitleBar)
        self.addDockWidget(Qt.Qt.BottomDockWidgetArea, self._histogram_dock_widget)
        self.histogram_widget.gamma_or_min_max_changed.connect(self.image_widget.update)
        self.histogram_scene.request_mouseover_info_status_text_change.connect(self.histogram_widget._on_request_mouseover_info_status_text_change)
        self.image_scene.request_mouseover_info_status_text_change.connect(self.histogram_widget._on_request_mouseover_info_status_text_change)

    @property
    def image_data(self):
        """image_data property:
        The input assigned to this property may be None, in which case the current image and histogram views are cleared,
        and otherwise must be convertable to a 2D or 3D numpy array of shape (w, h) or (w, h, c), respectively*.  2D input
        is interpreted as grayscale.  3D input, depending on the value of c, is iterpreted as grayscale & alpha (c of 2),
        red & blue & green (c of 3), or red & blue & green & alpha (c of 4).

        The following dtypes are directly supported (data of any other type is converted to 32-bit floating point,
        and an exception is thrown if conversion fails):
        numpy.uint8
        numpy.uint16
        numpy.float32

        Supplying a numpy array of one of the above types as input may avoid an intermediate copy step by allowing RisWidget
        to keep a reference to the supplied array, allowing its data to be accessed directly.


        * IE, the iterable assigned to the image property is interpreted as an iterable of columns (image left to right), each
        containing an iterable of rows (image top to bottom), each of which is either a grayscale intensity value or an
        iterable of color channel intensity values (gray & alpha, or red & green & blue, or red & green & blue & alpha)."""
        return None if self._image is None else self._image.data

    @image_data.setter
    def image_data(self, image_data):
        self.image = Image(image_data)

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, image):
        if image is not None and not issubclass(type(image), Image):
            raise ValueError('The value assigned to the image property must either be derived from ris_widget.image.Image or must be None.  Did you mean to assign to the image_data property?')
        self.image_widget._on_image_changed(image)
        self.histogram_widget._on_image_changed(image)
        self._image = image
        self.image_changed.emit()
#
#   def _image_view_zoom_changed(self, zoom_preset_idx, custom_zoom):
#       assert zoom_preset_idx == -1 and custom_zoom != 0 or zoom_preset_idx != -1 and custom_zoom == 0, 'zoom_preset_idx XOR custom_zoom must be set.'
#       if zoom_preset_idx == -1:
#           self._image_view_zoom_combo.lineEdit().setText(self._format_zoom(custom_zoom * 100) + '%')
#       else:
#           self._image_view_zoom_combo.setCurrentIndex(zoom_preset_idx)
#
#   def _image_view_zoom_to_fit_changed(self, zoom_to_fit):
#       """Handle self.image_widget.zoom_to_fit property change."""
#       if zoom_to_fit != self._image_view_zoom_to_fit_action.isChecked():
#           self._image_view_zoom_to_fit_action.setChecked(zoom_to_fit)
#           self._image_view_zoom_combo.setEnabled(not zoom_to_fit)
#
#   def _image_view_zoom_combo_changed(self, idx):
#       self.image_widget.zoom_preset_idx = idx
#
#   def _image_view_zoom_to_fit_action_toggled(self, zoom_to_fit):
#       """Change self._image_widget.zoom_to_fit property value in response to GUI manipulation."""
#       self._image_view_zoom_combo.setEnabled(not zoom_to_fit)
#       self.image_widget.zoom_to_fit = zoom_to_fit
#
#   def _image_view_zoom_combo_custom_value_entered(self):
#       txt = self._image_view_zoom_combo.lineEdit().text()
#       scale_txt = txt[:txt.find('%')]
#       try:
#           self.image_widget.custom_zoom = float(scale_txt) * 0.01
#       except ValueError:
#           Qt.QMessageBox.information(self, self.windowTitle(), 'Please enter a number between {} and {}.'.format(*map(RisWidget._format_zoom, image_widget.ImageWidget._ZOOM_MIN_MAX)))
#           self._image_view_zoom_combo.setFocus()
#           self._image_view_zoom_combo.lineEdit().selectAll()

class GLShare(Qt.QObject):
    # TODO: this isn't going to work, and it turns out that context sharing is not to be generally relied upon (if context A
    # is created and then instantiates a bunch of resources, and context B is subsequently created and set to share with A,
    # whether B will see any of the resources previously created in A depends on the quality of your video driver)
    """self.context is used for resource management purposes only (it is not used for draw
    ing).  This allows QGraphicsItem-derived objects such as those representing the image and histogram, which
    have various GL resources, be instantiated without concern for whether there is currently a view into
    the scene owning the object and destroyed without concern for whether any views still exist or have
    ever existed.  In model/view/controller terms, we prevent improper coupling between view from model by
    storing model resources that otherwise must be associated with a view in a shared pool that is not associated
    with any view."""
    def __init__(self, parent):
        super().__init__(parent)
        qsurface_format = Qt.QSurfaceFormat()
        qsurface_format.setRenderableType(Qt.QSurfaceFormat.OpenGL)
        qsurface_format.setVersion(2, 1)
        qsurface_format.setProfile(Qt.QSurfaceFormat.CompatibilityProfile)
        qsurface_format.setSwapBehavior(Qt.QSurfaceFormat.DoubleBuffer)
        qsurface_format.setStereo(False)
        qsurface_format.setSwapInterval(1)
        # Specifically enabling alpha channel is not sufficient for enabling QPainter composition modes that
        # use destination alpha (ie, nothing drawn in CompositionMode_DestinationOver with a QPainter will be visible in
        # a QOpenGLWidget).
#       qsurface_format.setRedBufferSize(8)
#       qsurface_format.setGreenBufferSize(8)
#       qsurface_format.setBlueBufferSize(8)
#       qsurface_format.setAlphaBufferSize(8)
        self.context = Qt.QOpenGLContext(self)
        self.context.setFormat(qsurface_format)
        if not self.context.create():
            raise RuntimeError('Failed to create shared OpenGL context.')

if __name__ == '__main__':
    import sys
    app = Qt.QApplication(sys.argv)
    rw = RisWidget()
    rw.show()
    app.exec_()
{'fraction_non_alphanumeric': 0.07797130655918622, 'fraction_numerical': 0.00446235785231035, 'mean_word_length': 3.6793893129770994, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1916847', 'n_tokens_mistral': 6309, 'n_tokens_neox': 6123, 'n_words': 1626}