Spacelab 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. spacelab/__init__.py +6 -0
  2. spacelab/__main__.py +3 -0
  3. spacelab/cli.py +97 -0
  4. spacelab/hlod/__init__.py +8 -0
  5. spacelab/hlod/__main__.py +5 -0
  6. spacelab/hlod/build.py +349 -0
  7. spacelab/hlod/cells.py +82 -0
  8. spacelab/hlod/cli.py +90 -0
  9. spacelab/hlod/hashing.py +38 -0
  10. spacelab/hlod/hilbert.py +91 -0
  11. spacelab/hlod/read.py +150 -0
  12. spacelab/hlod/verify.py +142 -0
  13. spacelab/prep_spatialdata.py +700 -0
  14. spacelab/server.py +130 -0
  15. spacelab/static/AI_lens_black.png +0 -0
  16. spacelab/static/ai-new-logo.png +0 -0
  17. spacelab/static/aifi_logo.png +0 -0
  18. spacelab/static/assets/AllenInstitutePlusText-Bold-DxLtQ4oE.woff2 +0 -0
  19. spacelab/static/assets/AllenInstitutePlusText-BoldItalic-mxzHQhQO.woff2 +0 -0
  20. spacelab/static/assets/AllenInstitutePlusText-Italic-CaKmxpsW.woff2 +0 -0
  21. spacelab/static/assets/AllenInstitutePlusText-Light-DUThXl1m.woff2 +0 -0
  22. spacelab/static/assets/AllenInstitutePlusText-LightItalic-CxfKknzG.woff2 +0 -0
  23. spacelab/static/assets/AllenInstitutePlusText-Regular-B0ryviHQ.woff2 +0 -0
  24. spacelab/static/assets/AllenInstitutePlusText-Semibold-3PoiDF1d.woff2 +0 -0
  25. spacelab/static/assets/AllenInstitutePlusText-SemiboldItalic-uXaVqnFo.woff2 +0 -0
  26. spacelab/static/assets/blosc-BKQbfHyB.js +18 -0
  27. spacelab/static/assets/blosc-j6ygecpn.js +18 -0
  28. spacelab/static/assets/chunk-INHXZS53-D3tQiqtZ.js +2 -0
  29. spacelab/static/assets/chunk-INHXZS53-QjxmivJp.js +2 -0
  30. spacelab/static/assets/deflate-BRVR_fNk.js +2 -0
  31. spacelab/static/assets/hlodTranscripts.worker-CjhNLk56.js +2 -0
  32. spacelab/static/assets/index-BT-mDgg2.js +5025 -0
  33. spacelab/static/assets/index-CNIqsj6m.css +1 -0
  34. spacelab/static/assets/jpeg-JUDfHHDo.js +2 -0
  35. spacelab/static/assets/lerc-CXQdKTps.js +2 -0
  36. spacelab/static/assets/lz4-C2MQfzIA.js +16 -0
  37. spacelab/static/assets/lz4-rzy_Siro.js +16 -0
  38. spacelab/static/assets/lzw-DBCCrYEo.js +2 -0
  39. spacelab/static/assets/packbits-LrtZ2mhj.js +2 -0
  40. spacelab/static/assets/pako.esm-C69tvH9t.js +2 -0
  41. spacelab/static/assets/polygons.worker-ClOPdWlW.js +2 -0
  42. spacelab/static/assets/raw-lPmjnySZ.js +2 -0
  43. spacelab/static/assets/spatialdataExpression.worker-D4W_OHS3.js +4 -0
  44. spacelab/static/assets/spatialdataShapes.worker-DQV1zeTt.js +974 -0
  45. spacelab/static/assets/spatialdataTranscripts.worker-DH9L-MwZ.js +974 -0
  46. spacelab/static/assets/transcripts.worker-CtxyO4n3.js +2 -0
  47. spacelab/static/assets/umap.worker-BPHuoLPV.js +2 -0
  48. spacelab/static/assets/webimage-xg0Pp5Jv.js +2 -0
  49. spacelab/static/assets/zstd-Cp7BEDDH.js +16 -0
  50. spacelab/static/assets/zstd-WlDjm0Vu.js +16 -0
  51. spacelab/static/blurred-bg.jpg +0 -0
  52. spacelab/static/favicon.ico +0 -0
  53. spacelab/static/index.html +22 -0
  54. spacelab/static/logo192.png +0 -0
  55. spacelab/static/logo512.png +0 -0
  56. spacelab/static/manifest.json +15 -0
  57. spacelab/static/robots.txt +3 -0
  58. spacelab-0.28.0.dist-info/METADATA +30 -0
  59. spacelab-0.28.0.dist-info/RECORD +62 -0
  60. spacelab-0.28.0.dist-info/WHEEL +4 -0
  61. spacelab-0.28.0.dist-info/entry_points.txt +2 -0
  62. spacelab-0.28.0.dist-info/licenses/LICENSE +21 -0
spacelab/server.py ADDED
@@ -0,0 +1,130 @@
1
+ """Static server mirroring server.js: the bundled frontend first, then the data dir."""
2
+
3
+ import json
4
+ import mimetypes
5
+ import os
6
+ import re
7
+ import sys
8
+ import time
9
+ from functools import partial
10
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
11
+ from pathlib import Path
12
+
13
+ STATIC_DIR = Path(__file__).parent / "static"
14
+ ZARR_METADATA = (".zarray", ".zattrs", ".zgroup")
15
+ # One range only (what the app sends); a multi-range request gets the whole file.
16
+ _RANGE = re.compile(r"bytes=(\d*)-(\d*)$")
17
+ _COPY_CHUNK = 64 * 1024
18
+ _START = time.time()
19
+
20
+
21
+ class Handler(SimpleHTTPRequestHandler):
22
+ def __init__(self, *args, data_dir: str, **kwargs):
23
+ self.data_dir = data_dir
24
+ super().__init__(*args, directory=str(STATIC_DIR), **kwargs)
25
+
26
+ def translate_path(self, path):
27
+ # Frontend assets win; anything else resolves against the data dir.
28
+ static = super().translate_path(path)
29
+ if os.path.exists(static):
30
+ return static
31
+ rel = os.path.relpath(static, STATIC_DIR)
32
+ return os.path.join(self.data_dir, rel)
33
+
34
+ def guess_type(self, path):
35
+ if str(path).endswith(ZARR_METADATA):
36
+ return "application/json"
37
+ return super().guess_type(path)
38
+
39
+ def end_headers(self):
40
+ self.send_header("Access-Control-Allow-Origin", "*")
41
+ self.send_header("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
42
+ self.send_header(
43
+ "Access-Control-Allow-Headers",
44
+ "Origin, X-Requested-With, Content-Type, Accept, Range",
45
+ )
46
+ self.send_header("Access-Control-Expose-Headers", "Content-Length, Content-Range")
47
+ self.send_header("Cache-Control", "no-cache")
48
+ super().end_headers()
49
+
50
+ def do_OPTIONS(self):
51
+ self.send_response(204)
52
+ self.end_headers()
53
+
54
+ # Byte ranges, as express.static serves them: the SpatialData reader fetches
55
+ # parquet footers and single row groups by range, and a 200 with the whole
56
+ # file instead makes it pull a multi-GB transcripts part into the browser.
57
+ def send_head(self):
58
+ self._range_remaining = None
59
+ header = self.headers.get("Range")
60
+ path = self.translate_path(self.path)
61
+ match = _RANGE.match(header.strip()) if header else None
62
+ if not match or not (match[1] or match[2]) or not os.path.isfile(path):
63
+ return super().send_head()
64
+
65
+ size = os.path.getsize(path)
66
+ if match[1]:
67
+ start = int(match[1])
68
+ end = min(int(match[2]), size - 1) if match[2] else size - 1
69
+ else: # suffix range: the last N bytes
70
+ start, end = max(0, size - int(match[2])), size - 1
71
+ if start >= size or start > end:
72
+ self.send_response(416)
73
+ self.send_header("Content-Range", f"bytes */{size}")
74
+ self.send_header("Content-Length", "0")
75
+ self.end_headers()
76
+ return None
77
+
78
+ f = open(path, "rb")
79
+ f.seek(start)
80
+ self.send_response(206)
81
+ self.send_header("Content-Type", self.guess_type(path))
82
+ self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
83
+ self.send_header("Content-Length", str(end - start + 1))
84
+ self.send_header("Accept-Ranges", "bytes")
85
+ self.end_headers()
86
+ self._range_remaining = end - start + 1
87
+ return f
88
+
89
+ def copyfile(self, source, outputfile):
90
+ remaining = getattr(self, "_range_remaining", None)
91
+ if remaining is None:
92
+ return super().copyfile(source, outputfile)
93
+ while remaining > 0:
94
+ buf = source.read(min(_COPY_CHUNK, remaining))
95
+ if not buf:
96
+ break
97
+ outputfile.write(buf)
98
+ remaining -= len(buf)
99
+
100
+ def do_GET(self):
101
+ if self.path == "/health":
102
+ body = json.dumps({"status": "healthy", "uptime": time.time() - _START}).encode()
103
+ self.send_response(200)
104
+ self.send_header("Content-Type", "application/json")
105
+ self.send_header("Content-Length", str(len(body)))
106
+ self.end_headers()
107
+ self.wfile.write(body)
108
+ return
109
+ super().do_GET()
110
+
111
+ def log_message(self, format, *args):
112
+ pass # per-chunk request logs would flood the terminal
113
+
114
+
115
+ class Server(ThreadingHTTPServer):
116
+ daemon_threads = True
117
+
118
+ def handle_error(self, request, client_address):
119
+ # The app aborts in-flight fetches (tiles on pan/zoom, chunks on tissue
120
+ # switch); the resulting resets are routine, not worth a traceback.
121
+ if isinstance(sys.exc_info()[1], (ConnectionResetError, BrokenPipeError)):
122
+ return
123
+ super().handle_error(request, client_address)
124
+
125
+
126
+ def make_server(data_dir: str, host: str, port: int) -> ThreadingHTTPServer:
127
+ mimetypes.add_type("application/javascript", ".js")
128
+ mimetypes.add_type("application/wasm", ".wasm")
129
+ handler = partial(Handler, data_dir=os.path.abspath(data_dir))
130
+ return Server((host, port), handler)
Binary file
Binary file
Binary file