Rudimentary Binary File Reader
jamie
#pragma rtGlobals=1 // Use modern global access method.
#pragma IgorVersion=5
#pragma IndependentModule=BinaryReader
//BinaryReader helps you peek into binary files on disk to see what is inside of them.
// Useful if, e.g., you are making your own file loader and need to read headers and find offsets and see what kind of data is in the file.
//modified Nov 19 2007 // made independent module
Constant kSegmentSize = 10000 //how many bytes of the file we are going to show at one time
menu "Data"
Submenu "packages"
"Binary File Reader", BinaryReader# BinaryFileReader ()
end
end
//*****************************************************************************************************
//make the packages folder and the global variables and the control panel
Function BinaryFileReader ()
if (!(datafolderexists ("root:packages:")))
newdatafolder root:packages
endif
if (!(datafolderexists ("root:packages:BinaryReader")))
newdatafolder root:packages:BinaryReader
//make waves for the list box showing the first segment of data in the selected file
make/o/t/n = (kSegmentSize,2) root:packages:BinaryReader:FileListWave
make/o/n = (kSegmentSize,2) root:packages:BinaryReader:FileListSelWave
//Make a text wave for the listbox that shows the data after being loaded according to the chosen specs
make/t/o/n=(1,2) root:packages:BinaryReader:OutPutWave
//make a temporary wave to read data into before displaying it in the listbox
make/o/n = (kSegmentSize) root:packages:BinaryReader:TempWave
//First row of waves will show byte offset
SetDimLabel 1,0,BytePos,root:packages:BinaryReader:FileListWave
SetDimLabel 1,0,BytePos root:packages:BinaryReader:OutPutWave
//Second row will show the data as a character
SetDimLabel 1,1,Char,root:packages:BinaryReader:FileListWave
//a global string to contain a list of segments in the file for loading
string/G root:packages:BinaryReader:SegmentsList = ""
//global variable to hold reference nmber to the open file
variable/G root:packages:BinaryReader:BRrefNum = nan
//global string to display name of open file in a title box
string/G root:packages:BinaryReader:fileNameStr = ""
endif
//try to bring panel to the front
doWindow/F Binary_Reader
if (V_Flag)
return 1
endif
//make the panel, as it was not already open
NewPanel /K=1 /W=(78,105,534,452) as "Binary Reader"
DoWindow/C Binary_Reader
ListBox FileAsByteList,pos={215,25},size={101,319}
ListBox FileAsByteList,help={"Shows the selected segment of data in the selected file as characters"}
ListBox FileAsByteList,listWave=root:packages:BinaryReader:FileListWave
ListBox FileAsByteList,selWave=root:packages:BinaryReader:FileListSelWave
ListBox FileAsByteList,mode= 3,widths={20,13}
Button OpenFileButton,pos={7,2},size={67,22},proc=BRopenFIleProc,title="Open File"
TitleBox FileNameTitle,pos={79,3},size={383,20}
TitleBox FileNameTitle,variable= root:packages:BinaryReader:fileNameStr
PopupMenu SegmentsPopup,pos={7,31},size={99,20},proc=BRLoadSegmentProc,title="Load Segment"
PopupMenu SegmentsPopup,mode=1,popvalue="0",value= #"root:packages:BinaryReader:SegmentsList"
PopupMenu EndianPopUp,pos={11,121},size={178,20},title="Byte Order"
PopupMenu EndianPopUp,mode=1,popvalue="Big-endian (Mac)",value= #"\"Big-endian (Mac);Little-endian (Win)\""
PopupMenu UnSignedPopup,pos={9,96},size={134,20},title="Integer Data is"
PopupMenu UnSignedPopup,mode=1,popvalue="Signed",value= #"\"Signed;UnSigned\""
PopupMenu DataFormatPopUp,pos={10,72},size={140,20},title="Data Format"
PopupMenu DataFormatPopUp,mode=1,popvalue="1 byte int",value= #"\"1 byte int;2 byte int (word);4 byte int;4 byte float;8 byte float (double)\""
Button ShowSelectionButton,pos={39,149},size={101,27},proc=BRShowSelection,title="Show Selection"
ListBox OutPutList,pos={323,27},size={129,315}
ListBox OutPutList,listWave=root:packages:BinaryReader:OutPutWave
SetWindow kwTopWin,hook=BRCloseHook
EndMacro
//*****************************************************************************************************
//Close the open file when the Binary_Reader panel is closed and also kill the packages folder
Function BRCloseHook (infoStr)
String InfoStr
if ((cmpstr (stringByKey ("EVENT", infoStr), "kill")) == 0)
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable LocalRefNum = BRrefNum
FStatus LocalRefNum
if (V_Flag)
Close LocalRefNum
endif
killdatafolder/Z root:packages:BinaryReader
endif
end
//*****************************************************************************************************
//Open a file on disk, save the refernce number in a global variable, and load the first segment of the file
Function BRopenFIleProc(ctrlName) : ButtonControl
String ctrlName
//make reference to global variables
SVAR fileNameStr =root:packages:binaryReader:fileNameStr
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
SVAR segmentsList = root:packages:BinaryReader:SegmentsList
segmentsList = ""
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE fileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
//close the file that was previously open, if any
FStatus localRefNum
if (V_Flag)
Close localRefNum
BRrefNum = Nan
endif
//Open a new file chosen by the user and save the refnum in the global variable
Open /M="Choose a file to examine"/R localRefNum
fileNameStr = S_fileName
if ((cmpstr (S_fileName, "")) == 0)
BRrefNum = nan
fileListWave [] [1] = ""
return 0
else
BRrefNum = localRefNum
endif
//Find the number of segments in the file, and make the list of segments used in the popmenu
FStatus localRefNum
variable ii, numSegments = ceil (V_logEOF/kSegmentSize)
for (ii = 0; ii < numSegments; ii += 1)
segmentsList += num2str (ii) + ";"
endfor
//Load the first Segment in the file
BRLoadSegmentProc("",1,"")
PopupMenu SegmentsPopup mode=1
End
//*****************************************************************************************************
//Show the data selected in the file listbox in the output list box by loading the data from the file with the selected options
Function BRShowSelection(ctrlName) : ButtonControl
String ctrlName
//Make references to globals
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE fileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
WAVE/T outputWave = root:packages:BinaryReader:OutPutWave
//make sure file reference is valid
FStatus localRefNum
if (!(V_Flag))
SVAR FileNameStr = root:packages:BinaryReader:FileNameStr
doalert 0, "The selected file, " + FileNameStr +", is not open."
FileNameStr = ""
return 1
endif
//find the start and end of the selection in the file listbox
variable ii, startpos =0,numBytes=0
for (ii = 0; ii < kSegmentSize; ii += 1)
if (fileListSelWave [ii] [0] == 1)
break
endif
endfor
StartPos = ii
for (;ii < kSegmentSize; ii += 1)
if (fileListSelWave [ii] [0] == 0)
break
endif
endfor
numBytes = (ii - StartPos)
//we will read the selected bit of the file into the temporary wave according to the choices on these panel popups
controlinfo DataFormatPopUp
variable DataFormat = V_Value //choices in menu listed in same order as for /F= option in FBinRead command
controlinfo UnSignedPopup
variable UnSigned = V_Value -1 //0 for Signed (default in FBInRead) 1 for unsigned (requires/U option)
controlinfo EndianPopUp
variable byteOrder = V_Value + 1 //byteorder will be 2 for bigendian, 3 for small endian to specify endian in FBinRead command
//set the numtype variable according to Wavemetrics conventions
variable theNumType = 0
variable dataBytes //this variable will hold the number of bytes per data point
if ((DataFormat < 4) && (UnSigned))
theNumType += 64
endif
switch (DataFormat)
case 1: //byte
theNumType += 8
dataBytes = 1
SetDimLabel 1,1,Byte,root:packages:BinaryReader:OutPutWave
break
case 2: // 2 byte word
theNumtype += 16
numbytes =floor(numBytes/2)
dataBytes = 2
SetDimLabel 1,1,Word,root:packages:BinaryReader:OutPutWave
break
case 3: // 32 bit int
theNumtype += 32
numBytes = floor(numBytes/4)
dataBytes = 4
SetDimLabel 1,1,LongInt,root:packages:BinaryReader:OutPutWave
break
case 4: //32 bit float
theNumType += 2
numBytes= floor(numBytes/4)
dataBytes = 4
SetDimLabel 1,1,Float,root:packages:BinaryReader:OutPutWave
break
case 5: //64 bit floating point
theNumType += 4
numBytes=floor(numBytes/8)
databytes = 8
SetDimLabel 1,1,Double,root:packages:BinaryReader:OutPutWave
break
default:
doalert 0, "uh oh, the Numtype Switch did not recognize the value, " + num2str (DataFormat) + "."
return 1
break
endswitch
//check that data is selected
if (numBytes == 0)
doalert 0, "First select part of the file to show. For multibyte data formats, you need to select enough bytes to show at least one point"
return 1
endif
//Redimension the temp wave for the amount and kind of data expected
Redimension/N = (numBytes)/Y=(theNumType) TempWave
Redimension/N = ((numBytes),2) OutPutWave
//load the selected data into the temp wave
fsetpos localRefNum, (str2num (fileListWave [StartPos] [0]))
if (UnSigned)
FBinRead /B=(byteOrder)/F=(DataFormat) localRefNum, TempWave
else
FBinRead /B=(byteOrder)/F=(DataFormat)/U localRefNum, TempWave
endif
//Show the data in the output wave
OutPutWave [] [0] = num2Str (str2Num (fileListWave [StartPos] [0])+ (p * databytes))
OutPutWave [] [1]= num2str (tempwave [p])
End
//*****************************************************************************************************
//Load the segment selected from the popup menu
Function BRLoadSegmentProc(ctrlName,popNum,popStr) : PopupMenuControl
String ctrlName
Variable popNum
String popStr
//References to globals
SVAR fileNameStr =root:packages:binaryReader:fileNameStr
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE FileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
popnum -= 1 //should be 0 based
//check that the file reference is valid
fstatus localRefNum
if (!(V_Flag))
doAlert 0, "The file, " + fileNameStr + " is not open."
FileNameStr = ""
endif
//load the segment into the temp wave. If it is last segment, only load to the end of the file
if (V_logEOF < ((popNum+1) * kSegmentSize))
redimension/n = (V_logEOF-(popNum * kSegmentSize)) tempwave
redimension/n = ((V_logEOF-(popNum * kSegmentSize)), 2) fileListWave, fileListSelWave
else
redimension/n = (kSegmentSize) tempwave
redimension/n = ((kSegmentSize), 2) fileListWave, fileListSelWave
endif
FSetPos localRefNum, (popNum * kSegmentSize)
FBinRead/f=1/u localRefNum, tempwave
//show the loaded segment in the fileListBox in char format. Print out the most common escape codes
variable ii, points = numpnts (tempwave)
for (ii=0;ii< points;ii+=1)
switch (tempWave [ii])
case 0:
fileListWave [ii] [1] = "NUL"
break
case 9:
fileListWave [ii] [1] = "HorTab"
break
case 10:
fileListWave [ii] [1] = "LineFeed"
break
case 11:
fileListWave [ii] [1] = "VerTab"
break
case 12:
fileListWave [ii] [1] = "FormFeed"
break
case 13:
fileListWave [ii] [1] = "Return"
break
default:
if (tempwave [ii] < 32)
fileListWave [ii] [1] = "Other Control"
else
fileListWave [ii] [1] =num2char ( tempwave [ii])
endif
break
endswitch
endfor
FileListWave [] [0] = num2Str((popNum * kSegmentSize)+ p)
End
#pragma IgorVersion=5
#pragma IndependentModule=BinaryReader
//BinaryReader helps you peek into binary files on disk to see what is inside of them.
// Useful if, e.g., you are making your own file loader and need to read headers and find offsets and see what kind of data is in the file.
//modified Nov 19 2007 // made independent module
Constant kSegmentSize = 10000 //how many bytes of the file we are going to show at one time
menu "Data"
Submenu "packages"
"Binary File Reader", BinaryReader# BinaryFileReader ()
end
end
//*****************************************************************************************************
//make the packages folder and the global variables and the control panel
Function BinaryFileReader ()
if (!(datafolderexists ("root:packages:")))
newdatafolder root:packages
endif
if (!(datafolderexists ("root:packages:BinaryReader")))
newdatafolder root:packages:BinaryReader
//make waves for the list box showing the first segment of data in the selected file
make/o/t/n = (kSegmentSize,2) root:packages:BinaryReader:FileListWave
make/o/n = (kSegmentSize,2) root:packages:BinaryReader:FileListSelWave
//Make a text wave for the listbox that shows the data after being loaded according to the chosen specs
make/t/o/n=(1,2) root:packages:BinaryReader:OutPutWave
//make a temporary wave to read data into before displaying it in the listbox
make/o/n = (kSegmentSize) root:packages:BinaryReader:TempWave
//First row of waves will show byte offset
SetDimLabel 1,0,BytePos,root:packages:BinaryReader:FileListWave
SetDimLabel 1,0,BytePos root:packages:BinaryReader:OutPutWave
//Second row will show the data as a character
SetDimLabel 1,1,Char,root:packages:BinaryReader:FileListWave
//a global string to contain a list of segments in the file for loading
string/G root:packages:BinaryReader:SegmentsList = ""
//global variable to hold reference nmber to the open file
variable/G root:packages:BinaryReader:BRrefNum = nan
//global string to display name of open file in a title box
string/G root:packages:BinaryReader:fileNameStr = ""
endif
//try to bring panel to the front
doWindow/F Binary_Reader
if (V_Flag)
return 1
endif
//make the panel, as it was not already open
NewPanel /K=1 /W=(78,105,534,452) as "Binary Reader"
DoWindow/C Binary_Reader
ListBox FileAsByteList,pos={215,25},size={101,319}
ListBox FileAsByteList,help={"Shows the selected segment of data in the selected file as characters"}
ListBox FileAsByteList,listWave=root:packages:BinaryReader:FileListWave
ListBox FileAsByteList,selWave=root:packages:BinaryReader:FileListSelWave
ListBox FileAsByteList,mode= 3,widths={20,13}
Button OpenFileButton,pos={7,2},size={67,22},proc=BRopenFIleProc,title="Open File"
TitleBox FileNameTitle,pos={79,3},size={383,20}
TitleBox FileNameTitle,variable= root:packages:BinaryReader:fileNameStr
PopupMenu SegmentsPopup,pos={7,31},size={99,20},proc=BRLoadSegmentProc,title="Load Segment"
PopupMenu SegmentsPopup,mode=1,popvalue="0",value= #"root:packages:BinaryReader:SegmentsList"
PopupMenu EndianPopUp,pos={11,121},size={178,20},title="Byte Order"
PopupMenu EndianPopUp,mode=1,popvalue="Big-endian (Mac)",value= #"\"Big-endian (Mac);Little-endian (Win)\""
PopupMenu UnSignedPopup,pos={9,96},size={134,20},title="Integer Data is"
PopupMenu UnSignedPopup,mode=1,popvalue="Signed",value= #"\"Signed;UnSigned\""
PopupMenu DataFormatPopUp,pos={10,72},size={140,20},title="Data Format"
PopupMenu DataFormatPopUp,mode=1,popvalue="1 byte int",value= #"\"1 byte int;2 byte int (word);4 byte int;4 byte float;8 byte float (double)\""
Button ShowSelectionButton,pos={39,149},size={101,27},proc=BRShowSelection,title="Show Selection"
ListBox OutPutList,pos={323,27},size={129,315}
ListBox OutPutList,listWave=root:packages:BinaryReader:OutPutWave
SetWindow kwTopWin,hook=BRCloseHook
EndMacro
//*****************************************************************************************************
//Close the open file when the Binary_Reader panel is closed and also kill the packages folder
Function BRCloseHook (infoStr)
String InfoStr
if ((cmpstr (stringByKey ("EVENT", infoStr), "kill")) == 0)
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable LocalRefNum = BRrefNum
FStatus LocalRefNum
if (V_Flag)
Close LocalRefNum
endif
killdatafolder/Z root:packages:BinaryReader
endif
end
//*****************************************************************************************************
//Open a file on disk, save the refernce number in a global variable, and load the first segment of the file
Function BRopenFIleProc(ctrlName) : ButtonControl
String ctrlName
//make reference to global variables
SVAR fileNameStr =root:packages:binaryReader:fileNameStr
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
SVAR segmentsList = root:packages:BinaryReader:SegmentsList
segmentsList = ""
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE fileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
//close the file that was previously open, if any
FStatus localRefNum
if (V_Flag)
Close localRefNum
BRrefNum = Nan
endif
//Open a new file chosen by the user and save the refnum in the global variable
Open /M="Choose a file to examine"/R localRefNum
fileNameStr = S_fileName
if ((cmpstr (S_fileName, "")) == 0)
BRrefNum = nan
fileListWave [] [1] = ""
return 0
else
BRrefNum = localRefNum
endif
//Find the number of segments in the file, and make the list of segments used in the popmenu
FStatus localRefNum
variable ii, numSegments = ceil (V_logEOF/kSegmentSize)
for (ii = 0; ii < numSegments; ii += 1)
segmentsList += num2str (ii) + ";"
endfor
//Load the first Segment in the file
BRLoadSegmentProc("",1,"")
PopupMenu SegmentsPopup mode=1
End
//*****************************************************************************************************
//Show the data selected in the file listbox in the output list box by loading the data from the file with the selected options
Function BRShowSelection(ctrlName) : ButtonControl
String ctrlName
//Make references to globals
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE fileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
WAVE/T outputWave = root:packages:BinaryReader:OutPutWave
//make sure file reference is valid
FStatus localRefNum
if (!(V_Flag))
SVAR FileNameStr = root:packages:BinaryReader:FileNameStr
doalert 0, "The selected file, " + FileNameStr +", is not open."
FileNameStr = ""
return 1
endif
//find the start and end of the selection in the file listbox
variable ii, startpos =0,numBytes=0
for (ii = 0; ii < kSegmentSize; ii += 1)
if (fileListSelWave [ii] [0] == 1)
break
endif
endfor
StartPos = ii
for (;ii < kSegmentSize; ii += 1)
if (fileListSelWave [ii] [0] == 0)
break
endif
endfor
numBytes = (ii - StartPos)
//we will read the selected bit of the file into the temporary wave according to the choices on these panel popups
controlinfo DataFormatPopUp
variable DataFormat = V_Value //choices in menu listed in same order as for /F= option in FBinRead command
controlinfo UnSignedPopup
variable UnSigned = V_Value -1 //0 for Signed (default in FBInRead) 1 for unsigned (requires/U option)
controlinfo EndianPopUp
variable byteOrder = V_Value + 1 //byteorder will be 2 for bigendian, 3 for small endian to specify endian in FBinRead command
//set the numtype variable according to Wavemetrics conventions
variable theNumType = 0
variable dataBytes //this variable will hold the number of bytes per data point
if ((DataFormat < 4) && (UnSigned))
theNumType += 64
endif
switch (DataFormat)
case 1: //byte
theNumType += 8
dataBytes = 1
SetDimLabel 1,1,Byte,root:packages:BinaryReader:OutPutWave
break
case 2: // 2 byte word
theNumtype += 16
numbytes =floor(numBytes/2)
dataBytes = 2
SetDimLabel 1,1,Word,root:packages:BinaryReader:OutPutWave
break
case 3: // 32 bit int
theNumtype += 32
numBytes = floor(numBytes/4)
dataBytes = 4
SetDimLabel 1,1,LongInt,root:packages:BinaryReader:OutPutWave
break
case 4: //32 bit float
theNumType += 2
numBytes= floor(numBytes/4)
dataBytes = 4
SetDimLabel 1,1,Float,root:packages:BinaryReader:OutPutWave
break
case 5: //64 bit floating point
theNumType += 4
numBytes=floor(numBytes/8)
databytes = 8
SetDimLabel 1,1,Double,root:packages:BinaryReader:OutPutWave
break
default:
doalert 0, "uh oh, the Numtype Switch did not recognize the value, " + num2str (DataFormat) + "."
return 1
break
endswitch
//check that data is selected
if (numBytes == 0)
doalert 0, "First select part of the file to show. For multibyte data formats, you need to select enough bytes to show at least one point"
return 1
endif
//Redimension the temp wave for the amount and kind of data expected
Redimension/N = (numBytes)/Y=(theNumType) TempWave
Redimension/N = ((numBytes),2) OutPutWave
//load the selected data into the temp wave
fsetpos localRefNum, (str2num (fileListWave [StartPos] [0]))
if (UnSigned)
FBinRead /B=(byteOrder)/F=(DataFormat) localRefNum, TempWave
else
FBinRead /B=(byteOrder)/F=(DataFormat)/U localRefNum, TempWave
endif
//Show the data in the output wave
OutPutWave [] [0] = num2Str (str2Num (fileListWave [StartPos] [0])+ (p * databytes))
OutPutWave [] [1]= num2str (tempwave [p])
End
//*****************************************************************************************************
//Load the segment selected from the popup menu
Function BRLoadSegmentProc(ctrlName,popNum,popStr) : PopupMenuControl
String ctrlName
Variable popNum
String popStr
//References to globals
SVAR fileNameStr =root:packages:binaryReader:fileNameStr
NVAR BRrefNum = root:packages:BinaryReader:BRrefNum
variable localRefNum = BRrefNum
WAVE/T fileListWave = root:packages:BinaryReader:FileListWave
WAVE FileListSelWave = root:packages:BinaryReader:FileListSelWave
WAVE tempwave = root:packages:BinaryReader:tempwave
popnum -= 1 //should be 0 based
//check that the file reference is valid
fstatus localRefNum
if (!(V_Flag))
doAlert 0, "The file, " + fileNameStr + " is not open."
FileNameStr = ""
endif
//load the segment into the temp wave. If it is last segment, only load to the end of the file
if (V_logEOF < ((popNum+1) * kSegmentSize))
redimension/n = (V_logEOF-(popNum * kSegmentSize)) tempwave
redimension/n = ((V_logEOF-(popNum * kSegmentSize)), 2) fileListWave, fileListSelWave
else
redimension/n = (kSegmentSize) tempwave
redimension/n = ((kSegmentSize), 2) fileListWave, fileListSelWave
endif
FSetPos localRefNum, (popNum * kSegmentSize)
FBinRead/f=1/u localRefNum, tempwave
//show the loaded segment in the fileListBox in char format. Print out the most common escape codes
variable ii, points = numpnts (tempwave)
for (ii=0;ii< points;ii+=1)
switch (tempWave [ii])
case 0:
fileListWave [ii] [1] = "NUL"
break
case 9:
fileListWave [ii] [1] = "HorTab"
break
case 10:
fileListWave [ii] [1] = "LineFeed"
break
case 11:
fileListWave [ii] [1] = "VerTab"
break
case 12:
fileListWave [ii] [1] = "FormFeed"
break
case 13:
fileListWave [ii] [1] = "Return"
break
default:
if (tempwave [ii] < 32)
fileListWave [ii] [1] = "Other Control"
else
fileListWave [ii] [1] =num2char ( tempwave [ii])
endif
break
endswitch
endfor
FileListWave [] [0] = num2Str((popNum * kSegmentSize)+ p)
End
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More