Spaces:
Runtime error
Runtime error
# -*- coding: utf-8 -*- | |
""" | |
=================================================== | |
A python interface for ARIS files | |
=================================================== | |
Last modified on: January 31, 2017 | |
The most recent version can be found at: https://github.com/EminentCodfish/pyARIS | |
@author: Chris Rillahan | |
""" | |
import struct, array, pytz, datetime, tqdm | |
import os | |
import subprocess as sp | |
from matplotlib import cm as colormap | |
from PIL import Image, ImageFont, ImageDraw | |
import numpy as np | |
from beams import load_beam_width_data | |
class ARIS_File: | |
'This is a class container for the ARIS file headers' | |
def __init__(self, filename, version_number, FrameCount, FrameRate, HighResolution, NumRawBeams, SampleRate, SamplesPerChannel, ReceiverGain, | |
WindowStart, WindowLength, Reverse, SN, strDate, strHeaderID, UserID1, UserID2, UserID3, UserID4, StartFrame,EndFrame, | |
TimeLapse, RecordInterval, RadioSeconds, FrameInterval, Flags, AuxFlags, Sspd, Flags3D, SoftwareVersion, WaterTemp, | |
Salinity, PulseLength, TxMode, VersionFGPA, VersionPSuC, ThumbnailFI, FileSize, OptionalHeaderSize, OptionalTailSize, | |
VersionMinor, LargeLens): | |
self.filename = filename #Name of the ARIS file | |
self.version_number = version_number #File format version DDF_05 = 0x05464444 | |
#OBSOLETE: Calculate the number of frames from file size & beams*samples. | |
self.FrameCount = FrameCount #Total frames in file | |
#OBSOLETE: See frame header instead. | |
self.FrameRate = FrameRate #Initial recorded frame rate | |
#OBSOLETE: See frame header instead. | |
self.HighResolution = HighResolution #Non-zero if HF, zero if LF | |
#OBSOLETE: See frame header instead. | |
self.NumRawBeams = NumRawBeams #ARIS 3000 = 128/64, ARIS 1800 = 96/48, ARIS 1200 = 48 | |
#OBSOLETE: See frame header instead. | |
self.SampleRate = SampleRate #1/Sample Period | |
#OBSOLETE: See frame header instead. | |
self.SamplesPerChannel = SamplesPerChannel #Number of range samples in each beam | |
#OBSOLETE: See frame header instead. | |
self.ReceiverGain = ReceiverGain #Relative gain in dB: 0 - 40 | |
#OBSOLETE: See frame header instead. | |
self.WindowStart = WindowStart #Image window start range in meters (code [0..31] in DIDSON) | |
#OBSOLETE: See frame header instead. | |
self.WindowLength = WindowLength #Image window length in meters (code [0..3] in DIDSON) | |
#OBSOLETE: See frame header instead. | |
self.Reverse = Reverse #Non-zero = lens down (DIDSON) or lens up (ARIS), zero = opposite | |
self.SN = SN #Sonar serial number | |
self.strDate = strDate #Date that file was recorded | |
self.strHeaderID = strHeaderID #User input to identify file in 256 characters | |
self.UserID1 = UserID1 #User-defined integer quantity | |
self.UserID2 = UserID2 #User-defined integer quantity | |
self.UserID3 = UserID3 #User-defined integer quantity | |
self.UserID4 = UserID4 #User-defined integer quantity | |
self.StartFrame = StartFrame #First frame number from source file (for DIDSON snippet files) | |
self.EndFrame = EndFrame #Last frame number from source file (for DIDSON snippet files) | |
self.TimeLapse = TimeLapse #Non-zero indicates time lapse recording | |
self.RecordInterval = RecordInterval #Number of frames/seconds between recorded frames | |
self.RadioSeconds = RadioSeconds #Frames or seconds interval | |
self.FrameInterval = FrameInterval #Record every Nth frame | |
self.Flags = Flags #See DDF_04 file format document (OBSOLETE) | |
self.AuxFlags = AuxFlags #See DDF_04 file format document | |
#OBSOLETE: See frame header instead. | |
self.Sspd = Sspd #Sound velocity in water | |
self.Flags3D = Flags3D #See DDF_04 file format document | |
self.SoftwareVersion = SoftwareVersion #DIDSON software version that recorded the file | |
self.WaterTemp = WaterTemp #Water temperature code: 0 = 5-15C, 1 = 15-25C, 2 = 25-35C | |
self.Salinity = Salinity #Salinity code: 0 = fresh, 1 = brackish, 2 = salt | |
self.PulseLength = PulseLength #Added for ARIS but not used | |
self.TxMode = TxMode #Added for ARIS but not used | |
self.VersionFGPA = VersionFGPA #Reserved for future use | |
self.VersionPSuC = VersionPSuC #Reserved for future use | |
self.ThumbnailFI = ThumbnailFI #Frame index of frame used for thumbnail image of file | |
#OBSOLETE: Do not use; query your filesystem instead. | |
self.FileSize = FileSize #Total file size in bytes | |
self.OptionalHeaderSize = OptionalHeaderSize#Reserved for future use (Obsolete, not used) | |
self.OptionalTailSize = OptionalTailSize #Reserved for future use (Obsolete, not used) | |
self.VersionMinor = VersionMinor #DIDSON_ADJUSTED_VERSION_MINOR (Obsolete) | |
self.LargeLens = LargeLens #Non-zero if telephoto lens (large lens, hi-res lens, big lens) is present | |
def __len__(self): | |
return self.FrameCount | |
def __repr__(self): | |
return 'ARIS File: ' + self.filename | |
def info(self): | |
print('Filename: ' + str(self.filename)) | |
print('Software Version: ' + str(self.SoftwareVersion)) | |
print('ARIS S/N: ' + str(self.SN)) | |
print('File size: ' + str(self.FileSize)) | |
print('Number of Frames: ' + str(self.FrameCount)) | |
print('Beam Count: ' + str(self.NumRawBeams)) | |
print('Samples/Beam: ' + str(self.SamplesPerChannel)) | |
class ARIS_Frame(ARIS_File): | |
"""This is a class container for the ARIS frame dataPI""" | |
def __init__(self, frameindex, frametime, version, status, sonartimestamp, tsday, tshour, tsminute, tssecond, tshsecond, transmitmode, | |
windowstart, windowlength, threshold, intensity, receivergain, degc1, degc2, humidity, focus, battery, uservalue1, uservalue2, | |
uservalue3, uservalue4, uservalue5, uservalue6, uservalue7, uservalue8, velocity, depth, altitude, pitch, pitchrate, roll, | |
rollrate, heading, headingrate, compassheading, compasspitch, compassroll, latitude, longitude, sonarposition, configflags, | |
beamtilt, targetrange, targetbearing, targetpresent, firmwarerevision, flags, sourceframe, watertemp, timerperiod, sonarx, | |
sonary, sonarz, sonarpan, sonartilt, sonarroll, panpnnl, tiltpnnl, rollpnnl, vehicletime, timeggk, dateggk, qualityggk, numsatsggk, | |
dopggk, ehtggk, heavetss, yeargps, monthgps, daygps, hourgps, minutegps, secondgps, hsecondgps, sonarpanoffset, sonartiltoffset, | |
sonarrolloffset, sonarxoffset, sonaryoffset, sonarzoffset, tmatrix, samplerate, accellx, accelly, accellz, pingmode, frequencyhilow, | |
pulsewidth, cycleperiod, sampleperiod, transmitenable, framerate, soundspeed, samplesperbeam, enable150v, samplestartdelay, largelens, | |
thesystemtype, sonarserialnumber, encryptedkey, ariserrorflagsuint, missedpackets, arisappversion, available2, reorderedsamples, | |
salinity, pressure, batteryvoltage, mainvoltage, switchvoltage, focusmotormoving, voltagechanging, focustimeoutfault, focusovercurrentfault, | |
focusnotfoundfault, focusstalledfault, fpgatimeoutfault, fpgabusyfault, fpgastuckfault, cputempfault, psutempfault, watertempfault, | |
humidityfault, pressurefault, voltagereadfault, voltagewritefault, focuscurrentposition, targetpan, targettilt, targetroll, panmotorerrorcode, | |
tiltmotorerrorcode, rollmotorerrorcode, panabsposition, tiltabsposition, rollabsposition, panaccelx, panaccely, panaccelz, tiltaccelx, | |
tiltaccely, tiltaccelz, rollaccelx, rollaccely, rollaccelz, appliedsettings, constrainedsettings, invalidsettings, enableinterpacketdelay, | |
interpacketdelayperiod, uptime, arisappversionmajor, arisappversionminor, gotime, panvelocity, tiltvelocity, rollvelocity, sentinel): | |
self.frameindex = frameindex #Frame number in file | |
self.frametime = frametime #PC time stamp when recorded; microseconds since epoch (Jan 1st 1970) | |
self.version = version #ARIS file format version = 0x05464444 | |
self.status = status | |
self.sonartimestamp = sonartimestamp #On-sonar microseconds since epoch (Jan 1st 1970) | |
self.tsday = tsday | |
self.tshour = tshour | |
self.tsminute = tsminute | |
self.tssecond = tssecond | |
self.tshsecond = tshsecond | |
self.transmitmode = transmitmode | |
self.windowstart = windowstart #Window start in meters | |
self.windowlength = windowlength #Window length in meters | |
self.threshold = threshold | |
self.intensity = intensity | |
self.receivergain = receivergain #Note: 0-24 dB | |
self.degc1 = degc1 #CPU temperature (C) | |
self.degc2 = degc2 #Power supply temperature (C) | |
self.humidity = humidity #% relative humidity | |
self.focus = focus #Focus units 0-1000 | |
self.battery = battery #OBSOLETE: Unused. | |
self.uservalue1 = uservalue1 | |
self.uservalue2 = uservalue2 | |
self.uservalue3 = uservalue3 | |
self.uservalue4 = uservalue4 | |
self.uservalue5 = uservalue5 | |
self.uservalue6 = uservalue6 | |
self.uservalue7 = uservalue7 | |
self.uservalue8 = uservalue8 | |
self.velocity = velocity # Platform velocity from AUV integration | |
self.depth = depth # Platform depth from AUV integration | |
self.altitude = altitude # Platform altitude from AUV integration | |
self.pitch = pitch # Platform pitch from AUV integration | |
self.pitchrate = pitchrate # Platform pitch rate from AUV integration | |
self.roll = roll # Platform roll from AUV integration | |
self.rollrate = rollrate # Platform roll rate from AUV integration | |
self.heading = heading # Platform heading from AUV integration | |
self.headingrate = headingrate # Platform heading rate from AUV integration | |
self.compassheading = compassheading # Sonar compass heading output | |
self.compasspitch = compasspitch # Sonar compass pitch output | |
self.compassroll = compassroll # Sonar compass roll output | |
self.latitude = latitude # from auxiliary GPS sensor | |
self.longitude = longitude # from auxiliary GPS sensor | |
self.sonarposition = sonarposition # special for PNNL | |
self.configflags = configflags | |
self.beamtilt = beamtilt | |
self.targetrange = targetrange | |
self.targetbearing = targetbearing | |
self.targetpresent = targetpresent | |
self.firmwarerevision = firmwarerevision #OBSOLETE: Unused. | |
self.flags = flags | |
self.sourceframe = sourceframe # Source file frame number for CSOT output files | |
self.watertemp = watertemp # Water temperature from housing temperature sensor | |
self.timerperiod = timerperiod | |
self.sonarx = sonarx # Sonar X location for 3D processing | |
self.sonary = sonary # Sonar Y location for 3D processing | |
self.sonayz = sonarz # Sonar Z location for 3D processing | |
self.sonarpan = sonarpan # X2 pan output | |
self.sonartilt = sonartilt # X2 tilt output | |
self.sonarroll = sonarroll # X2 roll output **** End of DDF_03 frame header data **** | |
self.panpnnl = panpnnl | |
self.tiltpnnl = tiltpnnl | |
self.rollpnnl = rollpnnl | |
self.vehicletime = vehicletime # special for Bluefin Robotics HAUV or other AUV integration | |
self.timeggk = timeggk # GPS output from NMEA GGK message | |
self.dateggk = dateggk # GPS output from NMEA GGK message | |
self.qualityggk = qualityggk # GPS output from NMEA GGK message | |
self.numsatsggk = numsatsggk # GPS output from NMEA GGK message | |
self.dopggk = dopggk # GPS output from NMEA GGK message | |
self.ehtggk = ehtggk # GPS output from NMEA GGK message | |
self.heavetss = heavetss # external sensor | |
self.yeargps = yeargps # GPS year output | |
self.monthgps = monthgps # GPS month output | |
self.daygps = daygps # GPS day output | |
self.hourgps = hourgps # GPS hour output | |
self.minutegps = minutegps # GPS minute output | |
self.secondgps = secondgps # GPS second output | |
self.hsecondgps = hsecondgps # GPS 1/100th second output | |
self.sonarpanoffset = sonarpanoffset # Sonar mount location pan offset for 3D processing | |
self.sonartiltoffset = sonartiltoffset # Sonar mount location tilt offset for 3D processing | |
self.sonarrolloffset = sonarrolloffset # Sonar mount location roll offset for 3D processing | |
self.sonarxoffset = sonarxoffset # Sonar mount location X offset for 3D processing | |
self.sonaryoffset = sonaryoffset # Sonar mount location Y offset for 3D processing | |
self.sonarzoffset = sonarzoffset # Sonar mount location Z offset for 3D processing | |
self.tmatirx = tmatrix # 3D processing transformation matrix | |
self.samplerate = samplerate # Calculated as 1e6/SamplePeriod | |
self.accellx = accellx # X-axis sonar acceleration | |
self.accelly = accelly # Y-axis sonar acceleration | |
self.accellz = accellz # Z-axis sonar acceleration | |
self.pingmode = pingmode # ARIS ping mode [1..12] | |
self.frequencyhilow = frequencyhilow # 1 = HF, 0 = LF | |
self.pulsewidth = pulsewidth # Width of transmit pulse in usec, [4..100] | |
self.cycleperiod = cycleperiod # Ping cycle time in usec, [1802..65535] | |
self.sampleperiod = sampleperiod # Downrange sample rate in usec, [4..100] | |
self.tranmitenable = transmitenable # 1 = Transmit ON, 0 = Transmit OFF | |
self.framerate = framerate # Instantaneous frame rate between frame N and frame N-1 | |
self.soundspeed = soundspeed # Sound velocity in water calculated from water temperature and salinity setting | |
self.samplesperbeam = samplesperbeam # Number of downrange samples in each beam | |
self.enable150v = enable150v # 1 = 150V ON (Max Power), 0 = 150V OFF (Min Power, 12V) | |
self.samplestartdelay = samplestartdelay # Delay from transmit until start of sampling (window start) in usec, [930..65535] | |
self.largelens = largelens # 1 = telephoto lens (large lens, big lens, hi-res lens) present | |
self.thesystemtype = thesystemtype # 1 = ARIS 3000, 0 = ARIS 1800, 2 = ARIS 1200 | |
self.sonarserialnumber = sonarserialnumber # Sonar serial number as labeled on housing | |
self.encryptedkey = encryptedkey # Reserved for future use | |
self.ariserrorflagsuint = ariserrorflagsuint # Error flag code bits | |
self.missedpackets = missedpackets # Missed packet count for Ethernet statistics reporting | |
self.arisappversion = arisappversion # Version number of ArisApp sending frame data | |
self.available2 = available2 # Reserved for future use | |
self.reorderedsamples = reorderedsamples # 1 = frame data already ordered into [beam,sample] array, 0 = needs reordering | |
self.salinity = salinity # Water salinity code: 0 = fresh, 15 = brackish, 35 = salt | |
self.pressure = pressure # Depth sensor output in meters (psi) | |
self.batteryvoltage = batteryvoltage # Battery input voltage before power steering | |
self.mainvoltage = mainvoltage # Main cable input voltage before power steering | |
self.switchvoltage = switchvoltage # Input voltage after power steering | |
self.focusmotormoving = focusmotormoving # Added 14-Aug-2012 for AutomaticRecording | |
self.voltagechanging = voltagechanging # Added 16-Aug (first two bits = 12V, second two bits = 150V, 00 = not changing, 01 = turning on, 10 = turning off) | |
self.focustimeoutfault = focustimeoutfault | |
self.focusovercurrentfault = focusovercurrentfault | |
self.focusnotfoundfault = focusnotfoundfault | |
self.focusstalledfault = focusstalledfault | |
self.fpgatimeoutfault = fpgatimeoutfault | |
self.fpgabusyfault = fpgabusyfault | |
self.fpgastuckfault = fpgastuckfault | |
self.cputempfault = cputempfault | |
self.psutempfault = psutempfault | |
self.watertempfault = watertempfault | |
self.humidityfault = humidityfault | |
self.pressurefault = pressurefault | |
self.voltagereadfault = voltagereadfault | |
self.voltagewritefault = voltagewritefault | |
self.focuscurrentposition = focuscurrentposition # Focus shaft current position in motor units [0.1000] | |
self.targetpan = targetpan # Commanded pan position | |
self.targettilt = targettilt # Commanded tilt position | |
self.targetroll = targetroll # Commanded roll position | |
self.panmotorerrorcode = panmotorerrorcode | |
self.tiltmotorerrorcode = tiltmotorerrorcode | |
self.rollmotorerrorcode = rollmotorerrorcode | |
self.panabsposition = panabsposition # Low-resolution magnetic encoder absolute pan position | |
self.tiltabsposition = tiltabsposition # Low-resolution magnetic encoder absolute tilt position | |
self.rollabsposition = rollabsposition # Low-resolution magnetic encoder absolute roll position | |
self.panaccelx = panaccelx # Accelerometer outputs from AR2 CPU board sensor | |
self.panaccely = panaccely | |
self.panaccelz = panaccelz | |
self.tiltaccelx = tiltaccelx | |
self.tiltaccely = tiltaccely | |
self.tiltaccelz = tiltaccelz | |
self.rollaccelx = rollaccelx | |
self.rollaccely = rollaccely | |
self.rollccelz = rollaccelz | |
self.appliedsettings = appliedsettings # Cookie indices for command acknowlege in frame header | |
self.constrainedsettings = constrainedsettings | |
self.invalidsettings = invalidsettings | |
self.enableinterpacketdelay = enableinterpacketdelay # If true delay is added between sending out image data packets | |
self.interpacketdelayperiod = interpacketdelayperiod # packet delay factor in us (does not include function overhead time) | |
self.uptime = uptime # Total number of seconds sonar has been running | |
self.arisappverionmajor = arisappversionmajor # Major version number | |
self.arisappversionminor = arisappversionminor # Minor version number | |
self.gotime = gotime # Sonar time when frame cycle is initiated in hardware | |
self.panvelocity = panvelocity # AR2 pan velocity in degrees/second | |
self.tiltvelocity = tiltvelocity # AR2 tilt velocity in degrees/second | |
self.rollvelocity = rollvelocity # AR2 roll velocity in degrees/second | |
self.sentinel = sentinel # Used to measure the frame header size | |
def __repr__(self): | |
return 'ARIS Frame Number: ' + str(self.frameindex) | |
def info(self): | |
print('Frame Number: ' + str(self.frameindex)) | |
print('Frame Time: ' + str(datetime.datetime.fromtimestamp(self.sonartimestamp/1000000, pytz.timezone('UTC')).strftime('%Y-%m-%d %H:%M:%S.%f'))) | |
print('Frame Rate: ' + str(self.framerate)) | |
print('Window Start: ' + str(self.windowstart)) | |
print('Window Length: ' + str(self.windowlength)) | |
print('Ping Mode: ' + str(self.pingmode)) | |
print('Frequency: ' + str(self.frequencyhilow)) | |
def DataImport(filename, startFrame = 1, frameBuffer = 0): | |
"""DataImport reads in the file specified by the filename. The function populates | |
a ARIS_File data structure. This function then calls the FrameRead() method | |
to load a starting frame. | |
Parameters | |
----------- | |
filename : Input file (*.aris) | |
startFrame : The first frame to be populated into the data structure | |
frameBuffer : This parameter is passed into the FrameRead method. It adds a | |
specified number of pixels around the edges of the remapped frame. | |
Returns | |
------- | |
output_data : a ARIS_File data structure | |
frame : An ARIS_Frame data structure | |
Notes | |
------- | |
Basic frame attributes can be found by calling the file.info() method. | |
A list of all the frames attributes can be found by using dir(file), some | |
of these may or may not be used by the ARIS. | |
""" | |
try: | |
data = open(filename, 'rb') | |
except: | |
print('File Error: An error occurred trying to read the file.') | |
raise | |
#Start reading file header | |
version_number = struct.unpack('I', data.read(4))[0] | |
FrameCount = struct.unpack('I', data.read(4))[0] | |
FrameRate = struct.unpack('I', data.read(4))[0] | |
HighResolution = struct.unpack('I', data.read(4))[0] | |
NumRawBeams = struct.unpack('I', data.read(4))[0] | |
SampleRate = struct.unpack('f', data.read(4))[0] | |
SamplesPerChannel = struct.unpack('I', data.read(4))[0] | |
ReceiverGain = struct.unpack('I', data.read(4))[0] | |
WindowStart = struct.unpack('f', data.read(4))[0] | |
WindowLength = struct.unpack('f', data.read(4))[0] | |
Reverse = struct.unpack('I', data.read(4))[0] | |
SN = struct.unpack('I', data.read(4))[0] | |
strDate = struct.unpack('32s', data.read(32))[0] | |
strHeaderID = struct.unpack('256s', data.read(256))[0] | |
UserID1 = struct.unpack('i', data.read(4))[0] | |
UserID2 = struct.unpack('i', data.read(4))[0] | |
UserID3 = struct.unpack('i', data.read(4))[0] | |
UserID4 = struct.unpack('i', data.read(4))[0] | |
StartFrame = struct.unpack('I', data.read(4))[0] | |
EndFrame = struct.unpack('I', data.read(4))[0] | |
TimeLapse = struct.unpack('I', data.read(4))[0] | |
RecordInterval = struct.unpack('I', data.read(4))[0] | |
RadioSeconds = struct.unpack('I', data.read(4))[0] | |
FrameInterval = struct.unpack('I', data.read(4))[0] | |
Flags = struct.unpack('I', data.read(4))[0] | |
AuxFlags = struct.unpack('I', data.read(4))[0] | |
Sspd = struct.unpack('I', data.read(4))[0] | |
Flags3D = struct.unpack('I', data.read(4))[0] | |
SoftwareVersion = struct.unpack('I', data.read(4))[0] | |
WaterTemp = struct.unpack('I', data.read(4))[0] | |
Salinity = struct.unpack('I', data.read(4))[0] | |
PulseLength = struct.unpack('I', data.read(4))[0] | |
TxMode = struct.unpack('I', data.read(4))[0] | |
VersionFGPA = struct.unpack('I', data.read(4))[0] | |
VersionPSuC = struct.unpack('I', data.read(4))[0] | |
ThumbnailFI = struct.unpack('I', data.read(4))[0] | |
FileSize = struct.unpack('Q', data.read(8))[0] | |
OptionalHeaderSize = struct.unpack('Q', data.read(8))[0] | |
OptionalTailSize = struct.unpack('Q', data.read(8))[0] | |
VersionMinor = struct.unpack('I', data.read(4))[0] | |
LargeLens = struct.unpack('I', data.read(4))[0] | |
#Create data structure | |
output_data = ARIS_File(filename, version_number, FrameCount, FrameRate, HighResolution, NumRawBeams, SampleRate, SamplesPerChannel, ReceiverGain, | |
WindowStart, WindowLength, Reverse, SN, strDate, strHeaderID, UserID1, UserID2, UserID3, UserID4, StartFrame,EndFrame, | |
TimeLapse, RecordInterval, RadioSeconds, FrameInterval, Flags, AuxFlags, Sspd, Flags3D, SoftwareVersion, WaterTemp, | |
Salinity, PulseLength, TxMode, VersionFGPA, VersionPSuC, ThumbnailFI, FileSize, OptionalHeaderSize, OptionalTailSize, | |
VersionMinor, LargeLens) | |
#Close data file | |
data.close() | |
#Create an empty container for the lookup table | |
output_data.LUP = None | |
#Load the first frame | |
frame = FrameRead(output_data, startFrame) | |
#Return the data structure | |
return output_data, frame | |
def FrameRead(ARIS_data, frameIndex, frameBuffer = None): | |
"""The FrameRead function loads in the specified frame data from the raw ARIS data. | |
The function then calls the remapARIS() function which remaps the raw data into | |
a 2D real world projection. | |
Parameters | |
----------- | |
ARIS_data : ARIS data structure returned via pyARIS.DataImport() | |
frameIndex : frame number | |
frameBuffer : This parameter add a specified number of pixels around the edges | |
of the remapped frame. | |
Returns | |
------- | |
output : a frame data structure | |
Notes | |
------- | |
Basic frame attributes can be found by calling the frame.info() method. | |
A list of all the frames attributes can be found by using dir(frame), some | |
of these may or may not be used by the ARIS. | |
""" | |
FrameSize = ARIS_data.NumRawBeams*ARIS_data.SamplesPerChannel | |
frameoffset = (1024+(frameIndex*(1024+(FrameSize)))) | |
data = open(ARIS_data.filename, 'rb') | |
data.seek(frameoffset, 0) | |
frameindex = struct.unpack('I', data.read(4))[0] #Frame number in file | |
frametime = struct.unpack('Q', data.read(8))[0] #PC time stamp when recorded; microseconds since epoch (Jan 1st 1970) | |
version = struct.unpack('I', data.read(4))[0] #ARIS file format version = 0x05464444 | |
status = struct.unpack('I', data.read(4))[0] | |
sonartimestamp = struct.unpack('Q', data.read(8))[0] #On-sonar microseconds since epoch (Jan 1st 1970) | |
tsday = struct.unpack('I', data.read(4))[0] | |
tshour = struct.unpack('I', data.read(4))[0] | |
tsminute = struct.unpack('I', data.read(4))[0] | |
tssecond = struct.unpack('I', data.read(4))[0] | |
tshsecond = struct.unpack('I', data.read(4))[0] | |
transmitmode = struct.unpack('I', data.read(4))[0] | |
windowstart = struct.unpack('f', data.read(4))[0] #Window start in meters | |
windowlength = struct.unpack('f', data.read(4))[0] #Window length in meters | |
threshold = struct.unpack('I', data.read(4))[0] | |
intensity = struct.unpack('i', data.read(4))[0] | |
receivergain = struct.unpack('I', data.read(4))[0] #Note: 0-24 dB | |
degc1 = struct.unpack('I', data.read(4))[0] #CPU temperature (C) | |
degc2 = struct.unpack('I', data.read(4))[0] #Power supply temperature (C) | |
humidity = struct.unpack('I', data.read(4))[0] #% relative humidity | |
focus = struct.unpack('I', data.read(4))[0] #Focus units 0-1000 | |
battery = struct.unpack('I', data.read(4))[0] #OBSOLETE: Unused. | |
uservalue1 = struct.unpack('f', data.read(4))[0] | |
uservalue2 = struct.unpack('f', data.read(4))[0] | |
uservalue3 = struct.unpack('f', data.read(4))[0] | |
uservalue4 = struct.unpack('f', data.read(4))[0] | |
uservalue5 = struct.unpack('f', data.read(4))[0] | |
uservalue6 = struct.unpack('f', data.read(4))[0] | |
uservalue7 = struct.unpack('f', data.read(4))[0] | |
uservalue8 = struct.unpack('f', data.read(4))[0] | |
velocity = struct.unpack('f', data.read(4))[0] # Platform velocity from AUV integration | |
depth = struct.unpack('f', data.read(4))[0] # Platform depth from AUV integration | |
altitude = struct.unpack('f', data.read(4))[0] # Platform altitude from AUV integration | |
pitch = struct.unpack('f', data.read(4))[0] # Platform pitch from AUV integration | |
pitchrate = struct.unpack('f', data.read(4))[0] # Platform pitch rate from AUV integration | |
roll = struct.unpack('f', data.read(4))[0] # Platform roll from AUV integration | |
rollrate = struct.unpack('f', data.read(4))[0] # Platform roll rate from AUV integration | |
heading = struct.unpack('f', data.read(4))[0] # Platform heading from AUV integration | |
headingrate = struct.unpack('f', data.read(4))[0] # Platform heading rate from AUV integration | |
compassheading = struct.unpack('f', data.read(4))[0] # Sonar compass heading output | |
compasspitch = struct.unpack('f', data.read(4))[0] # Sonar compass pitch output | |
compassroll = struct.unpack('f', data.read(4))[0] # Sonar compass roll output | |
latitude = struct.unpack('d', data.read(8))[0] # from auxiliary GPS sensor | |
longitude = struct.unpack('d', data.read(8))[0] # from auxiliary GPS sensor | |
sonarposition = struct.unpack('f', data.read(4))[0] # special for PNNL | |
configflags = struct.unpack('I', data.read(4))[0] | |
beamtilt = struct.unpack('f', data.read(4))[0] | |
targetrange = struct.unpack('f', data.read(4))[0] | |
targetbearing = struct.unpack('f', data.read(4))[0] | |
targetpresent = struct.unpack('I', data.read(4))[0] | |
firmwarerevision = struct.unpack('I', data.read(4))[0] #OBSOLETE: Unused. | |
flags = struct.unpack('I', data.read(4))[0] | |
sourceframe = struct.unpack('I', data.read(4))[0] # Source file frame number for CSOT output files | |
watertemp = struct.unpack('f', data.read(4))[0] # Water temperature from housing temperature sensor | |
timerperiod = struct.unpack('I', data.read(4))[0] | |
sonarx = struct.unpack('f', data.read(4))[0] # Sonar X location for 3D processing | |
sonary = struct.unpack('f', data.read(4))[0] # Sonar Y location for 3D processing | |
sonarz = struct.unpack('f', data.read(4))[0] # Sonar Z location for 3D processing | |
sonarpan = struct.unpack('f', data.read(4))[0] # X2 pan output | |
sonartilt = struct.unpack('f', data.read(4))[0] # X2 tilt output | |
sonarroll = struct.unpack('f', data.read(4))[0] # X2 roll output **** End of DDF_03 frame header data **** | |
panpnnl = struct.unpack('f', data.read(4))[0] | |
tiltpnnl = struct.unpack('f', data.read(4))[0] | |
rollpnnl = struct.unpack('f', data.read(4))[0] | |
vehicletime = struct.unpack('d', data.read(8))[0] # special for Bluefin Robotics HAUV or other AUV integration | |
timeggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message | |
dateggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message | |
qualityggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message | |
numsatsggk = struct.unpack('I', data.read(4))[0] # GPS output from NMEA GGK message | |
dopggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message | |
ehtggk = struct.unpack('f', data.read(4))[0] # GPS output from NMEA GGK message | |
heavetss = struct.unpack('f', data.read(4))[0] # external sensor | |
yeargps = struct.unpack('I', data.read(4))[0] # GPS year output | |
monthgps = struct.unpack('I', data.read(4))[0] # GPS month output | |
daygps = struct.unpack('I', data.read(4))[0] # GPS day output | |
hourgps = struct.unpack('I', data.read(4))[0] # GPS hour output | |
minutegps = struct.unpack('I', data.read(4))[0] # GPS minute output | |
secondgps = struct.unpack('I', data.read(4))[0] # GPS second output | |
hsecondgps = struct.unpack('I', data.read(4))[0] # GPS 1/100th second output | |
sonarpanoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location pan offset for 3D processing | |
sonartiltoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location tilt offset for 3D processing | |
sonarrolloffset = struct.unpack('f', data.read(4))[0] # Sonar mount location roll offset for 3D processing | |
sonarxoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location X offset for 3D processing | |
sonaryoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location Y offset for 3D processing | |
sonarzoffset = struct.unpack('f', data.read(4))[0] # Sonar mount location Z offset for 3D processing | |
tmatrix = array.array('f') # 3D processing transformation matrix | |
for i in range(16): | |
tmatrix.append(struct.unpack('f', data.read(4))[0]) | |
samplerate = struct.unpack('f', data.read(4))[0] # Calculated as 1e6/SamplePeriod | |
accellx = struct.unpack('f', data.read(4))[0] # X-axis sonar acceleration | |
accelly = struct.unpack('f', data.read(4))[0] # Y-axis sonar acceleration | |
accellz = struct.unpack('f', data.read(4))[0] # Z-axis sonar acceleration | |
pingmode = struct.unpack('I', data.read(4))[0] # ARIS ping mode [1..12] | |
frequencyhilow = struct.unpack('I', data.read(4))[0] # 1 = HF, 0 = LF | |
pulsewidth = struct.unpack('I', data.read(4))[0] # Width of transmit pulse in usec, [4..100] | |
cycleperiod = struct.unpack('I', data.read(4))[0] # Ping cycle time in usec, [1802..65535] | |
sampleperiod = struct.unpack('I', data.read(4))[0] # Downrange sample rate in usec, [4..100] | |
transmitenable = struct.unpack('I', data.read(4))[0] # 1 = Transmit ON, 0 = Transmit OFF | |
framerate = struct.unpack('f', data.read(4))[0] # Instantaneous frame rate between frame N and frame N-1 | |
soundspeed = struct.unpack('f', data.read(4))[0] # Sound velocity in water calculated from water temperature and salinity setting | |
samplesperbeam = struct.unpack('I', data.read(4))[0] # Number of downrange samples in each beam | |
enable150v = struct.unpack('I', data.read(4))[0] # 1 = 150V ON (Max Power), 0 = 150V OFF (Min Power, 12V) | |
samplestartdelay = struct.unpack('I', data.read(4))[0] # Delay from transmit until start of sampling (window start) in usec, [930..65535] | |
largelens = struct.unpack('I', data.read(4))[0] # 1 = telephoto lens (large lens, big lens, hi-res lens) present | |
thesystemtype = struct.unpack('I', data.read(4))[0] # 1 = ARIS 3000, 0 = ARIS 1800, 2 = ARIS 1200 | |
sonarserialnumber = struct.unpack('I', data.read(4))[0] # Sonar serial number as labeled on housing | |
encryptedkey = struct.unpack('Q', data.read(8))[0] # Reserved for future use | |
ariserrorflagsuint = struct.unpack('I', data.read(4))[0] # Error flag code bits | |
missedpackets = struct.unpack('I', data.read(4))[0] # Missed packet count for Ethernet statistics reporting | |
arisappversion = struct.unpack('I', data.read(4))[0] # Version number of ArisApp sending frame data | |
available2 = struct.unpack('I', data.read(4))[0] # Reserved for future use | |
reorderedsamples = struct.unpack('I', data.read(4))[0] # 1 = frame data already ordered into [beam,sample] array, 0 = needs reordering | |
salinity = struct.unpack('I', data.read(4))[0] # Water salinity code: 0 = fresh, 15 = brackish, 35 = salt | |
pressure = struct.unpack('f', data.read(4))[0] # Depth sensor output in meters (psi) | |
batteryvoltage = struct.unpack('f', data.read(4))[0] # Battery input voltage before power steering | |
mainvoltage = struct.unpack('f', data.read(4))[0] # Main cable input voltage before power steering | |
switchvoltage = struct.unpack('f', data.read(4))[0] # Input voltage after power steering | |
focusmotormoving = struct.unpack('I', data.read(4))[0] # Added 14-Aug-2012 for AutomaticRecording | |
voltagechanging = struct.unpack('I', data.read(4))[0] # Added 16-Aug (first two bits = 12V, second two bits = 150V, 00 = not changing, 01 = turning on, 10 = turning off) | |
focustimeoutfault = struct.unpack('I', data.read(4))[0] | |
focusovercurrentfault = struct.unpack('I', data.read(4))[0] | |
focusnotfoundfault = struct.unpack('I', data.read(4))[0] | |
focusstalledfault = struct.unpack('I', data.read(4))[0] | |
fpgatimeoutfault = struct.unpack('I', data.read(4))[0] | |
fpgabusyfault = struct.unpack('I', data.read(4))[0] | |
fpgastuckfault = struct.unpack('I', data.read(4))[0] | |
cputempfault = struct.unpack('I', data.read(4))[0] | |
psutempfault = struct.unpack('I', data.read(4))[0] | |
watertempfault = struct.unpack('I', data.read(4))[0] | |
humidityfault = struct.unpack('I', data.read(4))[0] | |
pressurefault = struct.unpack('I', data.read(4))[0] | |
voltagereadfault = struct.unpack('I', data.read(4))[0] | |
voltagewritefault = struct.unpack('I', data.read(4))[0] | |
focuscurrentposition = struct.unpack('I', data.read(4))[0] # Focus shaft current position in motor units [0.1000] | |
targetpan = struct.unpack('f', data.read(4))[0] # Commanded pan position | |
targettilt = struct.unpack('f', data.read(4))[0] # Commanded tilt position | |
targetroll = struct.unpack('f', data.read(4))[0] # Commanded roll position | |
panmotorerrorcode = struct.unpack('I', data.read(4))[0] | |
tiltmotorerrorcode = struct.unpack('I', data.read(4))[0] | |
rollmotorerrorcode = struct.unpack('I', data.read(4))[0] | |
panabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute pan position | |
tiltabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute tilt position | |
rollabsposition = struct.unpack('f', data.read(4))[0] # Low-resolution magnetic encoder absolute roll position | |
panaccelx = struct.unpack('f', data.read(4))[0] # Accelerometer outputs from AR2 CPU board sensor | |
panaccely = struct.unpack('f', data.read(4))[0] | |
panaccelz = struct.unpack('f', data.read(4))[0] | |
tiltaccelx = struct.unpack('f', data.read(4))[0] | |
tiltaccely = struct.unpack('f', data.read(4))[0] | |
tiltaccelz = struct.unpack('f', data.read(4))[0] | |
rollaccelx = struct.unpack('f', data.read(4))[0] | |
rollaccely = struct.unpack('f', data.read(4))[0] | |
rollaccelz = struct.unpack('f', data.read(4))[0] | |
appliedsettings = struct.unpack('I', data.read(4))[0] # Cookie indices for command acknowlege in frame header | |
constrainedsettings = struct.unpack('I', data.read(4))[0] | |
invalidsettings = struct.unpack('I', data.read(4))[0] | |
enableinterpacketdelay = struct.unpack('I', data.read(4))[0] # If true delay is added between sending out image data packets | |
interpacketdelayperiod = struct.unpack('I', data.read(4))[0] # packet delay factor in us (does not include function overhead time) | |
uptime = struct.unpack('I', data.read(4))[0] # Total number of seconds sonar has been running | |
arisappversionmajor = struct.unpack('H', data.read(2))[0] # Major version number | |
arisappversionminor = struct.unpack('H', data.read(2))[0] # Minor version number | |
gotime = struct.unpack('Q', data.read(8))[0] # Sonar time when frame cycle is initiated in hardware | |
panvelocity = struct.unpack('f', data.read(4))[0] # AR2 pan velocity in degrees/second | |
tiltvelocity = struct.unpack('f', data.read(4))[0] # AR2 tilt velocity in degrees/second | |
rollvelocity = struct.unpack('f', data.read(4))[0] # AR2 roll velocity in degrees/second | |
sentinel = struct.unpack('I', data.read(4))[0] # Used to measure the frame header size | |
#Create the ARIS_frame data structure and add the meta-data | |
output = ARIS_Frame(frameindex, frametime, version, status, sonartimestamp, tsday, tshour, tsminute, tssecond, tshsecond, transmitmode, | |
windowstart, windowlength, threshold, intensity, receivergain, degc1, degc2, humidity, focus, battery, uservalue1, uservalue2, | |
uservalue3, uservalue4, uservalue5, uservalue6, uservalue7, uservalue8, velocity, depth, altitude, pitch, pitchrate, roll, | |
rollrate, heading, headingrate, compassheading, compasspitch, compassroll, latitude, longitude, sonarposition, configflags, | |
beamtilt, targetrange, targetbearing, targetpresent, firmwarerevision, flags, sourceframe, watertemp, timerperiod, sonarx, | |
sonary, sonarz, sonarpan, sonartilt, sonarroll, panpnnl, tiltpnnl, rollpnnl, vehicletime, timeggk, dateggk, qualityggk, numsatsggk, | |
dopggk, ehtggk, heavetss, yeargps, monthgps, daygps, hourgps, minutegps, secondgps, hsecondgps, sonarpanoffset, sonartiltoffset, | |
sonarrolloffset, sonarxoffset, sonaryoffset, sonarzoffset, tmatrix, samplerate, accellx, accelly, accellz, pingmode, frequencyhilow, | |
pulsewidth, cycleperiod, sampleperiod, transmitenable, framerate, soundspeed, samplesperbeam, enable150v, samplestartdelay, largelens, | |
thesystemtype, sonarserialnumber, encryptedkey, ariserrorflagsuint, missedpackets, arisappversion, available2, reorderedsamples, | |
salinity, pressure, batteryvoltage, mainvoltage, switchvoltage, focusmotormoving, voltagechanging, focustimeoutfault, focusovercurrentfault, | |
focusnotfoundfault, focusstalledfault, fpgatimeoutfault, fpgabusyfault, fpgastuckfault, cputempfault, psutempfault, watertempfault, | |
humidityfault, pressurefault, voltagereadfault, voltagewritefault, focuscurrentposition, targetpan, targettilt, targetroll, panmotorerrorcode, | |
tiltmotorerrorcode, rollmotorerrorcode, panabsposition, tiltabsposition, rollabsposition, panaccelx, panaccely, panaccelz, tiltaccelx, | |
tiltaccely, tiltaccelz, rollaccelx, rollaccely, rollaccelz, appliedsettings, constrainedsettings, invalidsettings, enableinterpacketdelay, | |
interpacketdelayperiod, uptime, arisappversionmajor, arisappversionminor, gotime, panvelocity, tiltvelocity, rollvelocity, sentinel) | |
#Add the frame data | |
if pingmode in [1,2]: | |
ARIS_Frame.BeamCount = 48 | |
if pingmode in [3,4,5]: | |
ARIS_Frame.BeamCount = 96 | |
if pingmode in [6,7,8]: | |
ARIS_Frame.BeamCount = 64 | |
if pingmode in [9,10,11,12]: | |
ARIS_Frame.BeamCount = 128 | |
data.seek(frameoffset+1024, 0) | |
frame = np.empty([samplesperbeam, ARIS_Frame.BeamCount], dtype=float) | |
for r in range(len(frame)): | |
for c in range(len(frame[r])): | |
frame[r][c] = struct.unpack('B', data.read(1))[0] | |
frame = np.fliplr(frame) | |
#Remap the data from 0-255 to 0-80 dB | |
#remap = lambda t: (t * 80)/255 | |
#vfunc = np.vectorize(remap) | |
#frame = vfunc(frame) | |
output.frame_data = frame | |
output.WinStart = output.samplestartdelay * 0.000001 * output.soundspeed / 2 | |
#Close the data file | |
data.close() | |
return output | |
def get_box_for_sample(beam_num, bin_num, frame, beam_data): | |
""" Get the box coordinates (in meters) for a sample. | |
This is a non-axis aligned box. | |
Returns: | |
back left, back right, front right, front left | |
""" | |
sample_start_delay = frame.samplestartdelay # usec | |
sound_speed = frame.soundspeed # meters / sec | |
sample_period = frame.sampleperiod # usec | |
WindowStart = sample_start_delay * 1e-6 * sound_speed / 2 # meters | |
sample_length = sample_period * 1e-6 * sound_speed / 2. # meters | |
bin_front_edge_distance = WindowStart + sample_length * bin_num | |
bin_back_edge_distance = WindowStart + sample_length* (bin_num + 1) | |
beam_angles = beam_data[beam_data['beam_num'] == beam_num] | |
a1 = beam_angles['beam_left'].iloc[0] | |
a2 = beam_angles['beam_right'].iloc[0] | |
c = beam_angles['beam_center'].iloc[0] | |
# I can't figure out whats going on with the beam spacing in the files. | |
# Once the center point crosses 0, the ordering of the left and right angles swap... | |
# For now I'll assume the y axis is the common line. Positive angles go to the left, | |
# negative angles go to the right | |
left = max(a1, a2) | |
right = min(a1, a2) | |
# Left Edge | |
beam_left_angle = np.deg2rad(left) | |
rot_matrix = np.array([[np.cos(beam_left_angle), -np.sin(beam_left_angle)], [np.sin(beam_left_angle), np.cos(beam_left_angle)]]) | |
vec = np.array([0, bin_back_edge_distance]) | |
bin_left_back_point = np.matmul(rot_matrix, vec) | |
vec = np.array([0, bin_front_edge_distance]) | |
bin_left_front_point = np.matmul(rot_matrix, vec) | |
# Right Edge | |
beam_right_angle = np.deg2rad(right) | |
rot_matrix = np.array([[np.cos(beam_right_angle), -np.sin(beam_right_angle)], [np.sin(beam_right_angle), np.cos(beam_right_angle)]]) | |
vec = np.array([0, bin_front_edge_distance]) | |
bin_right_front_point = np.matmul(rot_matrix, vec) | |
vec = np.array([0, bin_back_edge_distance]) | |
bin_right_back_point = np.matmul(rot_matrix, vec) | |
return bin_left_back_point, bin_right_back_point, bin_right_front_point, bin_left_front_point | |
def xy_to_sample(x, y, frame, beam_data): | |
""" Convert an x,y location (in meters) to a beam sample. | |
Returns: | |
beam num | |
bin num | |
""" | |
# Get the angle | |
angle = np.rad2deg(np.arctan(x / y)) | |
beam_num = beam_data[(beam_data['beam_left'] <= angle) & (angle <= beam_data['beam_right'])] | |
if beam_num.shape[0] == 0: | |
return None, None | |
beam_num = beam_num['beam_num'].iloc[0] | |
# Get the distance | |
hyp = y / np.cos(np.arctan(x / y)) | |
# Take into account the window start | |
hyp -= frame.WinStart | |
# Sample length | |
bin_length = frame.sampleperiod * 0.000001 * frame.soundspeed / 2. | |
# Convert to bins | |
bin_num = int(hyp / bin_length) | |
if bin_num < 0 or bin_num > (frame.BeamCount - 1): | |
return None, None | |
return beam_num, bin_num | |
def get_minimum_pixel_meter_size(frame, beam_width_data): | |
""" Compute the smallest pixel size that will bound a sample. | |
""" | |
all_widths = [] | |
all_heights = [] | |
for beam_num in range(frame.BeamCount): | |
bl, br, fr, fl = get_box_for_sample(beam_num, 0, frame, beam_width_data) | |
# determine the axis aligned box around this sample box. | |
xs = [bl[0], br[0], fr[0], fl[0]] | |
ys = [bl[1], br[1], fr[1], fl[1]] | |
min_x = min(xs) | |
max_x = max(xs) | |
min_y = min(ys) | |
max_y = max(ys) | |
width = max_x - min_x | |
height = max_y - min_y | |
all_widths.append(width) | |
all_heights.append(height) | |
min_width = min(all_widths) | |
min_height = min(all_heights) | |
return min(min_width, min_height) | |
def compute_image_bounds(pixel_meter_size, frame, beam_width_data, additional_pixel_padding_x=0, additional_pixel_padding_y=0): | |
""" Given the size of a pixel in meters, compute the bounds of an image that will contain the frame. | |
""" | |
# Compute the projected locations of all samples so that we can get the extent | |
all_bl = [] | |
all_br = [] | |
all_fr = [] | |
all_fl = [] | |
for beam_num in [0, frame.BeamCount / 2, frame.BeamCount - 1]: | |
for bin_num in [0, frame.samplesperbeam - 1]: | |
bl, br, fr, fl = get_box_for_sample(beam_num, bin_num, frame, beam_width_data) | |
all_bl.append(bl) | |
all_br.append(br) | |
all_fr.append(fr) | |
all_fl.append(fl) | |
all_bl = np.array(all_bl) | |
all_br = np.array(all_br) | |
all_fr = np.array(all_fr) | |
all_fl = np.array(all_fl) | |
# Get the xdim extent | |
min_back_left = np.min(all_bl[:,0]) | |
min_back_right = np.min(all_br[:,0]) | |
min_front_left = np.min(all_fl[:,0]) | |
min_front_right = np.min(all_fr[:,0]) | |
assert min_back_left < min_back_right | |
assert min_back_left < min_front_left | |
assert min_back_left < min_front_right | |
max_back_left = np.max(all_bl[:,0]) | |
max_back_right = np.max(all_br[:,0]) | |
max_front_left = np.max(all_fl[:,0]) | |
max_front_right = np.max(all_fr[:,0]) | |
assert max_back_right > max_back_left | |
assert max_back_right > max_front_left | |
assert max_back_right > max_front_right | |
xdim_extent = np.array([min_back_left, max_back_right]) | |
# Get the ydim extent | |
min_back_left = np.min(all_bl[:,1]) | |
min_back_right = np.min(all_br[:,1]) | |
min_front_left = np.min(all_fl[:,1]) | |
min_front_right = np.min(all_fr[:,1]) | |
min_front = min(min_front_left, min_front_right) | |
assert min_front < min_back_right | |
assert min_front < min_back_left | |
max_back_left = np.max(all_bl[:,1]) | |
max_back_right = np.max(all_br[:,1]) | |
max_front_left = np.max(all_fl[:,1]) | |
max_front_right = np.max(all_fr[:,1]) | |
max_back = max(max_back_left, max_back_right) | |
assert max_back > max_front_right | |
assert max_back > max_front_left | |
ydim_extent = np.array([min_front, max_back]) | |
# Determine which meter location corresponds to our "target center" | |
bl, br, fr, fl = get_box_for_sample(frame.BeamCount / 2, 0, frame, beam_width_data) | |
target_center_x = (fl[0] + fr[0]) / 2. | |
target_center_y = (bl[1] + fl[1]) / 2. | |
# Determine the x dimension size and what this corresponds to in meters | |
extra_padding_x = pixel_meter_size + pixel_meter_size * additional_pixel_padding_x | |
# X Min | |
xmin_len = target_center_x - xdim_extent[0] | |
xp = xmin_len % pixel_meter_size | |
xmin_padded = xdim_extent[0] - (extra_padding_x - xp) | |
xmin_len = target_center_x - xmin_padded | |
x_min_cells = np.abs(xmin_len / pixel_meter_size) | |
x_min_meters = target_center_x - xmin_len | |
assert x_min_meters <= xdim_extent[0] | |
# X Max | |
xmax_len = xdim_extent[1] - target_center_x | |
xp = xmax_len % pixel_meter_size | |
xmax_padded = xdim_extent[1] + (extra_padding_x - xp) | |
xmax_len = xmax_padded - target_center_x | |
x_max_cells = np.abs(xmax_len / pixel_meter_size) | |
x_max_meters = target_center_x + xmax_len | |
assert x_max_meters >= xdim_extent[1] | |
# if we want a specific beam to be the in the middle of the image then we should take the max? | |
xdim = int(x_min_cells + x_max_cells) | |
x_meter_start = x_min_meters | |
x_meter_stop = x_max_meters | |
# Determine the y dimension size and what this corresponds to in meters | |
extra_padding_y = pixel_meter_size + pixel_meter_size * additional_pixel_padding_y | |
# Y Min | |
ymin_len = target_center_y - ydim_extent[0] | |
yp = ymin_len % pixel_meter_size | |
ymin_padded = ydim_extent[0] - ( extra_padding_y - yp) | |
ymin_len = target_center_y - ymin_padded | |
y_min_cells = np.abs(ymin_len / pixel_meter_size) | |
y_min_meters = target_center_y - ymin_len | |
assert y_min_meters <= ydim_extent[0] | |
# Y Max | |
ymax_len = ydim_extent[1] - target_center_y | |
yp = ymax_len % pixel_meter_size | |
ymax_padded = ydim_extent[1] + (extra_padding_y - yp) | |
ymax_len = ymax_padded - target_center_y | |
y_max_cells = np.abs(ymax_len / pixel_meter_size) | |
y_max_meters = target_center_y + ymax_len | |
assert y_max_meters >= ydim_extent[1] | |
ydim = int(y_min_cells + y_max_cells) | |
y_meter_start = y_max_meters | |
y_meter_stop = y_min_meters | |
return xdim, ydim, x_meter_start, y_meter_start, x_meter_stop, y_meter_stop | |
def compute_mapping_from_sample_to_image(pixel_meter_size, xdim, ydim, x_meter_start, y_meter_start, frame, beam_width_data): | |
x_meter_values = np.array([x_meter_start + i * pixel_meter_size for i in range(xdim)]) | |
y_meter_values = np.array([y_meter_start - i * pixel_meter_size for i in range(ydim)]) | |
YY, XX = np.meshgrid(y_meter_values, x_meter_values, indexing='ij') | |
XYpairs = np.vstack([ XX.reshape(-1), YY.reshape(-1) ]).T | |
II, JJ = np.meshgrid(np.arange(ydim), np.arange(xdim), indexing='ij') | |
IJpairs = np.vstack([ II.reshape(-1), JJ.reshape(-1)]).T | |
# Get the angle of the xy pairs | |
angles = np.rad2deg(np.arctan(XYpairs[:,0] / XYpairs[:,1])) | |
# Discard pairs that have an angle that is out of range | |
min_angle = beam_width_data['beam_left'].min() | |
max_angle = beam_width_data['beam_right'].max() | |
valid_pairs = (angles > min_angle) & (angles < max_angle) | |
angles = angles[valid_pairs] | |
XYpairs = XYpairs[valid_pairs] | |
IJpairs = IJpairs[valid_pairs] | |
# Get the distance of the xy pairs | |
hyp = XYpairs[:,1] / np.cos(np.arctan(XYpairs[:,0] / XYpairs[:,1])) | |
# Take into account the window start | |
hyp -= frame.WinStart | |
# Sample length | |
bin_length = frame.sampleperiod * 0.000001 * frame.soundspeed / 2. | |
# Convert to bins | |
bin_nums = (hyp / bin_length).astype(int) | |
# Discard pairs that have a distance that is out of range | |
valid_pairs = (bin_nums >= 0) & (bin_nums < frame.samplesperbeam) | |
bin_nums = bin_nums[valid_pairs] | |
angles = angles[valid_pairs] | |
XYpairs = XYpairs[valid_pairs] | |
IJpairs = IJpairs[valid_pairs] | |
beam_edges = beam_width_data['beam_left'].to_numpy() | |
beam_nums = np.digitize(angles, beam_edges) - 1 | |
# For each valid x,y pair compute which beam it falls into | |
write_to = [] # (i, j) of image | |
read_from = [] # (bin_num, beam_num) of frame data | |
for index in range(XYpairs.shape[0]): | |
bin_num = bin_nums[index] | |
beam_num = beam_nums[index] | |
write_to.append(IJpairs[index]) | |
read_from.append((bin_num, beam_num)) | |
read_from = np.array(read_from) | |
read_from_rows = np.array(read_from[:,0]) | |
read_from_cols = np.array(read_from[:,1]) | |
write_to = np.array(write_to) | |
write_to_rows = np.array(write_to[:,0]) | |
write_to_cols = np.array(write_to[:,1]) | |
return read_from_rows, read_from_cols, write_to_rows, write_to_cols | |
def make_video(data, | |
xdim, ydim, sample_read_rows, sample_read_cols, image_write_rows, image_write_cols, | |
directory, filename, fps = 24.0, start_frame = 1, end_frame = None, timestamp = False, fontsize = 30, ts_pos = (0,0), save_raw = False): | |
"""Output video using the ffmpeg pipeline. The current implementation | |
outputs compresses png files and outputs a mp4. | |
Parameters | |
----------- | |
data : (Str) ARIS data structure returned via pyARIS.DataImport() | |
filename : (Str) output filename. Must include file extension (i.e. 'video.mp4') | |
fps : (Float) Output video frame rate (frames per second). Default = 24 fps | |
start_frame, end_frame : (Int) Range of frames included in the output video | |
timestamp : (Bool) Add the timestamp from the sonar to the video frames | |
fontsize : (Int) Size of timestamp font | |
ts_pos : (Tuple) (x,y) location of the timestamp | |
Returns | |
------- | |
Returns a video into the current working directory | |
Notes | |
------ | |
Currently this function looks for ffmpeg.exe in the current working directory. | |
Must have the '*.mp4' file extension. | |
Uses the tqdm package to display a status bar. | |
Example | |
------- | |
>>> pyARIS.VideoExport(data, 'test_video.mp4', fps = 24) | |
""" | |
#Command to send via the command prompt which specifies the pipe parameters | |
# command = ['ffmpeg', | |
# '-y', # (optional) overwrite output file if it exists | |
# '-f', 'image2pipe', | |
# '-vcodec', 'mjpeg', #'mjpeg', | |
# '-r', '1', | |
# '-r', str(fps), # frames per second | |
# '-i', '-', # The input comes from a pipe | |
# '-an', # Tells FFMPEG not to expect any audio | |
# '-vcodec', 'mpeg4', | |
# '-b:v', '5000k', | |
# directory + filename + "/"+filename+".mp4", | |
# '-hide_banner', | |
# '-loglevel', 'panic'] | |
# Create directories if they don't exist | |
if not os.path.exists(os.path.join(directory, filename, 'frames/')): | |
os.makedirs(os.path.join(directory, filename, 'frames/')) | |
if save_raw and not os.path.exists(os.path.join(directory, filename, 'frames-raw/')): | |
os.makedirs(os.path.join(directory, filename, 'frames-raw/')) | |
if end_frame == None: | |
end_frame = data.FrameCount | |
cm = colormap.get_cmap('viridis') | |
for i, frame_offset in enumerate(tqdm.tqdm(range(start_frame, end_frame))): | |
frame = FrameRead(data, frame_offset) | |
frame_image = np.zeros([ydim, xdim], dtype=np.uint8) | |
frame_image[image_write_rows, image_write_cols] = frame.frame_data[sample_read_rows, sample_read_cols] | |
rgb_im = Image.fromarray(cm(frame_image, bytes=True)).convert('RGB') | |
rgb_im.save(os.path.join(directory, filename, 'frames/', f'{i}.jpg'), 'JPEG') | |
if save_raw: | |
Image.fromarray(np.uint8(frame.frame_data), mode='L').save(os.path.join(directory, filename, 'frames-raw/', f'{i}.jpg'), 'JPEG') | |