This commit is contained in:
parent
f907094d1a
commit
559fef7bc4
67
Analyzer.py
67
Analyzer.py
|
|
@ -10,35 +10,48 @@ import _thread
|
||||||
import imageio
|
import imageio
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
from VideoReader import VideoReader
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
import imutils
|
||||||
|
|
||||||
|
|
||||||
class Analyzer:
|
class Analyzer:
|
||||||
def __init__(self, videoPath):
|
|
||||||
|
def __init__(self, config):
|
||||||
print("Analyzer constructed")
|
print("Analyzer constructed")
|
||||||
data = self.readIntoMem(videoPath)
|
videoReader = VideoReader(config)
|
||||||
|
videoReader.fillBuffer()
|
||||||
vs = cv2.VideoCapture(videoPath)
|
self.config = config
|
||||||
threashold = 13
|
self.avg = imutils.resize(np.zeros((videoReader.h,videoReader.w,3),np.float), width=config["resizeWidth"])
|
||||||
res, image = vs.read()
|
self.end = videoReader.endFrame
|
||||||
firstFrame = None
|
self.c = 0
|
||||||
i = 0
|
start = time.time()
|
||||||
diff = []
|
fak = 10
|
||||||
while res:
|
while not videoReader.videoEnded():
|
||||||
res, frame = vs.read()
|
self.c, frame = videoReader.pop()
|
||||||
if not res:
|
if not self.c%fak == 0:
|
||||||
break
|
|
||||||
frame = imutils.resize(frame, width=500)
|
|
||||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
|
|
||||||
if firstFrame is None:
|
|
||||||
firstFrame = gray
|
|
||||||
continue
|
continue
|
||||||
frameDelta = cv2.absdiff(gray, firstFrame)
|
if videoReader.endFrame - self.c <= fak:
|
||||||
thresh = cv2.threshold(frameDelta, threashold, 255, cv2.THRESH_BINARY)[1]
|
break
|
||||||
diff.append(np.count_nonzero(thresh))
|
frame = imutils.resize(frame, width=self.config["resizeWidth"])
|
||||||
i+=1
|
|
||||||
if i % (60*30) == 0:
|
|
||||||
print("Minutes processed: ", i/(60*30))
|
self.avg += frame.astype(np.float)/(self.end/fak)
|
||||||
#print(diff)
|
if self.c%(1800*6) == 0:
|
||||||
|
print(f"{self.c/(60*30)} Minutes processed in {round((time.time() - start), 2)} each")
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
#print("done")
|
||||||
|
videoReader.thread.join()
|
||||||
|
self.avg = np.array(np.round(self.avg), dtype=np.uint8)
|
||||||
|
#return self.avg
|
||||||
|
cv2.imshow("changes overlayed", self.avg)
|
||||||
|
cv2.waitKey(10) & 0XFF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def average(self, frame):
|
||||||
|
frame = imutils.resize(frame[1], width=self.config["resizeWidth"])
|
||||||
|
self.avg += frame.astype(np.float)/(self.end/5)
|
||||||
|
|
||||||
|
|
||||||
plt.plot(diff)
|
|
||||||
plt.ylabel('some numbers')
|
|
||||||
plt.show()
|
|
||||||
|
|
@ -2,17 +2,17 @@
|
||||||
class Config:
|
class Config:
|
||||||
c = {
|
c = {
|
||||||
"min_area" : 500,
|
"min_area" : 500,
|
||||||
"max_area" : 9000,
|
"max_area" : 20000,
|
||||||
"threashold" : 10,
|
"threashold" : 13,
|
||||||
"resizeWidth" : 512,
|
"resizeWidth" : 512,
|
||||||
"inputPath" : None,
|
"inputPath" : None,
|
||||||
"outputPath": None,
|
"outputPath": None,
|
||||||
"maxLayerLength": 900,
|
"maxLayerLength": 900,
|
||||||
"minLayerLength": 30,
|
"minLayerLength": 20,
|
||||||
"tolerance": 10,
|
"tolerance": 10,
|
||||||
"maxLength": None,
|
"maxLength": None,
|
||||||
"ttolerance": 10,
|
"ttolerance": 10,
|
||||||
"videoBufferLength": 16
|
"videoBufferLength": 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import concurrent.futures
|
||||||
from VideoReader import VideoReader
|
from VideoReader import VideoReader
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
import threading
|
import threading
|
||||||
from multiprocessing.pool import ThreadPool
|
|
||||||
from Config import Config
|
from Config import Config
|
||||||
|
|
||||||
class ContourExtractor:
|
class ContourExtractor:
|
||||||
|
|
@ -38,6 +38,7 @@ class ContourExtractor:
|
||||||
self.xDim = 0
|
self.xDim = 0
|
||||||
self.yDim = 0
|
self.yDim = 0
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.diff = []
|
||||||
|
|
||||||
print("ContourExtractor initiated")
|
print("ContourExtractor initiated")
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ class ContourExtractor:
|
||||||
videoReader.fillBuffer()
|
videoReader.fillBuffer()
|
||||||
frameCount, frame = videoReader.pop()
|
frameCount, frame = videoReader.pop()
|
||||||
|
|
||||||
|
|
||||||
#init compare image
|
#init compare image
|
||||||
frame = imutils.resize(frame, width=self.resizeWidth)
|
frame = imutils.resize(frame, width=self.resizeWidth)
|
||||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
@ -57,36 +59,42 @@ class ContourExtractor:
|
||||||
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||||
self.firstFrame = gray
|
self.firstFrame = gray
|
||||||
|
|
||||||
threads = 16
|
threads = self.config["videoBufferLength"]
|
||||||
start = time.time()
|
self.start = time.time()
|
||||||
with ThreadPool(threads) as pool:
|
with ThreadPool(threads) as pool:
|
||||||
while not videoReader.videoEnded():
|
while not videoReader.videoEnded():
|
||||||
#FrameCount, frame = videoReader.pop()
|
#FrameCount, frame = videoReader.pop()
|
||||||
if frameCount % (60*30) == 0:
|
|
||||||
print(f"{frameCount/(60*30)} Minutes processed in {round((time.time() - start), 2)} each")
|
|
||||||
start = time.time()
|
|
||||||
|
|
||||||
if videoReader.buffer.qsize() == 0:
|
if videoReader.buffer.qsize() == 0:
|
||||||
time.sleep(.5)
|
time.sleep(.5)
|
||||||
|
|
||||||
tmpData = [videoReader.pop() for i in range(0, videoReader.buffer.qsize())]
|
tmpData = [videoReader.pop() for i in range(0, videoReader.buffer.qsize())]
|
||||||
frameCount = tmpData[-1][0]
|
|
||||||
pool.map(self.getContours, tmpData)
|
pool.map(self.getContours, tmpData)
|
||||||
|
#for data in tmpData:
|
||||||
|
#self.getContours(data)
|
||||||
|
|
||||||
|
frameCount = tmpData[-1][0]
|
||||||
videoReader.thread.join()
|
videoReader.thread.join()
|
||||||
return self.extractedContours
|
return self.extractedContours
|
||||||
|
|
||||||
def getContours(self, data):
|
def getContours(self, data):
|
||||||
frameCount, frame = data
|
frameCount, frame = data
|
||||||
firstFrame = self.firstFrame
|
firstFrame = self.firstFrame
|
||||||
|
if frameCount % (60*30) == 0:
|
||||||
|
print(f"{frameCount/(60*30)} Minutes processed in {round((time.time() - self.start), 2)} each")
|
||||||
|
self.start = time.time()
|
||||||
frame = imutils.resize(frame, width=self.resizeWidth)
|
frame = imutils.resize(frame, width=self.resizeWidth)
|
||||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||||
frameDelta = cv2.absdiff(gray, firstFrame)
|
frameDelta = cv2.absdiff(gray, firstFrame)
|
||||||
thresh = cv2.threshold(frameDelta, self.threashold, 255, cv2.THRESH_BINARY)[1]
|
thresh = cv2.threshold(frameDelta, self.threashold, 255, cv2.THRESH_BINARY)[1]
|
||||||
# dilate the thresholded image to fill in holes, then find contours
|
# dilate the thresholded image to fill in holes, then find contours
|
||||||
thresh = cv2.dilate(thresh, None, iterations=4)
|
thresh = cv2.dilate(thresh, None, iterations=10)
|
||||||
|
#cv2.imshow("changes x", thresh)
|
||||||
|
#cv2.waitKey(10) & 0XFF
|
||||||
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
self.diff.append(np.count_nonzero(thresh))
|
||||||
cnts = imutils.grab_contours(cnts)
|
cnts = imutils.grab_contours(cnts)
|
||||||
|
|
||||||
contours = []
|
contours = []
|
||||||
|
|
@ -111,8 +119,7 @@ class ContourExtractor:
|
||||||
frame = np.zeros(shape=[self.yDim, self.xDim, 3], dtype=np.uint8)
|
frame = np.zeros(shape=[self.yDim, self.xDim, 3], dtype=np.uint8)
|
||||||
frame = imutils.resize(frame, width=512)
|
frame = imutils.resize(frame, width=512)
|
||||||
frame[y:y+v.shape[0], x:x+v.shape[1]] = v
|
frame[y:y+v.shape[0], x:x+v.shape[1]] = v
|
||||||
cv2.imshow("changes overlayed", frame)
|
|
||||||
cv2.waitKey(10) & 0XFF
|
|
||||||
cv2.destroyAllWindows()
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
def exportContours(self):
|
def exportContours(self):
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ class Exporter:
|
||||||
continue
|
continue
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
frame2 = underlay
|
frame2 = np.copy(underlay)
|
||||||
for layer in layers:
|
for layer in layers:
|
||||||
if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount:
|
if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount:
|
||||||
for (x, y, w, h) in layer.bounds[frameCount - layer.startFrame]:
|
for (x, y, w, h) in layer.bounds[frameCount - layer.startFrame]:
|
||||||
|
|
@ -55,7 +55,8 @@ class Exporter:
|
||||||
w = int(w * factor)
|
w = int(w * factor)
|
||||||
h = int(h * factor)
|
h = int(h * factor)
|
||||||
|
|
||||||
frame2[y:y+h, x:x+w] = frame[y:y+h, x:x+w]
|
frame2[y:y+h, x:x+w] = np.copy(frame[y:y+h, x:x+w])
|
||||||
|
cv2.putText(frame2, str(int(frameCount/self.fps)), (int(x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1,(255,255,255), 2)
|
||||||
writer.append_data(frame2)
|
writer.append_data(frame2)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,7 +95,10 @@ class Exporter:
|
||||||
# if exportFrame as index instead of frameCount - layer.startFrame then we have layer after layer
|
# if exportFrame as index instead of frameCount - layer.startFrame then we have layer after layer
|
||||||
frame2 = frames[frameCount - layer.startFrame]
|
frame2 = frames[frameCount - layer.startFrame]
|
||||||
frame2[y:y+h, x:x+w] = frame[y:y+h, x:x+w]
|
frame2[y:y+h, x:x+w] = frame[y:y+h, x:x+w]
|
||||||
|
|
||||||
frames[frameCount - layer.startFrame] = np.copy(frame2)
|
frames[frameCount - layer.startFrame] = np.copy(frame2)
|
||||||
|
cv2.putText(frames[frameCount - layer.startFrame], str(int(frameCount/self.fps)), (int(x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1,(255,255,255), 2)
|
||||||
|
|
||||||
videoReader.thread.join()
|
videoReader.thread.join()
|
||||||
|
|
||||||
self.fps = videoReader.getFPS()
|
self.fps = videoReader.getFPS()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from Layer import Layer
|
from Layer import Layer
|
||||||
from Config import Config
|
from Config import Config
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
|
||||||
class LayerFactory:
|
class LayerFactory:
|
||||||
def __init__(self, config, data=None):
|
def __init__(self, config, data=None):
|
||||||
|
|
@ -23,10 +24,12 @@ class LayerFactory:
|
||||||
layers = []
|
layers = []
|
||||||
for i, layer in enumerate(self.layers):
|
for i, layer in enumerate(self.layers):
|
||||||
checks = 0
|
checks = 0
|
||||||
if abs(self.layers[i].bounds[0][0][0] - self.layers[i].bounds[-1][0][0]) < 5:
|
for bound in layer.bounds[0]:
|
||||||
checks += 1
|
for bound2 in layer.bounds[-1]:
|
||||||
if abs(self.layers[i].bounds[0][0][1] - self.layers[i].bounds[-1][0][1]) < 5:
|
if abs(bound[0] - bound2[0]) < 10:
|
||||||
checks += 1
|
checks += 1
|
||||||
|
if abs(bound[1] - bound2[1]) < 10:
|
||||||
|
checks += 1
|
||||||
if checks <= 2:
|
if checks <= 2:
|
||||||
layers.append(layer)
|
layers.append(layer)
|
||||||
self.layers = layers
|
self.layers = layers
|
||||||
|
|
@ -43,7 +46,7 @@ class LayerFactory:
|
||||||
|
|
||||||
|
|
||||||
def extractLayers(self, data = None):
|
def extractLayers(self, data = None):
|
||||||
tol = self.tolerance
|
|
||||||
|
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
if data is None:
|
if data is None:
|
||||||
|
|
@ -57,35 +60,46 @@ class LayerFactory:
|
||||||
for contour in contours:
|
for contour in contours:
|
||||||
self.layers.append(Layer(frameNumber, contour))
|
self.layers.append(Layer(frameNumber, contour))
|
||||||
|
|
||||||
oldLayerIDs = []
|
self.oldLayerIDs = []
|
||||||
# inserts all the fucking contours as layers?
|
|
||||||
for frameNumber in sorted(data.keys()):
|
|
||||||
contours = data[frameNumber]
|
|
||||||
if frameNumber%5000 == 0:
|
|
||||||
print(f"{int(round(frameNumber/max(data.keys()), 2)*100)}% done with Layer extraction")
|
|
||||||
|
|
||||||
for (x,y,w,h) in contours:
|
with ThreadPool(16) as pool:
|
||||||
foundLayer = False
|
for frameNumber in sorted(data.keys()):
|
||||||
for i in set(range(0, len(self.layers))).difference(set(oldLayerIDs)):
|
contours = data[frameNumber]
|
||||||
if frameNumber - self.layers[i].lastFrame > self.ttolerance:
|
if frameNumber%5000 == 0:
|
||||||
oldLayerIDs.append(i)
|
print(f"{int(round(frameNumber/max(data.keys()), 2)*100)}% done with Layer extraction")
|
||||||
continue
|
|
||||||
|
|
||||||
for bounds in self.layers[i].bounds[-1]:
|
tmp = [[frameNumber, contour] for contour in contours]
|
||||||
if bounds is None:
|
#pool.map_async(self.getLayers, tmp)
|
||||||
break
|
for x in tmp:
|
||||||
(x2,y2,w2,h2) = bounds
|
self.getLayers(x)
|
||||||
if self.contoursOverlay((x-tol,y+h+tol), (x+w+tol,y-tol), (x2,y2+h2), (x2+w2,y2)):
|
|
||||||
self.layers[i].add(frameNumber, (x,y,w,h))
|
|
||||||
foundLayer = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not foundLayer:
|
|
||||||
self.layers.append(Layer(frameNumber, (x,y,w,h)))
|
|
||||||
self.freeData()
|
self.freeData()
|
||||||
self.sortLayers()
|
self.sortLayers()
|
||||||
return self.layers
|
return self.layers
|
||||||
|
|
||||||
|
def getLayers(self, data):
|
||||||
|
frameNumber = data[0]
|
||||||
|
bounds = data[1]
|
||||||
|
(x,y,w,h) = bounds
|
||||||
|
tol = self.tolerance
|
||||||
|
foundLayer = False
|
||||||
|
for i in set(range(0, len(self.layers))).difference(set(self.oldLayerIDs)):
|
||||||
|
if frameNumber - self.layers[i].lastFrame > self.ttolerance:
|
||||||
|
self.oldLayerIDs.append(i)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for bounds in self.layers[i].bounds[-1]:
|
||||||
|
if bounds is None:
|
||||||
|
break
|
||||||
|
(x2,y2,w2,h2) = bounds
|
||||||
|
if self.contoursOverlay((x-tol,y+h+tol), (x+w+tol,y-tol), (x2,y2+h2), (x2+w2,y2)):
|
||||||
|
self.layers[i].add(frameNumber, (x,y,w,h))
|
||||||
|
foundLayer = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not foundLayer:
|
||||||
|
self.layers.append(Layer(frameNumber, (x,y,w,h)))
|
||||||
|
|
||||||
def contoursOverlay(self, l1, r1, l2, r2):
|
def contoursOverlay(self, l1, r1, l2, r2):
|
||||||
# If one rectangle is on left side of other
|
# If one rectangle is on left side of other
|
||||||
if(l1[0] >= r2[0] or l2[0] >= r1[0]):
|
if(l1[0] >= r2[0] or l2[0] >= r1[0]):
|
||||||
|
|
|
||||||
|
|
@ -68,29 +68,47 @@ class VideoReader:
|
||||||
self.endFrame = self.listOfFrames[-1]
|
self.endFrame = self.listOfFrames[-1]
|
||||||
|
|
||||||
while self.lastFrame < self.endFrame:
|
while self.lastFrame < self.endFrame:
|
||||||
if not self.buffer.full():
|
if self.lastFrame in self.listOfFrames:
|
||||||
if self.lastFrame in self.listOfFrames:
|
res, frame = self.vc.read()
|
||||||
res, frame = self.vc.read()
|
if res:
|
||||||
if res:
|
self.buffer.put((self.lastFrame, frame))
|
||||||
self.buffer.put((self.lastFrame, frame))
|
# since the list is sorted the first element is always the lowest relevant framenumber
|
||||||
# since the list is sorted the first element is always the lowest relevant framenumber
|
# [0,1,2,3,32,33,34,35,67,68,69]
|
||||||
# [0,1,2,3,32,33,34,35,67,68,69]
|
self.listOfFrames.pop(0)
|
||||||
self.listOfFrames.pop(0)
|
self.lastFrame += 1
|
||||||
self.lastFrame += 1
|
|
||||||
else:
|
|
||||||
# if current Frame number is not in list of Frames, we can skip a few frames
|
|
||||||
self.vc.set(1, self.listOfFrames[0])
|
|
||||||
self.lastFrame = self.listOfFrames[0]
|
|
||||||
else:
|
else:
|
||||||
sleep(0.1)
|
# if current Frame number is not in list of Frames, we can skip a few frames
|
||||||
|
self.vc.set(1, self.listOfFrames[0])
|
||||||
|
self.lastFrame = self.listOfFrames[0]
|
||||||
|
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
|
||||||
def videoEnded(self):
|
def videoEnded(self):
|
||||||
return self.stopped
|
if self.stopped and self.buffer.empty():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def getFPS(self):
|
def getFPS(self):
|
||||||
return self.vc.get(cv2.CAP_PROP_FPS)
|
return self.vc.get(cv2.CAP_PROP_FPS)
|
||||||
|
|
||||||
|
def get_file_metadata(self, path, filename, metadata):
|
||||||
|
# Path shouldn't end with backslash, i.e. "E:\Images\Paris"
|
||||||
|
# filename must include extension, i.e. "PID manual.pdf"
|
||||||
|
# Returns dictionary containing all file metadata.
|
||||||
|
sh = win32com.client.gencache.EnsureDispatch('Shell.Application', 0)
|
||||||
|
ns = sh.NameSpace(path)
|
||||||
|
|
||||||
|
# Enumeration is necessary because ns.GetDetailsOf only accepts an integer as 2nd argument
|
||||||
|
file_metadata = dict()
|
||||||
|
item = ns.ParseName(str(filename))
|
||||||
|
for ind, attribute in enumerate(metadata):
|
||||||
|
attr_value = ns.GetDetailsOf(item, ind)
|
||||||
|
if attr_value:
|
||||||
|
file_metadata[attribute] = attr_value
|
||||||
|
|
||||||
|
return file_metadata
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
5
main.py
5
main.py
|
|
@ -4,7 +4,6 @@ from ContourExctractor import ContourExtractor
|
||||||
from Exporter import Exporter
|
from Exporter import Exporter
|
||||||
from LayerFactory import LayerFactory
|
from LayerFactory import LayerFactory
|
||||||
from Analyzer import Analyzer
|
from Analyzer import Analyzer
|
||||||
from VideoReader import VideoReader
|
|
||||||
from Config import Config
|
from Config import Config
|
||||||
from Importer import Importer
|
from Importer import Importer
|
||||||
import cv2
|
import cv2
|
||||||
|
|
@ -21,6 +20,8 @@ def demo():
|
||||||
config["outputPath"] = os.path.join(os.path.dirname(__file__), "output/short.mp4")
|
config["outputPath"] = os.path.join(os.path.dirname(__file__), "output/short.mp4")
|
||||||
|
|
||||||
if config["importPath"] is None:
|
if config["importPath"] is None:
|
||||||
|
#ana = Analyzer(config)
|
||||||
|
#ref = ana.avg
|
||||||
contours = ContourExtractor(config).extractContours()
|
contours = ContourExtractor(config).extractContours()
|
||||||
print("Time consumed extracting: ", time.time() - start)
|
print("Time consumed extracting: ", time.time() - start)
|
||||||
layerFactory = LayerFactory(config)
|
layerFactory = LayerFactory(config)
|
||||||
|
|
@ -30,7 +31,7 @@ def demo():
|
||||||
|
|
||||||
exporter = Exporter(config)
|
exporter = Exporter(config)
|
||||||
exporter.exportRawData(layers)
|
exporter.exportRawData(layers)
|
||||||
exporter.exportOverlayed(layers)
|
exporter.exportLayers(layers)
|
||||||
|
|
||||||
print("Total time: ", time.time() - start)
|
print("Total time: ", time.time() - start)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
output/short.txt
BIN
output/short.txt
Binary file not shown.
Loading…
Reference in New Issue