CNIRevelator/src/main.py

1165 lines
53 KiB
Python
Raw Normal View History

# -*- coding: utf8 -*-
2019-07-09 23:01:09 +02:00
"""
********************************************************************************
* CNIRevelator *
* *
* Desc: Application IHM & work main class *
* *
* Copyright © 2018-2019 Adrien Bourmault (neox95) *
* *
* This file is part of CNIRevelator. *
* *
* CNIRevelator 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 3 of the License, or *
* any later version. *
* *
* CNIRevelator 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 CNIRevelator. If not, see <https:*www.gnu.org/licenses/>. *
********************************************************************************
"""
from PIL import Image, ImageFont, ImageDraw, ImageTk, ImageEnhance, ImageFilter
import math, warnings, string
2019-07-11 11:16:41 +02:00
from tkinter import *
from tkinter.messagebox import *
from tkinter import filedialog
from tkinter import ttk
2019-07-12 16:12:44 +02:00
import threading
from datetime import datetime
2019-08-19 17:56:23 +02:00
from importlib import reload
import unicodedata
2019-07-17 17:12:21 +02:00
import re
2019-08-07 15:30:22 +02:00
import cv2
import PIL.Image, PIL.ImageTk
2019-08-10 19:59:16 +02:00
import os, shutil
import sys, os
2019-08-26 13:45:33 +02:00
import numpy
import webbrowser
2019-07-09 23:01:09 +02:00
import critical # critical.py
2019-07-19 17:17:30 +02:00
import ihm # ihm.py
2019-07-12 16:12:44 +02:00
import logger # logger.py
2019-07-09 23:01:09 +02:00
import mrz # mrz.py
2019-07-12 10:57:03 +02:00
import globs # globs.py
2019-07-12 16:12:44 +02:00
import pytesseract # pytesseract.py
import lang # lang.py
2019-08-29 12:54:43 +02:00
import updater # updater.py
2019-07-12 16:12:44 +02:00
# Global handler
logfile = logger.logCur
2019-07-09 23:01:09 +02:00
class mainWindow(Tk):
2019-08-19 17:56:23 +02:00
## App Pattern
2019-07-12 16:12:44 +02:00
def __init__(self):
2019-07-09 23:01:09 +02:00
Tk.__init__(self)
2019-07-12 16:12:44 +02:00
self.initialize()
2019-07-09 23:01:09 +02:00
2019-07-12 16:12:44 +02:00
def initialize(self):
2019-08-19 17:56:23 +02:00
"""
Initializes the main window
"""
2019-08-10 19:59:16 +02:00
self.mrzChar = ""
2019-07-18 15:37:58 +02:00
self.mrzDecided = False
2019-07-19 17:17:30 +02:00
self.Tags = []
2019-08-01 16:52:36 +02:00
self.compliance = True
2019-08-09 17:07:26 +02:00
self.corners = []
self.indicators = []
2019-08-10 19:59:16 +02:00
self.validatedtext = ""
2019-07-09 23:01:09 +02:00
# The icon
if getattr(sys, 'frozen', False):
self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico')
else:
self.iconbitmap('id-card.ico')
2019-08-07 09:26:17 +02:00
# Hide during construction
self.withdraw()
# Get the screen size and center
2019-07-09 23:01:09 +02:00
ws = self.winfo_screenwidth()
hs = self.winfo_screenheight()
logfile.printdbg('Launching main window with resolution' + str(ws) + 'x' + str(hs))
2019-07-12 16:12:44 +02:00
# Configuring the size of each part of the window
2019-08-19 17:56:23 +02:00
self.grid_columnconfigure(0, minsize=(ws / 2 * 0.3333333333333333))
self.grid_columnconfigure(1, minsize=(ws / 2 * 0.3333333333333333))
2019-07-09 23:01:09 +02:00
self.grid_columnconfigure(2, weight=1, minsize=(ws / 2 * 0.3333333333333333))
2019-08-19 17:56:23 +02:00
self.grid_rowconfigure(0, minsize=(hs / 2 * 0.5))
self.grid_rowconfigure(1, minsize=(hs / 2 * 0.10))
self.grid_rowconfigure(2, minsize=(hs / 2 * 0.35))
self.grid_rowconfigure(3, minsize=10)
2019-07-12 16:12:44 +02:00
# Prepare the data sections
self.lecteur_ci = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Informations about the current document"])
2019-07-09 23:01:09 +02:00
self.lecteur_ci.grid_columnconfigure(0, weight=1)
self.lecteur_ci.grid_columnconfigure(1, weight=1)
self.lecteur_ci.grid_columnconfigure(2, weight=1)
self.lecteur_ci.grid_columnconfigure(3, weight=1)
self.lecteur_ci.grid_columnconfigure(4, weight=1)
self.lecteur_ci.grid_columnconfigure(5, weight=1)
self.lecteur_ci.grid_rowconfigure(1, weight=1)
self.lecteur_ci.grid_rowconfigure(2, weight=1)
self.lecteur_ci.grid_rowconfigure(3, weight=1)
self.lecteur_ci.grid_rowconfigure(4, weight=1)
self.lecteur_ci.grid_rowconfigure(5, weight=1)
self.lecteur_ci.grid_rowconfigure(6, weight=1)
2019-08-19 17:56:23 +02:00
# And what about the status bar ?
self.statusbar = ihm.StatusBar(self)
self.statusbar.grid(row=3, columnspan=3, sticky="NSEW")
2019-07-12 16:12:44 +02:00
# Fill the data sections
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Status"])).grid(column=0, row=0, padx=5, pady=5)
2019-08-19 17:56:23 +02:00
self.statusbar.set(lang.all[globs.CNIRlang]["IDLE"])
self.STATUStxt = ttk.Label((self.lecteur_ci), text=lang.all[globs.CNIRlang]["IDLE"], font=("TkDefaultFont", 13, "bold"), foreground="orange", anchor=CENTER)
2019-08-10 19:59:16 +02:00
self.STATUStxt.grid(column=1, row=0, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Name"])).grid(column=0, row=1, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.nom = ttk.Label((self.lecteur_ci), text=' ')
self.nom.grid(column=1, row=1, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} (2) : '.format(lang.all[globs.CNIRlang]["Name"])).grid(column=0, row=2, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.prenom = ttk.Label((self.lecteur_ci), text=' ')
self.prenom.grid(column=1, row=2, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Birth date"])).grid(column=0, row=3, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.bdate = ttk.Label((self.lecteur_ci), text=' ')
self.bdate.grid(column=1, row=3, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Issue date"])).grid(column=0, row=4, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.ddate = ttk.Label((self.lecteur_ci), text=' ')
self.ddate.grid(column=1, row=4, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text="{} : ".format(lang.all[globs.CNIRlang]["Expiration date"])).grid(column=0, row=5, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.edate = ttk.Label((self.lecteur_ci), text=' ')
self.edate.grid(column=1, row=5, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Sex"])).grid(column=4, row=1, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.sex = ttk.Label((self.lecteur_ci), text=' ')
self.sex.grid(column=5, row=1, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Issuing country"])).grid(column=4, row=2, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.pays = ttk.Label((self.lecteur_ci), text=' ')
self.pays.grid(column=5, row=2, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Nationality"])).grid(column=4, row=3, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.nat = ttk.Label((self.lecteur_ci), text=' ')
self.nat.grid(column=5, row=3, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Registration"])).grid(column=4, row=4, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.indic = ttk.Label((self.lecteur_ci), text=' ')
self.indic.grid(column=5, row=4, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Document number"])).grid(column=4, row=5, padx=5, pady=5)
2019-07-09 23:01:09 +02:00
self.no = ttk.Label((self.lecteur_ci), text=' ')
self.no.grid(column=5, row=5, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='{} : '.format(lang.all[globs.CNIRlang]["Length"])).grid(column=0, row=6, padx=5, pady=5)
self.len = ttk.Label((self.lecteur_ci), text=' ')
self.len.grid(column=1, row=6, padx=5, pady=5)
2019-07-12 16:12:44 +02:00
self.nom['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.prenom['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.bdate['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.ddate['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.edate['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.no['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.sex['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.nat['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.pays['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.indic['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.len['text'] = lang.all[globs.CNIRlang]["Unknown"]
2019-07-12 16:12:44 +02:00
2019-08-01 16:52:36 +02:00
self.infoList = \
{
"NOM" : self.nom,
"PRENOM" : self.prenom,
"BDATE" : self.bdate,
"DDATE" : self.ddate,
"EDATE" : self.edate,
"NO" : self.no,
"SEX" : self.sex,
"NAT" : self.nat,
"PAYS" : self.pays,
"INDIC" : self.indic,
"LEN" : self.len,
2019-08-01 16:52:36 +02:00
}
2019-08-10 19:59:16 +02:00
# The the image viewer
self.imageViewer = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Display and processing of documents"])
2019-08-10 19:59:16 +02:00
self.imageViewer.grid_columnconfigure(0, weight=1)
self.imageViewer.grid_columnconfigure(1, weight=0)
self.imageViewer.grid_rowconfigure(0, weight=1)
self.imageViewer.grid_rowconfigure(1, weight=1)
self.imageViewer.grid_rowconfigure(2, weight=1)
self.imageViewer.frame = Frame(self.imageViewer)
self.imageViewer.frame.grid(column=0, row=0, sticky='NSEW')
self.imageViewer.frame.grid_columnconfigure(0, weight=1)
self.imageViewer.frame.grid_rowconfigure(0, weight=1)
2019-08-09 17:07:26 +02:00
# + toolbar
2019-08-10 19:59:16 +02:00
self.toolbar = ttk.Frame(self.imageViewer)
2019-08-09 17:07:26 +02:00
self.toolbar.grid_columnconfigure(0, weight=1)
self.toolbar.grid_columnconfigure(1, weight=1)
self.toolbar.grid_columnconfigure(2, weight=1)
self.toolbar.grid_columnconfigure(3, weight=1)
self.toolbar.grid_columnconfigure(4, weight=1)
self.toolbar.grid_columnconfigure(5, weight=1)
self.toolbar.grid_columnconfigure(6, weight=1, minsize=10)
self.toolbar.grid_columnconfigure(7, weight=1)
self.toolbar.grid_columnconfigure(8, weight=1, minsize=10)
self.toolbar.grid_columnconfigure(9, weight=1)
self.toolbar.grid_columnconfigure(10, weight=1)
self.toolbar.grid_columnconfigure(11, weight=1)
self.toolbar.grid_columnconfigure(12, weight=1)
self.toolbar.grid_columnconfigure(13, weight=1, minsize=10)
self.toolbar.grid_columnconfigure(14, weight=1)
2019-08-11 16:26:06 +02:00
self.toolbar.grid_columnconfigure(15, weight=1, minsize=10)
self.toolbar.grid_columnconfigure(16, weight=1)
2019-08-09 17:07:26 +02:00
self.toolbar.grid_rowconfigure(0, weight=1)
self.toolbar.zoomIn50Img = ImageTk.PhotoImage(PIL.Image.open("zoomIn50.png"))
self.toolbar.zoomIn50 = ttk.Button(self.toolbar, image=self.toolbar.zoomIn50Img, command=self.zoomInScan50)
self.toolbar.zoomIn50.grid(column=0, row=0)
self.toolbar.zoomIn20Img = ImageTk.PhotoImage(PIL.Image.open("zoomIn20.png"))
self.toolbar.zoomIn20 = ttk.Button(self.toolbar, image=self.toolbar.zoomIn20Img, command=self.zoomInScan20)
self.toolbar.zoomIn20.grid(column=1, row=0)
self.toolbar.zoomInImg = ImageTk.PhotoImage(PIL.Image.open("zoomIn.png"))
self.toolbar.zoomIn = ttk.Button(self.toolbar, image=self.toolbar.zoomInImg, command=self.zoomInScan)
self.toolbar.zoomIn.grid(column=2, row=0)
self.toolbar.zoomOutImg = ImageTk.PhotoImage(PIL.Image.open("zoomOut.png"))
self.toolbar.zoomOut = ttk.Button(self.toolbar, image=self.toolbar.zoomOutImg, command=self.zoomOutScan)
self.toolbar.zoomOut.grid(column=3, row=0)
self.toolbar.zoomOut20Img = ImageTk.PhotoImage(PIL.Image.open("zoomOut20.png"))
self.toolbar.zoomOut20 = ttk.Button(self.toolbar, image=self.toolbar.zoomOut20Img, command=self.zoomOutScan20)
self.toolbar.zoomOut20.grid(column=4, row=0)
self.toolbar.zoomOut50Img = ImageTk.PhotoImage(PIL.Image.open("zoomOut50.png"))
self.toolbar.zoomOut50 = ttk.Button(self.toolbar, image=self.toolbar.zoomOut50Img, command=self.zoomOutScan50)
self.toolbar.zoomOut50.grid(column=5, row=0)
self.toolbar.invertImg = ImageTk.PhotoImage(PIL.Image.open("invert.png"))
self.toolbar.invert = ttk.Button(self.toolbar, image=self.toolbar.invertImg, command=self.threshScan)
2019-08-09 17:07:26 +02:00
self.toolbar.invert.grid(column=7, row=0)
self.toolbar.rotateLeftImg = ImageTk.PhotoImage(PIL.Image.open("rotateLeft.png"))
self.toolbar.rotateLeft = ttk.Button(self.toolbar, image=self.toolbar.rotateLeftImg, command=self.rotateLeft)
self.toolbar.rotateLeft.grid(column=9, row=0)
self.toolbar.rotateLeft1Img = ImageTk.PhotoImage(PIL.Image.open("rotateLeft1.png"))
self.toolbar.rotateLeft1 = ttk.Button(self.toolbar, image=self.toolbar.rotateLeft1Img, command=self.rotateLeft1)
self.toolbar.rotateLeft1.grid(column=10, row=0)
self.toolbar.rotateRight1Img = ImageTk.PhotoImage(PIL.Image.open("rotateRight1.png"))
self.toolbar.rotateRight1 = ttk.Button(self.toolbar, image=self.toolbar.rotateRight1Img, command=self.rotateRight1)
self.toolbar.rotateRight1.grid(column=11, row=0)
self.toolbar.rotateRightImg = ImageTk.PhotoImage(PIL.Image.open("rotateRight.png"))
self.toolbar.rotateRight = ttk.Button(self.toolbar, image=self.toolbar.rotateRightImg, command=self.rotateRight)
self.toolbar.rotateRight.grid(column=12, row=0)
2019-08-11 16:26:06 +02:00
self.toolbar.goOCRImg = ImageTk.PhotoImage(PIL.Image.open("OCR.png"))
self.toolbar.goOCR = ttk.Button(self.toolbar, image=self.toolbar.goOCRImg, command=self.goOCRDetection)
2019-08-09 17:07:26 +02:00
self.toolbar.goOCR.grid(column=14, row=0)
2019-08-11 16:26:06 +02:00
self.toolbar.pagenumber = StringVar()
self.toolbar.pageChooser = ttk.Combobox(self.toolbar, textvariable=self.toolbar.pagenumber)
self.toolbar.pageChooser.bind("<<ComboboxSelected>>", self.goPageChoice)
self.toolbar.pageChooser['values'] = ('1')
self.toolbar.pageChooser.current(0)
self.toolbar.pageChooser.grid(column=16, row=0)
2019-08-09 17:07:26 +02:00
self.toolbar.grid(column=0, row=2, padx=0, pady=0)
# + image with scrollbars
2019-08-10 19:59:16 +02:00
self.imageViewer.hbar = ttk.Scrollbar(self.imageViewer, orient='horizontal')
self.imageViewer.vbar = ttk.Scrollbar(self.imageViewer, orient='vertical')
self.imageViewer.hbar.grid(row=1, column=0, sticky="NSEW")
self.imageViewer.vbar.grid(row=0, column=1, sticky="NSEW")
2019-08-09 17:07:26 +02:00
2019-08-10 19:59:16 +02:00
self.imageViewer.ZONE = ihm.ResizeableCanvas(self.imageViewer.frame, bg=self["background"], xscrollcommand=(self.imageViewer.hbar.set),
yscrollcommand=(self.imageViewer.vbar.set))
self.imageViewer.ZONE.grid(sticky="NSEW")
2019-08-09 17:07:26 +02:00
2019-08-10 19:59:16 +02:00
self.imageViewer.hbar.config(command=self.imageViewer.ZONE.xview)
self.imageViewer.vbar.config(command=self.imageViewer.ZONE.yview)
2019-08-09 17:07:26 +02:00
2019-08-10 19:59:16 +02:00
self.STATUSimg = self.imageViewer.ZONE.create_image(0,0, image=None, anchor="nw")
2019-07-12 16:12:44 +02:00
# The terminal to enter the MRZ
self.terminal = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Complete MRZ capture terminal"])
2019-07-09 23:01:09 +02:00
self.terminal.grid_columnconfigure(0, weight=1)
self.terminal.grid_rowconfigure(0, weight=1)
self.termframe = Frame(self.terminal)
self.termframe.grid(column=0, row=0, sticky='EW')
self.termframe.grid_columnconfigure(0, weight=1)
self.termframe.grid_rowconfigure(0, weight=1)
self.termguide = Label((self.termframe), text='', font='Terminal 17', fg='#006699')
self.termguide.grid(column=0, row=0, padx=5, pady=0, sticky='NW')
self.termguide['text'] = '0 |5 |10 |15 |20 |25 |30 |35 |40 |45'
2019-07-17 17:12:21 +02:00
self.termtext = Text((self.termframe), state='normal', width=60, height=4, wrap='none', font='Terminal 17', fg='#121f38')
self.termtext.grid(column=0, row=0, sticky='SW', padx=5, pady=25)
# Speed Entry Zone for 731
self.terminal2 = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Quick entry terminal (731)"])
self.terminal2.grid_columnconfigure(0, weight=1)
self.terminal2.grid_rowconfigure(0, weight=1)
self.speed731 = Frame(self.terminal2)
self.speed731.grid(column=0, row=0, sticky='EW')
self.speed731.grid_columnconfigure(0, weight=1)
self.speed731.grid_columnconfigure(1, weight=1)
self.speed731.grid_columnconfigure(2, weight=1)
self.speed731.grid_columnconfigure(3, weight=1)
self.speed731.grid_columnconfigure(4, weight=1)
self.speed731.grid_columnconfigure(5, weight=1)
self.speed731.grid_columnconfigure(6, weight=1)
self.speed731.grid_columnconfigure(7, weight=1)
self.speed731.grid_columnconfigure(8, weight=1)
self.speed731.grid_columnconfigure(9, weight=1)
self.speed731.grid_rowconfigure(0, weight=1)
self.speed731text = Entry(self.speed731, font='Terminal 14')
2019-08-23 16:46:25 +02:00
self.speed731text.grid(column=0, row=0, columnspan=7, sticky='NEW', padx=5, pady=5)
self.speedResult = Text((self.speed731), state='disabled', width=1, height=1, wrap='none', font='Terminal 14')
2019-08-23 16:46:25 +02:00
self.speedResult.grid(column=7, row=0, sticky='NEW', padx=5, pady=5)
2019-07-12 16:12:44 +02:00
# The monitor that indicates some useful infos
self.monitor = ttk.Labelframe(self, text=lang.all[globs.CNIRlang]["Monitor"])
2019-07-09 23:01:09 +02:00
self.monlog = Text((self.monitor), state='disabled', width=60, height=10, wrap='word')
self.monlog.grid(column=0, row=0, sticky='EWNS', padx=5, pady=5)
self.scrollb = ttk.Scrollbar((self.monitor), command=(self.monlog.yview))
self.scrollb.grid(column=1, row=0, sticky='EWNS', padx=5, pady=5)
self.monlog['yscrollcommand'] = self.scrollb.set
self.monitor.grid_columnconfigure(0, weight=1)
self.monitor.grid_rowconfigure(0, weight=1)
2019-07-12 16:12:44 +02:00
# All the items griding
2019-08-09 17:07:26 +02:00
self.lecteur_ci.grid(column=2, row=0, sticky='EWNS', columnspan=1, padx=5, pady=5)
2019-08-10 19:59:16 +02:00
self.imageViewer.grid(column=0, row=0, sticky='EWNS', columnspan=2, padx=5, pady=5)
self.terminal.grid(column=0, row=2, sticky='EWNS', columnspan=2, padx=5, pady=5)
self.terminal2.grid(column=0, row=1, sticky='EWNS', columnspan=2, padx=5, pady=5)
self.monitor.grid(column=2, row=1, sticky='EWNS', columnspan=1, rowspan=2, padx=5, pady=5)
2019-07-12 16:12:44 +02:00
# What is a window without a menu bar ?
2019-07-09 23:01:09 +02:00
menubar = Menu(self)
menu1 = Menu(menubar, tearoff=0)
menu1.add_command(label=lang.all[globs.CNIRlang]["New"], command=(self.newEntry))
menu1.add_command(label=lang.all[globs.CNIRlang]["Open scan..."], command=(self.openingScan))
2019-07-09 23:01:09 +02:00
menu1.add_separator()
menu1.add_command(label=lang.all[globs.CNIRlang]["Quit"], command=(self.destroy))
menubar.add_cascade(label=lang.all[globs.CNIRlang]["File"], menu=menu1)
2019-08-19 17:56:23 +02:00
menu2 = Menu(menubar, tearoff=0)
menu2.add_command(label=lang.all[globs.CNIRlang]["Update options"], command=(self.updateSet))
menu2.add_command(label=lang.all[globs.CNIRlang]["Show Changelog"], command=(self.showChangeLog))
menu2.add_separator()
menu2.add_command(label=lang.all[globs.CNIRlang]["Language"], command=(self.languageSet))
menubar.add_cascade(label=lang.all[globs.CNIRlang]["Settings"], menu=menu2)
2019-07-09 23:01:09 +02:00
menu3 = Menu(menubar, tearoff=0)
menu3.add_command(label=lang.all[globs.CNIRlang]["Keyboard commands"], command=(self.helpbox))
menu3.add_command(label=lang.all[globs.CNIRlang]["Report a bug"], command=(self.openIssuePage))
2019-08-11 16:26:06 +02:00
menu3.add_separator()
menu3.add_command(label=lang.all[globs.CNIRlang]["Project Webpage"], command=(self.openProjectPage))
menu3.add_command(label=lang.all[globs.CNIRlang]["About CNIRevelator"], command=(self.infobox))
menubar.add_cascade(label=lang.all[globs.CNIRlang]["Help"], menu=menu3)
2019-08-19 17:56:23 +02:00
menubar.add_command(label="<|>", command=(self.panelResize))
2019-07-09 23:01:09 +02:00
self.config(menu=menubar)
2019-08-19 17:56:23 +02:00
2019-07-12 16:12:44 +02:00
# The title
2019-07-12 10:57:03 +02:00
self.wm_title(globs.CNIRName)
2019-07-12 16:12:44 +02:00
# Make this window resizable and set her size
2019-08-19 17:56:23 +02:00
self.resizable(0, 0)
2019-08-07 09:26:17 +02:00
self.update()
2019-08-19 17:56:23 +02:00
self.w = int(self.winfo_reqwidth())
self.h = int(self.winfo_reqheight())
self.ws = self.winfo_screenwidth()
self.hs = self.winfo_screenheight()
self.x = (self.ws - self.w)/2
self.y = (self.hs - self.h)/2
self.geometry('%dx%d+%d+%d' % (self.w, self.h, self.x, self.y))
2019-08-07 09:26:17 +02:00
self.update()
self.deiconify()
2019-08-26 13:45:33 +02:00
#self.attributes("-topmost", 1)
2019-08-19 17:56:23 +02:00
self.maxsize(self.w, self.h)
self.minsize(int(2.15 * (self.ws / 2 * 0.3333333333333333)), self.h)
self.currentw = self.w
2019-08-11 16:26:06 +02:00
# Set image
self.imageViewer.image = None
self.imageViewer.imagePath = None
2019-08-10 19:59:16 +02:00
self.imageViewer.imgZoom = 1
self.imageViewer.rotateCount = 0
self.imageViewer.blackhat = 0
2019-08-11 16:26:06 +02:00
self.imageViewer.pagenumber = 0
2019-07-12 16:12:44 +02:00
# Some bindings
self.bind('<Control_R>', self.entryValidation)
self.termtext.bind('<Key>', self.entryValidation)
2019-07-18 15:37:58 +02:00
self.termtext.bind('<<Paste>>', self.pasteValidation)
2019-08-01 16:52:36 +02:00
self.speed731text.bind('<Control_R>', self.speedValidation)
2019-08-10 19:59:16 +02:00
self.imageViewer.ZONE.bind("<Button-1>", self.rectangleSelectScan)
2019-08-09 17:07:26 +02:00
2019-07-17 17:12:21 +02:00
logfile.printdbg('Initialization successful')
if globs.CNIROpenFile:
self.after_idle(lambda : self.openScanFile(sys.argv[1]))
2019-07-09 23:01:09 +02:00
2019-08-19 17:56:23 +02:00
## OCR related functions
2019-08-11 16:26:06 +02:00
def rectangleSelectScan(self, event):
2019-08-19 17:56:23 +02:00
"""
Realises the selection of the MRZ to make OCR possible
"""
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
canvas = event.widget
#print("Get coordinates : [{}, {}], for [{}, {}]".format(canvas.canvasx(event.x), canvas.canvasy(event.y), event.x, event.y))
2019-08-11 16:26:06 +02:00
self.corners.append([canvas.canvasx(event.x), canvas.canvasy(event.y)])
if len(self.corners) == 2:
self.select = self.imageViewer.ZONE.create_rectangle(self.corners[0][0], self.corners[0][1], self.corners[1][0], self.corners[1][1], outline ='red', width = 2)
#print("Get rectangle : [{}, {}], for [{}, {}]".format(self.corners[0][0], self.corners[0][1], self.corners[1][0], self.corners[1][1]))
# Reinit
2019-08-11 16:26:06 +02:00
if len(self.corners) > 2:
self.corners = []
self.imageViewer.ZONE.delete(self.select)
for k in self.indicators:
self.imageViewer.ZONE.delete(k)
# Draw indicator
else:
self.indicators.append(self.imageViewer.ZONE.create_oval(canvas.canvasx(event.x)-4, canvas.canvasy(event.y)-4,canvas.canvasx(event.x)+4, canvas.canvasy(event.y)+4, fill='red', outline='red', width = 2))
2019-08-07 15:30:22 +02:00
2019-08-10 19:59:16 +02:00
def goOCRDetection(self):
2019-08-19 17:56:23 +02:00
"""
Realises the OCR detection and get the text in self.mrzChar (and prints it)
"""
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber]
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
if self.imageViewer.blackhat == 1:
2019-08-11 16:26:06 +02:00
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.threshold(cv_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
elif self.imageViewer.blackhat == 2:
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.medianBlur(cv_img, 3)
2019-08-26 13:45:33 +02:00
2019-08-11 16:26:06 +02:00
try:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
except ValueError:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
2019-08-26 13:45:33 +02:00
2019-08-11 16:26:06 +02:00
# Rotate
2019-08-26 13:45:33 +02:00
cv_img, width, height = self.rotateBound(cv_img, int(self.imageViewer.rotateCount*90))
2019-08-11 16:26:06 +02:00
# Resize
dim = (int(width * (self.imageViewer.imgZoom + 100) / 100), int(height * (self.imageViewer.imgZoom + 100) / 100))
cv_img = cv2.resize(cv_img, dim, interpolation = cv2.INTER_AREA)
# Crop
coords = self.imageViewer.ZONE.coords(self.select)
x0 = int(coords[0])
y0 = int(coords[1])
x1 = int(coords[2])
y1 = int(coords[3])
2019-08-11 16:26:06 +02:00
crop_img = cv_img[y0:y1, x0:x1]
# Get the text by OCR
2019-08-10 19:59:16 +02:00
try:
2019-08-11 16:26:06 +02:00
os.environ['PATH'] = globs.CNIRTesser
os.environ['TESSDATA_PREFIX'] = globs.CNIRTesser + '\\tessdata'
2019-08-20 18:38:22 +02:00
text = pytesseract.image_to_string(crop_img, lang='ocrb', config='--psm 6 --oem 1 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<')
2019-08-11 16:26:06 +02:00
# manual validation
# the regex
regex = re.compile("[^A-Z0-9<\n]")
text = re.sub(regex, '', text)
self.validatedtext = ''
invite = ihm.OpenScanDialog(self, text)
invite.transient(self)
invite.grab_set()
invite.focus_force()
self.wait_window(invite)
#print("text : {}".format(self.validatedtext))
2019-08-11 16:26:06 +02:00
self.mrzChar = ""
# Get that
for char in self.validatedtext:
self.termtext.delete("1.0","end")
self.termtext.insert("1.0", self.mrzChar)
self.mrzChar = self.mrzChar + char
2019-08-23 16:46:25 +02:00
self.stringValidation(isFull=True)
#print(self.mrzChar)
2019-08-11 16:26:06 +02:00
# Reinstall tesseract
except pytesseract.TesseractNotFoundError as e:
try:
shutil.rmtree(globs.CNIRTesser)
except Exception:
pass
showerror(lang.all[globs.CNIRlang]["OCR module error"], (lang.all[globs.CNIRlang]["The OCR module located at {} can not be found or corrupted. It will be reinstalled at the next run"].format(os.environ['PATH'])), parent=self)
logfile.printerr(lang.all[globs.CNIRlang]["Tesseract error : {}. Will be reinstallated"].format(e))
2019-08-11 16:26:06 +02:00
# Tesseract error
except pytesseract.TesseractError as e:
logfile.printerr("Tesseract error : {}".format(e))
showerror(lang.all[globs.CNIRlang]["OCR module error"], (lang.all[globs.CNIRlang]["The Tesseract module encountered a problem: {}"].format(e)), parent=self)
2019-08-07 15:30:22 +02:00
2019-08-19 17:56:23 +02:00
## Regex and document detection + control related functions
2019-08-23 16:46:25 +02:00
def stringValidation(self, keysym="", isFull=False):
2019-08-19 17:56:23 +02:00
"""
Analysis of the already typed document
"""
2019-07-19 17:17:30 +02:00
# analysis
# If we must decide the type of the document
if not self.mrzDecided:
# Get the candidates
2019-08-23 16:46:25 +02:00
candidates = mrz.allDocMatch(self.mrzChar.split("\n"), final=isFull)
2019-07-19 17:17:30 +02:00
2019-08-29 12:54:43 +02:00
if len(candidates) >= 2 and len(candidates) < 4 and len(self.mrzChar) >= 8:
2019-07-19 17:17:30 +02:00
# Parameters for the choice invite
2019-08-29 12:54:43 +02:00
invite = ihm.DocumentAsk(self, candidates)
2019-07-19 17:17:30 +02:00
invite.transient(self)
invite.grab_set()
invite.focus_force()
self.wait_window(invite)
self.logOnTerm(lang.all[globs.CNIRlang]["Document detected: {}\n"].format(candidates[invite.choice][2]))
2019-08-19 17:56:23 +02:00
self.statusbar.set(candidates[invite.choice][2])
2019-07-19 17:17:30 +02:00
self.mrzDecided = candidates[invite.choice]
elif len(candidates) == 1:
self.logOnTerm(lang.all[globs.CNIRlang]["Document detected: {}\n"].format(candidates[0][2]))
2019-08-19 17:56:23 +02:00
self.statusbar.set(candidates[0][2])
2019-07-19 17:17:30 +02:00
self.mrzDecided = candidates[0]
else:
2019-08-05 17:08:23 +02:00
# corrects some problems
if keysym in ["BackSpace", "Delete"]:
return
2019-08-05 15:52:02 +02:00
# get the cursor position
curPos = self.termtext.index(INSERT)
2019-07-19 17:17:30 +02:00
# break the line
if (len(self.mrzChar) - 2 >= len(self.mrzDecided[0][0])) and ("\n" not in self.mrzChar[:-1]):
# In case of there is no second line
if len(self.mrzDecided[0][1]) == 0:
2019-08-01 16:52:36 +02:00
self.mrzChar = self.termtext.get("1.0", "end")[:-1]
self.termtext.delete("1.0","end")
2019-08-01 16:52:36 +02:00
self.termtext.insert("1.0", self.mrzChar[:-1])
2019-08-05 15:52:02 +02:00
self.termtext.mark_set(INSERT, curPos)
else:
# In case of there is a second line
self.mrzChar = self.termtext.get("1.0", "end")[:-1] + '\n'
self.termtext.delete("1.0","end")
self.termtext.insert("1.0", self.mrzChar)
# stop when limit reached
elif (len(self.mrzChar) - 3 >= 2 * len(self.mrzDecided[0][0])):
2019-08-23 16:46:25 +02:00
i = len(self.mrzChar) - 3
while i >= 2 * len(self.mrzDecided[0][0]):
i-=1
self.mrzChar = self.termtext.get("1.0", "end")[:-1]
self.termtext.delete("1.0","end")
self.termtext.insert("1.0", self.mrzChar[:-1])
self.termtext.mark_set(INSERT, curPos)
# compute the control sum if needed
self.computeSigma()
2019-07-17 17:12:21 +02:00
def entryValidation(self, event):
"""
On the fly validation with regex
"""
2019-07-18 15:37:58 +02:00
controlled = False
2019-07-09 23:01:09 +02:00
2019-08-01 16:52:36 +02:00
# get the cursor
if self.mrzDecided:
2019-08-05 17:08:23 +02:00
curPosition = self.termtext.index(INSERT)
position = curPosition.split(".")
2019-08-01 16:52:36 +02:00
pos = (int(position[0]) - 1) * len(self.mrzDecided[0][0]) + (int(position[1]) - 1)
else:
2019-08-05 17:08:23 +02:00
curPosition = self.termtext.index(INSERT)
position = curPosition.split(".")
2019-08-01 16:52:36 +02:00
pos = (int(position[1]) - 1)
2019-07-18 15:37:58 +02:00
# verifying that there is no Ctrl-C/Ctrl-V and others
if event.state & 0x0004 and ( event.keysym == "c" or
event.keysym == "v" or
event.keysym == "a" or
event.keysym == "z" or
event.keysym == "y" ):
controlled = True
2019-07-12 16:12:44 +02:00
if event.keysym == "Tab":
if self.mrzDecided:
controlled = True
self.mrzChar = self.termtext.get("1.0", "end")[:-1]
# the regex
regex = re.compile("[^A-Z0-9<]")
code = re.sub(regex, '', self.mrzChar)
number = mrz.completeDocField(self.mrzDecided, code, pos) - 1
if number == 0:
return "break"
2019-08-05 17:08:23 +02:00
mrzChar = self.termtext.get(curPosition, "end")[:-1]
self.termtext.delete(curPosition,"end")
self.termtext.insert(curPosition, "<"*number + mrzChar)
self.termtext.mark_set("insert", "%d.%d" % (int(position[0]), int(position[1]) + number))
return "break"
2019-08-05 15:52:02 +02:00
if event.keysym == "Escape":
if self.mrzDecided:
# Get the candidates
candidates = mrz.allDocMatch(self.mrzChar.split("\n"))
2019-08-29 12:54:43 +02:00
if len(candidates) >= 2 and len(candidates) < 4 and len(self.mrzChar) >= 8:
2019-08-05 15:52:02 +02:00
# Parameters for the choice invite
2019-08-29 12:54:43 +02:00
invite = ihm.DocumentAsk(self, candidates)
2019-08-05 15:52:02 +02:00
invite.transient(self)
invite.grab_set()
invite.focus_force()
self.wait_window(invite)
2019-08-19 17:56:23 +02:00
self.logOnTerm(lang.all[globs.CNIRlang]["Document detected again: {}\n"].format(candidates[invite.choice][2]))
self.statusbar.set(candidates[invite.choice][2])
2019-08-05 15:52:02 +02:00
self.mrzDecided = candidates[invite.choice]
elif len(candidates) == 1:
2019-08-19 17:56:23 +02:00
self.logOnTerm(lang.all[globs.CNIRlang]["Document detected again: {}\n"].format(candidates[0][2]))
self.statusbar.set(candidates[0][2])
2019-08-05 15:52:02 +02:00
self.mrzDecided = candidates[0]
return "break"
2019-07-18 15:37:58 +02:00
# If not a control char
2019-07-19 17:17:30 +02:00
if not controlled and not event.keysym in ihm.controlKeys:
2019-07-18 15:37:58 +02:00
# the regex
regex = re.compile("[A-Z]|<|[0-9]")
# match !
if not regex.fullmatch(event.char):
self.logOnTerm(lang.all[globs.CNIRlang]["Character not accepted !\n"])
2019-07-18 15:37:58 +02:00
return "break"
2019-08-01 16:52:36 +02:00
# Adds the entry
tempChar = self.termtext.get("1.0", "end")[:-1]
self.mrzChar = tempChar[:pos+1] + event.char + tempChar[pos+1:] + '\n'
2019-07-18 15:37:58 +02:00
# validation of the mrz string
2019-08-05 17:08:23 +02:00
self.stringValidation(event.keysym)
2019-07-18 15:37:58 +02:00
def pasteValidation(self, event):
"""
On the fly validation of pasted text
"""
# cleanup
self.termtext.delete("1.0","end")
# get the clipboard content
lines = self.clipboard_get()
2019-07-19 17:17:30 +02:00
self.mrzChar = ""
2019-07-18 15:37:58 +02:00
# the regex
regex = re.compile("[^A-Z0-9<]")
lines = re.sub(regex, '', lines)
2019-07-19 17:17:30 +02:00
# Get that
for char in lines:
2019-08-10 19:59:16 +02:00
self.termtext.delete("1.0","end")
2019-07-19 17:17:30 +02:00
self.termtext.insert("1.0", self.mrzChar)
self.mrzChar = self.mrzChar + char
2019-08-01 16:52:36 +02:00
self.stringValidation("")
2019-08-10 19:59:16 +02:00
self.termtext.insert("1.0", self.mrzChar)
2019-07-19 17:17:30 +02:00
2019-07-18 15:37:58 +02:00
return "break"
2019-07-17 17:12:21 +02:00
def speedValidation(self, event):
"""
Computation of the speed entry
"""
char = self.speed731text.get()
self.speedResultPrint(str(mrz.computeControlSum(char)))
return "break"
2019-08-19 17:56:23 +02:00
def computeSigma(self):
"""
Launch the checksum computation, infos validation and print/display the results
"""
# the regex
regex = re.compile("[^A-Z0-9<]")
code = re.sub(regex, '', self.mrzChar)
self.compliance = True
2019-07-09 23:01:09 +02:00
2019-08-19 17:56:23 +02:00
allSums = mrz.computeAllControlSum(self.mrzDecided, code)["ctrlSumList"]
#print("Code : _{}_ | Sums : {}".format(code, allSums))
self.termtext.tag_remove("conforme", "1.0", "end")
self.termtext.tag_remove("nonconforme", "1.0", "end")
self.clearTerm()
self.logOnTerm(lang.all[globs.CNIRlang]["Document Review: {}\n\n"].format(self.mrzDecided[2]))
for sum in allSums:
x = sum[1] // len(self.mrzDecided[0][0]) +1
y = sum[1] % len(self.mrzDecided[0][0])
#print("index : {}.{}".format(x,y))
#print("{} == {}".format(code[sum[1]], sum[2]))
if sum[3]:
self.logOnTerm(lang.all[globs.CNIRlang]["Checksum position {}: Lu {} VS Calculated {} [facultative]\n"].format(sum[1], code[sum[1]], sum[2]))
else:
self.logOnTerm(lang.all[globs.CNIRlang]["Checksum position {}: Lu {} VS Calculated {}\n"].format(sum[1], code[sum[1]], sum[2]))
# if sum is facultative or if sum is ok
try:
if sum[3] or int(code[sum[1]]) == int(sum[2]):
self.termtext.tag_add("conforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1))
self.termtext.tag_configure("conforme", background="green", foreground="white")
else:
self.termtext.tag_add("nonconforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1))
self.termtext.tag_configure("nonconforme", background="red", relief='raised', foreground="white")
self.compliance = False
except ValueError:
self.termtext.tag_add("nonconforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1))
self.termtext.tag_configure("nonconforme", background="red", relief='raised', foreground="white")
self.compliance = False
# get the infos
docInfos = mrz.getDocInfos(self.mrzDecided, code)
#print(docInfos)
# display the infos
for key in [ e for e in docInfos ]:
#printkey)
2019-08-29 12:54:43 +02:00
if key in ["CODE", "CTRL", "CTRLF", "FACULT", "NOINT"]:
2019-08-19 17:56:23 +02:00
continue
2019-08-27 16:49:01 +02:00
if not docInfos[key][1] == False:
if not docInfos[key][0] == "":
self.infoList[key]['text'] = docInfos[key][0]
2019-08-23 16:46:25 +02:00
self.infoList[key]['background'] = self['background']
self.infoList[key]['foreground'] = "black"
else:
self.infoList[key]['text'] = lang.all[globs.CNIRlang]["Unknown"]
self.infoList[key]['background'] = self['background']
self.infoList[key]['foreground'] = "black"
2019-08-19 17:56:23 +02:00
else:
self.infoList[key]['background'] = "red"
self.infoList[key]['foreground'] = "white"
2019-08-27 16:49:01 +02:00
self.infoList[key]['text'] = docInfos[key][0]
2019-08-19 17:56:23 +02:00
self.compliance = False
if self.compliance == True:
self.STATUStxt["text"] = lang.all[globs.CNIRlang]["COMPLIANT"]
self.STATUStxt["foreground"] = "green"
self.statusbar.set(lang.all[globs.CNIRlang]["COMPLIANT"])
else:
self.STATUStxt["text"] = lang.all[globs.CNIRlang]["IMPROPER"]
self.STATUStxt["foreground"] = "red"
self.statusbar.set(lang.all[globs.CNIRlang]["IMPROPER"])
## Print functions
2019-07-09 23:01:09 +02:00
def logOnTerm(self, text):
2019-08-19 17:56:23 +02:00
"""
Writes on the monitor
"""
2019-07-09 23:01:09 +02:00
self.monlog['state'] = 'normal'
self.monlog.insert('end', text)
self.monlog['state'] = 'disabled'
self.monlog.yview(END)
2019-08-01 16:52:36 +02:00
def clearTerm(self):
2019-08-19 17:56:23 +02:00
"""
Clears the monitor
"""
2019-08-01 16:52:36 +02:00
self.monlog['state'] = 'normal'
self.monlog.delete('1.0', 'end')
self.monlog['state'] = 'disabled'
self.monlog.yview(END)
def speedResultPrint(self, text):
2019-08-19 17:56:23 +02:00
"""
Prints a result in the quick entry terminal
"""
self.speedResult['state'] = 'normal'
self.speedResult.delete("1.0", 'end')
self.speedResult.insert('end', text)
self.speedResult['state'] = 'disabled'
2019-08-19 17:56:23 +02:00
## Document display related functions
def DisplayUpdate(self, image=None, setplace=False):
"""
Reload the displayer to display the image or not
"""
if image:
self.imageViewer.image = image
self.imageViewer.ZONE.itemconfigure(self.STATUSimg, image=(self.imageViewer.image))
self.imageViewer.ZONE.configure(scrollregion=self.imageViewer.ZONE.bbox("all"))
2019-08-11 16:26:06 +02:00
def goPageChoice(self, event):
2019-08-19 17:56:23 +02:00
"""
Change the current viewed page of the multipage tiff if needed
"""
2019-08-11 16:26:06 +02:00
self.imageViewer.pagenumber = int(self.toolbar.pageChooser.get()) - 1
self.resizeScan()
2019-07-09 23:01:09 +02:00
def openingScan(self):
2019-08-19 17:56:23 +02:00
"""
Open the scan, ask its path and displays it
"""
2019-08-23 16:46:25 +02:00
self.initialize()
2019-08-07 15:30:22 +02:00
path = ''
path = filedialog.askopenfilename(parent=self, title=lang.all[globs.CNIRlang]["Open a scan of document..."], filetypes=(
('TIF files', '*.tif'),
2019-08-07 15:30:22 +02:00
('TIF files', '*.tiff'),
('JPEG files', '*.jpg'),
('JPEG files', '*.jpeg'),
('PNG files', '*.png')
))
self.openScanFile(path)
def openScanFile(self, path):
2019-08-19 17:56:23 +02:00
"""
Open an image file at path to display it on the displayer
"""
# Check if the file is valid
if ( path[-3:] != 'jpg'
and path[-3:] != 'tif'
and path[-4:] != 'jpeg'
and path[-4:] != 'tiff'
and path[-3:] != 'png' ) or not os.path.isfile(path):
showerror(lang.all[globs.CNIRlang]["Open a scan of document..."], lang.all[globs.CNIRlang]["The file you provided is not valid : {}"].format(path))
2019-08-23 16:46:25 +02:00
return
2019-08-09 17:07:26 +02:00
# Load an image using OpenCV
2019-08-10 19:59:16 +02:00
self.imageViewer.imagePath = path
self.imageViewer.imgZoom = 1
self.imageViewer.blackhat = 0
2019-08-10 19:59:16 +02:00
self.imageViewer.rotateCount = 0
2019-08-11 16:26:06 +02:00
self.imageViewer.pagenumber = 0
2019-08-11 16:26:06 +02:00
# Determine how many pages
self.toolbar.pageChooser['values'] = ('1')
total = len(cv2.imreadmulti(self.imageViewer.imagePath)[1])
for i in range(2, total + 1):
self.toolbar.pageChooser['values'] += tuple(str(i))
# Open the first page
try:
cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber]
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
except:
logfile.printerr("Error with : {} in {} with total of {} pages".format(cv2.imreadmulti(self.imageViewer.imagePath), self.imageViewer.imagePath, total))
critical.crashCNIR(False, "OpenCV openScanFile() error")
2019-08-11 16:26:06 +02:00
try:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
except ValueError:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
# Update shape
self.imageViewer.height, self.imageViewer.width = cv_img.shape[:2]
2019-08-09 17:07:26 +02:00
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
2019-08-19 17:56:23 +02:00
self.DisplayUpdate(photo)
2019-08-09 17:07:26 +02:00
def zoomInScan50(self, quantity = 50):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def zoomOutScan50(self, quantity = 50):
if self.imageViewer.image and int(self.imageViewer.width * (self.imageViewer.imgZoom - quantity + 100) / 100) > 100:
2019-08-11 16:26:06 +02:00
self.imageViewer.imgZoom -= quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def zoomInScan20(self, quantity = 20):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def zoomOutScan20(self, quantity = 20):
if self.imageViewer.image and int(self.imageViewer.width * (self.imageViewer.imgZoom - quantity + 100) / 100) > 100:
2019-08-11 16:26:06 +02:00
self.imageViewer.imgZoom -= quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def zoomInScan(self, quantity = 1):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def zoomOutScan(self, quantity = 1):
if self.imageViewer.image and int(self.imageViewer.width * (self.imageViewer.imgZoom - quantity + 100) / 100) > 100:
2019-08-11 16:26:06 +02:00
self.imageViewer.imgZoom -= quantity
self.resizeScan()
2019-08-09 17:07:26 +02:00
def rotateRight(self):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.rotateCount -= 1
if self.imageViewer.rotateCount < 0:
self.imageViewer.rotateCount = 4
self.resizeScan()
2019-08-09 17:07:26 +02:00
2019-08-10 19:59:16 +02:00
def rotateLeft(self):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.rotateCount += 1
if self.imageViewer.rotateCount > 4:
self.imageViewer.rotateCount = 0
self.resizeScan()
2019-08-09 17:07:26 +02:00
def rotateLeft1(self):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.rotateCount += 0.01
if self.imageViewer.rotateCount > 4:
self.imageViewer.rotateCount = 0
self.resizeScan()
2019-08-09 17:07:26 +02:00
2019-08-10 19:59:16 +02:00
def rotateRight1(self):
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
self.imageViewer.rotateCount -= 0.01
if self.imageViewer.rotateCount < 0:
self.imageViewer.rotateCount = 4
self.resizeScan()
2019-08-09 17:07:26 +02:00
def threshScan(self):
2019-08-19 17:56:23 +02:00
"""
Invert the bits to make a negative of the scan (and highlight the contrasts)
"""
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
# Load an image using OpenCV
cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber]
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
if self.imageViewer.blackhat == 0:
self.imageViewer.blackhat = 1
2019-08-11 16:26:06 +02:00
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.threshold(cv_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
elif self.imageViewer.blackhat == 1:
self.imageViewer.blackhat = 2
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.medianBlur(cv_img, 3)
2019-08-09 17:07:26 +02:00
else:
self.imageViewer.blackhat = 0
2019-08-11 16:26:06 +02:00
self.resizeScan(cv_img)
2019-08-26 13:45:33 +02:00
def rotateBound(self, image, angle):
"""
Computes the rotation matrix and the new shapes in order to rotate an image
"""
# grab the dimensions of the image and then determine the
# center
(h, w) = image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# grab the rotation matrix , then grab the sine and cosine
# (i.e., the rotation components of the matrix)
M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
cos = numpy.abs(M[0, 0])
sin = numpy.abs(M[0, 1])
# compute the new bounding dimensions of the image
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# adjust the rotation matrix to take into account translation
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# perform the actual rotation and return the image
return cv2.warpAffine(image, M, (nW, nH)), nW, nH
2019-08-11 16:26:06 +02:00
def resizeScan(self, cv_img = None):
2019-08-19 17:56:23 +02:00
"""
Reloads the image according to settings
"""
2019-08-11 16:26:06 +02:00
if self.imageViewer.image:
try:
if not hasattr(cv_img, 'shape'):
# Load an image using OpenCV
cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber]
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
if self.imageViewer.blackhat == 1:
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.threshold(cv_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
elif self.imageViewer.blackhat == 2:
2019-08-11 16:26:06 +02:00
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.medianBlur(cv_img, 3)
2019-08-26 13:45:33 +02:00
2019-08-11 16:26:06 +02:00
try:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
except ValueError:
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width = cv_img.shape
2019-08-26 13:45:33 +02:00
2019-08-11 16:26:06 +02:00
# Rotate
2019-08-26 13:45:33 +02:00
cv_img, width, height = self.rotateBound(cv_img, int(self.imageViewer.rotateCount*90))
2019-08-11 16:26:06 +02:00
# Resize
dim = (int(width * (self.imageViewer.imgZoom + 100) / 100), int(height * (self.imageViewer.imgZoom + 100) / 100))
cv_img = cv2.resize(cv_img, dim, interpolation = cv2.INTER_AREA)
2019-08-26 13:45:33 +02:00
# Update shape
self.imageViewer.height, self.imageViewer.width = cv_img.shape[:2]
2019-08-09 17:07:26 +02:00
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
2019-08-19 17:56:23 +02:00
self.DisplayUpdate( photo)
2019-08-09 17:07:26 +02:00
except Exception as e:
2019-08-11 16:26:06 +02:00
logfile.printerr("Error with opencv : {}".format(e))
try:
# Reload an image using OpenCV
path = self.imageViewer.imagePath
self.imageViewer.imgZoom = 1
self.imageViewer.blackhat = 0
2019-08-11 16:26:06 +02:00
self.imageViewer.rotateCount = 0
cv_img = cv2.imreadmulti(path)
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
# Get the image dimensions (OpenCV stores image data as NumPy ndarray)
height, width, channels_no = cv_img.shape
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
2019-08-19 17:56:23 +02:00
self.DisplayUpdate(photo)
2019-08-11 16:26:06 +02:00
except Exception as e:
logfile.printerr("Critical error with opencv : ".format(e))
critical.crashCNIR(False, "OpenCV resizeScan() error")
2019-08-11 16:26:06 +02:00
self.initialize()
2019-07-09 23:01:09 +02:00
2019-08-19 17:56:23 +02:00
## IHM and user interface related functions
2019-07-09 23:01:09 +02:00
def newEntry(self):
2019-08-19 17:56:23 +02:00
"""
Reinits the IHM and invite
"""
2019-07-12 16:12:44 +02:00
self.initialize()
self.logOnTerm('\n\n{}\n'.format(lang.all[globs.CNIRlang]["Please type a MRZ or open a scan"]))
2019-07-09 23:01:09 +02:00
def infobox(self):
2019-08-19 17:56:23 +02:00
"""
Shows the About dialog
"""
2019-07-09 23:01:09 +02:00
Tk().withdraw()
2019-07-12 16:12:44 +02:00
showinfo( lang.all[globs.CNIRlang]["About CNIRevelator"],
(
lang.all[globs.CNIRlang]["ABOUT"]
2019-07-12 16:12:44 +02:00
),
parent=self)
2019-07-09 23:01:09 +02:00
def helpbox(self):
2019-08-19 17:56:23 +02:00
"""
Shows the keyboard help summary
"""
Tk().withdraw()
showinfo( lang.all[globs.CNIRlang]["Keyboard commands"],
(
lang.all[globs.CNIRlang]["KEYBHELP"]
),
parent=self)
2019-08-11 16:26:06 +02:00
def openIssuePage(self):
2019-08-19 17:56:23 +02:00
"""
Sends a bug report
"""
critical.crashCNIR(shutdown=False, option="Voluntary Bug Report", isVoluntary=True)
def openProjectPage(self):
"""
Opens the Project Repository webpage
2019-08-19 17:56:23 +02:00
"""
webbrowser.open_new("https://github.com/neox95/CNIRevelator")
2019-08-11 16:26:06 +02:00
2019-08-19 17:56:23 +02:00
def showChangeLog(self):
changelogWin = ihm.ChangelogDialog(self, ('{} : CNIRevelator {}\n\n{}'.format(lang.all[globs.CNIRlang]["Program version"], globs.verstring_full, lang.all[globs.CNIRlang]["CHANGELOG"])))
changelogWin.transient(self)
changelogWin.grab_set()
changelogWin.focus_force()
self.wait_window(changelogWin)
def updateSet(self):
2019-08-01 16:52:36 +02:00
"""
2019-08-19 17:56:23 +02:00
Update Settings
2019-08-01 16:52:36 +02:00
"""
2019-08-29 12:54:43 +02:00
changeupdateWin = ihm.updateSetDialog(self, updater.getUpdateChannel())
2019-08-19 17:56:23 +02:00
changeupdateWin.transient(self)
changeupdateWin.grab_set()
changeupdateWin.focus_force()
self.wait_window(changeupdateWin)
def languageSet(self):
"""
Lang Settings
"""
2019-08-29 12:54:43 +02:00
changelangWin = ihm.langDialog(self, globs.CNIRlang)
2019-08-19 17:56:23 +02:00
changelangWin.transient(self)
changelangWin.grab_set()
changelangWin.focus_force()
self.wait_window(changelangWin)
global mrz
mrz = reload(mrz)
self.initialize()
def panelResize(self):
"""
Shows or hides the panel
"""
if self.currentw > int(2.15 * (self.ws / 2 * 0.3333333333333333)):
self.currentw = int(2.15 * (self.ws / 2 * 0.3333333333333333))
self.geometry('%dx%d+%d+%d' % (self.currentw, self.h, self.x, self.y))
self.update()
2019-08-07 15:30:22 +02:00
else:
2019-08-19 17:56:23 +02:00
self.currentw = self.w
self.geometry('%dx%d+%d+%d' % (self.currentw, self.h, self.x, self.y))
self.update()
2019-07-09 23:01:09 +02:00