File size: 6,669 Bytes
ed66dc1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74728e5
ed66dc1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env bash
# Copy Idol's graphs, side files and manifest into the app's private files/models/, one file at a time
# (adb push -> run-as cp -> rm), and with FIXTURES=1 also the test fixtures (device/fx/<tag>/ -> files/fx/<tag>/) and
# the autorun plans (plans/*.json -> files/plans/).
#
# The file list, sizes and SHA-256 come from manifest.json in IDOL_DIR. Sources:
#   IDOL_DIR  manifest.json, every graph except dino_* and dis, the side and JSON files
#   DINO_DIR  dino_0..3.tflite (and dino_rope_cos/sin.f32 when they are there, else IDOL_DIR's)
#   DIS_DIR   dis.tflite (optional: only the "Background removal on the GPU" setting uses it)
# Every source is checked by size before any device command; after each copy the SHA-256 is checked on the phone
# (the manifest's value for a graph, the local file's for the rest). A file whose copy on the phone already has the
# expected SHA-256 is not copied again. MODELS=0 skips the models (fixtures and plans only, with FIXTURES=1).
# Install the debug APK first: run-as needs a debuggable package. ANDROID_SERIAL selects the device. Needs python3
# (reads the manifest).
set -euo pipefail

MODULE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

# Defaults for local testing: this worktree's export (models/v2 holds every graph except dis.tflite).
# For the published release point them at the downloaded repos (see README), e.g.
#   IDOL_DIR=~/Downloads/Idol DINO_DIR=~/Downloads/DINOv3-ViT-H16plus-Idol-LiteRT DIS_DIR=~/Downloads/DIS-ISNet-LiteRT
#   (IDOL_DIR=~/Downloads/Idol/262k for the first release's form: 262,144 Gaussians, same DINO_DIR)
IDOL_DIR="${IDOL_DIR:-$MODULE_DIR/models/v2}"
DINO_DIR="${DINO_DIR:-$IDOL_DIR}"
DIS_DIR="${DIS_DIR:-$HOME/Downloads/DIS-ISNet-LiteRT}"
MODELS="${MODELS:-1}"
FIXTURES="${FIXTURES:-0}"
FX_TAGS="${FX_TAGS:-armchair axolotl succulent black}"
PACKAGE=com.idol
TEMP_DIR=/data/local/tmp/idol_install

if [[ $# -gt 0 ]]; then
    printf 'Usage: [IDOL_DIR=...] [DINO_DIR=...] [DIS_DIR=...] [MODELS=0] [FIXTURES=1] %s\n' "$0" >&2
    exit 2
fi
MANIFEST="$IDOL_DIR/manifest.json"
if [[ ! -f "$MANIFEST" ]]; then
    printf 'Missing %s: set IDOL_DIR (and DINO_DIR, DIS_DIR) to the downloaded model folders (see README)\n' \
        "$MANIFEST" >&2
    exit 1
fi

sha_of() {
    if command -v sha256sum > /dev/null; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi
}

# "kind name file bytes sha" per line; bytes and sha are "-" when the manifest has none
ENTRIES="$(python3 -I - "$MANIFEST" <<'PY'
import json, sys
m = json.load(open(sys.argv[1]))
for name, g in m['graphs'].items():
    print('graph', name, g['file'], g['bytes'], g.get('sha256') or '-')
for f, s in m.get('side_files', {}).items():
    n = 4
    for d in s['shape']:
        n *= d
    print('side', f, f, n, '-')
for f in m.get('json_files', []):
    print('json', f, f, '-', '-')
PY
)"

# The source of each file.
source_of() {
    local kind="$1" name="$2" file="$3"
    case "$kind:$name" in
        graph:dino_*) printf '%s/%s' "$DINO_DIR" "$file" ;;
        graph:dis) printf '%s/%s' "$DIS_DIR" "$file" ;;
        side:dino_rope_*)
            if [[ -f "$DINO_DIR/$file" ]]; then printf '%s/%s' "$DINO_DIR" "$file"; else printf '%s/%s' "$IDOL_DIR" "$file"; fi
            ;;
        *) printf '%s/%s' "$IDOL_DIR" "$file" ;;
    esac
}

# Check every source before any device command to avoid a partial installation.
PLAN=()
if [[ "$MODELS" == 0 ]]; then ENTRIES=''; fi
while read -r kind name file bytes sha; do
    [[ -z "$kind" ]] && continue
    src="$(source_of "$kind" "$name" "$file")"
    if [[ ! -f "$src" ]]; then
        if [[ "$kind:$name" == graph:dis ]]; then
            printf 'Skipping dis.tflite (not in %s): the GPU background removal setting will not work\n' "$DIS_DIR"
            continue
        fi
        printf 'Missing source file: %s\n' "$src" >&2
        exit 1
    fi
    if [[ "$bytes" != - ]]; then
        actual="$(wc -c < "$src" | tr -d ' ')"
        if [[ "$actual" != "$bytes" ]]; then
            printf '%s is %s bytes, expected %s\n' "$src" "$actual" "$bytes" >&2
            exit 1
        fi
    fi
    # files without a manifest SHA-256 (side and JSON files) are checked against the local copy's
    if [[ "$sha" == - ]]; then sha="$(sha_of "$src")"; fi
    PLAN+=("$src|models/$file|$sha")
done <<< "$ENTRIES"
if [[ "$MODELS" != 0 ]]; then
    PLAN+=("$MANIFEST|models/manifest.json|$(sha_of "$MANIFEST")")
fi

if [[ "$FIXTURES" == 1 ]]; then
    for tag in $FX_TAGS; do
        dir="$MODULE_DIR/device/fx/$tag"
        if [[ ! -d "$dir" ]]; then
            printf 'Missing fixture folder: %s\n' "$dir" >&2
            exit 1
        fi
        for f in "$dir"/*; do
            [[ -f "$f" ]] || continue
            PLAN+=("$f|fx/$tag/$(basename "$f")|$(sha_of "$f")")
        done
    done
    for f in "$MODULE_DIR"/plans/*.json; do
        [[ -f "$f" ]] || continue
        PLAN+=("$f|plans/$(basename "$f")|$(sha_of "$f")")
    done
fi

pending=''
cleanup() {
    if [[ -n "$pending" ]]; then
        adb shell rm -f "$pending" < /dev/null
    fi
}
trap cleanup EXIT

# Copies $1 (a local file) to files/$2 of the app and checks the SHA-256 $3 on the phone ("-": no check).
install_file() {
    local src="$1" dst="$2" sha="$3"
    pending="$TEMP_DIR/$$-$(basename "$dst")"
    adb push "$src" "$pending" < /dev/null
    adb shell run-as "$PACKAGE" mkdir -p "files/$(dirname "$dst")" < /dev/null
    adb shell run-as "$PACKAGE" cp "$pending" "files/$dst" < /dev/null
    adb shell rm "$pending" < /dev/null
    pending=''
    if [[ "$sha" != - ]]; then
        local got
        got="$(adb shell run-as "$PACKAGE" sha256sum "files/$dst" < /dev/null | cut -d' ' -f1 | tr -d '\r')"
        if [[ "$got" != "$sha" ]]; then
            printf 'files/%s on the device has SHA-256 %s, expected %s\n' "$dst" "$got" "$sha" >&2
            exit 1
        fi
    fi
}

adb shell mkdir -p "$TEMP_DIR" < /dev/null
adb shell run-as "$PACKAGE" mkdir -p files/models < /dev/null
for entry in "${PLAN[@]}"; do
    IFS='|' read -r src dst sha <<< "$entry"
    if [[ "$sha" != - ]]; then
        have="$(adb shell run-as "$PACKAGE" sha256sum "files/$dst" < /dev/null 2> /dev/null | cut -d' ' -f1 | tr -d '\r' || true)"
        if [[ "$have" == "$sha" ]]; then
            printf 'Up to date: %s\n' "$dst"
            continue
        fi
    fi
    printf 'Installing %s (%s bytes)\n' "$dst" "$(wc -c < "$src" | tr -d ' ')"
    install_file "$src" "$dst" "$sha"
done
adb shell rmdir "$TEMP_DIR" < /dev/null 2> /dev/null || true
adb shell run-as "$PACKAGE" ls -la files/models < /dev/null
printf 'Files installed. Open Idol.\n'