def testCorruptedVideoFails(self): # Unfortunately this invalid read creates a semi-unblockable write to stderr # Workaround: https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ # Just take a lot of extra code, probably not worth it # TODO think about implementing it anyway with self.assertRaises(OSError): print('\nExpecting IO error... ', end='') readVideo('tests/data/corrupt.avi')
def testReadFramesAreCorrect(self): frames = readVideo( 'tests/data/black.avi') # 5x frames of straight black self.assertEqual(len(frames), 5) gold = [[[0 for i in range(1024)] for j in range(1024)] for k in range(5)] self.assertTrue(numpy.array_equal(frames, gold))
def setUpClass(self): # Cache test data self.frames = readVideo('tests/data/small.avi').astype(numpy.int16) self.firstDiff = self.frames[1] - self.frames[0] self.thread = reikna.cluda.ocl_api().Thread.create() # Get the OpenCL kernels from the 2DF module self.fft = createComplexFFTKernel(self.thread, self.frames[0].shape) self.normalise = createNormalisationKernel(self.thread, self.frames[0].shape)
def testEmptyVideoFails(self): with self.assertRaises(OSError): print('\nExpecting IO error... ', end='') frames = readVideo('tests/data/empty.avi')
def testValidVideoOpens(self): readVideo('tests/data/short.avi') # single black frame
def sequentialGPUChunker(filename, spacings, RAMGB=4, progress=None, abortFlag=None): if progress is not None: progress.setText('Reading Video from Disk') progress.cycle() videoInput = readVideo(filename) numFrames = videoInput.shape[0] correlations = [None] * (numFrames - 1) if abortFlag: return None showProgress = progress is not None if showProgress: progress.setText('Getting OpenCL Context') # Create access node for OpenCL api = reikna.cluda.ocl_api() try: thr = api.Thread.create() except LogicError: print('No OpenCL-compatible devices (e.g. GPUs) found', file=sys.stderr) exit() # Display accessible devices for plat in api.get_platforms(): for cld in plat.get_devices(): print( f'Using {cld.name} with {cld.global_mem_size/1024**3:.1f}GB VRAM' ) # print('Has extensions:', cld.extensions) if showProgress: progress.setText('Creating OpenCL Kernels') # need to compile an OpenCL kernel to calculate FFTs with size = [d - 1 for d in videoInput[0].shape] fftComplex = createComplexFFTKernel(thr, size) fftNorm = createNormalisationKernel(thr, size) # Number of pixels per frame, multiplied by 128 for the size of a complex # float, but halved because the real transform is used RAMBytes = RAMGB * numpy.power(2.0, 30.0) complexFrameByteSize = videoInput.shape[1] * videoInput.shape[2] * 128 / 2 # One frame's RAM in reserve for the head framesPerSlice = int((RAMBytes // complexFrameByteSize) - 1) # The number of different slice intervals that must be taken numSpacingSets = int(numpy.ceil((numFrames - 1) / framesPerSlice)) # Used to show progress in the UI framesProcessed = 0 target = numFrames * (numFrames - 1) / 2 # algorithm complexity # Allow 10% extra time to calculate the q curves qProgress = target * 0.1 / numSpacingSets # per-slice q-curve allowance target += qProgress * numSpacingSets if abortFlag: return None # For each diagonal section for sliceSpacing in range(0, numSpacingSets): if progress is not None: progress.setText( f'Working on Slice {sliceSpacing+1}/{numSpacingSets}') #A double ended queue, more efficient than a list for queue operations currentSlice = deque() #The index by which new frames are grabbed for the slice baseIndex = 0 #Finding the expected shape of the transform results transformShape = (videoInput.shape[1] - 1, videoInput.shape[2] - 1) totalDifferencesShape = (framesPerSlice, transformShape[0], transformShape[1]) #Preparing the destination of the frame differences totalDifferences = numpy.zeros(totalDifferencesShape) numDifferences = numpy.zeros((framesPerSlice, )) #For each head for headIndex in range((sliceSpacing * framesPerSlice) + 1, numFrames): #If the queue is full, remove the oldest element if len(currentSlice) == framesPerSlice: currentSlice.popleft() #Get a new value into the slice queue #Also drops a row and column currentSlice.append( runKernelOperation(thr, fftComplex, videoInput[baseIndex, :-1, :-1])) baseIndex += 1 #Drops a row and column head = videoInput[headIndex, :-1, :-1] head = runKernelOperation(thr, fftComplex, head) #time difference between this frame and the first in the queue relativeDifference = 0 #iterating backwards through the list, oldest element first for sliceFrameIndex in range(len(currentSlice) - 1, -1, -1): # Update progress tracker if progress is not None: framesProcessed += 1 progress.setProgress(framesProcessed, target) difference = head - currentSlice[sliceFrameIndex] normalFrame = thr.array(size, dtype=numpy.float64) fftNorm(normalFrame, difference) totalDifferences[relativeDifference, :, :] += normalFrame.get() # TODO - Need to make this ^^^ run on the GPU # allocate list of empty buffers, add into? numDifferences[relativeDifference] += 1 relativeDifference += 1 if abortFlag: return None for relativeDifference in range(0, len(currentSlice)): if progress is not None: framesProcessed += qProgress / len(currentSlice) progress.setProgress(framesProcessed, target) meanDifference = (totalDifferences[relativeDifference, :, :] / numDifferences[relativeDifference]) timeDifference = relativeDifference + sliceSpacing * framesPerSlice correlations[timeDifference] = calculateWithCalls(meanDifference) if abortFlag: return None if progress is not None: progress.cycle() progress.setText('Calculating Correlation Curves') correlations = calculateCorrelation(correlations) frameRate = readFramerate(filename) timeSpacings = numpy.array(numpy.arange(1, len(correlations) + 1)) / frameRate # This is how you stack arrays in numpy, apparently 🙃 outputMatrix = numpy.c_[timeSpacings, correlations] if abortFlag: return None if progress is not None: progress.setPercentage(100) progress.setText('Done!') return outputMatrix
def sequentialChunkerMain(videoPath, spacings, outputPath=None, RAMGB=1, progress=None, abortFlag=None): if progress is not None: progress.setText('Reading Video from Disk') progress.cycle() videoInput = rV.readVideo(videoPath) numFrames = videoInput.shape[0] correlations = [None] * (numFrames - 1) #Number of pixels per frame, times 128 for the size of a complex float, #but halved because the real transform is used RAMBytes = RAMGB * np.power(2.0, 30.0) complexFrameByteSize = videoInput.shape[1] * videoInput.shape[2] * 128 / 2 #TODO: adjust for the other RAM using variables #one frame's RAM in reserve for the head framesPerSlice = int((RAMBytes // complexFrameByteSize) - 1) #The number of different slice intervals that must be take numSpacingSets = int(np.ceil((numFrames - 1) / framesPerSlice)) print('numSpacingSets:', numSpacingSets) print('framesPerSlice:', framesPerSlice) print('complexFrameByteSize:', complexFrameByteSize) # Used to show progress in the UI framesProcessed = 0 target = numFrames * (numFrames - 1) / 2 # algorithm complexity # allow 10% extra time to calculate the q curves qProgress = target * 0.1 / numSpacingSets # per-slice q-curve allowance target += qProgress * numSpacingSets #For each diagonal section for sliceSpacing in range(0, numSpacingSets): if progress is not None: progress.setText( f'Working on Slice {sliceSpacing+1}/{numSpacingSets}') #A double ended queue, more efficient than a list for queue operations currentSlice = deque() #The index by which new frames are grabbed for the slice baseIndex = 0 #Finding the expected shape of the transform results #trying something new, dropping a couple of samples to match matlab (1 in each dimension) if (videoInput.shape[2] - 1) % 2 == 0: transformShape = (videoInput.shape[1] - 1, (videoInput.shape[2] - 1) // 2 + 1) else: #+1 for the real transform correction, -1 to drop a sample based on MATLAB transformShape = (videoInput.shape[1] - 1, (videoInput.shape[2] + 1 - 1) // 2) totalDifferencesShape = (framesPerSlice, transformShape[0], transformShape[1]) #Preparing the destination of the frame differences totalDifferences = np.zeros(totalDifferencesShape) numDifferences = np.zeros((framesPerSlice, )) #For each head for headIndex in range((sliceSpacing * framesPerSlice) + 1, numFrames): #If the queue is full, remove the oldest element if len(currentSlice) == framesPerSlice: currentSlice.popleft() #Get a new value into the slice queue #Also drops a row and column currentSlice.append( tDF.realTwoDFourierUnnormalized( videoInput[baseIndex, :-1, :-1])) baseIndex += 1 #Drops a row and column head = videoInput[headIndex, :-1, :-1] head = tDF.realTwoDFourierUnnormalized(head) #time difference between this frame and the first in the queue relativeDifference = 0 #iterating backwards through the list, oldest element first for sliceFrameIndex in range(len(currentSlice) - 1, -1, -1): # Update progress tracker if progress is not None: framesProcessed += 1 progress.setProgress(framesProcessed, target) difference = head - currentSlice[sliceFrameIndex] totalDifferences[relativeDifference, :, :] += tDF.castToReal( difference) numDifferences[relativeDifference] += 1 relativeDifference += 1 if abortFlag: return None for relativeDifference in range(0, len(currentSlice)): if progress is not None: framesProcessed += qProgress / len(currentSlice) progress.setProgress(framesProcessed, target) meanDifference = (totalDifferences[relativeDifference, :, :] / numDifferences[relativeDifference]) timeDifference = relativeDifference + sliceSpacing * framesPerSlice correlations[timeDifference] = cQC.calculateRealQCurves( meanDifference) if abortFlag: return None if progress is not None: progress.cycle() progress.setText('Calculating Correlation Curves') correlations = cC.calculateCorrelation(correlations) frameRate = rV.readFramerate(videoPath) timeSpacings = np.array(np.arange(1, len(correlations) + 1)) / frameRate #This is the way to stack arrays in numpy outputMatrix = np.c_[timeSpacings, correlations] if abortFlag: return None if outputPath is not None: if progress is not None: progress.setText('Saving to Disk') np.savetxt(outputPath, outputMatrix) if progress is not None: progress.setPercentage(100) progress.setText('Done!') return outputMatrix
def setUpClass(self): # a (10-frame) 128x128 crop of the large data file self.frames = readVideo('tests/data/small.avi').astype(numpy.int16) self.firstDiff = self.frames[1] - self.frames[0]