1
0
mirror of https://gitlab.os-k.eu/neox/CNIRevelator.git synced 2023-08-25 14:03:10 +02:00

Merge pull request #13 from neox95/v3.0

V3.0.8 release and now working on 3.1
This commit is contained in:
Adrien Bourmault 2019-08-12 00:00:28 +02:00 committed by GitHub
commit 1548fae87b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 567 additions and 160 deletions

5
.gitignore vendored
View File

@ -8,3 +8,8 @@
build/*
dist/*
src/Tesseract-OCR4/*
src/downloads/*
src/config/*
src/logs/*
signtool_8.1/*

View File

@ -1,2 +1,2 @@
# ver|url|checksum, and | as separator, one version per ||
3.0.4|https://github.com/neox95/CNIRevelator/releases/download/3.0.4/CNIRevelator.zip|d03a18b35dfbb20d90664dc2c0f990adc5522e46||3.0.5|https://github.com/neox95/CNIRevelator/releases/download/3.0.5/CNIRevelator.zip|8b52290fb0910d8b9c4ec43293b08017e0031ca2||3.0.6|https://github.com/neox95/CNIRevelator/releases/download/3.0.6/CNIRevelator.zip|4bb4606dc9310d7b34b1fb38f9a0c2daf9518dc5||
3.0.4|https://github.com/neox95/CNIRevelator/releases/download/3.0.4/CNIRevelator.zip|d03a18b35dfbb20d90664dc2c0f990adc5522e46||3.0.5|https://github.com/neox95/CNIRevelator/releases/download/3.0.5/CNIRevelator.zip|8b52290fb0910d8b9c4ec43293b08017e0031ca2||3.0.6|https://github.com/neox95/CNIRevelator/releases/download/3.0.6/CNIRevelator.zip|4bb4606dc9310d7b34b1fb38f9a0c2daf9518dc5||3.0.7|https://github.com/neox95/CNIRevelator/releases/download/3.0.7/CNIRevelator.zip|9aea1627c0b75610225a02458d5705563ca0d6af||3.0.8|https://github.com/neox95/CNIRevelator/releases/download/3.0.8/CNIRevelator.zip|8e849f8fcb5c952c09bdd4eb3392456ec9c6cf8f||

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -4,15 +4,15 @@ title Compilation de CNIRevelator
call pyinstaller -w -D --exclude-module PyQt5 --bootloader-ignore-signals --add-data "C:\Users\pf04950\AppData\Local\Continuum\anaconda3\Lib\site-packages\tld\res\effective_tld_names.dat.txt";"tld\res" --add-data "id-card.ico";"id-card.ico" -i "id-card.ico" -n CNIRevelator src\CNIRevelator.py
call pyinstaller -w -D --exclude-module PyQt5 --bootloader-ignore-signals --add-data "C:\Users\adrie\Anaconda3\Lib\site-packages\tld\res\effective_tld_names.dat.txt";"tld\res" --add-data "src\id-card.ico";"id-card.ico" -i "src\id-card.ico" -n CNIRevelator src\CNIRevelator.py
copy LICENSE dist\CNIRevelator\LICENSE
copy src\id-card.ico dist\CNIRevelator\id-card.ico
copy src\background.png dist\CNIRevelator\background.png
copy src\*.png dist\CNIRevelator\*.png
D:\Public\CNIRevelator-master\CNIRevelator-master\signtool_8.1\signtool\signtool.exe sign /n "CNIRevelator by Adrien Bourmault (neox95)" dist\CNIRevelator\CNIRevelator.exe
signtool_8.1\signtool\signtool.exe sign /n "CNIRevelator by Adrien Bourmault (neox95)" dist\CNIRevelator\CNIRevelator.exe
pause

View File

@ -31,7 +31,7 @@ import threading
import traceback
import psutil
import launcher # launcher.py
import launcher # launcher.py"
import updater # updater.py
import globs # globs.py
import pytesseract # pytesseract.py

BIN
src/Invert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

BIN
src/OCR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

View File

@ -25,18 +25,13 @@
import os
# CNIRevelator version
verType = "alpha"
version = [3, 0, 6]
verType = "final release"
version = [3, 1, 0]
verstring_full = "{}.{}.{} {}".format(version[0], version[1], version[2], verType)
verstring = "{}.{}".format(version[0], version[1])
debug = True
changelog = "Version 3.0.6 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement de l'apparence du launcher de l'application\n- Améliorations de l'interface, notamment de la stabilité\n- Ajout de la signature numérique de l'exécutable\n\n" + \
"Version 3.0.5 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement de l'icône de l'exécutable afin de refléter le changement de version majeur accompli en 3.0\n\n" + \
"Version 3.0.4 \nMise-à-jour mineure avec les corrections suivantes :\n- Correction d'un bug affectant le système de mise-à-jour\n\n" + \
"Version 3.0.3 \nMise-à-jour mineure avec les corrections suivantes :\n- Correction d'un bug affectant le changelog\n- Correction d'une erreur avec la touche Suppr Arrière et Suppr causant une perte de données\n\n" + \
"Version 3.0.2 \nMise-à-jour mineure avec les corrections suivantes :\n- Changement d'icône de l'exécutable\n- Correction d'un bug affectant le logging\n- Correction d'un bug affectant la détection de documents\n- Et autres modifications mineures\n\n" + \
"Version 3.0.1 \nMise-à-jour majeure avec les corrections suivantes :\n- Renouvellement de la signature numérique de l'exécutable\n- Amélioration de présentation du log en cas d'erreur\n- Refonte totale du code source et désobfuscation\n- Téléchargements en HTTPS fiables avec somme de contrôle\n- Nouveaux terminaux d'entrées : un rapide (731) et un complet\n- Détection des documents améliorée, possibilité de choix plus fin\nEt les regressions suivantes :\n- Suppression temporaire de la fonction de lecture OCR. Retour planifié pour une prochaine version"
changelog = "Version 3.1.0 \nMise-à-jour majeure avec les progressions suivantes :\n- Modifications cosmétiques de l'interface utilisateur\n- Stabilisation des changements effectués sur la version mineure 3.0 : interface utilisateur, OCR, VISA A et B, logging"
CNIRTesserHash = '5b58db27f7bc08c58a2cb33d01533b034b067cf8'
CNIRFolder = os.getcwd()
@ -46,6 +41,7 @@ CNIRCryptoKey = '82Xh!efX3#@P~2eG'
CNIRNewVersion = False
CNIRConfig = CNIRFolder + '\\config\\conf.ig'
CNIRTesser = CNIRFolder + '\\Tesseract-OCR4\\'
CNIRErrLog = CNIRFolder + '\\logs\\error.log'
CNIRMainLog = CNIRFolder + '\\logs\\main.log'
CNIRUrlConfig = CNIRFolder + '\\config\\urlconf.ig'

View File

@ -32,7 +32,6 @@ import PIL.Image, PIL.ImageTk
import logger # logger.py
import globs # globs.py
import image # image.py
controlKeys = ["Escape", "Right", "Left", "Up", "Down", "Home", "End", "BackSpace", "Delete", "Inser", "Shift_L", "Shift_R", "Control_R", "Control_L"]
@ -70,6 +69,44 @@ class DocumentAsk(Toplevel):
def ok(self):
self.destroy()
class OpenScanDialog(Toplevel):
def __init__(self, parent, text):
super().__init__(parent)
self.parent = parent
self.title('Validation de la MRZ détectée par OCR')
self.resizable(width=False, height=False)
self.termtext = Text(self, state='normal', width=45, height=2, wrap='none', font='Terminal 17', fg='#121f38')
self.termtext.grid(column=0, row=0, sticky='NEW', padx=5, pady=5)
self.termtext.insert('end', text + '\n')
self.button = Button(self, text='Valider', command=(self.valid))
self.button.grid(column=0, row=1, sticky='S', padx=5, pady=5)
self.update()
hs = self.winfo_screenheight()
w = int(self.winfo_width())
h = int(self.winfo_height())
ws = self.winfo_screenwidth()
hs = self.winfo_screenheight()
x = ws / 2 - w / 2
y = hs / 2 - h / 2
self.geometry('%dx%d+%d+%d' % (w, h, x, y))
if getattr(sys, 'frozen', False):
self.iconbitmap(sys._MEIPASS + '\\id-card.ico\\id-card.ico')
else:
self.iconbitmap('id-card.ico')
def valid(self):
self.parent.validatedtext = self.termtext.get('1.0', 'end')
texting = self.parent.validatedtext.replace(' ', '').replace('\r', '').split('\n')
for i in range(len(texting)):
for char in texting[i]:
if char not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789<':
showerror('Erreur de validation', 'La MRZ soumise contient des caractères invalides', parent=self)
self.parent.validatedtext = ''
self.destroy()
class LoginDialog(Toplevel):
def __init__(self, parent):
@ -178,21 +215,6 @@ class LauncherWindow(Tk):
def exit(self):
self.after(1000, self.destroy)
class AutoScrollbar(ttk.Scrollbar):
def set(self, lo, hi):
if float(lo) <= 0.0:
if float(hi) >= 1.0:
self.grid_remove()
self.grid()
ttk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise TclError('Cannot use pack with the widget ' + self.__class__.__name__)
def place(self, **kw):
raise TclError('Cannot use place with the widget ' + self.__class__.__name__)
class ResizeableCanvas(Canvas):
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)

View File

@ -1,25 +0,0 @@
"""
********************************************************************************
* CNIRevelator *
* *
* Desc: Image calculation for CNI printing *
* *
* 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/>. *
********************************************************************************
"""

View File

@ -35,13 +35,14 @@ import re
import traceback
import cv2
import PIL.Image, PIL.ImageTk
import os, shutil
import webbrowser
import ihm # ihm.py
import logger # logger.py
import mrz # mrz.py
import globs # globs.py
import pytesseract # pytesseract.py
from image import * # image.py
# Global handler
logfile = logger.logCur
@ -53,10 +54,12 @@ class mainWindow(Tk):
self.initialize()
def initialize(self):
self.mrzChar = ''
self.mrzChar = ""
self.mrzDecided = False
self.Tags = []
self.compliance = True
self.corners = []
self.validatedtext = ""
# Hide during construction
self.withdraw()
@ -89,6 +92,9 @@ class mainWindow(Tk):
self.lecteur_ci.grid_rowconfigure(5, weight=1)
# Fill the data sections
ttk.Label((self.lecteur_ci), text='Statut : ').grid(column=0, row=0, padx=5, pady=5)
self.STATUStxt = ttk.Label((self.lecteur_ci), text='EN ATTENTE', font=("TkDefaultFont", 13, "bold"), foreground="orange", anchor=CENTER)
self.STATUStxt.grid(column=1, row=0, padx=5, pady=5)
ttk.Label((self.lecteur_ci), text='Nom : ').grid(column=0, row=1, padx=5, pady=5)
self.nom = ttk.Label((self.lecteur_ci), text=' ')
self.nom.grid(column=1, row=1, padx=5, pady=5)
@ -146,18 +152,110 @@ class mainWindow(Tk):
"INDIC" : self.indic,
}
# The STATUS indicator + image display
self.STATUT = ttk.Labelframe(self, text='Affichage de documents et statut')
self.STATUT.grid_columnconfigure(0, weight=1)
self.STATUT.grid_rowconfigure(0, weight=1)
self.STATUT.frame = Frame(self.STATUT)
self.STATUT.frame.grid(column=0, row=0, sticky='NSEW')
self.STATUT.frame.grid_columnconfigure(0, weight=1)
self.STATUT.frame.grid_rowconfigure(0, weight=1)
self.STATUT.ZONE = ihm.ResizeableCanvas(self.STATUT.frame, bg=self["background"])
self.STATUT.ZONE.pack(fill="both", expand=True)
self.STATUSimg = self.STATUT.ZONE.create_image(0,0, image=None)
self.STATUStxt = self.STATUT.ZONE.create_text(0,0, text='', font='Times 24', fill='#FFBF00')
# The the image viewer
self.imageViewer = ttk.Labelframe(self, text='Affichage et traitement de documents')
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)
# + toolbar
self.toolbar = ttk.Frame(self.imageViewer)
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)
self.toolbar.grid_columnconfigure(15, weight=1, minsize=10)
self.toolbar.grid_columnconfigure(16, weight=1)
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.negativeScan)
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)
self.toolbar.goOCRImg = ImageTk.PhotoImage(PIL.Image.open("OCR.png"))
self.toolbar.goOCR = ttk.Button(self.toolbar, image=self.toolbar.goOCRImg, command=self.goOCRDetection)
self.toolbar.goOCR.grid(column=14, row=0)
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)
self.toolbar.grid(column=0, row=2, padx=0, pady=0)
# + image with scrollbars
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")
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")
self.imageViewer.hbar.config(command=self.imageViewer.ZONE.xview)
self.imageViewer.vbar.config(command=self.imageViewer.ZONE.yview)
self.STATUSimg = self.imageViewer.ZONE.create_image(0,0, image=None, anchor="nw")
# The terminal to enter the MRZ
self.terminal = ttk.Labelframe(self, text='Terminal de saisie de MRZ complète')
@ -191,9 +289,9 @@ class mainWindow(Tk):
self.speed731.grid_columnconfigure(9, weight=1)
self.speed731.grid_rowconfigure(0, weight=1)
self.speed731text = Entry(self.speed731, font='Terminal 14')
self.speed731text.grid(column=0, row=0, sticky='NEW', padx=5)
self.speed731text.grid(column=0, row=0, sticky='NEW', padx=5, pady=5)
self.speedResult = Text((self.speed731), state='disabled', width=1, height=1, wrap='none', font='Terminal 14')
self.speedResult.grid(column=2, row=0, sticky='NEW')
self.speedResult.grid(column=2, row=0, sticky='NEW', padx=5, pady=5)
# The monitor that indicates some useful infos
self.monitor = ttk.Labelframe(self, text='Moniteur')
@ -206,8 +304,8 @@ class mainWindow(Tk):
self.monitor.grid_rowconfigure(0, weight=1)
# All the items griding
self.lecteur_ci.grid(column=0, row=0, sticky='EWNS', columnspan=2, padx=5, pady=5)
self.STATUT.grid(column=2, row=0, sticky='EWNS', columnspan=1, padx=5, pady=5)
self.lecteur_ci.grid(column=2, row=0, sticky='EWNS', columnspan=1, padx=5, pady=5)
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)
@ -222,6 +320,8 @@ class mainWindow(Tk):
menubar.add_cascade(label='Fichier', menu=menu1)
menu3 = Menu(menubar, tearoff=0)
menu3.add_command(label='Commandes au clavier', command=(self.helpbox))
menu3.add_command(label='Signaler un problème', command=(self.openIssuePage))
menu3.add_separator()
menu3.add_command(label='A propos de CNIRevelator', command=(self.infobox))
menubar.add_cascade(label='Aide', menu=menu3)
self.config(menu=menubar)
@ -249,35 +349,118 @@ class mainWindow(Tk):
self.deiconify()
self.minsize(self.winfo_width(), self.winfo_height())
# Load an image using OpenCV
cv_img = cv2.imread("background.png")
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.blur(cv_img, (15, 15))
# 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
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
self.statusUpdate("EN ATTENTE", "#FFBF00", photo, setplace=True)
# Set image
self.imageViewer.image = None
self.imageViewer.imagePath = None
self.imageViewer.imgZoom = 1
self.imageViewer.rotateCount = 0
self.imageViewer.blackhat = False
self.imageViewer.pagenumber = 0
# Some bindings
self.termtext.bind('<Key>', self.entryValidation)
self.termtext.bind('<<Paste>>', self.pasteValidation)
self.speed731text.bind('<Control_R>', self.speedValidation)
self.imageViewer.ZONE.bind("<Button-1>", self.rectangleSelectScan)
logfile.printdbg('Initialization successful')
def statusUpdate(self, msg, color, image=None, setplace=False):
def statusUpdate(self, image=None, setplace=False):
if image:
self.STATUT.image = 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"))
self.STATUT.ZONE.itemconfigure(self.STATUSimg, image=(self.STATUT.image))
self.STATUT.ZONE.itemconfigure(self.STATUStxt, text=(msg), fill=color)
def rectangleSelectScan(self, event):
if self.imageViewer.image:
canvas = event.widget
print("Get coordinates : [{}, {}], for [{}, {}]".format(canvas.canvasx(event.x), canvas.canvasy(event.y), event.x, event.y))
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 ='cyan', width = 2)
print("Get rectangle : [{}, {}], for [{}, {}]".format(self.corners[0][0], self.corners[0][1], self.corners[1][0], self.corners[1][1]))
if len(self.corners) > 2:
self.corners = []
self.imageViewer.ZONE.delete(self.select)
def goOCRDetection(self):
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:
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0)
cv_img = cv2.bitwise_not(cv_img)
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
# Rotate
rotationMatrix=cv2.getRotationMatrix2D((width/2, height/2),int(self.imageViewer.rotateCount*90),1)
cv_img=cv2.warpAffine(cv_img,rotationMatrix,(width,height))
# 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)
x0 = int(self.corners[0][0])
y0 = int(self.corners[0][1])
x1 = int(self.corners[1][0])
y1 = int(self.corners[1][1])
crop_img = cv_img[y0:y1, x0:x1]
# Get the text by OCR
try:
os.environ['PATH'] = globs.CNIRTesser
os.environ['TESSDATA_PREFIX'] = globs.CNIRTesser + '\\tessdata'
text = pytesseract.image_to_string(crop_img, lang='ocrb', boxes=False, config='--psm 6 --oem 0 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<')
# 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))
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
self.stringValidation("")
print(self.mrzChar)
# Reinstall tesseract
except pytesseract.TesseractNotFoundError as e:
try:
shutil.rmtree(globs.CNIRTesser)
except Exception:
pass
showerror('Erreur de module OCR', ('Le module OCR localisé en ' + str(os.environ['PATH']) + 'est introuvable ou corrompu. Il sera réinstallé à la prochaine exécution'), parent=self)
logfile.printerr("Tesseract error : {}. Will be reinstallated".format(e))
# Tesseract error
except pytesseract.TesseractError as e:
logfile.printerr("Tesseract error : {}".format(e))
showerror('Erreur de module OCR', ("Le module Tesseract a rencontré un problème : {}".format(e)), parent=self)
if setplace:
self.STATUT.ZONE.move(self.STATUSimg, self.STATUT.ZONE.winfo_reqwidth() / 2, self.STATUT.ZONE.winfo_reqheight() / 2)
self.STATUT.ZONE.move(self.STATUStxt, self.STATUT.ZONE.winfo_reqwidth() / 2, self.STATUT.ZONE.winfo_reqheight() / 2)
def stringValidation(self, keysym):
# analysis
@ -427,9 +610,11 @@ class mainWindow(Tk):
# Get that
for char in lines:
self.termtext.delete("1.0","end")
self.termtext.insert("1.0", self.mrzChar)
self.mrzChar = self.mrzChar + char
self.stringValidation("")
self.termtext.insert("1.0", self.mrzChar)
return "break"
@ -459,6 +644,9 @@ class mainWindow(Tk):
self.speedResult.insert('end', text)
self.speedResult['state'] = 'disabled'
def goPageChoice(self, event):
self.imageViewer.pagenumber = int(self.toolbar.pageChooser.get()) - 1
self.resizeScan()
def openingScan(self):
path = ''
@ -466,8 +654,165 @@ class mainWindow(Tk):
('TIF files', '*.tiff'),
('JPEG files', '*.jpg'),
('JPEG files', '*.jpeg')))
self.mrzdetected = ''
self.mrzdict = {}
# Load an image using OpenCV
self.imageViewer.imagePath = path
self.imageViewer.imgZoom = 1
self.imageViewer.blackhat = False
self.imageViewer.rotateCount = 0
self.imageViewer.pagenumber = 0
# 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
cv_img = cv2.imreadmulti(self.imageViewer.imagePath)[1][self.imageViewer.pagenumber]
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
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
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
self.statusUpdate(photo)
def zoomInScan50(self, quantity = 50):
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
def zoomOutScan50(self, quantity = 50):
if self.imageViewer.image:
self.imageViewer.imgZoom -= quantity
self.resizeScan()
def zoomInScan20(self, quantity = 20):
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
def zoomOutScan20(self, quantity = 20):
if self.imageViewer.image:
self.imageViewer.imgZoom -= quantity
self.resizeScan()
def zoomInScan(self, quantity = 1):
if self.imageViewer.image:
self.imageViewer.imgZoom += quantity
self.resizeScan()
def zoomOutScan(self, quantity = 1):
if self.imageViewer.image:
self.imageViewer.imgZoom -= quantity
self.resizeScan()
def rotateRight(self):
if self.imageViewer.image:
self.imageViewer.rotateCount -= 1
if self.imageViewer.rotateCount < 0:
self.imageViewer.rotateCount = 4
self.resizeScan()
def rotateLeft(self):
if self.imageViewer.image:
self.imageViewer.rotateCount += 1
if self.imageViewer.rotateCount > 4:
self.imageViewer.rotateCount = 0
self.resizeScan()
def rotateLeft1(self):
if self.imageViewer.image:
self.imageViewer.rotateCount += 0.01
if self.imageViewer.rotateCount > 4:
self.imageViewer.rotateCount = 0
self.resizeScan()
def rotateRight1(self):
if self.imageViewer.image:
self.imageViewer.rotateCount -= 0.01
if self.imageViewer.rotateCount < 0:
self.imageViewer.rotateCount = 4
self.resizeScan()
def negativeScan(self):
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 not self.imageViewer.blackhat:
self.imageViewer.blackhat = True
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0)
cv_img = cv2.bitwise_not(cv_img)
else:
self.imageViewer.blackhat = False
self.resizeScan(cv_img)
def resizeScan(self, cv_img = None):
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:
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
cv_img = cv2.GaussianBlur(cv_img, (3, 3), 0)
cv_img = cv2.bitwise_not(cv_img)
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
# Rotate
rotationMatrix=cv2.getRotationMatrix2D((width/2, height/2),int(self.imageViewer.rotateCount*90),1)
cv_img=cv2.warpAffine(cv_img,rotationMatrix,(width,height))
# 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)
# Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage
photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cv_img))
self.statusUpdate( photo)
except Exception as e:
logfile.printerr("Error with opencv : {}".format(e))
traceback.print_exc(file=sys.stdout)
try:
# Reload an image using OpenCV
path = self.imageViewer.imagePath
self.imageViewer.imgZoom = 1
self.imageViewer.blackhat = False
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))
self.statusUpdate(photo)
except Exception as e:
logfile.printerr("Critical error with opencv : ".format(e))
traceback.print_exc(file=sys.stdout)
showerror("Erreur OpenCV (traitement d'images)", "Une erreur critique s'est produite dans le gestionnaire de traitement d'images OpenCV utilisé par CNIRevelator. L'application va se réinitialiser")
self.initialize()
def newEntry(self):
self.initialize()
@ -477,25 +822,27 @@ class mainWindow(Tk):
Tk().withdraw()
showinfo('A propos de CNIRevelator',
( 'Version du logiciel : CNIRevelator ' + globs.verstring_full + '\n\n' +
"CNIRevelator est un logiciel libre : vous avez le droit de le modifier et/ou le distribuer " +
"dans les termes de la GNU General Public License telle que publiée par " +
"la Free Software Foundation, dans sa version 3 ou " +
"ultérieure. " + "\n\n" +
"CNIRevelator est distribué dans l'espoir d'être utile, sans toutefois " +
"impliquer une quelconque garantie de " +
"QUALITÉ MARCHANDE ou APTITUDE À UN USAGE PARTICULIER. Référez vous à la " +
"GNU General Public License pour plus de détails à ce sujet. " +
"\n\n" +
"Vous devriez avoir reçu une copie de la GNU General Public License " +
"avec CNIRevelator. Si cela n'est pas le cas, jetez un oeil à '<https://www.gnu.org/licenses/>. " +
"\n\n" +
"Le module d'OCR Tesseract 4.0 est soumis à l'Apache License 2004" +
"\n\n" +
"Les bibliothèques python et l'environnement Anaconda 3 sont soumis à la licence BSD 2018-2019" +
"\n\n" +
"Le code source de ce programme est disponible sur Github à l'adresse <https://github.com/neox95/CNIRevelator>.\n" +
" En cas de problèmes ou demande particulière, ouvrez-y une issue ou bien envoyez un mail à neox@os-k.eu !"
( 'Version du logiciel : CNIRevelator ' + globs.verstring_full + '\n\n'
"Copyright © 2018-2019 Adrien Bourmault (neox95)" + "\n\n"
"CNIRevelator est un logiciel libre : vous avez le droit de le modifier et/ou le distribuer "
"dans les termes de la GNU General Public License telle que publiée par "
"la Free Software Foundation, dans sa version 3 ou "
"ultérieure. " + "\n\n"
"CNIRevelator est distribué dans l'espoir d'être utile, sans toutefois "
"impliquer une quelconque garantie de "
"QUALITÉ MARCHANDE ou APTITUDE À UN USAGE PARTICULIER. Référez vous à la "
"GNU General Public License pour plus de détails à ce sujet. "
"\n\n"
"Vous devriez avoir reçu une copie de la GNU General Public License "
"avec CNIRevelator. Si cela n'est pas le cas, jetez un oeil à <https://www.gnu.org/licenses/>. "
"\n\n"
"Le module d'OCR Tesseract 4.0 est soumis à l'Apache License 2004."
"\n\n"
"Les bibliothèques python et l'environnement Anaconda 3 sont soumis à la licence BSD 2018-2019."
"\n\n"
"Le code source de ce programme est disponible sur Github à l'adresse <https://github.com/neox95/CNIRevelator>.\n"
"Son fonctionnement est conforme aux normes et directives du document 9303 de l'OACI régissant les documents de voyages et d'identité." + '\n\n'
" En cas de problèmes ou demande particulière, ouvrez-y une issue ou bien envoyez un mail à neox@os-k.eu !\n\n"
),
parent=self)
@ -521,6 +868,12 @@ class mainWindow(Tk):
parent=self)
def openIssuePage(self):
self.openBrowser("https://github.com/neox95/CNIRevelator/issues")
def openBrowser(self, url):
webbrowser.open_new(url)
def computeSigma(self):
"""
Launch the checksum computation, infos validation and display the results
@ -580,9 +933,11 @@ class mainWindow(Tk):
self.compliance = False
if self.compliance == True:
self.statusUpdate("CONFORME", "chartreuse2")
self.STATUStxt["text"] = "CONFORME"
self.STATUStxt["foreground"] = "green"
else:
self.statusUpdate("NON-CONFORME","red")
self.STATUStxt["text"] = "NON CONFORME"
self.STATUStxt["foreground"] = "red"
return

View File

@ -640,27 +640,26 @@ AC = [
"Certificat de membre d'équipage"
]
## XXXXXXXXXXX
# VB = [
# ["11222333333333333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCCCCCCCCCCCDE"],
# {
# "1": ["2", "CODE", "V."],
# "2": ["3", "PAYS", "[A-Z]+"],
# "3": ["39", "NOM", "[A-Z]+"],
# "4": ["9", "NO", ".+"],
# "5": ["1", "CTRL", "[0-9]","4"],
# "6": ["3", "NAT", "[A-Z]+"],
# "7": ["6", "BDATE", "[0-9]+"],
# "8": ["1", "CTRL", "[0-9]", "7"],
# "9": ["1", "SEX", "[A-Z]"],
# "A": ["6", "EDATE", "[0-9]+"],
# "B": ["1", "CTRL", "[0-9]", "A"],
# "C": ["14", "FACULT", ".+"]
# },
# "Visa de type B"
# ]
VA = [
["11222333333333333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCCCCCCCCCCCCC"],
{
"1": ["2", "CODE", "V."],
"2": ["3", "PAYS", "[A-Z]+"],
"3": ["39", "NOM", "[A-Z]+"],
"4": ["9", "NO", ".+"],
"5": ["1", "CTRL", "[0-9]","4"],
"6": ["3", "NAT", "[A-Z]+"],
"7": ["6", "BDATE", "[0-9]+"],
"8": ["1", "CTRL", "[0-9]", "7"],
"9": ["1", "SEX", "[A-Z]"],
"A": ["6", "EDATE", "[0-9]+"],
"B": ["1", "CTRL", "[0-9]", "A"],
"C": ["16", "FACULT", ".+"]
},
"Visa de type A"
]
VB = [
["112223333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCC"],
{
"1": ["2", "CODE", "V."],
@ -676,14 +675,14 @@ VA = [
"B": ["1", "CTRL", "[0-9]", "A"],
"C": ["8", "FACULT", ".+"]
},
"Visa de type A"
"Visa de type B"
]
TSF = [
["112223333333333333333333333333333333", "444444444566677777789AAAAAABCCCCCC"],
{
"1": ["2", "CODE", "TS"],
"2": ["3", "PAYS", "[A-Z]+"],
"2": ["3", "PAYS", "FRA"],
"3": ["31", "NOM", "([A-Z]|<)+"],
"4": ["9", "NO", ".+"],
"5": ["1", "CTRL", "[0-9]","4"],
@ -695,7 +694,7 @@ TSF = [
"B": ["1", "CTRL", "[0-9]", "A"],
"C": ["8", "FACULT", ".+"]
},
"Titre de séjour"
"Carte de séjour"
]
I__ = [
@ -752,8 +751,7 @@ DL = [
"Permis de conduire"
]
#TYPES = [ID, I__, VB, VA, AC, I_, IP, P, DL]
TYPES = [IDFR, I__, VA, AC, I_, IP, P, DL, TSF]
TYPES = [IDFR, I__, VB, VA, AC, I_, IP, P, DL, TSF]
# longest document MRZ line
longest = max([len(x[0][0]) for x in TYPES])

BIN
src/rotateLeft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

BIN
src/rotateLeft1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

BIN
src/rotateRight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

BIN
src/rotateRight1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

View File

@ -56,7 +56,7 @@ def createShortcut(path, target='', wDir='', icon=''):
shortcut.close()
else:
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(path)
shortcut = shell.CreateShortCut(shell.SpecialFolders("Desktop") + r"\{}".format(path))
shortcut.Targetpath = target
shortcut.WorkingDirectory = wDir
if icon == '':
@ -192,7 +192,6 @@ def getLatestVersion(credentials):
return (finalver, finalurl, finalchecksum)
# XXX Warning : when tesseracturl is not found, it seems to hang and freeze
def tessInstall(PATH, credentials):
# Global Handlers
logfile = logger.logCur
@ -207,18 +206,43 @@ def tessInstall(PATH, credentials):
logfile.printdbg('Preparing download of Tesseract OCR 4...')
getTesseract = downloader.newdownload(credentials, tesseracturl, PATH + '\\downloads\\TsrtPackage.zip', "Tesseract 4 OCR Module").download()
try:
# CHECKSUM
BUF_SIZE = 65536 # lets read stuff in 64kb chunks!
sha1 = hashlib.sha1()
with open(globs.CNIRFolder + '\\downloads\\TsrtPackage.zip', 'rb') as f:
while True:
data = f.read(BUF_SIZE)
if not data:
break
sha1.update(data)
check = sha1.hexdigest()
logfile.printdbg("SHA1: {0}".format(check))
if not check == globs.CNIRTesserHash:
logfile.printerr("Checksum error")
return False
# Unzip Tesseract
logfile.printdbg("Unzipping the package")
launcherWindow.printmsg('Installing the updates')
zip_ref = zipfile.ZipFile(PATH + '\\downloads\\TsrtPackage.zip', 'r')
zip_ref.extractall(PATH)
zip_ref.close()
# Cleanup
try:
os.remove(UPATH + '\\downloads\\TsrtPackage.zip')
except:
pass
return True
except:
return False
else:
return True
## Main Batch Function
def batch(credentials):
@ -284,6 +308,9 @@ def batch(credentials):
shutil.rmtree(UPATH + 'temp')
logfile.printdbg('Extracted :' + UPATH + '\\CNIRevelator.exe')
# Make a shortcut
createShortcut("CNIRevelator.lnk", UPATH + '\\CNIRevelator.exe', UPATH)
launcherWindow.printmsg('Success !')
# Cleanup
@ -366,8 +393,6 @@ def umain():
logfile.printerr(str(e))
launcherWindow.printmsg('Fail :{}'.format(e))
launcherWindow.printmsg('Starting...')
else:
tessInstall(globs.CNIRFolder, credentials)
try:
try:
@ -390,11 +415,42 @@ def umain():
launcherWindow.exit()
return 0
if UPDATE_IS_MADE:
launcherWindow.exit()
return 0
except:
logfile.printerr("A FATAL ERROR OCCURED : " + str(traceback.format_exc()))
launcherWindow.exit()
sys.exit(2)
return 2
try:
try:
# INSTALLING TESSERACT OCR
success = tessInstall(globs.CNIRFolder, credentials)
except Exception as e:
logfile.printerr("An error occured on the thread : " + str(traceback.format_exc()))
launcherWindow.printmsg('ERROR : ' + str(e))
time.sleep(3)
launcherWindow.exit()
return 1
if success:
logfile.printdbg("Software is up-to-date !")
launcherWindow.printmsg('Software is up-to-date !')
else:
logfile.printerr("An error occured. No effective update !")
launcherWindow.printmsg('An error occured. No effective update !')
time.sleep(2)
launcherWindow.exit()
return 0
except:
logfile.printerr("A FATAL ERROR OCCURED : " + str(traceback.format_exc()))
launcherWindow.exit()
sys.exit(2)
return 2
return
time.sleep(2)
launcherWindow.exit()
return 0

BIN
src/zoomIn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

BIN
src/zoomIn20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/zoomIn50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/zoomOut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

BIN
src/zoomOut20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 B

BIN
src/zoomOut50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB