Follow-up to #455, which contained the problem in the viewer and the transport but left the data itself unchanged.
The meshes are undecimated
volume_man.obj is published at full resolution. APL_L (VFB_jrmc3hpm) is 513,692,448 bytes — 5,677,124 vertices and 11,402,252 faces — for a single neuron. The hemibrain copy of the same cell is 7,537,079 bytes, so this is not inherent to the arbor.
Measured 2026-08-25, all aligned to JRC2018U unless stated:
| Neuron |
volume_man.obj |
volume.obj |
APL_L MaleCNS VFB_jrmc3hpm |
513,692,448 |
absent |
APL_R MaleCNS VFB_jrmc3hpl |
539,778,360 |
absent |
APL_R OpticLobe VFB_00103m9x |
656,186,044 |
3,526,734 |
APL FlyWire VFB_fw036977 |
295,091,332 |
295,091,332 (same file) |
APL_R hemibrain VFB_jrchjrhd on JRCFIB2018Fum |
absent |
7,537,079 |
| 10 sampled MaleCNS Kenyon cells |
3.7–6.3 MB |
absent |
It scales with arbor size rather than with dataset, so it affects large neurons everywhere. Even with the guard from #455 a 5.68 M-vertex mesh is roughly 140 MB of GPU buffers before parse overhead, so decimation is what makes these neurons viewable at all rather than merely non-fatal.
Suggested target is the ~10 MB hemibrain already achieves.
MaleCNS has no volume.obj
Every MaleCNS individual publishes volume_man.obj and nothing smaller, so there is no decimated asset to prefer. Generating one, and preferring it over volume_man.obj in the KB obj field where it exists, would let the viewer use the full mesh for far more neurons than it can today.
The data host serves .obj uncompressed
gzip on is set without gzip_types, and nginx's default is text/html only. Every data file is served as application/octet-stream (the default_type), so meshes and skeletons go out uncompressed while the directory listing is compressed. Adding gzip_types, gzip_vary and gzip_static in Robbie1977/Docker-nginx-fileserver fixes it. Measured on a real 5,795,778 byte mesh: level 1 gives 2.69x in 87 ms, level 4 2.99x in 147 ms, level 6 3.15x in 448 ms, and a pre-built .gz 3.15x in 14 ms. On-the-fly compression cost grows with the file, so the pipeline emitting .obj.gz alongside the decimated meshes would be worth having.
Separately, responses carry three Cache-Control headers from that config plus a fourth private added by the front proxy. private last wins for shared caches, which quietly defeats the public, immutable the file server is setting.
Longer term: pass the URL, not the geometry
ThreeDEngine.loadThreeOBJModelFromNode calls loader.parse(node.obj), so the mesh text travels inside the resolved model. THREE.OBJLoader.load(url, onLoad, onProgress, onError) already exists unused at geppetto-client/js/components/interface/3dCanvas/OBJLoader.js:42, and the URL is already on the unresolved import type client-side. Loading directly in the browser would remove the server round-trip and the model copy entirely, and give real load progress and a year of browser caching. Colour, opacity and visibility would need to stay bound to the existing instance handle or the controls break.
Follow-up to #455, which contained the problem in the viewer and the transport but left the data itself unchanged.
The meshes are undecimated
volume_man.objis published at full resolution. APL_L (VFB_jrmc3hpm) is 513,692,448 bytes — 5,677,124 vertices and 11,402,252 faces — for a single neuron. The hemibrain copy of the same cell is 7,537,079 bytes, so this is not inherent to the arbor.Measured 2026-08-25, all aligned to JRC2018U unless stated:
volume_man.objvolume.objVFB_jrmc3hpmVFB_jrmc3hplVFB_00103m9xVFB_fw036977VFB_jrchjrhdon JRCFIB2018FumIt scales with arbor size rather than with dataset, so it affects large neurons everywhere. Even with the guard from #455 a 5.68 M-vertex mesh is roughly 140 MB of GPU buffers before parse overhead, so decimation is what makes these neurons viewable at all rather than merely non-fatal.
Suggested target is the ~10 MB hemibrain already achieves.
MaleCNS has no
volume.objEvery MaleCNS individual publishes
volume_man.objand nothing smaller, so there is no decimated asset to prefer. Generating one, and preferring it overvolume_man.objin the KBobjfield where it exists, would let the viewer use the full mesh for far more neurons than it can today.The data host serves
.objuncompressedgzip onis set withoutgzip_types, and nginx's default istext/htmlonly. Every data file is served asapplication/octet-stream(thedefault_type), so meshes and skeletons go out uncompressed while the directory listing is compressed. Addinggzip_types,gzip_varyandgzip_staticinRobbie1977/Docker-nginx-fileserverfixes it. Measured on a real 5,795,778 byte mesh: level 1 gives 2.69x in 87 ms, level 4 2.99x in 147 ms, level 6 3.15x in 448 ms, and a pre-built.gz3.15x in 14 ms. On-the-fly compression cost grows with the file, so the pipeline emitting.obj.gzalongside the decimated meshes would be worth having.Separately, responses carry three
Cache-Controlheaders from that config plus a fourthprivateadded by the front proxy.privatelast wins for shared caches, which quietly defeats thepublic, immutablethe file server is setting.Longer term: pass the URL, not the geometry
ThreeDEngine.loadThreeOBJModelFromNodecallsloader.parse(node.obj), so the mesh text travels inside the resolved model.THREE.OBJLoader.load(url, onLoad, onProgress, onError)already exists unused atgeppetto-client/js/components/interface/3dCanvas/OBJLoader.js:42, and the URL is already on the unresolved import type client-side. Loading directly in the browser would remove the server round-trip and the model copy entirely, and give real load progress and a year of browser caching. Colour, opacity and visibility would need to stay bound to the existing instance handle or the controls break.