init
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +3 -0
- .gitignore +23 -0
- app.py +154 -0
- app_wrapper.py +19 -0
- arguments/__init__.py +112 -0
- assets/example/davis-dog/00000.jpg +0 -0
- assets/example/davis-dog/00001.jpg +0 -0
- assets/example/davis-dog/00002.jpg +0 -0
- assets/example/davis-dog/00003.jpg +0 -0
- assets/example/davis-dog/00004.jpg +0 -0
- assets/example/davis-dog/00005.jpg +0 -0
- assets/example/davis-dog/00006.jpg +0 -0
- assets/example/davis-dog/00007.jpg +0 -0
- assets/example/davis-dog/00008.jpg +0 -0
- assets/example/davis-dog/00009.jpg +0 -0
- assets/example/davis-dog/00010.jpg +0 -0
- assets/example/davis-dog/00011.jpg +0 -0
- assets/example/davis-dog/00012.jpg +0 -0
- assets/example/davis-dog/00013.jpg +0 -0
- assets/example/davis-dog/00014.jpg +0 -0
- assets/example/davis-dog/00015.jpg +0 -0
- assets/example/davis-dog/00016.jpg +0 -0
- assets/example/davis-dog/00017.jpg +0 -0
- assets/example/davis-dog/00018.jpg +0 -0
- assets/example/davis-dog/00019.jpg +0 -0
- assets/example/davis-dog/00020.jpg +0 -0
- assets/example/davis-dog/00021.jpg +0 -0
- assets/example/davis-dog/00022.jpg +0 -0
- assets/example/davis-dog/00023.jpg +0 -0
- assets/example/davis-dog/00024.jpg +0 -0
- assets/example/davis-dog/00025.jpg +0 -0
- assets/example/davis-dog/00026.jpg +0 -0
- assets/example/davis-dog/00027.jpg +0 -0
- assets/example/davis-dog/00028.jpg +0 -0
- assets/example/davis-dog/00029.jpg +0 -0
- assets/example/davis-dog/00030.jpg +0 -0
- assets/example/davis-dog/00031.jpg +0 -0
- assets/example/davis-dog/00032.jpg +0 -0
- assets/example/davis-dog/00033.jpg +0 -0
- assets/example/davis-dog/00034.jpg +0 -0
- assets/example/davis-dog/00035.jpg +0 -0
- assets/example/davis-dog/00036.jpg +0 -0
- assets/example/davis-dog/00037.jpg +0 -0
- assets/example/davis-dog/00038.jpg +0 -0
- assets/example/davis-dog/00039.jpg +0 -0
- assets/example/davis-dog/00040.jpg +0 -0
- assets/example/davis-dog/00041.jpg +0 -0
- assets/example/davis-dog/00042.jpg +0 -0
- assets/example/davis-dog/00043.jpg +0 -0
- assets/example/davis-dog/00044.jpg +0 -0
.gitattributes
CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.whl filter=lfs diff=lfs merge=lfs -text
|
37 |
+
# submodules/dust3r/checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth filter=lfs diff=lfs merge=lfs -text
|
38 |
+
# wheel/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/.idea/
|
2 |
+
/work_dirs*
|
3 |
+
.vscode/
|
4 |
+
/tmp
|
5 |
+
/data
|
6 |
+
# /checkpoints
|
7 |
+
*.so
|
8 |
+
*.patch
|
9 |
+
__pycache__/
|
10 |
+
*.egg-info/
|
11 |
+
/viz*
|
12 |
+
/submit*
|
13 |
+
build/
|
14 |
+
*.pyd
|
15 |
+
/cache*
|
16 |
+
*.stl
|
17 |
+
# *.pth
|
18 |
+
/venv/
|
19 |
+
.nk8s
|
20 |
+
*.mp4
|
21 |
+
.vs
|
22 |
+
/exp/
|
23 |
+
/dev/
|
app.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# build upon InstantSplat https://huggingface.co/spaces/kairunwen/InstantSplat/blob/main/app.py
|
2 |
+
import os, subprocess, shlex, sys, gc
|
3 |
+
import numpy as np
|
4 |
+
import shutil
|
5 |
+
import argparse
|
6 |
+
import gradio as gr
|
7 |
+
import uuid
|
8 |
+
import glob
|
9 |
+
import re
|
10 |
+
|
11 |
+
import spaces
|
12 |
+
|
13 |
+
subprocess.run(shlex.split("pip install wheel/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl"))
|
14 |
+
subprocess.run(shlex.split("pip install wheel/simple_knn-0.0.0-cp310-cp310-linux_x86_64.whl"))
|
15 |
+
# subprocess.run(shlex.split("pip install wheel/curope-0.0.0-cp310-cp310-linux_x86_64.whl"))
|
16 |
+
|
17 |
+
GRADIO_CACHE_FOLDER = './gradio_cache_folder'
|
18 |
+
|
19 |
+
|
20 |
+
def get_dust3r_args_parser():
|
21 |
+
parser = argparse.ArgumentParser()
|
22 |
+
parser.add_argument("--image_size", type=int, default=512, choices=[512, 224], help="image size")
|
23 |
+
parser.add_argument("--model_path", type=str, default="submodules/dust3r/checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth", help="path to the model weights")
|
24 |
+
parser.add_argument("--device", type=str, default='cuda', help="pytorch device")
|
25 |
+
parser.add_argument("--batch_size", type=int, default=1)
|
26 |
+
parser.add_argument("--schedule", type=str, default='linear')
|
27 |
+
parser.add_argument("--lr", type=float, default=0.01)
|
28 |
+
parser.add_argument("--niter", type=int, default=300)
|
29 |
+
parser.add_argument("--focal_avg", type=bool, default=True)
|
30 |
+
parser.add_argument("--n_views", type=int, default=3)
|
31 |
+
parser.add_argument("--base_path", type=str, default=GRADIO_CACHE_FOLDER)
|
32 |
+
return parser
|
33 |
+
|
34 |
+
|
35 |
+
def natural_sort(l):
|
36 |
+
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
37 |
+
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key.split('/')[-1])]
|
38 |
+
return sorted(l, key=alphanum_key)
|
39 |
+
|
40 |
+
def cmd(command):
|
41 |
+
print(command)
|
42 |
+
os.system(command)
|
43 |
+
|
44 |
+
@spaces.GPU(duration=150)
|
45 |
+
def process(inputfiles, input_path='demo'):
|
46 |
+
if inputfiles:
|
47 |
+
frames = natural_sort(inputfiles)
|
48 |
+
else:
|
49 |
+
frames = natural_sort(glob.glob('./assets/example/' + input_path + '/*'))
|
50 |
+
if len(frames) > 20:
|
51 |
+
stride = int(np.ceil(len(frames) / 20))
|
52 |
+
frames = frames[::stride]
|
53 |
+
|
54 |
+
# Create a temporary directory to store the selected frames
|
55 |
+
temp_dir = os.path.join(GRADIO_CACHE_FOLDER, str(uuid.uuid4()))
|
56 |
+
os.makedirs(temp_dir, exist_ok=True)
|
57 |
+
|
58 |
+
# Copy the selected frames to the temporary directory
|
59 |
+
for i, frame in enumerate(frames):
|
60 |
+
shutil.copy(frame, f"{temp_dir}/{i:04d}.{frame.split('.')[-1]}")
|
61 |
+
|
62 |
+
imgs_path = temp_dir
|
63 |
+
output_path = f'./results/{input_path}/output'
|
64 |
+
cmd(f"python dynamic_predictor/launch.py --mode=eval_pose_custom \
|
65 |
+
--pretrained=Kai422kx/das3r \
|
66 |
+
--dir_path={imgs_path} \
|
67 |
+
--output_dir={output_path} \
|
68 |
+
--use_pred_mask ")
|
69 |
+
|
70 |
+
cmd(f"python utils/rearrange.py --output_dir={output_path}")
|
71 |
+
output_path = f'{output_path}_rearranged'
|
72 |
+
|
73 |
+
print(output_path)
|
74 |
+
cmd(f"python train_gui.py -s {output_path} -m {output_path} --iter 2000")
|
75 |
+
cmd(f"python render.py -s {output_path} -m {output_path} --iter 2000 --get_video")
|
76 |
+
|
77 |
+
output_video_path = f"{output_path}/rendered.mp4"
|
78 |
+
output_ply_path = f"{output_path}/point_cloud/iteration_2000/point_cloud.ply"
|
79 |
+
return output_video_path, output_ply_path, output_ply_path
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
_TITLE = '''DAS3R'''
|
84 |
+
_DESCRIPTION = '''
|
85 |
+
<div style="display: flex; justify-content: center; align-items: center;">
|
86 |
+
<div style="width: 100%; text-align: center; font-size: 30px;">
|
87 |
+
<strong>DAS3R: Dynamics-Aware Gaussian Splatting for Static Scene Reconstruction</strong>
|
88 |
+
</div>
|
89 |
+
</div>
|
90 |
+
<p></p>
|
91 |
+
|
92 |
+
|
93 |
+
<div align="center">
|
94 |
+
<a style="display:inline-block" href="https://arxiv.org/abs/2412.19584"><img src="https://img.shields.io/badge/ArXiv-2412.19584-b31b1b.svg?logo=arXiv" alt='arxiv'></a>
|
95 |
+
<a style="display:inline-block" href="https://kai422.github.io/DAS3R/"><img src='https://img.shields.io/badge/Project-Website-blue.svg'></a>
|
96 |
+
<a style="display:inline-block" href="https://github.com/kai422/DAS3R"><img src='https://img.shields.io/badge/GitHub-%23121011.svg?logo=github&logoColor=white'></a>
|
97 |
+
</div>
|
98 |
+
<p></p>
|
99 |
+
|
100 |
+
|
101 |
+
* Official demo of [DAS3R: Dynamics-Aware Gaussian Splatting for Static Scene Reconstruction](https://kai422.github.io/DAS3R/).
|
102 |
+
* You can explore the sample results by clicking the sequence names at the bottom of the page.
|
103 |
+
* Due to GPU memory and time constraints, the total processing frame number is constrained at 20 and the iterations for GS training is constrained at 2000. We apply uniform sampling when the total number of input frames exceeds 20.
|
104 |
+
* This Gradio demo is built upon InstantSplat, which can be found at [https://huggingface.co/spaces/kairunwen/InstantSplat](https://huggingface.co/spaces/kairunwen/InstantSplat).
|
105 |
+
|
106 |
+
'''
|
107 |
+
|
108 |
+
block = gr.Blocks().queue()
|
109 |
+
with block:
|
110 |
+
with gr.Row():
|
111 |
+
with gr.Column(scale=1):
|
112 |
+
# gr.Markdown('# ' + _TITLE)
|
113 |
+
gr.Markdown(_DESCRIPTION)
|
114 |
+
|
115 |
+
with gr.Row(variant='panel'):
|
116 |
+
with gr.Tab("Input"):
|
117 |
+
inputfiles = gr.File(file_count="multiple", label="images")
|
118 |
+
input_path = gr.Textbox(visible=False, label="example_path")
|
119 |
+
button_gen = gr.Button("RUN")
|
120 |
+
|
121 |
+
with gr.Row(variant='panel'):
|
122 |
+
with gr.Tab("Output"):
|
123 |
+
with gr.Column(scale=2):
|
124 |
+
with gr.Group():
|
125 |
+
output_model = gr.Model3D(
|
126 |
+
label="3D Dense Model under Gaussian Splats Formats, need more time to visualize",
|
127 |
+
interactive=False,
|
128 |
+
camera_position=[0.5, 0.5, 1], # 稍微偏移一点,以便更好地查看模型
|
129 |
+
)
|
130 |
+
gr.Markdown(
|
131 |
+
"""
|
132 |
+
<div class="model-description">
|
133 |
+
Use the left mouse button to rotate, the scroll wheel to zoom, and the right mouse button to move.
|
134 |
+
</div>
|
135 |
+
"""
|
136 |
+
)
|
137 |
+
output_file = gr.File(label="ply")
|
138 |
+
with gr.Column(scale=1):
|
139 |
+
output_video = gr.Video(label="video")
|
140 |
+
|
141 |
+
button_gen.click(process, inputs=[inputfiles], outputs=[output_video, output_file, output_model])
|
142 |
+
|
143 |
+
gr.Examples(
|
144 |
+
examples=[
|
145 |
+
"davis-dog",
|
146 |
+
# "sintel-market_2",
|
147 |
+
],
|
148 |
+
inputs=[input_path],
|
149 |
+
outputs=[output_video, output_file, output_model],
|
150 |
+
fn=lambda x: process(inputfiles=None, input_path=x),
|
151 |
+
cache_examples=True,
|
152 |
+
label='Sparse-view Examples'
|
153 |
+
)
|
154 |
+
block.launch(server_name="0.0.0.0", share=False)
|
app_wrapper.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import os
|
3 |
+
# import spaces
|
4 |
+
|
5 |
+
hf_token = os.getenv("instantsplat_token")
|
6 |
+
|
7 |
+
# gr.load("kairunwen/tmp", hf_token=token, src="spaces").launch()
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
import shlex
|
12 |
+
import subprocess
|
13 |
+
|
14 |
+
from huggingface_hub import HfApi
|
15 |
+
|
16 |
+
api = HfApi()
|
17 |
+
api.snapshot_download(repo_id="kairunwen/tmp", repo_type="space", local_dir=".", token=hf_token)
|
18 |
+
subprocess.run(shlex.split("pip install -r requirements.txt"))
|
19 |
+
subprocess.run(shlex.split("python app.py"))
|
arguments/__init__.py
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#
|
2 |
+
# Copyright (C) 2023, Inria
|
3 |
+
# GRAPHDECO research group, https://team.inria.fr/graphdeco
|
4 |
+
# All rights reserved.
|
5 |
+
#
|
6 |
+
# This software is free for non-commercial, research and evaluation use
|
7 |
+
# under the terms of the LICENSE.md file.
|
8 |
+
#
|
9 |
+
# For inquiries contact [email protected]
|
10 |
+
#
|
11 |
+
|
12 |
+
from argparse import ArgumentParser, Namespace
|
13 |
+
import sys
|
14 |
+
import os
|
15 |
+
|
16 |
+
class GroupParams:
|
17 |
+
pass
|
18 |
+
|
19 |
+
class ParamGroup:
|
20 |
+
def __init__(self, parser: ArgumentParser, name : str, fill_none = False):
|
21 |
+
group = parser.add_argument_group(name)
|
22 |
+
for key, value in vars(self).items():
|
23 |
+
shorthand = False
|
24 |
+
if key.startswith("_"):
|
25 |
+
shorthand = True
|
26 |
+
key = key[1:]
|
27 |
+
t = type(value)
|
28 |
+
value = value if not fill_none else None
|
29 |
+
if shorthand:
|
30 |
+
if t == bool:
|
31 |
+
group.add_argument("--" + key, ("-" + key[0:1]), default=value, action="store_true")
|
32 |
+
else:
|
33 |
+
group.add_argument("--" + key, ("-" + key[0:1]), default=value, type=t)
|
34 |
+
else:
|
35 |
+
if t == bool:
|
36 |
+
group.add_argument("--" + key, default=value, action="store_true")
|
37 |
+
else:
|
38 |
+
group.add_argument("--" + key, default=value, type=t)
|
39 |
+
|
40 |
+
def extract(self, args):
|
41 |
+
group = GroupParams()
|
42 |
+
for arg in vars(args).items():
|
43 |
+
if arg[0] in vars(self) or ("_" + arg[0]) in vars(self):
|
44 |
+
setattr(group, arg[0], arg[1])
|
45 |
+
return group
|
46 |
+
|
47 |
+
class ModelParams(ParamGroup):
|
48 |
+
def __init__(self, parser, sentinel=False):
|
49 |
+
self.sh_degree = 3
|
50 |
+
self._source_path = ""
|
51 |
+
self._model_path = ""
|
52 |
+
self._images = "images"
|
53 |
+
self._resolution = -1
|
54 |
+
self._white_background = False
|
55 |
+
self.data_device = "cuda"
|
56 |
+
self.eval = False
|
57 |
+
super().__init__(parser, "Loading Parameters", sentinel)
|
58 |
+
|
59 |
+
def extract(self, args):
|
60 |
+
g = super().extract(args)
|
61 |
+
g.source_path = os.path.abspath(g.source_path)
|
62 |
+
return g
|
63 |
+
|
64 |
+
class PipelineParams(ParamGroup):
|
65 |
+
def __init__(self, parser):
|
66 |
+
self.convert_SHs_python = False
|
67 |
+
self.compute_cov3D_python = False
|
68 |
+
self.debug = False
|
69 |
+
super().__init__(parser, "Pipeline Parameters")
|
70 |
+
|
71 |
+
class OptimizationParams(ParamGroup):
|
72 |
+
def __init__(self, parser):
|
73 |
+
self.iterations = 30_000
|
74 |
+
self.position_lr_init = 0.00016
|
75 |
+
self.position_lr_final = 0.0000016
|
76 |
+
self.position_lr_delay_mult = 0.01
|
77 |
+
self.position_lr_max_steps = 30_000
|
78 |
+
self.feature_lr = 0.0025
|
79 |
+
self.opacity_lr = 0.05
|
80 |
+
self.scaling_lr = 0.005
|
81 |
+
self.rotation_lr = 0.001
|
82 |
+
self.percent_dense = 0.01
|
83 |
+
self.lambda_dssim = 0.2
|
84 |
+
self.densification_interval = 100
|
85 |
+
self.opacity_reset_interval = 3000
|
86 |
+
self.densify_from_iter = 500
|
87 |
+
self.densify_until_iter = 15_000
|
88 |
+
self.densify_grad_threshold = 0.0002
|
89 |
+
self.random_background = False
|
90 |
+
super().__init__(parser, "Optimization Parameters")
|
91 |
+
|
92 |
+
def get_combined_args(parser : ArgumentParser):
|
93 |
+
cmdlne_string = sys.argv[1:]
|
94 |
+
cfgfile_string = "Namespace()"
|
95 |
+
args_cmdline = parser.parse_args(cmdlne_string)
|
96 |
+
|
97 |
+
try:
|
98 |
+
cfgfilepath = os.path.join(args_cmdline.model_path, "cfg_args")
|
99 |
+
print("Looking for config file in", cfgfilepath)
|
100 |
+
with open(cfgfilepath) as cfg_file:
|
101 |
+
print("Config file found: {}".format(cfgfilepath))
|
102 |
+
cfgfile_string = cfg_file.read()
|
103 |
+
except TypeError:
|
104 |
+
print("Config file not found at")
|
105 |
+
pass
|
106 |
+
args_cfgfile = eval(cfgfile_string)
|
107 |
+
|
108 |
+
merged_dict = vars(args_cfgfile).copy()
|
109 |
+
for k,v in vars(args_cmdline).items():
|
110 |
+
if v != None:
|
111 |
+
merged_dict[k] = v
|
112 |
+
return Namespace(**merged_dict)
|
assets/example/davis-dog/00000.jpg
ADDED
assets/example/davis-dog/00001.jpg
ADDED
assets/example/davis-dog/00002.jpg
ADDED
assets/example/davis-dog/00003.jpg
ADDED
assets/example/davis-dog/00004.jpg
ADDED
assets/example/davis-dog/00005.jpg
ADDED
assets/example/davis-dog/00006.jpg
ADDED
assets/example/davis-dog/00007.jpg
ADDED
assets/example/davis-dog/00008.jpg
ADDED
assets/example/davis-dog/00009.jpg
ADDED
assets/example/davis-dog/00010.jpg
ADDED
assets/example/davis-dog/00011.jpg
ADDED
assets/example/davis-dog/00012.jpg
ADDED
assets/example/davis-dog/00013.jpg
ADDED
assets/example/davis-dog/00014.jpg
ADDED
assets/example/davis-dog/00015.jpg
ADDED
assets/example/davis-dog/00016.jpg
ADDED
assets/example/davis-dog/00017.jpg
ADDED
assets/example/davis-dog/00018.jpg
ADDED
assets/example/davis-dog/00019.jpg
ADDED
assets/example/davis-dog/00020.jpg
ADDED
assets/example/davis-dog/00021.jpg
ADDED
assets/example/davis-dog/00022.jpg
ADDED
assets/example/davis-dog/00023.jpg
ADDED
assets/example/davis-dog/00024.jpg
ADDED
assets/example/davis-dog/00025.jpg
ADDED
assets/example/davis-dog/00026.jpg
ADDED
assets/example/davis-dog/00027.jpg
ADDED
assets/example/davis-dog/00028.jpg
ADDED
assets/example/davis-dog/00029.jpg
ADDED
assets/example/davis-dog/00030.jpg
ADDED
assets/example/davis-dog/00031.jpg
ADDED
assets/example/davis-dog/00032.jpg
ADDED
assets/example/davis-dog/00033.jpg
ADDED
assets/example/davis-dog/00034.jpg
ADDED
assets/example/davis-dog/00035.jpg
ADDED
assets/example/davis-dog/00036.jpg
ADDED
assets/example/davis-dog/00037.jpg
ADDED
assets/example/davis-dog/00038.jpg
ADDED
assets/example/davis-dog/00039.jpg
ADDED
assets/example/davis-dog/00040.jpg
ADDED
assets/example/davis-dog/00041.jpg
ADDED
assets/example/davis-dog/00042.jpg
ADDED
assets/example/davis-dog/00043.jpg
ADDED
assets/example/davis-dog/00044.jpg
ADDED