This is started as a simple project, because I don't like software I'm using. Not that they are not good, but I wanted some custom stuff, so this little project started. This part of project is for live viewing while archiving is done with opencv (not shown here, I managed to get this work).I am very new to python, but I managed to build this GUI. My question for you is: Is there anything I can improve? I am satisfied with CPU and memory usage (15 cameras in view are using about 100 MiB of memory). First approach is made using opencv, but with this I lower the resource used. There is known bug on line 174, when one image is shown, picture not fit the view until 'right-click' (fitInView action) is called by user. So solution for this will be helpful. from PyQt4 import QtCore, QtGuiimport urllibimport threadimport mathtry:    _fromUtf8 = QtCore.QString.fromUtf8except AttributeError:    def _fromUtf8(s):        return stry:    _encoding = QtGui.QApplication.UnicodeUTF8    def _translate(context, text, disambig):        return QtGui.QApplication.translate(context, text, disambig, _encoding)except AttributeError:    def _translate(context, text, disambig):        return QtGui.QApplication.translate(context, text, disambig)class PhotoViewer(QtGui.QGraphicsView):    def __init__(self, parent):        super(PhotoViewer, self).__init__(parent)        self.added = False        self._zoom = 0        self._scene = QtGui.QGraphicsScene(self)        self._photo = QtGui.QGraphicsPixmapItem()        self._scene.addItem(self._photo)        self.setScene(self._scene)        self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)        self.setResizeAnchor(QtGui.QGraphicsView.AnchorUnderMouse)        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255, 255, 255)))    def fitInView(self):        rect = QtCore.QRectF(self._photo.pixmap().rect())        if not rect.isNull():            unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))            self.scale(1 / unity.width(), 1 / unity.height())            viewrect = self.viewport().rect()            scenerect = self.transform().mapRect(rect)            factor = min(viewrect.width() / scenerect.width(),                         viewrect.height() / scenerect.height())            self.scale(factor, factor)            self.centerOn(rect.center())            self._zoom = 0            return True        return False    def setPhoto(self, pixmap=None):        if pixmap and not pixmap.isNull():            self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)            self._photo.setPixmap(pixmap)            if self.added is False:                self.added = True                while self.fitInView():                    break        else:            self.setDragMode(QtGui.QGraphicsView.NoDrag)            self._photo.setPixmap(QtGui.QPixmap())    def zoomFactor(self):        return self._zoom    def wheelEvent(self, event):        if not self._photo.pixmap().isNull():            if event.delta() > 0:                factor = 1.35                self._zoom += 1            else:                factor = 0.7                self._zoom -= 1            if self._zoom > 0:                self.scale(factor, factor)                pass            elif self._zoom == 0:                self.fitInView()            else:                self._zoom = 0class Window(QtGui.QWidget):    def __init__(self):        super(Window, self).__init__()        self.gridLayout = QtGui.QGridLayout(self)        self.gridLayout.setObjectName(_fromUtf8(gridLayout))        self.tabWidget = QtGui.QTabWidget(self)        self.tabWidget.setObjectName(_fromUtf8(tabWidget))        self.tab = QtGui.QWidget()        self.tab.setObjectName(_fromUtf8(tab))        self.gridLayout_2 = QtGui.QGridLayout(self.tab)        self.gridLayout_2.setObjectName(_fromUtf8(gridLayout_2))        self.gridLayout_2.setSpacing(0)        self.gridLayout_2.setMargin(0)        self.reset_button = QtGui.QPushButton('Test', self.tab)        self.reset_button.clicked.connect(self.back_event)        self.gridLayout_2.addWidget(self.reset_button, 0, 0, 1, 1)        self.tabWidget.addTab(self.tab, _fromUtf8())        self.tab_2 = QtGui.QWidget()        self.tab_2.setObjectName(_fromUtf8(tab_2))        self.tabWidget.addTab(self.tab_2, _fromUtf8())        self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)        self.retranslateUi()        self.tabWidget.setCurrentIndex(0)        QtCore.QMetaObject.connectSlotsByName(self)    def setup_camera(self, cameras):        Initialize camera.                self.grid_position_x = 0        self.grid_position_y = 0        self.number_of_cameras = len(cameras)        self.images = [{}] * self.number_of_cameras        self.lock = [0] * self.number_of_cameras        self.timer = QtCore.QTimer()        self.timer.timeout.connect(self.display_video_stream)        # Display video stream every 100 ms, limit to 10 fps (for testing),        # can be more        self.timer.start(100)    def display_video_stream(self):        for frame in self.images:            try:                img = QtGui.QPixmap()                img.loadFromData(frame['image'])                frame['element'].setPhoto(img)            except (AttributeError, KeyError):                continue    def create_image_view(self, url, cam_id):        viewer = PhotoViewer(self)        viewer.installEventFilter(self)        # Automate placing views inside grids. This creates NxN grids by using        # square root of number of cameras        if cam_id % int(math.sqrt(self.number_of_cameras)) == 0:            self.grid_position_x = 0            self.grid_position_y += 1        else:            self.grid_position_x += 1        self.gridLayout_2.addWidget(            viewer, self.grid_position_y, self.grid_position_x, 1, 1)        thread.start_new_thread(self.CaptureImages, (url, cam_id, viewer))    def eventFilter(self, source, event):        if event.type() == QtCore.QEvent.MouseButtonPress:            # On right click, zoom out image            if event.button() == QtCore.Qt.RightButton:                source.fitInView()            # On middle click, show only that view in fool size            if event.button() == 4:                for label in self.images:                    if label['element'] != source:                        label['element'].hide()                        # lock the camera, so no processing power is used while                        # image is locked and not viewed by user                        self.lock[label['id']] = 1                    else:                        pass                source.fitInView() # does not fit the image inside view, must work on this        return super(Window, self).eventFilter(source, event)    def back_event(self):                The first loop is here to show the image view        The second fits those views. If content in second loop is placed inside first,        results are not correct, because not all views are shown, and fitInView will place        them wrongly                for num, i in enumerate(self.images):            self.lock[num] = 0            i['element'].show()        for i in self.images:            i['element'].fitInView()    def CaptureImages(self, url, cam_id, element):        self.images[cam_id] = {image: None, element: element, id: cam_id}        try:            stream = urllib.urlopen(url)        except:            while True:                stream = urllib.urlopen(url)                if stream:                    break        bytes = ''        while True:            try:                bytes += stream.read(1024)                a = bytes.find('\xff\xd8')                b = bytes.find('\xff\xd9')                # If different camera is opened in fullscreen, stop proccess                # image for this camera                if self.lock[cam_id]:                    if b != -1:                        bytes = bytes[b + 2:]                    continue                if a != -1 and b != -1:                    jpg = bytes[a:b + 2]                    bytes = bytes[b + 2:]                    self.images[cam_id] = {                        image: jpg, element: element, id: cam_id}            except:                pass    def retranslateUi(self):        self.setWindowTitle(_translate(MainWindow, MainWindow, None))        self.tabWidget.setTabText(            self.tabWidget.indexOf(                self.tab), _translate(                MainWindow, Online Monitor, None))        self.tabWidget.setTabText(            self.tabWidget.indexOf(                self.tab_2), _translate(                MainWindow, Archive, None))if __name__ == '__main__':    import sys    app = QtGui.QApplication(sys.argv)    window = Window()    window.showMaximized()    # list of MJPEG url's    # publicly available cameras for testing    cameras = [http://96.10.1.168/mjpg/video.mjpg,               http://92.255.239.225:80/mjpg/video.mjpg?COUNTER,               http://195.189.135.6:80/mjpg/video.mjpg?COUNTER,               http://128.104.138.26:80/mjpg/video.mjpg?COUNTER,               http://75.151.65.9:80/mjpg/video.mjpg?COUNTER,               http://210.249.39.236:80/mjpg/video.mjpg?COUNTER,               http://91.144.150.75:80/mjpg/video.mjpg?COUNTER,               http://91.144.174.140:80/mjpg/video.mjpg?COUNTER,               http://83.56.31.69:80/mjpg/video.mjpg?COUNTER,               ]    window.setup_camera(cameras)    for camera_id, camera_url in enumerate(cameras):        window.create_image_view(camera_url, camera_id)    window.show()    sys.exit(app.exec_())I will separate files properly later, just testing this for now.
Surveillance software using python and pyqt
python;gui;pyqt