├── example ├── ex0_bgr.png ├── ex0_im.png ├── ex1_bgr.png ├── ex1_im.png └── falling_pen.avi ├── dataset ├── geopatterns │ ├── svg.pyc │ ├── utils.pyc │ ├── __init__.pyc │ ├── geopatterns.pyc │ ├── __pycache__ │ │ ├── svg.cpython-37.pyc │ │ ├── utils.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ └── geopatterns.cpython-37.pyc │ ├── __init__.py │ ├── utils.py │ ├── svg.py │ └── geopatterns.py ├── __pycache__ │ ├── helpers.cpython-37.pyc │ └── helpers.cpython-38.pyc ├── generate_dataset.sh ├── generate_patterns.py ├── process_dataset_traj.py └── helpers.py ├── train.sh ├── setup_modules.sh ├── download_datasets.sh ├── LICENSE ├── README.md ├── train.py ├── run.py ├── run_fifa.py └── net_model.py /example/ex0_bgr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/example/ex0_bgr.png -------------------------------------------------------------------------------- /example/ex0_im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/example/ex0_im.png -------------------------------------------------------------------------------- /example/ex1_bgr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/example/ex1_bgr.png -------------------------------------------------------------------------------- /example/ex1_im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/example/ex1_im.png -------------------------------------------------------------------------------- /example/falling_pen.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/example/falling_pen.avi -------------------------------------------------------------------------------- /dataset/geopatterns/svg.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/svg.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/utils.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/__init__.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/geopatterns.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/geopatterns.pyc -------------------------------------------------------------------------------- /dataset/__pycache__/helpers.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/__pycache__/helpers.cpython-37.pyc -------------------------------------------------------------------------------- /dataset/__pycache__/helpers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/__pycache__/helpers.cpython-38.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__pycache__/svg.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/__pycache__/svg.cpython-37.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__pycache__/geopatterns.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozumden/FMODetect/HEAD/dataset/geopatterns/__pycache__/geopatterns.cpython-37.pyc -------------------------------------------------------------------------------- /dataset/geopatterns/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from .geopatterns import GeoPattern 5 | 6 | __all__ = ['GeoPattern'] 7 | -------------------------------------------------------------------------------- /train.sh: -------------------------------------------------------------------------------- 1 | # STORAGE_PATH=/mnt/lascar/rozumden/dataset 2 | STORAGE_PATH=/cluster/scratch/denysr/dataset 3 | DATASET_PATH="${STORAGE_PATH}/votfmomedtraj.hdf5" 4 | MODEL_PATH="/cluster/home/denysr/tmp/Tensorflow" 5 | python3 train.py --dataset_path ${DATASET_PATH} --model_path ${MODEL_PATH} -------------------------------------------------------------------------------- /setup_modules.sh: -------------------------------------------------------------------------------- 1 | module load python_cpu/3.7.4 jq 2 | module load eth_proxy 3 | module load python_gpu/3.7.4 4 | module load cmake glog eigen gflags boost 5 | module load cudnn cuda openblas opencv python_gpu protobuf hdf5 leveldb snappy atlas 6 | module load matlab libxml2 libxslt graphviz -------------------------------------------------------------------------------- /dataset/geopatterns/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | 5 | # Python implementation of Processing's map function 6 | # http://processing.org/reference/map_.html 7 | def promap(value, v_min, v_max, d_min, d_max): # v for value, d for desired 8 | v_value = float(value) 9 | v_range = v_max - v_min 10 | d_range = d_max - d_min 11 | d_value = (v_value - v_min) * d_range / v_range + d_min 12 | return d_value 13 | -------------------------------------------------------------------------------- /dataset/generate_dataset.sh: -------------------------------------------------------------------------------- 1 | # TODO: change it to your own dataset directory 2 | BASE_PATH=/cluster/scratch/denysr/dataset 3 | # BASE_PATH=/mnt/lascar/rozumden/dataset 4 | 5 | PATTERNS_PATH="${BASE_PATH}/patterns" 6 | 7 | BG_PATH="${BASE_PATH}/vot" 8 | DATASET_PATH="${BASE_PATH}/votfmomedtraj_inputs.hdf5" 9 | 10 | mkdir -p ${PATTERNS_PATH} 11 | mkdir -p ${BG_PATH} 12 | 13 | VOT_YEAR=2016 bash trackdat/scripts/download_vot.sh dl/vot 14 | bash trackdat/scripts/unpack_vot.sh dl/vot ${BG_PATH} 15 | python3 generate_patterns.py --fg_path ${PATTERNS_PATH} 16 | python3 process_dataset_traj.py --bg_path ${BG_PATH} --fg_path ${PATTERNS_PATH} --dataset_path ${DATASET_PATH} --dataset_size 5000 17 | 18 | 19 | -------------------------------------------------------------------------------- /download_datasets.sh: -------------------------------------------------------------------------------- 1 | MYPATH=/cluster/home/denysr/scratch/dataset/ 2 | 3 | # falling objects dataset 4 | mkdir ${MYPATH}falling_objects 5 | wget http://ptak.felk.cvut.cz/personal/rozumden/falling_imgs_gt.zip -P ${MYPATH}falling_objects 6 | unzip ${MYPATH}falling_objects/falling_imgs_gt.zip -d ${MYPATH}falling_objects/ 7 | rm ${MYPATH}falling_objects/falling_imgs_gt.zip 8 | 9 | # TbD-3D dataset 10 | mkdir ${MYPATH}TbD-3D 11 | wget http://ptak.felk.cvut.cz/personal/rozumden/TbD-3D-imgs.zip -P ${MYPATH}TbD-3D 12 | unzip ${MYPATH}TbD-3D/TbD-3D-imgs.zip -d ${MYPATH}TbD-3D/ 13 | rm ${MYPATH}TbD-3D/TbD-3D-imgs.zip 14 | 15 | # TbD dataset 16 | mkdir ${MYPATH}TbD_GC 17 | wget http://ptak.felk.cvut.cz/personal/rozumden/TbD_imgs_upd.zip -P ${MYPATH}TbD_GC 18 | unzip ${MYPATH}TbD_GC/TbD_imgs_upd.zip -d ${MYPATH}TbD_GC/ 19 | rm ${MYPATH}TbD_GC/TbD.zip 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Denys Rozumnyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FMODetect Evaluation and Training Code 2 | 3 | ### FMODetect: Robust Detection of Fast Moving Objects 4 | #### Denys Rozumnyi, Martin R. Oswald, Vittorio Ferrari, Jiri Matas, Marc Pollefeys 5 | 6 | ### Inference 7 | To detect fast moving objects in video: 8 | ```bash 9 | python run.py --video example/falling_pen.avi 10 | ``` 11 | 12 | To detect fast moving objects in a single frame with the given background: 13 | ```bash 14 | python run.py --im example/ex1_im.png --bgr example/ex1_bgr.png 15 | ``` 16 | 17 | We only provide the detection part. The deblurring and trajectory reconstruction part will be added later. 18 | 19 | ### Dataset generation 20 | Before generating the dataset, please make sure you cloned recursively, e.g. `git clone --recursive git@github.com:rozumden/FMODetect.git` 21 | Also, please set your paths in `dataset/generate_dataset.sh`. Then, run this script. 22 | 23 | ### Pre-trained models 24 | 25 | The pre-trained FMODetect model as reported in the paper is available here: https://polybox.ethz.ch/index.php/s/X3J41G9DFuwQOeY. 26 | 27 | Reference 28 | ------------ 29 | If you use this repository, please cite the following publication ( https://arxiv.org/abs/2012.08216 ): 30 | 31 | ```bibtex 32 | @inproceedings{fmodetect, 33 | author = {Denys Rozumnyi and Jiri Matas and Filip Sroubek and Marc Pollefeys and Martin R. Oswald}, 34 | title = {FMODetect: Robust Detection of Fast Moving Objects}, 35 | booktitle = {arxiv}, 36 | address = {online}, 37 | month = dec, 38 | year = {2020} 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /dataset/generate_patterns.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import shutil 4 | import argparse 5 | from geopatterns import GeoPattern 6 | import cairosvg 7 | import numpy as np 8 | import random 9 | import string 10 | import pdb 11 | 12 | def parse_args(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("--fg_path", required=True) 15 | return parser.parse_args() 16 | 17 | def randomString(stringLength=10): 18 | """Generate a random string of fixed length """ 19 | letters = string.ascii_lowercase 20 | return ''.join(random.choice(letters) for i in range(stringLength)) 21 | 22 | def main(): 23 | fg_number = 100 24 | args = parse_args() 25 | # generators = np.array(['chevrons','octagons','overlapping_circles','plus_signs','xes','sine_waves','hexagons','overlapping_rings','plaid','triangles','squares','nested_squares','mosaic_squares','concentric_circles','diamonds','tessellation']) 26 | generators = np.array(["bricks", "hexagons", "overlapping_circles", "overlapping_rings", "plaid", "plus_signs", "rings", "sinewaves", "squares", "triangles", "xes"]) 27 | 28 | for i in range(fg_number): 29 | gi = random.randint(0, generators.size-1) 30 | si = random.randint(10, 100) 31 | sstr = randomString(si) 32 | pattern = GeoPattern(sstr, generator=generators[gi]) 33 | pth = args.fg_path+"/pattern"+str(i)+".png" 34 | png = cairosvg.svg2png(bytestring=pattern.svg_string, write_to=pth) 35 | 36 | # pdb.set_trace() 37 | 38 | 39 | if __name__ == "__main__": 40 | main() -------------------------------------------------------------------------------- /dataset/geopatterns/svg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import math 5 | 6 | 7 | class SVG(object): 8 | def __init__(self): 9 | self._width = 100 10 | self._height = 100 11 | self.svg_string = '' 12 | 13 | @property 14 | def height(self): 15 | return self._height 16 | 17 | @height.setter 18 | def height(self, value): 19 | self._height = math.floor(value) 20 | 21 | @property 22 | def width(self): 23 | return self._width 24 | 25 | @width.setter 26 | def width(self, value): 27 | self._width = math.floor(value) 28 | 29 | @property 30 | def svg_header(self): 31 | return ''.format(**{ 32 | 'width': self.width, 'height': self.height 33 | }) 34 | 35 | @property 36 | def svg_closer(self): 37 | return '' 38 | 39 | def to_string(self): 40 | return ''.join([self.svg_header, self.svg_string, self.svg_closer]) 41 | 42 | def rect(self, x, y, width, height, **kwargs): 43 | self.svg_string += ''.format(**{ 44 | 'x': x, 'y': y, 'width': width, 'height': height, 'kwargs': self.write_args(**kwargs) 45 | }) 46 | 47 | def circle(self, cx, cy, r, **kwargs): 48 | self.svg_string += ''.format(**{ 49 | 'cx': cx, 'cy': cy, 'r': r, 'kwargs': self.write_args(**kwargs) 50 | }) 51 | 52 | def path(self, str, **kwargs): 53 | self.svg_string += ''.format(**{ 54 | 'str': str, 'kwargs': self.write_args(**kwargs) 55 | }) 56 | 57 | def polyline(self, str, **kwargs): 58 | self.svg_string += ''.format(**{ 59 | 'str': str, 'kwargs': self.write_args(**kwargs) 60 | }) 61 | 62 | def group(self, elements, **kwargs): 63 | self.svg_string += ''.format(self.write_args(**kwargs)) 64 | for element in elements: 65 | exec(element) 66 | self.svg_string += '' 67 | 68 | def write_args(self, **kwargs): 69 | str = '' 70 | for key, value in kwargs.items(): 71 | if isinstance(value, dict): 72 | str += '{}="'.format(key) 73 | for key, value in value.items(): 74 | str += '{}:{};'.format(key, value) 75 | str += '" ' 76 | else: 77 | str += '{}="{}" '.format(key, value) 78 | return str 79 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import argparse 3 | import numpy as np 4 | import random 5 | import tensorflow as tf 6 | from tensorflow import keras 7 | from net_model import * 8 | 9 | import imageio 10 | import os 11 | from os.path import isfile, join, isdir 12 | import shutil 13 | import datetime 14 | import h5py 15 | 16 | def parse_args(): 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument("--dataset_path", required=True) 19 | parser.add_argument("--model_path", required=True) 20 | return parser.parse_args() 21 | 22 | 23 | def main(): 24 | EPOCHS = 300 25 | BATCH_SIZE = 32 26 | PERC_TRAIN = 0.96 27 | input_bgr = True 28 | max_shape = [256, 512] 29 | n_gpus = 1 30 | 31 | folder_name = '{date:%Y%m%d_%H%M}'.format( date=datetime.datetime.now()) 32 | 33 | args = parse_args() 34 | 35 | h5_file = h5py.File(args.dataset_path, 'r', swmr=True) 36 | dataset_size = len(h5_file.keys()) 37 | 38 | print(tf.__version__) 39 | train_size = round(dataset_size*PERC_TRAIN) 40 | 41 | model = get_model(input_bgr, n_gpus) 42 | os.makedirs(join(args.model_path,folder_name)) 43 | os.makedirs(join(args.model_path,folder_name,"eval")) 44 | os.makedirs(join(args.model_path,folder_name,"models")) 45 | keras.utils.plot_model(model, join(args.model_path,folder_name,'model.png'), show_shapes=True) 46 | 47 | model.summary() 48 | for layer in model.layers: 49 | print(layer.name, ':', layer.trainable) 50 | 51 | log_dir = join(args.model_path,folder_name,"logs") 52 | shutil.rmtree(log_dir, ignore_errors=True) 53 | 54 | tensorboard_cbk = keras.callbacks.TensorBoard(log_dir=log_dir) 55 | model_saver = keras.callbacks.ModelCheckpoint(filepath=join(args.model_path,folder_name,"models","best_model.h5"),save_best_only=True,monitor='val_loss', mode='min', verbose=1) 56 | earlystopper = keras.callbacks.EarlyStopping(monitor='val_loss',mode='min',patience=50, verbose=1) 57 | 58 | history = model.fit_generator(get_generator(h5_file, BATCH_SIZE, range(train_size), max_shape, input_bgr), 59 | validation_data=get_generator(h5_file, BATCH_SIZE, range(train_size,dataset_size), max_shape, input_bgr), steps_per_epoch=train_size/BATCH_SIZE, validation_steps=1, epochs=EPOCHS, callbacks=[tensorboard_cbk,model_saver,earlystopper]) 60 | 61 | model.save(join(args.model_path,folder_name,'models','final_model.h5')) 62 | 63 | print('\nhistory dict:', history.history) 64 | 65 | subset = [0,3,5,900,902,990,995] 66 | valX, valY = get_data(h5_file, subset, max_shape, input_bgr) 67 | predictions = model.predict(valX) 68 | Xim = get_im(dset_im, subset) 69 | 70 | for ti in range(predictions.shape[0]): 71 | fname = "%08d_" % (ti) 72 | im = Xim[ti,:,:,:3] 73 | H = predictions[ti,:,:,0] 74 | npmax = np.max(H) 75 | if npmax == 0: 76 | npmax = 1 77 | imageio.imwrite(join(args.model_path,folder_name,"eval",fname+"im.png"), (255*im).astype(np.uint8)) 78 | imageio.imwrite(join(args.model_path,folder_name,"eval",fname+"psf.png"), (255*(H/npmax)).astype(np.uint8)) 79 | # imageio.imwrite(join(args.model_path,"eval",fname+"bgr.png"), (255*B).astype(np.uint8)) 80 | # imageio.imwrite(join(args.model_path,"eval",fname+"F.png"), (255*Fsave).astype(np.uint8)) 81 | # imageio.imwrite(join(args.model_path,"eval",fname+"M.png"), (255*M).astype(np.uint8)) 82 | 83 | if __name__ == "__main__": 84 | main() -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import tensorflow as tf 4 | from net_model import * 5 | import imageio 6 | import os 7 | import cv2 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("--video", required=False, default=None) 12 | parser.add_argument("--im", required=False, default=None) 13 | parser.add_argument("--bgr", required=False, default=None) 14 | parser.add_argument("--model", required=False, default="FMODetect.h5") 15 | parser.add_argument("--save", required=False, default="example") 16 | parser.add_argument("--median", required=False, default=3) 17 | return parser.parse_args() 18 | 19 | def main(): 20 | args = parse_args() 21 | if not os.path.exists(args.save): 22 | os.makedirs(args.save) 23 | 24 | model = tf.keras.models.load_model(args.model, custom_objects={ 'fmo_loss_function': custom_loss(None) }) 25 | 26 | if args.video is not None: 27 | ## estimate initial background 28 | Ims = [] 29 | cap = cv2.VideoCapture(args.video) 30 | while cap.isOpened(): 31 | ret, frame = cap.read() 32 | Ims.append(frame) 33 | if len(Ims) >= args.median: 34 | break 35 | B = np.median(np.asarray(Ims)/255, 0)[:,:,[2,1,0]] 36 | 37 | ## run FMODetect 38 | shape = process_image(B).shape 39 | out = cv2.VideoWriter(os.path.join(args.save, 'detections.avi'),cv2.VideoWriter_fourcc(*"MJPG"), 6, (shape[1], shape[0]),True) 40 | frmi = 0 41 | while cap.isOpened(): 42 | if frmi < args.median: 43 | frame = Ims[frmi] 44 | else: 45 | ret, frame = cap.read() 46 | if not ret: 47 | break 48 | Ims = Ims[1:] 49 | Ims.append(frame) 50 | ## update background (running median) 51 | B = np.median(np.asarray(Ims)/255, 0)[:,:,[2,1,0]] 52 | frmi += 1 53 | I = process_image(frame[:,:,[2,1,0]]/255) 54 | X = np.concatenate((I,process_image(B)),2)[None] 55 | predictions = model.predict(X) 56 | predictions[predictions < 0] = 0 57 | predictions[predictions > 1] = 1 58 | Io = I - I.min() 59 | Io = Io / Io.max() 60 | # out.write( (predictions[0][:,:,[0,0,0]] * 255).astype(np.uint8) ) 61 | out.write( (predictions[0][:,:,[0,0,0]]*Io[:,:,[2,1,0]] * 255).astype(np.uint8) ) 62 | 63 | cap.release() 64 | out.release() 65 | else: 66 | if args.im is None: 67 | ims = [] 68 | bgrs = [] 69 | for ss in range(2): 70 | ims.append(os.path.join('example','ex{:1d}_im.png'.format(ss))) 71 | bgrs.append(os.path.join('example','ex{:1d}_bgr.png'.format(ss))) 72 | else: 73 | ims = [args.im] 74 | bgrs = [args.bgr] 75 | 76 | for ss in range(len(ims)): 77 | I = process_image(get_im(ims[ss])) 78 | B = process_image(get_im(bgrs[ss])) 79 | X = np.concatenate((I,B),2)[None] 80 | 81 | predictions = model.predict(X) 82 | predictions[predictions < 0] = 0 83 | predictions[predictions > 1] = 1 84 | 85 | imageio.imwrite(os.path.join(args.save,"ex{:1d}_tdf.png".format(ss)), predictions[0]) 86 | Io = I - I.min() 87 | Io = Io / Io.max() 88 | imageio.imwrite(os.path.join(args.save,"ex{:1d}_tdfim.png".format(ss)), predictions[0][:,:,[0,0,0]]*Io[:,:,[2,1,0]]) 89 | 90 | if __name__ == "__main__": 91 | main() -------------------------------------------------------------------------------- /run_fifa.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import tensorflow as tf 4 | from net_model import * 5 | import imageio 6 | import os 7 | import cv2 8 | import skimage 9 | 10 | def parse_args(): 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument("--video", required=False, default=None) 13 | parser.add_argument("--model", required=False, default="FMODetect.h5") 14 | parser.add_argument("--save", required=False, default="example") 15 | parser.add_argument("--median", required=False, default=3) 16 | parser.add_argument("--average", required=False, default=True) 17 | return parser.parse_args() 18 | 19 | def interpolate_fifa(im): 20 | im0 = im.copy() 21 | im0[1:-1:2] = (im0[:-2:2] + im0[2::2])/2 22 | im1 = im.copy() 23 | im1[2:-2:2] = (im1[1:-3:2] + im1[3:-1:2])/2 24 | return im0, im1 25 | 26 | def get_frame(frame, inc_res = 2): 27 | sh = frame.shape[:2] 28 | sh0 = int(sh[0]/6) 29 | sh1 = int(sh[1]/6) 30 | # frame_crop = frame 31 | frame_crop = frame[2*sh0:-3*sh0,2*sh1:-3*sh1] 32 | frame_crop = skimage.transform.resize(frame_crop, (frame_crop.shape[0]*inc_res, frame_crop.shape[1]*inc_res), order=3) 33 | return frame_crop 34 | 35 | def main(): 36 | args = parse_args() 37 | if not os.path.exists(args.save): 38 | os.makedirs(args.save) 39 | 40 | model = tf.keras.models.load_model(args.model, custom_objects={ 'fmo_loss_function': custom_loss(None) }) 41 | 42 | ## estimate initial background 43 | Ims = [] 44 | cap = cv2.VideoCapture(args.video) 45 | while cap.isOpened(): 46 | ret, frame = cap.read() 47 | if not frame is None: 48 | frame = frame / 255 49 | frame0, frame1 = interpolate_fifa(frame) 50 | if args.average: 51 | Ims.append(get_frame((frame0 + frame1)/2)) 52 | else: 53 | Ims.append(get_frame(frame0)) 54 | if len(Ims) < args.median: 55 | Ims.append(get_frame(frame1)) 56 | 57 | if len(Ims) >= args.median: 58 | break 59 | B = np.median(np.asarray(Ims)/255, 0)[:,:,[2,1,0]] 60 | 61 | ## run FMODetect 62 | shape = process_image(B).shape 63 | out = cv2.VideoWriter(os.path.join(args.save, 'detections.avi'),cv2.VideoWriter_fourcc(*"MJPG"), 6, (shape[1], shape[0]),True) 64 | frmi = 0 65 | frame1 = None 66 | while cap.isOpened(): 67 | if frmi < args.median: 68 | frame = Ims[frmi] 69 | else: 70 | if frame1 is None: 71 | ret, frame = cap.read() 72 | if not ret: 73 | break 74 | frame = frame / 255 75 | frame0, frame1 = interpolate_fifa(frame) 76 | if args.average: 77 | frame = get_frame( (frame0 + frame1)/2 ) 78 | frame1 = None 79 | else: 80 | frame = get_frame(frame0) 81 | else: 82 | frame = get_frame(frame1) 83 | frame1 = None 84 | Ims = Ims[1:] 85 | Ims.append(frame) 86 | ## update background (running median) 87 | B = np.median(np.asarray(Ims), 0)[:,:,[2,1,0]] 88 | frmi += 1 89 | if args.average: 90 | mult = 1 91 | else: 92 | mult = 2 93 | if frmi < mult*88: 94 | continue 95 | 96 | I = process_image(frame[:,:,[2,1,0]]) 97 | X = np.concatenate((I,process_image(B)),2)[None] 98 | predictions = model.predict(X) 99 | predictions[predictions < 0] = 0 100 | predictions[predictions > 1] = 1 101 | Io = I - I.min() 102 | Io = Io / Io.max() 103 | 104 | imageio.imwrite('tmpi.png',frame[:,:,[2,1,0]]) 105 | imageio.imwrite('tmpb.png',B) 106 | imageio.imwrite('tmpo.png',predictions[0][:,:,[0,0,0]]) 107 | breakpoint() 108 | 109 | # out.write( (predictions[0][:,:,[0,0,0]] * 255).astype(np.uint8) ) 110 | out.write( (predictions[0][:,:,[0,0,0]]*Io[:,:,[2,1,0]]).astype(np.uint8) ) 111 | 112 | cap.release() 113 | out.release() 114 | 115 | if __name__ == "__main__": 116 | main() -------------------------------------------------------------------------------- /net_model.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import tensorflow as tf 4 | from tensorflow import keras 5 | 6 | from tensorflow.keras.models import Model 7 | from tensorflow.keras.layers import * 8 | from tensorflow.keras.preprocessing.image import ImageDataGenerator 9 | import tensorflow.keras.backend as K 10 | 11 | import numpy as np 12 | import cv2 13 | import skimage.transform 14 | import random 15 | from dataset.helpers import * 16 | import scipy 17 | 18 | def conv_layer(x, n_filters, do_batchnorm = False, activation = None): 19 | for kk in range(3): 20 | x = Conv2D(n_filters, (3,3), activation = activation, padding='same')(x) 21 | x = LeakyReLU(alpha=0.1)(x) 22 | if do_batchnorm: x = BatchNormalization()(x) 23 | 24 | return x 25 | 26 | def get_model(input_bgr, n_gpus=1): 27 | if input_bgr: 28 | input_layer = Input(shape=(None, None, 6)) 29 | else: 30 | input_layer = Input(shape=(None, None, 3)) 31 | 32 | ## encoder 33 | x = conv_layer(input_layer, 16) 34 | x_skip1 = x 35 | x = MaxPooling2D()(x) 36 | x = conv_layer(x, 64) 37 | x_skip2 = x 38 | x = MaxPooling2D()(x) 39 | x = conv_layer(x, 128) 40 | x_skip3 = x 41 | x = MaxPooling2D()(x) 42 | x = conv_layer(x, 256) 43 | x_skip4 = x 44 | x = MaxPooling2D()(x) 45 | 46 | ## latent space 47 | x = conv_layer(x, 256) 48 | 49 | ## decoder 50 | x = Conv2DTranspose(128, kernel_size=(2,2), strides=(2,2))(x) 51 | x = Concatenate()([x, x_skip4]) 52 | x = conv_layer(x, 128) 53 | 54 | x = Conv2DTranspose(64, kernel_size=(2,2), strides=(2,2))(x) 55 | x = Concatenate()([x, x_skip3]) 56 | x = conv_layer(x, 64) 57 | 58 | x = Conv2DTranspose(32, kernel_size=(2,2), strides=(2,2))(x) 59 | x = Concatenate()([x, x_skip2]) 60 | x = conv_layer(x, 32) 61 | 62 | x = Conv2DTranspose(16, kernel_size=(2,2), strides=(2,2))(x) 63 | x = Concatenate()([x, x_skip1]) 64 | x = conv_layer(x, 16) 65 | 66 | x = Conv2D(4,(3,3), activation = None, padding='same')(x) 67 | x = Conv2D(4,(3,3), activation = None, padding='same')(x) 68 | x = Conv2D(1,(3,3), activation = None, padding='same')(x) 69 | 70 | output_layer = x 71 | model = Model(inputs = input_layer, outputs = output_layer) 72 | if n_gpus > 1: 73 | model = tf.keras.utils.multi_gpu_model(model, gpus=n_gpus) 74 | 75 | optimizer = tf.keras.optimizers.Adam(learning_rate=2e-5) 76 | model.compile(optimizer = optimizer, loss = custom_loss(input_layer), 77 | metrics=["mae"], experimental_run_tf_function=False) 78 | 79 | return model 80 | 81 | 82 | def custom_loss(input_layer): 83 | def fmo_loss_function(Yact, Ypred): 84 | Ha = Yact[:,:,:,0] 85 | H = Ypred[:,:,:,0] 86 | ### DT loss inverse 87 | total_loss = K.mean(K.abs(H[Ha > 0] - Ha[Ha > 0])) + K.mean(K.abs(H[Ha==0] - 0)) 88 | return total_loss 89 | 90 | return fmo_loss_function 91 | 92 | def get_generator(h5_file, batch_size, all_subset, max_shape, input_bgr): 93 | while True: 94 | subset = random.sample(all_subset, batch_size) 95 | X, Y = get_data(h5_file, subset, max_shape, input_bgr) 96 | yield X, Y 97 | 98 | def process_image(img, do_resize = True, shape = None): 99 | if shape is None: 100 | shape = img.shape[:2] 101 | shape = [16 * (sh // 16) for sh in shape] 102 | if do_resize: 103 | img = skimage.transform.resize(img, shape, order=3) 104 | else: 105 | img = img[:shape[0],:shape[1]] 106 | img = img - np.mean(img) 107 | img = img / np.sqrt(np.var(img)) 108 | return img 109 | 110 | def get_data(h5_file, subset, max_shape, input_bgr): 111 | if input_bgr: 112 | X = np.zeros([len(subset),max_shape[0],max_shape[1],6]) 113 | else: 114 | X = np.zeros([len(subset),max_shape[0],max_shape[1],3]) 115 | 116 | Y = np.zeros([len(subset),max_shape[0],max_shape[1],1]) 117 | 118 | ki = 0 119 | for k in subset: 120 | fname = "%08d_" % (k) 121 | if 'im' in h5_file[fname].keys(): 122 | I = skimage.transform.resize(h5_file[fname]['im'], max_shape, order=3) 123 | I = process_image(I,False) 124 | 125 | H = skimage.transform.resize(h5_file[fname]['psf'], max_shape, order=1) 126 | 127 | M = skimage.transform.resize(h5_file[fname]['M'], max_shape, order=1) 128 | 129 | rad = np.sqrt(np.sum(M)) 130 | DT = scipy.ndimage.morphology.distance_transform_edt(H == 0) 131 | DT = DT / (2*rad) 132 | DT[DT > 1] = 1 133 | 134 | X[ki,:,:,:3] = I 135 | 136 | if input_bgr: 137 | B = skimage.transform.resize(h5_file[fname]['bgr'], max_shape, order=3) 138 | B = process_image(B,False) 139 | X[ki,:,:,3:] = B 140 | 141 | Y[ki,:,:,0] = 1 - DT 142 | else: 143 | X[ki] = h5_file[fname]['X'] 144 | Y[ki] = h5_file[fname]['Y'] 145 | ki = ki + 1 146 | return X,Y 147 | 148 | def get_im(path): 149 | I = cv2.imread(path) 150 | return I -------------------------------------------------------------------------------- /dataset/process_dataset_traj.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os import listdir 3 | from os.path import isfile, join, isdir 4 | import glob 5 | import shutil 6 | import argparse 7 | from geopatterns import GeoPattern 8 | import cairosvg 9 | import random 10 | import cv2 11 | import scipy 12 | from scipy import signal 13 | from skimage.draw import line_aa 14 | from helpers import * 15 | import math 16 | import imageio 17 | import skimage.transform 18 | import pdb 19 | import h5py 20 | 21 | 22 | def parse_args(): 23 | parser = argparse.ArgumentParser() 24 | 25 | parser.add_argument("--bg_path", required=True) 26 | parser.add_argument("--fg_path", required=True) 27 | parser.add_argument("--dataset_path", required=True) 28 | parser.add_argument("--dataset_size", type=int, required=True) 29 | 30 | return parser.parse_args() 31 | 32 | 33 | def main(): 34 | random.seed(3524) 35 | max_shape = [256, 512] 36 | generate_inputs = True 37 | 38 | args = parse_args() 39 | 40 | fgs = [f for f in listdir(args.fg_path) if isfile(join(args.fg_path, f))] 41 | videos = [f for f in listdir(args.bg_path) if isdir(join(args.bg_path, f))] 42 | bgs = [glob.glob(join(args.bg_path,f,'*.jpg')) for f in videos] 43 | 44 | ti = 0 45 | 46 | compression_type = None 47 | use_hdf5 = 'hdf5' in args.dataset_path 48 | if use_hdf5: 49 | if ti == 0 and os.path.exists(args.dataset_path): 50 | # TODO ask for confirmation to delete old file 51 | os.remove(args.dataset_path) 52 | output_file = h5py.File(args.dataset_path, 'a') 53 | 54 | while ti < (args.dataset_size): 55 | print(ti) 56 | si = random.randint(0,len(videos)-1) 57 | vid = videos[si] 58 | frms = bgs[si] 59 | bg = frms[random.randint(0,len(frms)-1)] 60 | fg = fgs[random.randint(0,len(fgs)-1)] 61 | 62 | frmi = int(bg[-12:-4]) 63 | if frmi < 3: 64 | continue 65 | 66 | 67 | F = cv2.imread(join(args.fg_path,fg))/255 68 | B = cv2.imread(bg)/255 69 | 70 | shp = np.mod(F.shape,100) 71 | if np.min(shp[:2]) < 20: 72 | continue 73 | F = skimage.transform.resize(F, shp, order=3) 74 | 75 | 76 | bg0 = bg[:-12]+str(frmi-1).zfill(8) +bg[-4:] 77 | bg00 = bg[:-12]+str(frmi-2).zfill(8) +bg[-4:] 78 | B0 = cv2.imread(bg0)/255 79 | B00 = cv2.imread(bg00)/255 80 | 81 | BC = np.zeros([B.shape[0],B.shape[1],3,3]) 82 | BC[:,:,:,0] = B 83 | BC[:,:,:,1] = B0 84 | BC[:,:,:,2] = B00 85 | BMED = np.median(BC,3) 86 | 87 | diam = round(min(F.shape[0:2])) 88 | 89 | F = F[:diam,:diam,:] 90 | rad = diam/2 91 | M = diskMask(rad) 92 | M3 = np.repeat(M[:, :, np.newaxis], 3, axis=2) 93 | FM = F*M3 94 | 95 | ## Generate random trajectories 96 | H = np.zeros(B.shape[0:2]) 97 | rind = random.randint(0,9) 98 | tlen = random.uniform(1.5,9.0)*rad 99 | start = [random.randint(0, H.shape[0]-1), random.randint(0, H.shape[1]-1)] 100 | towrite = np.zeros([2,4]) 101 | towrite[:,0] = start 102 | if rind == 0: 103 | ## generate broken line 104 | prc = random.uniform(0.15,0.85) 105 | ori0 = [] 106 | for pr1 in [prc, 1-prc]: 107 | while True: 108 | ori = random.uniform(0,2*math.pi) 109 | if ori0 == []: 110 | break 111 | delta = np.mod(ori - ori0 + 3*math.pi, 2*math.pi) - math.pi 112 | if np.abs(delta) < math.pi/6 or np.abs(delta) > 5*math.pi/6: ## if < 30 or > 150 degrees 113 | continue 114 | break 115 | 116 | end = [round(start[0] + math.sin(ori)*tlen*pr1), round(start[1] + math.cos(ori)*tlen*pr1)] 117 | rr, cc, val = line_aa(start[0], start[1], end[0], end[1]) 118 | valid = np.logical_and(np.logical_and(rr < H.shape[0], cc < H.shape[1]), np.logical_and(rr > 0, cc > 0)) 119 | rr = rr[valid] 120 | cc = cc[valid] 121 | val = val[valid] 122 | H[rr, cc] = val 123 | if ori0 == []: 124 | towrite[:,1] = np.array(end) - np.array(start) 125 | else: 126 | towrite[:,3] = np.array(end) - np.array(start) 127 | 128 | start = end 129 | ori0 = ori 130 | elif rind == 1: 131 | ## generate parabola 132 | ori = random.uniform(0,2*math.pi) 133 | end = [round(start[0] + math.sin(ori)*tlen), round(start[1] + math.cos(ori)*tlen)] 134 | towrite[:,1] = np.array(end) - np.array(start) 135 | towrite[:,2] = [random.uniform(10.0,20.0), random.uniform(10.0,20.0)] 136 | H = renderTraj(towrite, H) 137 | else: 138 | ori = random.uniform(0,2*math.pi) 139 | end = [round(start[0] + math.sin(ori)*tlen), round(start[1] + math.cos(ori)*tlen)] 140 | rr, cc, val = line_aa(start[0], start[1], end[0], end[1]) 141 | valid = np.logical_and(np.logical_and(rr < H.shape[0], cc < H.shape[1]), np.logical_and(rr > 0, cc > 0)) 142 | rr = rr[valid] 143 | cc = cc[valid] 144 | val = val[valid] 145 | H[rr, cc] = val 146 | towrite[:,1] = np.array(end) - np.array(start) 147 | 148 | if np.sum(H) < tlen: 149 | continue 150 | H = H/np.sum(H) 151 | ######## 152 | 153 | 154 | HM = signal.fftconvolve(H, M, mode='same') 155 | HM3 = np.repeat(HM[:, :, np.newaxis], 3, axis=2) 156 | HF = np.zeros(B.shape) 157 | for kk in range(3): 158 | HF[:,:,kk] = signal.fftconvolve(H, FM[:,:,kk], mode='same') 159 | 160 | im = B*(1-HM3) + HF 161 | 162 | TH = np.zeros(B.shape[0:2]) 163 | TH[round(TH.shape[0]/2),round(TH.shape[1]/2)] = 1 164 | M = signal.fftconvolve(TH, M, mode='same') 165 | Fsave = np.zeros(B.shape) 166 | for kk in range(3): 167 | Fsave[:,:,kk] = signal.fftconvolve(TH, FM[:,:,kk], mode='same') 168 | 169 | # imshow(im) 170 | # pdb.set_trace() 171 | fname = "%08d_" % (ti) 172 | if use_hdf5: 173 | output_file.create_group(fname) 174 | if generate_inputs: 175 | X, Y = get_data_processed(im, BMED, M, H/np.max(H), max_shape) 176 | output_file[fname].create_dataset('X', data=X, compression=compression_type) 177 | output_file[fname].create_dataset('Y', data=Y, compression=compression_type) 178 | else: 179 | output_file[fname].create_dataset('im', data=(255*im).astype(np.uint8), compression=compression_type) 180 | output_file[fname].create_dataset('bgr', data=(255*BMED).astype(np.uint8), compression=compression_type) 181 | output_file[fname].create_dataset('psf', data=(255*(H/np.max(H))).astype(np.uint8), compression=compression_type) 182 | output_file[fname].create_dataset('F', data=(255*Fsave).astype(np.uint8), compression=compression_type) 183 | output_file[fname].create_dataset('M', data=(255*M).astype(np.uint8), compression=compression_type) 184 | output_file[fname].create_dataset('traj', data=towrite, compression=compression_type) 185 | else: 186 | imageio.imwrite(join(args.dataset_path,fname+"im.png"), (255*im).astype(np.uint8)) 187 | imageio.imwrite(join(args.dataset_path,fname+"bgr.png"), (255*BMED).astype(np.uint8)) 188 | imageio.imwrite(join(args.dataset_path,fname+"psf.png"), (255*(H/np.max(H))).astype(np.uint8)) 189 | imageio.imwrite(join(args.dataset_path,fname+"F.png"), (255*Fsave).astype(np.uint8)) 190 | imageio.imwrite(join(args.dataset_path,fname+"M.png"), (255*M).astype(np.uint8)) 191 | 192 | with open(join(args.dataset_path,fname+"traj.txt"), 'w') as fff: 193 | for k1 in range(towrite.shape[0]): 194 | for k2 in range(towrite.shape[1]): 195 | fff.write('%.1f ' % towrite[k1,k2]) 196 | fff.write('\n') 197 | 198 | ti = ti + 1 199 | 200 | def get_data_processed(im, bgr, M, psf, max_shape): 201 | X = np.zeros([1,max_shape[0],max_shape[1],6]) 202 | Y = np.zeros([1,max_shape[0],max_shape[1],1]) 203 | ki = 0 204 | I = skimage.transform.resize(im, max_shape, order=3) 205 | I = I - np.mean(I) 206 | I = I / np.sqrt(np.var(I)) 207 | 208 | H = skimage.transform.resize(psf, max_shape, order=1) 209 | 210 | M = skimage.transform.resize(M, max_shape, order=1) 211 | 212 | rad = np.sqrt(np.sum(M)) 213 | DT = scipy.ndimage.morphology.distance_transform_edt(H == 0) 214 | DT = DT / (2*rad) 215 | DT[DT > 1] = 1 216 | 217 | X[ki,:,:,:3] = I 218 | 219 | if True: 220 | B = skimage.transform.resize(bgr, max_shape, order=3) 221 | B = B - np.mean(B) 222 | B = B / np.sqrt(np.var(B)) 223 | X[ki,:,:,3:] = B 224 | 225 | Y[ki,:,:,0] = 1 - DT 226 | 227 | return X,Y 228 | 229 | if __name__ == "__main__": 230 | main() 231 | -------------------------------------------------------------------------------- /dataset/helpers.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import math 4 | from skimage.draw import line_aa 5 | from skimage import measure 6 | import skimage.transform 7 | 8 | import pdb 9 | 10 | def imshow(im): 11 | cv2.imshow('image',im), cv2.waitKey(0), cv2.destroyAllWindows() 12 | 13 | def diskMask(rad): 14 | sz = 2*np.array([rad, rad]) 15 | 16 | ran1 = np.arange(-(sz[1]-1)/2, ((sz[1]-1)/2)+1, 1.0) 17 | ran2 = np.arange(-(sz[0]-1)/2, ((sz[0]-1)/2)+1, 1.0) 18 | xv, yv = np.meshgrid(ran1, ran2) 19 | mask = np.square(xv) + np.square(yv) <= rad*rad 20 | M = mask.astype(float) 21 | return M 22 | 23 | def convert_size(size_bytes): 24 | if size_bytes == 0: 25 | return "0B" 26 | size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") 27 | i = int(math.floor(math.log(size_bytes, 1024))) 28 | power = math.pow(1024, i) 29 | size = round(size_bytes / power, 2) 30 | return "{} {}".format(size, size_name[i]) 31 | 32 | def calc_tiou(gt_traj, traj, rad): 33 | ns = gt_traj.shape[1] 34 | est_traj = np.zeros(gt_traj.shape) 35 | if traj.shape[0] == 4: 36 | for ni, ti in zip(range(ns), np.linspace(0,1,ns)): 37 | est_traj[:,ni] = traj[[1,0]]*(1-ti) + ti*traj[[3,2]] 38 | else: 39 | bline = (np.abs(traj[3]+traj[7]) > 1.0).astype(float) 40 | if bline: 41 | len1 = np.linalg.norm(traj[[5,1]]) 42 | len2 = np.linalg.norm(traj[[7,3]]) 43 | v1 = traj[[5,1]]/len1 44 | v2 = traj[[7,3]]/len2 45 | piece = (len1+len2)/(ns-1) 46 | for ni in range(ns): 47 | est_traj[:,ni] = traj[[4,0]] + np.min([piece*ni, len1])*v1 + np.max([0,piece*ni-len1])*v2 48 | else: 49 | for ni, ti in zip(range(ns), np.linspace(0,1,ns)): 50 | est_traj[:,ni] = traj[[4,0]] + ti*traj[[5,1]] + ti*ti*traj[[6,2]] 51 | 52 | est_traj2 = est_traj[:,-1::-1] 53 | 54 | ious = calciou(gt_traj, est_traj, rad) 55 | ious2 = calciou(gt_traj, est_traj2, rad) 56 | return np.max([np.mean(ious), np.mean(ious2)]) 57 | 58 | def calciou(p1, p2, rad): 59 | dists = np.sqrt( np.sum( np.square(p1 - p2),0) ) 60 | dists[dists > 2*rad] = 2*rad 61 | 62 | theta = 2*np.arccos( dists/ (2*rad) ) 63 | A = ((rad*rad)/2) * (theta - np.sin(theta)) 64 | I = 2*A 65 | U = 2* np.pi * rad*rad - I 66 | iou = I / U 67 | return iou 68 | 69 | def make_collage(Y1,Y2,tY1,tY2,Xim,reorder=True): 70 | th = 0.01 71 | gm = 0.6 72 | Y1[Y1 < 0] = 0 73 | Y1[Y1 > 1] = 1 74 | Xim[Xim < 0] = 0 75 | Xim[Xim > 1] = 1 76 | M = Y1[:,:,0] 77 | F = Y1[:,:,1:] 78 | F_GT = tY1[:,:,1:] 79 | M_GT = tY1[:,:,0] 80 | 81 | M3 = np.repeat(M_GT[:, :, np.newaxis], 3, axis=2) 82 | M_GT[M_GT < th/10] = 1 83 | F_GT[M3 < th/10] = 1 84 | 85 | dt = np.dstack([Xim[:,:,0]]*3) 86 | 87 | M3 = np.repeat(M[:, :, np.newaxis], 3, axis=2) 88 | M[M < th] = 1 89 | F[M3 < th] = 1 90 | 91 | im = Xim[:,:,1:4] 92 | bgr = Xim[:,:,4:] 93 | if reorder: 94 | im = im**gm 95 | bgr = bgr**gm 96 | # F = F[:,:,[2,1,0]] 97 | im = im[:,:,[2,1,0]] 98 | bgr = bgr[:,:,[2,1,0]] 99 | F = F**gm 100 | M = M**gm 101 | 102 | collage1 = np.vstack([np.hstack([np.dstack([M_GT]*3),F_GT]), np.hstack([np.dstack([M]*3),F])]) 103 | h1 = np.hstack([bgr, renderTraj(tY2,np.copy(im))]) 104 | h2 = np.hstack([ renderTraj(Y2,np.copy(1-dt)) ,renderTraj(Y2,np.copy(im))]) 105 | collage2 = np.vstack([h1, h2]) 106 | collage1 = (255*collage1).astype(np.uint8) 107 | collage2 = (255*collage2).astype(np.uint8) 108 | return collage1, collage2 109 | 110 | def make_collage_small(Y1,Y2,tY1,tY2,Xim): 111 | gm = 0.6 112 | Y1[Y1 < 0] = 0 113 | Y1[Y1 > 1] = 1 114 | Xim[Xim < 0] = 0 115 | Xim[Xim > 1] = 1 116 | M = Y1[:,:,0] 117 | F = Y1[:,:,1:] 118 | F_GT = tY1[:,:,1:] 119 | M_GT = tY1[:,:,0] 120 | dt = np.dstack([Xim[:,:,0]]*3) 121 | im = Xim[:,:,1:4]**gm 122 | bgr = Xim[:,:,4:]**gm 123 | if True: 124 | # F = F[:,:,[2,1,0]] 125 | im = im[:,:,[2,1,0]] 126 | bgr = bgr[:,:,[2,1,0]] 127 | 128 | M3 = np.repeat(M[:, :, np.newaxis], 3, axis=2) 129 | M[M < 0.01] = 1 130 | F[M3 < 0.01] = 1 131 | F = F**gm 132 | M = M**gm 133 | 134 | col_gt = np.vstack([np.dstack([M_GT]*3),F_GT]) 135 | col_est = np.vstack([np.dstack([M]*3),F]) 136 | 137 | col_inp = np.vstack([bgr, im]) 138 | 139 | col_traj = np.vstack([ renderTraj(Y2,np.copy(1-dt)) ,renderTraj(Y2,np.copy(im))]) 140 | 141 | 142 | col_gt = (255*col_gt).astype(np.uint8) 143 | col_est = (255*col_est).astype(np.uint8) 144 | col_inp = (255*col_inp).astype(np.uint8) 145 | col_traj = (255*col_traj).astype(np.uint8) 146 | 147 | imgt = renderTraj(tY2,np.copy(im)) 148 | imgt = (255*imgt).astype(np.uint8) 149 | im = (255*im).astype(np.uint8) 150 | return col_gt, col_est, col_inp, col_traj, imgt, im 151 | 152 | def extract_comp(DT, im): 153 | var_th = 0.00001 154 | input_shape = [256, 256] 155 | 156 | BIN = (1-DT) < 0.7 157 | comp = measure.label(BIN) 158 | regions = measure.regionprops(comp) 159 | 160 | if len(im.shape) > 2: 161 | X = np.zeros([len(regions),input_shape[0],input_shape[1],im.shape[2]]) 162 | else: 163 | X = np.zeros([len(regions),input_shape[0],input_shape[1]]) 164 | 165 | ki = 0 166 | for region in regions: 167 | y, x, y2, x2 = region.bbox 168 | height = y2 - y 169 | width = x2 - x 170 | area = width*height 171 | if area < 100: ## too small 172 | continue 173 | 174 | if len(im.shape) > 2: 175 | img = skimage.transform.resize(im[y:y2, x:x2, :], input_shape, order=3) 176 | else: 177 | img = skimage.transform.resize(im[y:y2, x:x2], input_shape, order=3) 178 | 179 | X[ki,:] = img 180 | ki += 1 181 | 182 | X[X < 0] = 0 183 | X[X > 1] = 1 184 | return X 185 | 186 | def renderTraj(pars, H): 187 | if len(pars.shape) > 1 and pars.shape[0] > 8: 188 | pars = pars/np.max(pars) 189 | rr,cc = np.nonzero(pars > 0.1) 190 | H[rr, cc, 0] = 0 191 | H[rr, cc, 1] = 0 192 | H[rr, cc, 2] = pars[rr,cc] 193 | return H 194 | 195 | if pars.shape[0] == 8: 196 | pars = np.reshape(np.array(pars), [2,4]) 197 | 198 | ns = -1 199 | if np.sum(np.abs(pars[:,2])) == 0: 200 | ns = 2 201 | else: 202 | ns = np.round(np.linalg.norm(np.abs(pars[:,1]))/5) 203 | ns = np.max([2, ns]).astype(int) 204 | 205 | rangeint = np.linspace(0,1,ns) 206 | for timeinst in range(rangeint.shape[0]-1): 207 | ti0 = rangeint[timeinst] 208 | ti1 = rangeint[timeinst+1] 209 | start = pars[:,0] + pars[:,1]*ti0 + pars[:,2]*(ti0*ti0) 210 | end = pars[:,0] + pars[:,1]*ti1 + pars[:,2]*(ti1*ti1) 211 | start = np.round(start).astype(np.int32) 212 | end = np.round(end).astype(np.int32) 213 | rr, cc, val = line_aa(start[0], start[1], end[0], end[1]) 214 | valid = np.logical_and(np.logical_and(rr < H.shape[0], cc < H.shape[1]), np.logical_and(rr > 0, cc > 0)) 215 | rr = rr[valid] 216 | cc = cc[valid] 217 | val = val[valid] 218 | if len(H.shape) > 2: 219 | # for ci in range(H.shape[2]): 220 | # H[rr, cc, ci] = val 221 | H[rr, cc, 0] = 0 222 | H[rr, cc, 1] = 0 223 | H[rr, cc, 2] = val 224 | else: 225 | H[rr, cc] = val 226 | 227 | if np.sum(np.abs(pars[:,3])) > 0: 228 | start = pars[:,0] + pars[:,1] + pars[:,2] + pars[:,3]*(ti0) 229 | end = pars[:,0] + pars[:,1] + pars[:,2] + pars[:,3]*(ti1) 230 | start = np.round(start).astype(np.int32) 231 | end = np.round(end).astype(np.int32) 232 | rr, cc, val = line_aa(start[0], start[1], end[0], end[1]) 233 | valid = np.logical_and(np.logical_and(rr < H.shape[0], cc < H.shape[1]), np.logical_and(rr > 0, cc > 0)) 234 | rr = rr[valid] 235 | cc = cc[valid] 236 | val = val[valid] 237 | if len(H.shape) > 2: 238 | 239 | H[rr, cc, 0] = 0 240 | H[rr, cc, 1] = 0 241 | H[rr, cc, 2] = val 242 | else: 243 | H[rr, cc] = val 244 | 245 | # pdb.set_trace() 246 | 247 | return H 248 | 249 | def get_comp_2step(DT, I, B, normalize=True): 250 | th1 = 0.85 251 | th2 = 0.9 252 | var_th = 0.00001 253 | input_shape = [256, 256] 254 | X = np.zeros([0,input_shape[0],input_shape[1],10]) 255 | bbox_saved = np.zeros([0,4]) 256 | 257 | BIN = DT >= th1 258 | comp = measure.label(BIN) 259 | ki = 0 260 | for region in measure.regionprops(comp): 261 | y, x, y2, x2 = region.bbox 262 | 263 | dtt = DT[y:y2, x:x2] 264 | xx,yy = np.unravel_index(dtt.argmax(), dtt.shape) 265 | xx2,yy2 = np.nonzero(dtt <= th2) 266 | dists = np.square(xx2-xx) + np.square(yy2-yy) 267 | minind = dists.argmin() 268 | rad = np.ceil(1*np.sqrt(np.square(xx2[minind]-xx) + np.square(yy2[minind]-yy))/((1-th2)/0.5)).astype(int) 269 | 270 | x -= rad 271 | y -= rad 272 | x2 += rad 273 | y2 += rad 274 | if x < 0: 275 | x = 0 276 | if y < 0: 277 | y = 0 278 | if x2 > I.shape[1]: 279 | x2 = I.shape[1] 280 | if y2 > I.shape[0]: 281 | y2 = I.shape[0] 282 | 283 | height = y2 - y 284 | width = x2 - x 285 | area = width*height 286 | 287 | if area < 100: ## too small 288 | continue 289 | 290 | X = np.concatenate((X,np.zeros((1,X.shape[1],X.shape[2],X.shape[3])))) 291 | bbox_saved = np.concatenate((bbox_saved,np.zeros((1,bbox_saved.shape[1])))) 292 | 293 | dt = skimage.transform.resize(DT[y:y2, x:x2], input_shape, order=1) 294 | img = skimage.transform.resize(I[y:y2, x:x2, :], input_shape, order=3) 295 | img_orig = np.copy(img) 296 | 297 | bgr = skimage.transform.resize(B[y:y2, x:x2, :], input_shape, order=3) 298 | 299 | if normalize: 300 | img = img - np.mean(img) 301 | if np.abs(np.var(img)) > var_th: 302 | img = img / np.sqrt(np.var(img)) 303 | bgr = bgr - np.mean(bgr) 304 | if np.abs(np.var(bgr)) > var_th: 305 | bgr = bgr / np.sqrt(np.var(bgr)) 306 | 307 | X[ki,:,:,0] = dt 308 | X[ki,:,:,1:4] = img 309 | X[ki,:,:,4:7] = bgr 310 | X[ki,:,:,7:] = img_orig 311 | bbox_saved[ki,:] = [y, x, y2, x2] 312 | ki = ki + 1 313 | 314 | tX = [X[:,:,:,:7],X[:,:,:,7:]] 315 | return tX, bbox_saved -------------------------------------------------------------------------------- /dataset/geopatterns/geopatterns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import base64 5 | import hashlib 6 | import math 7 | 8 | from colour import Color 9 | 10 | from .svg import SVG 11 | from .utils import promap 12 | 13 | 14 | class GeoPattern(object): 15 | def __init__(self, string, generator=None): 16 | self.hash = hashlib.sha1(string.encode('utf8')).hexdigest() 17 | self.svg = SVG() 18 | 19 | available_generators = [ 20 | 'bricks', 21 | 'hexagons', 22 | 'overlapping_circles', 23 | 'overlapping_rings', 24 | 'plaid', 25 | 'plus_signs', 26 | 'rings', 27 | 'sinewaves', 28 | 'squares', 29 | 'triangles', 30 | 'xes' 31 | ] 32 | if generator not in available_generators: 33 | raise ValueError('{} is not a valid generator. Valid choices are {}.'.format( 34 | generator, ', '.join(['"{}"'.format(g) for g in available_generators]) 35 | )) 36 | self.generate_background() 37 | getattr(self, 'geo_%s' % generator)() 38 | 39 | @property 40 | def svg_string(self): 41 | return self.svg.to_string() 42 | 43 | @property 44 | def base64_string(self): 45 | return base64.encodestring(self.svg.to_string().encode()).replace(b'\n', b'') 46 | 47 | def generate_background(self): 48 | hue_offset = promap(int(self.hash[14:][:3], 16), 0, 4095, 0, 359) 49 | sat_offset = int(self.hash[17:][:1], 16) 50 | base_color = Color(hsl=(0, .42, .41)) 51 | base_color.hue = base_color.hue - hue_offset 52 | 53 | if sat_offset % 2: 54 | base_color.saturation = base_color.saturation + sat_offset / 100 55 | else: 56 | base_color.saturation = base_color.saturation - sat_offset / 100 57 | 58 | rgb = base_color.rgb 59 | r = int(round(rgb[0] * 255)) 60 | g = int(round(rgb[1] * 255)) 61 | b = int(round(rgb[2] * 255)) 62 | return self.svg.rect(0, 0, '100%', '100%', **{ 63 | 'fill': 'rgb({}, {}, {})'.format(r, g, b) 64 | }) 65 | 66 | def geo_bricks(self): 67 | square_size = promap(int(self.hash[1:][:1], 16), 0, 15, 6, 60) 68 | brick_width = square_size * 2 69 | gap_size = square_size * 0.1 70 | 71 | self.svg.width = (brick_width + gap_size) * 6 72 | self.svg.height = (square_size + gap_size) * 6 73 | 74 | i = 0 75 | for y in range(6): 76 | for x in range(6): 77 | val = int(self.hash[i:][:1], 16) 78 | opacity = promap(val, 0, 15, 0.02, 0.2) 79 | fill = '#ddd' if val % 2 == 0 else '#222' 80 | 81 | dx = -square_size if y % 2 == 0 else 0 82 | 83 | self.svg.rect(x * (brick_width + gap_size) + dx, 84 | y * (square_size + gap_size), brick_width, square_size, **{ 85 | 'fill': fill, 86 | 'stroke': '#000000', 87 | 'style': { 88 | 'opacity': opacity 89 | } 90 | }) 91 | 92 | if x == 0: 93 | self.svg.rect(6 * (brick_width + gap_size) + dx, 94 | y * (square_size + gap_size), brick_width, square_size, **{ 95 | 'fill': fill, 96 | 'stroke': '#000000', 97 | 'style': { 98 | 'opacity': opacity 99 | } 100 | }) 101 | 102 | if x == 0 and y == 0: 103 | self.svg.rect(6 * (brick_width + gap_size) + dx, 104 | 6 * (square_size + gap_size), brick_width, square_size, **{ 105 | 'fill': fill, 106 | 'stroke': '#000000', 107 | 'style': { 108 | 'opacity': opacity 109 | } 110 | }) 111 | 112 | i += 1 113 | 114 | def geo_hexagons(self): 115 | scale = int(self.hash[1:][:1], 16) 116 | side_length = promap(scale, 0, 15, 5, 120) 117 | hex_height = side_length * math.sqrt(3) 118 | hex_width = side_length * 2 119 | hex = self.build_hexagon_shape(side_length) 120 | 121 | self.svg.width = (hex_width * 3) + (side_length * 3) 122 | self.svg.height = hex_height * 6 123 | 124 | i = 0 125 | for y in range(6): 126 | for x in range(6): 127 | val = int(self.hash[i:][:1], 16) 128 | dy = (y * hex_height) if x % 2 else (y * hex_height + hex_height / 2) 129 | opacity = promap(val, 0, 15, 0.02, 0.18) 130 | fill = '#ddd' if val % 2 == 0 else '#222' 131 | tmp_hex = str(hex) 132 | 133 | self.svg.polyline(hex, **{ 134 | 'opacity': opacity, 135 | 'fill': fill, 136 | 'stroke': '#000000', 137 | 'transform': 'translate({}, {})'.format( 138 | x * side_length * 1.5 - hex_width / 2, 139 | dy - hex_height / 2 140 | ) 141 | }) 142 | 143 | # Add an extra one at top-right, for tiling. 144 | if x == 0: 145 | self.svg.polyline(hex, **{ 146 | 'opacity': opacity, 147 | 'fill': fill, 148 | 'stroke': '#000000', 149 | 'transform': 'translate({}, {})'.format( 150 | 6 * side_length * 1.5 - hex_width / 2, 151 | dy - hex_height / 2 152 | ) 153 | }) 154 | 155 | # Add an extra row at the end that matches the first row, for tiling. 156 | if y == 0: 157 | dy = (6 * hex_height) if x % 2 == 0 else (6 * hex_height + hex_height / 2) 158 | self.svg.polyline(hex, **{ 159 | 'opacity': opacity, 160 | 'fill': fill, 161 | 'stroke': '#000000', 162 | 'transform': 'translate({}, {})'.format( 163 | x * side_length * 1.5 - hex_width / 2, 164 | dy - hex_height / 2 165 | ) 166 | }) 167 | 168 | # Add an extra one at bottom-right, for tiling. 169 | if x == 0 and y == 0: 170 | self.svg.polyline(hex, **{ 171 | 'opacity': opacity, 172 | 'fill': fill, 173 | 'stroke': '#000000', 174 | 'transform': 'translate({}, {})'.format( 175 | 6 * side_length * 1.5 - hex_width / 2, 176 | 5 * hex_height + hex_height / 2 177 | ) 178 | }) 179 | 180 | i += 1 181 | 182 | def geo_overlapping_circles(self): 183 | scale = int(self.hash[1:][:1], 16) 184 | diameter = promap(scale, 0, 15, 20, 200) 185 | radius = diameter / 2 186 | 187 | self.svg.width = radius * 6 188 | self.svg.height = radius * 6 189 | 190 | i = 0 191 | for y in range(6): 192 | for x in range(6): 193 | val = int(self.hash[i:][:1], 16) 194 | opacity = promap(val, 0, 15, 0.02, 0.1) 195 | fill = '#ddd' if val % 2 == 0 else '#222' 196 | 197 | self.svg.circle(x * radius, y * radius, radius, **{ 198 | 'fill': fill, 199 | 'style': { 200 | 'opacity': opacity 201 | } 202 | }) 203 | 204 | # Add an extra one at top-right, for tiling. 205 | if x == 0: 206 | self.svg.circle(6 * radius, y * radius, radius, **{ 207 | 'fill': fill, 208 | 'style': { 209 | 'opacity': opacity 210 | } 211 | }) 212 | 213 | # Add an extra row at the end that matches the first row, for tiling. 214 | if y == 0: 215 | self.svg.circle(x * radius, 6 * radius, radius, **{ 216 | 'fill': fill, 217 | 'style': { 218 | 'opacity': opacity 219 | } 220 | }) 221 | 222 | # Add an extra one at bottom-right, for tiling. 223 | if x == 0 and y == 0: 224 | self.svg.circle(6 * radius, 6 * radius, radius, **{ 225 | 'fill': fill, 226 | 'style': { 227 | 'opacity': opacity 228 | } 229 | }) 230 | 231 | i += 1 232 | 233 | def geo_overlapping_rings(self): 234 | scale = int(self.hash[1:][:1], 16) 235 | ring_size = promap(scale, 0, 15, 5, 80) 236 | stroke_width = ring_size / 4 237 | 238 | self.svg.width = ring_size * 6 239 | self.svg.height = ring_size * 6 240 | 241 | i = 0 242 | for y in range(6): 243 | for x in range(6): 244 | val = int(self.hash[i:][:1], 16) 245 | opacity = promap(val, 0, 15, 0.02, 0.16) 246 | 247 | self.svg.circle(x * ring_size, y * ring_size, ring_size, **{ 248 | 'fill': 'none', 249 | 'stroke': '#000', 250 | 'style': { 251 | 'opacity': opacity, 252 | 'stroke-width': '{}px'.format(stroke_width) 253 | } 254 | } 255 | ) 256 | 257 | # Add an extra one at top-right, for tiling. 258 | if x == 0: 259 | self.svg.circle(6 * ring_size, y * ring_size, ring_size, **{ 260 | 'fill': 'none', 261 | 'stroke': '#000', 262 | 'style': { 263 | 'opacity': opacity, 264 | 'stroke-width': '{}px'.format(stroke_width) 265 | } 266 | } 267 | ) 268 | 269 | # Add an extra row at the end that matches the first row, for tiling. 270 | if y == 0: 271 | self.svg.circle(x * ring_size, 6 * ring_size, ring_size, **{ 272 | 'fill': 'none', 273 | 'stroke': '#000', 274 | 'style': { 275 | 'opacity': opacity, 276 | 'stroke-width': '{}px'.format(stroke_width) 277 | } 278 | } 279 | ) 280 | 281 | # Add an extra one at bottom-right, for tiling. 282 | if x == 0 and y == 0: 283 | self.svg.circle(6 * ring_size, 6 * ring_size, ring_size, **{ 284 | 'fill': 'none', 285 | 'stroke': '#000', 286 | 'style': { 287 | 'opacity': opacity, 288 | 'stroke-width': '{}px'.format(stroke_width) 289 | } 290 | } 291 | ) 292 | 293 | i += 1 294 | 295 | def geo_plaid(self): 296 | height = 0 297 | width = 0 298 | 299 | # Horizontal stripes. 300 | i = 0 301 | for y in range(18): 302 | space = int(self.hash[i:][:1], 16) 303 | height += space + 5 304 | 305 | val = int(self.hash[i + 1:][:1], 16) 306 | opacity = promap(val, 0, 15, 0.02, 0.15) 307 | fill = '#ddd' if val % 2 == 0 else '#222' 308 | stripe_height = val + 5 309 | 310 | self.svg.rect(0, height, '100%', stripe_height, **{ 311 | 'opacity': opacity, 312 | 'fill': fill 313 | }) 314 | 315 | height += stripe_height 316 | i += 2 317 | 318 | # Vertical stripes. 319 | i = 0 320 | for x in range(18): 321 | space = int(self.hash[i:][:1], 16) 322 | width += space + 5 323 | 324 | val = int(self.hash[i + 1:][:1], 16) 325 | opacity = promap(val, 0, 15, 0.02, 0.15) 326 | fill = '#ddd' if val % 2 == 0 else '#222' 327 | stripe_width = val + 5 328 | 329 | self.svg.rect(width, 0, stripe_width, '100%', **{ 330 | 'opacity': opacity, 331 | 'fill': fill 332 | }) 333 | 334 | width += stripe_width 335 | i += 2 336 | 337 | self.svg.width = width 338 | self.svg.height = height 339 | 340 | def geo_plus_signs(self): 341 | square_size = promap(int(self.hash[0:][:1], 16), 0, 15, 10, 25) 342 | plus_size = square_size * 3 343 | plus_shape = self.build_plus_shape(square_size) 344 | 345 | self.svg.width = square_size * 12 346 | self.svg.height = square_size * 12 347 | 348 | i = 0 349 | for y in range(6): 350 | for x in range(6): 351 | val = int(self.hash[i:][:1], 16) 352 | opacity = promap(val, 0, 15, 0.02, 0.15) 353 | fill = '#ddd' if val % 2 == 0 else '#222' 354 | dx = 0 if y % 2 == 0 else 1 355 | 356 | self.svg.group(plus_shape, **{ 357 | 'fill': fill, 358 | 'transform': 'translate({}, {})'.format( 359 | (x * plus_size - x * square_size + dx * square_size - square_size), (y * plus_size - y * square_size - plus_size / 2), 360 | ), 361 | 'style': { 362 | 'opacity': opacity 363 | } 364 | }) 365 | 366 | # Add an extra column on the right for tiling. 367 | if x == 0: 368 | self.svg.group(plus_shape, **{ 369 | 'fill': fill, 370 | 'transform': 'translate({}, {})'.format( 371 | (4 * plus_size - x * square_size + dx * square_size - square_size), (y * plus_size - y * square_size - plus_size / 2), 372 | ), 373 | 'style': { 374 | 'opacity': opacity 375 | } 376 | }) 377 | 378 | # Add an extra row on the bottom that matches the first row, for tiling. 379 | if y == 0: 380 | self.svg.group(plus_shape, **{ 381 | 'fill': fill, 382 | 'transform': 'translate({}, {})'.format( 383 | (x * plus_size - x * square_size + dx * square_size - square_size), (4 * plus_size - y * square_size - plus_size / 2), 384 | ), 385 | 'style': { 386 | 'opacity': opacity 387 | } 388 | }) 389 | 390 | # Add an extra one at top-right and bottom-right, for tiling. 391 | if x == 0 and y == 0: 392 | self.svg.group(plus_shape, **{ 393 | 'fill': fill, 394 | 'transform': 'translate({}, {})'.format( 395 | (4 * plus_size - x * square_size + dx * square_size - square_size), (4 * plus_size - y * square_size - plus_size / 2), 396 | ), 397 | 'style': { 398 | 'opacity': opacity 399 | } 400 | }) 401 | 402 | i += 1 403 | 404 | def geo_rings(self): 405 | scale = int(self.hash[1:][:1], 16) 406 | ring_size = promap(scale, 0, 15, 5, 80) 407 | stroke_width = ring_size / 4 408 | 409 | self.svg.width = (ring_size + stroke_width) * 6 410 | self.svg.height = (ring_size + stroke_width) * 6 411 | 412 | i = 0 413 | for y in range(6): 414 | for x in range(6): 415 | val = int(self.hash[i:][:1], 16) 416 | opacity = promap(val, 0, 15, 0.02, 0.16) 417 | 418 | self.svg.circle( 419 | x * ring_size + x * stroke_width + (ring_size + stroke_width) / 2, 420 | y * ring_size + y * stroke_width + (ring_size + stroke_width) / 2, 421 | ring_size / 2, **{ 422 | 'fill': 'none', 423 | 'stroke': '#000', 424 | 'style': { 425 | 'opacity': opacity, 426 | 'stroke-width': '{}px'.format(stroke_width) 427 | } 428 | } 429 | ) 430 | 431 | i += 1 432 | 433 | def geo_sinewaves(self): 434 | period = math.floor(promap(int(self.hash[1:][:1], 16), 0, 15, 100, 400)) 435 | amplitude = math.floor(promap(int(self.hash[2:][:1], 16), 0, 15, 30, 100)) 436 | wave_width = math.floor(promap(int(self.hash[3:][:1], 16), 0, 15, 3, 30)) 437 | 438 | self.svg.width = period 439 | self.svg.height = wave_width * 36 440 | 441 | for i in range(36): 442 | val = int(self.hash[i:][1], 16) 443 | fill = '#ddd' if val % 2 == 0 else '#222' 444 | opacity = promap(val, 0, 15, 0.02, 0.15) 445 | x_offset = period / 4 * 0.7 446 | 447 | str = 'M0 {} C {} 0, {} 0, {} {} S {} {}, {} {} S {} 0, {}, {}'.format( 448 | amplitude, x_offset, (period / 2 - x_offset), (period / 2), 449 | amplitude, (period - x_offset), (amplitude * 2), period, 450 | amplitude, (period * 1.5 - x_offset), (period * 1.5), amplitude 451 | ) 452 | 453 | self.svg.path(str, **{ 454 | 'fill': 'none', 455 | 'stroke': fill, 456 | 'transform': 'translate(-{}, {})'.format( 457 | (period / 4), (wave_width * i - amplitude * 1.5) 458 | ), 459 | 'style': { 460 | 'opacity': opacity, 461 | 'stroke_width': '{}px'.format(wave_width) 462 | } 463 | }) 464 | 465 | self.svg.path(str, **{ 466 | 'fill': 'none', 467 | 'stroke': fill, 468 | 'transform': 'translate(-{}, {})'.format( 469 | (period / 4), (wave_width * i - amplitude * 1.5 + wave_width * 36) 470 | ), 471 | 'style': { 472 | 'opacity': opacity, 473 | 'stroke_width': '{}px'.format(wave_width) 474 | } 475 | }) 476 | 477 | def geo_squares(self): 478 | square_size = promap(int(self.hash[0:][:1], 16), 0, 15, 10, 70) 479 | 480 | self.svg.width = square_size * 6 481 | self.svg.height = square_size * 6 482 | 483 | i = 0 484 | for y in range(6): 485 | for x in range(6): 486 | val = int(self.hash[i:][:1], 16) 487 | opacity = promap(val, 0, 15, 0.02, 0.2) 488 | fill = '#ddd' if val % 2 == 0 else '#222' 489 | 490 | self.svg.rect(x * square_size, y * square_size, square_size, square_size, **{ 491 | 'fill': fill, 492 | 'style': { 493 | 'opacity': opacity 494 | } 495 | }) 496 | 497 | i += 1 498 | 499 | def geo_triangles(self): 500 | scale = int(self.hash[1:][:1], 16) 501 | side_length = promap(scale, 0, 15, 5, 120) 502 | triangle_height = side_length / 2 * math.sqrt(3) 503 | triangle = self.build_triangle_shape(side_length, triangle_height) 504 | 505 | self.svg.width = side_length * 3 506 | self.svg.height = side_length * 6 507 | 508 | i = 0 509 | for y in range(6): 510 | for x in range(6): 511 | val = int(self.hash[i:][:1], 16) 512 | opacity = promap(val, 0, 15, 0.02, 0.15) 513 | fill = '#ddd' if val % 2 == 0 else '#222' 514 | 515 | rotation = '' 516 | if y % 2 == 0: 517 | rotation = 180 if x % 2 == 0 else 0 518 | else: 519 | rotation = 180 if x % 2 != 0 else 0 520 | 521 | tmp_tri = str(triangle) 522 | self.svg.polyline(tmp_tri, **{ 523 | 'opacity': opacity, 524 | 'fill': fill, 525 | 'stroke': '#444', 526 | 'transform': 'translate({}, {}) rotate({}, {}, {})'.format( 527 | (x * side_length * 0.5 - side_length / 2), (triangle_height * y), 528 | rotation, (side_length / 2), (triangle_height / 2) 529 | ) 530 | }) 531 | 532 | # Add an extra one at top-right, for tiling. 533 | if x == 0: 534 | tmp_tri = str(triangle) 535 | self.svg.polyline(tmp_tri, **{ 536 | 'opacity': opacity, 537 | 'fill': fill, 538 | 'stroke': '#444', 539 | 'transform': 'translate({}, {}) rotate({}, {}, {})'.format( 540 | (6 * side_length * 0.5 - side_length / 2), (triangle_height * y), 541 | rotation, (side_length / 2), (triangle_height / 2) 542 | ) 543 | }) 544 | 545 | i += 1 546 | 547 | def geo_xes(self): 548 | square_size = promap(int(self.hash[0:][:1], 16), 0, 15, 10, 25) 549 | x_shape = self.build_plus_shape(square_size) 550 | x_size = square_size * 3 * 0.943 551 | 552 | self.svg.width = x_size * 3 553 | self.svg.height = x_size * 3 554 | 555 | i = 0 556 | for y in range(6): 557 | for x in range(6): 558 | val = int(self.hash[i:][:1], 16) 559 | opacity = promap(val, 0, 15, 0.02, 0.15) 560 | dy = (y * x_size - x_size * 0.5) if x % 2 == 0 else (y * x_size - x_size * 0.5 + x_size / 4) 561 | fill = '#ddd' if val % 2 == 0 else '#222' 562 | 563 | self.svg.group(x_shape, **{ 564 | 'fill': fill, 565 | 'transform': 'translate({}, {}) rotate(45, {}, {})'.format( 566 | (x * x_size / 2 - x_size / 2), (dy - y * x_size / 2), 567 | (x_size / 2), (x_size / 2) 568 | ), 569 | 'style': { 570 | 'opacity': opacity 571 | } 572 | }) 573 | 574 | # Add an extra column on the right for tiling. 575 | if x == 0: 576 | self.svg.group(x_shape, **{ 577 | 'fill': fill, 578 | 'transform': 'translate({}, {}) rotate(45, {}, {})'.format( 579 | (6 * x_size / 2 - x_size / 2), (dy - y * x_size / 2), 580 | (x_size / 2), (x_size / 2) 581 | ), 582 | 'style': { 583 | 'opacity': opacity 584 | } 585 | }) 586 | 587 | # Add an extra row on the bottom that matches the first row, for tiling. 588 | if y == 0: 589 | dy = (6 * x_size - x_size / 2) if x % 2 == 0 else (6 * x_size - x_size / 2 + x_size / 4) 590 | self.svg.group(x_shape, **{ 591 | 'fill': fill, 592 | 'transform': 'translate({}, {}) rotate(45, {}, {})'.format( 593 | (x * x_size / 2 - x_size / 2), (dy - 6 * x_size / 2), 594 | (x_size / 2), (x_size / 2) 595 | ), 596 | 'style': { 597 | 'opacity': opacity 598 | } 599 | }) 600 | 601 | # These can hang off the bottom, so put a row at the top for tiling. 602 | if y == 5: 603 | self.svg.group(x_shape, **{ 604 | 'fill': fill, 605 | 'transform': 'translate({}, {}) rotate(45, {}, {})'.format( 606 | (x * x_size / 2 - x_size / 2), (dy - 11 * x_size / 2), 607 | (x_size / 2), (x_size / 2) 608 | ), 609 | 'style': { 610 | 'opacity': opacity 611 | } 612 | }) 613 | 614 | # Add an extra one at top-right and bottom-right, for tiling. 615 | if x == 0 and y == 0: 616 | self.svg.group(x_shape, **{ 617 | 'fill': fill, 618 | 'transform': 'translate({}, {}) rotate(45, {}, {})'.format( 619 | (6 * x_size / 2 - x_size / 2), (dy - 6 * x_size / 2), 620 | (x_size / 2), (x_size / 2) 621 | ), 622 | 'style': { 623 | 'opacity': opacity 624 | } 625 | }) 626 | 627 | i += 1 628 | 629 | def build_hexagon_shape(self, side_length): 630 | c = side_length 631 | a = c / 2 632 | b = math.sin(60 * math.pi / 180) * c 633 | return '0, {}, {}, 0, {}, 0, {}, {}, {}, {}, {}, {}, 0, {}'.format( 634 | b, a, a + c, 2 * c, b, a + c, 2 * b, a, 2 * b, b 635 | ) 636 | 637 | def build_plus_shape(self, square_size): 638 | return [ 639 | 'self.rect({}, 0, {}, {})'.format(square_size, square_size, square_size * 3), 640 | 'self.rect(0, {}, {}, {})'.format(square_size, square_size * 3, square_size) 641 | ] 642 | 643 | def build_triangle_shape(self, side_length, height): 644 | half_width = side_length / 2 645 | return '{}, 0, {}, {}, 0, {}, {}, 0'.format( 646 | half_width, side_length, height, height, half_width 647 | ) 648 | --------------------------------------------------------------------------------