├── .gitignore
├── HeatMapGenerator.Delphi
├── HeatMapGen.pas
├── HeatMapGenerator.dll
├── HeatMapGenerator.dpr
├── HeatMapGenerator.dproj
├── HeatMapGenerator.res
└── SlidingFilters.pas
├── LICENSE
├── README.md
├── SDRPluginSteps.htm
├── SDRSharp.HeatMapView
├── HeatMapGenerator.cs
├── HeatMapPanel.Designer.cs
├── HeatMapPanel.cs
├── HeatMapPanel.resx
├── HeatMapViewPlugin.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── Resources
│ └── Example.bmp
├── SDRSharp.HeatMapView.csproj
├── Settings.cs
├── SettingsPanel.Designer.cs
├── SettingsPanel.cs
├── SettingsPanel.resx
├── bin
│ └── Release
│ │ └── SDRSharp.HeatMapView.dll
└── img
│ ├── Check.png
│ ├── Example.bmp
│ ├── Minus.png
│ ├── Plus.png
│ └── Question.png
├── Screenshot.png
└── SdrSharpPlugins.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Delphi temp files
2 | *.dcu
3 | *.drc
4 | *.dof
5 | *.local
6 | *.dsk
7 | *.identcache
8 |
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 | [Xx]64/
28 | [Xx]86/
29 | [Bb]uild/
30 | bld/
31 | [Bb]in/
32 | [Oo]bj/
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 | artifacts/
55 |
56 | *_i.c
57 | *_p.c
58 | *_i.h
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.svclog
79 | *.scc
80 |
81 | # Chutzpah Test files
82 | _Chutzpah*
83 |
84 | # Visual C++ cache files
85 | ipch/
86 | *.aps
87 | *.ncb
88 | *.opendb
89 | *.opensdf
90 | *.sdf
91 | *.cachefile
92 | *.VC.db
93 |
94 | # Visual Studio profiler
95 | *.psess
96 | *.vsp
97 | *.vspx
98 | *.sap
99 |
100 | # TFS 2012 Local Workspace
101 | $tf/
102 |
103 | # Guidance Automation Toolkit
104 | *.gpState
105 |
106 | # ReSharper is a .NET coding add-in
107 | _ReSharper*/
108 | *.[Rr]e[Ss]harper
109 | *.DotSettings.user
110 |
111 | # JustCode is a .NET coding add-in
112 | .JustCode
113 |
114 | # TeamCity is a build add-in
115 | _TeamCity*
116 |
117 | # DotCover is a Code Coverage Tool
118 | *.dotCover
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 |
152 | # TODO: Un-comment the next line if you do not want to checkin
153 | # your web deploy settings because they may include unencrypted
154 | # passwords
155 | #*.pubxml
156 | *.publishproj
157 |
158 | # NuGet Packages
159 | *.nupkg
160 | # The packages folder can be ignored because of Package Restore
161 | **/packages/*
162 | # except build/, which is used as an MSBuild target.
163 | !**/packages/build/
164 | # Uncomment if necessary however generally it will be regenerated when needed
165 | #!**/packages/repositories.config
166 | # NuGet v3's project.json files produces more ignoreable files
167 | *.nuget.props
168 | *.nuget.targets
169 |
170 | # Microsoft Azure Build Output
171 | csx/
172 | *.build.csdef
173 |
174 | # Microsoft Azure Emulator
175 | ecf/
176 | rcf/
177 |
178 | # Windows Store app package directory
179 | AppPackages/
180 | BundleArtifacts/
181 |
182 | # Visual Studio cache files
183 | # files ending in .cache can be ignored
184 | *.[Cc]ache
185 | # but keep track of directories ending in .cache
186 | !*.[Cc]ache/
187 |
188 | # Others
189 | ClientBin/
190 | [Ss]tyle[Cc]op.*
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.pfx
196 | *.publishsettings
197 | node_modules/
198 | orleans.codegen.cs
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # LightSwitch generated files
244 | GeneratedArtifacts/
245 | ModelManifest.xml
246 |
247 | # Paket dependency manager
248 | .paket/paket.exe
249 |
250 | # FAKE - F# Make
251 | .fake/
252 |
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/HeatMapGen.pas:
--------------------------------------------------------------------------------
1 | unit HeatMapGen;
2 |
3 | interface
4 |
5 | uses
6 | Windows, SysUtils, Classes, Graphics, Math, PngImage, SlidingFilters;
7 |
8 | type
9 | PRgbPalette = ^TRgbPalette;
10 | TRgbPalette = array[Byte] of TRgbQuad;
11 |
12 | THeatMapGenerator = class
13 | private
14 | Fmt: TFormatSettings;
15 | FInFileName, FOutFileName: TFileName;
16 | Fields: TStringList;
17 |
18 | FreqStart, FreqEnd: integer;
19 | FreqStep: Single;
20 | TimeStart, TimeEnd: string;
21 | TimeStr: string;
22 |
23 | BinCnt: integer;
24 | Data: array of TSingleArray;
25 | Spect: TSingleArray;
26 |
27 | FInfo: string;
28 |
29 | procedure AnalyzeFile;
30 | procedure ImportText;
31 | procedure SubtractNoise2D;
32 | procedure SubtractNoise1D;
33 | procedure SubtractNoiseConst;
34 | procedure MakeImage(PngFormat: boolean);
35 | function InfoToJson: string;
36 | procedure ProcessLine(S: string);
37 | procedure MakePalette(Bmp: TBitmap);
38 | public
39 | function GenerateHeatMap(const InFileName, OutFileName: PChar;
40 | NoiseModel: integer; Info: PChar; InfoSize: integer;
41 | PngFormat: boolean): boolean;
42 | end;
43 |
44 | var
45 | Gen: THeatMapGenerator;
46 |
47 |
48 | implementation
49 |
50 | { THeatMapGenerator }
51 |
52 | function THeatMapGenerator.GenerateHeatMap(const InFileName,
53 | OutFileName: PChar; NoiseModel: integer; Info: PChar;
54 | InfoSize: integer; PngFormat: boolean): boolean;
55 | begin
56 | //date and FP format in csv file
57 | GetLocaleFormatSettings(GetThreadLocale, Fmt);
58 | Fmt.DateSeparator := '-';
59 | Fmt.ShortDateFormat := 'yyyy-mm-dd';
60 | Fmt.TimeSeparator := ':';
61 | Fmt.LongTimeFormat := 'hh:nn:ss';
62 | Fmt.DecimalSeparator := '.';
63 |
64 | FInFileName := InFileName;
65 | FOutFileName := OutFileName;
66 |
67 | try
68 | Fields := TStringList.Create;
69 | try
70 | AnalyzeFile;
71 | ImportText;
72 | finally
73 | Fields.Free;
74 | end;
75 |
76 | case NoiseModel of
77 | 2: SubtractNoise2D;
78 | 1: SubtractNoise1D;
79 | else SubtractNoiseConst;
80 | end;
81 |
82 | MakeImage(PngFormat);
83 |
84 | FInfo := InfoToJson;
85 | if Length(FInfo) >= InfoSize then raise Exception.Create('File name too long');
86 | Result := true;
87 | except on E: Exception do
88 | begin
89 | FInfo := E.Message;
90 | if Length(FInfo) >= InfoSize then SetLength(FInfo, InfoSize-1);
91 | Result := false;
92 | end;
93 | end;
94 |
95 | Move(FInfo[1], Info^, (Length(FInfo)+1) * SizeOf(Char));
96 | end;
97 |
98 |
99 | //------------------------------------------------------------------------------
100 | // import csv
101 | //------------------------------------------------------------------------------
102 | procedure THeatMapGenerator.AnalyzeFile;
103 | var
104 | S: string;
105 | Fs: TFileStream;
106 | Rd: TStreamReader;
107 | begin
108 | Fs := TFileStream.Create(FInFileName, fmOpenRead or fmShareDenyNone);
109 | Rd := TStreamReader.Create(Fs);
110 | S := Rd.ReadLine;
111 | Fields.CommaText := S;
112 | TimeStr := Fields[1];
113 | FreqStart := StrToInt(Fields[2]);
114 | FreqStep := StrToFloat(Fields[4], Fmt);
115 | TimeStart := Fields[0] + 'T' + Fields[1];
116 |
117 | repeat
118 | FreqEnd := StrToInt(Fields[3]);
119 | if Rd.EndOfStream then Break;
120 | S := Rd.ReadLine;
121 | Fields.CommaText := S;
122 | until Fields[1] <> TimeStr;
123 |
124 | BinCnt := Round((FreqEnd - FreqStart) / FreqStep);
125 | SetLength(Spect, BinCnt);
126 |
127 | Rd.Free;
128 | Fs.Free;
129 | end;
130 |
131 | procedure THeatMapGenerator.ImportText;
132 | var
133 | Fs: TFileStream;
134 | Rd: TStreamReader;
135 | begin
136 | Fs := TFileStream.Create(FInFileName, fmOpenRead or fmShareDenyNone);
137 | Rd := TStreamReader.Create(Fs);
138 | while not Rd.EndOfStream do ProcessLine(Rd.ReadLine);
139 |
140 | //last line
141 | SetLength(Data, Length(Data) + 1);
142 | Data[High(Data)] := Spect;
143 |
144 | Rd.Free;
145 | Fs.Free;
146 | end;
147 |
148 | procedure THeatMapGenerator.ProcessLine(S: string);
149 | var
150 | i, Idx: integer;
151 | begin
152 | Fields.CommaText := S;
153 |
154 | if (S = '') or (Fields[1] <> TimeStr) then
155 | begin
156 | TimeEnd := Fields[0] + 'T' + Fields[1];
157 | SetLength(Data, Length(Data) + 1);
158 | Data[High(Data)] := Spect;
159 | if S = '' then Exit;
160 | TimeStr := Fields[1];
161 | Spect := nil;
162 | SetLength(Spect, BinCnt);
163 | end;
164 |
165 | Idx := Round((StrToInt(Fields[2]) - FreqStart) / FreqStep);
166 | for i:=6 to Fields.Count-1 do
167 | if ((Idx+i-6) >= 0) and ((Idx+i-6) < BinCnt) then
168 | Spect[Idx+i-6] := StrToFloat(Fields[i], Fmt);
169 | end;
170 |
171 |
172 | //------------------------------------------------------------------------------
173 | // subtract noise floor
174 | //------------------------------------------------------------------------------
175 | const
176 | MAX_BYTE = 255;
177 |
178 | procedure THeatMapGenerator.SubtractNoiseConst;
179 | var
180 | i, j: integer;
181 | MinV, MaxV: Single;
182 | MinArr: TSingleArray;
183 | begin
184 | SetLength(MinArr, BinCnt);
185 | MinV := Data[0,0];
186 | MaxV := Data[0,0];
187 |
188 | for i:=0 to High(Data) do
189 | for j:=0 to High(Data[i]) do
190 | begin
191 | MinV := Min(MinV, Data[i,j]);
192 | MaxV := Max(MaxV, Data[i,j]);
193 | end;
194 |
195 | for i:=0 to High(Data) do
196 | for j:=0 to High(Data[i]) do
197 | Data[i,j] := Max(0, Min(MAX_BYTE, (Data[i,j] - MinV) / (MaxV - MinV) * MAX_BYTE));
198 | end;
199 |
200 | procedure THeatMapGenerator.SubtractNoise1D;
201 | var
202 | i, j: integer;
203 | MaxV: Single;
204 | Noise: TSingleArray;
205 | begin
206 | Integer(Noise) := 0;
207 | SetLength(Noise, BinCnt);
208 | MaxV := Data[0,0];
209 |
210 | for i:=0 to High(Data) do
211 | for j:=0 to High(Data[i]) do
212 | Noise[j] := Min(Noise[j], Data[i,j]);
213 |
214 | //150 is an empirical filter width that makes the heat map look nice
215 | Noise := SlidingMin(Noise, 150);
216 | Noise := SlidingAvg(Noise, 150);
217 |
218 | for i:=0 to High(Data) do
219 | for j:=0 to High(Data[i]) do
220 | begin
221 | Data[i,j] := Max(0, Data[i,j] - Noise[j]);
222 | MaxV := Max(MaxV, Data[i,j]);
223 | end;
224 |
225 | for i:=0 to High(Data) do
226 | for j:=0 to High(Data[i]) do
227 | Data[i,j] := Max(0, Min(MAX_BYTE, Data[i,j] / MaxV * MAX_BYTE));
228 | end;
229 |
230 | procedure THeatMapGenerator.SubtractNoise2D;
231 | var
232 | i, j: integer;
233 | MaxV: Single;
234 | Noise: TSingleArray;
235 | begin
236 |
237 | for i:=0 to High(Data) do
238 | begin
239 | Noise := SlidingMin(Data[i], 150);
240 | Noise := SlidingAvg(Noise, 150);
241 | for j:=0 to High(Data[i]) do Data[i,j] := Max(0, Data[i,j] - Noise[j]);
242 | end;
243 |
244 | MaxV := Data[0,0];
245 | for i:=0 to High(Data) do
246 | for j:=0 to High(Data[i]) do
247 | MaxV := Max(MaxV, Data[i,j]);
248 |
249 | for i:=0 to High(Data) do
250 | for j:=0 to High(Data[i]) do
251 | Data[i,j] := Data[i,j] / MaxV * MAX_BYTE;
252 | end;
253 |
254 |
255 | //------------------------------------------------------------------------------
256 | // make image
257 | //------------------------------------------------------------------------------
258 | procedure THeatMapGenerator.MakeImage(PngFormat: boolean);
259 | var
260 | Bmp: TBitmap;
261 | x, y: integer;
262 | ScanLine: PByte;
263 | Png: TPngImage;
264 | begin
265 | Bmp := TBitmap.Create;
266 | try
267 | Bmp.PixelFormat := pf8bit;
268 | Bmp.SetSize(Length(Data[0]), Length(Data));
269 | MakePalette(Bmp);
270 |
271 | for y:=0 to High(Data) do
272 | begin
273 | ScanLine := Bmp.ScanLine[y];
274 | for x:=0 to High(Data[y]) do ScanLine[x] := Round(Data[y, x]);
275 | end;
276 |
277 | Data := nil;
278 |
279 | if PngFormat then
280 | begin
281 | Png := TPngImage.Create;
282 | try
283 | Png.Assign(Bmp);
284 | Png.SaveToFile(FOutFileName);
285 | finally Png.Free; end;
286 | end
287 | else
288 | Bmp.SaveToFile(FOutFileName);
289 | finally Bmp.Free; end;
290 | end;
291 |
292 |
293 | procedure InterpolateColors(var APalette: TRgbPalette; IdxB, IdxE: integer);
294 | var
295 | i: integer;
296 | dR, dG, dB: Single;
297 | begin
298 | dR := (APalette[IdxE].rgbRed - APalette[IdxB].rgbRed) / (IdxE - IdxB);
299 | dG := (APalette[IdxE].rgbGreen - APalette[IdxB].rgbGreen) / (IdxE - IdxB);
300 | dB := (APalette[IdxE].rgbBlue - APalette[IdxB].rgbBlue) / (IdxE - IdxB);
301 |
302 | for i:=IdxB+1 to IdxE-1 do
303 | begin
304 | APalette[i].rgbRed := APalette[IdxB].rgbRed + Round(dR * (i-IdxB));
305 | APalette[i].rgbGreen := APalette[IdxB].rgbGreen + Round(dG * (i-IdxB));
306 | APalette[i].rgbBlue := APalette[IdxB].rgbBlue + Round(dB * (i-IdxB));
307 | end;
308 | end;
309 |
310 | procedure THeatMapGenerator.MakePalette(Bmp: TBitmap);
311 | const
312 | Colors: array[0..4] of TRGBQuad = (
313 | (rgbBlue: $88; rgbGreen: $0; rgbRed: 0),
314 | (rgbBlue: $ff; rgbGreen: $ff; rgbRed:$0),
315 | (rgbBlue: $0; rgbGreen: $FF; rgbRed: $ff),
316 | (rgbBlue: 0; rgbGreen: $0; rgbRed: $ff),
317 | (rgbBlue: $Ff; rgbGreen: $FF; rgbRed: $FF));
318 | var
319 | i, i1, i2: integer;
320 | Step: Single;
321 | ColorTable: TRgbPalette;
322 | begin
323 | FillChar(ColorTable, SizeOf(ColorTable), #0);
324 | Step := MAX_BYTE / (Length(Colors) - 1);
325 | ColorTable[0] := Colors[0];
326 | for i:=1 to High(Colors) do
327 | begin
328 | i1 := Round((i-1) * Step);
329 | i2 := Round(i * Step);
330 | ColorTable[i2] := Colors[i];
331 | InterpolateColors(ColorTable, i1, i2);
332 | end;
333 |
334 | Bmp.Palette := 0;
335 | SetDibColorTable(Bmp.Canvas.Handle, 0, MAX_BYTE+1, ColorTable[0]);
336 | end;
337 |
338 |
339 |
340 | function THeatMapGenerator.InfoToJson: string;
341 | const
342 | FormatString = '{"Name":"%s","StartFreq":%d,"EndFreq":%d,' +
343 | '"StartTime":"\/Date(%d)\/","EndTime":"\/Date(%d)\/"}';
344 | var
345 | DateB, DateE: TDateTime;
346 | NetDateB, NetDateE: Int64;
347 | FileName: TFileName;
348 | begin
349 | //string to delphi date
350 | DateB := StrToDateTime(TimeStart, Fmt);
351 | if TimeEnd = ''
352 | then DateE := DateB + 1/86400
353 | else DateE := StrToDateTime(TimeEnd, Fmt);
354 |
355 | //delphi date to .net date
356 | NetDateB := Round((DateB - EncodeDate(1970,1,1)) * 86400000);
357 | NetDateE := Round((DateE - EncodeDate(1970,1,1)) * 86400000);
358 |
359 | FileName := ChangeFileExt(ExtractFileName(FOutFileName), '');
360 |
361 | Result := Format(FormatString, [FileName, FreqStart, FreqEnd, NetDateB, NetDateE]);
362 | end;
363 |
364 | initialization
365 | Gen := THeatMapGenerator.Create;
366 |
367 | finalization
368 | Gen.Free;
369 |
370 | end.
371 |
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/HeatMapGenerator.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/HeatMapGenerator.Delphi/HeatMapGenerator.dll
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/HeatMapGenerator.dpr:
--------------------------------------------------------------------------------
1 | library HeatMapGenerator;
2 | uses
3 | SysUtils,
4 | HeatMapGen in 'HeatMapGen.pas',
5 | SlidingFilters in 'SlidingFilters.pas';
6 |
7 | {$R *.res}
8 |
9 | function GenerateHeatMap(const InFileName, OutFileName: PChar;
10 | NoiseModel: integer; Info: PChar; InfoSize: integer;
11 | PngFormat: boolean): boolean; stdcall;
12 | begin
13 | Result := Gen.GenerateHeatMap(InFileName, OutFileName, NoiseModel, Info,
14 | InfoSize, PngFormat);
15 | end;
16 |
17 | exports GenerateHeatMap;
18 |
19 | begin
20 | end.
21 |
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/HeatMapGenerator.dproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {23EAD873-9694-4A1A-AD82-C7A185E237BC}
4 | 12.0
5 | HeatMapGenerator.dpr
6 | Release
7 | DCC32
8 |
9 |
10 | true
11 |
12 |
13 | true
14 | Base
15 | true
16 |
17 |
18 | true
19 | Base
20 | true
21 |
22 |
23 | HeatMapGenerator.dll
24 | true
25 | 00400000
26 | false
27 | WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)
28 | x86
29 |
30 |
31 | false
32 | RELEASE;$(DCC_Define)
33 | 0
34 | false
35 |
36 |
37 | DEBUG;$(DCC_Define)
38 |
39 |
40 |
41 | MainSource
42 |
43 |
44 |
45 |
46 | Base
47 |
48 |
49 | Cfg_2
50 | Base
51 |
52 |
53 | Cfg_1
54 | Base
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Delphi.Personality.12
63 |
64 |
65 |
66 |
67 | HeatMapGenerator.dpr
68 |
69 |
70 | False
71 | True
72 | False
73 | C:\Ham\rtl-sdr\SDR#\SDRSharp.exe
74 |
75 |
76 | True
77 | False
78 | 1
79 | 0
80 | 0
81 | 1
82 | False
83 | False
84 | False
85 | False
86 | False
87 | 4105
88 | 1252
89 |
90 |
91 |
92 |
93 | 1.0.0.1
94 |
95 |
96 |
97 |
98 |
99 | 1.0.0.0
100 |
101 |
102 |
103 |
104 | 12
105 |
106 |
107 |
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/HeatMapGenerator.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/HeatMapGenerator.Delphi/HeatMapGenerator.res
--------------------------------------------------------------------------------
/HeatMapGenerator.Delphi/SlidingFilters.pas:
--------------------------------------------------------------------------------
1 | unit SlidingFilters;
2 |
3 | interface
4 |
5 | uses
6 | SysUtils;
7 |
8 | type
9 | TSingleArray = array of Single;
10 |
11 | function SlidingMin(Arr: TSingleArray; Wid: integer): TSingleArray;
12 | function SlidingAvg(Arr: TSingleArray; Wid: integer): TSingleArray;
13 |
14 |
15 | implementation
16 |
17 | function SlidingMin(Arr: TSingleArray; Wid: integer): TSingleArray;
18 | var
19 | Len, i, j, IModLen: integer;
20 | MinAhead: TSingleArray;
21 | MinG, MinR: Single;
22 | begin
23 | Len := 2*Wid + 1;
24 | SetLength(Result, Length(Arr));
25 | SetLength(MinAhead, Length(Arr));
26 |
27 | //first block: set Result[i] to Min(Arr[0]..Arr[i+Wid])
28 |
29 | MinG := Arr[0];
30 | for i:=1 to Wid-1 do
31 | if Arr[i] < MinG then MinG := Arr[i];
32 |
33 | for i:=Wid to Len-1 do
34 | begin
35 | if Arr[i] < MinG then MinG := Arr[i];
36 | Result[i-Wid] := MinG;
37 | end;
38 |
39 | //slide window one sample at a time
40 | //set the rest of Result[]
41 |
42 | IModLen := Len-1;
43 | for i:=Len to High(Arr) do
44 | begin
45 | //Slide;
46 | if Arr[i] < MinG then MinG := Arr[i];
47 |
48 | Inc(IModLen);
49 | if IModLen = Len then
50 | begin
51 | IModLen := 0;
52 | MinG := Arr[i];
53 | MinR := Arr[i];
54 | for j:=i downto i-Len do
55 | begin
56 | MinAhead[j] := MinR;
57 | if Arr[j] < MinR then MinR := Arr[j];
58 | end;
59 | end;
60 |
61 | MinR := MinAhead[i-Len];
62 |
63 | if MinG < MinR
64 | then Result[i-Wid] := MinG
65 | else Result[i-Wid] := MinR;
66 | end;
67 |
68 | //last block: set Result[i] to Min(Arr[i-Wid]..Arr[High(Arr)])
69 |
70 | MinG := Arr[High(Arr)];
71 | for i:=High(Arr)-1 downto High(Arr)-Wid do
72 | if Arr[i] < MinG then MinG := Arr[i];
73 |
74 | for i:=High(Result) downto High(Result)-Wid+1 do
75 | begin
76 | if Arr[i-Wid] < MinG then MinG := Arr[i-Wid];
77 | Result[i] := MinG;
78 | end;
79 | end;
80 |
81 | function SlidingAvg(Arr: TSingleArray; Wid: integer): TSingleArray;
82 | var
83 | i: integer;
84 | V, Scale: Single;
85 | begin
86 | SetLength(Result, Length(Arr));
87 | Scale := 1 / (2*Wid+1);
88 |
89 | V := 0;
90 | for i:=0 to 2*Wid do
91 | begin
92 | V := V + Arr[i];
93 | Result[i div 2] := V / (i+1);
94 | end;
95 |
96 | for i:=Wid to High(Arr)-Wid-1 do
97 | begin
98 | Result[i] := V * Scale;
99 | V := V - Arr[i-Wid] + Arr[i+Wid+1];
100 | end;
101 |
102 | V := 0;
103 | for i:=0 to 2*Wid do
104 | begin
105 | V := V + Arr[High(Arr)-i];
106 | Result[High(Arr) - (i div 2)] := V / (i+1);
107 | end;
108 | end;
109 |
110 | end.
111 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright 2018 Alex Shovkoplyas VE3NEA
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HeatMap View plugin for SDRSharp
2 |
3 | The plugin allows one to generate **heatmaps** from the data collected
4 | using the [rtl_power](http://kmkeen.com/rtl-power/) software and
5 | [rtl-sdr](https://www.rtl-sdr.com/about-rtl-sdr/) receiver, and browse them in
6 | [SDR#](https://airspy.com/download/).
7 |
8 | Heatmaps is a popular way of finding signals of interest in the large segments
9 | of frequencies. With this plugin, you can browse the heatmap and instantly tune
10 | to the frequencies where interesting activity was recorded.
11 |
12 | ### Installation
13 | 1. Place these two files in the folder where SDR# is installed:
14 | * [HeatMapGenerator.dll](HeatMapGenerator.Delphi/HeatMapGenerator.dll?raw=true)
15 | * [SDRSharp.HeatMapView.dll](SDRSharp.HeatMapView/bin/Release/SDRSharp.HeatMapView.dll?raw=true)
16 |
17 | 2. Open the *Plugins.xml* file in the SDR# folder using a text editor and
18 | add this line just before the `` line:
19 | ``
20 |
21 | 3. Start SDR# and verify that the HeatMap View plugin appears in the sidebar.
22 |
23 | ### Browsing a Heatmap
24 | Tick the Enabled check box in the plugin settings panel, or double-click
25 | on a heatmap name in the list of heat maps. A new panel with a heatmap
26 | will appear above the band scope. Use these commands to browse the heatmap:
27 | * zoom in and out using the mouse wheel;
28 | * pan the heatmap by dragging the mouse;
29 | * tune the radio by clicking on a signal trace.
30 |
31 | ### Generating a Heatmap
32 | The plugin comes with a sample heatmap that you can start exploring right away,
33 | but eventually you will want to make your own heatmaps, as described below.
34 |
35 | 1. Run the **rtl_power** program to collect some spectra.
36 | 2. Click on the Plus button [+], select a CSV file produced by **rtl_power**,
37 | and click on OK.
38 |
39 | The following command line is recommended for running **rtl_power**:
40 |
41 | `rtl_power.exe -f 24M:1700M:50k -i 1s -c 0.25 -g 50 -e 1h "Example.csv"`
42 |
43 | Multiple heatmaps may be generated, with different frequency and time coverage.
44 |
45 | Use the Minus button [-] to delete the selected heatmap, and Edit button [✔]
46 | to rename it.
47 |
48 | ### Source Code
49 | The source code of the plugin is available on the terms of the
50 | [MIT license](https://opensource.org/licenses/MIT).
51 |
52 | For those who are just starting with SDR# plugin development, I have included
53 | step by step [instructions](SDRPluginSteps.htm)
54 | how to create a blank plugin project in Visual Studio.
55 |
56 | ### Screenshot
57 | 
58 |
59 |
--------------------------------------------------------------------------------
/SDRPluginSteps.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Creating a blank plugin for SDR# in Visual Studio
4 |
5 |
6 |
Start Visual Studio 2017, select in the menu: File / New / Project. In the New Project dialog, click on Visual C# / Windows Desktop / Class Library. Enter parameters:
7 |
8 |
Name: SDRSharp.BlankPlugin
9 |
Location: C:\Projects\
10 |
Solution: Create New Solution
11 |
Solution name: SDRSharpPlugins
12 |
Framework: .NET Framework 4.6
13 |
14 | Click on OK.
15 |
16 |
17 |
In the C:\Projects\SDRSharpPlugins folder, create a subfolder named Vendor.
18 | Copy these files from the SDR# installation folder to the Vendor folder:
19 |
20 |
SDRSharp.Common.dll
21 |
SDRSharp.PanView.dll
22 |
SDRSharp.Radio.dll
23 |
24 |
25 |
In the Solution Explorer panel, right-click on References, click on Add Reference. In the Reference Manager dialog, click on Browse, navigate to the C:\Projects\SDRSharpPlugins\Vendor folder, select all three dll's and click on Add. Click on OK.
26 |
27 |
28 |
In the Solution Explorer panel, right-click on Class1.cs, click on Rename, enter new name: BlankPlugin. Click on the Yes button to rename the references.
29 |
30 |
31 |
Right-click on SDRSharp.BlankPlugin in the Solution Explorer panel, click on Add / New Item. In the Add New Item dialog, click on Visual C# Items / User Control. Enter parameter:
32 | Name: BlankPluginPanel. Click on OK.
33 |
34 |
Double-click on BlankPlugin.cs in the Solution Exlporer panel. Enter this code in the BlankPlugin.cs file:
35 |
36 |
37 | using System;
38 | using System.Collections.Generic;
39 | using System.Linq;
40 | using System.Text;
41 | using System.Windows.Forms;
42 | using SDRSharp.Common;
43 |
44 | namespace SDRSharp.BlankPlugin
45 | {
46 | public class BlankPlugin : ISharpPlugin
47 | {
48 | private const string _displayName = "Blank Plugin";
49 | private ISharpControl _control;
50 | private BlankPluginPanel _guiControl;
51 |
52 | public UserControl Gui
53 | {
54 | get { return _guiControl; }
55 | }
56 |
57 | public string DisplayName
58 | {
59 | get { return _displayName; }
60 | }
61 |
62 | public void Close()
63 | {
64 | }
65 |
66 | public void Initialize(ISharpControl control)
67 | {
68 | _control = control;
69 | _guiControl = new BlankPluginPanel();
70 | }
71 | }
72 | }
73 |
74 |
75 |
76 |
77 |
Click on Build / Rebuild Solution in the Visual Studio menu.
78 |
79 |
80 |
Copy C:\Projects\SDRSharpPlugins\SDRSharp.BlankPlugin\bin\Debug\SDRSharp.BlankPlugin.dll to the SDR# installation directory.
81 |
82 |
83 |
In the SDR# installation directory, add this line to the Plugins.xml file before </sharpPlugins>:
84 |