├── .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 | ![Screenshot](Screenshot.png) 58 | 59 | -------------------------------------------------------------------------------- /SDRPluginSteps.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Creating a blank plugin for SDR# in Visual Studio

4 | 5 |
    6 |
  1. 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 |

  2. 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 |

  3. 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 |

  4. 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 |

  5. 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 |

  6. 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 |
  7. Click on Build / Rebuild Solution in the Visual Studio menu. 78 | 79 | 80 |

  8. Copy C:\Projects\SDRSharpPlugins\SDRSharp.BlankPlugin\bin\Debug\SDRSharp.BlankPlugin.dll to the SDR# installation directory. 81 | 82 | 83 |

  9. In the SDR# installation directory, add this line to the Plugins.xml file before </sharpPlugins>: 84 |
    85 |     <add key="Blank Plugin" value="SDRSharp.BlankPlugin.BlankPlugin,SDRSharp.BlankPlugin" />
    86 | 
    87 | 88 |
  10. Start SDR#, verify that the plugin appears in the sidebar. 89 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/HeatMapGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Windows.Forms; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Web.Script.Serialization; 7 | 8 | 9 | namespace SDRSharp.HeatMapView 10 | { 11 | class HeatMapGenerator 12 | { 13 | [DllImport("HeatMapGenerator.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] 14 | [return: MarshalAs(UnmanagedType.I1)] 15 | private static extern bool GenerateHeatMap(string InFileName, string OutFileName, int NoiseModel, StringBuilder Info, int InfoSize, bool PngFormat); 16 | 17 | public static HeatMapInfo Generate(string InputFileName) 18 | { 19 | Directory.CreateDirectory(Settings.DataFolder()); 20 | string HeatMapName = Settings.EnsureUniqueName(Path.GetFileNameWithoutExtension(InputFileName)); 21 | string OutputFileName = Settings.BuildFilePath(HeatMapName); 22 | 23 | StringBuilder Builder = new StringBuilder(256); 24 | 25 | Cursor.Current = Cursors.WaitCursor; 26 | try 27 | { 28 | if (GenerateHeatMap(InputFileName, OutputFileName, 1, Builder, 256, Settings.IMAGE_EXT == ".png")) 29 | return (new JavaScriptSerializer()).Deserialize(Builder.ToString()); 30 | else 31 | throw new Exception(Builder.ToString()); 32 | } 33 | finally 34 | { 35 | Cursor.Current = Cursors.Default; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/HeatMapPanel.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SDRSharp.HeatMapView 2 | { 3 | partial class HeatMapPanel 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); 33 | this.label1 = new System.Windows.Forms.Label(); 34 | this.SuspendLayout(); 35 | // 36 | // toolTip1 37 | // 38 | this.toolTip1.AutomaticDelay = 0; 39 | this.toolTip1.AutoPopDelay = 200000; 40 | this.toolTip1.InitialDelay = 200000; 41 | this.toolTip1.ReshowDelay = 200000; 42 | this.toolTip1.ShowAlways = true; 43 | // 44 | // label1 45 | // 46 | this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 47 | this.label1.AutoSize = true; 48 | this.label1.BackColor = System.Drawing.Color.DarkSlateGray; 49 | this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 50 | this.label1.ForeColor = System.Drawing.Color.White; 51 | this.label1.Location = new System.Drawing.Point(7, 134); 52 | this.label1.MaximumSize = new System.Drawing.Size(0, 13); 53 | this.label1.Name = "label1"; 54 | this.label1.Size = new System.Drawing.Size(13, 13); 55 | this.label1.TabIndex = 0; 56 | this.label1.Text = "_"; 57 | this.label1.Visible = false; 58 | // 59 | // HeatMapPanel 60 | // 61 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 62 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 63 | this.Controls.Add(this.label1); 64 | this.Cursor = System.Windows.Forms.Cursors.Cross; 65 | this.DoubleBuffered = true; 66 | this.Name = "HeatMapPanel"; 67 | this.Size = new System.Drawing.Size(743, 150); 68 | this.ClientSizeChanged += new System.EventHandler(this.HeatMapPanel_ClientSizeChanged); 69 | this.Paint += new System.Windows.Forms.PaintEventHandler(this.HitMapViewFrontControl_Paint); 70 | this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.HeatMapPanel_MouseDown); 71 | this.MouseLeave += new System.EventHandler(this.HeatMapPanel_MouseLeave); 72 | this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.HeatMapPanel_MouseMove); 73 | this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.HeatMapPanel_MouseUp); 74 | this.ResumeLayout(false); 75 | this.PerformLayout(); 76 | 77 | } 78 | 79 | #endregion 80 | 81 | private System.Windows.Forms.ToolTip toolTip1; 82 | private System.Windows.Forms.Label label1; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/HeatMapPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace SDRSharp.HeatMapView 7 | { 8 | public partial class HeatMapPanel : UserControl 9 | { 10 | private const int ScaleHeight = 25; 11 | private static readonly float[] TickMults = {2f, 2.5f, 2f}; 12 | 13 | private HeatMapViewPlugin Plugin; 14 | internal HeatMapInfo Info; 15 | private Image HeatMap; 16 | private Point MouseDownPos, MouseMovePos; 17 | private bool MouseDragging = false; 18 | private float HeatMapHertzPerPixel; 19 | private double TimeTicksPerPixel; 20 | private Rectangle VisibleRectangle; 21 | private long PanFreq; 22 | private long PanTicks; 23 | private float Zoom; 24 | 25 | 26 | //----------------------------------------------------------------------------------------- 27 | // Init 28 | //----------------------------------------------------------------------------------------- 29 | public HeatMapPanel(HeatMapViewPlugin plugin) 30 | { 31 | InitializeComponent(); 32 | Plugin = plugin; 33 | Plugin.Control.PropertyChanged += PropertyChangedEventHandler; 34 | VisibleRectangle = GetVisibleRectangle(); 35 | } 36 | 37 | internal void LoadHeatMap(HeatMapInfo NewInfo) 38 | { 39 | try 40 | { 41 | if (NewInfo == null) 42 | { 43 | ReleaseImage(); 44 | return; 45 | } 46 | 47 | //already loaded 48 | if (HeatMap != null && NewInfo.Name == Info.Name) return; 49 | 50 | Info = NewInfo; 51 | LoadImage(); 52 | 53 | PanFreq = 0; 54 | PanTicks = 0; 55 | Zoom = 1; 56 | HeatMapHertzPerPixel = (Info.EndFreq - Info.StartFreq) / (float)HeatMap.Width; 57 | TimeTicksPerPixel = (Info.EndTime - Info.StartTime).Ticks / (float)HeatMap.Height; 58 | 59 | if (Double.IsNaN(TimeTicksPerPixel) || Double.IsInfinity(TimeTicksPerPixel) || TimeTicksPerPixel <= 0 || 60 | Single.IsNaN(HeatMapHertzPerPixel) || Single.IsInfinity(HeatMapHertzPerPixel) || HeatMapHertzPerPixel <= 0) 61 | throw new Exception("Invalid dimensions"); 62 | 63 | Plugin.Settings.SelectedHeatMap = Info.Name; 64 | 65 | Invalidate(); 66 | } 67 | catch (Exception ex) 68 | { 69 | ReleaseImage(); 70 | throw new Exception(String.Format("Unable to load heat map '{0}', error: {1}", NewInfo.Name, ex.Message)); 71 | } 72 | } 73 | 74 | internal void ReleaseImage() 75 | { 76 | if (HeatMap != null) HeatMap.Dispose(); 77 | HeatMap = null; 78 | } 79 | 80 | internal void LoadImage() 81 | { 82 | Cursor.Current = Cursors.WaitCursor; 83 | try 84 | { 85 | ReleaseImage(); 86 | HeatMap = Image.FromFile(Settings.BuildFilePath(Info.Name), true); 87 | } 88 | finally 89 | { 90 | Cursor.Current = Cursors.Default; 91 | } 92 | } 93 | 94 | 95 | //----------------------------------------------------------------------------------------- 96 | // Paint 97 | //----------------------------------------------------------------------------------------- 98 | private void HitMapViewFrontControl_Paint(object sender, PaintEventArgs e) 99 | { 100 | Graphics g = e.Graphics; 101 | DrawBackground(g); 102 | 103 | if (HeatMap != null) 104 | { 105 | DrawScale(g); 106 | DrawTriangle(g); 107 | if (e.ClipRectangle.Bottom > GetScaleRectangle().Bottom) DrawHeatMap(g); 108 | } 109 | } 110 | 111 | private void DrawBackground(Graphics g) 112 | { 113 | g.FillRectangle(Brushes.LightSlateGray, GetVisibleRectangle()); 114 | g.FillRectangle(Brushes.White, GetScaleRectangle()); 115 | } 116 | 117 | private void DrawScale(Graphics g) 118 | { 119 | //tick steps 120 | float TickStep = 200; 121 | float LabelStep = 1000; 122 | for (int i = 0; i <= 24; ++i) 123 | { 124 | if (LabelStep / DisplayHertzPerPixel() > 60) break; 125 | LabelStep *= TickMults[i % 3]; 126 | TickStep *= TickMults[(i + 1) % 3]; 127 | } 128 | 129 | //label format 130 | string LabelFormat; 131 | if (LabelStep % 1000000 == 0) LabelFormat = "F0"; 132 | else if (LabelStep % 100000 == 0) LabelFormat = "F1"; 133 | else if (LabelStep % 10000 == 0) LabelFormat = "F2"; 134 | else LabelFormat = "F3"; 135 | 136 | //draw lagre ticks and labels 137 | double Freq = Convert.ToInt64(Math.Truncate((Info.StartFreq + CurrentPanFreq()) / LabelStep) * LabelStep); 138 | while (true) 139 | { 140 | int x = VisibleRectangle.Left + Convert.ToInt32((Freq - Info.StartFreq - CurrentPanFreq()) / DisplayHertzPerPixel()); 141 | if (x > Width) break; 142 | g.FillRectangle(Brushes.Black, x, 10, 1, ScaleHeight - 10); 143 | g.DrawString((Freq * 1e-6).ToString(LabelFormat), Font, Brushes.Black, new Point(x + 2, 0)); 144 | Freq += LabelStep; 145 | } 146 | 147 | //draw small ticks 148 | Freq = Convert.ToInt64(Math.Truncate((Info.StartFreq + CurrentPanFreq()) / LabelStep) * LabelStep); 149 | while (true) 150 | { 151 | int x = VisibleRectangle.Left + Convert.ToInt32((Freq - Info.StartFreq - CurrentPanFreq()) / DisplayHertzPerPixel()); 152 | if (x > Width) break; 153 | g.FillRectangle(Brushes.Black, x, 18, 1, ScaleHeight - 18); 154 | Freq += TickStep; 155 | } 156 | } 157 | 158 | private void DrawHeatMap(Graphics g) 159 | { 160 | Rectangle Rd = VisibleRectangle; 161 | Rd.Location = new Point(Rd.Left, ScaleHeight); 162 | Rd.Height -= ScaleHeight; 163 | g.FillRectangle(Brushes.LightSlateGray, Rd); 164 | 165 | Rectangle Rs = Rd; 166 | Rs.Location = new Point( 167 | Convert.ToInt32(CurrentPanFreq() / HeatMapHertzPerPixel), 168 | Convert.ToInt32(CurrentPanTicks() / TimeTicksPerPixel)); 169 | Rs.Width = Convert.ToInt32(Rs.Width / Zoom); 170 | 171 | g.DrawImage(HeatMap, Rd, Rs, GraphicsUnit.Pixel); 172 | 173 | } 174 | 175 | private void DrawTriangle(Graphics g) 176 | { 177 | long Offset = Plugin.Control.Frequency - Info.StartFreq - CurrentPanFreq(); 178 | int x = VisibleRectangle.X + Convert.ToInt32(Offset / DisplayHertzPerPixel()); 179 | int y = ScaleHeight - 1; 180 | Point p = new Point(x, y); 181 | if (!VisibleRectangle.Contains(p)) return; 182 | 183 | Point[] Points = {new Point(p.X - 6, p.Y - 6), p, new Point(p.X + 6, p.Y - 6)}; 184 | g.FillPolygon(Brushes.Lime, Points); 185 | } 186 | 187 | private void ShowLabel() 188 | { 189 | Point MousePos = PointToClient(Cursor.Position); 190 | double DisplayFreq = FreqAtMouse(); 191 | DateTime DisplayTime = TimeAtMouse(); 192 | 193 | label1.Visible = 194 | !MouseDragging && 195 | VisibleRectangle.Contains(MousePos) && 196 | MousePos.Y > ScaleHeight && 197 | DisplayFreq >= Info.StartFreq && 198 | DisplayFreq < Info.EndFreq && 199 | DisplayTime >= Info.StartTime && 200 | DisplayTime < Info.EndTime; 201 | 202 | if (label1.Visible) label1.Text = String.Format(" {0:f3} MHz {1:s}", 203 | DisplayFreq * 1e-6, DisplayTime).Replace("T", " "); 204 | } 205 | 206 | private void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e) 207 | { 208 | if (e.PropertyName == "Frequency") Invalidate(GetScaleRectangle()); 209 | } 210 | 211 | private void HeatMapPanel_ClientSizeChanged(object sender, EventArgs e) 212 | { 213 | VisibleRectangle = GetVisibleRectangle(); 214 | 215 | //increase zoom if heatmap occupies less than half of the panel 216 | if (HeatMap != null) 217 | { 218 | while (HeatMap.Width * Zoom < 0.5 * VisibleRectangle.Width) Zoom *= 2; 219 | ValidatePanFreq(ref PanFreq); 220 | ValidatePanTime(ref PanTicks); 221 | Invalidate(); 222 | } 223 | } 224 | 225 | 226 | //----------------------------------------------------------------------------------------- 227 | // Mouse 228 | //----------------------------------------------------------------------------------------- 229 | private void HeatMapPanel_MouseDown(object sender, MouseEventArgs e) 230 | { 231 | if (FreqAtMouse() > Info.EndFreq) return; 232 | if (TimeAtMouse() > Info.EndTime) return; 233 | if (e.Button == MouseButtons.Left) MouseDownPos = e.Location; 234 | } 235 | 236 | private void HeatMapPanel_MouseMove(object sender, MouseEventArgs e) 237 | { 238 | if (e.Location.Equals(MouseMovePos)) return; 239 | 240 | MouseMovePos = e.Location; 241 | if (!MouseDragging && e.Button == MouseButtons.Left) 242 | { 243 | MouseDragging = true; 244 | Cursor = Cursors.NoMove2D; 245 | label1.Visible = false; 246 | } 247 | 248 | if (MouseDragging) 249 | Invalidate(); 250 | else 251 | ShowLabel(); 252 | } 253 | 254 | private void HeatMapPanel_MouseUp(object sender, MouseEventArgs e) 255 | { 256 | if (e.Button != MouseButtons.Left) return; 257 | 258 | if (MouseDragging) 259 | { 260 | PanFreq = CurrentPanFreq(); 261 | PanTicks = CurrentPanTicks(); 262 | MouseDragging = false; 263 | Cursor = Cursors.Cross; 264 | ShowLabel(); 265 | } 266 | else 267 | Plugin.Control.Frequency = FreqAtMouse(); 268 | } 269 | 270 | private void HeatMapPanel_MouseLeave(object sender, EventArgs e) 271 | { 272 | ShowLabel(); 273 | } 274 | 275 | protected override void OnMouseWheel(MouseEventArgs e) 276 | { 277 | if (MouseDragging) return; 278 | 279 | long MouseFreq = FreqAtMouse(); 280 | if (MouseFreq > Info.EndFreq) return; 281 | 282 | if (e.Delta > 0) 283 | { 284 | if (Zoom == 4) return; 285 | Zoom *= 2; 286 | } 287 | else 288 | { 289 | if (HeatMap.Width * Zoom < VisibleRectangle.Width) return; 290 | Zoom /= 2; 291 | } 292 | 293 | PanFreq = Convert.ToInt64(MouseFreq - Info.StartFreq - e.X * DisplayHertzPerPixel()); 294 | ValidatePanFreq(ref PanFreq); 295 | Invalidate(); 296 | } 297 | 298 | 299 | //----------------------------------------------------------------------------------------- 300 | // Helper func 301 | //----------------------------------------------------------------------------------------- 302 | private Rectangle GetVisibleRectangle() 303 | { 304 | //3 leftmost pixels are not visible for unknown reason. 305 | Rectangle Result = ClientRectangle; 306 | Result.Offset(3, 0); 307 | Result.Width -= 3; 308 | return Result; 309 | } 310 | 311 | protected long FreqAtMouse() 312 | { 313 | long Freq = Info.StartFreq + CurrentPanFreq() + Convert.ToInt64((MouseMovePos.X - VisibleRectangle.Left) * DisplayHertzPerPixel()); 314 | return (long)(Math.Truncate(Freq / HeatMapHertzPerPixel) * HeatMapHertzPerPixel); 315 | } 316 | 317 | protected DateTime TimeAtMouse() 318 | { 319 | return Info.StartTime + new TimeSpan(CurrentPanTicks() + Convert.ToInt64((MouseMovePos.Y - ScaleHeight) * TimeTicksPerPixel)); 320 | } 321 | 322 | protected void ValidatePanFreq(ref long PanFreq) 323 | { 324 | long WindowWidthHz = Convert.ToInt64(VisibleRectangle.Width * DisplayHertzPerPixel()); 325 | PanFreq = Math.Max(0, Math.Min(Info.EndFreq - Info.StartFreq - WindowWidthHz, PanFreq)); 326 | PanFreq = (long)(Math.Truncate(PanFreq / HeatMapHertzPerPixel) * HeatMapHertzPerPixel); 327 | } 328 | 329 | protected void ValidatePanTime(ref long PanTicks) 330 | { 331 | long WindowHeightTicks = Convert.ToInt64(VisibleRectangle.Height * TimeTicksPerPixel); 332 | long HeatMapHeightTicks = (Info.EndTime - Info.StartTime).Ticks; 333 | PanTicks = Math.Max(0, Math.Min(HeatMapHeightTicks - WindowHeightTicks, PanTicks)); 334 | } 335 | 336 | protected float DisplayHertzPerPixel() 337 | { 338 | return HeatMapHertzPerPixel / Zoom; 339 | } 340 | 341 | protected long CurrentPanFreq() 342 | { 343 | long Freq = PanFreq; 344 | 345 | if (MouseDragging) 346 | { 347 | Freq -= Convert.ToInt64(DisplayHertzPerPixel() * (MouseMovePos.X - MouseDownPos.X)); 348 | ValidatePanFreq(ref Freq); 349 | } 350 | 351 | return Freq; 352 | } 353 | 354 | protected long CurrentPanTicks() 355 | { 356 | long Ticks = PanTicks; 357 | if (MouseDragging) 358 | { 359 | Ticks -= Convert.ToInt64(TimeTicksPerPixel * (MouseMovePos.Y - MouseDownPos.Y)); 360 | ValidatePanTime(ref Ticks); 361 | } 362 | return Ticks; 363 | } 364 | 365 | protected Rectangle GetScaleRectangle() 366 | { 367 | Rectangle r = VisibleRectangle; 368 | r.Height = ScaleHeight; 369 | return r; 370 | } 371 | 372 | } 373 | } -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/HeatMapPanel.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 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/HeatMapViewPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using SDRSharp.Common; 3 | using System.IO; 4 | using SDRSharp.HeatMapView.Properties; 5 | using System.Web.Script.Serialization; 6 | 7 | 8 | namespace SDRSharp.HeatMapView 9 | { 10 | public class HeatMapViewPlugin : ISharpPlugin 11 | { 12 | private const string ExampleInfoStr = 13 | "{\"StartFreq\":24000000,\"EndFreq\":1200000000,\"StartTime\":\"\\/Date(1529427222000)" + 14 | "\\/\",\"EndTime\":\"\\/Date(1529430803000)\\/\",\"Name\":\"Example\"}"; 15 | 16 | internal Settings Settings; 17 | internal ISharpControl Control; 18 | internal SettingsPanel SettingsPanel; 19 | internal HeatMapPanel HeatMapPanel; 20 | 21 | 22 | 23 | //----------------------------------------------------------------------------------------- 24 | // ISharpPlugin 25 | //----------------------------------------------------------------------------------------- 26 | public UserControl Gui 27 | { 28 | get { return SettingsPanel; } 29 | } 30 | 31 | public string DisplayName 32 | { 33 | get { return "HeatMap View"; } 34 | } 35 | 36 | public void Close() 37 | { 38 | Settings.Save(); 39 | } 40 | 41 | public void Initialize(ISharpControl control) 42 | { 43 | Control = control; 44 | 45 | Directory.CreateDirectory(Settings.DataFolder()); 46 | 47 | Settings = Settings.Load(); 48 | 49 | //put example if no heat maps found 50 | if (Settings.HeatMaps.Count == 0) 51 | { 52 | Resources.Example.Save(Settings.BuildFilePath("Example")); 53 | Settings.HeatMaps.Add((new JavaScriptSerializer()).Deserialize(ExampleInfoStr)); 54 | } 55 | 56 | HeatMapPanel = new HeatMapPanel(this); 57 | Control.RegisterFrontControl(HeatMapPanel, PluginPosition.Top); 58 | HeatMapPanel.Visible = false; 59 | 60 | SettingsPanel = new SettingsPanel(this); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SDRSharp.HeatMapView")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("SDRSharp.HeatMapView")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("aa1b26e9-abba-423c-9036-4d3708b4dbe8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/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 SDRSharp.HeatMapView.Properties { 12 | using System; 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", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SDRSharp.HeatMapView.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap Example { 67 | get { 68 | object obj = ResourceManager.GetObject("Example", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/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 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\img\Example.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/Resources/Example.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/Resources/Example.bmp -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/SDRSharp.HeatMapView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AA1B26E9-ABBA-423C-9036-4D3708B4DBE8} 8 | Library 9 | Properties 10 | SDRSharp.HeatMapView 11 | SDRSharp.HeatMapView 12 | v4.6 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | 38 | False 39 | ..\..\..\..\Ham\rtl-sdr\SDR#\SDRSharp.Common.dll 40 | 41 | 42 | False 43 | ..\..\..\..\Ham\rtl-sdr\SDR#\SDRSharp.PanView.dll 44 | 45 | 46 | False 47 | ..\..\..\..\Ham\rtl-sdr\SDR#\SDRSharp.Radio.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | True 65 | True 66 | Resources.resx 67 | 68 | 69 | UserControl 70 | 71 | 72 | SettingsPanel.cs 73 | 74 | 75 | 76 | UserControl 77 | 78 | 79 | HeatMapPanel.cs 80 | 81 | 82 | 83 | 84 | 85 | 86 | ResXFileCodeGenerator 87 | Resources.Designer.cs 88 | 89 | 90 | SettingsPanel.cs 91 | 92 | 93 | HeatMapPanel.cs 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | copy /Y "$(TargetPath)" "C:\Ham\rtl-sdr\SDR#\" 106 | copy /Y "$(TargetDir)$(TargetName).pdb" "C:\Ham\rtl-sdr\SDR#\" 107 | 108 | 109 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Web.Script.Serialization; 8 | using System.ComponentModel; 9 | 10 | namespace SDRSharp.HeatMapView 11 | { 12 | public class HeatMapInfo 13 | { 14 | //Name needs to be a property to appear in Listbox 15 | public string Name { get; set; } 16 | public long StartFreq, EndFreq; 17 | public DateTime StartTime, EndTime; 18 | } 19 | 20 | internal class Settings 21 | { 22 | private const string DEFAULT_FILENAME = "settings.json"; 23 | 24 | //either bmp or png format may be used for the heatmaps. 25 | //bmp heatmaps are twice as large as png, 26 | //but they are generated and loaded faster 27 | 28 | public const string IMAGE_EXT = ".bmp"; //".png"; 29 | 30 | public bool Enabled = false; 31 | public string SelectedHeatMap; 32 | public BindingList HeatMaps; 33 | 34 | internal void Save(string fileName = DEFAULT_FILENAME) 35 | { 36 | System.IO.Directory.CreateDirectory(Settings.DataFolder()); 37 | fileName = Settings.DataFolder() + fileName; 38 | File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(this)); 39 | } 40 | 41 | internal static Settings Load(string fileName = DEFAULT_FILENAME) 42 | { 43 | Settings settings = new Settings(); 44 | settings.HeatMaps = new BindingList(); 45 | 46 | fileName = Settings.DataFolder() + fileName; 47 | if (File.Exists(fileName)) 48 | settings = (new JavaScriptSerializer()).Deserialize(File.ReadAllText(fileName)); 49 | 50 | settings.VerifyHeatMaps(); 51 | 52 | return settings; 53 | } 54 | 55 | internal static string DataFolder() 56 | { 57 | return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\Afreet\Products\HeatMapView\"; 58 | } 59 | 60 | internal static string BuildFilePath(string HeatMapName) 61 | { 62 | return DataFolder() + HeatMapName + IMAGE_EXT; 63 | } 64 | 65 | internal static string EnsureUniqueName(string Name) 66 | { 67 | //remove invalid chars 68 | Name = String.Concat(Name.Split(Path.GetInvalidFileNameChars())); 69 | 70 | //name is unique 71 | if (!File.Exists(BuildFilePath(Name))) return Name; 72 | 73 | //make the name unique 74 | for (int i = 1; i < 100; ++i) 75 | { 76 | string NewName = String.Format("{0} ({1})", Name, i).Trim(); 77 | if (!File.Exists(BuildFilePath(NewName))) return NewName; 78 | } 79 | 80 | throw new Exception(String.Format("Invalid heat map name: '{0}'" + Name)); 81 | } 82 | 83 | private void VerifyHeatMaps() 84 | { 85 | for (int i = HeatMaps.Count - 1; i >= 0; --i) 86 | if (!File.Exists(BuildFilePath(HeatMaps[i].Name))) HeatMaps.RemoveAt(i); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/SettingsPanel.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SDRSharp.HeatMapView 2 | { 3 | partial class SettingsPanel 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SettingsPanel)); 33 | this.TopPanel = new System.Windows.Forms.Panel(); 34 | this.HelpButton = new System.Windows.Forms.Button(); 35 | this.EditButton = new System.Windows.Forms.Button(); 36 | this.PlusButton = new System.Windows.Forms.Button(); 37 | this.MinusButton = new System.Windows.Forms.Button(); 38 | this.EnabledCheckBox = new System.Windows.Forms.CheckBox(); 39 | this.ListBox = new System.Windows.Forms.ListBox(); 40 | this.OpenFileDialog = new System.Windows.Forms.OpenFileDialog(); 41 | this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); 42 | this.TopPanel.SuspendLayout(); 43 | this.SuspendLayout(); 44 | // 45 | // TopPanel 46 | // 47 | this.TopPanel.Controls.Add(this.HelpButton); 48 | this.TopPanel.Controls.Add(this.EditButton); 49 | this.TopPanel.Controls.Add(this.PlusButton); 50 | this.TopPanel.Controls.Add(this.MinusButton); 51 | this.TopPanel.Controls.Add(this.EnabledCheckBox); 52 | this.TopPanel.Dock = System.Windows.Forms.DockStyle.Top; 53 | this.TopPanel.Location = new System.Drawing.Point(0, 0); 54 | this.TopPanel.Name = "TopPanel"; 55 | this.TopPanel.Size = new System.Drawing.Size(250, 30); 56 | this.TopPanel.TabIndex = 2; 57 | // 58 | // HelpButton 59 | // 60 | this.HelpButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 61 | this.HelpButton.Image = ((System.Drawing.Image)(resources.GetObject("HelpButton.Image"))); 62 | this.HelpButton.Location = new System.Drawing.Point(231, 10); 63 | this.HelpButton.Name = "HelpButton"; 64 | this.HelpButton.Size = new System.Drawing.Size(19, 19); 65 | this.HelpButton.TabIndex = 6; 66 | this.toolTip1.SetToolTip(this.HelpButton, "Online Help"); 67 | this.HelpButton.UseVisualStyleBackColor = true; 68 | this.HelpButton.Click += new System.EventHandler(this.HelpButton_Click); 69 | // 70 | // EditButton 71 | // 72 | this.EditButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 73 | this.EditButton.Image = ((System.Drawing.Image)(resources.GetObject("EditButton.Image"))); 74 | this.EditButton.Location = new System.Drawing.Point(213, 10); 75 | this.EditButton.Name = "EditButton"; 76 | this.EditButton.Size = new System.Drawing.Size(19, 19); 77 | this.EditButton.TabIndex = 5; 78 | this.toolTip1.SetToolTip(this.EditButton, "Rename heat map"); 79 | this.EditButton.UseVisualStyleBackColor = true; 80 | this.EditButton.Click += new System.EventHandler(this.EditButton_Click); 81 | // 82 | // PlusButton 83 | // 84 | this.PlusButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 85 | this.PlusButton.Image = ((System.Drawing.Image)(resources.GetObject("PlusButton.Image"))); 86 | this.PlusButton.Location = new System.Drawing.Point(177, 10); 87 | this.PlusButton.Name = "PlusButton"; 88 | this.PlusButton.Size = new System.Drawing.Size(19, 19); 89 | this.PlusButton.TabIndex = 4; 90 | this.toolTip1.SetToolTip(this.PlusButton, "Create heat map from rtl_sdr data"); 91 | this.PlusButton.UseVisualStyleBackColor = true; 92 | this.PlusButton.Click += new System.EventHandler(this.PlusButton_Click); 93 | // 94 | // MinusButton 95 | // 96 | this.MinusButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 97 | this.MinusButton.Image = ((System.Drawing.Image)(resources.GetObject("MinusButton.Image"))); 98 | this.MinusButton.Location = new System.Drawing.Point(195, 10); 99 | this.MinusButton.Name = "MinusButton"; 100 | this.MinusButton.Size = new System.Drawing.Size(19, 19); 101 | this.MinusButton.TabIndex = 3; 102 | this.toolTip1.SetToolTip(this.MinusButton, "Delete heat nap"); 103 | this.MinusButton.UseVisualStyleBackColor = true; 104 | this.MinusButton.Click += new System.EventHandler(this.MinusButton_Click); 105 | // 106 | // EnabledCheckBox 107 | // 108 | this.EnabledCheckBox.AutoSize = true; 109 | this.EnabledCheckBox.Location = new System.Drawing.Point(3, 3); 110 | this.EnabledCheckBox.Name = "EnabledCheckBox"; 111 | this.EnabledCheckBox.Size = new System.Drawing.Size(65, 17); 112 | this.EnabledCheckBox.TabIndex = 2; 113 | this.EnabledCheckBox.Text = "Enabled"; 114 | this.EnabledCheckBox.UseVisualStyleBackColor = true; 115 | this.EnabledCheckBox.CheckedChanged += new System.EventHandler(this.EnabledCheckBox_CheckedChanged); 116 | // 117 | // ListBox 118 | // 119 | this.ListBox.DisplayMember = "Name"; 120 | this.ListBox.Dock = System.Windows.Forms.DockStyle.Fill; 121 | this.ListBox.FormattingEnabled = true; 122 | this.ListBox.IntegralHeight = false; 123 | this.ListBox.Location = new System.Drawing.Point(0, 30); 124 | this.ListBox.Name = "ListBox"; 125 | this.ListBox.Size = new System.Drawing.Size(250, 146); 126 | this.ListBox.TabIndex = 3; 127 | this.ListBox.DoubleClick += new System.EventHandler(this.ListBox_DoubleClick); 128 | // 129 | // OpenFileDialog 130 | // 131 | this.OpenFileDialog.DefaultExt = "txt"; 132 | this.OpenFileDialog.Filter = "rtl_power files (*.csv)|*.csv|All files (*.*)|*.*"; 133 | this.OpenFileDialog.Title = "Import rtl_power Output File"; 134 | // 135 | // SettingsPanel 136 | // 137 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 138 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 139 | this.BackColor = System.Drawing.SystemColors.Control; 140 | this.Controls.Add(this.ListBox); 141 | this.Controls.Add(this.TopPanel); 142 | this.Name = "SettingsPanel"; 143 | this.Size = new System.Drawing.Size(250, 176); 144 | this.TopPanel.ResumeLayout(false); 145 | this.TopPanel.PerformLayout(); 146 | this.ResumeLayout(false); 147 | 148 | } 149 | 150 | #endregion 151 | 152 | private System.Windows.Forms.Panel TopPanel; 153 | private System.Windows.Forms.Button PlusButton; 154 | private System.Windows.Forms.Button MinusButton; 155 | private System.Windows.Forms.CheckBox EnabledCheckBox; 156 | private System.Windows.Forms.ListBox ListBox; 157 | private System.Windows.Forms.Button HelpButton; 158 | private System.Windows.Forms.Button EditButton; 159 | private System.Windows.Forms.OpenFileDialog OpenFileDialog; 160 | private System.Windows.Forms.ToolTip toolTip1; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/SettingsPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace SDRSharp.HeatMapView 5 | { 6 | public partial class SettingsPanel : UserControl 7 | { 8 | private HeatMapViewPlugin Plugin; 9 | 10 | public SettingsPanel(HeatMapViewPlugin plugin) 11 | { 12 | InitializeComponent(); 13 | Plugin = plugin; 14 | 15 | ListBox.DataSource = Plugin.Settings.HeatMaps; 16 | ListBox.SelectedIndex = ListBox.FindString(Plugin.Settings.SelectedHeatMap); 17 | if (ListBox.SelectedIndex == -1) ListBox.SelectedIndex = Plugin.Settings.HeatMaps.Count - 1; 18 | 19 | EnabledCheckBox.Checked = Plugin.Settings.Enabled; 20 | } 21 | 22 | private void EnabledCheckBox_CheckedChanged(object sender, EventArgs e) 23 | { 24 | if (EnabledCheckBox.Checked) 25 | try 26 | { 27 | Plugin.HeatMapPanel.LoadHeatMap((HeatMapInfo)ListBox.SelectedItem); 28 | } 29 | catch (Exception ex) 30 | { 31 | MessageBox.Show(ex.Message); 32 | EnabledCheckBox.Checked = false; 33 | return; 34 | } 35 | 36 | Plugin.HeatMapPanel.Visible = EnabledCheckBox.Checked; 37 | Plugin.Settings.Enabled = EnabledCheckBox.Checked; 38 | } 39 | 40 | private void PlusButton_Click(object sender, EventArgs e) 41 | { 42 | if (OpenFileDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; 43 | 44 | try 45 | { 46 | HeatMapInfo Info = HeatMapGenerator.Generate(OpenFileDialog.FileName); 47 | 48 | Plugin.Settings.HeatMaps.Add(Info); 49 | ListBox.SelectedIndex = ListBox.Items.Count-1; 50 | 51 | Plugin.HeatMapPanel.LoadHeatMap(Info); 52 | EnabledCheckBox.Enabled = true; 53 | EnabledCheckBox.Checked = true; 54 | } 55 | catch (Exception ex) 56 | { 57 | MessageBox.Show("Unable to generate heat map. Error: " + ex.Message); 58 | } 59 | } 60 | 61 | private void MinusButton_Click(object sender, EventArgs e) 62 | { 63 | //item to delete 64 | int Idx = ListBox.SelectedIndex; 65 | if (Idx < 0) return; 66 | 67 | try 68 | { 69 | //is the item being viewed? 70 | if (Plugin.Settings.HeatMaps[Idx].Name == Plugin.Settings.SelectedHeatMap) 71 | { 72 | EnabledCheckBox.Checked = false; 73 | Plugin.HeatMapPanel.ReleaseImage(); 74 | } 75 | 76 | //delete file 77 | System.IO.File.Delete(Settings.BuildFilePath(Plugin.Settings.HeatMaps[Idx].Name)); 78 | 79 | //delete from list 80 | Plugin.Settings.HeatMaps.RemoveAt(Idx); 81 | ListBox.SelectedIndex = Math.Min(ListBox.Items.Count - 1, ListBox.SelectedIndex); 82 | 83 | if (ListBox.Items.Count == 0) EnabledCheckBox.Enabled = false; 84 | } 85 | catch (Exception ex) 86 | { 87 | MessageBox.Show(String.Format("Unable to delete heat map, error: {0}", ex.Message)); 88 | } 89 | } 90 | 91 | //edit button: [✔] 92 | private void EditButton_Click(object sender, EventArgs e) 93 | { 94 | //item to rename 95 | int Idx = ListBox.SelectedIndex; 96 | if (Idx < 0) return; 97 | 98 | //new name 99 | string OldName = Plugin.Settings.HeatMaps[Idx].Name; 100 | string NewName = Microsoft.VisualBasic.Interaction.InputBox("Enter new name:", "Rename Heat Map", OldName, -1, -1); 101 | if (NewName == "") return; 102 | 103 | try 104 | { 105 | //rename 106 | NewName = Settings.EnsureUniqueName(NewName); 107 | 108 | //update heatmap name in HeatMapPanel 109 | if (Plugin.HeatMapPanel.Info.Name == OldName) 110 | { 111 | Plugin.HeatMapPanel.ReleaseImage(); 112 | try 113 | { 114 | System.IO.File.Move(Settings.BuildFilePath(OldName), Settings.BuildFilePath(NewName)); 115 | Plugin.HeatMapPanel.Info.Name = NewName; 116 | } 117 | finally 118 | { 119 | Plugin.HeatMapPanel.LoadImage(); 120 | } 121 | } 122 | else 123 | { 124 | System.IO.File.Move(Settings.BuildFilePath(OldName), Settings.BuildFilePath(NewName)); 125 | Plugin.Settings.HeatMaps[Idx].Name = NewName; 126 | } 127 | } 128 | catch (Exception ex) 129 | { 130 | MessageBox.Show(String.Format("Unable to rename heat map, error: {0}", ex.Message)); 131 | } 132 | 133 | //reflect changed name in the listbox 134 | ((CurrencyManager)ListBox.BindingContext[Plugin.Settings.HeatMaps]).Refresh(); 135 | } 136 | 137 | private void ListBox_DoubleClick(object sender, EventArgs e) 138 | { 139 | try 140 | { 141 | Plugin.HeatMapPanel.LoadHeatMap((HeatMapInfo)ListBox.SelectedItem); 142 | EnabledCheckBox.Checked = true; 143 | } 144 | catch (Exception ex) 145 | { 146 | MessageBox.Show(String.Format("Unable to load heat map, error: {0}", ex.Message)); 147 | } 148 | } 149 | 150 | private void HelpButton_Click(object sender, EventArgs e) 151 | { 152 | System.Diagnostics.Process.Start("https://github.com/VE3NEA/HeatMapView"); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/SettingsPanel.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 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 124 | xAAADsQBlSsOGwAAAIBJREFUOE+lkcsRwCAIBemNemyGWuyERuiAxIwxH94kkhz24A48QcnMfgFlBigz 125 | QJkBSlNxJnIasIuao9ogYvNzyOXQUOFbQ/XSQ1j0PSBQSw/8EHBMs8LiahMrDM5vUWpo3IFyY4xevIKb 126 | d6DMAGUj/gaug7LxO2AWKDNAOY/RAr7i1T0HWpNBAAAAAElFTkSuQmCC 127 | 128 | 129 | 130 | 152, 17 131 | 132 | 133 | 134 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 135 | xAAADsQBlSsOGwAAAKBJREFUOE+l0cERhDAIBVB6ox6aoRY6oRE6QFmXGAzr6OzhHfgGwQhm9pc2fKMN 136 | V+rC5EQ7Flczz2eXgz0hcIADydkcysGW0GgGktIcSrFSZ8zp5DKtnkqxmKZfV081UHXdHfU0Hblc3Ows 137 | lB2/0z4ND6aHUijjaBpupoclmH9ZQM5P6jWhOI0XoLP+nh7aMC4wLvNu9dSGb7ThG234nMEGAtWsusGj 138 | nyIAAAAASUVORK5CYII= 139 | 140 | 141 | 142 | 143 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 144 | xAAADsQBlSsOGwAAAGBJREFUOE/F0LENgDAMRFHvlnmyTGbJJlnkNjCkQAjlFxwgUbwi37KLhKRXMDow 145 | OjA6MDowTqOVjIgsbSTNDxinhwd61n1pLq5qdmk5dnl8cOD01x/4MDowOjA6MN6n2ABbr7LlMqASQgAA 146 | AABJRU5ErkJggg== 147 | 148 | 149 | 150 | 151 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 152 | xAAADsQBlSsOGwAAAEtJREFUOE+lzLENwDAMA0Eu7O08GDdQqhQxvsjDxRV6EErbKxgNjAZGA6OB0cBo 153 | YNwrk5zW7HbO7ed4XT8wMBoYDYwGRgOjgfG/5gE21L710K6Z1wAAAABJRU5ErkJggg== 154 | 155 | 156 | 157 | 17, 17 158 | 159 | -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/bin/Release/SDRSharp.HeatMapView.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/bin/Release/SDRSharp.HeatMapView.dll -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/img/Check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/img/Check.png -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/img/Example.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/img/Example.bmp -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/img/Minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/img/Minus.png -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/img/Plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/img/Plus.png -------------------------------------------------------------------------------- /SDRSharp.HeatMapView/img/Question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/SDRSharp.HeatMapView/img/Question.png -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VE3NEA/HeatMapView/25b3cc7839fb204bfdfa3cfec5645bd82ab9c963/Screenshot.png -------------------------------------------------------------------------------- /SdrSharpPlugins.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDRSharp.HeatMapView", "SDRSharp.HeatMapView\SDRSharp.HeatMapView.csproj", "{AA1B26E9-ABBA-423C-9036-4D3708B4DBE8}" 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 | {AA1B26E9-ABBA-423C-9036-4D3708B4DBE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AA1B26E9-ABBA-423C-9036-4D3708B4DBE8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AA1B26E9-ABBA-423C-9036-4D3708B4DBE8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AA1B26E9-ABBA-423C-9036-4D3708B4DBE8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {FC831003-E68F-4495-A9D1-E359A06B3871} 24 | EndGlobalSection 25 | EndGlobal 26 | --------------------------------------------------------------------------------