├── .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 | 
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 |
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 | 
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 |
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 |
27 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/NumberStepper.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Input;
6 |
7 | namespace ECGViewerWPF
8 | {
9 |
10 | public partial class NumberStepper : UserControl, INotifyPropertyChanged
11 | {
12 | public static readonly DependencyProperty NumValueProperty
13 | = DependencyProperty.Register("NumValue", typeof(double),
14 | typeof(NumberStepper), new PropertyMetadata((double)1));
15 |
16 | public static readonly DependencyProperty DecimalPlacesProperty
17 | = DependencyProperty.Register("DecimalPlaces", typeof(int),
18 | typeof(NumberStepper), new PropertyMetadata(0));
19 |
20 | public static readonly DependencyProperty MinValueProperty
21 | = DependencyProperty.Register("MinValue", typeof(double),
22 | typeof(NumberStepper), new PropertyMetadata((double)0));
23 |
24 | public static readonly DependencyProperty MaxValueProperty
25 | = DependencyProperty.Register("MaxValue", typeof(double),
26 | typeof(NumberStepper), new PropertyMetadata((double)1000));
27 |
28 | public static readonly DependencyProperty IncrementProperty
29 | = DependencyProperty.Register("Increment", typeof(double),
30 | typeof(NumberStepper), new PropertyMetadata((double)1));
31 |
32 | public delegate void ValueChanged(double oldValue, double newValue);
33 | public ValueChanged OnValueChanged { get; set; }
34 |
35 | public event PropertyChangedEventHandler PropertyChanged;
36 | private void RaisePropertyChanged(string propertyName)
37 | {
38 | if (PropertyChanged != null)
39 | {
40 | PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
41 | }
42 | }
43 |
44 | public NumberStepper() { InitializeComponent(); }
45 |
46 | public double MinValue
47 | {
48 | get { return (double)GetValue(MinValueProperty); }
49 | set { SetValue(MinValueProperty, value); }
50 | }
51 |
52 | public double MaxValue
53 | {
54 | get { return (double)GetValue(MaxValueProperty); }
55 | set
56 | {
57 | if (NumValue >= MaxValue) NumValue = MaxValue;
58 | SetValue(MaxValueProperty, value);
59 | }
60 | }
61 |
62 | public double Increment
63 | {
64 | get { return (double)GetValue(IncrementProperty); }
65 | set { SetValue(IncrementProperty, value); }
66 | }
67 |
68 | public int DecimalPlaces
69 | {
70 | get { return (int)GetValue(DecimalPlacesProperty); }
71 | set { SetValue(DecimalPlacesProperty, value); }
72 | }
73 |
74 | public double NumValue
75 | {
76 | get { return (double)GetValue(NumValueProperty); }
77 |
78 | set
79 | {
80 | double oldValue = (double)GetValue(NumValueProperty);
81 | SetValue(NumValueProperty, value);
82 | RaisePropertyChanged("NumValue");
83 | if (OnValueChanged != null) OnValueChanged(oldValue, value);
84 | }
85 | }
86 |
87 | private void CmdUp_Click(object sender, RoutedEventArgs e)
88 | {
89 | if (NumValue + Increment <= MaxValue) { NumValue += Increment; FormatText(); }
90 | }
91 |
92 | private void CmdDown_Click(object sender, RoutedEventArgs e)
93 | {
94 | if (NumValue - Increment >= MinValue) { NumValue -= Increment; FormatText(); }
95 | }
96 |
97 | private void FormatText()
98 | {
99 | string format = "0.";
100 | for (int i = 0; i < DecimalPlaces; i++) format += "0";
101 | TxtNum.Text = string.Format("{0:" + format + "}", NumValue);
102 | }
103 |
104 | private void TxtNum_TextChanged(object sender, TextChangedEventArgs e)
105 | {
106 | if (!TxtNum.IsFocused) FormatText();
107 | }
108 |
109 | private void TxtNum_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
110 | {
111 | e.Handled = !IsNumberKey(e.Key) && !IsActionKey(e.Key);
112 | if (e.Key == Key.Enter) NumValue = double.Parse(TxtNum.Text);
113 | }
114 |
115 | private void TxtNum_LostFocus(object sender, RoutedEventArgs e)
116 | {
117 | try
118 | {
119 | double num = double.Parse(TxtNum.Text);
120 | if (num > MaxValue) NumValue = MaxValue;
121 | if (num < MinValue) NumValue = MinValue;
122 | }
123 | catch (Exception) { }
124 | }
125 |
126 | private bool IsNumberKey(Key inKey)
127 | {
128 | if (inKey == Key.OemPeriod) return true;
129 |
130 | if (inKey < Key.D0 || inKey > Key.D9)
131 | {
132 | if (inKey < Key.NumPad0 || inKey > Key.NumPad9) return false;
133 | }
134 | return true;
135 | }
136 |
137 | private bool IsActionKey(Key inKey)
138 | {
139 | return inKey == Key.Delete
140 | || inKey == Key.Back
141 | || inKey == Key.Tab
142 | || inKey == Key.Return
143 | || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt);
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("ECGViewerWPF")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("ECGViewerWPF")]
15 | [assembly: AssemblyCopyright("Copyright © 2016")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ECGViewerWPF.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ECGViewerWPF.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ECGViewerWPF.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/wpfapp/ECGViewerWPF/SignalAnalysis.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml;
5 | using System.Collections.ObjectModel;
6 |
7 | namespace ECGViewerWPF
8 | {
9 | /*****************************************************************************************************
10 | * Class: SignalBeatFamily
11 | ******************************************************************************************************/
12 | public class SignalBeatFamily
13 | {
14 | public int ID { get; set; }
15 | public string Signature { get; set; }
16 | public int NumberOfBeats { get; set; }
17 |
18 | public SignalBeatFamily() { }
19 | }
20 |
21 | /******************************************************************************************************
22 | * Class: SignalBeat
23 | /******************************************************************************************************/
24 | public class SignalBeat
25 | {
26 | public int FamilyID { get; set; }
27 | public string Label { get; set; }
28 | public int Time { get; set; }
29 | public int TimeOffset { get; set; }
30 |
31 | public SignalBeat() { }
32 | public SignalBeat(XmlAttributeCollection attr)
33 | {
34 | FamilyID = Int32.Parse(attr["FamilyID"].Value);
35 | Label = attr["Label"].Value;
36 | Time = Int32.Parse(attr["Time"].Value);
37 | TimeOffset = Int32.Parse(attr["TimeOffset"].Value);
38 | }
39 | public SignalBeat(int familyID, string label, int time, int timeOffset)
40 | {
41 | FamilyID = familyID;
42 | Label = label;
43 | Time = time;
44 | TimeOffset = timeOffset;
45 | }
46 | public override string ToString()
47 | {
48 | return "" + FamilyID + " " + Label + " " + Time + " " + TimeOffset;
49 | }
50 | }
51 |
52 | /******************************************************************************************************
53 | * Class: SignalAnalysisResults
54 | /******************************************************************************************************/
55 | public class SignalAnalysis
56 | {
57 | SignalBeat[] m_beats;
58 | SignalBeatFamily[] m_beatFamilies;
59 | string[] m_signalLeads;
60 | string m_summaryInfo = "";
61 |
62 | public SignalAnalysis() { }
63 |
64 | public SignalAnalysis(string xmlFilePath)
65 | {
66 | LoadDataFromXml(xmlFilePath);
67 | }
68 |
69 | public bool HasResults
70 | {
71 | get { return m_beats != null && m_beats.Length >= 1; }
72 | }
73 |
74 | private void LoadDataFromXml(string xmlFilePath)
75 | {
76 | var beats = new List();
77 | var beatFamilies = new List();
78 |
79 | XmlDocument doc = new XmlDocument();
80 |
81 | doc.Load(xmlFilePath);
82 |
83 | XmlNode leadNode = doc.SelectSingleNode("/SignalAnalysisResults/AnalyzedLeads");
84 | if (leadNode != null) m_signalLeads = leadNode.InnerText.Split(new char[] { ',' });
85 |
86 | XmlNodeList beatNodes = doc.SelectNodes("/SignalAnalysisResults/Beats/Beat");
87 | foreach (XmlNode node in beatNodes)
88 | {
89 | SignalBeat beat = new SignalBeat(node.Attributes);
90 | beats.Add(beat);
91 | }
92 |
93 | XmlNodeList beatFamilyNodes = doc.SelectNodes("/SignalAnalysisResults/BeatFamilies/BeatFamily");
94 | foreach (XmlNode node in beatFamilyNodes)
95 | {
96 | SignalBeatFamily beatFam = new SignalBeatFamily();
97 | beatFam.ID = Int32.Parse(node.Attributes["ID"].Value);
98 | beatFam.Signature = node.Attributes["Signature"].Value;
99 | beatFam.NumberOfBeats = Int32.Parse(node.Attributes["NumberOfBeats"].Value);
100 | beatFamilies.Add(beatFam);
101 | }
102 |
103 | m_summaryInfo = (m_signalLeads != null) ? "Leads: " + string.Join(",", m_signalLeads) : "";
104 |
105 | m_summaryInfo += " Total beats: " + beatNodes.Count
106 | + " Total families: " + beatFamilyNodes.Count;
107 |
108 | m_beatFamilies = beatFamilies.ToArray();
109 | m_beats = beats.ToArray();
110 | }
111 |
112 | public SignalBeatFamily[] GetBeatFamilies()
113 | {
114 | return m_beatFamilies != null
115 | ? m_beatFamilies : new List().ToArray();
116 | }
117 |
118 | public SignalBeat[] GetBeats(int limit = 0)
119 | {
120 | return m_beats != null
121 | ? m_beats : new List().ToArray();
122 | }
123 |
124 | public string ToXml()
125 | {
126 | string strXml = "\r\n";
127 | SignalBeat[] beats = GetBeats();
128 | SignalBeatFamily[] families = GetBeatFamilies();
129 | string tab = " ";//4 spaces tab
130 |
131 | strXml += tab + "" + string.Join(",", m_signalLeads) + "\r\n";
132 |
133 | //Beat families
134 | strXml += tab + "\r\n";
135 | foreach (SignalBeatFamily f in families)
136 | {
137 | strXml += string.Format(
138 | tab + tab + "\r\n",
139 | f.ID, f.Signature, f.NumberOfBeats);
140 | }
141 | strXml += tab + "\r\n";
142 |
143 | //Beats
144 | strXml += tab + "\r\n";
145 | foreach (SignalBeat beat in beats)
146 | {
147 | strXml += string.Format(
148 | tab + tab + "\r\n",
149 | beat.FamilyID, beat.Label, beat.Time, beat.TimeOffset);
150 | }
151 | strXml += tab + "\r\n";
152 |
153 | strXml += "";
154 |
155 | return strXml;
156 | }
157 |
158 | public ObservableCollection