dreibh's picture
Added ECG information (25 mm/sec, 1 mV/10 mm) to plot.
b62eb97 verified
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ==========================================================================
# ____ __ _ _____ ____ ____
# | _ \ ___ ___ _ __ / _| __ _| | _____ | ____/ ___/ ___|
# | | | |/ _ \/ _ \ '_ \| |_ / _` | |/ / _ \ | _|| | | | _
# | |_| | __/ __/ |_) | _| (_| | < __/ | |__| |__| |_| |
# |____/ \___|\___| .__/|_| \__,_|_|\_\___| |_____\____\____|
# |_|
#
# --- Deepfake ECG Generator ---
# https://github.com/vlbthambawita/deepfake-ecg
# ==========================================================================
#
# DeepfakeECG GUI Application
# Copyright (C) 2023-2025 by Vajira Thambawita
# Copyright (C) 2025 by Thomas Dreibholz
#
# This program 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
# (at your option) any later version.
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Contact:
# * Vajira Thambawita <[email protected]>
# * Thomas Dreibholz <[email protected]>
import datetime
import deepfakeecg
import ecg_plot
import gradio
import io
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy
import pathlib
import random
import sys
import tempfile
import threading
import torch
import typing
import PIL
TempDirectory = None
Sessions = {}
# ###### Print log message ##################################################
def log(logstring):
print(('\x1b[34m' + datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') +
': ' + logstring + '\x1b[0m'));
# ###### DeepFakeECG Plus Session (session with web browser) ################
class Session:
# ###### Constructor #####################################################
def __init__(self):
self.Lock = threading.Lock()
self.Counter = 0
self.Selected = 0
self.Results = None
self.Type = None
self.TempDirectory = tempfile.TemporaryDirectory(dir = TempDirectory.name)
log(f'Prepared temporary directory {self.TempDirectory.name}')
# ###### Destructor ######################################################
def __del__(self):
log(f'Cleaning up temporary directory {self.TempDirectory.name}')
self.TempDirectory.cleanup()
# ###### Increment counter ###############################################
def increment(self):
with self.lock:
self.counter += 1
return self.counter
# ###### Increment counter ###############################################
def increment(self):
with self.lock:
self.counter += 1
return self.counter
# ###### Initialize a new session ###########################################
def initializeSession(request: gradio.Request):
Sessions[request.session_hash] = Session()
log(f'Session "{request.session_hash}" initialized')
# ###### Clean up a session #################################################
def cleanUpSession(request: gradio.Request):
if request.session_hash in Sessions:
del instances[request.session_hash]
log(f'Session "{request.session_hash}" cleaned up')
# ###### Increment counter in session #######################################
def incrementCounter(request: gradio.Request):
if request.session_hash in Sessions:
instance = Sessions[request.session_hash]
return instance.increment()
log(f'ERROR: Session "{request.session_hash}" is not initialized!')
# ###### Generate ECGs ######################################################
def predict(numberOfECGs: int = 1,
# ecgLengthInSeconds: int = 10,
ecgTypeString: str = 'ECG-12',
generatorModel: str = 'Default',
request: gradio.Request = None) -> list:
ecgLengthInSeconds = 10
log(f'Session "{request.session_hash}": Generate EGCs!')
# ====== Set ECG type ====================================================
ecgType = deepfakeecg.DATA_ECG12
if ecgTypeString == 'ECG-8':
ecgType = deepfakeecg.DATA_ECG8
elif ecgTypeString == 'ECG-12':
ecgType = deepfakeecg.DATA_ECG12
else:
sys.stderr.write(f'WARNING: Invalid ecgTypeString {ecgTypeString}, using ECG-12!\n')
# ====== Raise Locator.MAXTICKS, if necessary ============================
matplotlib.ticker.Locator.MAXTICKS = \
max(1000, ecgLengthInSeconds * deepfakeecg.ECG_SAMPLING_RATE)
# print(matplotlib.ticker.Locator.MAXTICKS)
# ====== Generate the ECGs ===============================================
Sessions[request.session_hash].Results = \
deepfakeecg.generateDeepfakeECGs(numberOfECGs,
ecgType = ecgType,
ecgLengthInSeconds = ecgLengthInSeconds,
ecgScaleFactor = deepfakeecg.ECG_DEFAULT_SCALE_FACTOR,
outputFormat = deepfakeecg.OUTPUT_TENSOR,
showProgress = False,
runOnDevice = runOnDevice)
Sessions[request.session_hash].Type = ecgType
# ====== Create a list of image/label tuples for gradio.Gallery ==========
plotList = []
ecgNumber = 1
info = '25 mm/sec, 1 mV/10 mm'
for result in Sessions[request.session_hash].Results:
# ====== Plot ECG =====================================================
# 1. Convert to NumPy
# 2. Remove the Timestamp column (0)
# 3. Convert from µV to mV
result = result.t().detach().cpu().numpy()[1:] / 1000
# print(result)
# ------ ECG-12 -------------------------------------------------------
if ecgType == deepfakeecg.DATA_ECG12:
ecg_plot.plot(result,
title = 'ECG-12 – ' + info,
sample_rate = deepfakeecg.ECG_SAMPLING_RATE,
lead_index = [ 'I', 'II', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'III', 'aVR', 'aVL', 'aVF' ],
lead_order = [0, 1, 8, 9, 10, 11, 2, 3, 4, 5, 6, 7],
show_grid = True)
# ------ ECG-8 --------------------------------------------------------
else:
ecg_plot.plot(result,
title = 'ECG-8 – ' + info,
sample_rate = deepfakeecg.ECG_SAMPLING_RATE,
lead_index = [ 'I', 'II', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6' ],
lead_order = [0, 1, 2, 3, 4, 5, 6, 7],
show_grid = True)
# ====== Generate WebP output =========================================
imageBuffer = io.BytesIO()
plt.savefig(imageBuffer, format = 'webp')
plt.close()
image = PIL.Image.open(imageBuffer)
plotList.append( (image, f'ECG Number {ecgNumber}') )
ecgNumber = ecgNumber + 1
return plotList
# ###### Select ECG in the gallery ##########################################
def select(event: gradio.SelectData,
request: gradio.Request):
# Get selection index from Gallery select() event:
# https://github.com/gradio-app/gradio/issues/1976#issuecomment-1726018500
Sessions[request.session_hash].Selected = event.index
log(f'Session "{request.session_hash}": Selected ECG #{Sessions[request.session_hash].Selected + 1}')
# ###### Download CSV #######################################################
def downloadCSV(request: gradio.Request) -> pathlib.Path:
ecgResult = Sessions[request.session_hash].Results[Sessions[request.session_hash].Selected]
ecgType = Sessions[request.session_hash].Type
fileName = pathlib.Path(Sessions[request.session_hash].TempDirectory.name) / \
('ECG-' + str(Sessions[request.session_hash].Selected + 1) + '.csv')
deepfakeecg.dataToCSV(ecgResult, ecgType, fileName)
log(f'Session "{request.session_hash}": Download CSV file {fileName}')
return fileName
# ###### Download PDF #######################################################
def downloadPDF(request: gradio.Request):
ecgResult = Sessions[request.session_hash].Results[Sessions[request.session_hash].Selected]
ecgType = Sessions[request.session_hash].Type
fileName = pathlib.Path(Sessions[request.session_hash].TempDirectory.name) / \
('ECG-' + str(Sessions[request.session_hash].Selected + 1) + '.pdf')
if ecgType == deepfakeecg.DATA_ECG12:
outputLeads = [ 'I', 'II', 'III', 'aVL', 'aVR', 'aVF', 'V1', 'V2', 'V3', 'V4' , 'V5' , 'V6' ]
else:
outputLeads = [ 'I', 'II', 'V1', 'V2', 'V3', 'V4' , 'V5' , 'V6' ]
deepfakeecg.dataToPDF(ecgResult, ecgType, outputLeads, fileName,
Sessions[request.session_hash].Selected + 1)
log(f'Session "{request.session_hash}": Download PDF file {fileName}')
return fileName
# ###### Analyze the selected ECG ###########################################
def analyze(request: gradio.Request):
log(f'Session "{request.session_hash}": Analyze ECG #{Sessions[request.session_hash].Selected + 1}!')
data = Sessions[request.session_hash].Results[Sessions[request.session_hash].Selected]
print(data)
# ###### Main program #######################################################
# ====== Initialise =========================================================
runOnDevice: typing.Literal['cpu', 'cuda'] = 'cuda' if torch.cuda.is_available() else 'cpu'
css = r"""
div {
background-image: url("https://www.nntb.no/~dreibh/graphics/backgrounds/background-essen.png");
}
/* ###### General Settings ############################################## */
html, body {
height: 100%;
padding: 0;
margin: 0;
font-family: sans-serif;
font-size: small;
background-color: #E3E3E3; /* Simula background colour: #E3E3E3 */
}
/* ###### Header ######################################################## */
div.header {
background-image: none;
background-color: #F15D22; /* Simula header colour: #F15D22 */
height: 7.5%;
display: flex;
justify-content: space-between;
}
div.logo-left {
width: 12.5%;
float: left;
display: flex;
padding: 0% 1%;
align-items: center;
background: white;
}
div.logo-right {
width: 12.5%;
float: right;
display: flex;
padding: 0% 1%;
align-items: center;
background: white;
}
div.title {
display: flex;
align-items: center;
padding: 0% 1%;
background-image: none;
background-color: #F15D22; /* Simula header colour: #F15D22 */
font-family: "Ubuntu", sans-serif;
font-size: 4vh;
font-weight: bold;
}r
img.logo-image {
max-width: 100%;
max-height: 100%;
}
"""
# ====== Create GUI =========================================================
with gradio.Blocks(css = css, theme = gradio.themes.Glass(secondary_hue=gradio.themes.colors.blue)) as gui:
# ====== Session handling ================================================
# Session initialization, to be called when page is loaded
gui.load(initializeSession)
# Session clean-up, to be called when page is closed/refreshed
gui.unload(cleanUpSession)
# ====== Header ==========================================================
big_block = gradio.HTML("""
<div class="header">
<div class="logo-left">
<img class="logo-image" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNDUyLjk3N3B4IiBoZWlnaHQ9IjEyNC43MjVweCIgdmlld0JveD0iMCAwIDQ1Mi45NzcgMTI0LjcyNSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNDUyLjk3NyAxMjQuNzI1Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxnPg0KCQk8cGF0aCBmaWxsPSIjRjE1QTIyIiBkPSJNNDMzLjg1NCw5Mi42OTJjMCwxMS4yNi03LjQ0OCwxNi42MjktMTguMTg4LDE2LjYyOWMtOS43LDAtMTQuMjA0LTQuNTA0LTE0LjIwNC0xMS4yNTkNCgkJCWMwLTYuNTgyLDQuMTU3LTkuMDA3LDEzLjUxMi0xMC45MTNsNy42MjEtMS41NThjNS44ODktMS4yMTMsOC4zMTMtMS45MDYsMTEuMjU5LTIuOTQ1VjkyLjY5MnogTTQ1Mi43MzQsMTAzLjA4NlY2Mi4yMDgNCgkJCWMwLTE4LjE4OS0xMy44NTctMjQuMjUtMzIuNzM3LTI0LjI1Yy0xOS43NDYsMC0zNi4zNzUsOC4xNDEtMzYuMDI3LDI4LjQwOGwxOC41MzIsMS4yMTJjLTAuMTczLTkuNzAxLDUuNTQzLTE0LjM3NiwxNi42MjktMTQuMzc2DQoJCQljMTAuMjIsMCwxNC43MjQsMy40NjQsMTQuNzI0LDEwLjM5MnYxLjM4NmMwLDMuMTE4LTAuODY2LDMuNDY0LTUuMTk2LDQuMzNsLTE4LjE4OCwzLjExOGMtMTAuNzM5LDEuOTA2LTE5LjQsNS4xOTctMjQuNDI0LDExLjI1OQ0KCQkJYy0yLjk0MywzLjYzOS00LjY3Nyw4LjMxNC00LjY3NywxNC41NWMwLDE4LjAxNSwxMy4xNjUsMjUuOTgyLDI5LjEwMSwyNS45ODJjMTQuMTg4LDAsMTkuNzkzLTcuMDA4LDIzLjQxLTEwLjU3OXY4LjA2MmgxOC44NDQNCgkJCXYtMTguNTA2QzQ1Mi43MjcsMTAzLjE2LDQ1Mi43MzQsMTAzLjEyMSw0NTIuNzM0LDEwMy4wODYgTTU5Ljk3NCw2Mi43NzdjLTUuNzE3LTUuNTQzLTEzLjY4NC05LjM1My0yMy4yMTEtOS4zNTMNCgkJCWMtNy43OTUsMC0xMy41MTEsMi43NzEtMTMuNTExLDguMTQxYzAsNS44OSw1LjcxNiw3LjEwMiwxNy4zMjIsOS41MjZsOS44NzMsMi4yNTJjMTIuNDcxLDIuOTQ1LDIzLjczLDcuNjIyLDIzLjczLDIyLjY5MQ0KCQkJYzAsMTguODgtMTcuMTQ4LDI3Ljg4Ny0zNi4wMjgsMjcuODg3Yy0xOS43NDcsMC0zMy4wODQtOS42OTktMzguMTA3LTE2LjgwMWwxMy4zMzctMTEuNzc5DQoJCQljNC42NzcsNi4yMzYsMTIuNjQ1LDEyLjI5OCwyNS4yODksMTIuMjk4YzkuODczLDAsMTUuNTg5LTMuNDY0LDE1LjU4OS05LjM1M2MwLTUuNzE3LTQuNjc2LTYuNzU1LTEyLjY0NC04LjQ4OGwtMTIuMjk4LTIuNTk4DQoJCQljLTE0LjM3Ny0zLjExOC0yNS42MzYtOS4xODEtMjUuNjM2LTI0LjI1YzAtMTguMDE1LDE3LjQ5NS0yNS42MzYsMzMuMjU3LTI1LjYzNmMxNi40NTUsMCwyOS4xLDcuMjc1LDM0LjQ3LDEzLjE2NEw1OS45NzQsNjIuNzc3eg0KCQkJIE0xODcsNDUuMjg0Yy00Ljg1LTUuMzctMTEuOTUyLTcuOTY5LTIwLjQzOS03Ljk2OWMtMTIuOTkxLDAtMjEuODI1LDUuNzE3LTI1LjgwOSwxMi42NDVWMzkuMzkzaC0xOS43NDZ2ODIuNDUxaDE5Ljc0NlY3Ny42NzUNCgkJCWMwLTEzLjMzOCw1LjcxNi0yMi44NjYsMTcuODQxLTIyLjg2NmMxNC44OTYsMCwxNS45MzYsMTAuMzk0LDE1LjkzNiwyNS4yOTF2NDEuNzQ0aDE5Ljc0NlY3Ny42NzUNCgkJCWMwLTEzLjMzOCw1LjU0My0yMi44NjYsMTcuODQxLTIyLjg2NmMxNC44OTcsMCwxNS45MzgsMTAuMzk0LDE1LjkzOCwyNS4yOTF2NDEuNzQ0SDI0Ny44Vjc1LjA3Ng0KCQkJYzAtMTguMTg4LTIuMDc4LTIzLjkwNC03LjI3NC0yOS43OTJjLTQuMzMxLTQuODUxLTExLjI1OS03Ljk2OS0yMS44MjctNy45NjljLTE0Ljg5NiwwLTIzLjIxMSw2Ljc1NS0yOC4yMzQsMTIuODE4DQoJCQlDMTg5LjQyNSw0OC40LDE4OC4zODYsNDYuODQyLDE4Nyw0NS4yODQgTTI2MS44NTMsMzkuMzkzaDE5Ljc0NnY0My44MjRjMCw1Ljg5LDAuMTc0LDExLjYwNiwyLjA3OCwxNS45MzYNCgkJCWMxLjkwNiw0LjMzLDUuNzE3LDcuMjc1LDEyLjY0Niw3LjI3NWMxMi4yOTgsMCwxNy42NjgtOS41MjcsMTcuNjY4LTIyLjg2NHYtNDQuMTdoMTkuNzQ2djgyLjQ1MUgzMTMuOTl2LTEwLjU2Ng0KCQkJYy0zLjk4NCw2LjkyOS0xMi44MTcsMTIuNjQ1LTI0LjU5NywxMi42NDVjLTguMzE0LDAtMTUuNDE2LTIuNTk4LTIwLjI2Ni03Ljk2N2MtNS4xOTctNS44OTEtNy4yNzUtMTEuNjA2LTcuMjc1LTI5Ljc5M1YzOS4zOTN6DQoJCQkgTTEwNS4zNTUsMzkuMzgySDg1LjU5MXY4Mi40NjFoMTkuNzY0VjM5LjM4MnogTTM2OS4wNjksMTIxLjg0NGgtMTkuNzQ2VjQuMDU4aDE5Ljc0NlYxMjEuODQ0eiBNODIuMTIyLDEzLjk4Nw0KCQkJYzAsNy4zOTQsNi4wMjQsMTMuNjA5LDEzLjM1MSwxMy42MDljNy40OSwwLDEzLjM1Mi02LjIxNiwxMy4zNTItMTMuNjA5YzAtNy43MjktNS44NjItMTMuOTQ0LTEzLjM1Mi0xMy45NDQNCgkJCUM4OC4xNDYsMC4wNDIsODIuMTIyLDYuMjU4LDgyLjEyMiwxMy45ODciLz4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4NCg==" alt="SimulaMet" height="32" />
</div>
<div class="title" id="title"><a href="https://ihi-search.eu/">SEARCH</a>&nbsp;Fake ECG Generator</div>
<div class="logo-right">
<img class="logo-image" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iMTAzNy44OTVweCIgaGVpZ2h0PSIxNzQuMjVweCIgdmlld0JveD0iMCAwIDEwMzcuODk1IDE3NC4yNSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAzNy44OTUgMTc0LjI1Ig0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGZpbGw9IiNGMTVBMjIiIGQ9Ik0xMTUuMTAzLDIwLjkzN2MwLDEwLjIyNiw4LjM0MywxOC44NCwxOC40ODEsMTguODRjMTAuMzcxLDAsMTguNDcyLTguNjE0LDE4LjQ3Mi0xOC44NA0KCQljMC0xMC42OTEtOC4xMDEtMTkuMjk2LTE4LjQ3Mi0xOS4yOTZDMTIzLjQ0NSwxLjY0MSwxMTUuMTAzLDEwLjI0NSwxMTUuMTAzLDIwLjkzNyBNNTEyLjI1NiwxNzAuMjIyaC0yNy4zMzdWNy4xOTloMjcuMzM3VjE3MC4yMjINCgkJeiBNMTQ3LjI1Miw1Ni4wNzVoLTI3LjM1OHYxMTQuMTI3aDI3LjM1OFY1Ni4wNzV6IE0zNjMuODY2LDU2LjA5NWgyNy4zMzh2NjAuNjcyYzAsOC4xMywwLjIzMywxNi4wNDYsMi44NzEsMjIuMDQyDQoJCWMyLjYzOSw1Ljk3Niw3LjkxNiwxMC4wNjgsMTcuNSwxMC4wNjhjMTcuMDE3LDAsMjQuNDQ4LTEzLjE5MiwyNC40NDgtMzEuNjQ1VjU2LjA5NWgyNy4zMzl2MTE0LjEyN2gtMjcuMzM5di0xNC42NDkNCgkJYy01LjUwOSw5LjYwNS0xNy43NTIsMTcuNTIxLTM0LjAzMiwxNy41MjFjLTExLjUwNiwwLTIxLjM0My0zLjYwOC0yOC4wNTctMTEuMDQxYy03LjE5Ny04LjE0Ny0xMC4wNjgtMTYuMDQ1LTEwLjA2OC00MS4yM1Y1Ni4wOTV6DQoJCSBNMjU5LjU0NSw2NC4yNDNjLTYuNzEzLTcuNDMxLTE2LjU1LTExLjAyMS0yOC4yODktMTEuMDIxYy0xNy45ODYsMC0zMC4xOTEsNy44OTYtMzUuNzIsMTcuNTAydi0xNC42M2gtMjcuMzE5djExNC4xMjdoMjcuMzE5DQoJCXYtNjEuMTM5YzAtMTguNDcyLDcuOTE2LTMxLjY0NiwyNC42OTktMzEuNjQ2YzIwLjYyNSwwLDIyLjA2MSwxNC4zNzcsMjIuMDYxLDM1LjAwMnY1Ny43ODJoMjcuMzM4di02MS4xMzkNCgkJYzAtMTguNDcyLDcuNjY1LTMxLjY0NiwyNC42ODEtMzEuNjQ2YzIwLjYwNSwwLDIyLjA2MSwxNC4zNzcsMjIuMDYxLDM1LjAwMnY1Ny43ODJoMjcuMzE5di02NC43MjkNCgkJYzAtMjUuMTY1LTIuODUzLTMzLjEwMS0xMC4wNTEtNDEuMjVjLTUuOTk2LTYuNzEzLTE1LjYtMTEuMDIxLTMwLjIyOS0xMS4wMjFjLTIwLjYwNiwwLTMyLjExMiw5LjM1Mi0zOS4wNzcsMTcuNzMzDQoJCUMyNjIuOTAyLDY4LjU1MiwyNjEuNDg1LDY2LjQxNiwyNTkuNTQ1LDY0LjI0MyBNODQuNDQ2LDg4LjQ1OEM3Ni41NCw4MC43OTQsNjUuNTEsNzUuNTE3LDUyLjMxNSw3NS41MTcNCgkJYy0xMC43ODgsMC0xOC42OTQsMy44MjItMTguNjk0LDExLjI1NGMwLDguMTY4LDcuOTA2LDkuODM3LDIzLjk4MSwxMy4xOTNsMTMuNjUsMy4xMjRjMTcuMjY5LDQuMDc0LDMyLjg2OCwxMC41NTUsMzIuODY4LDMxLjQxMg0KCQljMCwyNi4xMzYtMjMuNzQ5LDM4LjU5My00OS44NzQsMzguNTkzYy0yNy4zMzksMC00NS44LTEzLjQyNy01Mi43NDYtMjMuMjQ0bDE4LjQ1MS0xNi4zMTgNCgkJYzYuNDgxLDguNjM2LDE3LjUwMiwxNy4wMTcsMzUuMDEzLDE3LjAxN2MxMy42NjksMCwyMS41NzUtNC43NzMsMjEuNTc1LTEyLjkyNGMwLTcuOTM1LTYuNDgtOS4zNy0xNy41MDEtMTEuNzU3bC0xNy4wMzUtMy42MDgNCgkJQzIyLjExNiwxMTcuOTUsNi41NDQsMTA5LjU2OCw2LjU0NCw4OC43MWMwLTI0LjkzMywyNC4xOTUtMzUuNDg3LDQ2LjAxNC0zNS40ODdjMjIuNzg4LDAsNDAuMjcsMTAuMDY5LDQ3LjcyMSwxOC4yMkw4NC40NDYsODguNDU4DQoJCXogTTYyOC4wNywxNDQuMjZWODcuNjYyYzAtMjUuMTY1LTE5LjIwOS0zMy41NDctNDUuMzI0LTMzLjU0N2MtMjcuMzM5LDAtNTAuMzUsMTEuMjUzLTQ5Ljg2NSwzOS4zMTFsMjUuNjUsMS42NjgNCgkJYy0wLjI1My0xMy40MjcsNy42NjUtMTkuODg4LDIzLjAxMi0xOS44ODhjMTQuMTYzLDAsMjAuMzczLDQuNzkyLDIwLjM3MywxNC4zOTZ2MS45MDJjMCw0LjMwNy0xLjE4Myw0Ljc5Mi03LjE5OCw1Ljk5NA0KCQlsLTI1LjE2Niw0LjMyN2MtMTQuODYxLDIuNjQtMjYuODM0LDcuMTgtMzMuOCwxNS41NjJjLTQuMDczLDUuMDQ1LTYuNDc5LDExLjUyNC02LjQ3OSwyMC4xNTkNCgkJYzAsMjQuOTMzLDE4LjIxOSwzNS45NTMsNDAuMjc4LDM1Ljk1M2MxOS42NTUsMCwyNy4zOTYtOS43MDEsMzIuNDAzLTE0LjY0OHYxMS4xNzZoMjYuMDk4di0yNS42MzENCgkJQzYyOC4wNTMsMTQ0LjM1Nyw2MjguMDcsMTQ0LjMsNjI4LjA3LDE0NC4yNiBNNjAxLjkxNiwxMjkuODYzYzAsMTUuNTgtMTAuMjg0LDIzLjAxMi0yNS4xNjYsMjMuMDEyDQoJCWMtMTMuNDI3LDAtMTkuNjUzLTYuMjI5LTE5LjY1My0xNS41ODFjMC05LjExOCw1Ljc2Mi0xMi40NzYsMTguNzA0LTE1LjA5NWwxMC41MzMtMi4xNTRjOC4xNS0xLjY2OCwxMS41MDctMi42NTcsMTUuNTgyLTQuMDczDQoJCVYxMjkuODYzeiIvPg0KCTxwYXRoIGZpbGw9IiMyNDEzNjMiIGQ9Ik0xMDM2LjUyNSwxNDkuMzgxYy0yLjg4NCwwLjQ4LTcuMjA5LDAuOTYxLTEyLjAxNiwwLjk2MWMtNC44MDYsMC05LjM3MS0wLjcyMS0xMS41MzQtMy4xMjQNCgkJYy0yLjE2My0yLjE2Mi0zLjEyNC01LjI4Ni0zLjEyNC0xMS43NzRWNzcuMDUyaDI2LjY3NFY1NS42NjZoLTI2LjY3NFY5Ljc3bC0yNy4zOTMsNi4wMDd2MzkuODloLTI0LjUxdjIxLjM4NmgyNC41MXY2Mi45NTgNCgkJYzAsMTEuMDUzLDAuMjQsMTcuNTQxLDQuODA1LDIzLjA2N2M2LjI0OCw3LjY4OSwxNy43ODIsOS44NTMsMjguODM2LDkuODUzYzguMTcsMCwxNS4xMzktMC43MjEsMjAuNDI2LTEuOTIyVjE0OS4zODF6DQoJCSBNODk5LjMxNiw3NC44OWMxNS4zNzksMCwyNi4xOTEsMTAuMDkyLDI2LjkxMiwyNC45OWgtNTUuNzQ5Qzg3MS4yMDEsODYuMTgzLDg4My42OTcsNzQuODksODk5LjMxNiw3NC44OSBNOTM0Ljg3OSwxMjkuNjc3DQoJCWMtMy42MDQsNS43NjctMTIuNzM1LDE5LjcwNC0zMy40MDEsMTkuNzA0Yy0yMC4xODQsMC0yOS43OTYtMTMuNDU2LTMxLjcxOS0yOS4wNzZoODMuMzgzYzAtMS45MjIsMC4yNC01LjA0NiwwLjI0LTYuOTY4DQoJCWMwLTM1LjA4My0xNy41NDEtNjAuMzE0LTU0LjA2NS02MC4zMTRjLTMxLjk2MSwwLTU2LjIyOSwyMy41NDktNTYuMjI5LDU4Ljg3MmMwLDM3LjAwNSwyMy4wNjgsNjEuMDM1LDU2Ljk1LDYxLjAzNQ0KCQljMzMuNCwwLDQ4Ljc3OS0yMC45MDYsNTMuODI2LTI4LjgzNUw5MzQuODc5LDEyOS42Nzd6IE03NDAuOTYyLDYzLjgzNmMtNi43MjgtNy40NDktMTYuNTgtMTEuMDU1LTI4LjM1NS0xMS4wNTUNCgkJYy0xOC4wMjEsMC0zMC4yNzYsNy45MzEtMzUuODA0LDE3LjU0MlY1NS42NjZINjQ5LjQxdjExNC4zOGgyNy4zOTN2LTYxLjI3NWMwLTE4LjUwMyw3LjkzLTMxLjcxOSwyNC43NTEtMzEuNzE5DQoJCWMyMC42NjUsMCwyMi4xMDcsMTQuNDE4LDIyLjEwNywzNS4wODN2NTcuOTExaDI3LjM5M3YtNjEuMjc1YzAtMTguNTAzLDcuNjktMzEuNzE5LDI0Ljc1MS0zMS43MTkNCgkJYzIwLjY2NSwwLDIyLjEwNywxNC40MTgsMjIuMTA3LDM1LjA4M3Y1Ny45MTFoMjcuMzk0di02NC44NzljMC0yNS4yMzEtMi44ODQtMzMuMTYxLTEwLjA5My00MS4zMzENCgkJYy02LjAwNy02LjcyOS0xNS42MTktMTEuMDU1LTMwLjI3Ny0xMS4wNTVjLTIwLjY2NSwwLTMyLjE5OSw5LjM3Mi0zOS4xNjgsMTcuNzgyQzc0NC4zMjYsNjguMTYsNzQyLjg4NCw2NS45OTksNzQwLjk2Miw2My44MzYiLz4NCjwvZz4NCjwvc3ZnPg0K" alt="NorNet" height="64" />
</div>
</div>
""")
gradio.Markdown('## Settings')
with gradio.Row():
sliderNumberOfECGs = gradio.Slider(1, 100, label="Number of ECGs", step = 1, value = 4, interactive = True)
# sliderLengthInSeconds = gradio.Slider(5, 60, label="Length (s)", step = 5, value = 10, interactive = True)
dropdownType = gradio.Dropdown( [ 'ECG-12', 'ECG-8' ], label = 'ECG Type', interactive = True)
dropdownGeneratorModel = gradio.Dropdown( [ 'Default' ], label = 'Generator Model', interactive = True)
with gradio.Column():
buttonGenerate = gradio.Button("Generate ECGs!")
buttonAnalyze = gradio.Button("Analyze this ECG!")
with gradio.Row():
buttonCSV = gradio.DownloadButton("Download CSV")
buttonCSV_hidden = gradio.DownloadButton(visible=False, elem_id="download_csv_hidden")
buttonPDF = gradio.DownloadButton("Download PDF")
buttonPDF_hidden = gradio.DownloadButton(visible=False, elem_id="download_pdf_hidden")
gradio.Markdown('## Output')
with gradio.Row():
outputGallery = gradio.Gallery(label = 'output',
columns = [ 1 ],
height = 'auto',
show_label = True,
preview = True)
outputGallery.select(select)
gradio.Markdown('## Analysis')
# ====== Add click event handling for "Generate" button ==================
buttonGenerate.click(predict,
inputs = [ sliderNumberOfECGs,
# sliderLengthInSeconds,
dropdownType,
dropdownGeneratorModel ],
outputs = [ outputGallery ]
)
# ====== Add click event handling for "Analyze" button ===================
buttonAnalyze.click(analyze)
# ====== Add click event handling for download buttons ===================
# Using hidden button and JavaScript, to generate download file on-the-fly:
# https://github.com/gradio-app/gradio/issues/9230#issuecomment-2323771634
buttonCSV.click(downloadCSV)
buttonCSV.click(fn = downloadCSV, inputs = None, outputs = [ buttonCSV_hidden ]).then(
fn = None, inputs = None, outputs = None,
js = "() => document.querySelector('#download_csv_hidden').click()")
buttonPDF.click(downloadPDF)
buttonPDF.click(fn = downloadPDF, inputs = None, outputs = [ buttonPDF_hidden ]).then(
fn = None, inputs = None, outputs = None,
js = "() => document.querySelector('#download_pdf_hidden').click()")
# ====== Run on startup ==================================================
gui.load(predict,
inputs = [ sliderNumberOfECGs,
# sliderLengthInSeconds,
dropdownType,
dropdownGeneratorModel ],
outputs = [ outputGallery ]
)
# ====== Run the GUI ========================================================
if __name__ == "__main__":
# ------ Prepare temporary directory -------------------------------------
TempDirectory = tempfile.TemporaryDirectory(prefix = 'DeepFakeECGPlus-')
log(f'Prepared temporary directory {TempDirectory.name}')
# ------ Run the GUI, with downloads from temporary directory allowed ----
gui.launch(allowed_paths = [ TempDirectory.name ])
# ------ Clean up --------------------------------------------------------
log(f'Cleaning up temporary directory {TempDirectory.name}')
TempDirectory.cleanup()
log('Done!')