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