Loading noctua/api/fits_image.py +3 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ Endpoints GET /info JSON metadata: shape, dtype, auto-range percentiles. GET /png?vmin=&vmax= PNG rendered with Viridis, longest side capped at 512 px. PNG rendered with Viridis, longest side capped at 256 px. GET /panoramic?vmin=&vmax=&max_px= Small PNG thumbnail (longest side ≤ *max_px*, default 256). GET /tile?cx=&cy=&rx=&ry= Loading Loading @@ -62,7 +62,7 @@ CAMERAS_INI = PACKAGE_ROOT / 'config' / 'cameras.ini' cameras_cfg = configparser.ConfigParser() cameras_cfg_mtime = None _PNG_MAX_PX = 512 _PNG_MAX_PX = 256 def get_cameras_cfg() -> configparser.ConfigParser: Loading Loading @@ -191,7 +191,7 @@ async def image_panoramic(cam_id): vmin = request.args.get('vmin', default=None, type=float) vmax = request.args.get('vmax', default=None, type=float) max_px = int(request.args.get('max_px', 256)) max_px = int(request.args.get('max_px', 128)) png = fits_to_png(data, vmin=vmin, vmax=vmax, thumbnail_size=max_px) return Response(png, mimetype='image/png', headers={'Cache-Control': 'no-store'}) Loading noctua/web/static/js/viewer/fits-viewer.js +21 −6 Original line number Diff line number Diff line Loading @@ -493,12 +493,13 @@ export class FitsViewer { } if (state.mode === 'fits') { // WebGL path has no decoded PNG <img> to reuse for the panoramic // canvas, so it still needs its own /panoramic request. await this._renderFits(seq); await this._refreshPanoramic(seq); } else { await this._renderPng(seq); } await this._refreshPanoramic(seq); this._redrawOverlay(); } Loading @@ -509,6 +510,12 @@ export class FitsViewer { * the longest side, which is small enough to transfer quickly over localhost * and large enough for the 800-px canvas. * * The same decoded image is also reused for the panoramic thumbnail (see * _drawPanoViewport) instead of issuing a separate /panoramic request: * both endpoints render from the same data with the same vmin/vmax, so * downscaling this 512-px image client-side is visually equivalent to a * fresh 256-px render. * * Parameters * ---------- * seq : number, optional Loading Loading @@ -538,6 +545,9 @@ export class FitsViewer { const lb = state.letterbox; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, lb.dx, lb.dy, lb.dw, lb.dh); this._panImage = img; this._drawPanoViewport(); } /** Loading Loading @@ -713,10 +723,10 @@ export class FitsViewer { try { if (this._primary.mode === 'fits') { await this._renderFits(); await this._refreshPanoramic(); } else { await this._renderPng(); } await this._refreshPanoramic(); this._redrawOverlay(); } finally { this._primaryRangeRefreshBusy = false; Loading Loading @@ -1251,9 +1261,14 @@ export class FitsViewer { // server's auto-range. "Auto" (autoUpdate) is untouched — it only // controls whether frames keep refreshing at all. this._primary.autoRange = false; // PNG mode: re-fetch at the new range. FITS mode: already rendered by GPU. if (this._primary.mode === 'png') await this._renderPng(); // PNG mode: re-fetch at the new range (also updates the panoramic // canvas, see _renderPng). FITS mode: cv-main already rendered by // GPU, but the panoramic thumbnail still needs its own PNG refresh. if (this._primary.mode === 'png') { await this._renderPng(); } else { await this._refreshPanoramic(); } this._redrawOverlay(); } Loading Loading
noctua/api/fits_image.py +3 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ Endpoints GET /info JSON metadata: shape, dtype, auto-range percentiles. GET /png?vmin=&vmax= PNG rendered with Viridis, longest side capped at 512 px. PNG rendered with Viridis, longest side capped at 256 px. GET /panoramic?vmin=&vmax=&max_px= Small PNG thumbnail (longest side ≤ *max_px*, default 256). GET /tile?cx=&cy=&rx=&ry= Loading Loading @@ -62,7 +62,7 @@ CAMERAS_INI = PACKAGE_ROOT / 'config' / 'cameras.ini' cameras_cfg = configparser.ConfigParser() cameras_cfg_mtime = None _PNG_MAX_PX = 512 _PNG_MAX_PX = 256 def get_cameras_cfg() -> configparser.ConfigParser: Loading Loading @@ -191,7 +191,7 @@ async def image_panoramic(cam_id): vmin = request.args.get('vmin', default=None, type=float) vmax = request.args.get('vmax', default=None, type=float) max_px = int(request.args.get('max_px', 256)) max_px = int(request.args.get('max_px', 128)) png = fits_to_png(data, vmin=vmin, vmax=vmax, thumbnail_size=max_px) return Response(png, mimetype='image/png', headers={'Cache-Control': 'no-store'}) Loading
noctua/web/static/js/viewer/fits-viewer.js +21 −6 Original line number Diff line number Diff line Loading @@ -493,12 +493,13 @@ export class FitsViewer { } if (state.mode === 'fits') { // WebGL path has no decoded PNG <img> to reuse for the panoramic // canvas, so it still needs its own /panoramic request. await this._renderFits(seq); await this._refreshPanoramic(seq); } else { await this._renderPng(seq); } await this._refreshPanoramic(seq); this._redrawOverlay(); } Loading @@ -509,6 +510,12 @@ export class FitsViewer { * the longest side, which is small enough to transfer quickly over localhost * and large enough for the 800-px canvas. * * The same decoded image is also reused for the panoramic thumbnail (see * _drawPanoViewport) instead of issuing a separate /panoramic request: * both endpoints render from the same data with the same vmin/vmax, so * downscaling this 512-px image client-side is visually equivalent to a * fresh 256-px render. * * Parameters * ---------- * seq : number, optional Loading Loading @@ -538,6 +545,9 @@ export class FitsViewer { const lb = state.letterbox; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, lb.dx, lb.dy, lb.dw, lb.dh); this._panImage = img; this._drawPanoViewport(); } /** Loading Loading @@ -713,10 +723,10 @@ export class FitsViewer { try { if (this._primary.mode === 'fits') { await this._renderFits(); await this._refreshPanoramic(); } else { await this._renderPng(); } await this._refreshPanoramic(); this._redrawOverlay(); } finally { this._primaryRangeRefreshBusy = false; Loading Loading @@ -1251,9 +1261,14 @@ export class FitsViewer { // server's auto-range. "Auto" (autoUpdate) is untouched — it only // controls whether frames keep refreshing at all. this._primary.autoRange = false; // PNG mode: re-fetch at the new range. FITS mode: already rendered by GPU. if (this._primary.mode === 'png') await this._renderPng(); // PNG mode: re-fetch at the new range (also updates the panoramic // canvas, see _renderPng). FITS mode: cv-main already rendered by // GPU, but the panoramic thumbnail still needs its own PNG refresh. if (this._primary.mode === 'png') { await this._renderPng(); } else { await this._refreshPanoramic(); } this._redrawOverlay(); } Loading