Skip to content

Commit 9838123

Browse files
committed
I guess I forgot to commit the entire time
1 parent aaf4918 commit 9838123

File tree

2 files changed

+330
-18
lines changed

2 files changed

+330
-18
lines changed

bosslet/__init__.py

Lines changed: 209 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,249 @@
44
import json
55
import os
66

7-
from flask import Flask, request, Response
7+
from utils import block_compute
8+
9+
from flask import Flask, request, Response, send_file, jsonify
810
import numpy as np
911
# from intern.remote.boss import BossRemote
1012
# from intern.resource.boss.resource import ChannelResource
1113
# from intern.utils.parallel import block_compute
1214
# from requests import codes, post
1315

1416
APP = Flask(__name__)
17+
1518
UPLOADS_PATH = "./uploads"
19+
BLOCK_SIZE = (256, 256, 256)
1620

1721

1822
"""
1923
https://api.theboss.io/v1/cutout/:collection/:experiment/:channel/:resolution/:x_range/:y_range/:z_range/:time_range/?iso=:iso
2024
"""
2125

2226

23-
@APP.route("/v1/cutout/<collection>/<experiment>/<channel>/<resolution>/<x_range>/<y_range>/<z_range>/", methods=["POST"])
24-
def upload_cutout_xyz(collection, experiment, channel, resolution, x_range, y_range, z_range):
27+
def file_compute(x_start, x_stop,
28+
y_start, y_stop,
29+
z_start, z_stop,
30+
origin=(0, 0, 0),
31+
block_size=(256, 256, 256)):
2532
"""
26-
Upload a volume
33+
Which files do we need to pull for this box?
34+
"""
35+
# x
36+
37+
x_block_origins = [
38+
b
39+
for b in range(0, x_stop + block_size[0], block_size[0])
40+
if b > (x_start - block_size[0]) and b < x_stop
41+
]
42+
y_block_origins = [
43+
b
44+
for b in range(0, y_stop + block_size[1], block_size[1])
45+
if b > (y_start - block_size[1]) and b < y_stop
46+
]
47+
z_block_origins = [
48+
b
49+
for b in range(0, z_stop + block_size[2], block_size[2])
50+
if b > (z_start - block_size[2]) and b < z_stop
51+
]
52+
53+
files = []
54+
for x in x_block_origins:
55+
for y in y_block_origins:
56+
for z in z_block_origins:
57+
files.append((x, y, z))
58+
return files
59+
60+
61+
def blockfile_indices(xs, ys, zs, origin=(0, 0, 0), block_size=(256, 256, 256)):
62+
blocks = block_compute(
63+
xs[0], xs[1], ys[0], ys[1], zs[0], zs[1],
64+
origin, block_size
65+
)
66+
files = file_compute(
67+
xs[0], xs[1], ys[0], ys[1], zs[0], zs[1],
68+
origin, block_size
69+
)
70+
71+
inds = []
72+
for b, f in zip(blocks, files):
73+
inds.append([
74+
(b[0][0] - f[0], b[0][1] - f[0]),
75+
(b[1][0] - f[1], b[1][1] - f[1]),
76+
(b[2][0] - f[2], b[2][1] - f[2])
77+
])
2778

79+
return inds
80+
81+
82+
83+
class StorageManager:
2884
"""
29-
for _, f in request.files.items():
30-
memfile = io.BytesIO()
31-
f.save(memfile)
32-
data = np.fromstring(memfile.getvalue(), dtype=np.uint8)
85+
Abstract class.
86+
"""
87+
pass
88+
89+
90+
class FilesystemStorageManager(StorageManager):
91+
92+
def __init__(self, upload_path=UPLOADS_PATH, block_size=BLOCK_SIZE):
93+
self.upload_path = upload_path
94+
self.block_size = block_size
95+
96+
def setdata(self, data, col, exp, chan, res, xs, ys, zs):
97+
"""
98+
Uploads the file.
99+
"""
100+
# Chunk the file into its parts
101+
blocks = block_compute(
102+
xs[0], xs[1], ys[0], ys[1], zs[0], zs[1],
103+
origin=(0, 0, 0),
104+
block_size=self.block_size
105+
)
106+
107+
for b in blocks:
108+
# Check to see if the file already exists
109+
110+
# TODO: All of this assumes 0-padded block alignment
111+
self.store(
112+
data[b[0][0] - xs[0]: b[0][1] - xs[0],
113+
b[1][0] - ys[0]: b[1][1] - ys[0],
114+
b[2][0] - zs[0]: b[2][1] - zs[0]],
115+
col, exp, chan, res, b
116+
)
117+
118+
# if self._exists(col, exp, chan, res, b):
119+
# # Open file, add new data, write file
120+
# # data = self.read(col, exp, chan, res, b)
121+
# pass
122+
# else:
123+
# # Create the file with the appropriate contents
124+
# # and 0pad if necessary.
125+
# # pad0_data = np.zeros((self.block_size))
126+
# pass
127+
128+
def getdata(self, col, exp, chan, res, xs, ys, zs):
129+
"""
130+
Gets the data from disk.
131+
"""
132+
blocks = block_compute(
133+
xs[0], xs[1], ys[0], ys[1], zs[0], zs[1],
134+
origin=(0, 0, 0),
135+
block_size=self.block_size
136+
)
137+
files = file_compute(
138+
xs[0], xs[1], ys[0], ys[1], zs[0], zs[1],
139+
origin=(0, 0, 0),
140+
block_size=self.block_size,
141+
)
142+
indices = blockfile_indices(
143+
xs, ys, zs,
144+
origin=(0, 0, 0),
145+
block_size=self.block_size
146+
)
147+
148+
payload = np.zeros((
149+
(xs[1] - xs[0]),
150+
(ys[1] - ys[0]),
151+
(zs[1] - zs[0])
152+
), dtype="uint8")
153+
for b, f, i in zip(blocks, files, indices):
154+
try:
155+
data_partial = self.retrieve(col, exp, chan, res, f)[
156+
i[0][0]:i[0][1],
157+
i[1][0]:i[1][1],
158+
i[2][0]:i[2][1],
159+
]
160+
payload[
161+
f[0] + i[0][0]: f[0] + i[0][1],
162+
f[1] + i[1][0]: f[1] + i[1][1],
163+
f[2] + i[2][0]: f[2] + i[2][1],
164+
] = data_partial
165+
166+
except:
167+
payload[
168+
f[0] + i[0][0]: f[0] + i[0][1],
169+
f[1] + i[1][0]: f[1] + i[1][1],
170+
f[2] + i[2][0]: f[2] + i[2][1],
171+
] = np.zeros(self.block_size, dtype="uint8")[
172+
i[0][0]:i[0][1],
173+
i[1][0]:i[1][1],
174+
i[2][0]:i[2][1],
175+
]
176+
return payload
177+
178+
def store(self, data, col, exp, chan, res, b):
33179
os.makedirs("{}/{}/{}/{}/".format(
34180
UPLOADS_PATH,
35-
collection, experiment, channel
181+
col, exp, chan
36182
), exist_ok=True)
37-
np.save("{}/{}/{}/{}/{}-{}-{}-{}.npy".format(
183+
fname = "{}/{}/{}/{}/{}-{}-{}-{}.npy".format(
38184
UPLOADS_PATH,
39-
collection, experiment, channel,
40-
resolution, x_range, y_range, z_range,
41-
))
42-
return ""
185+
col, exp, chan,
186+
res, b[0], b[1], b[2],
187+
)
188+
# print(fname)
189+
return np.save(fname, data)
43190

191+
def retrieve(self, col, exp, chan, res, b):
192+
fname = "{}/{}/{}/{}/{}-{}-{}-{}.npy".format(
193+
UPLOADS_PATH,
194+
col, exp, chan,
195+
res,
196+
(b[0], b[0] + self.block_size[0]),
197+
(b[1], b[1] + self.block_size[1]),
198+
(b[2], b[2] + self.block_size[2]),
199+
)
200+
return np.load(fname)
44201

45-
@APP.route("/v1/cutout/<collection>/<experiment>/<channel>/<resolution>/<x_range>/<y_range>/<z_range>/", methods=["GET"])
46-
def get_cutout_xyz(collection, experiment, channel, resolution, x_range, y_range, z_range):
202+
203+
204+
MANAGER = FilesystemStorageManager()
205+
206+
@APP.route("/v1/cutout/<collection>/<experiment>/<channel>/<resolution>/<x_range>/<y_range>/<z_range>/", methods=["POST"])
207+
def upload_cutout_xyz(collection, experiment, channel, resolution, x_range, y_range, z_range):
47208
"""
48209
Upload a volume
49210
50211
"""
51212
for _, f in request.files.items():
52213
memfile = io.BytesIO()
53214
f.save(memfile)
54-
data = np.fromstring(memfile.getvalue(), dtype=np.uint8)
55-
break
215+
data = np.fromstring(memfile.getvalue(), dtype="uint8")
216+
xs = [int(i) for i in x_range.split(":")]
217+
ys = [int(i) for i in y_range.split(":")]
218+
zs = [int(i) for i in z_range.split(":")]
219+
data = data.reshape(xs[1] - xs[0], ys[1] - ys[0], zs[1] - zs[0])
220+
MANAGER.setdata(data, collection, experiment, channel, resolution, xs, ys, zs)
56221
return ""
57222

58223

224+
@APP.route("/v1/cutout/<collection>/<experiment>/<channel>/<resolution>/<x_range>/<y_range>/<z_range>/", methods=["GET"])
225+
def get_cutout_xyz(collection, experiment, channel, resolution, x_range, y_range, z_range):
226+
"""
227+
Download a volume
228+
"""
229+
xs = [int(i) for i in x_range.split(":")]
230+
ys = [int(i) for i in y_range.split(":")]
231+
zs = [int(i) for i in z_range.split(":")]
232+
if (
233+
os.path.isdir("{}/{}".format(UPLOADS_PATH, collection)) and
234+
os.path.isdir("{}/{}/{}".format(UPLOADS_PATH, collection, experiment)) and
235+
os.path.isdir("{}/{}/{}/{}".format(UPLOADS_PATH, collection, experiment, channel))
236+
):
237+
data = MANAGER.getdata(collection, experiment, channel, resolution, xs, ys, zs)
238+
res = {}
239+
res["dtype"] = str(data.dtype)
240+
res["data"] = data.tolist()
241+
return jsonify(res)
242+
else:
243+
return Response(
244+
json.dumps({"message": "DNE"}),
245+
status=402,
246+
mimetype="application/json"
247+
)
248+
249+
59250
@APP.route("/")
60251
def hello():
61252
"""Root."""

bosslet/utils.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Copyright 2016 NeuroData (http://neurodata.io)
2+
# Copyright 2016 The Johns Hopkins University Applied Physics Laboratory
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from __future__ import absolute_import
17+
import numpy
18+
from six.moves import range
19+
20+
21+
def snap_to_cube(q_start, q_stop, chunk_depth=16, q_index=1):
22+
"""
23+
For any q in {x, y, z, t}
24+
Takes in a q-range and returns a 1D bound that starts at a cube
25+
boundary and ends at another cube boundary and includes the volume
26+
inside the bounds. For instance, snap_to_cube(2, 3) = (1, 17)
27+
Arguments:
28+
q_start (int): The lower bound of the q bounding box of volume
29+
q_stop (int): The upper bound of the q bounding box of volume
30+
chunk_depth (int : CHUNK_DEPTH) The size of the chunk in this
31+
volume (use ``get_info()``)
32+
q_index (int : 1): The starting index of the volume (in q)
33+
Returns:
34+
2-tuple: (lo, hi) bounding box for the volume
35+
"""
36+
lo = 0
37+
hi = 0
38+
# Start by indexing everything at zero for our own sanity
39+
q_start -= q_index
40+
q_stop -= q_index
41+
42+
if q_start % chunk_depth == 0:
43+
lo = q_start
44+
else:
45+
lo = q_start - (q_start % chunk_depth)
46+
47+
if q_stop % chunk_depth == 0:
48+
hi = q_stop
49+
else:
50+
hi = q_stop + (chunk_depth - q_stop % chunk_depth)
51+
52+
return [lo + q_index, hi + q_index + 1]
53+
54+
55+
def block_compute(x_start, x_stop,
56+
y_start, y_stop,
57+
z_start, z_stop,
58+
origin=(0, 0, 0),
59+
block_size=(512, 512, 16)):
60+
"""
61+
Get bounding box coordinates (in 3D) of small cutouts to request in
62+
order to reconstitute a larger cutout.
63+
Arguments:
64+
x_start (int): The lower bound of dimension x
65+
x_stop (int): The upper bound of dimension x
66+
y_start (int): The lower bound of dimension y
67+
y_stop (int): The upper bound of dimension y
68+
z_start (int): The lower bound of dimension z
69+
z_stop (int): The upper bound of dimension z
70+
Returns:
71+
[((x_start, x_stop), (y_start, y_stop), (z_start, z_stop)), ... ]
72+
"""
73+
# x
74+
x_bounds = range(origin[0], x_stop + block_size[0], block_size[0])
75+
x_bounds = [x for x in x_bounds if (x > x_start and x < x_stop)]
76+
if len(x_bounds) is 0:
77+
x_slices = [(x_start, x_stop)]
78+
else:
79+
x_slices = []
80+
for start_x in x_bounds[:-1]:
81+
x_slices.append((start_x, start_x + block_size[0]))
82+
x_slices.append((x_start, x_bounds[0]))
83+
x_slices.append((x_bounds[-1], x_stop))
84+
85+
# y
86+
y_bounds = range(origin[1], y_stop + block_size[1], block_size[1])
87+
y_bounds = [y for y in y_bounds if (y > y_start and y < y_stop)]
88+
if len(y_bounds) is 0:
89+
y_slices = [(y_start, y_stop)]
90+
else:
91+
y_slices = []
92+
for start_y in y_bounds[:-1]:
93+
y_slices.append((start_y, start_y + block_size[1]))
94+
y_slices.append((y_start, y_bounds[0]))
95+
y_slices.append((y_bounds[-1], y_stop))
96+
97+
# z
98+
z_bounds = range(origin[2], z_stop + block_size[2], block_size[2])
99+
z_bounds = [z for z in z_bounds if (z > z_start and z < z_stop)]
100+
if len(z_bounds) is 0:
101+
z_slices = [(z_start, z_stop)]
102+
else:
103+
z_slices = []
104+
for start_z in z_bounds[:-1]:
105+
z_slices.append((start_z, start_z + block_size[2]))
106+
z_slices.append((z_start, z_bounds[0]))
107+
z_slices.append((z_bounds[-1], z_stop))
108+
109+
# alright, yuck. but now we have {x, y, z}_slices, each of which hold the
110+
# start- and end-points of each cube-aligned boundary. For instance, if you
111+
# requested z-slices 4 through 20, it holds [(4, 16), (16, 20)].
112+
113+
# For my next trick, I'll convert these to a list of:
114+
# ((x_start, x_stop), (y_start, y_stop), (z_start, z_stop))
115+
116+
chunks = []
117+
for x in x_slices:
118+
for y in y_slices:
119+
for z in z_slices:
120+
chunks.append((x, y, z))
121+
return chunks

0 commit comments

Comments
 (0)