# -*- 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')