506 lines
21 KiB
Python
506 lines
21 KiB
Python
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
from PyQt5.QtWidgets import QPushButton
|
|
import numpy as np
|
|
import math
|
|
import re
|
|
import matplotlib.pyplot as plt
|
|
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox
|
|
|
|
|
|
|
|
#from imageio import imread
|
|
|
|
|
|
class Ui_MainWindow(object):
|
|
def setupUi(self, MainWindow):
|
|
|
|
MainWindow.setObjectName("MainWindow")
|
|
MainWindow.resize(1032, 847)
|
|
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
|
self.centralwidget.setObjectName("centralwidget")
|
|
self.image = QtWidgets.QLabel(self.centralwidget)
|
|
self.image.setGeometry(QtCore.QRect(0, 0, MainWindow.width(), MainWindow.height()))
|
|
self.image.setText("")
|
|
self.image.setScaledContents(False)
|
|
self.image.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
|
self.image.setWordWrap(False)
|
|
self.image.setObjectName("image")
|
|
MainWindow.setCentralWidget(self.centralwidget)
|
|
|
|
self.menubar = QtWidgets.QMenuBar(MainWindow)
|
|
self.menubar.setGeometry(QtCore.QRect(0, 0, 1032, 22))
|
|
self.menubar.setObjectName("menubar")
|
|
|
|
self.menuFile = QtWidgets.QMenu(self.menubar)
|
|
self.menuFile.setObjectName("menuFile")
|
|
self.menuEdit = QtWidgets.QMenu(self.menubar)
|
|
self.menuEdit.setObjectName("menuEdit")
|
|
self.menuView = QtWidgets.QMenu(self.menubar)
|
|
self.menuView.setObjectName("menuView")
|
|
|
|
self.menuFilters = QtWidgets.QMenu(self.menubar)
|
|
self.menuFilters.setObjectName("menuFilters")
|
|
|
|
self.menuDesaturation = QtWidgets.QMenu(self.menubar)
|
|
self.menuDesaturation.setObjectName("menuDesaturation")
|
|
|
|
self.menuHist = QtWidgets.QMenu(self.menubar)
|
|
self.menuHist.setObjectName("menuHist")
|
|
|
|
self.menuHistGenerate = QtWidgets.QMenu(self.menubar)
|
|
self.menuHistGenerate.setObjectName("menuHistGenerate")
|
|
|
|
MainWindow.setMenuBar(self.menubar)
|
|
|
|
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
|
self.statusbar.setObjectName("statusbar")
|
|
MainWindow.setStatusBar(self.statusbar)
|
|
|
|
self.actionOpen = QtWidgets.QAction(MainWindow)
|
|
self.actionOpen.setObjectName("actionOpen")
|
|
self.actionSave_As = QtWidgets.QAction(MainWindow)
|
|
self.actionSave_As.setObjectName("actionSave_As")
|
|
self.actionExit = QtWidgets.QAction(MainWindow)
|
|
self.actionExit.setObjectName("actionExit")
|
|
self.actionCopy = QtWidgets.QAction(MainWindow)
|
|
self.actionCopy.setObjectName("actionCopy")
|
|
self.actionPaste = QtWidgets.QAction(MainWindow)
|
|
self.actionPaste.setObjectName("actionPaste")
|
|
self.actionZoom_In_25 = QtWidgets.QAction(MainWindow)
|
|
self.actionZoom_In_25.setObjectName("actionZoom_In_25")
|
|
self.actionZoom_Out_25 = QtWidgets.QAction(MainWindow)
|
|
self.actionZoom_Out_25.setObjectName("actionZoom_Out_25")
|
|
self.actionNormal_Size = QtWidgets.QAction(MainWindow)
|
|
self.actionNormal_Size.setObjectName("actionNormal_Size")
|
|
self.actionFit_to_Window = QtWidgets.QAction(MainWindow)
|
|
self.actionFit_to_Window.setObjectName("actionFit_to_Window")
|
|
|
|
self.actionDesatHSV = QtWidgets.QAction(MainWindow)
|
|
self.actionDesatHSV.setObjectName("actionDesatHSV")
|
|
|
|
self.actionDesatSimplified = QtWidgets.QAction(MainWindow)
|
|
self.actionDesatSimplified.setObjectName("actionDesatSimplified")
|
|
|
|
self.actionDesatCCIR = QtWidgets.QAction(MainWindow)
|
|
self.actionDesatCCIR.setObjectName("actionDesatCCIR")
|
|
|
|
self.actionHistGenLumine = QtWidgets.QAction(MainWindow)
|
|
self.actionHistGenLumine.setObjectName("actionHistGenLumine")
|
|
self.actionHistGenR = QtWidgets.QAction(MainWindow)
|
|
self.actionHistGenR.setObjectName("actionHistGenR")
|
|
self.actionHistGenG = QtWidgets.QAction(MainWindow)
|
|
self.actionHistGenG.setObjectName("actionHistGenG")
|
|
self.actionHistGenB = QtWidgets.QAction(MainWindow)
|
|
self.actionHistGenB.setObjectName("actionHistGenB")
|
|
self.actionHistRoz = QtWidgets.QAction(MainWindow)
|
|
self.actionHistRoz.setObjectName("actionHistRoz")
|
|
self.actionHistWyr = QtWidgets.QAction(MainWindow)
|
|
self.actionHistWyr.setObjectName("actionHistWyr")
|
|
|
|
self.menubar.addAction(self.menuFile.menuAction())
|
|
self.menuFile.addAction(self.actionOpen)
|
|
self.menuFile.addAction(self.actionSave_As)
|
|
self.menuFile.addSeparator()
|
|
self.menuFile.addAction(self.actionExit)
|
|
self.menubar.addAction(self.menuEdit.menuAction())
|
|
self.menuEdit.addAction(self.actionCopy)
|
|
self.menuEdit.addAction(self.actionPaste)
|
|
self.menubar.addAction(self.menuView.menuAction())
|
|
self.menuView.addAction(self.actionZoom_In_25)
|
|
self.menuView.addAction(self.actionZoom_Out_25)
|
|
self.menuView.addAction(self.actionNormal_Size)
|
|
self.menuView.addAction(self.actionFit_to_Window)
|
|
|
|
|
|
self.menubar.addAction(self.menuFilters.menuAction())
|
|
|
|
self.menuFilters.addAction(self.menuDesaturation.menuAction())
|
|
self.menuDesaturation.addAction(self.actionDesatHSV)
|
|
self.menuDesaturation.addAction(self.actionDesatSimplified)
|
|
self.menuDesaturation.addAction(self.actionDesatCCIR)
|
|
|
|
self.menubar.addAction(self.menuHist.menuAction())
|
|
self.menuHist.addAction(self.menuHistGenerate.menuAction())
|
|
self.menuHistGenerate.addAction(self.actionHistGenLumine)
|
|
self.menuHistGenerate.addAction(self.actionHistGenR)
|
|
self.menuHistGenerate.addAction(self.actionHistGenG)
|
|
self.menuHistGenerate.addAction(self.actionHistGenB)
|
|
self.menuHist.addAction(self.actionHistRoz)
|
|
self.menuHist.addAction(self.actionHistWyr)
|
|
|
|
|
|
self.retranslateUi(MainWindow)
|
|
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
|
|
|
self.actionOpen.triggered.connect(lambda: self.loadImageFromFile())
|
|
self.actionSave_As.triggered.connect(lambda: self.saveAs())
|
|
self.actionZoom_In_25.triggered.connect(lambda: self.zoomInBy25())
|
|
self.actionZoom_Out_25.triggered.connect(lambda: self.zoomOutBy25())
|
|
self.actionNormal_Size.triggered.connect(lambda: self.normalSize())
|
|
self.actionFit_to_Window.triggered.connect(lambda: self.fitToWindow())
|
|
|
|
self.actionDesatHSV.triggered.connect(lambda: self.desaturation('hsv'))
|
|
self.actionDesatSimplified.triggered.connect(lambda: self.desaturation('simplified'))
|
|
self.actionDesatCCIR.triggered.connect(lambda: self.desaturation('CCIR'))
|
|
|
|
self.actionHistGenLumine.triggered.connect(lambda: self.generateHistogram(-1, 'show'))
|
|
self.actionHistGenR.triggered.connect(lambda: self.generateHistogram(0, 'show'))
|
|
self.actionHistGenG.triggered.connect(lambda: self.generateHistogram(1, 'show'))
|
|
self.actionHistGenB.triggered.connect(lambda: self.generateHistogram(2, 'show'))
|
|
self.actionHistRoz.triggered.connect(lambda: self.manipulateHistogram('stretch'))
|
|
self.actionHistWyr.triggered.connect(lambda: self.manipulateHistogram('eq'))
|
|
|
|
|
|
def retranslateUi(self, MainWindow):
|
|
_translate = QtCore.QCoreApplication.translate
|
|
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
|
|
self.menuFile.setTitle(_translate("MainWindow", "File"))
|
|
self.menuEdit.setTitle(_translate("MainWindow", "Edit"))
|
|
self.menuView.setTitle(_translate("MainWindow", "View"))
|
|
self.menuFilters.setTitle(_translate("MainWindow", "Filters"))
|
|
self.actionOpen.setText(_translate("MainWindow", "Open ..."))
|
|
self.actionSave_As.setText(_translate("MainWindow", "Save As ..."))
|
|
self.actionExit.setText(_translate("MainWindow", "Exit"))
|
|
self.actionCopy.setText(_translate("MainWindow", "Copy"))
|
|
self.actionPaste.setText(_translate("MainWindow", "Paste"))
|
|
self.actionZoom_In_25.setText(_translate("MainWindow", "Zoom In (25%)"))
|
|
self.actionZoom_Out_25.setText(_translate("MainWindow", "Zoom Out (25%)"))
|
|
self.actionNormal_Size.setText(_translate("MainWindow", "Normal Size"))
|
|
self.actionFit_to_Window.setText(_translate("MainWindow", "Fit to Window"))
|
|
|
|
self.menuDesaturation.setTitle(_translate("MainWindow", "Desaturation"))
|
|
self.actionDesatHSV.setText(_translate("MainWindow", "Based on HSV"))
|
|
self.actionDesatSimplified.setText(_translate("MainWindow", "Simplified"))
|
|
self.actionDesatCCIR.setText(_translate("MainWindow", "CCIR 601"))
|
|
|
|
self.menuHist.setTitle(_translate("MainWindow", "Histogram"))
|
|
self.menuHistGenerate.setTitle(_translate("MainWindow", "Generate Histogram"))
|
|
self.actionHistGenLumine.setText(_translate("MainWindow", "Luminancy"))
|
|
self.actionHistGenR.setText(_translate("MainWindow", "R"))
|
|
self.actionHistGenG.setText(_translate("MainWindow", "G"))
|
|
self.actionHistGenB.setText(_translate("MainWindow", "B"))
|
|
self.actionHistRoz.setText(_translate("MainWindow", "Histogram Stretching"))
|
|
self.actionHistWyr.setText(_translate("MainWindow", "Histogram equalization"))
|
|
|
|
|
|
def loadImageFromFile(self):
|
|
fname = QtWidgets.QFileDialog.getOpenFileName(self.centralwidget, 'Open file', './', "Image files (*.pgm *.pbm *.ppm)")
|
|
reader = QtGui.QImageReader(fname[0])
|
|
reader.setAutoTransform(True)
|
|
new_image = reader.read()
|
|
|
|
self.width = new_image.width()
|
|
self.height = new_image.height()
|
|
self.nb_chanels = int(new_image.depth() / 8) if new_image.depth() > 1 else 1
|
|
self.nb_real_channels = int(new_image.bitPlaneCount() / 8) if new_image.bitPlaneCount() > 1 else 1
|
|
self.img_format = new_image.format()
|
|
self.image_object = new_image
|
|
self.data = []
|
|
|
|
for i in range(self.height):
|
|
row = []
|
|
|
|
for j in range(self.width):
|
|
impix = new_image.pixel(j, i)
|
|
pixel = []
|
|
|
|
if self.nb_chanels == 1:
|
|
pixel = [QtGui.qRed(impix)]
|
|
|
|
elif self.nb_chanels == 3:
|
|
pixel = [QtGui.qRed(impix), QtGui.qGreen(impix), QtGui.qBlue(impix)]
|
|
|
|
elif self.nb_chanels == 4:
|
|
pixel = [QtGui.qRed(impix), QtGui.qGreen(impix), QtGui.qBlue(impix), QtGui.qAlpha(impix)]
|
|
|
|
row.append(pixel)
|
|
|
|
self.data.append(row)
|
|
|
|
self.pixmap = QtGui.QPixmap.fromImage(new_image)
|
|
self.loadImage()
|
|
|
|
def saveAs(self):
|
|
name = QtWidgets.QFileDialog.getSaveFileName(self.centralwidget, 'Save file', './', "Image files (*.pgm *.pbm *.ppm)")
|
|
if name:
|
|
m = re.match(r'.*\/(.*\.(ppm|pgm|pbm))$', name[0])
|
|
if m:
|
|
self.pixmap.save(m.group(1), m.group(2))
|
|
|
|
def loadImage(self):
|
|
self.image.setPixmap(self.pixmap)
|
|
|
|
def zoomInBy25(self):
|
|
self.pixmap = self.pixmap.scaled(int(self.pixmap.width()*1.25),
|
|
int(self.pixmap.height()*1.25))
|
|
self.loadImage()
|
|
|
|
def zoomOutBy25(self):
|
|
self.pixmap = self.pixmap.scaled(int(self.pixmap.width()*0.75),
|
|
int(self.pixmap.height()*0.75))
|
|
self.loadImage()
|
|
|
|
def normalSize(self):
|
|
self.pixmap = QtGui.QPixmap.fromImage(self.image_object)
|
|
|
|
self.loadImage()
|
|
|
|
def fitToWindow(self):
|
|
self.image.resize(MainWindow.width(), MainWindow.height())
|
|
self.pixmap = self.pixmap.scaled(MainWindow.width(),
|
|
MainWindow.height())
|
|
self.loadImage()
|
|
|
|
|
|
def desaturation(self, standard):
|
|
if self.nb_chanels > 1:
|
|
data_result = []
|
|
|
|
for i in range(self.height):
|
|
row = []
|
|
for j in range(self.width):
|
|
pixel = []
|
|
|
|
for k in range(self.nb_real_channels):
|
|
pixel.append(self.data[i][j][k])
|
|
|
|
if self.nb_real_channels < self.nb_chanels:
|
|
pixel.append(255)
|
|
|
|
row.append(pixel)
|
|
|
|
data_result.append(row)
|
|
|
|
image_result = QtGui.QImage(self.width, self.height, self.img_format)
|
|
|
|
if standard == 'hsv':
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
color = QtGui.QColor(data_result[i][j][0],
|
|
data_result[i][j][1],
|
|
data_result[i][j][2],
|
|
data_result[i][j][3])
|
|
|
|
|
|
color.convertTo(2)
|
|
color.setHsv(color.hue(), 0, color.value())
|
|
|
|
image_result.setPixelColor(j, i, color)
|
|
elif standard == 'simplified':
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
R = data_result[i][j][0]
|
|
G = data_result[i][j][1]
|
|
B = data_result[i][j][2]
|
|
|
|
gray = (max(R,G,B) + min(R,G,B))/3
|
|
|
|
image_result.setPixel(j, i, QtGui.qRgba(gray,
|
|
gray,
|
|
gray,
|
|
data_result[i][j][3]))
|
|
|
|
else: #CCIR
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
R = data_result[i][j][0]
|
|
G = data_result[i][j][1]
|
|
B = data_result[i][j][2]
|
|
|
|
gray = 0.299 * R + 0.587 * G + 0.114 * B
|
|
|
|
image_result.setPixel(j, i, QtGui.qRgba(gray,
|
|
gray,
|
|
gray,
|
|
data_result[i][j][3]))
|
|
|
|
|
|
self.pixmap = QtGui.QPixmap.fromImage(image_result)
|
|
self.image.setPixmap(self.pixmap)
|
|
self.data = data_result
|
|
|
|
def showDialog(self):
|
|
msgBox = QMessageBox()
|
|
msgBox.setIcon(QMessageBox.Information)
|
|
msgBox.setText("This is mono, click the Luminancy to see the histogram")
|
|
msgBox.setWindowTitle("woah woah wait a second please")
|
|
msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
|
|
returnValue = msgBox.exec()
|
|
if returnValue == QMessageBox.Ok:
|
|
print('OK clicked')
|
|
|
|
|
|
def generateHistogram(self, channel, after):
|
|
|
|
# tablica reprezentująca histogram naszego obrazu;
|
|
h = [0] * 256
|
|
|
|
exists = False
|
|
|
|
if channel == -1:
|
|
if self.image_object.bitPlaneCount() == 8:
|
|
k = 0
|
|
else:
|
|
|
|
# histogram na luminancy dla obrazów kolorów wyliczamy na
|
|
# podstawie monochromatycznej wersji tego obrazu, dlatego
|
|
# wpierw poddajemy jego kolory desaturacji
|
|
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
color = QtGui.QColor(self.data[i][j][0],
|
|
self.data[i][j][1],
|
|
self.data[i][j][2],
|
|
self.data[i][j][3])
|
|
|
|
|
|
color.convertTo(2)
|
|
color.setHsv(color.hue(), 0, color.value())
|
|
|
|
# kolor po desaturacji ma takie same wartości na
|
|
# każdym z jego kanałów, dlatego do histogramu bierzemy
|
|
# przykładowo wartości z kanału Red
|
|
h[color.red()] += 1
|
|
|
|
exists = True
|
|
else:
|
|
k = channel
|
|
|
|
# wyświetlenie komunikatu, kiedy wybieramy opcje R,G,B dla obrazów
|
|
# mono
|
|
if k in [0,1,2] and after == 'show' and self.image_object.bitPlaneCount() == 8:
|
|
self.showDialog()
|
|
exists = True
|
|
|
|
if not exists:
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
h[min(255, self.data[i][j][k])] += 1
|
|
|
|
if after == 'show':
|
|
plt.plot(h)
|
|
plt.show(block=False)
|
|
else:
|
|
return h
|
|
|
|
|
|
|
|
def manipulateHistogram(self, operation):
|
|
|
|
image_result = QtGui.QImage(self.width, self.height, self.img_format)
|
|
|
|
if self.nb_chanels > 1:
|
|
|
|
for k in [0,1,2]:
|
|
if operation == 'stretch':
|
|
self.stretchHistogramOneChannel(k)
|
|
else: #wyrównanie
|
|
self.equalizeHistogramOneChannel(k)
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
image_result.setPixel(j, i, QtGui.qRgba(self.data[i][j][0],
|
|
self.data[i][j][1],
|
|
self.data[i][j][2],
|
|
self.data[i][j][3]))
|
|
elif self.image_object.bitPlaneCount() == 8:
|
|
if operation == 'stretch':
|
|
self.stretchHistogramOneChannel(0)
|
|
else: #wyrównanie
|
|
self.equalizeHistogramOneChannel(0)
|
|
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
image_result.setPixel(j, i, QtGui.qRgba(self.data[i][j][0],
|
|
self.data[i][j][0],
|
|
self.data[i][j][0],
|
|
255))
|
|
self.pixmap = QtGui.QPixmap.fromImage(image_result)
|
|
self.image.setPixmap(self.pixmap)
|
|
self.generateHistogram(-1, 'show')
|
|
|
|
|
|
def stretchHistogramOneChannel(self, channel):
|
|
|
|
h = self.generateHistogram(channel, 'r')
|
|
|
|
# szukamy najmniejszej niezerowej wartości naszego histogramu i
|
|
# odczytujemy odpowiadającą mu wartość piksela
|
|
for a in range(256):
|
|
if h[a] > 0:
|
|
A = a
|
|
break
|
|
|
|
# szukamy największej niezerowej wartości naszego histogramu i
|
|
# odczytujemy odpowiadającą mu wartość piksela
|
|
B = 0
|
|
for a in range(256):
|
|
if h[a] > B:
|
|
B = a
|
|
|
|
# przygotowujemy tablicę LUT
|
|
LUT = [0] * 256
|
|
for i in range(256):
|
|
LUT[i] = int(((255/(B-A)) * (i - A)))
|
|
|
|
|
|
# podmieniamy wartosci pikseli korzystajac z wyzej przygotowanej
|
|
# tablicy LUT
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
self.data[i][j][channel] = LUT[self.data[i][j][channel]]
|
|
|
|
|
|
|
|
def dystrybuanta(self, channel):
|
|
h = self.generateHistogram(channel, 'r')
|
|
|
|
# liczba wszystkich pikseli w naszym obrazie
|
|
s = self.width * self.height
|
|
|
|
# tablica z wartosciami dystrybuanty dla kolejnych pikseli
|
|
dys = [0] * 256
|
|
|
|
temp = 0
|
|
|
|
for i in range(256):
|
|
temp += h[i]
|
|
dys[i] = temp/s
|
|
|
|
return dys
|
|
|
|
|
|
def equalizeHistogramOneChannel(self, channel):
|
|
|
|
dys = self.dystrybuanta(channel)
|
|
|
|
# szukamy pierwszej niezerowej wartosci dystrybuanty
|
|
i = 0
|
|
while dys[i] == 0:
|
|
i = i + 1
|
|
Dmin = dys[i]
|
|
|
|
|
|
# przygotowujemy tablicę LUT w oparciu o wzór wykorzystujący
|
|
# dystrybuantę, a następnie podmieniamy odpowiednio pikselki
|
|
|
|
LUT = [0]*256
|
|
|
|
for i in range(256):
|
|
LUT[i] = round(((dys[i] - Dmin) / (1 - Dmin)) * 255)
|
|
|
|
for i in range(self.height):
|
|
for j in range(self.width):
|
|
self.data[i][j][0] = int(LUT[self.data[i][j][0]])
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
MainWindow = QtWidgets.QMainWindow()
|
|
ui = Ui_MainWindow()
|
|
ui.setupUi(MainWindow)
|
|
MainWindow.show()
|
|
sys.exit(app.exec_())
|
|
|