├── .gitignore ├── LICENSE.md ├── README.md ├── beatanalyzer └── EPBeatAnalyzer │ ├── .gitignore │ ├── EPBeatAnalysis.sln │ ├── EPBeatAnalysis │ ├── EPBeatAnalysis.vcxproj │ ├── EPBeatAnalysis.vcxproj.filters │ ├── EPLib │ │ ├── ANALBEAT.CPP │ │ ├── ANALBEAT.H │ │ ├── BDAC.CPP │ │ ├── BDAC.H │ │ ├── BXB.CPP │ │ ├── CLASSIFY.CPP │ │ ├── EASYTEST.CPP │ │ ├── ECGCODES.H │ │ ├── ECGMAP.H │ │ ├── MATCH.CPP │ │ ├── MATCH.H │ │ ├── NOISECHK.CPP │ │ ├── POSTCLAS.CPP │ │ ├── POSTCLAS.H │ │ ├── QRSDET.CPP │ │ ├── QRSDET.H │ │ ├── QRSDET2.CPP │ │ ├── QRSFILT.CPP │ │ ├── RYTHMCHK.CPP │ │ ├── RYTHMCHK.H │ │ ├── VERSION RELEASE NOTES.txt │ │ ├── WFDB.H │ │ ├── WFLIB.LIB │ │ └── picqrs.c │ └── main.c │ └── README.md ├── qtapp ├── glecgcanvas.cpp ├── glecgcanvas.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── qtecg.pro └── qtecg.pro.user.68dc8e7 ├── webapp ├── ecg.js ├── lib │ └── jquery.min.js ├── main.css └── main.html ├── win32app ├── .gitignore ├── ECGViewerScreenshot.png ├── ECGViewerWin32.sln ├── ECGViewerWin32 │ ├── ECGViewerWin32.vcxproj │ ├── ECGViewerWin32.vcxproj.filters │ ├── appinit.h │ ├── build.bat │ ├── build.sh │ ├── ecg.cpp │ ├── ecg.h │ ├── logging.c │ ├── logging.h │ ├── signalfileio.cpp │ ├── signalfileio.h │ ├── windowtools.cpp │ ├── windowtools.h │ ├── wndmain.cpp │ └── wndmain.h └── README.md └── wpfapp └── ECGViewerWPF ├── App.config ├── App.xaml ├── App.xaml.cs ├── DLLs ├── LibEDF.dll └── PdfSharp.dll ├── ECGView.cs ├── ECGViewerWPF.csproj ├── ECGViewerWPF.sln ├── ImageTools.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── NumberStepper.xaml ├── NumberStepper.xaml.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── SignalAnalysis.cs └── SignalGenerator.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pro.user 3 | 4 | old 5 | testdata.txt 6 | 7 | *.min.js 8 | *.min.map 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # MSTest test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | 43 | # NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | # DNX 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | *_i.c 58 | *_p.c 59 | *_i.h 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.svclog 80 | *.scc 81 | 82 | # Chutzpah Test files 83 | _Chutzpah* 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opendb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | *.VC.db 94 | *.VC.VC.opendb 95 | 96 | # Visual Studio profiler 97 | *.psess 98 | *.vsp 99 | *.vspx 100 | *.sap 101 | 102 | # TFS 2012 Local Workspace 103 | $tf/ 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | *.DotSettings.user 112 | 113 | # JustCode is a .NET coding add-in 114 | .JustCode 115 | 116 | # TeamCity is a build add-in 117 | _TeamCity* 118 | 119 | # DotCover is a Code Coverage Tool 120 | *.dotCover 121 | 122 | # NCrunch 123 | _NCrunch_* 124 | .*crunch*.local.xml 125 | nCrunchTemp_* 126 | 127 | # MightyMoose 128 | *.mm.* 129 | AutoTest.Net/ 130 | 131 | # Web workbench (sass) 132 | .sass-cache/ 133 | 134 | # Installshield output folder 135 | [Ee]xpress/ 136 | 137 | # DocProject is a documentation generator add-in 138 | DocProject/buildhelp/ 139 | DocProject/Help/*.HxT 140 | DocProject/Help/*.HxC 141 | DocProject/Help/*.hhc 142 | DocProject/Help/*.hhk 143 | DocProject/Help/*.hhp 144 | DocProject/Help/Html2 145 | DocProject/Help/html 146 | 147 | # Click-Once directory 148 | publish/ 149 | 150 | # Publish Web Output 151 | *.[Pp]ublish.xml 152 | *.azurePubxml 153 | # TODO: Comment the next line if you want to checkin your web deploy settings 154 | # but database connection strings (with potential passwords) will be unencrypted 155 | *.pubxml 156 | *.publishproj 157 | 158 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 159 | # checkin your Azure Web App publish settings, but sensitive information contained 160 | # in these scripts will be unencrypted 161 | PublishScripts/ 162 | 163 | # NuGet Packages 164 | *.nupkg 165 | # The packages folder can be ignored because of Package Restore 166 | **/packages/* 167 | # except build/, which is used as an MSBuild target. 168 | !**/packages/build/ 169 | # Uncomment if necessary however generally it will be regenerated when needed 170 | #!**/packages/repositories.config 171 | # NuGet v3's project.json files produces more ignoreable files 172 | *.nuget.props 173 | *.nuget.targets 174 | 175 | # Microsoft Azure Build Output 176 | csx/ 177 | *.build.csdef 178 | 179 | # Microsoft Azure Emulator 180 | ecf/ 181 | rcf/ 182 | 183 | # Windows Store app package directories and files 184 | AppPackages/ 185 | BundleArtifacts/ 186 | Package.StoreAssociation.xml 187 | _pkginfo.txt 188 | 189 | # Visual Studio cache files 190 | # files ending in .cache can be ignored 191 | *.[Cc]ache 192 | # but keep track of directories ending in .cache 193 | !*.[Cc]ache/ 194 | 195 | # Others 196 | ClientBin/ 197 | ~$* 198 | *~ 199 | *.dbmdl 200 | *.dbproj.schemaview 201 | *.pfx 202 | *.publishsettings 203 | node_modules/ 204 | orleans.codegen.cs 205 | 206 | # Since there are multiple workflows, uncomment next line to ignore bower_components 207 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 208 | #bower_components/ 209 | 210 | # RIA/Silverlight projects 211 | Generated_Code/ 212 | 213 | # Backup & report files from converting an old project file 214 | # to a newer Visual Studio version. Backup files are not needed, 215 | # because we have git ;-) 216 | _UpgradeReport_Files/ 217 | Backup*/ 218 | UpgradeLog*.XML 219 | UpgradeLog*.htm 220 | 221 | # SQL Server files 222 | *.mdf 223 | *.ldf 224 | 225 | # Business Intelligence projects 226 | *.rdl.data 227 | *.bim.layout 228 | *.bim_*.settings 229 | 230 | # Microsoft Fakes 231 | FakesAssemblies/ 232 | 233 | # GhostDoc plugin setting file 234 | *.GhostDoc.xml 235 | 236 | # Node.js Tools for Visual Studio 237 | .ntvs_analysis.dat 238 | 239 | # Visual Studio 6 build log 240 | *.plg 241 | 242 | # Visual Studio 6 workspace options file 243 | *.opt 244 | 245 | # Visual Studio LightSwitch build output 246 | **/*.HTMLClient/GeneratedArtifacts 247 | **/*.DesktopClient/GeneratedArtifacts 248 | **/*.DesktopClient/ModelManifest.xml 249 | **/*.Server/GeneratedArtifacts 250 | **/*.Server/ModelManifest.xml 251 | _Pvt_Extensions 252 | 253 | # Paket dependency manager 254 | .paket/paket.exe 255 | paket-files/ 256 | 257 | # FAKE - F# Make 258 | .fake/ 259 | 260 | # JetBrains Rider 261 | .idea/ 262 | *.sln.iml -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECG Viewer 2 | 3 | View graphical signal data from ECG files. 4 | 5 | This project is provided under the terms of the [MIT license](http://choosealicense.com/licenses/mit/). 6 | 7 | There are three applications: 8 | 9 | | App | Description | 10 | |-----------------------------------|------------------------------------------------------------------------------------------------| 11 | | [ECG Viewer QT App](qtapp/) | Developed with C++ and the [QT](https://www.qt.io/) framework. | 12 | | [ECG Viewer Web App](webapp/) | Developed with JS and HTML to view in a web browser. | 13 | | [ECG Viewer Win32 App](win32app/) | Developed with C/C++ and [Win32 API](https://en.wikipedia.org/wiki/Windows_API). | 14 | | [ECG Viewer WPF App](wpfapp/) | Developed with C# and [WPF](https://msdn.microsoft.com/en-us/library/ms754130(v=vs.100).aspx). | 15 | 16 | Screenshot of Win32 App: 17 | 18 | ![Screenshot image](win32app/ECGViewerScreenshot.png?raw=true "Title") 19 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/.gitignore: -------------------------------------------------------------------------------- 1 | old 2 | *.exe 3 | 4 | .svn/ 5 | TestData/ 6 | TestResults/ 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # DNX 51 | project.lock.json 52 | artifacts/ 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # NCrunch 120 | _NCrunch_* 121 | .*crunch*.local.xml 122 | nCrunchTemp_* 123 | 124 | # MightyMoose 125 | *.mm.* 126 | AutoTest.Net/ 127 | 128 | # Web workbench (sass) 129 | .sass-cache/ 130 | 131 | # Installshield output folder 132 | [Ee]xpress/ 133 | 134 | # DocProject is a documentation generator add-in 135 | DocProject/buildhelp/ 136 | DocProject/Help/*.HxT 137 | DocProject/Help/*.HxC 138 | DocProject/Help/*.hhc 139 | DocProject/Help/*.hhk 140 | DocProject/Help/*.hhp 141 | DocProject/Help/Html2 142 | DocProject/Help/html 143 | 144 | # Click-Once directory 145 | publish/ 146 | 147 | # Publish Web Output 148 | *.[Pp]ublish.xml 149 | *.azurePubxml 150 | # TODO: Comment the next line if you want to checkin your web deploy settings 151 | # but database connection strings (with potential passwords) will be unencrypted 152 | *.pubxml 153 | *.publishproj 154 | 155 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 156 | # checkin your Azure Web App publish settings, but sensitive information contained 157 | # in these scripts will be unencrypted 158 | PublishScripts/ 159 | 160 | # NuGet Packages 161 | *.nupkg 162 | # The packages folder can be ignored because of Package Restore 163 | **/packages/* 164 | # except build/, which is used as an MSBuild target. 165 | !**/packages/build/ 166 | # Uncomment if necessary however generally it will be regenerated when needed 167 | #!**/packages/repositories.config 168 | # NuGet v3's project.json files produces more ignoreable files 169 | *.nuget.props 170 | *.nuget.targets 171 | 172 | # Microsoft Azure Build Output 173 | csx/ 174 | *.build.csdef 175 | 176 | # Microsoft Azure Emulator 177 | ecf/ 178 | rcf/ 179 | 180 | # Windows Store app package directories and files 181 | AppPackages/ 182 | BundleArtifacts/ 183 | Package.StoreAssociation.xml 184 | _pkginfo.txt 185 | 186 | # Visual Studio cache files 187 | # files ending in .cache can be ignored 188 | *.[Cc]ache 189 | # but keep track of directories ending in .cache 190 | !*.[Cc]ache/ 191 | 192 | # Others 193 | ClientBin/ 194 | ~$* 195 | *~ 196 | *.dbmdl 197 | *.dbproj.schemaview 198 | *.pfx 199 | *.publishsettings 200 | node_modules/ 201 | orleans.codegen.cs 202 | 203 | # Since there are multiple workflows, uncomment next line to ignore bower_components 204 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 205 | #bower_components/ 206 | 207 | # RIA/Silverlight projects 208 | Generated_Code/ 209 | 210 | # Backup & report files from converting an old project file 211 | # to a newer Visual Studio version. Backup files are not needed, 212 | # because we have git ;-) 213 | _UpgradeReport_Files/ 214 | Backup*/ 215 | UpgradeLog*.XML 216 | UpgradeLog*.htm 217 | 218 | # SQL Server files 219 | *.mdf 220 | *.ldf 221 | 222 | # Business Intelligence projects 223 | *.rdl.data 224 | *.bim.layout 225 | *.bim_*.settings 226 | 227 | # Microsoft Fakes 228 | FakesAssemblies/ 229 | 230 | # GhostDoc plugin setting file 231 | *.GhostDoc.xml 232 | 233 | # Node.js Tools for Visual Studio 234 | .ntvs_analysis.dat 235 | 236 | # Visual Studio 6 build log 237 | *.plg 238 | 239 | # Visual Studio 6 workspace options file 240 | *.opt 241 | 242 | # Visual Studio LightSwitch build output 243 | **/*.HTMLClient/GeneratedArtifacts 244 | **/*.DesktopClient/GeneratedArtifacts 245 | **/*.DesktopClient/ModelManifest.xml 246 | **/*.Server/GeneratedArtifacts 247 | **/*.Server/ModelManifest.xml 248 | _Pvt_Extensions 249 | 250 | # Paket dependency manager 251 | .paket/paket.exe 252 | paket-files/ 253 | 254 | # FAKE - F# Make 255 | .fake/ 256 | 257 | # JetBrains Rider 258 | .idea/ 259 | *.sln.iml -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EPBeatAnalysis", "EPBeatAnalysis\EPBeatAnalysis.vcxproj", "{AD41A586-3E88-4FC5-A61B-9E84BC16C54A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Debug|x64.ActiveCfg = Debug|x64 17 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Debug|x64.Build.0 = Debug|x64 18 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Debug|x86.ActiveCfg = Debug|Win32 19 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Debug|x86.Build.0 = Debug|Win32 20 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Release|x64.ActiveCfg = Release|x64 21 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Release|x64.Build.0 = Release|x64 22 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Release|x86.ActiveCfg = Release|Win32 23 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPBeatAnalysis.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {AD41A586-3E88-4FC5-A61B-9E84BC16C54A} 49 | Win32Proj 50 | EPBeatAnalysis 51 | 8.1 52 | 53 | 54 | 55 | Application 56 | true 57 | v140 58 | Unicode 59 | 60 | 61 | Application 62 | false 63 | v140 64 | true 65 | Unicode 66 | 67 | 68 | Application 69 | true 70 | v140 71 | Unicode 72 | 73 | 74 | Application 75 | false 76 | v140 77 | true 78 | Unicode 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | true 100 | 101 | 102 | true 103 | 104 | 105 | false 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 113 | 114 | Level3 115 | Disabled 116 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 117 | 118 | 119 | Console 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | Level3 128 | Disabled 129 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 130 | 131 | 132 | Console 133 | true 134 | 135 | 136 | 137 | 138 | Level3 139 | 140 | 141 | MaxSpeed 142 | true 143 | true 144 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 145 | 146 | 147 | Console 148 | true 149 | true 150 | true 151 | 152 | 153 | 154 | 155 | Level3 156 | 157 | 158 | MaxSpeed 159 | true 160 | true 161 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 162 | 163 | 164 | Console 165 | true 166 | true 167 | true 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPBeatAnalysis.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/ANALBEAT.H: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: analbeat.h AUTHOR: Patrick S. Hamilton REVISED: 12/4/2001 ___________________________________________________________________________ analbeat.h: Beat analysis prototype definition. Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | ******************************************************************************/ 20 | 21 | // External prototypes for analbeat.cpp 22 | 23 | void AnalyzeBeat(int *beat, int *onset, int *offset, 24 | int *isoLevel, int *beatBegin, int *beatEnd, int *amp) ; 25 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/BDAC.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: bdac.cpp AUTHOR: Patrick S. Hamilton REVISED: 5/13/2002 ___________________________________________________________________________ bdac.cpp: Beat Detection And Classification Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | __________________________________________________________________________ 20 | 21 | bdac.cpp contains functions for handling Beat Detection And Classification. 22 | The primary function calls a qrs detector. When a beat is detected it waits 23 | until a sufficient number of samples from the beat have occurred. When the 24 | beat is ready, BeatDetectAndClassify passes the beat and the timing 25 | information on to the functions that actually classify the beat. 26 | 27 | Functions in bdac.cpp require functions in the following files: 28 | qrsfilt.cpp 29 | qrsdet.cpp 30 | classify.cpp 31 | rythmchk.cpp 32 | noisechk.cpp 33 | analbeat.cpp 34 | match.cpp 35 | postclas.cpp 36 | 37 | __________________________________________________________________________ 38 | 39 | Revisions: 40 | 5/13/02: 41 | Encapsulated down sampling from input stream to beat template in 42 | the function DownSampleBeat. 43 | 44 | Constants related to time are derived from SAMPLE_RATE in qrsdet 45 | and BEAT_SAMPLE_RATE in bcac.h. 46 | 47 | *******************************************************************************/ 48 | #include "qrsdet.h" // For base SAMPLE_RATE 49 | #include "bdac.h" 50 | 51 | #define ECG_BUFFER_LENGTH 1000 // Should be long enough for a beat 52 | // plus extra space to accommodate 53 | // the maximum detection delay. 54 | #define BEAT_QUE_LENGTH 10 // Length of que for beats awaiting 55 | // classification. Because of 56 | // detection delays, Multiple beats 57 | // can occur before there is enough data 58 | // to classify the first beat in the que. 59 | 60 | // Internal function prototypes. 61 | 62 | void DownSampleBeat(int *beatOut, int *beatIn) ; 63 | 64 | // External function prototypes. 65 | 66 | int QRSDet( int datum, int init ) ; 67 | int NoiseCheck(int datum, int delay, int RR, int beatBegin, int beatEnd) ; 68 | int Classify(int *newBeat,int rr, int noiseLevel, int *beatMatch, int *fidAdj, int init) ; 69 | int GetDominantType(void) ; 70 | int GetBeatEnd(int type) ; 71 | int GetBeatBegin(int type) ; 72 | int gcd(int x, int y) ; 73 | 74 | // Global Variables 75 | 76 | int ECGBuffer[ECG_BUFFER_LENGTH], ECGBufferIndex = 0 ; // Circular data buffer. 77 | int BeatBuffer[BEATLGTH] ; 78 | int BeatQue[BEAT_QUE_LENGTH], BeatQueCount = 0 ; // Buffer of detection delays. 79 | int RRCount = 0 ; 80 | int InitBeatFlag = 1 ; 81 | 82 | /****************************************************************************** 83 | ResetBDAC() resets static variables required for beat detection and 84 | classification. 85 | *******************************************************************************/ 86 | 87 | void ResetBDAC(void) 88 | { 89 | int dummy ; 90 | QRSDet(0,1) ; // Reset the qrs detector 91 | RRCount = 0 ; 92 | Classify(BeatBuffer,0,0,&dummy,&dummy,1) ; 93 | InitBeatFlag = 1 ; 94 | BeatQueCount = 0 ; // Flush the beat que. 95 | } 96 | 97 | /***************************************************************************** 98 | Syntax: 99 | int BeatDetectAndClassify(int ecgSample, int *beatType, *beatMatch) ; Description: BeatDetectAndClassify() implements a beat detector and classifier. ECG samples are passed into BeatDetectAndClassify() one sample at a time. BeatDetectAndClassify has been designed for a sample rate of 200 Hz. When a beat has been detected and classified the detection delay is returned and the beat classification is returned through the pointer *beatType. For use in debugging, the number of the template that the beat was matched to is returned in via *beatMatch. Returns BeatDetectAndClassify() returns 0 if no new beat has been detected and classified. If a beat has been classified, BeatDetectAndClassify returns the number of samples since the approximate location of the R-wave. ****************************************************************************/ int BeatDetectAndClassify(int ecgSample, int *beatType, int *beatMatch) 100 | { 101 | int detectDelay, rr, i, j ; 102 | int noiseEst = 0, beatBegin, beatEnd ; 103 | int domType ; 104 | int fidAdj ; 105 | int tempBeat[(SAMPLE_RATE/BEAT_SAMPLE_RATE)*BEATLGTH] ; 106 | 107 | // Store new sample in the circular buffer. 108 | 109 | ECGBuffer[ECGBufferIndex] = ecgSample ; 110 | if(++ECGBufferIndex == ECG_BUFFER_LENGTH) 111 | ECGBufferIndex = 0 ; 112 | 113 | // Increment RRInterval count. 114 | 115 | ++RRCount ; 116 | 117 | // Increment detection delays for any beats in the que. 118 | 119 | for(i = 0; i < BeatQueCount; ++i) 120 | ++BeatQue[i] ; 121 | 122 | // Run the sample through the QRS detector. 123 | 124 | detectDelay = QRSDet(ecgSample,0) ; 125 | if(detectDelay != 0) 126 | { 127 | BeatQue[BeatQueCount] = detectDelay ; 128 | ++BeatQueCount ; 129 | } 130 | 131 | // Return if no beat is ready for classification. 132 | 133 | if((BeatQue[0] < (BEATLGTH-FIDMARK)*(SAMPLE_RATE/BEAT_SAMPLE_RATE)) 134 | || (BeatQueCount == 0)) 135 | { 136 | NoiseCheck(ecgSample,0,rr, beatBegin, beatEnd) ; // Update noise check buffer 137 | return 0 ; 138 | } 139 | 140 | // Otherwise classify the beat at the head of the que. 141 | 142 | rr = RRCount - BeatQue[0] ; // Calculate the R-to-R interval 143 | detectDelay = RRCount = BeatQue[0] ; 144 | 145 | // Estimate low frequency noise in the beat. 146 | // Might want to move this into classify(). 147 | 148 | domType = GetDominantType() ; 149 | if(domType == -1) 150 | { 151 | beatBegin = MS250 ; 152 | beatEnd = MS300 ; 153 | } 154 | else 155 | { 156 | beatBegin = (SAMPLE_RATE/BEAT_SAMPLE_RATE)*(FIDMARK-GetBeatBegin(domType)) ; 157 | beatEnd = (SAMPLE_RATE/BEAT_SAMPLE_RATE)*(GetBeatEnd(domType)-FIDMARK) ; 158 | } 159 | noiseEst = NoiseCheck(ecgSample,detectDelay,rr,beatBegin,beatEnd) ; 160 | 161 | // Copy the beat from the circular buffer to the beat buffer 162 | // and reduce the sample rate by averageing pairs of data 163 | // points. 164 | 165 | j = ECGBufferIndex - detectDelay - (SAMPLE_RATE/BEAT_SAMPLE_RATE)*FIDMARK ; 166 | if(j < 0) j += ECG_BUFFER_LENGTH ; 167 | 168 | for(i = 0; i < (SAMPLE_RATE/BEAT_SAMPLE_RATE)*BEATLGTH; ++i) 169 | { 170 | tempBeat[i] = ECGBuffer[j] ; 171 | if(++j == ECG_BUFFER_LENGTH) 172 | j = 0 ; 173 | } 174 | 175 | DownSampleBeat(BeatBuffer,tempBeat) ; 176 | 177 | // Update the QUE. 178 | 179 | for(i = 0; i < BeatQueCount-1; ++i) 180 | BeatQue[i] = BeatQue[i+1] ; 181 | --BeatQueCount ; 182 | 183 | 184 | // Skip the first beat. 185 | 186 | if(InitBeatFlag) 187 | { InitBeatFlag = 0 ; *beatType = 13 ; *beatMatch = 0 ; fidAdj = 0 ; } // Classify all other beats. else { *beatType = Classify(BeatBuffer,rr,noiseEst,beatMatch,&fidAdj,0) ; 188 | fidAdj *= SAMPLE_RATE/BEAT_SAMPLE_RATE ; 189 | } 190 | 191 | // Ignore detection if the classifier decides that this 192 | // was the trailing edge of a PVC. 193 | 194 | if(*beatType == 100) 195 | { 196 | RRCount += rr ; 197 | return(0) ; 198 | } 199 | 200 | // Limit the fiducial mark adjustment in case of problems with 201 | // beat onset and offset estimation. 202 | 203 | if(fidAdj > MS80) 204 | fidAdj = MS80 ; 205 | else if(fidAdj < -MS80) 206 | fidAdj = -MS80 ; 207 | 208 | return(detectDelay-fidAdj) ; 209 | } 210 | 211 | void DownSampleBeat(int *beatOut, int *beatIn) 212 | { 213 | int i ; 214 | 215 | for(i = 0; i < BEATLGTH; ++i) 216 | beatOut[i] = (beatIn[i<<1]+beatIn[(i<<1)+1])>>1 ; 217 | } 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/BDAC.H: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: bdac.h AUTHOR: Patrick S. Hamilton REVISED: 9/25/2001 ___________________________________________________________________________ bdac.h: Beat detection and classification parameter definitions. Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | ******************************************************************************/ 20 | #define BEAT_SAMPLE_RATE 100 21 | #define BEAT_MS_PER_SAMPLE ( (double) 1000/ (double) BEAT_SAMPLE_RATE) 22 | 23 | #define BEAT_MS10 ((int) (10/BEAT_MS_PER_SAMPLE + 0.5)) 24 | #define BEAT_MS20 ((int) (20/BEAT_MS_PER_SAMPLE + 0.5)) 25 | #define BEAT_MS40 ((int) (40/BEAT_MS_PER_SAMPLE + 0.5)) 26 | #define BEAT_MS50 ((int) (50/BEAT_MS_PER_SAMPLE + 0.5)) 27 | #define BEAT_MS60 ((int) (60/BEAT_MS_PER_SAMPLE + 0.5)) 28 | #define BEAT_MS70 ((int) (70/BEAT_MS_PER_SAMPLE + 0.5)) 29 | #define BEAT_MS80 ((int) (80/BEAT_MS_PER_SAMPLE + 0.5)) 30 | #define BEAT_MS90 ((int) (90/BEAT_MS_PER_SAMPLE + 0.5)) 31 | #define BEAT_MS100 ((int) (100/BEAT_MS_PER_SAMPLE + 0.5)) 32 | #define BEAT_MS110 ((int) (110/BEAT_MS_PER_SAMPLE + 0.5)) 33 | #define BEAT_MS130 ((int) (130/BEAT_MS_PER_SAMPLE + 0.5)) 34 | #define BEAT_MS140 ((int) (140/BEAT_MS_PER_SAMPLE + 0.5)) 35 | #define BEAT_MS150 ((int) (150/BEAT_MS_PER_SAMPLE + 0.5)) 36 | #define BEAT_MS250 ((int) (250/BEAT_MS_PER_SAMPLE + 0.5)) 37 | #define BEAT_MS280 ((int) (280/BEAT_MS_PER_SAMPLE + 0.5)) 38 | #define BEAT_MS300 ((int) (300/BEAT_MS_PER_SAMPLE + 0.5)) 39 | #define BEAT_MS350 ((int) (350/BEAT_MS_PER_SAMPLE + 0.5)) 40 | #define BEAT_MS400 ((int) (400/BEAT_MS_PER_SAMPLE + 0.5)) 41 | #define BEAT_MS1000 BEAT_SAMPLE_RATE 42 | 43 | #define BEATLGTH BEAT_MS1000 44 | #define MAXTYPES 8 45 | #define FIDMARK BEAT_MS400 46 | 47 | 48 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/EASYTEST.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: easytest.cpp 3 | AUTHOR: Patrick S. Hamilton 4 | REVISED: 5/13/2002 5 | ___________________________________________________________________________ 6 | 7 | easytest.cpp: Use bdac to generate an annotation file. 8 | Copywrite (C) 2001 Patrick S. Hamilton 9 | Copywrite (C) 1999 George B. Moody 10 | 11 | This file is free software; you can redistribute it and/or modify it under 12 | the terms of the GNU Library General Public License as published by the Free 13 | Software Foundation; either version 2 of the License, or (at your option) any 14 | later version. 15 | 16 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 17 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 18 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 19 | details. 20 | 21 | You should have received a copy of the GNU Library General Public License along 22 | with this library; if not, write to the Free Software Foundation, Inc., 59 23 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 24 | 25 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 26 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 27 | MA 02143 USA). For updates to this software, please visit our website 28 | (http://www.eplimited.com). 29 | __________________________________________________________________________ 30 | 31 | Easytest.exe is a simple program to help test the performance of our 32 | beat detection and classification software. Data is read from the 33 | indicated ECG file, the channel 1 signal is fed to bdac.c, and the 34 | resulting detections are saved in the annotation file .ate. 35 | .ate may then be compared to .atr to using bxb to 36 | analyze the performance of the the beat detector and classifier detector. 37 | 38 | Note that data in the MIT/BIH Arrythmia database file has been sampled 39 | at 360 samples-per-second, but the beat detection and classification 40 | software has been written for data sampled at 200 samples-per-second. 41 | Date is converterted from 360 sps to 200 sps with the function NextSample. 42 | Code for resampling was copied from George Moody's xform utility. The beat 43 | locations are then adjusted back to coincide with the original sample 44 | rate of 360 samples/second so that the annotation files generated by 45 | easytest can be compared to the "atruth" annotation files. 46 | 47 | This file must be linked with object files produced from: 48 | wfdb software library (source available at www.physionet.org) 49 | analbeat.cpp 50 | match.cpp 51 | rythmchk.cpp 52 | classify.cpp 53 | bdac.cpp 54 | qrsfilt.cpp 55 | qrsdet.cpp 56 | __________________________________________________________________________ 57 | 58 | Revisions 59 | 4/13/02: 60 | Added conditional define statements that allow MIT/BIH or AHA 61 | records to be processed. 62 | Normalize input to 5 mV/LSB (200 A-to-D units/mV). 63 | 64 | *******************************************************************************/ 65 | 66 | #include "wfdb.h" 67 | #include "ecgcodes.h" 68 | #include "ecgmap.h" 69 | #include "stdio.h" 70 | #include "qrsdet.h" // For sample rate. 71 | 72 | #define MITDB // Comment this line out to process AHA data. 73 | #ifdef MITDB 74 | #define ECG_DB_PATH "C:\\MITDB\\" // Path to where MIT/BIH data. 75 | #define REC_COUNT 48 76 | int Records[REC_COUNT] = {100,101,102,103,104,105,106,107,108,109,111,112, 77 | 113,114,115,116,117,118,119,121,122,123,124, 78 | 200,201,202,203,205,207,208,209,210,212,213,214, 79 | 215,217,219,220,221,222,223,228,230,231,232,233,234} ; 80 | 81 | #else 82 | #define ECG_DB_PATH "C:\\AHADAT~1\\" // Path to where AHA data. 83 | #define REC_COUNT 69 84 | int Records[REC_COUNT] = {1201,1202,1203,1204,1205,1206,1207,1208,1209,1210, 85 | 2201,2203,2204,2205,2206,2207,2208,2209,2210, 86 | 3201,3202,3203,3204,3205,3206,3207,3208,3209,3210, 87 | 4201,4202,4203,4204,4205,4206,4207,4208,4209,4210, 88 | 5201,5202,5203,5204,5205,5206,5207,5208,5209,5210, 89 | 6201,6202,6203,6204,6205,6206,6207,6208,6209,6210, 90 | 7201,7202,7203,7204,7205,7206,7207,7208,7209,7210} ; 91 | #endif 92 | // External function prototypes. 93 | void ResetBDAC(void) ; 94 | int BeatDetectAndClassify(int ecgSample, int *beatType, int *beatMatch) ; 95 | 96 | // Local Prototypes. 97 | int NextSample(int *vout,int nosig,int ifreq, 98 | int ofreq,int init) ; 99 | int gcd(int x, int y); 100 | 101 | // Global variables. 102 | 103 | int ADCZero, ADCUnit, InputFileSampleFrequency ; 104 | 105 | void main() 106 | { 107 | char record[10], fname[20] ; 108 | int i, ecg[2], delay, recNum ; 109 | WFDB_Siginfo s[2] ; 110 | WFDB_Anninfo a[2] ; 111 | WFDB_Annotation annot ; 112 | 113 | unsigned char byte ; 114 | FILE *newAnn0, *newAnn1 ; 115 | long SampleCount = 0, lTemp, DetectionTime ; 116 | int beatType, beatMatch ; 117 | 118 | // Set up path to database directory 119 | 120 | setwfdb(ECG_DB_PATH) ; 121 | 122 | // Analyze all 48 MIT/BIH Records. 123 | 124 | for(recNum = 0; recNum < REC_COUNT; ++recNum) 125 | { 126 | sprintf(record,"%d",Records[recNum]) ; 127 | printf("Record %d\n",Records[recNum]) ; 128 | 129 | // Open a 2 channel record 130 | 131 | if(isigopen(record,s,2) < 1) 132 | { 133 | printf("Couldn't open %s\n",record) ; 134 | return ; 135 | } 136 | 137 | ADCZero = s[0].adczero ; 138 | ADCUnit = s[0].gain ; 139 | InputFileSampleFrequency = sampfreq(record) ; 140 | 141 | // Setup for output annotations 142 | 143 | a[0].name = "atest"; a[0].stat = WFDB_WRITE ; 144 | 145 | if(annopen(record, a, 1) < 0) 146 | return ; 147 | 148 | // Initialize sampling frequency adjustment. 149 | 150 | NextSample(ecg,2,InputFileSampleFrequency,SAMPLE_RATE,1) ; 151 | 152 | // Initialize beat detection and classification. 153 | 154 | ResetBDAC() ; 155 | SampleCount = 0 ; 156 | 157 | // Read data from MIT/BIH file until there is none left. 158 | 159 | while(NextSample(ecg,2,InputFileSampleFrequency,SAMPLE_RATE,0) >= 0) 160 | { 161 | ++SampleCount ; 162 | 163 | // Set baseline to 0 and resolution to 5 mV/lsb (200 units/mV) 164 | 165 | lTemp = ecg[0]-ADCZero ; 166 | lTemp *= 200 ; lTemp /= ADCUnit ; ecg[0] = lTemp ; 167 | 168 | // Pass sample to beat detection and classification. 169 | 170 | delay = BeatDetectAndClassify(ecg[0], &beatType, &beatMatch) ; 171 | 172 | // If a beat was detected, annotate the beat location 173 | // and type. 174 | 175 | if(delay != 0) 176 | { 177 | DetectionTime = SampleCount - delay ; 178 | 179 | // Convert sample count to input file sample 180 | // rate. 181 | 182 | DetectionTime *= InputFileSampleFrequency ; 183 | DetectionTime /= SAMPLE_RATE ; 184 | annot.time = DetectionTime ; 185 | annot.anntyp = beatType ; 186 | annot.aux = NULL ; 187 | putann(0,&annot) ; 188 | } 189 | } 190 | 191 | // Reset database after record is done. 192 | 193 | wfdbquit() ; 194 | 195 | // Copy "atest." to ".ate" for future ascess. 196 | // (This is necessary for PC files) 197 | 198 | sprintf(fname,"%s.ate",record) ; 199 | newAnn0 = fopen(fname,"rb") ; 200 | sprintf(fname,"%s%s.ate",ECG_DB_PATH,record) ; 201 | newAnn1 = fopen(fname,"wb") ; 202 | 203 | // Copy byte image of annotation file in this 204 | // directory to a correctly named file in the 205 | // database directory. 206 | 207 | while(fread(&byte,sizeof(char),1,newAnn0) == 1) 208 | fwrite(&byte,sizeof(char),1,newAnn1) ; 209 | 210 | fclose(newAnn0) ; 211 | fclose(newAnn1) ; 212 | } 213 | } 214 | 215 | /********************************************************************** 216 | NextSample reads MIT/BIH Arrhythmia data from a file of data 217 | sampled at ifreq and returns data sampled at ofreq. Data is 218 | returned in vout via *vout. NextSample must be initialized by 219 | passing in a nonzero value in init. NextSample returns -1 when 220 | there is no more data left. 221 | ***********************************************************************/ 222 | 223 | int NextSample(int *vout,int nosig,int ifreq, 224 | int ofreq,int init) 225 | { 226 | int i ; 227 | static int m, n, mn, ot, it, vv[WFDB_MAXSIG], v[WFDB_MAXSIG], rval ; 228 | 229 | if(init) 230 | { 231 | i = gcd(ifreq, ofreq); 232 | m = ifreq/i; 233 | n = ofreq/i; 234 | mn = m*n; 235 | ot = it = 0 ; 236 | getvec(vv) ; 237 | rval = getvec(v) ; 238 | } 239 | 240 | else 241 | { 242 | while(ot > it) 243 | { 244 | for(i = 0; i < nosig; ++i) 245 | vv[i] = v[i] ; 246 | rval = getvec(v) ; 247 | if (it > mn) { it -= mn; ot -= mn; } 248 | it += n; 249 | } 250 | for(i = 0; i < nosig; ++i) 251 | vout[i] = vv[i] + (ot%n)*(v[i]-vv[i])/n; 252 | ot += m; 253 | } 254 | 255 | return(rval) ; 256 | } 257 | 258 | // Greatest common divisor of x and y (Euclid's algorithm) 259 | 260 | int gcd(int x, int y) 261 | { 262 | while (x != y) { 263 | if (x > y) x-=y; 264 | else y -= x; 265 | } 266 | return (x); 267 | } 268 | 269 | 270 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/ECGCODES.H: -------------------------------------------------------------------------------- 1 | /* file: ecgcodes.h T. Baker and G. Moody June 1981 2 | Last revised: 19 March 1992 dblib 7.0 3 | ECG annotation codes 4 | 5 | Copyright (C) Massachusetts Institute of Technology 1992. All rights reserved. 6 | */ 7 | 8 | #ifndef db_ECGCODES_H /* avoid multiple definitions */ 9 | #define db_ECGCODES_H 10 | 11 | #define NOTQRS 0 /* not-QRS (not a getann/putann code) */ 12 | #define NORMAL 1 /* normal beat */ 13 | #define LBBB 2 /* left bundle branch block beat */ 14 | #define RBBB 3 /* right bundle branch block beat */ 15 | #define ABERR 4 /* aberrated atrial premature beat */ 16 | #define PVC 5 /* premature ventricular contraction */ 17 | #define FUSION 6 /* fusion of ventricular and normal beat */ 18 | #define NPC 7 /* nodal (junctional) premature beat */ 19 | #define APC 8 /* atrial premature contraction */ 20 | #define SVPB 9 /* premature or ectopic supraventricular beat */ 21 | #define VESC 10 /* ventricular escape beat */ 22 | #define NESC 11 /* nodal (junctional) escape beat */ 23 | #define PACE 12 /* paced beat */ 24 | #define UNKNOWN 13 /* unclassifiable beat */ 25 | #define NOISE 14 /* signal quality change */ 26 | #define ARFCT 16 /* isolated QRS-like artifact */ 27 | #define STCH 18 /* ST change */ 28 | #define TCH 19 /* T-wave change */ 29 | #define SYSTOLE 20 /* systole */ 30 | #define DIASTOLE 21 /* diastole */ 31 | #define NOTE 22 /* comment annotation */ 32 | #define MEASURE 23 /* measurement annotation */ 33 | #define BBB 25 /* left or right bundle branch block */ 34 | #define PACESP 26 /* non-conducted pacer spike */ 35 | #define RHYTHM 28 /* rhythm change */ 36 | #define LEARN 30 /* learning */ 37 | #define FLWAV 31 /* ventricular flutter wave */ 38 | #define VFON 32 /* start of ventricular flutter/fibrillation */ 39 | #define VFOFF 33 /* end of ventricular flutter/fibrillation */ 40 | #define AESC 34 /* atrial escape beat */ 41 | #define SVESC 35 /* supraventricular escape beat */ 42 | #define NAPC 37 /* non-conducted P-wave (blocked APB) */ 43 | #define PFUS 38 /* fusion of paced and normal beat */ 44 | #define PQ 39 /* PQ junction (beginning of QRS) */ 45 | #define JPT 40 /* J point (end of QRS) */ 46 | #define RONT 41 /* R-on-T premature ventricular contraction */ 47 | 48 | /* ... annotation codes between RONT+1 and ACMAX inclusive are user-defined */ 49 | 50 | #define ACMAX 49 /* value of largest valid annot code (must be < 50) */ 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/ECGMAP.H: -------------------------------------------------------------------------------- 1 | /* file: ecgmap.h G. Moody 8 June 1983 2 | Last revised: 4 May 1999 wfdblib 10.0.0 3 | ECG annotation code mapping macros 4 | 5 | _______________________________________________________________________________ 6 | wfdb: a library for reading and writing annotated waveforms (time series data) 7 | Copyright (C) 1999 George B. Moody 8 | 9 | This library is free software; you can redistribute it and/or modify it under 10 | the terms of the GNU Library General Public License as published by the Free 11 | Software Foundation; either version 2 of the License, or (at your option) any 12 | later version. 13 | 14 | This library is distributed in the hope that it will be useful, but WITHOUT ANY 15 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 16 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 17 | details. 18 | 19 | You should have received a copy of the GNU Library General Public License along 20 | with this library; if not, write to the Free Software Foundation, Inc., 59 21 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | 23 | You may contact the author by e-mail (george@mit.edu) or postal mail 24 | (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, 25 | please visit PhysioNet (http://www.physionet.org/). 26 | _______________________________________________________________________________ 27 | 28 | These macros evaluate their arguments only once, so that they behave like 29 | functions with respect to side-effects (e.g., `isqrs(x++)' is safe). With 30 | the exception of isann(), each macro uses a table; to avoid wasting space 31 | in programs compiled from more than one source, try to keep all references 32 | to these macros in a single source file so that multiple instances of the 33 | tables are not required. To save even more space, simply define the unneeded 34 | macros before including this file (e.g., `#define map1').*/ 35 | 36 | #ifndef wfdb_ECGMAP_H /* avoid multiple definitions */ 37 | #define wfdb_ECGMAP_H 38 | 39 | #ifndef wfdb_ECGCODES_H 40 | #include "ecgcodes.h" 41 | #endif 42 | 43 | /* isann(A) is true if A is a legal annotation code, false otherwise */ 44 | #define isann(A) (0<(wfdb_mt=(A)) && wfdb_mt<=ACMAX) 45 | static int wfdb_mt; /* macro temporary variable */ 46 | 47 | /* isqrs(A) is true (1) if A denotes a QRS complex, false (0) otherwise */ 48 | #ifndef isqrs 49 | #define isqrs(A) (isann(A) ? wfdb_qrs[wfdb_mt] : 0) 50 | #define setisqrs(A, X) (isann(A) ? (wfdb_qrs[wfdb_mt] = (X)) : 0) 51 | static char wfdb_qrs[] = { 52 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0 - 9 */ 53 | 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 10 - 19 */ 54 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, /* 20 - 29 */ 55 | 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, /* 30 - 39 */ 56 | 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 /* 40 - 49 */ 57 | }; 58 | #endif 59 | 60 | /* map1(A) maps A into one of {NOTQRS, NORMAL, PVC, FUSION, LEARN} */ 61 | #ifndef map1 62 | #define map1(A) (isann(A) ? wfdb_mp1[wfdb_mt] : NOTQRS) 63 | #define setmap1(A, X) (isann(A) ? (wfdb_mp1[wfdb_mt] = (X)) : NOTQRS) 64 | static char wfdb_mp1[] = { 65 | NOTQRS, NORMAL, NORMAL, NORMAL, NORMAL, /* 0 - 4 */ 66 | PVC, FUSION, NORMAL, NORMAL, NORMAL, /* 5 - 9 */ 67 | PVC, NORMAL, NORMAL, NORMAL, NOTQRS, /* 10 - 14 */ 68 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 15 - 19 */ 69 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 20 - 24 */ 70 | NORMAL, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 25 - 29 */ 71 | LEARN, PVC, NOTQRS, NOTQRS, NORMAL, /* 30 - 34 */ 72 | NORMAL, NOTQRS, NOTQRS, NORMAL, NOTQRS, /* 35 - 39 */ 73 | NOTQRS, PVC, NOTQRS, NOTQRS, NOTQRS, /* 40 - 44 */ 74 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS /* 45 - 49 */ 75 | }; 76 | #endif 77 | 78 | /* map2(A) maps A into one of {NOTQRS, NORMAL, SVPB, PVC, FUSION, LEARN} */ 79 | #ifndef map2 80 | #define map2(A) (isann(A) ? wfdb_mp2[wfdb_mt] : NOTQRS) 81 | #define setmap2(A, X) (isann(A) ? (wfdb_mp2[wfdb_mt] = (X)) : NOTQRS) 82 | static char wfdb_mp2[] = { 83 | NOTQRS, NORMAL, NORMAL, NORMAL, SVPB, /* 0 - 4 */ 84 | PVC, FUSION, SVPB, SVPB, SVPB, /* 5 - 9 */ 85 | PVC, NORMAL, NORMAL, NORMAL, NOTQRS, /* 10 - 14 */ 86 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 15 - 19 */ 87 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 20 - 24 */ 88 | NORMAL, NOTQRS, NOTQRS, NOTQRS, NOTQRS, /* 25 - 29 */ 89 | LEARN, PVC, NOTQRS, NOTQRS, NORMAL, /* 30 - 34 */ 90 | NORMAL, NOTQRS, NOTQRS, NORMAL, NOTQRS, /* 35 - 39 */ 91 | NOTQRS, PVC, NOTQRS, NOTQRS, NOTQRS, /* 40 - 44 */ 92 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NOTQRS /* 45 - 49 */ 93 | }; 94 | #endif 95 | 96 | /* ammap(A) maps an AHA annotation code, A, into an MIT annotation code */ 97 | #ifndef ammap 98 | #define ammap(A) (('D' < (wfdb_mt = (A)) && wfdb_mt <= ']') ? \ 99 | wfdb_ammp[wfdb_mt-'E'] : NOTQRS) 100 | static char wfdb_ammp[] = { 101 | VESC, FUSION, NOTQRS, NOTQRS, NOTQRS, /* 'E' - 'I' */ 102 | NOTQRS, NOTQRS, NOTQRS, NOTQRS, NORMAL, /* 'J' - 'N' */ 103 | NOTE, PACE, UNKNOWN,RONT, NOTQRS, /* 'O' - 'S' */ 104 | NOTQRS, NOISE, PVC, NOTQRS, NOTQRS, /* 'T' - 'X' */ 105 | NOTQRS, NOTQRS, VFON, NOTQRS, VFOFF /* 'Y' - ']' */ 106 | }; 107 | #endif 108 | 109 | /* mamap(A,S) maps MIT code A, subtype S, into an AHA annotation code */ 110 | #ifndef mamap 111 | #define mamap(A,S) (isann(A) ? \ 112 | (((wfdb_mt = wfdb_mamp[wfdb_mt]) == 'U' && (S) != -1) ? \ 113 | 'O': wfdb_mt) : 'O') 114 | static char wfdb_mamp[] = { 115 | 'O', 'N', 'N', 'N', 'N', /* 0 - 4 */ 116 | 'V', 'F', 'N', 'N', 'N', /* 5 - 9 */ 117 | 'E', 'N', 'P', 'Q', 'U', /* 10 - 14 */ 118 | 'O', 'O', 'O', 'O', 'O', /* 15 - 19 */ 119 | 'O', 'O', 'O', 'O', 'O', /* 20 - 24 */ 120 | 'N', 'O', 'O', 'O', 'O', /* 25 - 29 */ 121 | 'Q', 'O', '[', ']', 'N', /* 30 - 34 */ 122 | 'N', 'O', 'O', 'N', 'O', /* 35 - 39 */ 123 | 'O', 'R', 'O', 'O', 'O', /* 40 - 44 */ 124 | 'O', 'O', 'O', 'O', 'O' /* 45 - 49 */ 125 | }; 126 | #endif 127 | 128 | /* Annotation position codes. These may be used by applications which plot 129 | signals and annotations to determine where to print annotation mnemonics. */ 130 | #define APUNDEF 0 /* for undefined annotation types */ 131 | #define APSTD 1 /* standard position */ 132 | #define APHIGH 2 /* a level above APSTD */ 133 | #define APLOW 3 /* a level below APSTD */ 134 | #define APATT 4 /* attached to the signal specified by `chan' */ 135 | #define APAHIGH 5 /* a level above APATT */ 136 | #define APALOW 6 /* a level below APATT */ 137 | 138 | /* annpos(A) returns the appropriate position code for A */ 139 | #ifndef annpos 140 | #define annpos(A) (isann(A) ? wfdb_annp[wfdb_mt] : APUNDEF) 141 | #define setannpos(A, X) (isann(A) ? (wfdb_annp[wfdb_mt] = (X)) : APUNDEF) 142 | static char wfdb_annp[] = { 143 | APUNDEF,APSTD, APSTD, APSTD, APSTD, /* 0 - 4 */ 144 | APSTD, APSTD, APSTD, APSTD, APSTD, /* 5 - 9 */ 145 | APSTD, APSTD, APSTD, APSTD, APHIGH, /* 10 - 14 */ 146 | APUNDEF,APHIGH, APUNDEF,APHIGH, APHIGH, /* 15 - 19 */ 147 | APHIGH, APHIGH, APHIGH, APHIGH, APHIGH, /* 20 - 24 */ 148 | APSTD, APHIGH, APHIGH, APLOW, APHIGH, /* 25 - 29 */ 149 | APSTD, APSTD, APSTD, APSTD, APSTD, /* 30 - 34 */ 150 | APSTD, APHIGH, APHIGH, APSTD, APHIGH, /* 35 - 39 */ 151 | APHIGH, APSTD, APUNDEF,APUNDEF,APUNDEF, /* 40 - 44 */ 152 | APUNDEF,APUNDEF,APUNDEF,APUNDEF,APUNDEF /* 45 - 49 */ 153 | }; 154 | #endif 155 | 156 | #endif 157 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/MATCH.H: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: match.h AUTHOR: Patrick S. Hamilton REVISED: 12/4/2001 ___________________________________________________________________________ match.h: Beat matching prototype definitions. Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | ******************************************************************************/ 20 | 21 | int NewBeatType(int *beat) ; 22 | void BestMorphMatch(int *newBeat,int *matchType,double *matchIndex, double *mi2, int *shiftAdj) ; 23 | void UpdateBeatType(int matchType,int *newBeat, double mi2, int shiftAdj) ; 24 | int GetTypesCount(void) ; 25 | int GetBeatTypeCount(int type) ; 26 | int IsTypeIsolated(int type) ; 27 | void SetBeatClass(int type, int beatClass) ; 28 | int GetBeatClass(int type) ; 29 | int GetDominantType(void) ; 30 | int GetBeatWidth(int type) ; 31 | int GetPolarity(int type) ; 32 | int GetRhythmIndex(int type) ; 33 | void ResetMatch(void) ; 34 | void ClearLastNewType(void) ; 35 | int GetBeatBegin(int type) ; 36 | int GetBeatEnd(int type) ; 37 | int GetBeatAmp(int type) ; 38 | int MinimumBeatVariation(int type) ; 39 | int GetBeatCenter(int type) ; 40 | int WideBeatVariation(int type) ; 41 | double DomCompare2(int *newBeat, int domType) ; 42 | double DomCompare(int newType, int domType) ; 43 | 44 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/NOISECHK.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: noisechk.cpp AUTHOR: Patrick S. Hamilton REVISED: 5/13/2002 ___________________________________________________________________________ noisechk.cpp: Noise Check Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | __________________________________________________________________________ 20 | 21 | This file contains functions for evaluating the noise content of a beat. 22 | 23 | *****************************************************************************/ 24 | 25 | #include 26 | #include "qrsdet.h" 27 | 28 | #define NB_LENGTH MS1500 29 | #define NS_LENGTH MS50 30 | 31 | int NoiseBuffer[NB_LENGTH], NBPtr = 0 ; 32 | int NoiseEstimate ; 33 | 34 | /************************************************************************ 35 | GetNoiseEstimate() allows external access the present noise estimate. 36 | this function is only used for debugging. 37 | *************************************************************************/ 38 | 39 | int GetNoiseEstimate() 40 | { 41 | return(NoiseEstimate) ; 42 | } 43 | 44 | /*********************************************************************** 45 | NoiseCheck() must be called for every sample of data. The data is 46 | stored in a circular buffer to facilitate noise analysis. When a 47 | beat is detected NoiseCheck() is passed the sample delay since the 48 | R-wave of the beat occurred (delay), the RR interval between this 49 | beat and the next most recent beat, the estimated offset from the 50 | R-wave to the beginning of the beat (beatBegin), and the estimated 51 | offset from the R-wave to the end of the beat. 52 | 53 | NoiseCheck() estimates the noise in the beat by the maximum and 54 | minimum signal values in either a window from the end of the 55 | previous beat to the beginning of the present beat, or a 250 ms 56 | window preceding the present beat, which ever is shorter. 57 | 58 | NoiseCheck() returns ratio of the signal variation in the window 59 | between beats to the length of the window between the beats. If 60 | the heart rate is too high and the beat durations are too long, 61 | NoiseCheck() returns 0. 62 | 63 | ***********************************************************************/ 64 | 65 | int NoiseCheck(int datum, int delay, int RR, int beatBegin, int beatEnd) 66 | { 67 | int ptr, i; 68 | int ncStart, ncEnd, ncMax, ncMin ; 69 | double noiseIndex ; 70 | 71 | NoiseBuffer[NBPtr] = datum ; 72 | if(++NBPtr == NB_LENGTH) 73 | NBPtr = 0 ; 74 | 75 | // Check for noise in region that is 300 ms following 76 | // last R-wave and 250 ms preceding present R-wave. 77 | 78 | ncStart = delay+RR-beatEnd ; // Calculate offset to end of previous beat. 79 | ncEnd = delay+beatBegin ; // Calculate offset to beginning of this beat. 80 | if(ncStart > ncEnd + MS250) 81 | ncStart = ncEnd + MS250 ; 82 | 83 | 84 | // Estimate noise if delay indicates a beat has been detected, 85 | // the delay is not to long for the data buffer, and there is 86 | // some space between the end of the last beat and the beginning 87 | // of this beat. 88 | 89 | if((delay != 0) && (ncStart < NB_LENGTH) && (ncStart > ncEnd)) 90 | { 91 | 92 | ptr = NBPtr - ncStart ; // Find index to end of last beat in 93 | if(ptr < 0) // the circular buffer. 94 | ptr += NB_LENGTH ; 95 | 96 | // Find the maximum and minimum values in the 97 | // isoelectric region between beats. 98 | 99 | ncMax = ncMin = NoiseBuffer[ptr] ; 100 | for(i = 0; i < ncStart-ncEnd; ++i) 101 | { 102 | if(NoiseBuffer[ptr] > ncMax) 103 | ncMax = NoiseBuffer[ptr] ; 104 | else if(NoiseBuffer[ptr] < ncMin) 105 | ncMin = NoiseBuffer[ptr] ; 106 | if(++ptr == NB_LENGTH) 107 | ptr = 0 ; 108 | } 109 | 110 | // The noise index is the ratio of the signal variation 111 | // over the isoelectric window length, scaled by 10. 112 | 113 | noiseIndex = (ncMax-ncMin) ; 114 | noiseIndex /= (ncStart-ncEnd) ; 115 | NoiseEstimate = noiseIndex * 10 ; 116 | } 117 | else 118 | NoiseEstimate = 0 ; 119 | return(NoiseEstimate) ; 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/POSTCLAS.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: postclas.cpp AUTHOR: Patrick S. Hamilton REVISED: 5/13/2002 ___________________________________________________________________________ postclas.cpp: Post classifier Copywrite (C) 2002 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This software 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact the author by e-mail (pat@eplimited.edu) or postal mail (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, MA 02143 USA). For updates to this software, please visit our website (http://www.eplimited.com). __________________________________________________________________________ This file contains functions for classifying beats based after the following beat is detected. ResetPostClassify() -- Resets static variables used by PostClassify() PostClassify() -- classifies each beat based on six preceding beats and the following beat. CheckPostClass() -- classifys beat type based on the last eight post classifications of that beat. CheckPCRhythm() -- returns the classification of the RR interval for this type of beat based its previous eight RR intervals. ****************************************************************/ 3 | 4 | #include "bdac.h" 5 | #include "ecgcodes.h" 6 | 7 | // External Prototypes. 8 | 9 | double DomCompare(int newType, int domType) ; 10 | int GetBeatTypeCount(int type) ; 11 | 12 | // Records of post classifications. 13 | 14 | int PostClass[MAXTYPES][8], PCInitCount = 0 ; 15 | int PCRhythm[MAXTYPES][8] ; 16 | 17 | /********************************************************************** 18 | Resets post classifications for beats. 19 | **********************************************************************/ 20 | 21 | void ResetPostClassify() 22 | { 23 | int i, j ; 24 | for(i = 0; i < MAXTYPES; ++i) 25 | for(j = 0; j < 8; ++j) 26 | { 27 | PostClass[i][j] = 0 ; 28 | PCRhythm[i][j] = 0 ; 29 | } 30 | PCInitCount = 0 ; 31 | } 32 | 33 | /*********************************************************************** 34 | Classify the previous beat type and rhythm type based on this beat 35 | and the preceding beat. This classifier is more sensitive 36 | to detecting premature beats followed by compensitory pauses. 37 | ************************************************************************/ 38 | 39 | void PostClassify(int *recentTypes, int domType, int *recentRRs, int width, double mi2, 40 | int rhythmClass) 41 | { 42 | static int lastRC, lastWidth ; 43 | static double lastMI2 ; 44 | int i, regCount, pvcCount, normRR ; 45 | double mi3 ; 46 | 47 | // If the preceeding and following beats are the same type, 48 | // they are generally regular, and reasonably close in shape 49 | // to the dominant type, consider them to be dominant. 50 | 51 | if((recentTypes[0] == recentTypes[2]) && (recentTypes[0] != domType) 52 | && (recentTypes[0] != recentTypes[1])) 53 | { 54 | mi3 = DomCompare(recentTypes[0],domType) ; 55 | for(i = regCount = 0; i < 8; ++i) 56 | if(PCRhythm[recentTypes[0]][i] == NORMAL) 57 | ++regCount ; 58 | if((mi3 < 2.0) && (regCount > 6)) 59 | domType = recentTypes[0] ; 60 | } 61 | 62 | // Don't do anything until four beats have gone by. 63 | 64 | if(PCInitCount < 3) 65 | { 66 | ++PCInitCount ; 67 | lastWidth = width ; 68 | lastMI2 = 0 ; 69 | lastRC = 0 ; 70 | return ; 71 | } 72 | 73 | if(recentTypes[1] < MAXTYPES) 74 | { 75 | 76 | // Find first NN interval. 77 | for(i = 2; (i < 7) && (recentTypes[i] != recentTypes[i+1]); ++i) ; 78 | if(i == 7) normRR = 0 ; 79 | else normRR = recentRRs[i] ; 80 | 81 | // Shift the previous beat classifications to make room for the 82 | // new classification. 83 | for(i = pvcCount = 0; i < 8; ++i) 84 | if(PostClass[recentTypes[1]][i] == PVC) 85 | ++pvcCount ; 86 | 87 | for(i = 7; i > 0; --i) 88 | { 89 | PostClass[recentTypes[1]][i] = PostClass[recentTypes[1]][i-1] ; 90 | PCRhythm[recentTypes[1]][i] = PCRhythm[recentTypes[1]][i-1] ; 91 | } 92 | 93 | // If the beat is premature followed by a compensitory pause and the 94 | // previous and following beats are normal, post classify as 95 | // a PVC. 96 | 97 | if(((normRR-(normRR>>3)) >= recentRRs[1]) && ((recentRRs[0]-(recentRRs[0]>>3)) >= normRR)// && (lastMI2 > 3) 98 | && (recentTypes[0] == domType) && (recentTypes[2] == domType) 99 | && (recentTypes[1] != domType)) 100 | PostClass[recentTypes[1]][0] = PVC ; 101 | 102 | // If previous two were classified as PVCs, and this is at least slightly 103 | // premature, classify as a PVC. 104 | 105 | else if(((normRR-(normRR>>4)) > recentRRs[1]) && ((normRR+(normRR>>4)) < recentRRs[0]) && 106 | (((PostClass[recentTypes[1]][1] == PVC) && (PostClass[recentTypes[1]][2] == PVC)) || 107 | (pvcCount >= 6) ) && 108 | (recentTypes[0] == domType) && (recentTypes[2] == domType) && (recentTypes[1] != domType)) 109 | PostClass[recentTypes[1]][0] = PVC ; 110 | 111 | // If the previous and following beats are the dominant beat type, 112 | // and this beat is significantly different from the dominant, 113 | // call it a PVC. 114 | 115 | else if((recentTypes[0] == domType) && (recentTypes[2] == domType) && (lastMI2 > 2.5)) 116 | PostClass[recentTypes[1]][0] = PVC ; 117 | 118 | // Otherwise post classify this beat as UNKNOWN. 119 | 120 | else PostClass[recentTypes[1]][0] = UNKNOWN ; 121 | 122 | // If the beat is premature followed by a compensitory pause, post 123 | // classify the rhythm as PVC. 124 | 125 | if(((normRR-(normRR>>3)) > recentRRs[1]) && ((recentRRs[0]-(recentRRs[0]>>3)) > normRR)) 126 | PCRhythm[recentTypes[1]][0] = PVC ; 127 | 128 | // Otherwise, post classify the rhythm as the same as the 129 | // regular rhythm classification. 130 | 131 | else PCRhythm[recentTypes[1]][0] = lastRC ; 132 | } 133 | 134 | lastWidth = width ; 135 | lastMI2 = mi2 ; 136 | lastRC = rhythmClass ; 137 | } 138 | 139 | 140 | /************************************************************************* 141 | CheckPostClass checks to see if three of the last four or six of the 142 | last eight of a given beat type have been post classified as PVC. 143 | *************************************************************************/ 144 | 145 | int CheckPostClass(int type) 146 | { 147 | int i, pvcs4 = 0, pvcs8 ; 148 | 149 | if(type == MAXTYPES) 150 | return(UNKNOWN) ; 151 | 152 | for(i = 0; i < 4; ++i) 153 | if(PostClass[type][i] == PVC) 154 | ++pvcs4 ; 155 | for(pvcs8=pvcs4; i < 8; ++i) 156 | if(PostClass[type][i] == PVC) 157 | ++pvcs8 ; 158 | 159 | if((pvcs4 >= 3) || (pvcs8 >= 6)) 160 | return(PVC) ; 161 | else return(UNKNOWN) ; 162 | } 163 | 164 | /**************************************************************************** 165 | Check classification of previous beats' rhythms based on post beat 166 | classification. If 7 of 8 previous beats were classified as NORMAL 167 | (regular) classify the beat type as NORMAL (regular). 168 | Call it a PVC if 2 of the last 8 were regular. 169 | ****************************************************************************/ 170 | 171 | int CheckPCRhythm(int type) 172 | { 173 | int i, normCount, n ; 174 | 175 | 176 | if(type == MAXTYPES) 177 | return(UNKNOWN) ; 178 | 179 | if(GetBeatTypeCount(type) < 9) 180 | n = GetBeatTypeCount(type)-1 ; 181 | else n = 8 ; 182 | 183 | for(i = normCount = 0; i < n; ++i) 184 | if(PCRhythm[type][i] == NORMAL) 185 | ++normCount; 186 | if(normCount >= 7) 187 | return(NORMAL) ; 188 | if(((normCount == 0) && (n < 4)) || 189 | ((normCount <= 1) && (n >= 4) && (n < 7)) || 190 | ((normCount <= 2) && (n >= 7))) 191 | return(PVC) ; 192 | return(UNKNOWN) ; 193 | } -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/POSTCLAS.H: -------------------------------------------------------------------------------- 1 | void ResetPostClassify() ; 2 | void PostClassify(int *recentTypes, int domType, int *recentRRs, int width, double mi2, 3 | int rhythmClass) ; 4 | int CheckPostClass(int type) ; 5 | int CheckPCRhythm(int type) ; -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/QRSDET.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** FILE: qrsdet.cpp AUTHOR: Patrick S. Hamilton REVISED: 12/04/2000 ___________________________________________________________________________ qrsdet.cpp: A QRS detector. Copywrite (C) 2000 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This software 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact the author by e-mail (pat@eplimited.edu) or postal mail (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, MA 02143 USA). For updates to this software, please visit our website (http://www.eplimited.com). __________________________________________________________________________ This file contains functions for detecting QRS complexes in an ECG. The QRS detector requires filter functions in qrsfilt.cpp and parameter definitions in qrsdet.h. QRSDet is the only function that needs to be visable outside of these files. Syntax: int QRSDet(int ecgSample, int init) ; Description: QRSDet() implements a modified version of the QRS detection algorithm described in: Hamilton, Tompkins, W. J., "Quantitative investigation of QRS detection rules using the MIT/BIH arrhythmia database", IEEE Trans. Biomed. Eng., BME-33, pp. 1158-1165, 1987. Consecutive ECG samples are passed to QRSDet. QRSDet was designed for a 200 Hz sample rate. QRSDet contains a number of static variables that it uses to adapt to different ECG signals. These variables can be reset by passing any value not equal to 0 in init. Note: QRSDet() requires filters in QRSFilt.cpp Returns: When a QRS complex is detected QRSDet returns the detection delay. ****************************************************************/ #include /* For memmov. */ #include #include "qrsdet.h" #define PRE_BLANK MS200 // External Prototypes. int QRSFilter(int datum, int init) ; int deriv1( int x0, int init ) ; // Local Prototypes. int Peak( int datum, int init ) ; int median(int *array, int datnum) ; int thresh(int qmedian, int nmedian) ; int BLSCheck(int *dBuf,int dbPtr,int *maxder) ; int earlyThresh(int qmedian, int nmedian) ; double TH = 0.475 ; int DDBuffer[DER_DELAY], DDPtr ; /* Buffer holding derivative data. */ int Dly = 0 ; const int MEMMOVELEN = 7*sizeof(int); int QRSDet( int datum, int init ) { static int det_thresh, qpkcnt = 0 ; static int qrsbuf[8], noise[8], rrbuf[8] ; static int rsetBuff[8], rsetCount = 0 ; static int nmedian, qmedian, rrmedian ; static int count, sbpeak = 0, sbloc, sbcount = MS1500 ; static int maxder, lastmax ; static int initBlank, initMax ; static int preBlankCnt, tempPeak ; int fdatum, QrsDelay = 0 ; int i, newPeak, aPeak ; /* Initialize all buffers to 0 on the first call. */ if( init ) { for(i = 0; i < 8; ++i) { noise[i] = 0 ; /* Initialize noise buffer */ rrbuf[i] = MS1000 ;/* and R-to-R interval buffer. */ } qpkcnt = maxder = lastmax = count = sbpeak = 0 ; initBlank = initMax = preBlankCnt = DDPtr = 0 ; sbcount = MS1500 ; QRSFilter(0,1) ; /* initialize filters. */ Peak(0,1) ; } fdatum = QRSFilter(datum,0) ; /* Filter data. */ /* Wait until normal detector is ready before calling early detections. */ aPeak = Peak(fdatum,0) ; // Hold any peak that is detected for 200 ms // in case a bigger one comes along. There // can only be one QRS complex in any 200 ms window. newPeak = 0 ; if(aPeak && !preBlankCnt) // If there has been no peak for 200 ms { // save this one and start counting. tempPeak = aPeak ; preBlankCnt = PRE_BLANK ; // MS200 } else if(!aPeak && preBlankCnt) // If we have held onto a peak for { // 200 ms pass it on for evaluation. if(--preBlankCnt == 0) newPeak = tempPeak ; } else if(aPeak) // If we were holding a peak, but { // this ones bigger, save it and if(aPeak > tempPeak) // start counting to 200 ms again. { tempPeak = aPeak ; preBlankCnt = PRE_BLANK ; // MS200 } else if(--preBlankCnt == 0) newPeak = tempPeak ; } /* newPeak = 0 ; if((aPeak != 0) && (preBlankCnt == 0)) newPeak = aPeak ; else if(preBlankCnt != 0) --preBlankCnt ; */ /* Save derivative of raw signal for T-wave and baseline shift discrimination. */ DDBuffer[DDPtr] = deriv1( datum, 0 ) ; if(++DDPtr == DER_DELAY) DDPtr = 0 ; /* Initialize the qrs peak buffer with the first eight */ /* local maximum peaks detected. */ if( qpkcnt < 8 ) { ++count ; if(newPeak > 0) count = WINDOW_WIDTH ; if(++initBlank == MS1000) { initBlank = 0 ; qrsbuf[qpkcnt] = initMax ; initMax = 0 ; ++qpkcnt ; if(qpkcnt == 8) { qmedian = median( qrsbuf, 8 ) ; nmedian = 0 ; rrmedian = MS1000 ; sbcount = MS1500+MS150 ; det_thresh = thresh(qmedian,nmedian) ; } } if( newPeak > initMax ) initMax = newPeak ; } else /* Else test for a qrs. */ { ++count ; if(newPeak > 0) { /* Check for maximum derivative and matching minima and maxima for T-wave and baseline shift rejection. Only consider this peak if it doesn't seem to be a base line shift. */ if(!BLSCheck(DDBuffer, DDPtr, &maxder)) { // Classify the beat as a QRS complex // if the peak is larger than the detection threshold. if(newPeak > det_thresh) { memmove(&qrsbuf[1], qrsbuf, MEMMOVELEN) ; qrsbuf[0] = newPeak ; qmedian = median(qrsbuf,8) ; det_thresh = thresh(qmedian,nmedian) ; memmove(&rrbuf[1], rrbuf, MEMMOVELEN) ; rrbuf[0] = count - WINDOW_WIDTH ; rrmedian = median(rrbuf,8) ; sbcount = rrmedian + (rrmedian >> 1) + WINDOW_WIDTH ; count = WINDOW_WIDTH ; sbpeak = 0 ; lastmax = maxder ; maxder = 0 ; QrsDelay = WINDOW_WIDTH + FILTER_DELAY ; initBlank = initMax = rsetCount = 0 ; // preBlankCnt = PRE_BLANK ; } // If a peak isn't a QRS update noise buffer and estimate. // Store the peak for possible search back. else { memmove(&noise[1],noise,MEMMOVELEN) ; noise[0] = newPeak ; nmedian = median(noise,8) ; det_thresh = thresh(qmedian,nmedian) ; // Don't include early peaks (which might be T-waves) // in the search back process. A T-wave can mask // a small following QRS. if((newPeak > sbpeak) && ((count-WINDOW_WIDTH) >= MS360)) { sbpeak = newPeak ; sbloc = count - WINDOW_WIDTH ; } } } } /* Test for search back condition. If a QRS is found in */ /* search back update the QRS buffer and det_thresh. */ if((count > sbcount) && (sbpeak > (det_thresh >> 1))) { memmove(&qrsbuf[1],qrsbuf,MEMMOVELEN) ; qrsbuf[0] = sbpeak ; qmedian = median(qrsbuf,8) ; det_thresh = thresh(qmedian,nmedian) ; memmove(&rrbuf[1],rrbuf,MEMMOVELEN) ; rrbuf[0] = sbloc ; rrmedian = median(rrbuf,8) ; sbcount = rrmedian + (rrmedian >> 1) + WINDOW_WIDTH ; QrsDelay = count = count - sbloc ; QrsDelay += FILTER_DELAY ; sbpeak = 0 ; lastmax = maxder ; maxder = 0 ; initBlank = initMax = rsetCount = 0 ; } } // In the background estimate threshold to replace adaptive threshold // if eight seconds elapses without a QRS detection. if( qpkcnt == 8 ) { if(++initBlank == MS1000) { initBlank = 0 ; rsetBuff[rsetCount] = initMax ; initMax = 0 ; ++rsetCount ; // Reset threshold if it has been 8 seconds without // a detection. if(rsetCount == 8) { for(i = 0; i < 8; ++i) { qrsbuf[i] = rsetBuff[i] ; noise[i] = 0 ; } qmedian = median( rsetBuff, 8 ) ; nmedian = 0 ; rrmedian = MS1000 ; sbcount = MS1500+MS150 ; det_thresh = thresh(qmedian,nmedian) ; initBlank = initMax = rsetCount = 0 ; sbpeak = 0 ; } } if( newPeak > initMax ) initMax = newPeak ; } return(QrsDelay) ; } /************************************************************** * peak() takes a datum as input and returns a peak height * when the signal returns to half its peak height, or **************************************************************/ int Peak( int datum, int init ) { static int max = 0, timeSinceMax = 0, lastDatum ; int pk = 0 ; if(init) max = timeSinceMax = 0 ; if(timeSinceMax > 0) ++timeSinceMax ; if((datum > lastDatum) && (datum > max)) { max = datum ; if(max > 2) timeSinceMax = 1 ; } else if(datum < (max >> 1)) { pk = max ; max = 0 ; timeSinceMax = 0 ; Dly = 0 ; } else if(timeSinceMax > MS95) { pk = max ; max = 0 ; timeSinceMax = 0 ; Dly = 3 ; } lastDatum = datum ; return(pk) ; } /******************************************************************** median returns the median of an array of integers. It uses a slow sort algorithm, but these arrays are small, so it hardly matters. ********************************************************************/ int median(int *array, int datnum) { int i, j, k, temp, sort[20] ; for(i = 0; i < datnum; ++i) sort[i] = array[i] ; for(i = 0; i < datnum; ++i) { temp = sort[i] ; for(j = 0; (temp < sort[j]) && (j < i) ; ++j) ; for(k = i - 1 ; k >= j ; --k) sort[k+1] = sort[k] ; sort[j] = temp ; } return(sort[datnum>>1]) ; } /* int median(int *array, int datnum) { long sum ; int i ; for(i = 0, sum = 0; i < datnum; ++i) sum += array[i] ; sum /= datnum ; return(sum) ; } */ /**************************************************************************** thresh() calculates the detection threshold from the qrs median and noise median estimates. ****************************************************************************/ int thresh(int qmedian, int nmedian) { int thrsh, dmed ; double temp ; dmed = qmedian - nmedian ; /* thrsh = nmedian + (dmed>>2) + (dmed>>3) + (dmed>>4); */ temp = dmed ; temp *= TH ; dmed = temp ; thrsh = nmedian + dmed ; /* dmed * THRESHOLD */ return(thrsh) ; } /*********************************************************************** BLSCheck() reviews data to see if a baseline shift has occurred. This is done by looking for both positive and negative slopes of roughly the same magnitude in a 220 ms window. ***********************************************************************/ int BLSCheck(int *dBuf,int dbPtr,int *maxder) { int max, min, maxt, mint, t, x ; max = min = 0 ; return(0) ; for(t = 0; t < MS220; ++t) { x = dBuf[dbPtr] ; if(x > max) { maxt = t ; max = x ; } else if(x < min) { mint = t ; min = x; } if(++dbPtr == DER_DELAY) dbPtr = 0 ; } *maxder = max ; min = -min ; /* Possible beat if a maximum and minimum pair are found where the interval between them is less than 150 ms. */ if((max > (min>>3)) && (min > (max>>3)) && (abs(maxt - mint) < MS150)) return(0) ; else return(1) ; } -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/QRSDET.H: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: qrsdet.h 3 | AUTHOR: Patrick S. Hamilton 4 | REVISED: 4/16/2002 5 | ___________________________________________________________________________ 6 | 7 | qrsdet.h QRS detector parameter definitions 8 | Copywrite (C) 2000 Patrick S. Hamilton 9 | 10 | This file is free software; you can redistribute it and/or modify it under 11 | the terms of the GNU Library General Public License as published by the Free 12 | Software Foundation; either version 2 of the License, or (at your option) any 13 | later version. 14 | 15 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 16 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 17 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 18 | details. 19 | 20 | You should have received a copy of the GNU Library General Public License along 21 | with this library; if not, write to the Free Software Foundation, Inc., 59 22 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 | 24 | You may contact the author by e-mail (pat@eplimited.com) or postal mail 25 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 26 | MA 02143 USA). For updates to this software, please visit our website 27 | (http://www.eplimited.com). 28 | __________________________________________________________________________ 29 | Revisions: 30 | 4/16: Modified to allow simplified modification of digital filters in 31 | qrsfilt(). 32 | *****************************************************************************/ 33 | 34 | 35 | #define SAMPLE_RATE 200 /* Sample rate in Hz. */ 36 | #define MS_PER_SAMPLE ( (double) 1000/ (double) SAMPLE_RATE) 37 | #define MS10 ((int) (10/ MS_PER_SAMPLE + 0.5)) 38 | #define MS25 ((int) (25/MS_PER_SAMPLE + 0.5)) 39 | #define MS30 ((int) (30/MS_PER_SAMPLE + 0.5)) 40 | #define MS80 ((int) (80/MS_PER_SAMPLE + 0.5)) 41 | #define MS95 ((int) (95/MS_PER_SAMPLE + 0.5)) 42 | #define MS100 ((int) (100/MS_PER_SAMPLE + 0.5)) 43 | #define MS125 ((int) (125/MS_PER_SAMPLE + 0.5)) 44 | #define MS150 ((int) (150/MS_PER_SAMPLE + 0.5)) 45 | #define MS160 ((int) (160/MS_PER_SAMPLE + 0.5)) 46 | #define MS175 ((int) (175/MS_PER_SAMPLE + 0.5)) 47 | #define MS195 ((int) (195/MS_PER_SAMPLE + 0.5)) 48 | #define MS200 ((int) (200/MS_PER_SAMPLE + 0.5)) 49 | #define MS220 ((int) (220/MS_PER_SAMPLE + 0.5)) 50 | #define MS250 ((int) (250/MS_PER_SAMPLE + 0.5)) 51 | #define MS300 ((int) (300/MS_PER_SAMPLE + 0.5)) 52 | #define MS360 ((int) (360/MS_PER_SAMPLE + 0.5)) 53 | #define MS450 ((int) (450/MS_PER_SAMPLE + 0.5)) 54 | #define MS1000 SAMPLE_RATE 55 | #define MS1500 ((int) (1500/MS_PER_SAMPLE)) 56 | #define DERIV_LENGTH MS10 57 | #define LPBUFFER_LGTH ((int) (2*MS25)) 58 | #define HPBUFFER_LGTH MS125 59 | 60 | #define WINDOW_WIDTH MS80 // Moving window integration width. 61 | #define FILTER_DELAY (int) (((double) DERIV_LENGTH/2) + ((double) LPBUFFER_LGTH/2 - 1) + (((double) HPBUFFER_LGTH-1)/2) + PRE_BLANK) // filter delays plus 200 ms blanking delay 62 | #define DER_DELAY WINDOW_WIDTH + FILTER_DELAY + MS100 63 | 64 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/QRSDET2.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** FILE: qrsdet2.cpp AUTHOR: Patrick S. Hamilton REVISED: 7/08/2002 ___________________________________________________________________________ qrsdet2.cpp: A QRS detector. Copywrite (C) 2002 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This software 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact the author by e-mail (pat@eplimited.edu) or postal mail (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, MA 02143 USA). For updates to this software, please visit our website (http://www.eplimited.com). __________________________________________________________________________ This file contains functions for detecting QRS complexes in an ECG. The QRS detector requires filter functions in qrsfilt.cpp and parameter definitions in qrsdet.h. QRSDet is the only function that needs to be visable outside of these files. Syntax: int QRSDet(int ecgSample, int init) ; Description: QRSDet() implements a modified version of the QRS detection algorithm described in: Hamilton, Tompkins, W. J., "Quantitative investigation of QRS detection rules using the MIT/BIH arrhythmia database", IEEE Trans. Biomed. Eng., BME-33, pp. 1158-1165, 1987. Consecutive ECG samples are passed to QRSDet. QRSDet was designed for a 200 Hz sample rate. QRSDet contains a number of static variables that it uses to adapt to different ECG signals. These variables can be reset by passing any value not equal to 0 in init. Note: QRSDet() requires filters in QRSFilt.cpp Returns: When a QRS complex is detected QRSDet returns the detection delay. ****************************************************************/ #include /* For memmov. */ #include #include "qrsdet.h" #define PRE_BLANK MS195 #define MIN_PEAK_AMP 7 // Prevents detections of peaks smaller than 150 uV. // External Prototypes. int QRSFilter(int datum, int init) ; int deriv1( int x0, int init ) ; // Local Prototypes. int Peak( int datum, int init ) ; int mean(int *array, int datnum) ; int thresh(int qmean, int nmean) ; int BLSCheck(int *dBuf,int dbPtr,int *maxder) ; double TH = .3125 ; int DDBuffer[DER_DELAY], DDPtr ; /* Buffer holding derivative data. */ int Dly = 0 ; const int MEMMOVELEN = 7*sizeof(int); int QRSDet( int datum, int init ) { static int det_thresh, qpkcnt = 0 ; static int qrsbuf[8], noise[8], rrbuf[8] ; static int rsetBuff[8], rsetCount = 0 ; static int nmean, qmean, rrmean ; static int count, sbpeak = 0, sbloc, sbcount = MS1500 ; static int maxder, lastmax ; static int initBlank, initMax ; static int preBlankCnt, tempPeak ; int fdatum, QrsDelay = 0 ; int i, newPeak, aPeak ; /* Initialize all buffers to 0 on the first call. */ if( init ) { for(i = 0; i < 8; ++i) { noise[i] = 0 ; /* Initialize noise buffer */ rrbuf[i] = MS1000 ;/* and R-to-R interval buffer. */ } qpkcnt = maxder = lastmax = count = sbpeak = 0 ; initBlank = initMax = preBlankCnt = DDPtr = 0 ; sbcount = MS1500 ; QRSFilter(0,1) ; /* initialize filters. */ Peak(0,1) ; } fdatum = QRSFilter(datum,0) ; /* Filter data. */ /* Wait until normal detector is ready before calling early detections. */ aPeak = Peak(fdatum,0) ; if(aPeak < MIN_PEAK_AMP) aPeak = 0 ; // Hold any peak that is detected for 200 ms // in case a bigger one comes along. There // can only be one QRS complex in any 200 ms window. newPeak = 0 ; if(aPeak && !preBlankCnt) // If there has been no peak for 200 ms { // save this one and start counting. tempPeak = aPeak ; preBlankCnt = PRE_BLANK ; // MS200 } else if(!aPeak && preBlankCnt) // If we have held onto a peak for { // 200 ms pass it on for evaluation. if(--preBlankCnt == 0) newPeak = tempPeak ; } else if(aPeak) // If we were holding a peak, but { // this ones bigger, save it and if(aPeak > tempPeak) // start counting to 200 ms again. { tempPeak = aPeak ; preBlankCnt = PRE_BLANK ; // MS200 } else if(--preBlankCnt == 0) newPeak = tempPeak ; } /* Save derivative of raw signal for T-wave and baseline shift discrimination. */ DDBuffer[DDPtr] = deriv1( datum, 0 ) ; if(++DDPtr == DER_DELAY) DDPtr = 0 ; /* Initialize the qrs peak buffer with the first eight */ /* local maximum peaks detected. */ if( qpkcnt < 8 ) { ++count ; if(newPeak > 0) count = WINDOW_WIDTH ; if(++initBlank == MS1000) { initBlank = 0 ; qrsbuf[qpkcnt] = initMax ; initMax = 0 ; ++qpkcnt ; if(qpkcnt == 8) { qmean = mean( qrsbuf, 8 ) ; nmean = 0 ; rrmean = MS1000 ; sbcount = MS1500+MS150 ; det_thresh = thresh(qmean,nmean) ; } } if( newPeak > initMax ) initMax = newPeak ; } else /* Else test for a qrs. */ { ++count ; if(newPeak > 0) { /* Check for maximum derivative and matching minima and maxima for T-wave and baseline shift rejection. Only consider this peak if it doesn't seem to be a base line shift. */ if(!BLSCheck(DDBuffer, DDPtr, &maxder)) { // Classify the beat as a QRS complex // if the peak is larger than the detection threshold. if(newPeak > det_thresh) { memmove(&qrsbuf[1], qrsbuf, MEMMOVELEN) ; qrsbuf[0] = newPeak ; qmean = mean(qrsbuf,8) ; det_thresh = thresh(qmean,nmean) ; memmove(&rrbuf[1], rrbuf, MEMMOVELEN) ; rrbuf[0] = count - WINDOW_WIDTH ; rrmean = mean(rrbuf,8) ; sbcount = rrmean + (rrmean >> 1) + WINDOW_WIDTH ; count = WINDOW_WIDTH ; sbpeak = 0 ; lastmax = maxder ; maxder = 0 ; QrsDelay = WINDOW_WIDTH + FILTER_DELAY ; initBlank = initMax = rsetCount = 0 ; } // If a peak isn't a QRS update noise buffer and estimate. // Store the peak for possible search back. else { memmove(&noise[1],noise,MEMMOVELEN) ; noise[0] = newPeak ; nmean = mean(noise,8) ; det_thresh = thresh(qmean,nmean) ; // Don't include early peaks (which might be T-waves) // in the search back process. A T-wave can mask // a small following QRS. if((newPeak > sbpeak) && ((count-WINDOW_WIDTH) >= MS360)) { sbpeak = newPeak ; sbloc = count - WINDOW_WIDTH ; } } } } /* Test for search back condition. If a QRS is found in */ /* search back update the QRS buffer and det_thresh. */ if((count > sbcount) && (sbpeak > (det_thresh >> 1))) { memmove(&qrsbuf[1],qrsbuf,MEMMOVELEN) ; qrsbuf[0] = sbpeak ; qmean = mean(qrsbuf,8) ; det_thresh = thresh(qmean,nmean) ; memmove(&rrbuf[1],rrbuf,MEMMOVELEN) ; rrbuf[0] = sbloc ; rrmean = mean(rrbuf,8) ; sbcount = rrmean + (rrmean >> 1) + WINDOW_WIDTH ; QrsDelay = count = count - sbloc ; QrsDelay += FILTER_DELAY ; sbpeak = 0 ; lastmax = maxder ; maxder = 0 ; initBlank = initMax = rsetCount = 0 ; } } // In the background estimate threshold to replace adaptive threshold // if eight seconds elapses without a QRS detection. if( qpkcnt == 8 ) { if(++initBlank == MS1000) { initBlank = 0 ; rsetBuff[rsetCount] = initMax ; initMax = 0 ; ++rsetCount ; // Reset threshold if it has been 8 seconds without // a detection. if(rsetCount == 8) { for(i = 0; i < 8; ++i) { qrsbuf[i] = rsetBuff[i] ; noise[i] = 0 ; } qmean = mean( rsetBuff, 8 ) ; nmean = 0 ; rrmean = MS1000 ; sbcount = MS1500+MS150 ; det_thresh = thresh(qmean,nmean) ; initBlank = initMax = rsetCount = 0 ; } } if( newPeak > initMax ) initMax = newPeak ; } return(QrsDelay) ; } /************************************************************** * peak() takes a datum as input and returns a peak height * when the signal returns to half its peak height, or **************************************************************/ int Peak( int datum, int init ) { static int max = 0, timeSinceMax = 0, lastDatum ; int pk = 0 ; if(init) max = timeSinceMax = 0 ; if(timeSinceMax > 0) ++timeSinceMax ; if((datum > lastDatum) && (datum > max)) { max = datum ; if(max > 2) timeSinceMax = 1 ; } else if(datum < (max >> 1)) { pk = max ; max = 0 ; timeSinceMax = 0 ; Dly = 0 ; } else if(timeSinceMax > MS95) { pk = max ; max = 0 ; timeSinceMax = 0 ; Dly = 3 ; } lastDatum = datum ; return(pk) ; } /******************************************************************** mean returns the mean of an array of integers. It uses a slow sort algorithm, but these arrays are small, so it hardly matters. ********************************************************************/ int mean(int *array, int datnum) { long sum ; int i ; for(i = 0, sum = 0; i < datnum; ++i) sum += array[i] ; sum /= datnum ; return(sum) ; } /**************************************************************************** thresh() calculates the detection threshold from the qrs mean and noise mean estimates. ****************************************************************************/ int thresh(int qmean, int nmean) { int thrsh, dmed ; double temp ; dmed = qmean - nmean ; /* thrsh = nmean + (dmed>>2) + (dmed>>3) + (dmed>>4); */ temp = dmed ; temp *= TH ; dmed = temp ; thrsh = nmean + dmed ; /* dmed * THRESHOLD */ return(thrsh) ; } /*********************************************************************** BLSCheck() reviews data to see if a baseline shift has occurred. This is done by looking for both positive and negative slopes of roughly the same magnitude in a 220 ms window. ***********************************************************************/ int BLSCheck(int *dBuf,int dbPtr,int *maxder) { int max, min, maxt, mint, t, x ; max = min = 0 ; for(t = 0; t < MS220; ++t) { x = dBuf[dbPtr] ; if(x > max) { maxt = t ; max = x ; } else if(x < min) { mint = t ; min = x; } if(++dbPtr == DER_DELAY) dbPtr = 0 ; } *maxder = max ; min = -min ; /* Possible beat if a maximum and minimum pair are found where the interval between them is less than 150 ms. */ if((max > (min>>3)) && (min > (max>>3)) && (abs(maxt - mint) < MS150)) return(0) ; else return(1) ; } -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/QRSFILT.CPP: -------------------------------------------------------------------------------- 1 | /***************************************************************************** FILE: qrsfilt.cpp AUTHOR: Patrick S. Hamilton REVISED: 5/13/2002 ___________________________________________________________________________ qrsfilt.cpp filter functions to aid beat detecton in electrocardiograms. Copywrite (C) 2000 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 2 | Software Foundation; either version 2 of the License, or (at your option) any 3 | later version. 4 | 5 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 6 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 7 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 8 | details. 9 | 10 | You should have received a copy of the GNU Library General Public License along 11 | with this library; if not, write to the Free Software Foundation, Inc., 59 12 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 13 | 14 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 15 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 16 | MA 02143 USA). For updates to this software, please visit our website 17 | (http://www.eplimited.com). 18 | __________________________________________________________________________ 19 | This file includes QRSFilt() and associated filtering files used for QRS detection. Only QRSFilt() and deriv1() are called by the QRS detector other functions can be hidden. Revisions: 5/13: Filter implementations have been modified to allow simplified modification for different sample rates. *******************************************************************************/ #include #include "qrsdet.h" // Local Prototypes. int lpfilt( int datum ,int init) ; int hpfilt( int datum, int init ) ; int deriv1( int x0, int init ) ; int deriv2( int x0, int init ) ; int mvwint(int datum, int init) ; /****************************************************************************** * Syntax: * int QRSFilter(int datum, int init) ; * Description: * QRSFilter() takes samples of an ECG signal as input and returns a sample of * a signal that is an estimate of the local energy in the QRS bandwidth. In * other words, the signal has a lump in it whenever a QRS complex, or QRS * complex like artifact occurs. The filters were originally designed for data * sampled at 200 samples per second, but they work nearly as well at sample * frequencies from 150 to 250 samples per second. * * The filter buffers and static variables are reset if a value other than * 0 is passed to QRSFilter through init. *******************************************************************************/ int QRSFilter(int datum,int init) { int fdatum ; if(init) { hpfilt( 0, 1 ) ; // Initialize filters. lpfilt( 0, 1 ) ; mvwint( 0, 1 ) ; deriv1( 0, 1 ) ; deriv2( 0, 1 ) ; } fdatum = lpfilt( datum, 0 ) ; // Low pass filter data. fdatum = hpfilt( fdatum, 0 ) ; // High pass filter data. fdatum = deriv2( fdatum, 0 ) ; // Take the derivative. fdatum = abs(fdatum) ; // Take the absolute value. fdatum = mvwint( fdatum, 0 ) ; // Average over an 80 ms window . return(fdatum) ; } /************************************************************************* * lpfilt() implements the digital filter represented by the difference * equation: * * y[n] = 2*y[n-1] - y[n-2] + x[n] - 2*x[t-24 ms] + x[t-48 ms] * * Note that the filter delay is (LPBUFFER_LGTH/2)-1 * **************************************************************************/ int lpfilt( int datum ,int init) { static long y1 = 0, y2 = 0 ; static int data[LPBUFFER_LGTH], ptr = 0 ; long y0 ; int output, halfPtr ; if(init) { for(ptr = 0; ptr < LPBUFFER_LGTH; ++ptr) data[ptr] = 0 ; y1 = y2 = 0 ; ptr = 0 ; } halfPtr = ptr-(LPBUFFER_LGTH/2) ; // Use halfPtr to index if(halfPtr < 0) // to x[n-6]. halfPtr += LPBUFFER_LGTH ; y0 = (y1 << 1) - y2 + datum - (data[halfPtr] << 1) + data[ptr] ; y2 = y1; y1 = y0; output = y0 / ((LPBUFFER_LGTH*LPBUFFER_LGTH)/4); data[ptr] = datum ; // Stick most recent sample into if(++ptr == LPBUFFER_LGTH) // the circular buffer and update ptr = 0 ; // the buffer pointer. return(output) ; } /****************************************************************************** * hpfilt() implements the high pass filter represented by the following * difference equation: * * y[n] = y[n-1] + x[n] - x[n-128 ms] * z[n] = x[n-64 ms] - y[n] ; * * Filter delay is (HPBUFFER_LGTH-1)/2 ******************************************************************************/ int hpfilt( int datum, int init ) { static long y=0 ; static int data[HPBUFFER_LGTH], ptr = 0 ; int z, halfPtr ; if(init) { for(ptr = 0; ptr < HPBUFFER_LGTH; ++ptr) data[ptr] = 0 ; ptr = 0 ; y = 0 ; } y += datum - data[ptr]; halfPtr = ptr-(HPBUFFER_LGTH/2) ; if(halfPtr < 0) halfPtr += HPBUFFER_LGTH ; z = data[halfPtr] - (y / HPBUFFER_LGTH); data[ptr] = datum ; if(++ptr == HPBUFFER_LGTH) ptr = 0 ; return( z ); } /***************************************************************************** * deriv1 and deriv2 implement derivative approximations represented by * the difference equation: * * y[n] = x[n] - x[n - 10ms] * * Filter delay is DERIV_LENGTH/2 *****************************************************************************/ int deriv1(int x, int init) { static int derBuff[DERIV_LENGTH], derI = 0 ; int y ; if(init != 0) { for(derI = 0; derI < DERIV_LENGTH; ++derI) derBuff[derI] = 0 ; derI = 0 ; return(0) ; } y = x - derBuff[derI] ; derBuff[derI] = x ; if(++derI == DERIV_LENGTH) derI = 0 ; return(y) ; } int deriv2(int x, int init) { static int derBuff[DERIV_LENGTH], derI = 0 ; int y ; if(init != 0) { for(derI = 0; derI < DERIV_LENGTH; ++derI) derBuff[derI] = 0 ; derI = 0 ; return(0) ; } y = x - derBuff[derI] ; derBuff[derI] = x ; if(++derI == DERIV_LENGTH) derI = 0 ; return(y) ; } /***************************************************************************** * mvwint() implements a moving window integrator. Actually, mvwint() averages * the signal values over the last WINDOW_WIDTH samples. *****************************************************************************/ int mvwint(int datum, int init) { static long sum = 0 ; static int data[WINDOW_WIDTH], ptr = 0 ; int output; if(init) { for(ptr = 0; ptr < WINDOW_WIDTH ; ++ptr) data[ptr] = 0 ; sum = 0 ; ptr = 0 ; } sum += datum ; sum -= data[ptr] ; data[ptr] = datum ; if(++ptr == WINDOW_WIDTH) ptr = 0 ; if((sum / WINDOW_WIDTH) > 32000) output = 32000 ; else output = sum / WINDOW_WIDTH ; return(output) ; } -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/RYTHMCHK.H: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | FILE: rythmchk.h AUTHOR: Patrick S. Hamilton REVISED: 9/25/2001 ___________________________________________________________________________ rythmchk.h: Prototype definitions for rythmchk.cpp Copywrite (C) 2001 Patrick S. Hamilton This file is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free 3 | Software Foundation; either version 2 of the License, or (at your option) any 4 | later version. 5 | 6 | This software is distributed in the hope that it will be useful, but WITHOUT ANY 7 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 8 | PARTICULAR PURPOSE. See the GNU Library General Public License for more 9 | details. 10 | 11 | You should have received a copy of the GNU Library General Public License along 12 | with this library; if not, write to the Free Software Foundation, Inc., 59 13 | Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14 | 15 | You may contact the author by e-mail (pat@eplimited.edu) or postal mail 16 | (Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville, 17 | MA 02143 USA). For updates to this software, please visit our website 18 | (http://www.eplimited.com). 19 | ******************************************************************************/ 20 | 21 | // External prototypes for rythmchk.cpp 22 | 23 | void ResetRhythmChk(void) ; 24 | int RhythmChk(int rr) ; 25 | int IsBigeminy(void) ; -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/VERSION RELEASE NOTES.txt: -------------------------------------------------------------------------------- 1 | VERSION RELEASE NOTES 2 | 3 | Release 1.3 (9/19/2002): 4 | This release includes minor modifications to qrsdet2.cpp so that the performance of the QRS detector in qrsdet2.cpp conforms to EC13. Specifically, a minimum detection threshold has been added and the blanking constant has been slightly modified. I a separate file I have included the test files I used for adapting qrsdet2.cpp to EC13. 5 | There are more substantial changes in the documentation document. Previous release notes have been integrated into the documentation and the documentation has been layed out in a more organized fashion. 6 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/WFLIB.LIB: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdtek/ECGViewer/3fdb799acf24e62205e2e461427f2c96dddefb9e/beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/EPLib/WFLIB.LIB -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/EPBeatAnalysis/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | printf("Test"); 6 | //TODO: load ECG signal samples and call the beat detection and clasification algorithm 7 | 8 | getchar(); 9 | return 0; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /beatanalyzer/EPBeatAnalyzer/README.md: -------------------------------------------------------------------------------- 1 | # EP Beat Analyzer 2 | 3 | Testing the open source beat detector and classifier provided by [EP Limited](http://www.eplimited.com/confirmation.htm) 4 | -------------------------------------------------------------------------------- /qtapp/glecgcanvas.cpp: -------------------------------------------------------------------------------- 1 | #include "GLEcgCanvas.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | GLEcgCanvas::GLEcgCanvas(QWidget *parent) : QOpenGLWidget(parent) 11 | { 12 | setFixedSize(600, 400); 13 | setAutoFillBackground(false); 14 | } 15 | 16 | void GLEcgCanvas::setSignalData(vector& signalData) 17 | { 18 | m_signalBuffer = signalData; //TODO: load signal data into member vector 19 | update(); 20 | } 21 | 22 | void GLEcgCanvas::paintEvent(QPaintEvent *event) 23 | { 24 | QPainter painter(this); 25 | painter.begin(this); 26 | painter.setRenderHint(QPainter::Antialiasing); 27 | painter.fillRect(event->rect(), QBrush(Qt::white)); 28 | painter.end(); 29 | 30 | drawGridLines(10, QColor(255, 192, 192)); 31 | drawGridLines(50, QColor(240, 128, 128)); 32 | drawSignal(QColor(0, 0, 0)); 33 | } 34 | 35 | void GLEcgCanvas::drawGridLines(int interval, const QColor &penColor) 36 | { 37 | int windowWidth = this->width(); 38 | int windowHeight = this->height(); 39 | 40 | QPainter painter(this); 41 | painter.begin(this); 42 | painter.setRenderHint(QPainter::Antialiasing); 43 | painter.setPen(QPen(penColor, 1)); 44 | 45 | //Vertical lines 46 | for(int i = 0; i * interval < windowWidth; i++){ 47 | QVector polyPoints; 48 | polyPoints.append( QPoint(interval * i, 0) ); 49 | polyPoints.append( QPoint(interval * i, windowHeight) ); 50 | painter.drawPolyline( polyPoints ); 51 | } 52 | 53 | //Horizontal lines 54 | for(int i = 0; i * interval < windowHeight; i++){ 55 | QVector polyPoints; 56 | polyPoints.append( QPoint(0, interval * i) ); 57 | polyPoints.append( QPoint(windowWidth, interval * i) ); 58 | painter.drawPolyline( polyPoints ); 59 | } 60 | 61 | painter.end(); 62 | } 63 | 64 | void GLEcgCanvas::drawSignal(const QColor &penColor) 65 | { 66 | qDebug() << "Draw signal."; 67 | //QPainter painter(this); 68 | //painter.begin(this); 69 | //painter.setPen(QPen(penColor, 1)); 70 | 71 | double yOffset = trackYOffset() / 2; 72 | double xPos = m_paddingX, yPos = 0; 73 | int i = 0, iSample = 0; 74 | 75 | if(m_signalBuffer.empty()) return; 76 | 77 | for (i = 0, iSample = 0; iSample < 1000 - 1; i++, iSample++) { 78 | 79 | qDebug() << "loop: "; 80 | //qDebug() << QString("%1").arg(m_signalBuffer[i]); 81 | 82 | double x1 = xPos; 83 | double test = m_signalBuffer[iSample] * -1; 84 | double y1 = yOffset + scaleSignalYToPixels(m_signalBuffer[iSample] * -1); 85 | double x2 = m_paddingX + scaleSignalXToPixels(i + 1); 86 | double y2 = yOffset + scaleSignalYToPixels(m_signalBuffer[iSample + 1] * -1); 87 | 88 | QVector linePoints; 89 | linePoints.append( QPoint(x1, y1) ); 90 | linePoints.append( QPoint(x2, y2) ); 91 | //painter.drawPolyline( linePoints ); 92 | 93 | xPos = x2; 94 | yPos = y2; 95 | 96 | if (i == (pointsPerTrack() - 1)) { 97 | i = 0; 98 | xPos = m_paddingX; 99 | yOffset += trackYOffset(); 100 | } 101 | } 102 | } 103 | 104 | void GLEcgCanvas::setScaleFactor(double scaleFactor) { 105 | m_scaleFactor = scaleFactor; 106 | } 107 | 108 | double GLEcgCanvas::trackYOffset() { 109 | return 2.5 * ecgBigSquarePx(); 110 | } 111 | 112 | double GLEcgCanvas::ecgBigSquarePx() { 113 | return m_scaleFactor * round(m_mmPerBigSquare * m_dotsPerInch / m_mmPerInch); 114 | } 115 | 116 | double GLEcgCanvas::ecgSmallSquarePx() { 117 | return ecgBigSquarePx() / 5; 118 | } 119 | 120 | double GLEcgCanvas::trackWidthPx() { 121 | return this->width() - 2 * m_paddingX; 122 | } 123 | 124 | int GLEcgCanvas::pointsPerTrack() { 125 | return round(trackWidthPx() / ecgBigSquarePx() * m_sampleFrequency); 126 | } 127 | 128 | double GLEcgCanvas::scaleSignalXToPixels(int sampleIndex) { 129 | double xPixelValue = sampleIndex / m_sampleFrequency * ecgBigSquarePx(); 130 | return round(xPixelValue); 131 | } 132 | 133 | double GLEcgCanvas::scaleSignalYToPixels(int sample) { 134 | qDebug() << "s"; 135 | double yPixelValue = sample * m_sampleResolution * ecgBigSquarePx(); 136 | qDebug() << "pv" << yPixelValue; 137 | return round(yPixelValue); 138 | } 139 | -------------------------------------------------------------------------------- /qtapp/glecgcanvas.h: -------------------------------------------------------------------------------- 1 | #ifndef GLECGCANVAS_H 2 | #define GLECGCANVAS_H 3 | 4 | #include 5 | #include 6 | 7 | class GLEcgCanvas : public QOpenGLWidget 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | GLEcgCanvas(QWidget *parent); 13 | void setSignalData(long* signalData); 14 | long* getSignalData(); 15 | 16 | protected: 17 | void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; 18 | 19 | private: 20 | void drawGridLines(int interval, const QColor &penColor); 21 | void drawSignal(const QColor &penColor); 22 | void setScaleFactor(double scaleFactor); 23 | double trackYOffset(); 24 | double ecgBigSquarePx(); 25 | double ecgSmallSquarePx(); 26 | double trackWidthPx(); 27 | int pointsPerTrack(); 28 | double scaleSignalXToPixels(int sampleIndex); 29 | double scaleSignalYToPixels(int sample); 30 | 31 | double m_mmPerBigSquare = 25.0; 32 | double m_msPerBigSquare = 1000; 33 | double m_dotsPerInch = 96.0; 34 | double m_mmPerInch = 25.4; 35 | double m_scaleFactor; 36 | std::vector m_signalBuffer; 37 | int m_maxSignalSamples = 10000; 38 | int m_sampleFrequency = 500; 39 | double m_sampleResolution = 0.005; 40 | int m_paddingX = 50; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /qtapp/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication app(argc, argv); 8 | 9 | QSurfaceFormat fmt; 10 | fmt.setSamples(4); 11 | QSurfaceFormat::setDefaultFormat(fmt); 12 | 13 | MainWindow mainWindow; 14 | mainWindow.show(); 15 | return app.exec(); 16 | } 17 | -------------------------------------------------------------------------------- /qtapp/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "GLEcgCanvas.h" 10 | #include "MainWindow.h" 11 | #include 12 | 13 | MainWindow::MainWindow() 14 | { 15 | QMenu *fileMenu = menuBar()->addMenu(tr("&File")); 16 | QAction *fileOpenAct = new QAction(QString("Open"),this); 17 | connect(fileOpenAct, &QAction::triggered, this, &MainWindow::openFile); 18 | fileMenu->addAction(fileOpenAct); 19 | 20 | setWindowTitle(tr("2D Painting on OpenGL Widget")); 21 | m_ecg = new GLEcgCanvas(this); 22 | this->setCentralWidget(m_ecg); 23 | } 24 | 25 | void MainWindow::openFile(){ 26 | 27 | QString fileName = QFileDialog::getOpenFileName(this, 28 | tr("Open ECG Data"), "/home", tr("TXT Files (*.txt)")); 29 | qDebug() << "Filename:" << fileName; 30 | 31 | QFile inputFile(fileName); 32 | if (inputFile.open(QIODevice::ReadOnly)) 33 | { 34 | int indexBuffer = 0; 35 | long signalBuffer[10000]; 36 | int maxBufferLength = 10000; 37 | QTextStream inputStream(&inputFile); 38 | 39 | while (!inputStream.atEnd()){ 40 | QString line = inputStream.readLine(); 41 | 42 | bool convertOk = false; 43 | signalBuffer[indexBuffer] = line.toLong(&convertOk, 10); 44 | indexBuffer++; 45 | if(indexBuffer >= maxBufferLength) break; 46 | } 47 | inputFile.close(); 48 | 49 | for(int i = 0; i < indexBuffer; i++){ 50 | qDebug() << "Line: " << signalBuffer[i]; 51 | } 52 | 53 | m_ecg->setSignalData(signalBuffer); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /qtapp/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | #include 4 | #include 5 | #include "GLEcgCanvas.h" 6 | 7 | class MainWindow : public QMainWindow 8 | { 9 | Q_OBJECT 10 | public: 11 | MainWindow(); 12 | private: 13 | GLEcgCanvas *m_ecg; 14 | private slots: 15 | void openFile(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /qtapp/qtecg.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = qtecg_app 3 | 4 | QT = core gui 5 | 6 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 7 | 8 | SOURCES += \ 9 | GLEcgCanvas.cpp \ 10 | MainWindow.cpp \ 11 | Main.cpp 12 | 13 | HEADERS += \ 14 | GLEcgCanvas.h \ 15 | MainWindow.h 16 | -------------------------------------------------------------------------------- /webapp/ecg.js: -------------------------------------------------------------------------------- 1 | var adx_ecg = { 2 | 3 | DPI : 96.0, 4 | MM_PER_BIG_SQUARE : 25.0, 5 | MS_PER_BIG_SQUARE : 1000, 6 | MM_PER_INCH : 25.4, //25.4 millimetres = 1 inch. 7 | BIG_GRID_COLOR : "#F08080", //rgb(240, 128, 128); 8 | SMALL_GRID_COLOR : "#FFC0C0", //rgb(255, 192, 192); 9 | SIGNAL_COLOR : "#000033", 10 | SAMPLE_FREQUENCY : 500, 11 | SAMPLE_RESOLUTION : 0.005, 12 | TRACKS_PER_PAGE : 5, 13 | SCALE_FACTOR : 1, 14 | MIN_HEIGHT : 800, 15 | PADDING_X : 50, 16 | 17 | SignalData : [], 18 | AnalysisData : [], 19 | 20 | ECGCanvas : function () { return $("#ECGCanvas"); }, 21 | 22 | SetResizeHandler : function (callback, timeout) { 23 | var timer_id = undefined; 24 | window.addEventListener("resize", function () { 25 | if (timer_id != undefined) { 26 | clearTimeout(timer_id); 27 | timer_id = undefined; 28 | } 29 | timer_id = setTimeout(function () { 30 | timer_id = undefined; 31 | callback(); 32 | }, timeout); 33 | }); 34 | }, 35 | 36 | InitCanvas : function () { 37 | var canvas = this.ECGCanvas(); 38 | var parent = canvas.parent(); 39 | 40 | parent.find(".ECGChooseFile").change(function() { 41 | alert("change"); 42 | var input = event.target; 43 | var reader = new FileReader(); 44 | reader.onload = function(){ 45 | var text = reader.result; 46 | //TODO: load data into SignalData array for drawing 47 | console.log(reader.result.substring(0, 200)); 48 | }; 49 | reader.readAsText(input.files[0]); 50 | }); 51 | 52 | parent.find(".BtnOpenECG").off("click").on( "click", function() { 53 | var fileApi = !!(window.File && window.FileReader && window.FileList && window.Blob); 54 | if(fileApi) 55 | parent.find(".ECGChooseFile").click(); 56 | else 57 | console.log("File API not supported."); 58 | }); 59 | 60 | var context = canvas.get(0).getContext("2d"); 61 | context.canvas.width = parent.outerWidth() 62 | - (canvas.css("marginLeft").replace('px', '') * 2) - 5; 63 | context.canvas.height = parent.outerHeight() 64 | - (canvas.css("marginTop").replace('px', '') * 2) - 5; 65 | this.Paint(); 66 | }, 67 | 68 | CanvasContext: function () { 69 | var canvas = this.ECGCanvas(); 70 | var context = canvas.get(0).getContext("2d"); 71 | context.lineCap = "round"; 72 | return context; 73 | }, 74 | 75 | // Name: EcgBigSquarePx 76 | // Desc: Get the width and height of big ECG grid square representing 1 second 77 | // Returns: Width and height of big grid square in pixel units 78 | EcgBigSquarePx : function () { 79 | return this.ScaleFactor() * Math.round(this.MM_PER_BIG_SQUARE * this.DPI / this.MM_PER_INCH); 80 | }, 81 | 82 | // Name: EcgSmallSquarePx 83 | // Desc: Get the width and height of small ECG grid square representing 1/5 second 84 | // Returns: Width and height of small square in pixel units 85 | EcgSmallSquarePx : function () { 86 | return this.EcgBigSquarePx() / 5; 87 | }, 88 | 89 | // Name: TrackWidthPx 90 | // Desc: Calculates width of signal track taking account of padding or margins 91 | // Returns: Width of track in pixel units as integer 92 | TrackWidthPx : function () { 93 | var me = this; 94 | return me.ECGCanvas().outerWidth() - 2 * me.PADDING_X; 95 | }, 96 | 97 | TrackYOffset : function () { 98 | return 2.5 * this.EcgBigSquarePx(); 99 | }, 100 | 101 | ScaleFactor : function (scaleFactor) { 102 | var me = this; 103 | if (arguments.length >= 1) 104 | me.SCALE_FACTOR = scaleFactor; 105 | return me.SCALE_FACTOR; 106 | }, 107 | 108 | 109 | // Name: PointsPerTrack 110 | // Desc: Calculates how many points fit in one signal track 111 | // Returns: Whole number of points for one track 112 | PointsPerTrack : function () { 113 | var me = this; 114 | return Math.round(me.TrackWidthPx() / me.EcgBigSquarePx() * me.SAMPLE_FREQUENCY); 115 | }, 116 | 117 | IncreaseScaleFactor : function (increment) { 118 | var me = this; 119 | if (arguments.length >= 1) 120 | me.SCALE_FACTOR += increment; 121 | me.Paint(); 122 | }, 123 | 124 | DecreaseScaleFactor : function (increment) { 125 | var me = this; 126 | if (arguments.length >= 1) 127 | me.SCALE_FACTOR -= increment; 128 | me.Paint(); 129 | }, 130 | 131 | Clear : function () { 132 | var me = this; 133 | me.SignalData = []; 134 | me.AnalysisData = []; 135 | me.Paint(); 136 | }, 137 | 138 | Paint: function () { 139 | var me = this; 140 | me.SetCanvasHeight(); 141 | me.DrawGrid(); 142 | me.DrawSignal(); 143 | me.DrawAnnotations(); 144 | }, 145 | 146 | SetCanvasHeight : function(){ 147 | var me = this; 148 | var ctx = me.CanvasContext(); 149 | var totalTracks = Math.ceil(me.SignalData.length / me.PointsPerTrack()); 150 | var calculatedHeight = totalTracks * me.TrackYOffset() + me.TrackYOffset(); 151 | ctx.canvas.height = calculatedHeight > me.MIN_HEIGHT ? calculatedHeight : me.MIN_HEIGHT; 152 | }, 153 | 154 | // Name: DrawLine 155 | // Desc: Draws single line onto HTML canvas, used to draw grid lines and signal waves. 156 | DrawLine : function (x1, y1, x2, y2, penWidth, penColor) { 157 | var ctx = this.CanvasContext(); 158 | ctx.lineWidth = penWidth; 159 | ctx.beginPath(); 160 | ctx.strokeStyle = penColor; 161 | ctx.moveTo(x1, y1); 162 | ctx.lineTo(x2, y2); 163 | ctx.stroke(); 164 | }, 165 | 166 | // Name: DrawGrid 167 | // Desc: Draws grid squares indicating time along horizontal and voltage in vertical 168 | DrawGrid : function() { 169 | 170 | var me = this; 171 | var ctx = me.CanvasContext(); 172 | 173 | ctx.fillStyle = "#fff"; 174 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 175 | 176 | var DrawGridLines = function (square, maxWidth, maxHeight, color) { 177 | var x = -square, y = -square; 178 | while ((x += square) < maxWidth) me.DrawLine(x, 0, x, maxHeight, 1, color); 179 | while ((y += square) < maxHeight) me.DrawLine(0, y, maxWidth, y, 1, color); 180 | } 181 | 182 | DrawGridLines(me.EcgSmallSquarePx(), ctx.canvas.width, ctx.canvas.height, me.SMALL_GRID_COLOR); 183 | DrawGridLines(me.EcgBigSquarePx(), ctx.canvas.width, ctx.canvas.height, me.BIG_GRID_COLOR); 184 | }, 185 | 186 | // Name: DrawSignal 187 | // Desc: Draws rows of signal wave lines overlaid on ECG grid 188 | DrawSignal : function () { 189 | 190 | var me = this; 191 | var ctx = me.CanvasContext(); 192 | var signalData = me.SignalData; 193 | var yOffset = me.TrackYOffset() / 2; 194 | var i = 0, j = 0, xPos = me.PADDING_X, yPos = 0; 195 | 196 | for (i = 0, j = 0; j < signalData.length - 1; i += 1, j += 1) { 197 | 198 | var x1 = xPos; 199 | var y1 = yOffset + me.ScaleSignalYToPixels(signalData[j] * -1); 200 | var x2 = me.PADDING_X + me.ScaleSignalXToPixels(i + 1); 201 | var y2 = yOffset + me.ScaleSignalYToPixels(signalData[j + 1] * -1); 202 | 203 | me.DrawLine(x1, y1, x2, y2, 1, me.SIGNAL_COLOR); 204 | 205 | xPos = x2; 206 | yPos = y2; 207 | 208 | if (i == (me.PointsPerTrack() - 1)) { 209 | i = 0; 210 | xPos = me.PADDING_X; 211 | yOffset += me.TrackYOffset(); 212 | } 213 | } 214 | }, 215 | 216 | ScaleSignalXToPixels : function (sampleIndex) { 217 | var xPixels = sampleIndex / this.SAMPLE_FREQUENCY * this.EcgBigSquarePx(); 218 | return Math.round(xPixels); 219 | }, 220 | 221 | // Name: ScaleSignalXToPixels 222 | // Desc: Creates a X coordinate based on grid scale so we can draw the signal point 223 | // Param: sampleIndex - the index of the sample in the signal samples array 224 | // Returns: X pixel coordinate as integer 225 | ScaleSignalYToPixels : function(sample) { 226 | var yPixels = sample * this.SAMPLE_RESOLUTION * this.EcgBigSquarePx(); 227 | return Math.round(yPixels); 228 | }, 229 | 230 | DrawAnnotations : function(){ 231 | 232 | if (this.AnalysisData == null || this.AnalysisData.Beats == null) return; 233 | 234 | var ctx = this.CanvasContext(); 235 | ctx.font = "11px Arial"; 236 | 237 | var annotations = this.GetAnnotations(1); 238 | 239 | for (var i = 0; i < annotations.length; i++) 240 | { 241 | var a = annotations[i]; 242 | var p = a.point; 243 | ctx.fillStyle = 'rgba(225,225,225,0.9)'; 244 | ctx.fillRect(p.x - 8, p.y - 12, 14, 16); 245 | ctx.fillStyle = '#000000'; 246 | ctx.fillText(a.label, p.x - 5, p.y); 247 | } 248 | }, 249 | 250 | TimeToX : function (timeMs) { 251 | var me = this; 252 | var x = timeMs / me.MS_PER_BIG_SQUARE * me.EcgBigSquarePx(); 253 | var trackNum = Math.floor(x / me.TrackWidthPx()); 254 | return x % me.TrackWidthPx(); 255 | }, 256 | 257 | TrackToY : function (trackNum) { 258 | var me = this; 259 | var y = (trackNum % me.TRACKS_PER_PAGE + 1) * me.TrackYOffset(); 260 | return y + 3 * me.EcgSmallSquarePx(); 261 | }, 262 | 263 | GetAnnotationPosition : function (timeMs) { 264 | var me = this; 265 | var x = 0, y = 0, trackNum = 0; 266 | 267 | x = timeMs / me.MS_PER_BIG_SQUARE * me.EcgBigSquarePx(); 268 | trackNum = Math.floor(x / me.TrackWidthPx()); 269 | x = x % me.TrackWidthPx(); 270 | 271 | y = (trackNum % me.TRACKS_PER_PAGE + 1) * me.TrackYOffset(); 272 | y = y + 3 * me.EcgSmallSquarePx(); 273 | 274 | return { x: x, y: y }; 275 | }, 276 | 277 | GetAnnotations : function(pageNum){ 278 | 279 | var me = this; 280 | var annotations = []; 281 | var beats = me.AnalysisData.Beats; 282 | 283 | var timeMsPerPage = ((me.TrackWidthPx() * me.TRACKS_PER_PAGE) / me.EcgBigSquarePx()) * 1000; 284 | var pageStartTime = (pageNum - 1) * timeMsPerPage; 285 | var pageEndTime = pageStartTime + timeMsPerPage; 286 | 287 | for (var i = 0; i < beats.length; i++) 288 | { 289 | var beat = beats[i]; 290 | if (beat.Time >= pageStartTime && beat.Time < pageEndTime) { 291 | 292 | annotations.push({ 293 | label: beat.Label, 294 | time: beat.Time, 295 | point: me.GetAnnotationPosition(beat.Time) 296 | }); 297 | } 298 | } 299 | 300 | return annotations; 301 | } 302 | } 303 | 304 | window.onload = function () { 305 | adx_ecg.SetResizeHandler(function () { 306 | adx_ecg.InitCanvas(); 307 | }, 100); 308 | adx_ecg.InitCanvas(); 309 | } -------------------------------------------------------------------------------- /webapp/main.css: -------------------------------------------------------------------------------- 1 | body, html{height:100%;margin:0;} 2 | html > body{margin:0;} 3 | body{margin:0 20px 0 20px;} 4 | body fieldset{margin:0 0 5px 0;padding:10px;} 5 | body button{line-height:30px;padding:0 1em;} 6 | body select{margin:0 0 5px 0;} 7 | 8 | #PageBody{margin:10px;height:100%;} 9 | #PageHeader{overflow:auto;text-align:right;background-color:#4591aa;-webkit-box-shadow:0px 3px 5px rgba(100,100,100,0.49);-moz-box-shadow:0px 3px 5px rgba(100,100,100,0.49);box-shadow:0px 3px 5px rgba(100,100,100,0.49);} 10 | #PageHeader > h1{display:inline-block;text-align:left;width:400px;position:absolute;left:0px;top:0px;margin:0;color:white;padding-left:10px;font-size:2em;background-color:#4591aa;} 11 | 12 | .SettingsIcon{margin:5px;cursor:pointer;width:30px;} 13 | body h3{font-size:1.2em;} 14 | label.FieldLabel{font-weight:bold;margin:5px 0 5px 0;} 15 | fieldset{border:1px solid gray;border-radius:4px;} 16 | textarea{margin:10px 0 0 0;} 17 | 18 | #TxtRequestContent, #TxtAPIResponse{font-size:10px;} 19 | #FileB64View{font-size:10px;max-width:280px;overflow:hidden;} 20 | 21 | #TabsContainer{overflow:auto;} 22 | #Tabs{margin-bottom:5px;} 23 | #ECGView{position:relative;overflow:auto;margin:0;min-height:800px;border:1px solid grey;border-radius:3px;background-color:white;} 24 | 25 | input.ECGChooseFile { display: none; } 26 | button.BtnOpenECG{ position:absolute;top:15px;left:20px;margin:0; } 27 | button.BtnClearECG{ position:absolute;top:15px;left:85px;margin:0; } 28 | div.ECGZoomButtons{ position:absolute;top:15px;right:20px;margin:0; } 29 | button.BtnECGScaleZoomOut, button.BtnECGScaleZoomIn{margin:0;} 30 | #ECGCanvas{margin:10px;} 31 | #IFramePDF{width:100%;min-height:680px;border:1px solid gray;} -------------------------------------------------------------------------------- /webapp/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | This browser does not support the HTML5 canvas tag. 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /win32app/.gitignore: -------------------------------------------------------------------------------- 1 | old 2 | *.exe 3 | 4 | .svn/ 5 | TestData/ 6 | TestResults/ 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # DNX 51 | project.lock.json 52 | artifacts/ 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # NCrunch 120 | _NCrunch_* 121 | .*crunch*.local.xml 122 | nCrunchTemp_* 123 | 124 | # MightyMoose 125 | *.mm.* 126 | AutoTest.Net/ 127 | 128 | # Web workbench (sass) 129 | .sass-cache/ 130 | 131 | # Installshield output folder 132 | [Ee]xpress/ 133 | 134 | # DocProject is a documentation generator add-in 135 | DocProject/buildhelp/ 136 | DocProject/Help/*.HxT 137 | DocProject/Help/*.HxC 138 | DocProject/Help/*.hhc 139 | DocProject/Help/*.hhk 140 | DocProject/Help/*.hhp 141 | DocProject/Help/Html2 142 | DocProject/Help/html 143 | 144 | # Click-Once directory 145 | publish/ 146 | 147 | # Publish Web Output 148 | *.[Pp]ublish.xml 149 | *.azurePubxml 150 | # TODO: Comment the next line if you want to checkin your web deploy settings 151 | # but database connection strings (with potential passwords) will be unencrypted 152 | *.pubxml 153 | *.publishproj 154 | 155 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 156 | # checkin your Azure Web App publish settings, but sensitive information contained 157 | # in these scripts will be unencrypted 158 | PublishScripts/ 159 | 160 | # NuGet Packages 161 | *.nupkg 162 | # The packages folder can be ignored because of Package Restore 163 | **/packages/* 164 | # except build/, which is used as an MSBuild target. 165 | !**/packages/build/ 166 | # Uncomment if necessary however generally it will be regenerated when needed 167 | #!**/packages/repositories.config 168 | # NuGet v3's project.json files produces more ignoreable files 169 | *.nuget.props 170 | *.nuget.targets 171 | 172 | # Microsoft Azure Build Output 173 | csx/ 174 | *.build.csdef 175 | 176 | # Microsoft Azure Emulator 177 | ecf/ 178 | rcf/ 179 | 180 | # Windows Store app package directories and files 181 | AppPackages/ 182 | BundleArtifacts/ 183 | Package.StoreAssociation.xml 184 | _pkginfo.txt 185 | 186 | # Visual Studio cache files 187 | # files ending in .cache can be ignored 188 | *.[Cc]ache 189 | # but keep track of directories ending in .cache 190 | !*.[Cc]ache/ 191 | 192 | # Others 193 | ClientBin/ 194 | ~$* 195 | *~ 196 | *.dbmdl 197 | *.dbproj.schemaview 198 | *.pfx 199 | *.publishsettings 200 | node_modules/ 201 | orleans.codegen.cs 202 | 203 | # Since there are multiple workflows, uncomment next line to ignore bower_components 204 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 205 | #bower_components/ 206 | 207 | # RIA/Silverlight projects 208 | Generated_Code/ 209 | 210 | # Backup & report files from converting an old project file 211 | # to a newer Visual Studio version. Backup files are not needed, 212 | # because we have git ;-) 213 | _UpgradeReport_Files/ 214 | Backup*/ 215 | UpgradeLog*.XML 216 | UpgradeLog*.htm 217 | 218 | # SQL Server files 219 | *.mdf 220 | *.ldf 221 | 222 | # Business Intelligence projects 223 | *.rdl.data 224 | *.bim.layout 225 | *.bim_*.settings 226 | 227 | # Microsoft Fakes 228 | FakesAssemblies/ 229 | 230 | # GhostDoc plugin setting file 231 | *.GhostDoc.xml 232 | 233 | # Node.js Tools for Visual Studio 234 | .ntvs_analysis.dat 235 | 236 | # Visual Studio 6 build log 237 | *.plg 238 | 239 | # Visual Studio 6 workspace options file 240 | *.opt 241 | 242 | # Visual Studio LightSwitch build output 243 | **/*.HTMLClient/GeneratedArtifacts 244 | **/*.DesktopClient/GeneratedArtifacts 245 | **/*.DesktopClient/ModelManifest.xml 246 | **/*.Server/GeneratedArtifacts 247 | **/*.Server/ModelManifest.xml 248 | _Pvt_Extensions 249 | 250 | # Paket dependency manager 251 | .paket/paket.exe 252 | paket-files/ 253 | 254 | # FAKE - F# Make 255 | .fake/ 256 | 257 | # JetBrains Rider 258 | .idea/ 259 | *.sln.iml -------------------------------------------------------------------------------- /win32app/ECGViewerScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdtek/ECGViewer/3fdb799acf24e62205e2e461427f2c96dddefb9e/win32app/ECGViewerScreenshot.png -------------------------------------------------------------------------------- /win32app/ECGViewerWin32.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ECGViewerWin32", "ECGViewerWin32\ECGViewerWin32.vcxproj", "{EC4BE475-80AF-428F-9B6E-A2927CB69AED}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Debug|x64.ActiveCfg = Debug|x64 17 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Debug|x64.Build.0 = Debug|x64 18 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Debug|x86.ActiveCfg = Debug|Win32 19 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Debug|x86.Build.0 = Debug|Win32 20 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Release|x64.ActiveCfg = Release|x64 21 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Release|x64.Build.0 = Release|x64 22 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Release|x86.ActiveCfg = Release|Win32 23 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/ECGViewerWin32.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {EC4BE475-80AF-428F-9B6E-A2927CB69AED} 23 | Win32Proj 24 | Hello32 25 | 8.1 26 | ECGViewerWin32 27 | 28 | 29 | 30 | Application 31 | true 32 | v140 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v140 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v140 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v140 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | $(Configuration)\ 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | NotUsing 89 | Level3 90 | Disabled 91 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 92 | false 93 | CompileAsCpp 94 | 95 | 96 | Windows 97 | true 98 | 99 | 100 | 101 | 102 | Use 103 | Level3 104 | Disabled 105 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 106 | 107 | 108 | Windows 109 | true 110 | 111 | 112 | 113 | 114 | Level3 115 | Use 116 | MaxSpeed 117 | true 118 | true 119 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 120 | 121 | 122 | Windows 123 | true 124 | true 125 | true 126 | 127 | 128 | 129 | 130 | Level3 131 | Use 132 | MaxSpeed 133 | true 134 | true 135 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 136 | 137 | 138 | Windows 139 | true 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/ECGViewerWin32.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/appinit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #pragma comment(lib,"comctl32.lib") 4 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 5 | const int MAX_SAMPLES = 1000000; -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/build.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | set "APP_NAME=ECGViewerWin32" 3 | set "OUTPUT_DIR=Release" 4 | 5 | if not exist %OUTPUT_DIR% mkdir %OUTPUT_DIR% 6 | 7 | echo Building %APP_NAME% 8 | gcc -D"UNICODE" -D"_UNICODE" wndmain.c ecgview.c logging.c -lgdi32 -mwindows -std=gnu99 -o %OUTPUT_DIR%\%APP_NAME%.exe 9 | 10 | echo %OUTPUT_DIR%\%APP_NAME%.exe -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/build.sh: -------------------------------------------------------------------------------- 1 | APP_NAME="ECGViewerWin32" 2 | OUTPUT_DIR="Release" 3 | mkdir -p $OUTPUT_DIR 4 | echo Building $APP_NAME 5 | gcc -D"UNICODE" -D"_UNICODE" wndmain.c ecgview.c logging.c -lgdi32 -mwindows -std=gnu99 -o $OUTPUT_DIR/$APP_NAME.exe 6 | echo $OUTPUT_DIR/$APP_NAME.exe -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/ecg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define _USE_MATH_DEFINES 3 | #include 4 | #include 5 | #include "appinit.h" 6 | #include "windowtools.h" 7 | #include "logging.h" 8 | 9 | typedef struct { 10 | LPCSTR label; 11 | double samples[1000000]; 12 | int numberOfSamples; 13 | } HeartSignal; 14 | 15 | //int padding_x; 16 | //int sampleFrequency; 17 | 18 | void InitECG(HWND hWindow); 19 | int IncrementPagination(); 20 | int DecrementPagination(); 21 | 22 | void GenerateSignal(HeartSignal* ptr_signal); 23 | void GenerateLine(HeartSignal* ptr_signal, size_t length, double gradient); 24 | double CalculateGausianPDF(double x, double mu, double variance); 25 | void GenerateGausianCurve(HeartSignal* ptr_signal, double mu, double variance, double scaleFactor); 26 | 27 | void SetECGSignal(HeartSignal* signal); 28 | void DrawGrid(HDC hdc); 29 | void DrawGridLines(HDC hdc, int interval); 30 | void DrawSignal(HDC hDeviceContext); 31 | void DrawTrackStartTime(HDC hDeviceContext, int trackIndex, RECT positionRect); 32 | 33 | int MaxTracksPerPage(); 34 | int PointsPerTrack(); 35 | int TrackWidthPx(); 36 | int TrackHeightPx(); 37 | int ScaleSignalXToPixels(int sampleIndex); 38 | int EcgBigSquarePx(); 39 | double EcgSmallSquarePx(); 40 | int TotalPages(); 41 | int PaddingBottom(); -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/logging.c: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | const char* LOG_FILE_NAME = "C:\\temp\\ecg_log.txt"; 4 | 5 | void log_int(const char* note, int intVal){ 6 | 7 | FILE *pfile = NULL; 8 | pfile = fopen(LOG_FILE_NAME, "a"); 9 | 10 | if(pfile == NULL) { 11 | printf("Error opening %s for writing.", LOG_FILE_NAME); 12 | } else { 13 | fputs("\n", pfile); 14 | fputs(note, pfile); 15 | fputs(" ", pfile); 16 | fprintf(pfile, "%d", intVal); 17 | } 18 | 19 | fclose(pfile); 20 | } 21 | 22 | void log_long(const char* note, long longVal){ 23 | 24 | FILE *pfile = NULL; 25 | pfile = fopen(LOG_FILE_NAME, "a"); 26 | 27 | if(pfile == NULL) { 28 | printf("Error opening %s for writing.", LOG_FILE_NAME); 29 | } else { 30 | fputs("\n", pfile); 31 | fputs(note, pfile); 32 | fputs(" ", pfile); 33 | fprintf(pfile, "%ld", longVal); 34 | } 35 | 36 | fclose(pfile); 37 | } 38 | 39 | void log_float(const char* note, float floatVal){ 40 | 41 | FILE *pfile = NULL; 42 | pfile = fopen(LOG_FILE_NAME, "a"); 43 | 44 | if(pfile == NULL) { 45 | printf("Error opening %s for writing.", LOG_FILE_NAME); 46 | } else { 47 | fputs("\n", pfile); 48 | fputs(note, pfile); 49 | fputs(" ", pfile); 50 | fprintf(pfile, "%f", floatVal); 51 | } 52 | 53 | fclose(pfile); 54 | } 55 | 56 | void log_dbl(const char* note, double doubleVal){ 57 | 58 | FILE *pfile = NULL; 59 | pfile = fopen(LOG_FILE_NAME, "a"); 60 | 61 | if(pfile == NULL) { 62 | printf("Error opening %s for writing.", LOG_FILE_NAME); 63 | } else { 64 | fputs("\n", pfile); 65 | fputs(note, pfile); 66 | fputs(" ", pfile); 67 | fprintf(pfile, "%f", doubleVal); 68 | } 69 | 70 | fclose(pfile); 71 | } 72 | 73 | void log_wstr(const wchar_t* note, const wchar_t* strVal) { 74 | 75 | FILE *pfile = NULL; 76 | pfile = fopen(LOG_FILE_NAME, "a"); 77 | 78 | if (pfile == NULL) { 79 | printf("Error opening %s for writing.", LOG_FILE_NAME); 80 | } 81 | else { 82 | fputws(L"\n", pfile); 83 | fputws(note, pfile); 84 | fputws(L" ", pfile); 85 | fputws(strVal, pfile); 86 | } 87 | 88 | fclose(pfile); 89 | } -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | void log_int 6 | (const char* note, int intVal); 7 | 8 | void log_long 9 | (const char* note, long longVal); 10 | 11 | void log_float 12 | (const char* note, float floatVal); 13 | 14 | void log_dbl 15 | (const char* note, double doubleVal); 16 | 17 | void log_wstr 18 | (const wchar_t* note, const wchar_t* strVal); -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/signalfileio.cpp: -------------------------------------------------------------------------------- 1 | #include "signalfileio.h" 2 | 3 | void DoOpenFile(HeartSignal* heartSignal, int maxNum, LPWSTR fileNameOut) { 4 | 5 | OPENFILENAME openFileName; 6 | WCHAR szFile[100]; 7 | 8 | ZeroMemory(&openFileName, sizeof(openFileName)); 9 | openFileName.lStructSize = sizeof(openFileName); 10 | openFileName.hwndOwner = NULL; 11 | openFileName.lpstrFile = szFile; 12 | openFileName.lpstrFile[0] = '\0'; 13 | openFileName.nMaxFile = sizeof(szFile); 14 | openFileName.lpstrFilter = L"All\0*.*\0Text\0*.TXT\0"; 15 | openFileName.nFilterIndex = 1; 16 | openFileName.lpstrFileTitle = NULL; 17 | openFileName.nMaxFileTitle = 0; 18 | openFileName.lpstrInitialDir = NULL; 19 | openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; 20 | 21 | if (GetOpenFileName(&openFileName) == 1) { 22 | FILE * ptr_file = _wfopen(openFileName.lpstrFile, L"r"); 23 | wcscpy(fileNameOut, openFileName.lpstrFile); 24 | 25 | if (ptr_file != NULL) { 26 | 27 | int indexSamples = 0; 28 | heartSignal->numberOfSamples = 0; 29 | char strLine[128]; 30 | 31 | while (fgets(strLine, sizeof strLine, ptr_file) != NULL) { 32 | heartSignal->samples[indexSamples] = strtol(strLine, NULL, 10); 33 | heartSignal->numberOfSamples++; 34 | indexSamples++; 35 | if (heartSignal->numberOfSamples >= maxNum) break; 36 | } 37 | 38 | fclose(ptr_file); 39 | } 40 | }; 41 | 42 | return; 43 | } 44 | 45 | int CountFileLines(const wchar_t* fileName) { 46 | int numLines = 0; 47 | int chr = 0; 48 | 49 | log_wstr(L"ECG Filename: ", fileName); 50 | 51 | FILE * ptr_file = _wfopen(fileName, L"r"); 52 | if (ptr_file != NULL) { 53 | while (!feof(ptr_file)) { 54 | chr = fgetc(ptr_file); 55 | if (chr == '\n') { 56 | numLines++; 57 | } 58 | } 59 | fclose(ptr_file); 60 | } 61 | 62 | log_int("\nNumber of lines: ", numLines); 63 | return numLines; 64 | } -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/signalfileio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "ecg.h" 5 | 6 | void DoOpenFile 7 | (HeartSignal* heartSignal, int maxNum, LPWSTR fileNameOut); 8 | 9 | int CountFileLines 10 | (const wchar_t* fileName); -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/windowtools.cpp: -------------------------------------------------------------------------------- 1 | #include "windowtools.h" 2 | 3 | void SaveWindowSize(HWND hwnd, WindowSize* windowSize) { 4 | RECT windowRect; 5 | if (GetWindowRect(hwnd, &windowRect)) { 6 | windowSize->width = windowRect.right - windowRect.left; 7 | windowSize->height = windowRect.bottom - windowRect.top; 8 | } 9 | } 10 | 11 | BOOL WindowSizeChanged(HWND hwnd, WindowSize* oldSize) { 12 | RECT windowRect; 13 | BOOL changed = 0; 14 | if (GetWindowRect(hwnd, &windowRect)) { 15 | if ((windowRect.right - windowRect.left) != oldSize->width 16 | || (windowRect.bottom - windowRect.top) != oldSize->height) { 17 | changed = 1; 18 | } 19 | } 20 | return changed; 21 | } 22 | 23 | int GetWindowWidth(HWND hWindow) { 24 | RECT rect; 25 | return GetWindowRect(hWindow, &rect) 26 | ? GetRectWidth(rect) : -1; 27 | } 28 | 29 | int GetWindowHeight(HWND hWindow) { 30 | RECT rect; 31 | return GetWindowRect(hWindow, &rect) 32 | ? GetRectHeight(rect) : -1; 33 | } 34 | 35 | int GetClientWidth(HWND hWindow) { 36 | RECT rect; 37 | return GetClientRect(hWindow, &rect) 38 | ? GetRectWidth(rect) : -1; 39 | } 40 | 41 | int GetClientHeight(HWND hWindow) { 42 | RECT rect; 43 | return GetClientRect(hWindow, &rect) 44 | ? GetRectHeight(rect) : -1; 45 | } 46 | 47 | int GetRectWidth(RECT rect) { 48 | return rect.right - rect.left; 49 | } 50 | 51 | int GetRectHeight(RECT rect) { 52 | return rect.bottom - rect.top; 53 | } 54 | 55 | HWND CreateButtonW(HWND hWindow, int btnId, LPCWSTR lpButtonText, int x, int y, int width, int height) { 56 | return CreateWindow(L"BUTTON", lpButtonText, 57 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 58 | x, y, width, height, hWindow, (HMENU)btnId, 59 | GetModuleHandle(NULL), NULL); 60 | } 61 | 62 | HWND CreateToolTip(int toolID, HINSTANCE hInst, HWND hWindow, PWSTR pszText) 63 | { 64 | if (!toolID || !hWindow || !pszText) 65 | { 66 | return FALSE; 67 | } 68 | // Get the window of the tool. 69 | HWND hwndTool = GetDlgItem(hWindow, toolID); 70 | 71 | // Create the tooltip. g_hInst is the global instance handle. 72 | HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, 73 | WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, 74 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 75 | hWindow, NULL, hInst, NULL); 76 | 77 | if (!hwndTool || !hwndTip) 78 | { 79 | return (HWND)NULL; 80 | } 81 | 82 | // Associate the tooltip with the tool. 83 | TOOLINFO toolInfo = { 0 }; 84 | toolInfo.cbSize = sizeof(toolInfo); 85 | toolInfo.hwnd = hWindow; 86 | toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; 87 | toolInfo.uId = (UINT_PTR)hwndTool; 88 | toolInfo.lpszText = pszText; 89 | 90 | if(!SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo)) 91 | MessageBox(0, TEXT("Failed: TTM_ADDTOOL"), 0, 0); 92 | 93 | return hwndTip; 94 | } -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/windowtools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef struct { int width; int height; } WindowSize; 6 | 7 | void SaveWindowSize 8 | (HWND hwnd, WindowSize* windowSize); 9 | 10 | BOOL WindowSizeChanged 11 | (HWND hwnd, WindowSize* oldSize); 12 | 13 | int GetWindowWidth 14 | (HWND hWindow); 15 | 16 | int GetWindowHeight 17 | (HWND hWindow); 18 | 19 | int GetClientWidth 20 | (HWND hWindow); 21 | 22 | int GetClientHeight 23 | (HWND hWindow); 24 | 25 | int GetRectWidth 26 | (RECT rect); 27 | 28 | int GetRectHeight 29 | (RECT rect); 30 | 31 | HWND CreateButtonW 32 | (HWND hWindow, int btnId, LPCWSTR lpButtonText, int x, int y, int width, int height); 33 | 34 | HWND CreateToolTip 35 | (int toolID, HINSTANCE hInst, HWND hWindow, PWSTR pszText); -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/wndmain.cpp: -------------------------------------------------------------------------------- 1 | #include "wndmain.h" 2 | 3 | bool windowCreated = false; 4 | WindowSize windowSize; 5 | HeartSignal m_heartSignal; 6 | 7 | VOID CALLBACK PaintTimerProc(HWND hwnd, UINT uMessage, UINT_PTR uEventId, DWORD dwTime) { 8 | BOOL result = KillTimer(hwnd, uEventId); 9 | wantDrawSignal = 1; 10 | SaveWindowSize(hwnd, &windowSize); 11 | DoRedraw(hwnd); 12 | } 13 | 14 | //Window Procedure - handles window messages 15 | LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam) 16 | { 17 | PAINTSTRUCT ps; 18 | HDC hDeviceContext; 19 | RECT windowRect; 20 | 21 | switch (msg) 22 | { 23 | case WM_CREATE: 24 | { 25 | AddMenus(hWindow); 26 | int windowWidth = GetWindowWidth(hWindow); 27 | 28 | //Buttons 29 | hBtnPageLeft = CreateButtonW(hWindow, IDC_PAGELEFT_BUTTON, 30 | L"<", windowWidth - 100, 220, 100, 24); 31 | hBtnPageRight = CreateButtonW(hWindow, IDC_PAGERIGHT_BUTTON, 32 | L">", 50, 220, 100, 24); 33 | 34 | //Tooltips 35 | HINSTANCE hi = (HINSTANCE)GetWindowLong(hWindow, GWL_HINSTANCE); 36 | CreateToolTip(IDC_PAGELEFT_BUTTON, hi, hWindow, L"Show previous page."); 37 | CreateToolTip(IDC_PAGERIGHT_BUTTON, hi, hWindow, L"Show next page."); 38 | 39 | InitECG(hWindow); 40 | 41 | windowCreated = true; 42 | break; 43 | } 44 | 45 | case WM_GETMINMAXINFO: 46 | { 47 | LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam; 48 | lpMMI->ptMinTrackSize.x = 300; 49 | lpMMI->ptMinTrackSize.y = 300; 50 | break; 51 | } 52 | 53 | case WM_SIZE: 54 | { 55 | if (GetClientRect(hWindow, &windowRect)) { 56 | if (windowCreated == true) { 57 | int windowWidth = GetWindowWidth(hWindow); 58 | SetWindowPos(hBtnPageLeft, NULL, windowWidth - 100, 10, 30, 30, NULL); 59 | SetWindowPos(hBtnPageRight, NULL, windowWidth - 60, 10, 30, 30, NULL); 60 | } 61 | SaveWindowSize(hWindow, &windowSize); 62 | wantDrawSignal = 0; 63 | SetTimer(hWindow, 0, 150, PaintTimerProc); 64 | } 65 | break; 66 | } 67 | 68 | case WM_EXITSIZEMOVE: 69 | { 70 | wantDrawSignal = 1; 71 | if (WindowSizeChanged(hWindow, &windowSize) >= 1) { 72 | DoRedraw(hWindow); 73 | } 74 | break; 75 | } 76 | 77 | case WM_PAINT: 78 | { 79 | hDeviceContext = BeginPaint(hWindow, &ps); 80 | DrawGrid(hDeviceContext); 81 | if (signalLoaded == 1 && wantDrawSignal >= 1) { 82 | DrawSignal(hDeviceContext); 83 | //Save window size to avoid unnecessary redraw 84 | SaveWindowSize(hWindow, &windowSize); 85 | } 86 | EndPaint(hWindow, &ps); 87 | break; 88 | } 89 | 90 | case WM_COMMAND: 91 | { 92 | HandleWMCommand(hWindow, LOWORD(wParam)); 93 | break; 94 | } 95 | 96 | case WM_CLOSE: { DestroyWindow(hWindow); break; } 97 | case WM_DESTROY: { PostQuitMessage(0); break; } 98 | 99 | default: break; 100 | } 101 | 102 | return DefWindowProc(hWindow, msg, wParam, lParam); 103 | } 104 | 105 | void SetWindowTitle(HWND hWindow, LPWSTR extraTitle) { 106 | //Set the window title text 107 | std::wstring wsFileName(extraTitle); 108 | std::wstring wsWindowTitle = std::wstring(MYWINDOWNAME) + std::wstring(L" ") + wsFileName; 109 | SetWindowText(hWindow, wsWindowTitle.c_str()); 110 | } 111 | 112 | void HandleWMCommand(HWND hWindow, WORD w) { 113 | 114 | //Variable to store filename from open file dialogue 115 | WCHAR fileName[100]; 116 | 117 | switch (w) { 118 | case IDM_FILE_OPEN: 119 | DoOpenFile(&m_heartSignal, MAX_SAMPLES, fileName); 120 | SetWindowTitle(hWindow, fileName); 121 | SetECGSignal(&m_heartSignal); 122 | wantDrawSignal = 1; 123 | signalLoaded = 1; 124 | DoRedraw(hWindow); 125 | break; 126 | 127 | case IDM_FILE_QUIT: 128 | SendMessage(hWindow, WM_CLOSE, 0, 0); 129 | break; 130 | 131 | case IDM_TOOLS_REFRESH: 132 | InvalidateRect(hWindow, 0, 1); DoRedraw(hWindow); 133 | break; 134 | 135 | case IDM_TOOLS_GENERATESIGNAL: 136 | GenerateSignal(&m_heartSignal); 137 | SetWindowTitle(hWindow, L""); 138 | SetECGSignal(&m_heartSignal); 139 | wantDrawSignal = 1; 140 | signalLoaded = 1; 141 | DoRedraw(hWindow); 142 | MessageBox(NULL, L"Generated signal.", L"Action complete.", 143 | MB_ICONINFORMATION | MB_OK); 144 | break; 145 | 146 | case IDC_PAGELEFT_BUTTON: 147 | if (DecrementPagination() == 1) DoRedraw(hWindow); 148 | break; 149 | 150 | case IDC_PAGERIGHT_BUTTON: 151 | if (IncrementPagination() == 1) DoRedraw(hWindow); 152 | break; 153 | } 154 | } 155 | 156 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 157 | LPSTR lpCmdLine, int nCmdShow) 158 | { 159 | WNDCLASSEX wc; 160 | HWND hwnd; 161 | MSG Msg; 162 | INITCOMMONCONTROLSEX structInitCommCtl; 163 | LPCWSTR szAppName = L"ECG Viewer"; 164 | 165 | structInitCommCtl.dwICC = ICC_TAB_CLASSES; 166 | structInitCommCtl.dwSize = sizeof(INITCOMMONCONTROLSEX); 167 | BOOL initControls = InitCommonControlsEx(&structInitCommCtl); 168 | 169 | //Registering the Window Class 170 | wc.cbSize = sizeof(WNDCLASSEX); 171 | wc.style = 0; 172 | wc.lpfnWndProc = WndProc; 173 | wc.cbClsExtra = 0; 174 | wc.cbWndExtra = 0; 175 | wc.hInstance = hInstance; 176 | wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 177 | wc.hCursor = LoadCursor(NULL, IDC_ARROW); 178 | wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 179 | wc.lpszMenuName = NULL; 180 | wc.lpszClassName = MYCLASSNAME;; 181 | wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 182 | 183 | if (!RegisterClassEx(&wc)) { 184 | MessageBox(NULL, L"Window Registration Failed!", L"Error!", 185 | MB_ICONEXCLAMATION | MB_OK); 186 | return 0; 187 | } 188 | 189 | //Creating the Window 190 | hwnd = CreateWindow(MYCLASSNAME, MYWINDOWNAME, WS_OVERLAPPEDWINDOW, 191 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 192 | NULL, NULL, hInstance, NULL); 193 | 194 | if (hwnd == NULL) { 195 | MessageBox(NULL, L"Window Creation Failed!", L"Error!", 196 | MB_ICONEXCLAMATION | MB_OK); 197 | return 0; 198 | } 199 | 200 | ShowWindow(hwnd, nCmdShow); 201 | UpdateWindow(hwnd); 202 | 203 | //Setup the Message Loop 204 | while (GetMessage(&Msg, NULL, 0, 0) > 0) 205 | { 206 | TranslateMessage(&Msg); 207 | DispatchMessage(&Msg); 208 | } 209 | return Msg.wParam; 210 | } 211 | 212 | void DoRedraw(HWND hwnd) { 213 | RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); 214 | } 215 | 216 | void AddMenus(HWND hwnd) { 217 | 218 | HMENU hMenubar; 219 | HMENU hMenuFile; 220 | HMENU hMenuTools; 221 | 222 | hMenubar = CreateMenu(); 223 | 224 | hMenuFile = CreateMenu(); 225 | AppendMenuW(hMenuFile, MF_STRING, IDM_FILE_OPEN, L"&Open"); 226 | AppendMenuW(hMenuFile, MF_STRING, IDM_FILE_SAVE, L"&Save"); 227 | AppendMenuW(hMenuFile, MF_SEPARATOR, 0, NULL); 228 | AppendMenuW(hMenuFile, MF_STRING, IDM_FILE_QUIT, L"&Quit"); 229 | AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR)hMenuFile, L"&File"); 230 | 231 | hMenuTools = CreateMenu(); 232 | AppendMenuW(hMenuTools, MF_STRING, IDM_TOOLS_REFRESH, L"&Refresh"); 233 | AppendMenuW(hMenuTools, MF_STRING, IDM_TOOLS_GENERATESIGNAL, L"&Generate Signal"); 234 | AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR)hMenuTools, L"&Tools"); 235 | 236 | SetMenu(hwnd, hMenubar); 237 | } -------------------------------------------------------------------------------- /win32app/ECGViewerWin32/wndmain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "appinit.h" 8 | #include "windowtools.h" 9 | #include "logging.h" 10 | #include "ecg.h" 11 | #include "signalfileio.h" 12 | 13 | #define MYCLASSNAME L"MainWndClass" 14 | #define MYWINDOWNAME L"ECG Viewer (Win32)" 15 | #define IDM_FILE_NEW 1 16 | #define IDM_FILE_OPEN 2 17 | #define IDM_FILE_SAVE 3 18 | #define IDM_FILE_QUIT 4 19 | #define IDM_TOOLS_REFRESH 5 20 | #define IDM_TOOLS_GENERATESIGNAL 6 21 | #define IDC_PAGELEFT_BUTTON 101 22 | #define IDC_PAGERIGHT_BUTTON 102 23 | 24 | int windowWidth; 25 | int windowHeight; 26 | int wantDrawSignal = 0; 27 | int signalLoaded = 0; 28 | 29 | HWND hBtnPageLeft; 30 | HWND hBtnPageRight; 31 | 32 | void AddMenus(HWND hwnd); 33 | void DoRedraw(HWND hwnd); 34 | VOID CALLBACK PaintTimerProc(HWND hwnd, UINT uMessage, UINT_PTR uEventId, DWORD dwTime); 35 | void HandleWMCommand(HWND hWindow, WORD w); -------------------------------------------------------------------------------- /win32app/README.md: -------------------------------------------------------------------------------- 1 | # ECG Viewer Win32 App 2 | 3 | ### Build 4 | Within Visual Studio click the debug run button or menu Build > Build Solution. 5 | 6 | NOTE: To compile as C++ or C check the project properties as below. 7 | Project > Properties > Configuration Properties > C/C++ > Advanced > Compile As (C++ = /TP, C = /TC) 8 | 9 | Alternatively run one of the build.bat/sh scripts. 10 | The .EXE should be output to the Debug/ or Release/ directory. 11 | 12 | ![Screenshot image](ECGViewerScreenshot.png?raw=true "Title") 13 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace ECGViewerWPF 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/DLLs/LibEDF.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdtek/ECGViewer/3fdb799acf24e62205e2e461427f2c96dddefb9e/wpfapp/ECGViewerWPF/DLLs/LibEDF.dll -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/DLLs/PdfSharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdtek/ECGViewer/3fdb799acf24e62205e2e461427f2c96dddefb9e/wpfapp/ECGViewerWPF/DLLs/PdfSharp.dll -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/ECGViewerWPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2879ACDA-12C4-465A-A64E-D258F856B66E} 8 | WinExe 9 | Properties 10 | ECGViewerWPF 11 | ECGViewerWPF 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | DLLs\LibEDF.dll 39 | 40 | 41 | DLLs\PdfSharp.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 4.0 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | MSBuild:Compile 62 | Designer 63 | 64 | 65 | 66 | NumberStepper.xaml 67 | 68 | 69 | 70 | MSBuild:Compile 71 | Designer 72 | 73 | 74 | App.xaml 75 | Code 76 | 77 | 78 | 79 | 80 | MainWindow.xaml 81 | Code 82 | 83 | 84 | Designer 85 | MSBuild:Compile 86 | 87 | 88 | 89 | 90 | Code 91 | 92 | 93 | True 94 | True 95 | Resources.resx 96 | 97 | 98 | True 99 | Settings.settings 100 | True 101 | 102 | 103 | ResXFileCodeGenerator 104 | Resources.Designer.cs 105 | 106 | 107 | SettingsSingleFileGenerator 108 | Settings.Designer.cs 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 123 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/ECGViewerWPF.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECGViewerWPF", "ECGViewerWPF.csproj", "{2879ACDA-12C4-465A-A64E-D258F856B66E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2879ACDA-12C4-465A-A64E-D258F856B66E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2879ACDA-12C4-465A-A64E-D258F856B66E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2879ACDA-12C4-465A-A64E-D258F856B66E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2879ACDA-12C4-465A-A64E-D258F856B66E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/ImageTools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows.Media.Imaging; 4 | using System.Windows.Media; 5 | using System.Drawing; 6 | using System.Windows; 7 | using PdfSharp.Pdf; 8 | using PdfSharp.Drawing; 9 | 10 | namespace ECGViewerWPF 11 | { 12 | public static class ImageTools 13 | { 14 | public static BitmapImage BitmapToImage(Bitmap bitmap) 15 | { 16 | using (MemoryStream memory = new MemoryStream()) 17 | { 18 | bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Png); 19 | memory.Position = 0; 20 | BitmapImage bitmapimage = new BitmapImage(); 21 | bitmapimage.BeginInit(); 22 | bitmapimage.StreamSource = memory; 23 | bitmapimage.CacheOption = BitmapCacheOption.OnLoad; 24 | bitmapimage.EndInit(); 25 | return bitmapimage; 26 | } 27 | } 28 | 29 | public static Bitmap BitmapImage2Bitmap(BitmapImage bitmapImage) 30 | { 31 | using (MemoryStream outStream = new MemoryStream()) 32 | { 33 | BitmapEncoder enc = new BmpBitmapEncoder(); 34 | enc.Frames.Add(BitmapFrame.Create(bitmapImage)); 35 | enc.Save(outStream); 36 | Bitmap bitmap = new Bitmap(outStream); 37 | return new Bitmap(bitmap); 38 | } 39 | } 40 | 41 | public static Bitmap CreateBitmap(int width, int height, float dpi) 42 | { 43 | Bitmap bmp = new Bitmap(1, 1); 44 | 45 | try { bmp = new Bitmap(width, height); } 46 | catch (ArgumentException ex) 47 | { 48 | //This error could occur if the bitmap takes up too much memory. 49 | Console.WriteLine("Error creating bitmap.\n" + ex.Message); 50 | } 51 | 52 | bmp.SetResolution(dpi, dpi); 53 | return bmp; 54 | } 55 | 56 | public static void DrawGrid(System.Windows.Media.DrawingContext dc, float square, 57 | System.Windows.Media.Pen pen, double maxWidth, double maxHeight) 58 | { 59 | float x = -square; float y = -square; 60 | 61 | while ((x += square) < maxWidth) 62 | dc.DrawLine(pen, new System.Windows.Point(x, 0), 63 | new System.Windows.Point(x, maxHeight)); 64 | 65 | while ((y += square) < maxHeight) 66 | dc.DrawLine(pen, new System.Windows.Point(0, y), 67 | new System.Windows.Point(maxWidth, y)); 68 | } 69 | 70 | public static void DrawGrid(System.Drawing.Graphics g, float square, 71 | System.Drawing.Pen pen, int maxWidth, int maxHeight) 72 | { 73 | float x = -square; float y = -square; 74 | 75 | while ((x += square) < maxWidth) 76 | g.DrawLine(pen, new System.Drawing.Point((int)Math.Round(x), 0), 77 | new System.Drawing.Point((int)Math.Round(x), maxHeight)); 78 | 79 | while ((y += square) < maxHeight) 80 | g.DrawLine(pen, new System.Drawing.Point(0, (int)Math.Round(y)), 81 | new System.Drawing.Point(maxWidth, (int)Math.Round(y))); 82 | } 83 | 84 | public static void DrawText(System.Windows.Media.DrawingContext dc, FormattedText text, double x, double y, bool background = true) 85 | { 86 | System.Windows.Media.Brush brush = new SolidColorBrush( 87 | System.Windows.Media.Color.FromArgb(190, 255, 255, 255)); 88 | int pad = 1; 89 | 90 | //Draw semi transparent rectangle behind text 91 | Rect size = new Rect(x - pad, y - pad, text.Width + 2 * pad, text.Height + 2 * pad); 92 | dc.DrawRoundedRectangle(brush, new System.Windows.Media.Pen(brush, 0), size, pad, pad); 93 | dc.DrawText(text, new System.Windows.Point(x, y)); 94 | } 95 | 96 | public static void DrawText(System.Drawing.Graphics g, FormattedText text, float fontEmSize, double x, double y, bool background = true) 97 | { 98 | Font font = new Font("Arial", fontEmSize, System.Drawing.FontStyle.Regular); 99 | int pad = 1; 100 | SizeF textSize = g.MeasureString(text.Text, font); 101 | 102 | //Draw semi transparent rectangle behind text 103 | System.Drawing.Brush brush = new SolidBrush(System.Drawing.Color.FromArgb(190, 255, 255, 255)); 104 | Rectangle box = new Rectangle((int)x - pad, (int)y - pad, (int)textSize.Width - 1, (int)textSize.Height - 1); 105 | if (background) g.FillRectangle(brush, box); 106 | 107 | g.DrawString(text.Text, font, System.Drawing.Brushes.Black, (float)(x - 2), (float)(y - 2)); 108 | } 109 | 110 | public static void SavePdf(string filePath, System.Drawing.Image[] pageImages, string docInfoTitle = "") 111 | { 112 | PdfSharp.Pdf.PdfDocument document = new PdfDocument(); 113 | document.Info.Title = docInfoTitle; 114 | 115 | for (int i = 0; i < pageImages.Length; i++) 116 | { 117 | PdfPage page = document.AddPage(); 118 | XGraphics gfx = XGraphics.FromPdfPage(page); 119 | 120 | System.Drawing.Image img = pageImages[i]; 121 | float margin = 20; 122 | double ratio = ImageTools.GetImageResizeRatio(img, page.Width - 2 * margin, page.Height - 2 * margin); 123 | 124 | gfx.DrawImage(XImage.FromGdiPlusImage(pageImages[i]), margin, margin, ratio * img.Width, ratio * img.Height); 125 | } 126 | 127 | try { document.Save(filePath); } 128 | catch (Exception) { Console.WriteLine("Error saving PDF file."); } 129 | } 130 | 131 | public static double GetImageResizeRatio(System.Drawing.Image img, double maxWidth, double maxHeight) 132 | { 133 | double ratio = 0; 134 | 135 | // Figure out the ratio and use whichever multiplier is smaller 136 | double ratioX = maxWidth / img.Width; 137 | double ratioY = maxHeight / img.Height; 138 | ratio = ratioX < ratioY ? ratioX : ratioY; 139 | 140 | return ratio; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System.IO; 3 | using System.Windows; 4 | using System.Collections.Generic; 5 | using LibEDF_CSharp; 6 | 7 | namespace ECGViewerWPF 8 | { 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private void MenuItem_GenerateSignal_Click(object sender, RoutedEventArgs e) 17 | { 18 | float[] signalSamples = SignalGenerator.GenerateSignal(); 19 | ECG.SetSignalData(signalSamples); 20 | } 21 | 22 | private void MenuItem_FileOpen_Click(object sender, RoutedEventArgs e) 23 | { 24 | OpenFileDialog fd = new OpenFileDialog(); 25 | fd.Filter = "Signal Files (*.EDF, *.TXT, *.CSV) | *.TXT; *.EDF; *.CSV"; 26 | fd.Title = "Open RAW data file."; 27 | fd.Multiselect = false; 28 | 29 | if (fd.ShowDialog() == true) 30 | { 31 | var signalSamples = ReadSignalFile(fd.FileName); 32 | ECG.SetSignalData(signalSamples); 33 | } 34 | } 35 | 36 | private float[] ReadSignalFile(string fileName) 37 | { 38 | var signalSamples = new List(); 39 | 40 | if (File.Exists(fileName)) 41 | { 42 | string ext = Path.GetExtension(fileName).Replace(".", ""); 43 | 44 | if (ext.ToUpper() == "CSV" || ext.ToUpper() == "TXT") { 45 | using (var reader = new StreamReader(fileName)) { 46 | string line; 47 | while ((line = reader.ReadLine()) != null) { 48 | float sample; 49 | if (float.TryParse(line, out sample)) 50 | signalSamples.Add(sample); 51 | } 52 | } 53 | } 54 | 55 | if (ext.ToUpper() == "EDF") { 56 | var edfFile = new EDFFile(); 57 | edfFile.Open(fileName); 58 | if(edfFile.Signals.Length >= 1) { 59 | for (int i = 0; i < edfFile.Signals[0].Samples.Length; i++) { 60 | //NOTE: Resolution may be different for each ECG sensor 61 | float resolution = 1.8f; 62 | var sample = edfFile.Signals[0].Samples[i] * resolution; 63 | signalSamples.Add(sample); 64 | } 65 | } 66 | } 67 | } 68 | 69 | return signalSamples.ToArray(); 70 | } 71 | 72 | private void MenuItem_FileClose_Click(object sender, RoutedEventArgs e) 73 | { 74 | 75 | } 76 | 77 | private void MenuItem_FileSave_Click(object sender, RoutedEventArgs e) 78 | { 79 | 80 | } 81 | 82 | private void MenuItem_FileExit_Click(object sender, RoutedEventArgs e) 83 | { 84 | Application.Current.Shutdown(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /wpfapp/ECGViewerWPF/NumberStepper.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 |