├── 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 ''
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 |
--------------------------------------------------------------------------------