From 119daf38453a0e6aa63aa9f4190d524490614789 Mon Sep 17 00:00:00 2001 From: roby Date: Thu, 13 Aug 2026 15:54:20 -0600 Subject: [PATCH] Firefly-2058: Add update to become FITS aware - Fits file extraction - Fits readout - Fits cube integration - Wcs lock will support syncing cube planes - Download request can attach to an active download - Improved caching - rEdo flux readout so the the readout data all on the server - Fixed wcs match issue --- eslint.config.mjs | 2 +- .../ipac/firefly/data/ServerParams.java | 1 + .../caltech/ipac/firefly/server/SrvParam.java | 10 + .../server/query/URLFileInfoProcessor.java | 2 +- .../firefly/server/rpc/VisServerCommands.java | 23 +- .../server/servlets/AnyFileDownload.java | 2 +- .../server/servlets/AnyFileUpload.java | 2 +- .../firefly/server/servlets/HiPSRetrieve.java | 269 +++++++++++++----- .../firefly/server/util/LockingRetrieve.java | 106 ++++--- .../firefly/server/visualize/FitsCacher.java | 124 ++++++-- .../server/visualize/FluxValueUtil.java | 110 +++++++ .../server/visualize/ImagePlotBuilder.java | 26 +- .../server/visualize/ImagePlotCreator.java | 16 +- .../server/visualize/PlotServUtils.java | 12 +- .../server/visualize/ProgressStat.java | 18 +- .../server/visualize/VisJsonSerializer.java | 37 ++- .../server/visualize/VisServerOps.java | 32 +-- .../server/visualize/WebPlotFactory.java | 15 +- .../server/visualize/hips/HiPSListUtil.java | 65 ++++- .../visualize/hips/IrsaHiPSListSource.java | 45 +-- .../visualize/hips/LsstHiPSListSource.java | 60 +--- .../imageretrieve/URIFileRetriever.java | 59 +++- .../ipac/firefly/visualize/BandState.java | 24 +- .../firefly/visualize/DirectFileAccess.java | 15 + .../visualize/DirectFitsAccessData.java | 73 ----- .../ipac/firefly/visualize/PlotState.java | 4 - .../edu/caltech/ipac/util/FitsHDUUtil.java | 8 +- .../edu/caltech/ipac/util/FormatUtil.java | 6 + .../ipac/util/download/BaseNetParams.java | 23 +- .../util/download/ConcurrentDownload.java | 2 + .../ipac/util/download/DownloadListener.java | 1 + .../ipac/util/download/Downloader.java | 3 + .../ipac/util/download/RetrieveUtil.java | 6 +- .../ipac/util/download/S3Download.java | 4 + .../ipac/util/download/URLDownload.java | 9 +- .../ipac/util/download/UriRefParams.java | 9 +- .../ipac/visualize/net/IbeImageGetter.java | 2 +- .../visualize/net/SloanDssImageParams.java | 2 +- .../ipac/visualize/plot/ImagePlot.java | 27 +- .../ipac/visualize/plot/PixelValue.java | 91 +++--- .../visualize/plot/plotdata/FitsExtract.java | 2 +- .../visualize/plot/plotdata/FitsRead.java | 6 +- .../visualize/plot/plotdata/FitsReadUtil.java | 46 ++- src/firefly/js/core/BootstrapRegistry.js | 3 +- src/firefly/js/data/ServerParams.js | 1 + .../js/drawingLayers/ExtractHiPSTileTool.js | 198 +++++++++++++ .../js/drawingLayers/ExtractPointsTool.js | 26 +- .../js/drawingLayers/PointSelection.js | 3 +- src/firefly/js/rpc/PlotServicesJson.js | 51 ++-- src/firefly/js/ui/HiPSImageSelect.jsx | 2 +- src/firefly/js/visualize/BandState.js | 13 +- src/firefly/js/visualize/ChangePrime.js | 24 +- src/firefly/js/visualize/FitsHeaderUtil.js | 1 + src/firefly/js/visualize/HiPSUtil.js | 150 +++++++++- src/firefly/js/visualize/ImagePlotCntlr.js | 7 +- src/firefly/js/visualize/ImagePlotDispatch.js | 5 +- src/firefly/js/visualize/MenuItemKeys.js | 1 + src/firefly/js/visualize/MouseReadoutCntlr.js | 1 + src/firefly/js/visualize/PlotAttribute.js | 5 + src/firefly/js/visualize/PlotState.js | 9 +- src/firefly/js/visualize/PlotViewUtil.js | 59 ++-- src/firefly/js/visualize/RangeValues.js | 5 + src/firefly/js/visualize/VisMouseSync.js | 3 +- src/firefly/js/visualize/WebPlot.js | 48 ++-- src/firefly/js/visualize/WebPlotRequest.js | 3 +- .../projection/ProjectionHeaderParser.js | 68 ++--- .../js/visualize/reducer/HandlePlotChange.js | 20 +- .../visualize/reducer/HandlePlotCreation.js | 10 +- .../js/visualize/saga/MouseReadoutWatch.js | 92 +++--- src/firefly/js/visualize/task/PlotHipsTask.js | 19 +- .../js/visualize/task/PlotImageTask.js | 4 +- src/firefly/js/visualize/task/WcsMatchTask.js | 36 ++- src/firefly/js/visualize/ui/Buttons.jsx | 3 + src/firefly/js/visualize/ui/ColorDialog.jsx | 2 +- .../js/visualize/ui/ExtractionWatchers.js | 8 +- .../js/visualize/ui/FitsHeaderView.jsx | 4 +- .../js/visualize/ui/MouseReadPopoutAll.jsx | 16 +- .../visualize/ui/MouseReadoutBottomLine.jsx | 13 +- .../visualize/ui/MouseReadoutOptionPopups.jsx | 1 + .../js/visualize/ui/MouseReadoutUIUtil.js | 8 +- .../js/visualize/ui/VisCtxToolbarView.jsx | 45 +-- .../js/visualize/ui/VisMiniToolbar.jsx | 51 ++-- .../ui/extraction/ExtractionDialog.jsx | 83 ++---- .../ui/extraction/ExtractionTable.jsx | 8 +- .../ui/extraction/ExtractionUIUtil.js | 75 +++++ .../extraction/HiPSTileExtractionDialog.jsx | 149 ++++++++++ .../util/serialization/SerializerTest.java | 8 +- 87 files changed, 1846 insertions(+), 894 deletions(-) create mode 100644 src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FluxValueUtil.java create mode 100644 src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFileAccess.java delete mode 100644 src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFitsAccessData.java create mode 100644 src/firefly/js/drawingLayers/ExtractHiPSTileTool.js create mode 100644 src/firefly/js/visualize/ui/extraction/ExtractionUIUtil.js create mode 100644 src/firefly/js/visualize/ui/extraction/HiPSTileExtractionDialog.jsx diff --git a/eslint.config.mjs b/eslint.config.mjs index 2981b607a..aaa3794a8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -103,7 +103,7 @@ export default [ 'react-hooks/component-hook-factories': 'warn', 'react-hooks/gating': 'warn', 'react-hooks/preserve-manual-memoization': 'warn', - 'react-hooks/set-state-in-effect': 'warn', + // 'react-hooks/set-state-in-effect': 'warn', 'react-hooks/static-components': 'warn', 'react-hooks/unsupported-syntax': 'warn', 'react-hooks/use-memo': 'warn', diff --git a/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java b/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java index f8bc1df32..63d5d68e6 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/data/ServerParams.java @@ -99,6 +99,7 @@ public class ServerParams { public static final String LSST = "lsst"; public static final String ALL = "all"; public static final String CDS = "cds"; + public static final String IS_HIPS_TILE = "isHipsTile"; public static final String HIPS_SOURCES = "hipsSources"; public static final String HIPS_LIST_SOURCE= "hipsListSource"; public static final String HIPS_LIST_SOURCE_NAME= "hipsListSourceName"; diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/SrvParam.java b/src/firefly/java/edu/caltech/ipac/firefly/server/SrvParam.java index a5d5d899c..6aedf8d16 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/SrvParam.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/SrvParam.java @@ -286,6 +286,16 @@ public ImagePt getRequiredImagePt(String key) { } } + public WorldPt getRequiredWorldPt(String key) { + String v= getRequired(key); + try { + return WorldPt.parse(v); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "parameter could not be parsed as WorldPt: parameter: "+ key + ", value: "+v, e); + } + } + public ImagePt[] getRequiredImagePtAry(String key) { String v= getRequired(key); try { diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/query/URLFileInfoProcessor.java b/src/firefly/java/edu/caltech/ipac/firefly/server/query/URLFileInfoProcessor.java index c22186d5e..4ab46ae78 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/query/URLFileInfoProcessor.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/query/URLFileInfoProcessor.java @@ -60,7 +60,7 @@ public static FileInfo retrieveViaURL(URL url, if (url==null) throw new MalformedURLException("Invalid URL"); UriRefParams params = new UriRefParams(url); - params.setPlotId(plotId); + params.setId(plotId); params.setStatusKey(progressKey); params.setDownloadDir(dir); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/rpc/VisServerCommands.java b/src/firefly/java/edu/caltech/ipac/firefly/server/rpc/VisServerCommands.java index bf2234825..39b858cf3 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/rpc/VisServerCommands.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/rpc/VisServerCommands.java @@ -4,13 +4,13 @@ package edu.caltech.ipac.firefly.server.rpc; import edu.caltech.ipac.firefly.core.Util; -import static edu.caltech.ipac.util.FormatUtil.Format.*; import edu.caltech.ipac.firefly.data.ServerParams; import edu.caltech.ipac.firefly.server.ServCommand; import edu.caltech.ipac.firefly.server.ServerCommandAccess; import edu.caltech.ipac.firefly.server.SrvParam; import edu.caltech.ipac.firefly.server.util.Logger; import edu.caltech.ipac.firefly.server.visualize.DirectStretchUtils.CompressType; +import edu.caltech.ipac.firefly.server.visualize.FluxValueUtil; import edu.caltech.ipac.firefly.server.visualize.VisJsonSerializer; import edu.caltech.ipac.firefly.server.visualize.VisServerOps; import edu.caltech.ipac.firefly.server.visualize.imagesources.ImageMasterData; @@ -19,7 +19,6 @@ import edu.caltech.ipac.firefly.visualize.WebPlotRequest; import edu.caltech.ipac.firefly.visualize.WebPlotResult; import edu.caltech.ipac.visualize.plot.ImagePt; -import edu.caltech.ipac.visualize.plot.PixelValue; import edu.caltech.ipac.visualize.plot.plotdata.FitsExtract; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; @@ -35,6 +34,9 @@ import java.util.List; import java.util.Map; +import static edu.caltech.ipac.util.FormatUtil.Format.JSON; +import static edu.caltech.ipac.util.FormatUtil.Format.OCTET_STREAM; + /** * @author Trey Roby * Date: 2/8/12 @@ -44,9 +46,20 @@ public class VisServerCommands { public static class FileFluxCmdJson extends ServCommand { public String doCommand(SrvParam sp) throws IllegalArgumentException { - PlotState[] stateAry= sp.getStateAry(); - List res= VisServerOps.getFlux(stateAry,sp.getRequiredImagePt("pt")); - return VisJsonSerializer.createPixelResultJson(res,stateAry[0].getBands(),stateAry.length); + var isHips= sp.getOptionalBoolean(ServerParams.IS_HIPS_TILE, false); + if (isHips) { + var res= FluxValueUtil.getFluxHiPS( + sp.getRequired(ServerParams.URL), + sp.getRequiredImagePt("pt"), + sp.getRequiredWorldPt("wpt"), + sp.getRequiredInt(ServerParams.PLANE)); + return VisJsonSerializer.createPixelResultJson(res,new Band[] {Band.NO_BAND},1); + } + else { + var stateAry= sp.getStateAry(); + var res= FluxValueUtil.getFlux(sp.getStateAry(),sp.getRequiredImagePt("pt")); + return VisJsonSerializer.createPixelResultJson(res,stateAry[0].getBands(),stateAry.length); + } } } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileDownload.java b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileDownload.java index f35d5f966..1c0e7e994 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileDownload.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileDownload.java @@ -127,7 +127,7 @@ private void handleHiPSRequest(HttpServletRequest req, HttpServletResponse res, SrvParam sp= new SrvParam(req.getParameterMap()); String hips= sp.getRequired(HIPS_PARAM); boolean alwaysUseCached= sp.getOptionalBoolean(ALWAYS_USE_CACHED,false); - FileInfo fi= HiPSRetrieve.retrieveHiPSData(hips, null, alwaysUseCached); + FileInfo fi= HiPSRetrieve.retrieveHiPSData(hips, alwaysUseCached); if (fi.getFile()==null) { if (fi.getResponseCode()==204) { diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileUpload.java b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileUpload.java index 14ea89732..4047810e4 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileUpload.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/AnyFileUpload.java @@ -246,7 +246,7 @@ private static Result retrieveFile(SrvParam sp, FileItemInput uploadedItem) thro } else if (fromUrl != null) { // from a URL... get it String fname; if (hipsCache) { - statusFileInfo= HiPSRetrieve.retrieveHiPSData(fromUrl,null,false); + statusFileInfo= HiPSRetrieve.retrieveHiPSData(fromUrl,false); fname= (statusFileInfo.getFile()!=null) ? statusFileInfo.getFile().getName() : null; } else { diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/HiPSRetrieve.java b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/HiPSRetrieve.java index 9b8f34fe8..8dfc989bf 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/HiPSRetrieve.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/servlets/HiPSRetrieve.java @@ -8,25 +8,39 @@ import edu.caltech.ipac.firefly.data.FileInfo; import edu.caltech.ipac.firefly.server.ServerContext; +import edu.caltech.ipac.firefly.server.util.LockingRetrieve; +import edu.caltech.ipac.util.FormatUtil; +import edu.caltech.ipac.util.FormatUtil.Format; +import edu.caltech.ipac.util.download.DownloadListener; import edu.caltech.ipac.util.download.FailedRequestException; import edu.caltech.ipac.util.download.URLDownload; +import edu.caltech.ipac.util.download.UriRefParams; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static edu.caltech.ipac.util.FileUtil.K; import static edu.caltech.ipac.util.FileUtil.isDirectoryEmpty; +import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; +import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT; +import static java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; /** * @author Trey Roby @@ -34,96 +48,223 @@ public class HiPSRetrieve { private static final List extList= Arrays.asList("jpg", "jpeg", "png", "webp"); + private static final List formatList= Arrays.asList(Format.JPEG,Format.PNG,Format.WEBP); private static final long minFileLengthOnError = 2*K; + private static final long minFitsFileLengthOnError = 5*K; + private static final long minSizeCacheFile = K; - public static FileInfo retrieveHiPSData(String urlStr, String pathExt, boolean alwaysUseCached) { - try { - URL url= new URL(urlStr); + private static record DeadEntry(long time, int status) {}; + private static final Map deadFitsUrl= Collections.synchronizedMap(new HashMap<>()); + private static final int MAX_DEAD_URL_SIZE= 10000; + private static final ExecutorService executor = Executors.newFixedThreadPool(10); - String fPath = pathExt == null ? url.getPath() : (url.getPath() + "/" + pathExt); - File dir= new File(ServerContext.getHiPSDir(),new File(url.getHost() + fPath).getParent()); - if (!dir.exists()) dir.mkdirs(); + public static FileInfo retrieveHiPSData(String urlStr, boolean alwaysUseCached) { + return retrieveHiPSData(urlStr,null, alwaysUseCached); - File targetFile= new File(dir, new File((pathExt == null ? url.getFile() : pathExt)).getName()); + } - if (targetFile.canRead() && targetFile.isDirectory()) { - if (isDirectoryEmpty(targetFile)) { - targetFile.delete(); - } - else { - return new FileInfo(HttpURLConnection.HTTP_FORBIDDEN, "this hips request conflicts with the HiPS protocol, attempt replace a directory with a file"); - } - } + public static FileInfo retrieveHiPSData(String urlStr, String fileName, boolean alwaysUseCached) { + URL url= URLDownload.makeURL(urlStr); + if (url==null) return new FileInfo(404, "Invalid URL: " + urlStr); + var params= new UriRefParams(url); + params.setNotify(false); + var dl= LockingRetrieve.makeDownloadProgressOrFind(params); + return retrieveHiPSData(url, fileName, alwaysUseCached, dl); + } - boolean fileExistLocal= targetFile.canRead() && targetFile.length()>400; + public static FileInfo retrieveHiPSData(UriRefParams params) { + URL url= params.getUriRef().getURL(); + var dl= LockingRetrieve.makeDownloadProgressOrFind(params); + return retrieveHiPSData(url, null, !params.getCheckForNewer(), dl); + } + /** + * + * @param url the url + * @param fileName add a filename to the cacheName, if null then compute a name + s @param alwaysUseCached if true and the file is in the cache then don't make a If-Modified-Since call, just return + * @param dl the download listener + * @return the retrieved (or cached) file + */ + public static FileInfo retrieveHiPSData(URL url, String fileName, boolean alwaysUseCached, DownloadListener dl) { + if (url==null) return new FileInfo(404, "Invalid URL"); - FileInfo preFetchFileInfo= new FileInfo(targetFile); - if (alwaysUseCached && fileExistLocal) return preFetchFileInfo; + File targetFile = getHipsCacheFile(url, fileName); + var locationPrepared= prepareTargetLocation(targetFile); - // if we already have a version of the file set the download modified only option. Also set a very time timeout, - // so that if the server is down we don't wait long. - URLDownload.Options options= fileExistLocal ? URLDownload.Options.modifiedAndTimeoutOp(true,4) : URLDownload.Options.def(); - int rCode; - File retFile; - FileInfo fetchedFileInfo; - try { - fetchedFileInfo= URLDownload.getDataToFile(url,targetFile,null, null, options); - rCode= fetchedFileInfo.getResponseCode(); - retFile= fetchedFileInfo.getFile(); - } - catch (FailedRequestException e) { - if (fileExistLocal && targetFile.length() > minFileLengthOnError) return preFetchFileInfo; // if the file existed and has content, return it - else return new FileInfo(e.getResponseCode()); - } + if (!locationPrepared) { + return new FileInfo(HttpURLConnection.HTTP_FORBIDDEN, + "this hips request conflicts with the HiPS protocol, cannot cache hips file correctly"); + } + boolean fileExistLocal= isHiPSFileCached(url.toString(),fileName); - switch (rCode) { - case HTTP_OK -> { - if (isValid(retFile)) return fetchedFileInfo; - if (retFile!=null) retFile.delete(); - return new FileInfo(rCode); - } - case HTTP_NOT_MODIFIED -> { - return fetchedFileInfo; - } - default -> { - if (fileExistLocal && targetFile.length() > minFileLengthOnError) return preFetchFileInfo; // if the file existed and has content, return it - if (retFile != null) retFile.delete(); - if (rCode == 404 && imageRequest(retFile)) return new FileInfo(204); - else return new FileInfo(rCode); - } + if (alwaysUseCached && fileExistLocal) return new FileInfo(targetFile); + + // if we already have a version of the file set the download modified only option. Also set a very small timeout, + // so that if the server is down we don't wait long. + try { + URLDownload.Options options= fileExistLocal ? URLDownload.Options.modifiedAndTimeoutOp(true,3) : URLDownload.Options.def(); + options.setDl(dl); + FileInfo fetchedFileInfo= lockUrlDownload(url,fileName,targetFile,options); + var rCode= fetchedFileInfo.getResponseCode(); + return switch (rCode) { + case HTTP_OK, HTTP_NOT_MODIFIED -> fetchedFileInfo; + case HTTP_NOT_FOUND -> cleanupNoFound(targetFile,url); + case HTTP_CLIENT_TIMEOUT, 429, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, // transient failures, use cache if valid + HTTP_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT -> + isValidCached(targetFile) ? new FileInfo(targetFile) : cleanupBadRequest(targetFile,url,rCode,true); + default -> cleanupBadRequest(targetFile,url,rCode,false); + }; + } + catch (FailedRequestException e) { + if (isValidCached(targetFile)) return new FileInfo(targetFile); // if the file existed and has content, return it + else return new FileInfo(e.getResponseCode()); + } + + } + + private static FileInfo cleanupBadRequest(File f, URL url, int rCode, boolean useDeadFits) { + if (f != null) f.delete(); + if (useDeadFits && url.getPath().toLowerCase().endsWith(".fits")) { + deadFitsUrl.put(url.toString(), new DeadEntry(System.currentTimeMillis() + 15 * 1000,rCode)); // 15 seconds + } + return new FileInfo(rCode); + } + + private static FileInfo cleanupNoFound(File f, URL url) { + if (f != null) f.delete(); + if (url.getPath().toLowerCase().endsWith(".fits")) { + deadFitsUrl.put(url.toString(), new DeadEntry(System.currentTimeMillis() + 10 * 60 * 1000,HTTP_NOT_FOUND)); // 10 minutes + } + return imageRequest(f) ? new FileInfo(HTTP_NO_CONTENT) : new FileInfo(HTTP_NOT_FOUND); // return 204 because: the request was valid, but there is no image content to return + } + + private static boolean prepareTargetLocation(File targetFile) { + File dir= targetFile.getParentFile(); + if (!dir.exists() && !dir.mkdirs()) { + return false; + } + + if (!targetFile.exists()) return true; + + if (targetFile.isDirectory()) { + if (isDirectoryEmpty(targetFile)) { + if (!targetFile.delete()) return false; } - } catch (MalformedURLException e) { - return new FileInfo(null, null, 404, e.toString()); + else { + return false; + } + } + if (targetFile.length() { + File f= getHipsCacheFile(url,fileName); + if (f!=null && f.canRead() && f.length()>minSizeCacheFile) return new FileInfo(targetFile);; + return URLDownload.getDataToFile(url,targetFile,null, null, options); + }); + } + + public static void retrieveHiPSTileInBackground(String urlStr) { + if (isRetrieving(urlStr)) return; + executor.execute(() -> retrieveHiPSData(urlStr,false)); + } + + public static boolean isRetrieving(String urlStr) { + return LockingRetrieve.isActiveRequest(urlStr); + } + + public static File getHipsCacheFile(URL url, String fileName) { + if (url==null) return null; + String fPath = fileName == null ? url.getPath() : (url.getPath() + "/" + fileName); + File dir= new File(ServerContext.getHiPSDir(),new File(url.getHost() + fPath).getParent()); + return new File(dir, new File((fileName == null ? url.getFile() : fileName)).getName()); + } + + public static boolean isHiPSFileCached(String urlStr) { + return isHiPSFileCached(urlStr,null); + } + + public static boolean isHiPSFileCached(String urlStr, String fileName) { + if (isRetrieving(urlStr)) return false; + URL url= URLDownload.makeURL(urlStr); + if (url==null) return false; + File f= getHipsCacheFile(url,fileName); + if (f==null) return false; + return f.canRead() && f.length()>minSizeCacheFile; } private static boolean imageRequest(File f) { if (f==null) return false; String lowerF= f.getAbsolutePath().toLowerCase(); - return extList.stream().anyMatch(lowerF::contains); + return extList.stream().anyMatch(ext -> lowerF.endsWith("." + ext)); } - private static boolean isValid(File f) { - if (f==null) return false; + /** + * If we are getting errors then do some basic validation on the cached file + * @param cachedFile the cache file on disk + * @return true if valid + */ + private static boolean isValidCached(File cachedFile) { + if (cachedFile==null) return false; + if (!cachedFile.canRead()) return false; + if (cachedFile.length()= minFitsFileLengthOnError; } - } catch (IOException e) { return false; } + return true; + } + public static boolean isDeadFitsUrl(String urlStr) { + var entry= deadFitsUrl.get(urlStr); + if (entry == null) return false; + if (deadFitsUrl.size() > MAX_DEAD_URL_SIZE) cleanDeadFitsUrlCache(); + if (System.currentTimeMillis() > entry.status) { + deadFitsUrl.remove(urlStr); + return false; + } return true; } + public static int getDeadFitsUrlCode(String urlStr) { + if (isDeadFitsUrl(urlStr)) { + var entry= deadFitsUrl.get(urlStr); + return entry!=null ? entry.status : 200; + } + return 200; + } + private synchronized static void cleanDeadFitsUrlCache() { + if (deadFitsUrl.size() <= MAX_DEAD_URL_SIZE) return; + var copyMap= new HashMap<>(deadFitsUrl); + var cleanedUpMap= new HashMap(); + var cTime= System.currentTimeMillis(); + deadFitsUrl.clear(); + copyMap.forEach((key, entry) -> { + if (cTime activeRequest = Collections.synchronizedMap(new HashMap<>()); + private static final Map activeRequest = Collections.synchronizedMap(new HashMap<>()); private static final Map activeListeners = Collections.synchronizedMap(new HashMap<>()); public static FileInfo serviceWithCacheMsg(ImageServiceParams params, ServiceCaller svcCaller) throws FailedRequestException { @@ -48,7 +48,7 @@ public static FileInfo serviceWithCacheMsg(ImageServiceParams params, ServiceCal } /** - * download file with locking, cacheing, and messaging + * download file with locking, caching, and messaging * @param uri - accepts a URL, String, S3Ref, or UriRef **/ public static FileInfo downloadWithCacheMsg(Object uri) throws FailedRequestException { @@ -56,7 +56,7 @@ public static FileInfo downloadWithCacheMsg(Object uri) throws FailedRequestExce } /** - * download file with locking, cacheing, and messaging + * download a uri with locking, caching, and messaging * @param uri - accepts a URL, String, S3Ref, or UriRef **/ public static FileInfo downloadWithCacheMsg(Object uri, File downloadDir) throws FailedRequestException { @@ -69,44 +69,67 @@ public static FileInfo downloadWithCacheMsg(Object uri, File downloadDir) throws return downloadWithCacheMsg(params); } - /** download file with locking, cacheing, and messaging */ + /** download a uri with locking, caching, and messaging */ public static FileInfo downloadWithCacheMsg(UriRefParams params) throws FailedRequestException { return lockingRetrieve(params, () -> RetrieveUtil.downloadCaching(params, makeDownloadProgressOrFind(params))); } -//====================================================================== -//----------------------- Private Methods ------------------------------ -//====================================================================== - - private static FileInfo lockingRetrieve(BaseNetParams params, Callable getter) throws FailedRequestException { + public static FileInfo lockingRetrieve(BaseNetParams params, Callable getter) throws FailedRequestException { + LockEntry lockEntry= getLockEntry(params); try { - Object lockKey= activeRequest.computeIfAbsent(params, k -> new Object()); - synchronized (lockKey) { + synchronized (lockEntry) { return getter.call(); } } catch (Exception e) { throw ResponseMessage.simplifyNetworkCallException(e); } finally { - activeRequest.remove(params); + releaseLockEntry(params, lockEntry); } } - private static DownloadProgress makeDownloadProgressOrFind(UriRefParams params) { // todo: generalize beyond just plotId - if (params==null || params.getStatusKey()== null) return null; - DownloadProgress dl; - if (activeListeners.containsKey(params)) { - dl = activeListeners.get(params); - dl.addPlotId(params.getPlotId()); + private static LockEntry getLockEntry(BaseNetParams params) { + synchronized (activeRequest) { + LockEntry entry = activeRequest.computeIfAbsent(params, k -> new LockEntry()); + entry.refCount++; + return entry; } - else { - dl= new DownloadProgress(params.getStatusKey(), params.getPlotId()); - activeListeners.put(params, dl); + } + + /** Atomically decrement the refcount, and remove the map entry only if this was the last holder. */ + private static void releaseLockEntry(BaseNetParams params, LockEntry entry) { + synchronized (activeRequest) { + entry.refCount--; + if (entry.refCount <= 0) activeRequest.remove(params, entry); } - return dl; } + + public static boolean isActiveRequest(BaseNetParams params) { return activeRequest.containsKey(params); } + public static boolean isActiveRequest(String urlStr) { return isActiveRequest(new UriRefParams(urlStr)); } + + public static DownloadListener makeDownloadProgressOrFind(UriRefParams params) { // todo: generalize beyond just plotId + if (params==null || params.getStatusKey()== null) return null; + synchronized (activeListeners) { + DownloadProgress dl = activeListeners.get(params); + if (dl!=null) { + dl.addEntry(params); + } + else { + dl= new DownloadProgress(params) { + public void downloadDone() { activeListeners.remove(params); } + }; + activeListeners.put(params, dl); + } + return dl; + } + } + +//====================================================================== +//----------------------- Private Methods ------------------------------ +//====================================================================== + private static FileInfo retrieveImageService(ImageServiceParams params, ServiceCaller svcCaller) throws IOException, FailedRequestException { FileInfo fileInfo= FileCacheHelper.getFileInfo(params); if (fileInfo == null) { @@ -116,29 +139,34 @@ private static FileInfo retrieveImageService(ImageServiceParams params, ServiceC return fileInfo; } - private static class DownloadProgress implements DownloadListener { - private final String key; - private final List plotIdList= new ArrayList<>(); + private static abstract class DownloadProgress implements DownloadListener { + private record ListenerEntry(String key, String id) {} + private final List entryList = new ArrayList<>(); - DownloadProgress(String key, String plotId) { - this.key = key; - plotIdList.add(plotId); - } + DownloadProgress(BaseNetParams params) { addEntry(params); } - void addPlotId(String plotId) {plotIdList.add(plotId);} + synchronized void addEntry(BaseNetParams params) { + if (!params.getNotify()) return; + entryList.add(new ListenerEntry(params.getStatusKey(),params.getId())); + } + /** + * call the listeners for each plot id. We don't care about synchronization when reading the list since the next + * call will fix it. Also, we don't want to sync when reading because that will be done very often + * @param ev + */ public void dataDownloading(DownloadEvent ev) { - if (key == null) return; - String offStr = ""; + if (entryList.isEmpty()) return; long current= ev.getCurrent(); long max= ev.getMax(); - if (max > 0 && current 0 && current + PlotServUtils.updateProgress(entry.key,entry.id, ProgressStat.PType.DOWNLOADING, messStr) ); } } + + private static final class LockEntry { + int refCount = 0; + } } \ No newline at end of file diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FitsCacher.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FitsCacher.java index e046234bf..e82dfe3f1 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FitsCacher.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FitsCacher.java @@ -10,6 +10,7 @@ import edu.caltech.ipac.firefly.server.util.Logger; import edu.caltech.ipac.firefly.server.visualize.fitseval.FitsDataEval; import edu.caltech.ipac.firefly.server.visualize.fitseval.FitsEvaluation; +import edu.caltech.ipac.firefly.visualize.DirectFileAccess; import edu.caltech.ipac.firefly.visualize.WebPlotRequest; import edu.caltech.ipac.util.FileUtil; import edu.caltech.ipac.util.UTCTimeUtil; @@ -18,25 +19,40 @@ import edu.caltech.ipac.util.cache.CacheManager; import edu.caltech.ipac.util.cache.StringKey; import edu.caltech.ipac.visualize.plot.plotdata.FitsRead; +import edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil; import nom.tam.fits.Fits; import nom.tam.fits.FitsException; +import nom.tam.fits.Header; import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static edu.caltech.ipac.visualize.plot.ImageHeader.AIRMASS; +import static edu.caltech.ipac.visualize.plot.ImageHeader.EXPTIME; +import static edu.caltech.ipac.visualize.plot.ImageHeader.EXTINCT; +import static edu.caltech.ipac.visualize.plot.ImageHeader.IMAGEZPT; +import static edu.caltech.ipac.visualize.plot.ImageHeader.ORIGIN; +import static edu.caltech.ipac.visualize.plot.ImageHeader.PALOMAR_ID; import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadFactory.BAD_FORMAT_MSG; +import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.SPOT_BP; +import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.SPOT_HS; +import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.SPOT_OFF; /** * @author Trey Roby */ public class FitsCacher { - private static final Cache memCache= CacheManager.getVisMemCache(); - private static final Cache fileInfoCache= CacheManager.getLocal(); + private static final Cache memCache= CacheManager.getVisMemCache(); + private static final Cache fileInfoCache= CacheManager.getLocal(); private static final Map activeRequest = new ConcurrentHashMap<>(61); private static final Logger.LoggerImpl _log = Logger.getLogger(); + private static final String directFileAccessPrefix= "directFileAccess--"; static FitsDataEval readFits(File fitsFile) throws FitsException, IOException { return readFits(getFileInfoFromCache(fitsFile),null, true, false); @@ -46,7 +62,7 @@ static FitsDataEval readFits(FileInfo fitsFileInfo, WebPlotRequest req, boolean File fitsFile= fitsFileInfo.getFile(); FitsDataEval fitsDataEval= null; - if (useCache) fitsDataEval= getFromCache(fitsFileInfo); + if (useCache) fitsDataEval= getFromLargeMemCache(fitsFileInfo); if (fitsDataEval!=null) return fitsDataEval; // check first without any locking // if we are going to read the file then we might have multiple readers, // we want to lock here to give the second one a chance to get it from cache @@ -54,15 +70,15 @@ static FitsDataEval readFits(FileInfo fitsFileInfo, WebPlotRequest req, boolean try { Object lockKey= activeRequest.computeIfAbsent(fitsFileInfo, k -> new Object()); synchronized (lockKey) { - fitsDataEval= getFromCache(fitsFileInfo); + fitsDataEval= getFromLargeMemCache(fitsFileInfo); if (fitsDataEval!=null) return fitsDataEval; try { - prepareCacheSpace(fitsFileInfo); + prepareLargeMemCacheSpace(fitsFileInfo); long start = System.currentTimeMillis(); fitsDataEval= FitsEvaluation.readAndEvaluate(fitsFile, clearHdu, req); fitsDataEval.addRelatedDataToAllImages(fitsFileInfo.getRelatedData()); - addToCache(fitsFileInfo, fitsDataEval); + addToLargeMemCache(fitsFileInfo, fitsDataEval); // already holding lockKey, don't re-acquire/remove it addFileInfoToCache(fitsFileInfo); logTime(fitsFileInfo, System.currentTimeMillis() - start); return fitsDataEval; @@ -71,7 +87,7 @@ static FitsDataEval readFits(FileInfo fitsFileInfo, WebPlotRequest req, boolean if ( e.getMessage().equals(BAD_FORMAT_MSG) && (dir.equals(ServerContext.getVisCacheDir()) || dir.equals(ServerContext.getUploadDir())) ) { // if in cache or upload dir, rename the file String newF= fitsFile.getAbsolutePath()+"--bad-file"; - fitsFile.renameTo(new File(newF)); + var ignore= fitsFile.renameTo(new File(newF)); throw new FitsException("bad fits file renamed to: "+newF,e); } else { @@ -91,7 +107,7 @@ static FitsDataEval readFits(FileInfo fitsFileInfo, WebPlotRequest req, boolean */ static FitsDataEval loadFits(Fits fits, File cachePath) throws FitsException, IOException { FitsDataEval fitsDataEval= FitsEvaluation.readAndEvaluateFits(fits, cachePath, true, null); - addToCache(new FileInfo(cachePath),fitsDataEval); + addToLargeMemCacheWithLock(new FileInfo(cachePath),fitsDataEval); return fitsDataEval; } @@ -99,13 +115,41 @@ static FitsDataEval loadFits(Fits fits, File cachePath) throws FitsException, IO * add the FitsRead to the cache before the file is written, use the file name only for caching * FitsRead is assumed to be an uncompressed image with no related data */ - public static void addFitsReadToCache(File f, FitsRead fr) { + public static void addFitsReadToLargeMemCache(File f, FitsRead fr) { if (f==null) return; - addToCache(getFileInfoFromCache(f), new FitsDataEval(new FitsRead[]{fr},null)); + addToLargeMemCacheWithLock(getFileInfoFromCache(f), new FitsDataEval(new FitsRead[]{fr},null)); } - private static void addToCache(FileInfo fitsFileInfo, FitsDataEval fitsDataEval) { + /** + * Add fitsDataEval to cache + * entry point for callers that don't already hold the per-file lock + */ + private static void addToLargeMemCacheWithLock(FileInfo fitsFileInfo, FitsDataEval fitsDataEval) { + Object lockKey= activeRequest.computeIfAbsent(fitsFileInfo, k -> new Object()); + try { + synchronized (lockKey) { + addToLargeMemCache(fitsFileInfo, fitsDataEval); + } + } finally { + activeRequest.remove(fitsFileInfo); + } + } + + /** + * Add fitsDataEval to cache + * call directly only if there is already a lock acquired + */ + private static void addToLargeMemCache(FileInfo fitsFileInfo, FitsDataEval fitsDataEval) { memCache.put(fitsFileInfo, fitsDataEval); + FitsRead[] inFrAry= fitsDataEval.getFitReadAry(); + var dfaList= Arrays.stream(inFrAry) + .filter( (fr) -> !fr.isCube() || fr.getPlaneNumber()==1) + .map(FitsCacher::makeDirectFileAccessData).toList(); + fileInfoCache.put(makeDirectFileAccessKey(fitsFileInfo), dfaList); + } + + private static StringKey makeDirectFileAccessKey(FileInfo fi) { + return new StringKey(directFileAccessPrefix+fi.getUniqueString()); } /** @@ -113,12 +157,12 @@ private static void addToCache(FileInfo fitsFileInfo, FitsDataEval fitsDataEval) */ public static void refreshCache(File fitsFile) { FileInfo fitsFileInfo= getFileInfoFromCache(fitsFile); - FitsDataEval fitsDataEval= getFromCache(fitsFileInfo); + FitsDataEval fitsDataEval= getFromLargeMemCache(fitsFileInfo); if (fitsDataEval==null) return; - addToCache(fitsFileInfo,fitsDataEval); + addToLargeMemCacheWithLock(fitsFileInfo,fitsDataEval); } - private static void prepareCacheSpace(FileInfo fitsFileInfo) { + private static void prepareLargeMemCacheSpace(FileInfo fitsFileInfo) { memCache.put(fitsFileInfo, (HasSizeOf) () -> fitsFileInfo.getFile().length()); //force the cache to make space memCache.remove(fitsFileInfo); } @@ -130,9 +174,28 @@ private static void logTime(FileInfo fitsFileInfo, long time) { } static boolean isCached(File fitsFile) { - return getFromCache(getFileInfoFromCache(fitsFile))!=null; + return getFromLargeMemCache(getFileInfoFromCache(fitsFile))!=null; + } + + public static boolean isDirectFileAccessCached(File fitsFile) { + FileInfo fi= getFileInfoFromCache(fitsFile); + return fileInfoCache.get(makeDirectFileAccessKey(fi))!=null; + } + + public static List getDirectFileAccessFromCache(File fitsFile) { + FileInfo fi= getFileInfoFromCache(fitsFile); + return (List)fileInfoCache.get(makeDirectFileAccessKey(fi)); + } + + public static List confirmAndGetDirectFileAccess(File fitsFile) throws IOException { + if (!FitsCacher.isDirectFileAccessCached(fitsFile)) { + FitsCacher.readFits(fitsFile).getFitReadAry(); // forces the fits file to re-read and will put DirectFileAccess in cache + } + var retList= FitsCacher.getDirectFileAccessFromCache(fitsFile); + return retList!=null ? retList : Collections.emptyList(); } + private static FileInfo getFileInfoFromCache(File file) { CacheKey fileName= new StringKey(file.getAbsolutePath()); if (!fileInfoCache.isCached(fileName)) return addFileInfoToCache(new FileInfo(file)); @@ -145,7 +208,7 @@ private static FileInfo addFileInfoToCache(FileInfo fitsFileInfo) { return fitsFileInfo; } - private static FitsDataEval getFromCache(FileInfo key) { + private static FitsDataEval getFromLargeMemCache(FileInfo key) { if (!memCache.isCached(key)) return null; if (memCache.get(key) instanceof FitsDataEval fitsDataInfo) { return fitsDataInfo; @@ -156,9 +219,9 @@ private static FitsDataEval getFromCache(FileInfo key) { } } - static void clearCachedHDU(File fitsFile) { + static void clearLargeMemCachedHDU(File fitsFile) { FileInfo fi= getFileInfoFromCache(fitsFile); - FitsDataEval fitsDataInfo= getFromCache(fi); + FitsDataEval fitsDataInfo= getFromLargeMemCache(fi); if (fitsDataInfo==null) return; boolean needsReinsert= false; for (FitsRead fr : fitsDataInfo.getFitReadAry()) { @@ -169,4 +232,29 @@ static void clearCachedHDU(File fitsFile) { } if (needsReinsert) memCache.put(fi, fitsDataInfo); } + + + private static DirectFileAccess makeDirectFileAccessData(FitsRead fr) { + Header h= fr.getHeader(); + DirectFileAccess.PalomarDirectMod palomar= null; + var origin= h.getStringValue(ORIGIN,""); + if (origin.startsWith(PALOMAR_ID)) { + var expTime= h.getDoubleValue(EXPTIME); + var imageZPt= h.getDoubleValue(IMAGEZPT); + var airMass= h.getDoubleValue(AIRMASS); + var extinct= h.getDoubleValue(EXTINCT); + palomar= new DirectFileAccess.PalomarDirectMod(expTime,imageZPt,airMass,extinct); + } + var dataOffset= h.getLongValue(SPOT_OFF) + h.getLongValue(SPOT_HS,0); + var blankVal= FitsReadUtil.getBlankValue(h); + var blankValStr= Double.isNaN(blankVal) ? "" : blankVal+""; + return new DirectFileAccess( + fr.getHduNumber(), fr.isCube(), FitsReadUtil.getNaxis3(h), -1, dataOffset, h.getIntValue(SPOT_BP), + FitsReadUtil.getNaxis1(h), FitsReadUtil.getNaxis2(h), FitsReadUtil.getNaxis3(h), + FitsReadUtil.getCdelt2(h), FitsReadUtil.getBUnit(h,"---"), + FitsReadUtil.getBscale(h), FitsReadUtil.getBzero(h), + blankValStr, origin, palomar); + } + } + diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FluxValueUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FluxValueUtil.java new file mode 100644 index 000000000..cc31bd0c3 --- /dev/null +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/FluxValueUtil.java @@ -0,0 +1,110 @@ +package edu.caltech.ipac.firefly.server.visualize; + +import edu.caltech.ipac.firefly.server.ServerContext; +import edu.caltech.ipac.firefly.server.servlets.HiPSRetrieve; +import edu.caltech.ipac.firefly.visualize.Band; +import edu.caltech.ipac.firefly.visualize.BandState; +import edu.caltech.ipac.firefly.visualize.DirectFileAccess; +import edu.caltech.ipac.firefly.visualize.PlotState; +import edu.caltech.ipac.util.download.FailedRequestException; +import edu.caltech.ipac.util.download.URLDownload; +import edu.caltech.ipac.visualize.plot.ImagePt; +import edu.caltech.ipac.visualize.plot.PixelValue; +import edu.caltech.ipac.visualize.plot.WorldPt; +import nom.tam.fits.FitsException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +/** + * @author Trey Roby + * + */ +public class FluxValueUtil { + + private static final List unavailable= asResList(PixelValue.Result.makeUnavailable()); + private static final List nan= asResList(new PixelValue.Result(PixelValue.Result.STATUS_NAN,"","","")); + private static final List retrieving= asResList(PixelValue.Result.makeRetrieving()); + + private static List asResList(PixelValue.Result v) { return Collections.singletonList(v); } + + private static boolean isValidCubeMatch(DirectFileAccess dfa, int cubePlane) { + return dfa.cube() && cubePlane >= 0 && cubePlane < dfa.cubeLength(); + } + + public static List getFluxHiPS(String fitsTileUrl, ImagePt pt, WorldPt wpt, int cubePlane) { + if (HiPSRetrieve.isHiPSFileCached(fitsTileUrl)) { + File fitsFile= HiPSRetrieve.getHipsCacheFile(URLDownload.makeURL(fitsTileUrl),null); + try { + List dfaList= FitsCacher.confirmAndGetDirectFileAccess(fitsFile); + if (dfaList.isEmpty() || dfaList.getFirst() == null) return unavailable; + DirectFileAccess dfa= dfaList.getFirst(); + if (dfa.cube() && !isValidCubeMatch(dfa, cubePlane)) return unavailable; + var result= PixelValue.getPixelValue(fitsFile,pt,cubePlane,dfa); + return asResList(result); + } catch (IOException | FitsException e) { + return unavailable; + } + } + else { + if (HiPSRetrieve.isDeadFitsUrl(fitsTileUrl)) { + return HiPSRetrieve.getDeadFitsUrlCode(fitsTileUrl)==HTTP_NOT_FOUND + ? nan + : unavailable; + } + HiPSRetrieve.retrieveHiPSTileInBackground(fitsTileUrl); + return retrieving; + } + } + + public static List getFlux(PlotState[] stateAry, ImagePt ipt) { + if (stateAry == null || stateAry.length == 0) return Collections.emptyList(); + PlotState primState= stateAry[0]; + + // 1. handle primary plot + var bandStateList = Arrays.stream(primState.getBands()).map(primState::get).toList(); + + try { + CtxControl.confirmFiles(stateAry[0]); + } catch (FailedRequestException e) { + return Collections.nCopies(bandStateList.size(), PixelValue.Result.makeUnavailable()); + } + var baseList= getFileFlux(bandStateList, ipt); + if (stateAry.length==1) return baseList; + + // 2. if there are overlays - handle them + List fluxList= new ArrayList<>(baseList); + for(int i=1; (i getFileFlux(List bandStateList, ImagePt ipt) { + return bandStateList.stream() + .map (bandState -> { + File f= ServerContext.convertToFile(bandState.getWorkingFitsFileStr()); + try { + var hduNum= bandState.getHduNumber(); + var planeNum= Math.max(0,bandState.getCubePlaneNumber()); + var dfaList= FitsCacher.confirmAndGetDirectFileAccess(f); + var dfa = dfaList.stream() + .filter(d -> d.hduNumber() == hduNum) + .findFirst() + .orElse(null); + return dfa!=null + ? PixelValue.getPixelValue(f,ipt,planeNum,dfa) + : PixelValue.Result.makeUnavailable(); + } catch (IOException e) { + return PixelValue.Result.makeUnavailable(); + } + }) + .toList(); + } +} diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotBuilder.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotBuilder.java index 1c84d7c09..da12c73f4 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotBuilder.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotBuilder.java @@ -28,6 +28,7 @@ import java.util.Map; import static edu.caltech.ipac.firefly.server.visualize.ProgressStat.PType; +import static edu.caltech.ipac.firefly.server.visualize.WebPlotFactory.makePlottingException; import static edu.caltech.ipac.firefly.visualize.Band.BLUE; import static edu.caltech.ipac.firefly.visualize.Band.GREEN; import static edu.caltech.ipac.firefly.visualize.Band.NO_BAND; @@ -73,7 +74,7 @@ public static PlotInfo create3Color(WebPlotRequest redRequest, if (piAry!=null && piAry.length>0) retval= piAry[0]; return retval; } catch (Exception e) { - throw makeException(e); + throw makePlottingException(e); } } @@ -88,22 +89,10 @@ private static List createList(WebPlotRequest wpr) throws FailedReques Collections.addAll(retList, allPlots.plotInfoAry()); return retList; } catch (Exception e) { - throw makeException(e); + throw makePlottingException(e); } } - private static FailedRequestException makeException(Exception e) { - if (e instanceof FailedRequestException) { - return new FailedRequestException("Could not create plot. " + e.getMessage(), - ((FailedRequestException)e).getDetailMessage()); - } - else if (e instanceof FitsException) { - return new FailedRequestException("Could not create plot. Invalid FITS File format.", e.getMessage()); - } - else { - return new FailedRequestException("Could not create plot.", e.getMessage(), e); - } - } static Results build(Map requestMap, MultiImageAction multiAction, @@ -119,7 +108,6 @@ static Results build(Map requestMap, long readStart = System.currentTimeMillis(); WebPlotRequest firstR = requestMap.values().iterator().next(); var readInfoMap = WebPlotReader.readFiles(fileDataMap, firstR); - PlotServUtils.updateProgress( firstR, PType.CREATING, PlotServUtils.CREATING_MSG); purgeFailedBands(readInfoMap, requestMap); long readElapse = System.currentTimeMillis() - readStart; @@ -162,7 +150,13 @@ private static Map findFiles(Map requestMa WebPlotRequest request = entry.getValue(); FileRetriever retrieve = ImageFileRetrieverFactory.getRetriever(request); if (retrieve != null) { - fitsFiles.put(band, retrieve.getFile(request)); + FileInfo fi= retrieve.getFile(request); + if (fi.isSuccess()) { + fitsFiles.put(band, retrieve.getFile(request)); + } + else { + throw new FailedRequestException(fi.getResponseCodeMsg(),"",fi.getResponseCode(), fi); + } } else { _log.error("failed to find FileRetriever should only be FILE, URL, ALL_SKY, or SERVICE, for band " + band.toString()); } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotCreator.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotCreator.java index dba5c0203..3679d09c5 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotCreator.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ImagePlotCreator.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; -import static edu.caltech.ipac.firefly.server.visualize.ProgressStat.PType; import static edu.caltech.ipac.firefly.visualize.Band.NO_BAND; import static java.util.Collections.emptyMap; @@ -37,8 +36,6 @@ static PlotInfo[] makeAllNoBand(PlotState[] stateAry, WebPlotReader.FileReadInfo var len= readAry.length; var piAry= new PlotInfo[len]; for(int i= 0; (i 3) { - PlotServUtils.updateProgress(req, PType.CREATING, - PlotServUtils.CREATING_MSG + ": " + (cnt + 1) + " of " + totLength); - } else { - PlotServUtils.updateProgress(req, PType.CREATING, PlotServUtils.CREATING_MSG); - } - } - static PlotInfo makeOneImagePerBand3Color(PlotState state, Map readInfoMap) throws FailedRequestException, FitsException, GeomException, IOException { @@ -130,13 +118,13 @@ private static ModFileWriter createBand(PlotState state, throws FitsException, IOException, GeomException { ModFileWriter retval= null; Band band= readInfo.band(); - FitsCacher.clearCachedHDU(readInfo.originalFile()); + FitsCacher.clearLargeMemCachedHDU(readInfo.originalFile()); frGroup.setThreeColorBandIn(readInfo.fitsRead(),band); FitsRead tmpFR= frGroup.getFitsRead(band); if (tmpFR!=readInfo.fitsRead() && readInfo.workingFile()!=null) { // testing to see it the fits read got geomed when the band was added state.setImageIdx(0, band); retval = new ModFileWriter(readInfo.workingFile(),0,tmpFR,readInfo.band()); - FitsCacher.addFitsReadToCache(retval.getTargetFile(), tmpFR); + FitsCacher.addFitsReadToLargeMemCache(retval.getTargetFile(), tmpFR); } return retval; diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotServUtils.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotServUtils.java index c0efd1475..db702ac03 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotServUtils.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotServUtils.java @@ -39,13 +39,13 @@ public class PlotServUtils { public static final String STARTING_READ_MSG = "Retrieving Data"; public static final String READ_PERCENT_MSG = "Retrieving "; public static final String ENDING_READ_MSG = "Loading Data"; - public static final String CREATING_MSG = "Creating Images"; +// public static final String CREATING_MSG = "Creating Images"; public static final String PROCESSING_MSG = "Processing Images"; public static final String PROCESSING_COMPLETED_MSG = "Processing Images Completed"; public static void updateProgress(ProgressStat pStat) { Cache cache= CacheManager.getUserCache(); - CacheKey key= new StringKey(pStat.getId()); + CacheKey key= new StringKey(pStat.getKey()); ProgressStat lastPstat= (ProgressStat) cache.get(key); boolean fireAction= true; if (lastPstat!=null) { @@ -54,17 +54,17 @@ public static void updateProgress(ProgressStat pStat) { } } - if (pStat.getId()!=null) cache.put(key, pStat); + if (pStat.getKey()!=null) cache.put(key, pStat); if (fireAction) { ProgressMessage progMsg= getPlotProgressMessage(pStat); FluxAction a= new FluxAction("ImagePlotCntlr.PlotProgressUpdate"); a.setValue(progMsg.message,"message"); - a.setValue(pStat.getId(),"requestKey"); + a.setValue(pStat.getKey(),"requestKey"); a.setValue(pStat.getType()==ProgressStat.PType.GROUP,"group"); a.setValue(progMsg.done,"done"); - a.setValue( pStat.getPlotId(),"plotId"); + a.setValue( pStat.getId(),"plotId"); ServerEventManager.fireAction(a); } } @@ -281,7 +281,7 @@ static ProgressMessage getPlotProgressMessage(ProgressStat stat) { if (stat.isGroup()) { List keyList = stat.getMemberIDList(); progMessage = (keyList.size() == 1) ? - getSingleStatusMessage(keyList.get(0)) : + getSingleStatusMessage(keyList.getFirst()) : getMultiStatMessage(stat); } else { progMessage = new ProgressMessage(stat.getMessage(), stat.isDone()); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ProgressStat.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ProgressStat.java index b6796e328..75e8dcf47 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ProgressStat.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/ProgressStat.java @@ -19,26 +19,26 @@ public class ProgressStat implements Serializable { public enum PType { DOWNLOADING, READING, CREATING, OTHER, GROUP, SUCCESS, FAIL } private final PType type; - private final String id; + private final String key; private final String message; - private final String plotId; + private final String id; private final List memberIDList; protected ProgressStat() {this(null, null, null, null);} - public ProgressStat(String id, String plotId, PType type, String message) { + public ProgressStat(String key, String id, PType type, String message) { + this.key = key; this.id = id; - this.plotId = plotId; this.message = message; this.memberIDList= null; this.type= type; } - public ProgressStat(List memberIDList, String id) { + public ProgressStat(String key, List memberIDList) { + this.key = key; this.memberIDList = memberIDList; - this.id = id; this.message= ""; - this.plotId= ""; + this.id = ""; this.type= PType.GROUP; } @@ -48,9 +48,9 @@ public ProgressStat(List memberIDList, String id) { public String getMessage() { return message; } - public String getId() { return id; } + public String getKey() { return key; } - public String getPlotId() { return plotId; } + public String getId() { return id; } public List getMemberIDList() { return memberIDList; } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisJsonSerializer.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisJsonSerializer.java index 0014b96ef..203125688 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisJsonSerializer.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisJsonSerializer.java @@ -10,7 +10,6 @@ import edu.caltech.ipac.firefly.visualize.Band; import edu.caltech.ipac.firefly.visualize.BandState; import edu.caltech.ipac.firefly.visualize.CreatorResults; -import edu.caltech.ipac.firefly.visualize.DirectFitsAccessData; import edu.caltech.ipac.firefly.visualize.PlotState; import edu.caltech.ipac.firefly.visualize.WebFitsData; import edu.caltech.ipac.firefly.visualize.WebPlotHeaderInitializer; @@ -31,7 +30,6 @@ import org.json.simple.parser.ParseException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -65,6 +63,7 @@ private static JSONObject createJsonPixelResultObj(PixelValue.Result r) { putStrNotNull(jsonPixelResult,"type",r.type()); putStrNotNull(jsonPixelResult,"valueBase10",r.valueBase10()); putStrNotNull(jsonPixelResult,"valueBase16",r.valueBase16()); + putStrNotNull(jsonPixelResult,"unit",r.unit()); return jsonPixelResult; } @@ -462,7 +461,7 @@ private static BandState deserializeBandState(JSONObject map) { b.setOriginalImageIdx(getInt(map,"originalImageIdx",0)); b.setWebPlotRequest(WebPlotRequest.parse(getStr(map, "plotRequestSerialize"))); b.setRangeValues(RangeValues.parse(getStr(map,"rangeValuesSerialize"))); - b.setDirectFileAccessData(deserializeDirectFileAccess((JSONObject)map.get("directFileAccessData"))); + b.setHduNumber(getInt(map, "hduNumber", 0)); b.setMultiImageFile(getBoolean(map,"multiImageFile")); b.setCubeCnt(getInt(map,"cubeCnt",0)); b.setCubePlaneNumber(getInt(map, "cubePlaneNumber",0)); @@ -472,14 +471,6 @@ private static BandState deserializeBandState(JSONObject map) { return null; } } - - static DirectFitsAccessData deserializeDirectFileAccess(JSONObject map) { - if (map==null) return null; - Map tMap= new HashMap<>(30); - for(Object key : map.keySet()) tMap.put((String)key, map.get(key)+""); - return new DirectFitsAccessData(tMap); - } - private static String getStr(JSONObject j, String key) throws IllegalArgumentException, ClassCastException { return getStr(j,key,false); } @@ -505,6 +496,30 @@ private static int getInt(JSONObject j, String key, int defValue) { } } + private static long getLong(JSONObject j, String key, long defValue) { + try { + Object o= j.get(key); + if (o==null) return defValue; + if (o instanceof Number num) return num.longValue(); + if (!(o instanceof String s)) return defValue; + return Long.parseLong(s); + } catch (Exception e) { + return defValue; + } + } + + private static double getDouble(JSONObject j, String key, double defValue) { + try { + Object o= j.get(key); + if (o==null) return defValue; + if (o instanceof Number num) return num.doubleValue(); + if (!(o instanceof String s)) return defValue; + return Double.parseDouble(s); + } catch (Exception e) { + return defValue; + } + } + private static boolean getBoolean(JSONObject j, String key) throws IllegalArgumentException, ClassCastException { return Objects.requireNonNullElse((Boolean)j.get(key), false); } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java index 8468507bf..8ccd34056 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerOps.java @@ -32,7 +32,6 @@ import edu.caltech.ipac.visualize.plot.CropFile; import edu.caltech.ipac.visualize.plot.Histogram; import edu.caltech.ipac.visualize.plot.ImagePt; -import edu.caltech.ipac.visualize.plot.PixelValue; import edu.caltech.ipac.visualize.plot.plotdata.FitsExtract; import edu.caltech.ipac.visualize.plot.plotdata.FitsRead; import edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil; @@ -97,7 +96,7 @@ public static WebPlotResult create3ColorPlot(WebPlotRequest redR, WebPlotRequest public static List createPlotGroup(List rList, String progressKey) { List keyList= rList.stream().map(WebPlotRequest::getProgressKey).filter(Objects::nonNull).toList(); - PlotServUtils.updateProgress(new ProgressStat(keyList, progressKey)); + PlotServUtils.updateProgress(new ProgressStat(progressKey, keyList)); ExecutorService executor = Executors.newFixedThreadPool(rList.size()); boolean allCompleted = false; @@ -163,35 +162,6 @@ public static List getPointDataAry(PlotState state, ImagePt[] ptAry, int (f) -> FitsExtract.getPointDataAryFromFile(ptAry, plane, f, hduNum, ptSizeX, ptSizeY, ct)); } - public static List getFlux(PlotState[] stateAry, ImagePt ipt) { - PlotState primState= stateAry[0]; - - // 1. handle primary plot - var faHList = Arrays.stream(primState.getBands()).map(primState::getFileAndHeaderInfo).toList(); - - try { - CtxControl.confirmFiles(stateAry[0]); - } catch (FailedRequestException e) { - return faHList.stream().map( f -> PixelValue.Result.makeUnavailable()).toList(); - } - - var baseList= getFileFlux(faHList, ipt); - if (stateAry.length==1) return baseList; - - // 2. if there are overlays - handle them - List fluxList= new ArrayList<>(baseList); - for(int i=1; (i getFileFlux(List fileAndHeader, ImagePt ipt) { - return fileAndHeader.stream() - .map (fap -> PixelValue.getPixelValue(ServerContext.convertToFile(fap.fileName()), ipt, fap.header())) - .toList(); - } private static Semaphore getUserSemaphore() { Cache cache= CacheManager.getSessionCache(); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/WebPlotFactory.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/WebPlotFactory.java index cbb163da5..c41d4606f 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/WebPlotFactory.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/WebPlotFactory.java @@ -103,13 +103,18 @@ private static WebPlotFactoryRet create(Map requestMap, return new WebPlotFactoryRet(wpInit,wpHeader); } catch (Exception e) { PlotServUtils.updateProgress(saveRequest, PType.FAIL, "Failed"); - throw makeException(e); + throw makePlottingException(e); } } - private static FailedRequestException makeException(Exception e) { - if (e instanceof FailedRequestException) return (FailedRequestException)e; - else if (e instanceof FitsException) return new FailedRequestException(e.getMessage(), e.getMessage(), e); + public static FailedRequestException makePlottingException(Exception e) { + if (e instanceof FailedRequestException fe) { + var msg= fe.getResponseCode()>0 + ? String.format("Could not create plot. %s (%d)", fe.getMessage(), fe.getResponseCode()) + : String.format("Could not create plot. %s", fe.getMessage()); + return new FailedRequestException(msg, fe.getDetailMessage(),fe); + } + else if (e instanceof FitsException) return new FailedRequestException("Could not create plot. FITS reading Failed: "+e.getMessage(), e.getMessage(), e); else return new FailedRequestException("Could not create plot.", e.getMessage(), e); } @@ -137,7 +142,7 @@ private static void cleanupAnyCachedHDUs(ImagePlotCreator.PlotInfo[] pInfo) { if (state.getWorkingFitsFileStr(b)!=null) cleanList.add(PlotStateUtil.getWorkingFitsFile(state,b)); } } - cleanList.stream().distinct().forEach(FitsCacher::clearCachedHDU); + cleanList.stream().distinct().forEach(FitsCacher::clearLargeMemCachedHDU); } private static WebPlotHeaderInitializer makeWpHeaderInit(ImagePlotCreator.PlotInfo pInfo) { diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/hips/HiPSListUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/hips/HiPSListUtil.java index 5538ea7fb..9857341d3 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/hips/HiPSListUtil.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/hips/HiPSListUtil.java @@ -3,12 +3,17 @@ import edu.caltech.ipac.firefly.data.FileInfo; import edu.caltech.ipac.firefly.server.servlets.HiPSRetrieve; import edu.caltech.ipac.firefly.server.util.Logger; +import edu.caltech.ipac.table.DataGroup; +import edu.caltech.ipac.table.DataObject; +import edu.caltech.ipac.table.TableUtil; +import edu.caltech.ipac.util.download.URLDownload; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; +import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -21,6 +26,7 @@ public class HiPSListUtil { static final Logger.LoggerImpl _log = Logger.getLogger(); private final static String PROP = HiPSMasterListEntry.PARAMS.PROPERTIES.getKey().toLowerCase(); + static final Logger.LoggerImpl log = Logger.getLogger(); private final static Map defParamMap = new HashMap<>(); @@ -68,8 +74,10 @@ public static List createHiPSListFromUrl(String url, String _log.debug("executing " + source + " url query: " + url); long cTime = System.currentTimeMillis(); + String hipsListName= "hips-list.properties"; + String fileName= childExt!=null? childExt+"-"+hipsListName : hipsListName; - FileInfo listFileInfo = HiPSRetrieve.retrieveHiPSData(url, childExt, false); + FileInfo listFileInfo = HiPSRetrieve.retrieveHiPSData(url, fileName, false); File listFile= listFileInfo.getFile(); if (listFile==null) throw new IOException("Could not retrieve file: "+ listFileInfo.getResponseCode()); @@ -181,7 +189,7 @@ private static void addItemsFromProperties(HiPSMasterListEntry listEntry, Map getAdditionalMOCS(String urlStr, String source) { + try { + if (urlStr==null) return null; + URL url= URLDownload.makeURL(urlStr); + if (url == null) return null; + FileInfo listFileInfo = HiPSRetrieve.retrieveHiPSData(url.toString(), source+"-moc-table.dat", false); + if (listFileInfo.getResponseCode()!=200 && listFileInfo.getResponseCode()!=304) return null; + DataGroup dg= TableUtil.readAnyFormat(listFileInfo.getFile()); + if (dg == null || dg.isEmpty()) return null; + var retList = new ArrayList(); + String baseUrl= null; + if (url.getQuery()==null) { + String s= urlStr.endsWith("/") ? urlStr.substring(0,urlStr.length()-1) : urlStr; + var endIdx= s.lastIndexOf("/"); + baseUrl= endIdx>-1 ? s.substring(0, s.lastIndexOf("/")) : s; + } + + for(var i=0; i getHiPSListData(String[] dataTypes, String sour } public List getAdditionalMOCS(String source) { - try { - URL url= new URI(getMocUrl()).toURL(); - FileInfo listFileInfo = HiPSRetrieve.retrieveHiPSData(url.toString(), source, false); - if (listFileInfo.getResponseCode()!=200 && listFileInfo.getResponseCode()!=304) return null; - DataGroup dg= TableUtil.readAnyFormat(listFileInfo.getFile()); - var retList = new ArrayList(); - - for(var i=0; i getHiPSListData(String[] dataTypes, String source) { @@ -37,57 +27,9 @@ public List getHiPSListData(String[] dataTypes, String sour } public List getAdditionalMOCS(String source) { - try { - if (lsstMocListUrl==null) return null; - URL url= URLDownload.makeURL(lsstMocListUrl); - if (url == null) return null; - FileInfo listFileInfo = HiPSRetrieve.retrieveHiPSData(url.toString(), source, false); - if (listFileInfo.getResponseCode()!=200 && listFileInfo.getResponseCode()!=304) return null; - DataGroup dg= TableUtil.readAnyFormat(listFileInfo.getFile()); - if (dg == null || dg.isEmpty()) return null; - var retList = new ArrayList(); - String baseUrl= null; - if (url.getQuery()==null) { - String s= lsstMocListUrl.endsWith("/") ? lsstMocListUrl.substring(0,lsstMocListUrl.length()-1) : lsstMocListUrl; - var endIdx= s.lastIndexOf("/"); - baseUrl= endIdx>-1 ? s.substring(0, s.lastIndexOf("/")) : s; - } - - for(var i=0; i 0 && StringUtils.getInt(npixNumParts[0], -1) > -1; + } + return false; + } + private static UriRef makeS3UriRef(WebPlotRequest request) { var region= request.getS3Region(); var bucket= request.getS3Bucket(); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/BandState.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/BandState.java index 4a19a98d8..66301de76 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/visualize/BandState.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/visualize/BandState.java @@ -26,11 +26,11 @@ public class BandState implements Serializable { private String plotRequestSerialize = null; // Serialized WebPlotRequest private String rangeValuesSerialize = null; // Serialized RangeValues - private DirectFitsAccessData directFileAccessData; private boolean multiImageFile = false; private boolean tileCompress = false; private int cubeCnt = 0; private int cubePlaneNumber = 0; + private int hduNumber = 0; private transient WebPlotRequest plotRequestTmp = null; private transient RangeValues rangeValues = null; @@ -54,6 +54,9 @@ public void setTileCompress(boolean tCompress) { public int getCubePlaneNumber() { return cubePlaneNumber; } public void setCubePlaneNumber(int cubePlaneNumber) { this.cubePlaneNumber = cubePlaneNumber; } + public int getHduNumber() { return hduNumber; } + public void setHduNumber(int hduNumber) { this.hduNumber = hduNumber; } + public int getCubeCnt() { return cubeCnt; } public void setCubeCnt(int cubeCnt) { this.cubeCnt = cubeCnt; } @@ -102,17 +105,6 @@ public String getRangeValuesSerialized() { return rangeValuesSerialize; } - /** - * this method will make a copy of DirectFitsAccessData. Any changes to the DirectFitsAccessData object - * after the set will not be - * reflected here. - * @param header client fits header object - */ - public void setDirectFileAccessData(DirectFitsAccessData header) { directFileAccessData = header; } - - public FileAndHeaderInfo getFileAndHeaderInfo() { - return new FileAndHeaderInfo(workingFitsFileStr, directFileAccessData); - } public String getWorkingFitsFileStr() { return workingFitsFileStr; } public void setWorkingFitsFileStr(String fileStr) { workingFitsFileStr = fileStr; } @@ -130,14 +122,13 @@ public BandState makeCopy() { b.uploadFileNameStr = this.uploadFileNameStr; b.imageIdx = this.imageIdx; b.originalImageIdx = this.originalImageIdx; - b.plotRequestSerialize = this.plotRequestSerialize; b.rangeValuesSerialize = this.rangeValuesSerialize; - b.directFileAccessData = this.directFileAccessData; b.multiImageFile = this.multiImageFile; b.tileCompress = this.tileCompress; b.cubeCnt = this.cubeCnt; b.cubePlaneNumber = this.cubePlaneNumber; + b.hduNumber = this.hduNumber; b.fileType = this.fileType; return b; } @@ -152,11 +143,11 @@ public String toString() { originalImageIdx +"", plotRequestSerialize, rangeValuesSerialize, - directFileAccessData +"", multiImageFile+"", tileCompress+"", fileType+"", cubeCnt+"", + hduNumber+"", cubePlaneNumber+""); } @@ -168,11 +159,8 @@ public boolean equals(Object o) { ComparisonUtil.equals(uploadFileNameStr, bs.uploadFileNameStr) && ComparisonUtil.equals(plotRequestSerialize, bs.plotRequestSerialize) && ComparisonUtil.equals(rangeValuesSerialize, bs.rangeValuesSerialize) && - ComparisonUtil.equals(directFileAccessData, bs.directFileAccessData) && ComparisonUtil.equals(fileType, bs.fileType) && imageIdx ==bs.imageIdx && originalImageIdx ==bs.originalImageIdx); } - - public record FileAndHeaderInfo(String fileName, DirectFitsAccessData header) { } } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFileAccess.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFileAccess.java new file mode 100644 index 000000000..b8a8b9d35 --- /dev/null +++ b/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFileAccess.java @@ -0,0 +1,15 @@ +package edu.caltech.ipac.firefly.visualize; + +import java.io.Serializable; + +/** + * @author Trey Roby + * + */ +public record DirectFileAccess ( + int hduNumber, boolean cube, int cubeLength, int planeNumber, + long dataOffset, int bitpix, int naxis1, int naxis2, int naxis3, double cdelt2, + String bunit, double bscale, double bzero, String blankValue, String origin, PalomarDirectMod palomar +) implements Serializable { + public record PalomarDirectMod ( double expTime, double imageZPt, double airMass, double extinct) implements Serializable {} +} \ No newline at end of file diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFitsAccessData.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFitsAccessData.java deleted file mode 100644 index c06be4d2c..000000000 --- a/src/firefly/java/edu/caltech/ipac/firefly/visualize/DirectFitsAccessData.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt - */ -package edu.caltech.ipac.firefly.visualize; - -import edu.caltech.ipac.util.StringUtils; -import java.io.Serializable; -import java.util.Map; - - -/** - * @author Trey Roby - */ -public class DirectFitsAccessData implements Serializable { - - private static final String PLANE_NUMBER= "planeNumber"; - private static final String BITPIX= "bitpix"; - private static final String NAXIS= "naxis"; - private static final String NAXIS1= "naxis1"; - private static final String NAXIS2= "naxis2"; - private static final String NAXIS3= "naxis3"; - private static final String CDELT2= "cdelt2"; - private static final String BSCALE= "bscale"; - private static final String BZERO= "bzero"; - private static final String BLANK_VALUE= "blank_value"; - private static final String DATA_OFFSET= "dataOffset"; - private final Map headers; - - public DirectFitsAccessData(Map headers) { this.headers = headers;} - - - public int planeNumber() { return getIntHeader(PLANE_NUMBER,0); } - public int bitpix() { return getIntHeader(BITPIX,0); } - public int naxis1() { return getIntHeader(NAXIS1,0); } - public int naxis2() { return getIntHeader(NAXIS2,0); } - public double cDelt2() { return getDoubleHeader(CDELT2,0.0); } - public double bScale() { return getDoubleHeader(BSCALE,1.0); } - public double bZero() { return getDoubleHeader(BZERO,0.0); } - public String blankValue() { return getStringHeader(BLANK_VALUE,""); } - public long dataOffset() { return getLongHeader(DATA_OFFSET,0L); } - - public int getIntHeader(String key, int defValue) { - if (headers.containsKey(key)) { - try { - return Integer.parseInt(headers.get(key)); - } catch (NumberFormatException ignore) { } - } - return defValue; - } - - public long getLongHeader(String key, long defValue) { - if (headers.containsKey(key)) { - try { - return Long.parseLong(headers.get(key)); - } catch (NumberFormatException ignore) { } - } - return defValue; - } - - public double getDoubleHeader(String key) { return getDoubleHeader(key, 0.0); } - - public double getDoubleHeader(String key, double defValue) { - if (headers.containsKey(key)) { - try { - return StringUtils.parseDouble(headers.get(key)); - } catch (NumberFormatException ignore) { } - } - return defValue; - } - - public String getStringHeader(String key, String defValue) { return headers.getOrDefault(key, defValue); } - public boolean containsKey(String key) { return headers.containsKey(key);} -} diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/PlotState.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/PlotState.java index 0dc2d3bad..0a5d16800 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/visualize/PlotState.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/visualize/PlotState.java @@ -171,10 +171,6 @@ public RangeValues getRangeValues(Band band) { public RangeValues getRangeValues() { return get(firstBand()).getRangeValues(); } - public BandState.FileAndHeaderInfo getFileAndHeaderInfo(Band band) { - return get(band).getFileAndHeaderInfo(); - } - public String getWorkingFitsFileStr(Band band) { return band!=null ? get(band).getWorkingFitsFileStr() : null; } public void setWorkingFitsFileStr(String fileStr, Band band) { get(band).setWorkingFitsFileStr(fileStr); } diff --git a/src/firefly/java/edu/caltech/ipac/util/FitsHDUUtil.java b/src/firefly/java/edu/caltech/ipac/util/FitsHDUUtil.java index e60350681..b7793c760 100644 --- a/src/firefly/java/edu/caltech/ipac/util/FitsHDUUtil.java +++ b/src/firefly/java/edu/caltech/ipac/util/FitsHDUUtil.java @@ -34,7 +34,7 @@ import static edu.caltech.ipac.firefly.core.FileAnalysisReport.Type.Image; import static edu.caltech.ipac.firefly.core.FileAnalysisReport.Type.Table; import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.getAxisCnt; -import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.getNaxisLength; +import static edu.caltech.ipac.visualize.plot.plotdata.FitsReadUtil.getNaxisN; /** @@ -99,11 +99,11 @@ public static FitsAnalysisReport analyze(File infile, FileAnalysisReport.ReportT int axisCnt= (workingAxis != 4) ? workingAxis - : getNaxisLength(header, 4, isCompressed)==1 ? 3 :workingAxis; + : getNaxisN(header, 4, isCompressed)==1 ? 3 :workingAxis; desc.append((axisCnt <= 2) ? " (" : axisCnt == 3 ? " (cube " : " (" + axisCnt + "d "); if (axisCnt>1) { for(int d=1;(d<=axisCnt);d++) { - desc.append(String.format("%d", getNaxisLength(header,d,isCompressed))); + desc.append(String.format("%d", getNaxisN(header,d,isCompressed))); desc.append(d < axisCnt ? " x " : ")"); } if (axisCnt>=4) { @@ -113,7 +113,7 @@ public static FitsAnalysisReport analyze(File infile, FileAnalysisReport.ReportT } } else { - desc.append(String.format("%d x 1)", getNaxisLength(header, 1, isCompressed))); + desc.append(String.format("%d x 1)", getNaxisN(header, 1, isCompressed))); } part.setDesc(desc.toString()); diff --git a/src/firefly/java/edu/caltech/ipac/util/FormatUtil.java b/src/firefly/java/edu/caltech/ipac/util/FormatUtil.java index 335a46de2..43d718f92 100644 --- a/src/firefly/java/edu/caltech/ipac/util/FormatUtil.java +++ b/src/firefly/java/edu/caltech/ipac/util/FormatUtil.java @@ -61,6 +61,7 @@ public enum Format { REGION ("reg", ".reg", "application/region-file"), PNG ("png", ".png", "image/png"), JPEG ("jpeg", ".jpg", "image/jpeg"), + WEBP ("webp", ".webp", "image/webp"), UWS ("uws", ".xml", "application/xml+uws"), PARQUET (DuckDbReadable.Parquet.NAME, "."+DuckDbReadable.Parquet.NAME, "application/vnd.apache.parquet"), ZIP ("zip", ".zip", "application/zip"), @@ -99,6 +100,8 @@ public static Format fromMime(String mime) { case "application/csv" -> CSV; case "application/tsv" -> TSV; case "image/jpg" -> JPEG; + case "image/png" -> PNG; + case "image/webp" -> WEBP; case "application/x-zip-compressed" -> ZIP; case "application/x-gzip" -> GZIP; case "application/tar" -> TAR; @@ -134,6 +137,9 @@ public static MimeDesc getMimeType(String inFile) { // JPEG: FF D8 FF if (magic(hdr, 0, 0xFF,0xD8,0xFF)) return new MimeDesc(JPEG.mime(), "JPEG image"); + // WEBP RIFF, 32 bit int with file size, WEBP + if (magic(hdr, 0, 'R','I','F','F') && magic(hdr, 8, 'W','E','B','P')) + return new MimeDesc(WEBP.mime(), "WEBP image"); // PDF: %PDF if (magic(hdr, 0, '%','P','D','F')) return new MimeDesc(PDF.mime(), "PDF document"); diff --git a/src/firefly/java/edu/caltech/ipac/util/download/BaseNetParams.java b/src/firefly/java/edu/caltech/ipac/util/download/BaseNetParams.java index abf80658a..ba67ae731 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/BaseNetParams.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/BaseNetParams.java @@ -15,11 +15,12 @@ public abstract class BaseNetParams implements NetParams { public String _statusKey; - public String _plotId; + public String id; + public boolean notify= true; - public BaseNetParams(String statusKey, String plotId) { + public BaseNetParams(String statusKey, String id) { _statusKey= statusKey; - _plotId= plotId; + this.id = id; } public abstract String getUniqueString(); @@ -29,15 +30,13 @@ public BaseNetParams(String statusKey, String plotId) { @Override public boolean equals(Object o) { - boolean retval= false; if (this==o) { - retval= true; + return true; } - else if (o!=null && o instanceof BaseNetParams) { - BaseNetParams other= (BaseNetParams)o; - retval= toString().equals(other.toString()); + else if (o instanceof BaseNetParams other) { + return toString().equals(other.toString()); } - return retval; + return false; } @Override @@ -45,8 +44,10 @@ else if (o!=null && o instanceof BaseNetParams) { public String getStatusKey() { return _statusKey; } public void setStatusKey(String statusKey) { _statusKey= statusKey; } - public String getPlotId() { return _plotId; } - public void setPlotId(String plotId) { _plotId= plotId; } + public String getId() { return id; } + public void setId(String plotId) { id = plotId; } + public void setNotify(boolean notify) { this.notify= notify; } + public boolean getNotify() { return notify; } } diff --git a/src/firefly/java/edu/caltech/ipac/util/download/ConcurrentDownload.java b/src/firefly/java/edu/caltech/ipac/util/download/ConcurrentDownload.java index cec141f86..cb87576cc 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/ConcurrentDownload.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/ConcurrentDownload.java @@ -131,6 +131,7 @@ private static FileInfo doMultiThreadedDownload(URL url, File outfile, Map pdList) { } private static void callListener(DownloadListener dl, long transferredBytes, long length) { + if (dl==null) return; String msg; if (length==0) { msg= FileUtil.getSizeAsString(transferredBytes); diff --git a/src/firefly/java/edu/caltech/ipac/util/download/DownloadListener.java b/src/firefly/java/edu/caltech/ipac/util/download/DownloadListener.java index 85854a421..b7edea537 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/DownloadListener.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/DownloadListener.java @@ -11,6 +11,7 @@ */ public interface DownloadListener extends EventListener { void dataDownloading(DownloadEvent ev); + default void downloadDone() {} } diff --git a/src/firefly/java/edu/caltech/ipac/util/download/Downloader.java b/src/firefly/java/edu/caltech/ipac/util/download/Downloader.java index a9cb59c1d..6933c6476 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/Downloader.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/Downloader.java @@ -99,6 +99,9 @@ private void processDownload(Writer writer) throws IOException, FailedRequestExc throw new IOException("No data was downloaded", e); } } + finally { + if (downloadListener!=null) downloadListener.downloadDone(); + } } private void checkSize(long totalRead) throws FailedRequestException { diff --git a/src/firefly/java/edu/caltech/ipac/util/download/RetrieveUtil.java b/src/firefly/java/edu/caltech/ipac/util/download/RetrieveUtil.java index 38e8bb6da..0558184b0 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/RetrieveUtil.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/RetrieveUtil.java @@ -39,11 +39,11 @@ public class RetrieveUtil { * @throws FailedRequestException when request fails */ public static FileInfo downloadCaching(UriRefParams params, DownloadListener dl) throws FailedRequestException { - if (params==null) throw new FailedRequestException("downloadCaching: params is null"); + if (params==null || params.getUriRef()==null) { + throw new FailedRequestException("downloadCaching: params or uri is null"); + } FileInfo fileInfo= FileCacheHelper.getFileInfo(params); if (fileInfo!=null && !params.getCheckForNewer()) return fileInfo; - - try { File fileName= (fileInfo==null) ? FileCacheHelper.makeFile(params.getDownloadDir(), params.getUniqueString()) : fileInfo.getFile(); var ops= URLDownload.Options.def(); diff --git a/src/firefly/java/edu/caltech/ipac/util/download/S3Download.java b/src/firefly/java/edu/caltech/ipac/util/download/S3Download.java index 051e568ac..7c24d4a67 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/S3Download.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/S3Download.java @@ -143,6 +143,9 @@ public static FileInfo getData(S3Ref ref, logFail(e, outfile, ref, code, seconds); throw new FailedRequestException(e.getMessage(),e); } + finally { + if (options.dl()!=null) options.dl().downloadDone(); + } } @@ -174,6 +177,7 @@ private static void makeReq(GetObjectRequest.Builder rBuild, S3Ref ref, File out } private static void callListener(DownloadListener dl, long transferredBytes, long length, boolean complete ) { + if (dl==null) return; String msg; if (length==0) { msg= FileUtil.getSizeAsString(transferredBytes); diff --git a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java index e21bfc040..ac0be581c 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java @@ -155,15 +155,12 @@ public static String firstParamValUsingKeyList(Map> params, return foundKey!=null ? getFirstVal(params, foundKey) : null; } - - - private static int codeFromException(Exception e) { return switch (e) { case SSLException ignored -> 495; - case SocketTimeoutException ignored -> 408; - case UnknownHostException ignored -> 404; - default -> 500; + case SocketTimeoutException ignored -> HttpURLConnection.HTTP_CLIENT_TIMEOUT; + case UnknownHostException ignored -> HttpURLConnection.HTTP_BAD_GATEWAY; + default -> HttpURLConnection.HTTP_INTERNAL_ERROR; }; } diff --git a/src/firefly/java/edu/caltech/ipac/util/download/UriRefParams.java b/src/firefly/java/edu/caltech/ipac/util/download/UriRefParams.java index 56f7d0da4..bde5d9b81 100644 --- a/src/firefly/java/edu/caltech/ipac/util/download/UriRefParams.java +++ b/src/firefly/java/edu/caltech/ipac/util/download/UriRefParams.java @@ -23,8 +23,15 @@ public class UriRefParams extends BaseNetParams { private HttpServiceInput addtlInfo; private boolean expectStaticFile= false; + public UriRefParams(String urlStr) { + this(Collections.singletonList(UriRef.make(urlStr)),null,null); + setStatusKey(urlStr); + } - public UriRefParams(URL url) { this(Collections.singletonList(UriRef.make(url)),null,null); } + public UriRefParams(URL url) { + this(Collections.singletonList(UriRef.make(url)),null,null); + setStatusKey(url.toString()); + } public UriRefParams(UriRef ref, File downloadDir) { this(Collections.singletonList(ref),null,null); diff --git a/src/firefly/java/edu/caltech/ipac/visualize/net/IbeImageGetter.java b/src/firefly/java/edu/caltech/ipac/visualize/net/IbeImageGetter.java index 46e10aca2..fa581d3d9 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/net/IbeImageGetter.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/net/IbeImageGetter.java @@ -133,7 +133,7 @@ else if (params instanceof PtfImageParams) { IbeDataParam dataParam= ibeSource.makeDataParam(dataMap); Map sourceParams= new HashMap<>(); sourceParams.put("ProgressKey", params.getStatusKey()); - sourceParams.put("plotId", params.getPlotId()); + sourceParams.put("plotId", params.getId()); if (!StringUtils.isEmpty(sizeStr) && !sizeStr.equalsIgnoreCase(NULL) && !sizeStr.equalsIgnoreCase(NaN)) { dataParam.setCutout(true, params.getRaJ2000String() + "," + params.getDecJ2000String(), sizeStr); diff --git a/src/firefly/java/edu/caltech/ipac/visualize/net/SloanDssImageParams.java b/src/firefly/java/edu/caltech/ipac/visualize/net/SloanDssImageParams.java index e712e376e..273d4f699 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/net/SloanDssImageParams.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/net/SloanDssImageParams.java @@ -34,7 +34,7 @@ public String toString() { } public SloanDssImageParams makeQueryKey() { - SloanDssImageParams newParam= new SloanDssImageParams(this.getStatusKey(),this.getPlotId()); + SloanDssImageParams newParam= new SloanDssImageParams(this.getStatusKey(),this.getId()); newParam._queryKey= true; newParam.setSizeInDeg(_sizeInDeg); newParam.setBand(_band); diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/ImagePlot.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/ImagePlot.java index e8633f701..2d26e65df 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/ImagePlot.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/ImagePlot.java @@ -21,6 +21,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static edu.caltech.ipac.firefly.visualize.Band.NO_BAND; + /** * This class is now only used for creating PNG file, not for interactive visualization * @author Trey Roby @@ -65,7 +67,6 @@ private ImagePlot(ActiveFitsReadGroup frGroup, boolean threeColor, int initColor configureImage(); } - /** 07/20/15 LZ * Create a ImagePlot with given IndexColorModel */ @@ -82,6 +83,12 @@ public ImagePlot(ActiveFitsReadGroup frGroup, ImageMask[] iMasks) throws FitsEx configureImage(); } + public static ImagePlot makeImagePlot(FitsRead fr) { + var frGroup= new ActiveFitsReadGroup(); + frGroup.setFitsRead(NO_BAND,fr); + return new ImagePlot(frGroup, false); + } + public boolean isUseForMask() { return useForMask; } public PlotGroup getPlotGroup() { return plotGroup; } public void setPlotDesc(String d) { plotDesc = d; } @@ -262,6 +269,11 @@ else if (wpt.getCoordSys().equals(CoordinateSys.PIXEL)) { } } + public static ImagePt getImageCoords(FitsRead fr, WorldPt wpt) throws ProjectionException { + var imWp= ImagePlot.makeImagePlot(fr).getImageCoords(wpt); + return new ImagePt(imWp.getX(),imWp.getY()); + } + public ImageWorkSpacePt getImageWorkSpaceCoords(Point2D pt) throws NoninvertibleTransformException { AffineTransform inverse= plotGroup.getInverseTransform(); @@ -336,7 +348,7 @@ public WorldPt getWorldCoords(Point2D pt) throws NoninvertibleTransformException } - /** + /** * Return a point the represents the passed point with a distance in * World coordinates added to it. * @param pt the x and y coordinate @@ -406,6 +418,17 @@ public WorldPt getWorldCoords( ImageWorkSpacePt ipt, CoordinateSys outputCoordSy return wpt; } + + public WorldPt getWorldCoords(FitsRead fr, ImagePt ipt, CoordinateSys outputCoordSys) { + var plot= ImagePlot.makeImagePlot(fr); + try { + return plot.getWorldCoords(new ImageWorkSpacePt(ipt.getX(), ipt.getY())); + } catch (ProjectionException ignore) { + return null; + } + } + + /** * get the scale (usually in arcseconds) that on image pixel of data * represents. diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/PixelValue.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/PixelValue.java index 9fea787c2..e8dc47276 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/PixelValue.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/PixelValue.java @@ -3,7 +3,8 @@ */ package edu.caltech.ipac.visualize.plot; -import edu.caltech.ipac.firefly.visualize.DirectFitsAccessData; +import edu.caltech.ipac.firefly.visualize.DirectFileAccess; + import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -17,23 +18,23 @@ public class PixelValue { * @param h - the direct file access data, mostly from the fits header * @return - the read result */ - public static Result getPixelValue(File f, ImagePt pt, DirectFitsAccessData h) { + public static Result getPixelValue(File f, ImagePt pt, int planeNumber, DirectFileAccess h) { if (h == null || !f.canRead()) return Result.makeUnavailable(); try (RandomAccessFile raf = new RandomAccessFile(f, "r")) { - if (h.bitpix()==64 && ((h.bZero()==0 && h.bScale()==1))) { - return pixelValLongResult(raf,pt,h); + if (h.bitpix()==64 && ((h.bzero()==0 && h.bscale()==1))) { + return pixelValLongResult(raf,pt,planeNumber, h); } else { - return pixelValDoubleResult(raf,pt,h); + return pixelValDoubleResult(raf,pt,planeNumber, h); } } catch (IOException e) { return Result.makeUnavailable(); } } - private static Result pixelValDoubleResult(RandomAccessFile raf, ImagePt pt, DirectFitsAccessData header) throws IOException { - double v= pixelValDouble(raf,pt,header); + private static Result pixelValDoubleResult(RandomAccessFile raf, ImagePt pt, int planeNumber, DirectFileAccess header) throws IOException { + double v= pixelValDouble(raf,pt,planeNumber, header); int bitpix= header.bitpix(); if (bitpix<64) { @@ -48,11 +49,12 @@ private static Result pixelValDoubleResult(RandomAccessFile raf, ImagePt pt, Dir String status= getValStatus(v,isHeaderInt); double scaledV = scaleValue(v,header); String base10 = isHeaderInt ? Integer.toString((int)scaledV): scaledV+""; - return new Result(status,resultType,base10,base16); + return new Result(status,resultType,base10,base16,header.bunit()); } else { - return !Double.isNaN(v) ? - getLongResult((long)v,Result.STATUS_VALUE) : getLongResult((long)v,Result.STATUS_UNDEFINED); + return !Double.isNaN(v) + ? getLongResult((long)v,Result.STATUS_VALUE,header.bunit()) + : getLongResult((long)v,Result.STATUS_UNDEFINED,header.bunit()); } } @@ -61,15 +63,15 @@ private static String getValStatus(double v, boolean isInt) { else return !Double.isNaN(v) ? Result.STATUS_VALUE : Result.STATUS_NAN; } - private static Result pixelValLongResult(RandomAccessFile raf, ImagePt pt, DirectFitsAccessData header) throws IOException { - long v= pixelValLong(raf, pt, header); + private static Result pixelValLongResult(RandomAccessFile raf, ImagePt pt, int planeNumber, DirectFileAccess header) throws IOException { + long v= pixelValLong(raf, pt, planeNumber, header); String vUnsignedStr= Long.toUnsignedString(v); try { String bV= Long.toUnsignedString(Long.parseUnsignedLong(header.blankValue())); String status= !vUnsignedStr.equals(bV) ? Result.STATUS_VALUE : Result.STATUS_UNDEFINED; - return getLongResult(v,status); + return getLongResult(v,status,header.bunit()); } catch (NumberFormatException ignore) { - return getLongResult(v,Result.STATUS_VALUE); + return getLongResult(v,Result.STATUS_VALUE,header.bunit()); } } @@ -83,31 +85,30 @@ private static String getIntBase16String(int v, int bitpix) { private static String getLongBase16String(long v) { return String.format("0x%016x",v); } - private static Result getLongResult(long v, String status) { - return new Result(status, Result.TYPE_DECIMAL_INT, Long.toString(v,10), getLongBase16String(v)); + private static Result getLongResult(long v, String status, String bunit) { + return new Result(status, Result.TYPE_DECIMAL_INT, Long.toString(v,10), getLongBase16String(v), bunit); } - private static long pixelValLong(RandomAccessFile fits_file, ImagePt pt, DirectFitsAccessData header) throws IOException { - return readValueLong(fits_file, getFitsFilePointer(header,pt)); + private static long pixelValLong(RandomAccessFile fits_file, ImagePt pt, int planeNumber, DirectFileAccess header) throws IOException { + return readValueLong(fits_file, getFitsFilePointer(header,pt, planeNumber)); } - private static long getFitsFilePointer(DirectFitsAccessData h, ImagePt pt) { + private static long getFitsFilePointer(DirectFileAccess h, ImagePt pt, int planeNumber) { int x= (int)pt.getX(); int y= (int)pt.getY(); - double cdelt2 = h.cDelt2(); + double cdelt2 = h.cdelt2(); long naxis1 = h.naxis1(); long naxis2 = h.naxis2(); long data_offset = h.dataOffset(); - int plane_number = h.planeNumber(); int bytesPerPixel= getBytePerPixel(h.bitpix()); long yLong = cdelt2 < 0 ? naxis2 - 1 - y : y; - int plane_offset = plane_number > -1 ? plane_number : 0; + int plane_offset = planeNumber > -1 ? planeNumber : 0; long pixel_offset = (naxis1 * naxis2 * plane_offset) + (yLong * naxis1 + x); return data_offset + pixel_offset * bytesPerPixel; } - private static double pixelValDouble(RandomAccessFile fits_file, ImagePt pt, DirectFitsAccessData header) + private static double pixelValDouble(RandomAccessFile fits_file, ImagePt pt, int planeNumber, DirectFileAccess header) throws IOException{ double blankValueDouble= Double.NaN; try { @@ -115,32 +116,30 @@ private static double pixelValDouble(RandomAccessFile fits_file, ImagePt pt, Dir } catch (NumberFormatException ignore) { } - return readValue(fits_file,getFitsFilePointer(header,pt), header, blankValueDouble); + return readValue(fits_file,getFitsFilePointer(header,pt, planeNumber), header, blankValueDouble); } - private static double scaleValue(double v, DirectFitsAccessData h) { - return !isPalomar(h) ? v * h.bScale() + h.bZero() : convertToPolomar(v,h); + private static double scaleValue(double v, DirectFileAccess h) { + return !isPalomar(h) ? v * h.bscale() + h.bzero() : convertToPolomar(v,h); } - private static double convertToPolomar(double fileValue, DirectFitsAccessData header) { + private static double convertToPolomar(double fileValue, DirectFileAccess header) { // todo- this code should never have been here, but we are stuck with it for now // If this is a Palomar Transient Factory single-epoch FITS image, then // convert pixel values to magnitudes and apply photometric and airMass corrections. // (200x-era request from PTF scientists) // See other uses of PALOMAR_ID elsewhere in Firefly for other pieces of this. - double airMass= header.getDoubleHeader(ImageHeader.AIRMASS); - double extinct= header.getDoubleHeader(ImageHeader.EXTINCT); - double imageZpt= header.getDoubleHeader(ImageHeader.IMAGEZPT); - double expTime= header.getDoubleHeader(ImageHeader.EXPTIME); + var pal= header.palomar(); + var airMass= pal.airMass(); + var extinct= pal.extinct(); + var imageZpt= pal.imageZPt(); + var expTime= pal.expTime(); return !Double.isNaN(fileValue)? -2.5 * .43429 * Math.log(fileValue/ expTime) + imageZpt + extinct * airMass:Double.NaN; } - private static boolean isPalomar(DirectFitsAccessData h) { - // Identify Palomar Transient Factory single-epoch images based on FITS headers - return h.getStringHeader(ImageHeader.ORIGIN,"").startsWith(ImageHeader.PALOMAR_ID) && - h.containsKey(ImageHeader.AIRMASS) && h.containsKey(ImageHeader.EXTINCT) && - h.containsKey(ImageHeader.IMAGEZPT) && h.containsKey(ImageHeader.EXPTIME); + private static boolean isPalomar(DirectFileAccess h) { + return (h.palomar()!=null) && h.origin()!=null && h.origin().startsWith(ImageHeader.PALOMAR_ID); } private static int getBytePerPixel(int bitpix) { @@ -153,17 +152,17 @@ private static int getBytePerPixel(int bitpix) { }; } - private static boolean isInt(DirectFitsAccessData h) { + private static boolean isInt(DirectFileAccess h) { // From FITS 4.00 (https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf), p. 14-15, Table 11 and section 4.4.2.5 - return (h.bitpix()>0 && h.bScale()==1.0 && (h.bZero()==0 + return (h.bitpix()>0 && h.bscale()==1.0 && (h.bzero()==0 // OR if bZero was used as follows for representation of unsigned-integer data - || (h.bitpix()==8 && h.bZero()==-128.0) - || (h.bitpix()==16 && h.bZero()==32768.0) - || (h.bitpix()==32 && h.bZero()==2147483648.0) + || (h.bitpix()==8 && h.bzero()==-128.0) + || (h.bitpix()==16 && h.bzero()==32768.0) + || (h.bitpix()==32 && h.bzero()==2147483648.0) )); } - private static double readValue(RandomAccessFile fits_file, long file_pointer, DirectFitsAccessData h, double blankValueDouble) + private static double readValue(RandomAccessFile fits_file, long file_pointer, DirectFileAccess h, double blankValueDouble) throws IOException{ fits_file.seek(file_pointer); double value= switch (h.bitpix()) { @@ -183,8 +182,12 @@ private static long readValueLong(RandomAccessFile fits_file, long file_pointer) return fits_file.readLong(); } - public record Result(String status, String type, String valueBase10, String valueBase16) { + public record Result(String status, String type, String valueBase10, String valueBase16, String unit) { + public Result(String status, String type, String valueBase10, String valueBase16) { + this (status, type, valueBase10, valueBase16, null); + } public static final String STATUS_UNAVAILABLE= "UNAVAILABLE"; + public static final String STATUS_RETRIEVING= "RETRIEVING"; public static final String STATUS_NAN= "NaN"; public static final String STATUS_UNDEFINED= "UNDEFINED"; public static final String STATUS_VALUE= "VALUE"; @@ -192,6 +195,8 @@ public record Result(String status, String type, String valueBase10, String valu public static final String TYPE_DECIMAL_INT = "DECIMAL_INT"; public static final String TYPE_FLOAT= "FLOAT"; private static final Result unavailable= new Result(STATUS_UNAVAILABLE,TYPE_EMPTY,"",""); + private static final Result retrieving= new Result(STATUS_RETRIEVING,TYPE_EMPTY,"",""); public static Result makeUnavailable() {return unavailable;} + public static Result makeRetrieving() {return retrieving;} }; } diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsExtract.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsExtract.java index d3832bf16..47c5f07c2 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsExtract.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsExtract.java @@ -408,7 +408,7 @@ private static void validateCubeAtHDU(BasicHDU[] hdus, int hduNum) throws Fit String hduNumStr= "HDU #"+hduNum; int nAxis= FitsReadUtil.getNaxis(header); if (nAxis<3) throw new FitsException(hduNumStr + " is not a cube"); - if (nAxis==4 && FitsReadUtil.getNaxisLength(header,4)!=1) throw new FitsException(hduNumStr + " is not a cube, 4 axes"); + if (nAxis==4 && FitsReadUtil.getNaxisN(header,4)!=1) throw new FitsException(hduNumStr + " is not a cube, 4 axes"); } private static void validateImageAtHDU(BasicHDU[] hdus, int hduNum) throws FitsException { diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java index 01209e0a4..7dbb46ab5 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java @@ -99,7 +99,7 @@ private static ImageHDU makeImageHDU(BasicHDU hdu) throws FitsException { public int getNaxis() { return FitsReadUtil.getNaxis(header); } public int getNaxis1() { return FitsReadUtil.getNaxis1(header); } public int getNaxis2() { return FitsReadUtil.getNaxis2(header); } - public int getNaxisLength(int axis) { return FitsReadUtil.getNaxisLength(header, axis); } + public int getNaxisLength(int axis) { return FitsReadUtil.getNaxisN(header, axis); } public String getBUnit() { return this.bunit;} public double getBscale() { return FitsReadUtil.getBscale(header); } public double getBzero() { return FitsReadUtil.getBzero(header); } @@ -122,6 +122,10 @@ public float[] getRawFloatAry() { return float1d; } + public boolean isDataLoaded() { + return float1d!=null && long1d!=null; + } + public long[] getRawLongAry() { if (long1d!=null) return long1d; long1d= (long[])FitsReadUtil.dataArrayFromHDUAndPlane(this.file,this.hduNumber, planeNumber, Long.TYPE); diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsReadUtil.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsReadUtil.java index 2fceb04c8..ac65deab9 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsReadUtil.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsReadUtil.java @@ -22,6 +22,7 @@ import nom.tam.fits.UndefinedData; import nom.tam.fits.UndefinedHDU; import nom.tam.fits.header.Bitpix; +import nom.tam.fits.header.Standard; import nom.tam.image.StandardImageTiler; import nom.tam.image.compression.hdu.CompressedImageHDU; import nom.tam.util.ArrayFuncs; @@ -45,9 +46,8 @@ public class FitsReadUtil { public static final String SPOT_EXT = "SPOT_EXT"; // HDU Number public static final String SPOT_OFF = "SPOT_OFF"; public static final String SPOT_BP = "SPOT_BP"; // original bitpix - public static final String SPOT_PL = "SPOT_PL"; // cube plane number, only used with cubes, deprecated - public static final String EXTNAME= "EXTNAME"; - public static final String EXTTYPE= "EXTTYPE"; + public static final String SPOT_PL = "SPOT_PL"; // cube plane number, only used with cubes, + private static final String EXTTYPE= "EXTTYPE"; private static final String RESTORE_COMMENT= "restored after uncompression by Firefly"; public static ImageData getImageData(BasicHDU refHdu, float[] float1d) { @@ -107,7 +107,7 @@ public static UncompressFitsInfo createdUncompressVersionOfFile(BasicHDU[] HD String extType= getExtType(header,null); ImageHDU iHDU= cHDU.asImageHDU(); var h= iHDU.getHeader(); - if (getExtName(h)==null && extName!=null) h.addValue(EXTNAME,extName, RESTORE_COMMENT); + if (getExtName(h)==null && extName!=null) h.addValue(Standard.EXTNAME.key(),extName, RESTORE_COMMENT); if (getExtType(h,null)==null && extType!=null) h.addValue(EXTTYPE,extType, RESTORE_COMMENT); fits.addHDU(iHDU); } @@ -415,38 +415,36 @@ public static void writeFitsFileForCropOnly(File outfile, FitsRead[] fitsReadAry outputFits.write(outfile); } - public static int getBitPix(Header h) {return h.getIntValue("BITPIX"); } - public static int getNaxis(Header h) { return h.getIntValue("NAXIS", 0); } + public static int getBitPix(Header h) {return h.getIntValue(Standard.BITPIX); } + public static int getNaxis(Header h) { return h.getIntValue(Standard.NAXIS, 0); } public static int getZNaxis(Header h) { return h.getIntValue("ZNAXIS", 0); } - public static int getNaxis1(Header h) { return h.getIntValue("NAXIS1", 0); } - public static int getNaxis2(Header h) { return h.getIntValue("NAXIS2", 0); } - public static int getNaxis3(Header h) { return (getNaxis2(h) > 1) ? h.getIntValue("NAXIS3") : 1; } - - public static int getNaxisLength(Header h, int num) { - if (num<1) return 0; - return h.getIntValue("NAXIS"+num,0); - } + public static int getNaxis1(Header h) { return getNaxisN(h,1); } + public static int getNaxis2(Header h) { return getNaxisN(h,2); } + public static int getNaxis3(Header h) { return (getNaxis2(h) > 1) ? getNaxisN(h,3) : 1; } + public static int getNaxisN(Header h, int num) { return num>0 ? h.getIntValue(Standard.NAXISn.n(num),0) : 0; } + public static double getCdelt2(Header h) { return h.getDoubleValue("CDELT2",0);} public static int getAxisCnt(Header h, boolean compressed) { if (!compressed) getNaxis(h); return getZNaxis(h)==0 ? getNaxis(h) : getZNaxis(h); } - public static int getNaxisLength(Header h, int num, boolean compressed) { - if (!compressed) return getNaxisLength(h, num); - return getZNaxisLength(h,num) > -1 ? getZNaxisLength(h,num) : getNaxisLength(h,num); + public static int getNaxisN(Header h, int num, boolean compressed) { + if (!compressed) return getNaxisN(h, num); + return getZNaxisN(h,num) > -1 ? getZNaxisN(h,num) : getNaxisN(h,num); } - public static int getZNaxisLength(Header h, int axis) { return h.getIntValue("ZNAXIS"+axis,-1); } - public static double getBscale(Header h) { return h.getDoubleValue("BSCALE", 1.0); } - public static double getBzero(Header h) { return h.getDoubleValue("BZERO", 0.0); } + public static int getZNaxisN(Header h, int axis) { return h.getIntValue("ZNAXIS"+axis,-1); } + public static double getBscale(Header h) { return h.getDoubleValue(Standard.BSCALE, 1.0); } + public static double getBzero(Header h) { return h.getDoubleValue(Standard.BZERO, 0.0); } public static double getBlankValue(Header h) { // blank value is only applicable to integer values (BITPIX > 0) - return getBitPix(h) > 0 ? h.getDoubleValue("BLANK", Double.NaN) : Double.NaN; + return getBitPix(h) > 0 ? h.getDoubleValue(Standard.BLANK, Double.NaN) : Double.NaN; } - public static String getBUnit(Header h) { return h.getStringValue("BUNIT", ""); } - public static String getExtName(Header h) { return h.getStringValue(EXTNAME); } + public static String getBUnit(Header h) { return getBUnit(h,""); } + public static String getBUnit(Header h, String def) { return h.getStringValue(Standard.BUNIT, def); } + public static String getExtName(Header h) { return h.getStringValue(Standard.EXTNAME); } public static String getExtType(Header h, String defVal) { return h.getStringValue(EXTTYPE,defVal); } public static String getUtype(Header h) { return h.getStringValue("UTYPE"); } public static String getExtNameOrType(Header h) { return getExtName(h)!=null ? getExtName(h) : getExtType(h,null);} @@ -496,7 +494,7 @@ public static void closeFits(Fits fits) { public static Object dataArrayFromFitsFile(ImageHDU hdu, int x, int y, int width, int height, int plane, Class arrayType) throws IOException { Header header= hdu.getHeader(); int naxis= getNaxis(header); - if (naxis==4 && getNaxisLength(header,4)!=1) throw new IllegalArgumentException("naxis 4 must be only 1 dimension"); + if (naxis==4 && getNaxisN(header,4)!=1) throw new IllegalArgumentException("naxis 4 must be only 1 dimension"); else if (naxis!=2 && naxis!=3 && naxis!=4) throw new IllegalArgumentException("only naxis 2 or 3 or 4 is supported"); int[] loc= null; int[] tileSize= null; diff --git a/src/firefly/js/core/BootstrapRegistry.js b/src/firefly/js/core/BootstrapRegistry.js index 2515837dd..7cdd23f3f 100644 --- a/src/firefly/js/core/BootstrapRegistry.js +++ b/src/firefly/js/core/BootstrapRegistry.js @@ -35,6 +35,7 @@ import ExtractLineTool from '../drawingLayers/ExtractLineTool.js'; import PointSelection from '../drawingLayers/PointSelection.js'; import ExtractPoints from '../drawingLayers/ExtractPointsTool.js'; import SearchSelectTool from '../drawingLayers/SearchSelectTool.js'; +import ExtractHiPSTileTool from '../drawingLayers/ExtractHiPSTileTool'; import StatsPoint from '../drawingLayers/StatsPoint.js'; import NorthUpCompass from '../drawingLayers/NorthUpCompass.js'; import ImageRoot from '../drawingLayers/ImageRoot.js'; @@ -113,7 +114,7 @@ export const getBootstrapRegistry= once(() => { }; const drawLayerFactory= DrawLayerFactory.makeFactory( - FixedMarker, SelectArea,DistanceTool, ExtractLineTool, ExtractPoints, + FixedMarker, SelectArea,DistanceTool, ExtractLineTool, ExtractPoints, ExtractHiPSTileTool, PointSelection, StatsPoint, NorthUpCompass, ImageRoot, SearchTarget, Catalog, HpxCatalog, Artifact, WebGrid, RegionPlot, MarkerTool, FootprintTool, SearchSelectTool, HiPSGrid, HiPSMOC, ImageOutline, ImageLineBasedFootprint); diff --git a/src/firefly/js/data/ServerParams.js b/src/firefly/js/data/ServerParams.js index d6bb72e04..7e35bd7da 100644 --- a/src/firefly/js/data/ServerParams.js +++ b/src/firefly/js/data/ServerParams.js @@ -90,6 +90,7 @@ export const ServerParams = { LSST : 'lsst', ALL : 'all', CDS : 'cds', + IS_HIPS_TILE : 'isHipsTile', HIPS_SOURCES : 'hipsSources', HIPS_LIST_SOURCE: 'hipsListSource', HIPS_LIST_SOURCE_NAME: 'hipsListSourceName', diff --git a/src/firefly/js/drawingLayers/ExtractHiPSTileTool.js b/src/firefly/js/drawingLayers/ExtractHiPSTileTool.js new file mode 100644 index 000000000..4eaa8333a --- /dev/null +++ b/src/firefly/js/drawingLayers/ExtractHiPSTileTool.js @@ -0,0 +1,198 @@ +/* + * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt + */ + + +import {visRoot} from '../api/ApiUtilImage'; +import CsysConverter from '../visualize/CsysConverter'; +import ShapeDataObj from '../visualize/draw/ShapeDataObj'; +import {dispatchForceDrawLayerUpdate} from '../visualize/DrawLayerDispatch'; +import {dispatchAttributeChange} from '../visualize/ImagePlotDispatch'; +import {PlotAttribute} from '../visualize/PlotAttribute'; +import {isDrawLayerVisible, currentP, getCenterOfProjection, refreshP} from '../visualize/PlotViewUtil.js'; +import {getHiPSNorderlevel, getHealpixCellAtNorder} from '../visualize/HiPSUtil.js'; +import FootprintObj from '../visualize/draw/FootprintObj.js'; +import {makeDrawingDef} from '../visualize/draw/DrawingDef.js'; +import DrawLayer, {ColorChangeType} from '../visualize/draw/DrawLayer.js'; +import {makeFactoryDef} from '../visualize/draw/DrawLayerFactory.js'; +import CysConverter from '../visualize/CsysConverter'; +import {getAllPlotViewIdByOverlayLock} from '../visualize/PlotViewUtil'; +import {isDefined} from '../util/WebUtil.js'; +import {changeHiPSProjectionCenterAndType, isHiPSAitoff} from 'firefly/visualize/WebPlot.js'; +import {makeImagePt, pointEquals} from '../visualize/Point'; +import { + ANY_REPLOT, ATTACH_LAYER_TO_PLOT, CHANGE_CENTER_OF_PROJECTION, CHANGE_VISIBILITY, FORCE_DRAW_LAYER_UPDATE, + MODIFY_CUSTOM_FIELD, SELECT_POINT +} from '../visualize/VisConst'; +import {MouseState} from '../visualize/VisMouseSync'; + +const ID= 'EXTRACT_HIPS_FILE_TOOL'; +const TYPE_ID= `${ID}_TYPE`; + + +let lastDownClick= undefined; + +const factoryDef= makeFactoryDef(TYPE_ID,creator,null,getLayerChanges,null,undefined); + +export default {factoryDef, TYPE_ID}; // every draw layer must default export with factoryDef and TYPE_ID + +let idCnt=0; + +function getTargetOrders(plot) { + const maxOrder = Number(plot.hipsProperties?.hips_order); + const {norder} = getHiPSNorderlevel(plot, true); + return { maxOrder, norder}; +} + +function dispatchSelectPoint(mouseStatePayload) { + const {plotId,screenPt,drawLayer}= mouseStatePayload; + if (mouseStatePayload.shiftDown || !drawLayer.drawData.data) return; + let {plot}= currentP(plotId); + if (!plot?.hasFits) return; + const center= getCenterOfProjection(plot); + if (lastDownClick && (!pointEquals(center, lastDownClick?.center) || lastDownClick.plotId!==plotId)) return; + + setTimeout(() => { + plot= refreshP(plot); + if (!plot) return; + const cc= CsysConverter.make(currentP(plotId).plot); + const pt= cc.getWorldCoords(screenPt); + if (!pt) return; + + const {maxOrder,norder}= getTargetOrders(plot); + const orderToUse = plot.hasFitsCube ? maxOrder : norder; + const cell = getHealpixCellAtNorder(orderToUse, pt, plot.dataCoordSys); + const oldCell= plot.attributes[PlotAttribute.ACTIVE_HIPS_CELL] ?? {}; + if (oldCell.ipix!==cell.ipix || oldCell.norder!==cell.norder) { + dispatchAttributeChange( {plotId, + changes: { + [PlotAttribute.ACTIVE_HIPS_CELL]: cell, + [PlotAttribute.ACTIVE_HIPS_NORDER]: orderToUse, + }}); + dispatchForceDrawLayerUpdate(drawLayer.drawLayerId, plotId); + } + },0); +} + + +function saveLastDown(mouseStatePayload) { + const {plotId}= mouseStatePayload; + lastDownClick= {center:getCenterOfProjection(currentP(plotId).plot), plotId}; +} + +function creator(initPayload, presetDefaults) { + + let drawingDef= makeDrawingDef('magenta', {lineWidth:1, size:6} ); + drawingDef= Object.assign(drawingDef,presetDefaults); + + + idCnt++; + + const pairs= { + [MouseState.UP.key]: dispatchSelectPoint, + [MouseState.DOWN.key]: saveLastDown + }; + const actionTypes= [SELECT_POINT]; + + const options= { + hasPerPlotData:true, + isPointData:false, + canUserChangeColor: ColorChangeType.DYNAMIC, + }; + return DrawLayer.makeDrawLayer(`${ID}-${idCnt}`,TYPE_ID, {}, options, drawingDef, actionTypes, pairs); +} + +function getLayerChanges(drawLayer, action) { + switch (action.type) { + case CHANGE_CENTER_OF_PROJECTION: + case ANY_REPLOT: + case FORCE_DRAW_LAYER_UPDATE: + return {drawData:computeDrawData(drawLayer,action)}; + case ATTACH_LAYER_TO_PLOT: + const {plotId} = action.payload; + let {plotIdAry}= action.payload; + + if (!plotIdAry && !plotId) return null; + plotIdAry = plotIdAry ? plotIdAry : [plotId]; + + const title= Object.assign({},drawLayer.title); + plotIdAry.forEach( (id) => title[id]= getTitle()); + + return {title, drawData:computeDrawData(drawLayer,action,) }; + case MODIFY_CUSTOM_FIELD: + return dealWithMods(drawLayer,action); + case CHANGE_VISIBILITY: + if (action.payload.visible) { + return {drawData:computeDrawData(drawLayer,action, true)}; + } + } + return null; +} + + +function getTitle() { + return 'Extract HiPS Tile'; +} + + +function dealWithMods(drawLayer,action) { + // for future use +} + +function computeDrawData(drawLayer,action, isVisible = false) { + const {payload}= action; + const plotIdAry= payload.plotId ? getAllPlotViewIdByOverlayLock(visRoot(), payload.plotId, false, true) : payload.plotIdAry; + if (plotIdAry) { + const drawData= {data: {...drawLayer.drawData.data}}; + const projectionTypeChange= isDefined(payload.fullSky); + + plotIdAry.forEach( (plotId) => { + if (plotId && (isDrawLayerVisible(drawLayer, plotId) || isVisible)) { + drawData.data[plotId] = computeDrawDataForId(plotId, projectionTypeChange); + } else { + drawData.data[plotId] = null; + } + }); + return drawData; + } + else { + return drawLayer.drawData; + } +} + +function computeDrawDataForId(plotId, projectionTypeChange) { + let {plot} = currentP(plotId); + + const cell= plot?.attributes[PlotAttribute.ACTIVE_HIPS_CELL]; + const markedNorder= plot?.attributes[PlotAttribute.ACTIVE_HIPS_NORDER]; + if (!plot?.hasFits || !cell) return undefined; + let aitoff = isHiPSAitoff(plot); + const {maxOrder,norder}= getTargetOrders(plot); + if (isNaN(maxOrder)) return undefined; + + if (projectionTypeChange) { + aitoff = !aitoff; + plot = changeHiPSProjectionCenterAndType(plot, undefined, aitoff); + } + + const cc = CysConverter.make(plot); + const scrCorners = cell.wpCorners.map((corner) => cc.getImageCoords(corner)); + if (scrCorners.some((scrC) => !scrC)) return undefined; + + const s1 = cc.getImageCoords(cell.wpCorners[0]); + const s2 = cc.getImageCoords(cell.wpCorners[2]); + const drawAry = [FootprintObj.make([scrCorners])]; + if (s1 && s2) { + drawAry.push( + ShapeDataObj.makeTextWithOffset( + norder cc.pointInPlot(pt)); - const newPt= makeSelectedPt(screenPt,plotId); - if (newPt) newPtAry.push(newPt); - dispatchAttributeChange( - {plotId, changes:{[PlotAttribute.PT_ARY]:newPtAry} }); - flux.process({type:EXTRACT_POINT, payload:mouseStatePayload} ); - dispatchForceDrawLayerUpdate(drawLayer.drawLayerId, plotId); - } + const {plotId, screenPt, drawLayer, shiftDown} = mouseStatePayload; + if (shiftDown || !drawLayer.drawData.data) return; + const {plot} = currentP(plotId); + const ptAry= plot.attributes[PlotAttribute.PT_ARY] ?? []; + const cc= CsysConverter.make(plot); + const newPtAry= ptAry.filter( (pt) => cc.pointInPlot(pt)); + const newPt= makeSelectedPt(screenPt,plotId); + if (newPt) newPtAry.push(newPt); + dispatchAttributeChange( + {plotId, changes:{[PlotAttribute.PT_ARY]:newPtAry}}); + flux.process({type: EXTRACT_POINT, payload: mouseStatePayload}); + dispatchForceDrawLayerUpdate(drawLayer.drawLayerId, plotId); } diff --git a/src/firefly/js/drawingLayers/PointSelection.js b/src/firefly/js/drawingLayers/PointSelection.js index 05a401574..ab2b449de 100644 --- a/src/firefly/js/drawingLayers/PointSelection.js +++ b/src/firefly/js/drawingLayers/PointSelection.js @@ -98,8 +98,7 @@ function getLayerChanges(drawLayer, action) { function makeSelectedPt(screenPt,plotId) { const cc= CsysConverter.make(currentP(plotId).plot); - const selPt= cc.getWorldCoords(screenPt); //todo put back - return selPt ?? cc.getImageCoords(screenPt); + return cc.getWorldCoords(screenPt) ?? cc.getImageCoords(screenPt); } diff --git a/src/firefly/js/rpc/PlotServicesJson.js b/src/firefly/js/rpc/PlotServicesJson.js index 49bd49862..152c45a5f 100644 --- a/src/firefly/js/rpc/PlotServicesJson.js +++ b/src/firefly/js/rpc/PlotServicesJson.js @@ -67,20 +67,31 @@ export const callGetAreaStatistics= (state, ipt1, ipt2, ipt3, ipt4, areaShape = export function callCrop(stateAry, corner1ImagePt, corner2ImagePt, cropMultiAll) { - const params= makeParamsWithStateAry(stateAry,false, [ - {name:ServerParams.PT1, value: corner1ImagePt.toString()}, - {name:ServerParams.PT2, value: corner2ImagePt.toString()}, - {name:ServerParams.CRO_MULTI_ALL, value: cropMultiAll +''} - ]); + const params= makeParamsWithStateAry(stateAry, { + [ServerParams.PT1]: corner1ImagePt.toString(), + [ServerParams.PT2]: corner2ImagePt.toString(), + [ServerParams.CRO_MULTI_ALL]: cropMultiAll +'' + }); return doJsonRequest(ServerParams.CROP, params, true); } -export function callGetFileFlux(stateAry, pt) { - const params = makeParamsWithStateAry(stateAry,true, - [ {name: [ServerParams.PT], value: pt.toString()}]); +export function callGetFileFlux(stateAry, pt, wpt,isHips, hipsTileUrl, hipsPlane=0) { + + const obj= { + [ServerParams.PT]: pt.toString(), + }; + if (isHips) { + obj[ServerParams.WPT]= wpt.toString(); + obj[ServerParams.IS_HIPS_TILE]= true+''; + obj[ServerParams.URL]= hipsTileUrl; + obj[ServerParams.PLANE]= hipsPlane; + } + + const params= makeParamsWithStateAry(stateAry,obj ); return doJsonRequest(ServerParams.FILE_FLUX_JSON, params,true); } + async function fetchExtraction(plot, inParams, cmd= ServerParams.FITS_EXTRACTION) { const use64Bit= getBixPix(plot)===-64; const params= {...inParams, [ServerParams.EXTRACTION_FLOAT_SIZE]: use64Bit ? 64 : 32}; @@ -163,24 +174,16 @@ export const saveDS9RegionFile= (regionData) => /** * @param stateAry - * @param includeDirectAccessData * @param otherParams * @return {Promise} */ -function makeParamsWithStateAry(stateAry, includeDirectAccessData, otherParams=[]) { - return [ - ...makeStateParamAry(stateAry,includeDirectAccessData), - ...otherParams, - ]; +function makeParamsWithStateAry(stateAry, otherParams={}) { + const stateObj= stateAry.reduce( (obj, s,idx) => { + obj['state'+idx]= s.toJson(); + return obj; + },{} ); + return { ...stateObj, ...otherParams, + }; } -/** - * @param {Array} startAry - * @param {boolean} includeDirectAccessData - * @return {Array} - */ -function makeStateParamAry(startAry, includeDirectAccessData= true) { - return startAry.map( (s,idx) => { - return {name:'state'+idx, value: s.toJson(includeDirectAccessData) }; - } ); -} \ No newline at end of file + diff --git a/src/firefly/js/ui/HiPSImageSelect.jsx b/src/firefly/js/ui/HiPSImageSelect.jsx index 0c59576b9..9600994df 100644 --- a/src/firefly/js/ui/HiPSImageSelect.jsx +++ b/src/firefly/js/ui/HiPSImageSelect.jsx @@ -111,7 +111,7 @@ export function showHiPSSurveysPopup(pv, moc= false) { } else { const hipsUrl = getHipsUrl(); - if (hipsUrl) return; + if (!hipsUrl) return; const plot = pv ? primePlot(pv) : currentP().plot; moc ? createHiPSMocLayer({ diff --git a/src/firefly/js/visualize/BandState.js b/src/firefly/js/visualize/BandState.js index f50ac4032..a6aec74f3 100644 --- a/src/firefly/js/visualize/BandState.js +++ b/src/firefly/js/visualize/BandState.js @@ -17,12 +17,12 @@ import {RangeValues} from './RangeValues.js'; * @prop plotRequest * @prop rangeValuesSerialize * @prop rangeValues - * @prop directFileAccessData * @prop multiImageFile * @prop tileCompress * @prop cubeCnt * @prop fileType - * @prop cubePlaneNumber + * @prop {number} hduNumber + * @prop {number} cubePlaneNumber */ /** @@ -37,11 +37,11 @@ export function makeBandState(plotRequest, rangeValues) { uploadFileNameStr: undefined, imageIdx: 0, originalImageIdx: 0, - directFileAccessData: undefined, multiImageFile: false, tileCompress: false, cubeCnt: 0, cubePlaneNumber: 0, + hduNumber: 0, fileType: undefined, plotRequest : isString(plotRequest) ? WebPlotRequest.parse(plotRequest) : plotRequest, rangeValues: isString(rangeValues) ? RangeValues.parse(rangeValues) : rangeValues, @@ -68,16 +68,15 @@ export function makeBandStateWithJson(bsJson, overridePlotRequest, overrideRV ) bState.tileCompress = Boolean(bsJson.tileCompress); bState.cubeCnt= bsJson.cubeCnt || 0; bState.cubePlaneNumber= bsJson.cubePlaneNumber || 0; - bState.directFileAccessData= bsJson.directFileAccessData; bState.fileType= bsJson.fileType; + bState.hduNumber= bsJson.hduNumber; return bState; } /** * @param {BandState|undefined|null} bs - * @param {boolean} includeDirectAccessData include the directFileAccessData object */ -export function convertBandStateToJSON(bs, includeDirectAccessData= true) { +export function convertBandStateToJSON(bs) { if (!bs || !bs.plotRequest) return undefined; const json= {}; json.workingFitsFileStr= bs.workingFitsFileStr; @@ -89,12 +88,12 @@ export function convertBandStateToJSON(bs, includeDirectAccessData= true) { json.rangeValuesSerialize= bs.rangeValues?.toJSON() ?? undefined; - if (includeDirectAccessData) json.directFileAccessData= bs.directFileAccessData; if (bs.multiImageFile) json.multiImageFile= bs.multiImageFile; if (bs.tileCompress) json.tileCompress = bs.tileCompress; if (bs.cubeCnt) json.cubeCnt= bs.cubeCnt; if (bs.cubePlaneNumber) json.cubePlaneNumber= bs.cubePlaneNumber; if (bs.fileType) json.fileType= bs.fileType; + if (bs.hduNumber) json.hduNumber= bs.hduNumber; return json; } diff --git a/src/firefly/js/visualize/ChangePrime.js b/src/firefly/js/visualize/ChangePrime.js index 0f50d5f4a..1bd27599b 100644 --- a/src/firefly/js/visualize/ChangePrime.js +++ b/src/firefly/js/visualize/ChangePrime.js @@ -2,10 +2,15 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ -import {dispatchProcessScroll, dispatchZoom} from './ImagePlotDispatch'; +import {dispatchChangeHiPS, dispatchChangePrimePlot, dispatchProcessScroll, dispatchZoom} from './ImagePlotDispatch'; +import {matchCubePlanes} from './task/WcsMatchTask'; import {IMAGE_PLOT_KEY, UserZoomTypes, ZOOM_IMAGE} from './VisConst'; -import {hasWCSProjection, getPlotViewById, primePlot} from './PlotViewUtil.js'; -import {getPixScaleArcSec, getScreenPixScaleArcSec} from './WebPlot.js'; +import { + hasWCSProjection, getPlotViewById, primePlot, getCubeLength, operateOnOthersInPositionGroup, isCube, + getCubePlaneIdx +} from './PlotViewUtil.js'; +import {visRoot} from './VisStoreRoots'; +import {getPixScaleArcSec, getScreenPixScaleArcSec, isHiPS, isImage} from './WebPlot.js'; import {getZoomLevelForScale} from './ZoomUtil.js'; import {CysConverter} from './CsysConverter.js'; import {makeScreenPt} from './Point.js'; @@ -46,8 +51,8 @@ function getZoomDecision(oldP,newP) { export function changePrime(rawAction, dispatcher, getState) { const {plotId,primeIdx:newPrimeIdx}= rawAction.payload; - const visRoot= getState()[IMAGE_PLOT_KEY]; - const pv= getPlotViewById(visRoot, plotId); + let visRoot= getState()[IMAGE_PLOT_KEY]; + let pv= getPlotViewById(visRoot, plotId); const {plots,primeIdx}= pv; const oldP= plots[primeIdx]; const newP= plots[newPrimeIdx]; @@ -57,6 +62,13 @@ export function changePrime(rawAction, dispatcher, getState) { const scrollToImagePt= cc.getImageCoords(makeScreenPt(pv.scrollX,pv.scrollY)); dispatcher(rawAction); checkZoom(plotId,oldP, newP, scrollToImagePt,visRoot); + + visRoot= getState()[IMAGE_PLOT_KEY]; + pv= getPlotViewById(visRoot, plotId); + const plot= primePlot(pv); + + if (isCube(plot) && visRoot.positionLock) matchCubePlanes(plotId); + } /** @type actionWatcherCallback */ @@ -71,7 +83,7 @@ function zoomCompleteWatch(action, cancelSelf, {plotId,scrollToImagePt},dispatch function changeScrollToImagePt(visRoot, plotId, scrollToImagePt) { const pv= getPlotViewById(visRoot,plotId); const cc= CysConverter.make(primePlot(pv)); - dispatchProcessScroll({plotId, scrollPt:cc.getScreenCoords(scrollToImagePt)}); + dispatchProcessScroll({plotId, scrollPt:cc.getScreenCoords(scrollToImagePt), updateWcsPrimId:false}); } const addWatcher= (plotId,scrollToImagePt) => dispatchAddActionWatcher( { diff --git a/src/firefly/js/visualize/FitsHeaderUtil.js b/src/firefly/js/visualize/FitsHeaderUtil.js index 6e6750488..f5c9072e8 100644 --- a/src/firefly/js/visualize/FitsHeaderUtil.js +++ b/src/firefly/js/visualize/FitsHeaderUtil.js @@ -25,6 +25,7 @@ export const HdrConst= { BSCALE : 'BSCALE', BUNIT : 'BUNIT', BZERO : 'BZERO', + BLANK : 'BLANK', CRPIX1 : 'CRPIX1', CRPIX2 : 'CRPIX2', CRVAL1 : 'CRVAL1', diff --git a/src/firefly/js/visualize/HiPSUtil.js b/src/firefly/js/visualize/HiPSUtil.js index 392d93087..84b04cabf 100644 --- a/src/firefly/js/visualize/HiPSUtil.js +++ b/src/firefly/js/visualize/HiPSUtil.js @@ -20,11 +20,16 @@ import {getFireflySessionId} from '../Firefly'; import {encodeServerUrl, getRootURL, loadImage} from '../util/WebUtil.js'; import {CoordinateSys} from './CoordSys.js'; import {CysConverter} from './CsysConverter.js'; -import {getFoV, primePlot} from './PlotViewUtil.js'; -import {makeDevicePt, makeWorldPt} from './Point.js'; +import {dispatchPlotImage} from './ImagePlotDispatch'; +import {findViewerWithItemId, getMultiViewRoot} from './MultiViewCntlr'; +import {currentP, getFoV, primePlot} from './PlotViewUtil.js'; +import {makeDevicePt, makeImagePt, makeWorldPt} from './Point.js'; import {makeHiPSProjection} from './projection/Projection'; +import RangeValues, {ABSOLUTE_STR, PERCENTAGE_STR, STRETCH_ASINH, ZSCALE} from './RangeValues'; +import {IMAGE} from './VisConst'; import {computeDistance, convertCelestial, toDegrees, toRadians} from './VisUtil.js'; -import {changeHiPSProjectionCenter, getScreenPixScaleArcSec, isHiPSAitoff} from './WebPlot.js'; +import {changeHiPSProjectionCenter, getScreenPixScaleArcSec, isHiPS, isHiPSAitoff} from './WebPlot.js'; +import WebPlotRequest, {TitleOptions} from './WebPlotRequest'; export const MAX_SUPPORTED_HIPS_LEVEL= ORDER_MAX-2; @@ -97,7 +102,7 @@ export function getTilePixelAngSize(nOrder) { /** * - * @param {WebPlot} plot + * @param {WebPlot|undefined} plot * @param {boolean} [limitToImageDepth] When true, do not return a number that is greater than what this HiPS map * can display. Use hipsProperties.hips_order to determine. * @return {{useAllSky:boolean, norder:number, desiredNorder:number, isMaxOrder:boolean}} norder is the result, useAllSky true when the norder is 2 or 3 but @@ -184,14 +189,43 @@ function getCatalogNOrderForPixArcSecSize(sizeInArcSec) { return norder; } +/** + * @param plot + * @param nOrder + * @param tileNumber + * @return {string|null} + */ export function makeHiPSTileUrl(plot, nOrder, tileNumber) { if (!plot) return null; + return makeHipsUrl(makeHipsTilePath(plot,nOrder,tileNumber), + plot.proxyHips, plot.hipsFromHipsList); +} + +/** + * @param plot + * @param nOrder + * @param tileNumber + * @param [ext] + * @return {string|null} + */ +function makeHipsTilePath(plot,nOrder,tileNumber,ext) { const dir= Math.floor(tileNumber/10000)*10000; - const exts= plot.hipsProperties?.hips_tile_format ?? 'jpg'; + const exts= ext ?? getHiPSTileExt(plot.hipsProperties?.hips_tile_format ?? 'jpg'); const cubeExt= plot.cubeDepth>1 && plot.cubeIdx>0 ? '_'+plot.cubeIdx : ''; const root= plot.hipsUrlRoot.endsWith('/') ? plot.hipsUrlRoot : plot.hipsUrlRoot+'/'; - return makeHipsUrl(`${root}Norder${nOrder}/Dir${dir}/Npix${tileNumber}${cubeExt}.${getHiPSTileExt(exts)}`, - plot.proxyHips, plot.hipsFromHipsList); + return `${root}Norder${nOrder}/Dir${dir}/Npix${tileNumber}${cubeExt}.${exts}`; +} + +export function makeHipsFitsTilePath(plot,nOrder,tileNumber) { + let tileUrl; + if (plot.hasFitsCube && plot.cubeDepth>1) { + tileUrl= makeHipsTilePath(plot,nOrder,tileNumber,'fits'); + tileUrl= tileUrl.replace(/_\d*.fits/,'_cube.fits'); + } + else { + tileUrl= makeHipsTilePath(plot,nOrder,tileNumber,'fits'); + } + return tileUrl; } /** @@ -384,19 +418,48 @@ export function getHealpixCornerTool() { * * @param {WebPlot} plot * @param {WorldPt} wp - * @return {{norder:number, pixel:number}} the pixel if we can go that deep, undefined otherwise + * @return {{norder:number, pixel:number, tilePixel:number, tileCoords:Point, tileImagePt:ImagePt}} the pixel if we can go that deep, undefined otherwise */ export function getHealpixPixel(plot, wp) { - const {norder}= getHiPSNorderlevel(plot, true); - if (norder>MAX_SUPPORTED_HIPS_LEVEL-9) return undefined; + return getHealpixPixelAtNorder(getHiPSNorderlevel(plot, true).norder,wp); +} + +/** + * @param {number} tileNorder + * @param {WorldPt} wp + * @return {{norder:number, pixel:number, tilePixel:number, tileCoords:Point, tileImagePt:ImagePt}} the pixel if we can go that deep, undefined otherwise + */ +export function getHealpixPixelAtNorder(tileNorder, wp) { + if (tileNorder>MAX_SUPPORTED_HIPS_LEVEL-9) return undefined; const polar = radecToPolar(wp.x,wp.y); - const tilePixel= ang2pixNest(polar.theta, polar.phi,2**(norder)); - const pixel= ang2pixNest(polar.theta, polar.phi,2**(norder+9)); + const tilePixel= ang2pixNest(polar.theta, polar.phi,2**(tileNorder)); + const pixel= ang2pixNest(polar.theta, polar.phi,2**(tileNorder+9)); const tileCoords= healpixPixelTo512TileXY(pixel); - return { norder:norder+9, tileNorder: norder, pixel, tilePixel, tileCoords }; + // const tileImagePt= makeImagePt(tileCoords.x,512-tileCoords.y-1); + const tileImagePt= makeImagePt(tileCoords.x,512-tileCoords.y-1); + return { norder:tileNorder+9, tileNorder, pixel, tilePixel, tileCoords, tileImagePt}; } +/** + * + * @param norder + * @param wp + * @param {CoordinateSys} dataCoordSys + * @return {Number} + * @return {{ipix:number, wpCorners:Array., norder:number}} contains the healpix pixel number and a worldPt array of corners + */ +export function getHealpixCellAtNorder(norder, wp, dataCoordSys) { + const dataWp= convertCelestial(wp, dataCoordSys); + const polar = radecToPolar(dataWp.x,dataWp.y); + const ipix= ang2pixNest(polar.theta, polar.phi,2**(norder)); + const nside= 2**norder; + return {...healpixCache.makeCornersForPix(ipix, nside, dataCoordSys),norder}; +} + + + + const twoPos= [256,128,64,32,16,8,4,2,1]; /** @@ -679,4 +742,63 @@ export async function loadImageMultiCall(url) { }); } - +let keyCnt= 0; + +export function extractFitsFromHiPS(plot,nOrder,tileNumber) { + if (!plot.hasFits) return; + + const {min,max}= getHipsDataRange(plot); + const rv= getHipsPixelCutRangeValues(plot); + + const {pv} = currentP(plot.plotId); + const tileUrl= makeHipsFitsTilePath(plot,nOrder,tileNumber); + const wpRequest= WebPlotRequest.makeURIPlotRequest(tileUrl); + const plotId= `hipsExtract-${nOrder}-${tileNumber}-${keyCnt}`; + keyCnt++; + wpRequest.setTitle(`Tile: ${nOrder} / ${tileNumber}`); + wpRequest.setTitleOptions(TitleOptions.NONE); + wpRequest.setPlotId(plotId); + wpRequest.setInitialRangeValues(rv); + wpRequest.setInitialColorTable('0'); + const pvOptions= {}; + pvOptions.userCanDeletePlots= true; + wpRequest.setPlotGroupId(pv.plotGroupId); + + const {viewerId}= findViewerWithItemId(getMultiViewRoot(),plot.plotId, IMAGE) ?? {}; + dispatchPlotImage({ plotId, wpRequest, viewerId, pvOptions, }); +} + +function getHipsDataRange(plot) { + if (!isHiPS(plot) || !plot.hipsProperties?.hips_data_range) return {min:0,max:0}; + const mmStrAry= plot.hipsProperties.hips_data_range.split(' '); + if (mmStrAry.length===2) { + const min= Number(mmStrAry[0]); + const max= Number(mmStrAry[1]); + if (!isNaN(min) && !isNaN(max) && min < max) { + return {min,max}; + } + } + return {min:0,max:0}; +} + + +function getHipsPixelCutRangeValues(plot) { + const {hips_pixel_cut:pixelCut}= plot.hipsProperties ?? {}; + const defaultRv= RangeValues.makeRV({which:ZSCALE, asinhQValue:4.0, algorithm:STRETCH_ASINH}); + if (!pixelCut) return defaultRv; + + const stretchValues= pixelCut.split(' '); + if (stretchValues.length>=2) { + const min= Number(stretchValues[0]); + const max= Number(stretchValues[1]); + if (!isNaN(min) && !isNaN(max) && min < max) { + const algorithm= (stretchValues.length>2 && RangeValues.isAlgorithmSupported(stretchValues[2])) + ? stretchValues[2] + : 'log'; + const rv= RangeValues.makeSimple(ABSOLUTE_STR,min,max,algorithm); + if (algorithm==='asinh') rv.asinhQValue= 1; + return rv; + } + } + return defaultRv; +} diff --git a/src/firefly/js/visualize/ImagePlotCntlr.js b/src/firefly/js/visualize/ImagePlotCntlr.js index 51a5dce15..43a8b774b 100644 --- a/src/firefly/js/visualize/ImagePlotCntlr.js +++ b/src/firefly/js/visualize/ImagePlotCntlr.js @@ -97,7 +97,7 @@ const initState= () => { //-- wcs match parameters positionLock: false, // images are locked together - wcsMatchCenterWP: null, // the point to match to + wcsMatchCenterWP: undefined, // the point to match to wcsMatchType: false, // one of 'Standard', 'Target', 'Pixel', 'PixelCenter', or false mpwWcsPrimId: null, // the plotId others are match to }; @@ -177,6 +177,11 @@ export default { reducers, actionCreators}; //======================================== Action Creators ============================= //======================================== Action Creators ============================= +const changeHipsActionCreator= (rawAction) => + (dispatcher, getState) => { + + }; + /** * @param {Action} rawAction diff --git a/src/firefly/js/visualize/ImagePlotDispatch.js b/src/firefly/js/visualize/ImagePlotDispatch.js index 4503e8a0a..5e5e231ae 100644 --- a/src/firefly/js/visualize/ImagePlotDispatch.js +++ b/src/firefly/js/visualize/ImagePlotDispatch.js @@ -656,10 +656,11 @@ export function dispatchCrop({plotId, imagePt1, imagePt2, cropMultiAll, dispatch * @param {string} p.plotId * @param {Object} p.scrollPt a new point to scroll * @param {Object} [p.disableBoundCheck] + * @param {Object} [p.updateWcsPrimId] * @param {Function} [p.dispatcher] only for special dispatching uses such as remote */ -export function dispatchProcessScroll({plotId, scrollPt, disableBoundCheck = false, dispatcher = flux.process}) { - dispatcher({type: PROCESS_SCROLL, payload: {plotId, scrollPt, disableBoundCheck}}); +export function dispatchProcessScroll({plotId, scrollPt, disableBoundCheck = false, updateWcsPrimId= true, dispatcher = flux.process}) { + dispatcher({type: PROCESS_SCROLL, payload: {plotId, scrollPt, updateWcsPrimId, disableBoundCheck}}); } /** diff --git a/src/firefly/js/visualize/MenuItemKeys.js b/src/firefly/js/visualize/MenuItemKeys.js index ba08ed501..bb7b6390e 100644 --- a/src/firefly/js/visualize/MenuItemKeys.js +++ b/src/firefly/js/visualize/MenuItemKeys.js @@ -39,6 +39,7 @@ export function getDefMenuItemKeys() { extractZAxis: true, extractLine: true, extractPoint: true, + extractHiPSTile: true, extract: true, showImageToolbar: true, hipsSurveyPopup: true, diff --git a/src/firefly/js/visualize/MouseReadoutCntlr.js b/src/firefly/js/visualize/MouseReadoutCntlr.js index 6e867a188..22f14f26a 100644 --- a/src/firefly/js/visualize/MouseReadoutCntlr.js +++ b/src/firefly/js/visualize/MouseReadoutCntlr.js @@ -21,6 +21,7 @@ export const DESC_VAL= 'desc'; export const STATUS_UNAVAILABLE= 'UNAVAILABLE'; +export const STATUS_RETRIEVING= 'RETRIEVING'; export const STATUS_NAN= 'NaN'; export const STATUS_UNDEFINED= 'UNDEFINED'; export const STATUS_VALUE= 'VALUE'; diff --git a/src/firefly/js/visualize/PlotAttribute.js b/src/firefly/js/visualize/PlotAttribute.js index abd973805..6bade20f0 100644 --- a/src/firefly/js/visualize/PlotAttribute.js +++ b/src/firefly/js/visualize/PlotAttribute.js @@ -106,6 +106,11 @@ export const PlotAttribute= { */ ACTIVE_POINT: 'ACTIVE_POINT', + /** * The active hips tile last clicked on */ + ACTIVE_HIPS_CELL: 'ACTIVE_HIPS_CELL', + + /** * The plot norder of the hips tile clicked on */ + ACTIVE_HIPS_NORDER: 'ACTIVE_HIPS_NORDER', /** * if set, must be one of the string values defined by the enum ZoomUtil.FullType diff --git a/src/firefly/js/visualize/PlotState.js b/src/firefly/js/visualize/PlotState.js index 2e53bdd7c..374888190 100644 --- a/src/firefly/js/visualize/PlotState.js +++ b/src/firefly/js/visualize/PlotState.js @@ -186,8 +186,8 @@ export class PlotState { return this.bandStateAry[idx]; } - toJson(includeDirectAccessData= true) { - return JSON.stringify(PlotState.convertToJSON(this, includeDirectAccessData)); + toJson() { + return JSON.stringify(PlotState.convertToJSON(this)); } copy() { @@ -235,9 +235,8 @@ export class PlotState { /** * @summary convert his PlotState to something can be used with JSON.stringify * @param {PlotState} s - * @param {boolean} includeDirectAccessData include the includeDirectAccessData object */ - static convertToJSON(s, includeDirectAccessData= true) { + static convertToJSON(s) { if (!s) return undefined; const json= {}; json.ctxStr=s.ctxStr; @@ -247,7 +246,7 @@ export class PlotState { if (s.threeColor) json.threeColor= true; - json.bandStateAry= s.bandStateAry.map( (bJ) => convertBandStateToJSON(bJ,includeDirectAccessData)); + json.bandStateAry= s.bandStateAry.map( (bJ) => convertBandStateToJSON(bJ)); return json; } diff --git a/src/firefly/js/visualize/PlotViewUtil.js b/src/firefly/js/visualize/PlotViewUtil.js index 11866edc1..c6ccf82b6 100644 --- a/src/firefly/js/visualize/PlotViewUtil.js +++ b/src/firefly/js/visualize/PlotViewUtil.js @@ -114,7 +114,7 @@ export function refreshP(plotOrPv) { export function currentP(plotId) { const vr= visRoot(); const pv= plotId ? getPlotViewById(vr, plotId) : getActivePlotView(vr); - const currentPlotId= plotId ?? pv?.plotId; + const currentPlotId= pv?.plotId; return {pv, plot:primePlot(pv), plotId: currentPlotId , active:isActivePlotView(vr, currentPlotId)}; } @@ -813,14 +813,20 @@ export function getNumberOfCubesInPV(pv) { } /** - * Get the total number of planes in the cube of the plot + * Get the total number of planes in the cube of the plot either image or hips * @param {PlotView|WebPlot} plotOrPv * @return {number} the number of cube planes, 0 if this is not a cube */ -export const getCubePlaneCnt= (plotOrPv) => { +export const getCubeLength= (plotOrPv) => { const plot= isPlotView(plotOrPv) ? primePlot(plotOrPv) : plotOrPv; - if (!isImageCube(plot)) return 0; - return plot.cubeCtx.cubeLength; + if (!plot) return 0; + if (isImage(plot)) { + if (!isCube(plot)) return 0; + return plot.cubeCtx.cubeLength; + } + else { + return (plot.cubeDepth>1) ? plot.cubeDepth : 0; + } }; @@ -868,19 +874,27 @@ export function getHDUIndex(pv, plot= undefined) { export const hasImageCubes = (pv) => getNumberOfCubesInPV(pv)>0; /** - * get the plane index of this plot in cube + * get the plane index of this plot in a cube either hips or image * @param {WebPlot|undefined} plot * @return {number} the plane index, -1 if not in a cube */ -export const getImageCubeIdx = (plot) => plot?.cubeCtx?.cubePlane ?? -1; +export function getCubePlaneIdx(plot) { + if (!plot) return -1; + return isImage(plot) + ? (plot.cubeCtx?.cubePlane ?? -1) + : plot.cubeIdx; +} /** - * plot is plane in a image cube + * plot is plane in a cube either hips or image * @param {WebPlot|undefined} plot * @return {boolean} true if plot is a plane in a cube, otherwise false */ -export const isImageCube = (plot) => getImageCubeIdx(plot) > -1; +export function isCube(plot) { + if (!plot) return false; + return isImage(plot) ? Boolean(plot.cubeCtx) : plot.cubeDepth>1; +} /** * Given a HDU index and optionally a cube index, return the image idx @@ -893,29 +907,29 @@ export function convertHDUIdxToImageIdx(pv, hduIdx, cubeIdx=0) { if (!pv || !isPlotView(pv)) return undefined; if (!isMultiImageFits(pv)) return 0; const plot= primePlot(pv); - if (cubeIdx==='follow' && isImageCube(plot)) { + if (cubeIdx==='follow' && isCube(plot)) { const idx= pv.plots.findIndex((p)=> p===plot); cubeIdx= convertImageIdxToHDU(pv,idx).cubeIdx; } const startIndexes= getHduPlotStartIndexes(pv); if (hduIdx>startIndexes.length-1)return 0; - const cnt= getCubePlaneCnt(pv.plots[startIndexes[hduIdx]]); - return (isImageCube(pv.plots[startIndexes[hduIdx]]) && cubeIdxpv.plots.length-1) return {hduIdx:0, cubeIdx:undefined, isCube:false}; - const isCube= isImageCube(pv.plots[imageIdx]); + const isPlotCube= isCube(pv.plots[imageIdx]); const startIndexes= getHduPlotStartIndexes(pv); const hduIdx=getHDUIndex(pv,pv.plots[imageIdx]); - return {hduIdx, cubeIdx:isCube && imageIdx-startIndexes[hduIdx], isCube}; + return {hduIdx, cubeIdx:isPlotCube && imageIdx-startIndexes[hduIdx], cube:isPlotCube}; } @@ -955,8 +969,8 @@ export const hasPlaneOnlyWLInfo= (plot) => Boolean(plot?.wlData?.hasPlainOnlyCoo */ export function getAllWaveLengthsForCube(pv,imPt) { const plot= primePlot(pv); - if (!plot || !isImageCube(plot) || !hasWLInfo(plot)) return; - const len= getCubePlaneCnt(pv); + if (!plot || !isCube(plot) || !hasWLInfo(plot)) return; + const len= getCubeLength(pv); if (!len) return; return Array(len).fill('').map( (v,i) => getPtWavelength(plot, imPt, i)); } @@ -988,6 +1002,8 @@ const getWlUnitValue= (plot, idx, band= Band.NO_BAND) => hasWLInfo(plot) ? plot.wlDataAry[band.value]?.spectralCoords?.[idx]?.units : ''; +const micronRe= new RegExp('microns|micron|um|micrometers','gi'); + /** * * @param {WebPlot|String} plotOrStr - pass a WebPlot to get the units from and the format or a string that will be formatted @@ -998,7 +1014,7 @@ export function getFormattedWaveLengthUnits(plotOrStr, anyPartOfStr=false) { const MICRON_SYMBOL= WAVELENGTH_UNITS.um.symbol; const uStr= isString(plotOrStr) ? plotOrStr : getWaveLengthUnits(plotOrStr); if (anyPartOfStr) { - return uStr.replace(new RegExp('microns|micron|um|micrometers','gi'),MICRON_SYMBOL); + return uStr.replace(micronRe, MICRON_SYMBOL); } else { const u= uStr.toLowerCase(); @@ -1006,6 +1022,13 @@ export function getFormattedWaveLengthUnits(plotOrStr, anyPartOfStr=false) { } } +export function getWaveLengthUnitsPrecision(plotOrStr) { + if (!plotOrStr) return 4; + const uStr= isString(plotOrStr) ? plotOrStr : getWaveLengthUnits(plotOrStr); + if (!uStr) return 4; + if (uStr.match(micronRe)?.[0]) return 3; + return 4; +} /** * diff --git a/src/firefly/js/visualize/RangeValues.js b/src/firefly/js/visualize/RangeValues.js index 2386b567e..591ae0bc5 100644 --- a/src/firefly/js/visualize/RangeValues.js +++ b/src/firefly/js/visualize/RangeValues.js @@ -28,6 +28,7 @@ export const STRETCH_POWERLAW_GAMMA = 51; const LINEAR_STR= 'linear'; const alStrToConst = { + linear: STRETCH_LINEAR, log : STRETCH_LOG, loglog : STRETCH_LOGLOG, equal : STRETCH_EQUAL, @@ -269,6 +270,10 @@ export class RangeValues { rv.asinhStretch+','+ rv.scalingK; } + + static isAlgorithmSupported(a) { + return Boolean(alStrToConst[a]); + } } diff --git a/src/firefly/js/visualize/VisMouseSync.js b/src/firefly/js/visualize/VisMouseSync.js index 893b5a381..b8c723d49 100644 --- a/src/firefly/js/visualize/VisMouseSync.js +++ b/src/firefly/js/visualize/VisMouseSync.js @@ -125,8 +125,7 @@ export function makeMouseStatePayload(plotId,mouseState,screenPt,screenX,screenY if (isHiPS(plot) && worldPt) { const result= getHealpixPixel(plot,worldPt); if (result) { - payload.healpixPixel= result.pixel; - payload.norder= result.norder; + payload.hipsLocation= result; } } return payload; diff --git a/src/firefly/js/visualize/WebPlot.js b/src/firefly/js/visualize/WebPlot.js index 44a9b33a6..58a268b92 100644 --- a/src/firefly/js/visualize/WebPlot.js +++ b/src/firefly/js/visualize/WebPlot.js @@ -1,19 +1,19 @@ /* * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ +import {getNumberHeader, HdrConst} from './FitsHeaderUtil'; import {ZoomType} from './VisConst'; import {isArray, isBoolean, isEmpty, isNumber, isUndefined} from 'lodash'; -import {isDefined, memorizeLastCall} from '../util/WebUtil'; +import {isDefined, memorizeLastCall, splitByWhiteSpace} from '../util/WebUtil'; import {allBandAry, Band} from './Band'; import CoordinateSys from './CoordSys.js'; import {CysConverter} from './CsysConverter.js'; import PlotState, {makePlotStateShimForHiPS} from './PlotState'; -import {makeImagePt} from './Point'; -import {makeDevicePt, makeScreenPt, makeWorldPt} from './Point.js'; +import {makeImagePt, makeDevicePt, makeScreenPt, makeWorldPt} from './Point.js'; import { HIPS_AITOFF, HIPS_DATA_HEIGHT, HIPS_DATA_WIDTH, makeHiPSProjection, makeProjectionNew, UNRECOGNIZED, UNSPECIFIED } from './projection/Projection.js'; -import {makeDirectFileAccessData, parseSpacialHeaderInfo} from './projection/ProjectionHeaderParser.js'; +import {parseSpacialHeaderInfo} from './projection/ProjectionHeaderParser.js'; import {parseWavelengthHeaderInfo} from './projection/WavelengthHeaderParser.js'; import {findAContrastColor, NO_COLOR_TABLE} from './rawData/ColorTable'; import {convertCelestial} from './VisUtil'; @@ -63,6 +63,7 @@ export const RDConst= { * @prop {String} title - the title * @prop {number} colorTableId * @prop {Object} header + * @prop {RelatedData} relatedData * @prop {number} totalImageHdusInFile * @prop {{cubePlane,cubeHeaderAry}} cubeCtx * @prop {number} cubeIdx @@ -87,7 +88,6 @@ export const RDConst= { * @prop {{width:number, height:number}} viewDim size of viewable area (div size: offsetWidth & offsetHeight) * @prop {Array.} * @prop {Object} attributes - * directFileAccessDataAry - object of parameters to get flux from the FITS file * * @see PlotView */ @@ -500,16 +500,12 @@ export const WebPlot= { allWCSMap['']= projection; - // because of history we keep directFileAccessData in the plot state, however now we compute it on the client - // also- we need to keep a copy in plotState for backward compatibility and in the plot to put in back in the plotState - // when a new one is generated + // that bandState does not come with the hduNumber initialized for(let i= 0; (i<3); i++) { - if (headerAry[i]) plotState.get(i).directFileAccessData= makeDirectFileAccessData(headerAry[i], cubeCtx?.cubePlane ?? -1); + if (headerAry[i]) plotState.get(i).hduNumber= getNumberHeader(headerAry[i],HdrConst.SPOT_EXT,0); } - const directFileAccessDataAry= plotState.bandStateAry.map( (bs) => bs.directFileAccessData); - const imageCoordSys= cubeCtx ? cubeCtx.imageCoordSys : wpInit.imageCoordSys; - let plot= makePlotTemplate(plotId,'image',asOverlay, CoordinateSys.parse(imageCoordSys)); + const plotTemplate= makePlotTemplate(plotId,'image',asOverlay, CoordinateSys.parse(imageCoordSys)); const dataWidth= cubeCtx ? cubeCtx.dataWidth : wpInit.dataWidth; const dataHeight= cubeCtx ? cubeCtx.dataHeight : wpInit.dataHeight; const zf= getInitZoomLevel(viewDim, request0, dataWidth, dataHeight, projection.getPixelScaleDegree()); @@ -517,8 +513,6 @@ export const WebPlot= { // noinspection JSUnresolvedVariable /** @type WebPlot */ const imagePlot= { - tileData : undefined, - relatedData : null, colorTableId, totalImageHdusInFile: wpInit.totalImageHdusInFile ?? 1, header, @@ -534,7 +528,6 @@ export const WebPlot= { allWlMap, dataWidth, dataHeight, - title : '', plotDesc : cubeCtx ? cubeCtx.desc : wpInit.desc, dataDesc : wpInit.dataDesc, webFitsData : isArray(wpInit.fitsData) ? wpInit.fitsData : wpInit.fitsData ? [wpInit.fitsData] : [{}], @@ -544,7 +537,6 @@ export const WebPlot= { attributes, rawData, lastByteRefreshData:Date.now(), - directFileAccessDataAry, dataRequested: false, cubeIdx: cubeCtx?.cubePlane ?? -1, //=== End Mutable ===================== @@ -557,10 +549,14 @@ export const WebPlot= { return newWfd; }); } - plot= {...plot, ...imagePlot}; + /** @type WebPlot */ + const plot= {...plotTemplate, ...imagePlot}; if (relatedData) { plot.relatedData= relatedData.map( (d) => - ({...d,relatedDataId: plotId+relatedIdRoot+d.dataKey+'-'+d.band+'-'+dataWidth+'-'+dataHeight})); + ({...d, + relatedDataId: plotId+relatedIdRoot+d.dataKey+'-'+d.band+'-'+dataWidth+'-'+dataHeight + }) + ); } if ((!cubeCtx || cubeCtx.cubePlane===0) && wlData && wlData.failReason) { @@ -580,7 +576,7 @@ export const WebPlot= { * @param {boolean} proxyHips - if true use the proxy (firefly server) to get the hips tailes * @return {WebPlot} the new WebPlot object for HiPS */ - makeWebPlotDataHIPS(plotId, hipsUrlRoot, wpRequest, hipsProperties, attributes= {}, proxyHips) { + makeWebPlotDataHIPS(plotId, hipsUrlRoot, wpRequest, hipsProperties={}, attributes= {}, proxyHips) { const blank= isBlankHiPSURL(wpRequest.getHipsRootUrl()); const hipsCoordSys= makeHiPSCoordSys(hipsProperties); @@ -588,6 +584,10 @@ export const WebPlot= { const lat= blank ? 0 : Number(hipsProperties.hips_initial_dec) || 0; const projection= makeHiPSProjection(hipsCoordSys, lon,lat, false); const plot= makePlotTemplate(plotId,'hips',false, hipsCoordSys); + const formatAry= splitByWhiteSpace(hipsProperties.hips_tile_format); + const hasFitsCube= splitByWhiteSpace(hipsProperties.custom_tile_format).includes('fits-cube'); + const hasFits= formatAry.includes('fits') || hasFitsCube; + const cubeDepth= Number(hipsProperties?.hips_cube_depth) || 1; const zoomFactor= .0001; const hipsPlot= { @@ -597,6 +597,8 @@ export const WebPlot= { hipsUrlRoot, dataCoordSys : hipsCoordSys, hipsProperties, + hasFits, + hasFitsCube, proxyHips, /// other @@ -611,7 +613,7 @@ export const WebPlot= { dataDesc : hipsProperties.label || 'HiPS', blank, blankColor: 'rgba(55,55,55,1)', - cubeDepth: Number(hipsProperties?.hips_cube_depth) || 1, + cubeDepth, //=== Mutable ===================== screenSize: {width:HIPS_DATA_WIDTH*zoomFactor, height:HIPS_DATA_HEIGHT*zoomFactor}, cubeIdx: Number(hipsProperties?.hips_cube_firstframe) || 0, @@ -649,12 +651,6 @@ export const WebPlot= { //keep the plotState populated with the fitsHeader information, this is only used with get flux calls //todo: i think is could be cached on the server side so we don't need to be send it back and forth - const {bandStateAry}= plotState; - for(let i=0; (i 0 ? parse.getValue('BLANK','NaN') : NaN, // blank value is only applicable to integer values (BITPIX > 0) + naxis1: parse.getIntValue(HdrConst.NAXIS1), + naxis2: parse.getIntValue(HdrConst.NAXIS2), + naxis3: parse.getIntValue(HdrConst.NAXIS3,-1), + cdelt2: parse.getDoubleValue(HdrConst.CDELT2, 0), + bscale: parse.getDoubleValue(HdrConst.BSCALE, 1.0), + bzero: parse.getDoubleValue(HdrConst.BZERO, 0.0), + bunit: parse.getValue(HdrConst.BUNIT, '---'), + blank_value: bitpix > 0 ? parse.getValue(HdrConst.BLANK,'') : '', // blank value is only applicable to integer values (BITPIX > 0) bitpix, }; } @@ -402,7 +403,6 @@ function getFluxUnits(parse, zeroHeader) { function getCoordSys(params) { const {ctype1} = params; - if (!ctype1) return -1; /** @@ -430,53 +430,25 @@ function getCoordSys(params) { } } - - function getJsys(params) { - let jsys; const {radecsys, file_equinox } = params; switch (getCoordSys(params)) { case EQ: - if (radecsys.startsWith('FK4')) jsys = EQUATORIAL_B; - else if (radecsys.startsWith('FK5') || radecsys.startsWith('ICRS')) jsys = EQUATORIAL_J; - else if (file_equinox < 2000.0) jsys = EQUATORIAL_B; - else jsys = EQUATORIAL_J; - break; + if (radecsys.startsWith('FK4')) return EQUATORIAL_B; + else if (radecsys.startsWith('FK5') || radecsys.startsWith('ICRS')) return EQUATORIAL_J; + else if (file_equinox < 2000.0) return EQUATORIAL_B; + else return EQUATORIAL_J; case EC: - if (radecsys.startsWith('FK4')) jsys = ECLIPTIC_B; - else if (radecsys.startsWith('FK5')) jsys = ECLIPTIC_J; - else if (file_equinox < 2000.0) jsys = ECLIPTIC_B; - else jsys = ECLIPTIC_J; - break; + if (radecsys.startsWith('FK4')) return ECLIPTIC_B; + else if (radecsys.startsWith('FK5')) return ECLIPTIC_J; + else if (file_equinox < 2000.0) return ECLIPTIC_B; + else return ECLIPTIC_J; case GA: - jsys = GALACTIC_JSYS; - break; + return GALACTIC_JSYS; case SGAL: - jsys = SUPERGALACTIC_JSYS; - break; + return SUPERGALACTIC_JSYS; default: - jsys = NONCELESTIAL; - } - return jsys; -} - - - - -export function makeDirectFileAccessData(header,cubePlane) { - - const parse= makeHeaderParse(header); - const dataOffset = parse.getIntValue(HdrConst.SPOT_OFF,0)+ parse.getIntValue(HdrConst.SPOT_HS,0); - const miniHeader= {...getBasicHeaderValues(parse), dataOffset, planeNumber:cubePlane>-1?cubePlane:0}; - miniHeader.bitpix= parse.getValue(HdrConst.SPOT_BP); - - if (parse.getValue(ORIGIN,'').startsWith(PALOMAR_ID)) { - miniHeader[ORIGIN]= header[ORIGIN]; - miniHeader[EXPTIME]= header[EXPTIME]; - miniHeader[IMAGEZPT]= header[IMAGEZPT]; - miniHeader[AIRMASS]= header[AIRMASS]; - miniHeader[EXTINCT]= header[EXTINCT]; + return NONCELESTIAL; } - return miniHeader; } diff --git a/src/firefly/js/visualize/reducer/HandlePlotChange.js b/src/firefly/js/visualize/reducer/HandlePlotChange.js index 230cb8d5d..b7913e495 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotChange.js +++ b/src/firefly/js/visualize/reducer/HandlePlotChange.js @@ -32,7 +32,7 @@ import { primePlot, clonePvAry, clonePvAryWithPv, applyToOnePvOrAll, applyToOnePvOrOverlayGroup, matchPlotViewByPositionGroup, getPlotViewIdxById, getPlotGroupIdxById, findPlotGroup, getPlotViewById, findCurrentCenterPoint, getCenterOfProjection, - isRotationMatching, hasWCSProjection, isThreeColor, getHDU, getMatchingRotationAngle, isImageCube, + isRotationMatching, hasWCSProjection, isThreeColor, getHDU, getMatchingRotationAngle, isCube, convertImageIdxToHDU, hasLocalStretchByteData } from '../PlotViewUtil.js'; import Point, {parseAnyPt, makeImagePt, makeWorldPt, makeDevicePt} from '../Point.js'; @@ -430,14 +430,14 @@ function changeHipsCoordinateSys(plotViewAry, pv, coordSys, applyToGroup) { function processScroll(state,action) { - const {plotId,scrollPt}= action.payload; + const {plotId,scrollPt,updateWcsPrimId=true}= action.payload; const {plotGroupAry, wcsMatchType}= state; let {plotViewAry, mpwWcsPrimId}= state; plotViewAry= updatePlotGroupScrollXY(state,plotId,plotViewAry, plotGroupAry,scrollPt); - if (wcsMatchType) mpwWcsPrimId= plotId; + if (wcsMatchType && updateWcsPrimId) mpwWcsPrimId= plotId; - return Object.assign({},state,{plotViewAry, mpwWcsPrimId}); + return {...state,plotViewAry, mpwWcsPrimId}; } @@ -718,7 +718,7 @@ function makeNewPrimePlot(state,action) { if (!existingPv || isEmpty(existingPv.plots) || existingPv.plots.length<=primeIdx) return state; const pv= changePrimePlot(existingPv, primeIdx); - if (isImageCube(primePlot(pv))) { + if (isCube(primePlot(pv))) { const primeIdx= convertImageIdxToHDU(pv,pv.primeIdx).cubeIdx; pv.overlayPlotViews= pv.overlayPlotViews.map( (oPv) => { if (!oPv.cube || !oPv.plot || !oPv.plots.length) return oPv; @@ -833,13 +833,14 @@ function requestLocalData(state, action) { } function updatePlotProgress(state,action) { - const {plotId, message:plottingStatusMsg, done, requestKey, callSuccess=true, allowBackwardUpdates= false}= action.payload; + const {plotId, message:plottingStatusMsg, done, callSuccess=true, allowBackwardUpdates= false}= action.payload; const plotView= getPlotViewById(state,plotId); const plot= primePlot(plotView); // validate the update if (!plotView) return state; - if (requestKey!==plotView.request.getRequestKey()) return state; + + if (!matchRequest(action,plotView)) return state; if (plotView.plottingStatusMsg===plottingStatusMsg) return state; const tileDataLoading= isImage(plot) && !plot?.tileData && !hasLocalStretchByteData(plot); @@ -852,6 +853,11 @@ function updatePlotProgress(state,action) { return {...state,plotViewAry:clonePvAry(state,plotId, {plottingStatusMsg,serverCall})}; } +function matchRequest(action, plotView) { + const {payload={}}= action ?? {}; + return (payload.requestKey===plotView?.request.getRequestKey()); +} + function changeVisibility(state,action) { const {plotId, imageOverlayId, visible}= action.payload; if (imageOverlayId) { diff --git a/src/firefly/js/visualize/reducer/HandlePlotCreation.js b/src/firefly/js/visualize/reducer/HandlePlotCreation.js index fe89405f7..02470861f 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotCreation.js +++ b/src/firefly/js/visualize/reducer/HandlePlotCreation.js @@ -15,7 +15,7 @@ import { import {makeOverlayPlotView, initOverlayPlots} from './OverlayPlotView.js'; import { primePlot, getPlotViewById, clonePvAry, getOverlayById, getPlotViewIdListByPositionLock, - getCubePlaneCnt, getHDU, getImageCubeIdx + getCubeLength, getHDU, getCubePlaneIdx } from '../PlotViewUtil.js'; import {getPlotGroupById, makePlotGroup} from '../PlotGroup.js'; import {PlotAttribute} from '../PlotAttribute.js'; @@ -166,8 +166,8 @@ function addHiPS(state,action, setActive= true, newPlot= true) { function countCubes(pv) { if (!pv || !isImage(primePlot(pv)) ) return 0; return pv.plots.reduce( (total, p, idx) => { - if (idx===0) return getImageCubeIdx(p)>=0 ? 1 : 0; - return ( getHDU(p)!==getHDU(pv.plots[idx-1]) && getImageCubeIdx(p)>-1) ? total+1 : total; + if (idx===0) return getCubePlaneIdx(p)>=0 ? 1 : 0; + return ( getHDU(p)!==getHDU(pv.plots[idx-1]) && getCubePlaneIdx(p)>-1) ? total+1 : total; }, 0); } @@ -190,12 +190,12 @@ function addPlot(state,action, setActive, newPlot) { pv.plotViewCtx.multiHdu= hduCnt.length>1; pv.plotViewCtx.cubeCnt= countCubes(pv); pv.plotViewCtx.hduPlotStartIndexes= pv.plotViewCtx.multiHdu ? - pv.plots.map( (p,idx) => idx).filter( (idx) => getImageCubeIdx(pv.plots[idx])<1) : [0]; + pv.plots.map( (p,idx) => idx).filter( (idx) => getCubePlaneIdx(pv.plots[idx])<1) : [0]; if (pv.plotViewCtx.cubeCnt>0) { const firstCubePlotIdx= pv.plots.findIndex( (p) => p.cubeIdx>-1); - const cnt= getCubePlaneCnt(pv.plots[firstCubePlotIdx]); + const cnt= getCubeLength(pv.plots[firstCubePlotIdx]); const frameIdx= getFirstFrameFromAttribute(pv,cnt); if (frameIdx>0) { diff --git a/src/firefly/js/visualize/saga/MouseReadoutWatch.js b/src/firefly/js/visualize/saga/MouseReadoutWatch.js index cc389f79a..cf47d45e3 100644 --- a/src/firefly/js/visualize/saga/MouseReadoutWatch.js +++ b/src/firefly/js/visualize/saga/MouseReadoutWatch.js @@ -11,15 +11,18 @@ import { } from '../MouseReadoutCntlr.js'; import {callGetFileFlux} from '../../rpc/PlotServicesJson.js'; import {allBandAry, Band} from '../Band.js'; +import {makeImagePt} from '../Point'; import {MouseState} from '../VisMouseSync.js'; import CsysConverter, {CysConverter} from '../CsysConverter.js'; import {getPixScale, getScreenPixScale, getScreenPixScaleArcSec, isImage, isHiPS, getFluxUnits} from '../WebPlot.js'; -import {getPlotTilePixelAngSize} from '../HiPSUtil.js'; +import { + getHealpixCellAtNorder, getHealpixPixelAtNorder, getPlotTilePixelAngSize, makeHipsFitsTilePath +} from '../HiPSUtil.js'; import {mouseUpdatePromise, fireMouseReadoutChange} from '../VisMouseSync'; import { - primePlot, getPlotStateAry, getImageCubeIdx, + primePlot, getPlotStateAry, getCubePlaneIdx, getWavelengthParseFailReason, getWaveLengthUnits, hasPixelLevelWLInfo, hasPlaneOnlyWLInfo, - isImageCube, wavelengthInfoParsedSuccessfully, getPtSpectralCoords, getBandWidthUnits, isThreeColor, currentP, + isCube, wavelengthInfoParsedSuccessfully, getPtSpectralCoords, getBandWidthUnits, isThreeColor, currentP, } from '../PlotViewUtil'; import {getFluxRadix} from 'firefly/visualize/ui/MouseReadoutUIUtil'; @@ -30,7 +33,7 @@ const igoreEvTypes= ['touchend']; /** - * Readout watcher defined the algorythm to drive the mouse readout. It does the following: + * Readout watcher defined the algorithm to drive the mouse readout. It does the following: * - waits for a promise of a mouse event * - Has two modes lockByClick, on or off * - lockByClick off: @@ -57,7 +60,7 @@ export function* watchReadout() { let getNextWithWithAsync= false; const lockByClick= isLockByClick(readoutRoot()); let {worldPt,screenPt,imagePt}= mouseCtx; - const {plotId,mouseState, healpixPixel, norder, shiftDown,eventType}= mouseCtx; + const {plotId,mouseState, shiftDown,eventType, hipsLocation}= mouseCtx; const useEv= !igoreEvTypes.includes(eventType); if (!useEv) { @@ -79,7 +82,7 @@ export function* watchReadout() { if (isPayloadNeeded(mouseState,lockByClick)) { if (plot) { - const readoutItems= makeImmediateReadout(plot, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder); + const readoutItems= makeImmediateReadout(plot, worldPt, screenPt, imagePt, threeColor, hipsLocation ); fireMouseReadoutChange({plotId, readoutItems, threeColor, readoutType:getReadoutKey(plot)}); getNextWithWithAsync= hasAsyncReadout(plot); } @@ -91,8 +94,8 @@ export function* watchReadout() { if (useEv && getNextWithWithAsync) { // get the next mouse event or the flux mouseCtx= lockByClick || eventType=== 'touchstart' ? - yield call(processAsyncDataImmediate,plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) : - yield call(processAsyncDataDelayed,plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder); + yield call(processAsyncDataImmediate,plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) : + yield call(processAsyncDataDelayed,plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation); } else { // get the next mouse event mouseCtx = yield call(mouseUpdatePromise); @@ -101,9 +104,9 @@ export function* watchReadout() { } } -function* processAsyncDataImmediate(plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) { +function* processAsyncDataImmediate(plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) { try { - const readoutItems= yield call(makeAsyncReadout,plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder); + const readoutItems= yield call(makeAsyncReadout,plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation); if (readoutItems) { const plot= primePlot(plotView); // dispatchReadoutData({plotId:plotView.plotId,readoutItems, threeColor, readoutKey:getReadoutKey(plot)}); @@ -120,7 +123,7 @@ function* processAsyncDataImmediate(plotView, worldPt, screenPt, imagePt, threeC } -function* processAsyncDataDelayed(plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) { +function* processAsyncDataDelayed(plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) { const plot= primePlot(plotView); const mousePausedRaceWinner = yield race({ mouseCtx: call(mouseUpdatePromise), timer: call(delay, PAUSE_DELAY) }); @@ -129,7 +132,7 @@ function* processAsyncDataDelayed(plotView, worldPt, screenPt, imagePt, threeCol try { const mouseMoveRaceWinner = yield race({ mouseCtx: call(mouseUpdatePromise), - readoutItems: call(makeAsyncReadout,plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) + readoutItems: call(makeAsyncReadout,plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) }); if (mouseMoveRaceWinner.mouseCtx) return mouseMoveRaceWinner.mouseCtx; @@ -165,14 +168,14 @@ function isPayloadNeeded(mouseState, lockByClick) { const getReadoutType= (plot) => readoutTypes.find( (r) => r.matches(plot)); -function makeImmediateReadout(plot,worldPt,screenPt,imagePt, threeColor, healpixPixel, norder) { +function makeImmediateReadout(plot,worldPt,screenPt,imagePt, threeColor, hipsLocation) { const rt= getReadoutType(plot); - return rt && rt.createImmediateReadout(plot,worldPt,screenPt,imagePt, threeColor, healpixPixel, norder); + return rt && rt.createImmediateReadout(plot,worldPt,screenPt,imagePt, threeColor, hipsLocation); } -function makeAsyncReadout(plotView,worldPt,screenPt,imagePt, threeColor) { +function makeAsyncReadout(plotView,worldPt,screenPt,imagePt, threeColor, hipsLocation) { const rt= getReadoutType(primePlot(plotView)); - return Promise.resolve(rt && rt.createAsyncReadout(plotView,worldPt,screenPt,imagePt, threeColor)); + return Promise.resolve(rt && rt.createAsyncReadout(plotView,worldPt,screenPt,imagePt, threeColor, hipsLocation)); } function hasAsyncReadout(plot) { @@ -236,8 +239,8 @@ const readoutTypes= [ readoutKey: HIPS_STANDARD_READOUT, matches: (plot) => isHiPS(plot), createImmediateReadout: makeHiPSReadout, - createAsyncReadout: () => {throw Error('HiPS should not do async');}, - hasAsyncReadout: (plot) => false, + createAsyncReadout: makeHiPSPlotAsyncReadout, + hasAsyncReadout: (plot) => plot.hasFits, }, ]; @@ -253,10 +256,10 @@ function makeImagePlotImmediateReadout(plot, worldPt, screenPt, imagePt, threeCo return makeReadoutWithFlux(makeReadout(plot,worldPt,screenPt,imagePt), plot, null, 10, threeColor); } -function makeImagePlotAsyncReadout(plotView, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) { +function makeImagePlotAsyncReadout(plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) { const plot= primePlot(plotView); - const readoutItems= makeImmediateReadout(plot, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder); + const readoutItems= makeImmediateReadout(plot, worldPt, screenPt, imagePt, threeColor, hipsLocation); const {readoutPref}= readoutRoot(); const radix= getFluxRadix(readoutPref, plot); return doFluxCall(plotView,imagePt).then( (fluxResult) => { @@ -264,6 +267,22 @@ function makeImagePlotAsyncReadout(plotView, worldPt, screenPt, imagePt, threeCo }); } +function makeHiPSPlotAsyncReadout(plotView, worldPt, screenPt, imagePt, threeColor, hipsLocation) { + const plot = primePlot(plotView); + const readoutItems = makeImmediateReadout(plot, worldPt, screenPt, imagePt, threeColor, hipsLocation); + const radix = 10; + const norderForFitsReadout= plot.hasFitsCube ? Number(plot.hipsProperties?.hips_order) : hipsLocation.tileNorder; + + const cell = getHealpixCellAtNorder(norderForFitsReadout, worldPt, plot.dataCoordSys); + if (!cell || norderForFitsReadout<1) return; + const hipsTileUrl= makeHipsFitsTilePath(plot,norderForFitsReadout,cell.ipix); + const {tileImagePt}= getHealpixPixelAtNorder(norderForFitsReadout,worldPt); + return doFluxCall(plotView, makeImagePt(tileImagePt.x+.5, tileImagePt.y+.5), worldPt, hipsTileUrl, plot.cubeIdx) + .then((fluxResult) => { + return makeReadoutWithFlux(readoutItems, primePlot(plotView), fluxResult, radix, false); + }); +} + /** * * @param readout @@ -298,13 +317,22 @@ function makeReadoutWithFlux(readout, plot, fluxResult, radix, threeColor) { } -function doFluxCall(plotView,iPt) { +/** + * + * @param plotView + * @param iPt + * @param [wpt] + * @param [hipsTileUrl] + * @param [hipsPlane] + * @return {Promise|Promise|Promise>} + */ +function doFluxCall(plotView,iPt, wpt, hipsTileUrl=undefined, hipsPlane=0) { const plot= primePlot(plotView); if (CysConverter.make(plot).pointInPlot(iPt)) { const plotStateAry= getPlotStateAry(plotView); const passAry=[plotStateAry[0]]; if (plotStateAry[1]) passAry.push(plotStateAry[1]); - return callGetFileFlux(passAry, iPt) + return callGetFileFlux(passAry, iPt, wpt,isHiPS(plot), hipsTileUrl, hipsPlane) .then((result) => { return result; }) @@ -321,7 +349,8 @@ function doFluxCall(plotView,iPt) { function getFlux(result, plot) { const fluxArray = []; if (result.NO_BAND) { - fluxArray[0]= {...result.NO_BAND, unit: getFluxUnits(plot)}; + const unit= isHiPS(plot) ? result.NO_BAND.unit : getFluxUnits(plot); + fluxArray[0]= {...result.NO_BAND, unit}; } else { const bands = plot.plotState.getBands(); @@ -402,10 +431,10 @@ function showSingleBandFluxLabel(plot, band) { * @return {Object} */ function makeWLResult(plot,imagePt= undefined) { - if ((hasPixelLevelWLInfo(plot) || (hasPlaneOnlyWLInfo(plot) && !isImageCube(plot)))) { + if ((hasPixelLevelWLInfo(plot) || (hasPlaneOnlyWLInfo(plot) && !isCube(plot)))) { if (wavelengthInfoParsedSuccessfully(plot)) { if (!imagePt) return {}; - const cubeIdx= (isImageCube(plot) && getImageCubeIdx(plot)) || 0; + const cubeIdx= (isCube(plot) && getCubePlaneIdx(plot)) || 0; const specCoords= getPtSpectralCoords(plot, imagePt, cubeIdx); const result= { wl: makeValueReadoutItem('Wavelength', specCoords[0] ?? 0, getWaveLengthUnits(plot), 4), @@ -496,24 +525,23 @@ function makeHiPSPixelReadoutItem(plot) { * @param {ScreenPt} screenPt * @param {ImagePt} imagePt * @param {boolean} threeColor - * @param {number} [healpixPixel] the healpix pixel for the current tile, only passed with HiPS - * @param {number} [norder] the healpix pixel norder + * @param {Object} [hipsLocation] * @return {Object} */ -function makeHiPSReadout(plot, worldPt, screenPt, imagePt, threeColor, healpixPixel, norder) { +function makeHiPSReadout(plot, worldPt, screenPt, imagePt, threeColor, hipsLocation={}) { const csys= CysConverter.make(plot); if (csys.pointInView(imagePt)) { return { worldPt: makePointReadoutItem('World Point', worldPt), screenPt: makePointReadoutItem('Screen Point', screenPt), - imagePt: makePointReadoutItem('Image Point', imagePt), + imagePt: makePointReadoutItem('Image Point', hipsLocation.tileImagePt), devPt: makePointReadoutItem('Dev Point', csys.getDeviceCoords(screenPt)), - fitsImagePt: makePointReadoutItem('FITS Standard Image Point', csys.getFitsStandardImagePtFromInternal(imagePt)), + fitsImagePt: makePointReadoutItem('FITS Standard Image Point', hipsLocation.tileImagePt), title: makeDescriptionItem(plot.title), pixel: makeHiPSPixelReadoutItem(plot), screenPixel:makeValueReadoutItem('Screen Pixel Size',getScreenPixScaleArcSec(plot),'arcsec', 3), - healpixPixel:makeValueReadoutItem('Healpix Pixel', healpixPixel, 'pixel', 0), - healpixNorder:makeValueReadoutItem('Healpix norder', norder,'norder', 0), + healpixPixel:makeValueReadoutItem('Healpix Pixel', hipsLocation.pixel, 'pixel', 0), + healpixNorder:makeValueReadoutItem('Healpix norder', hipsLocation.norder,'norder', 0), }; } else { diff --git a/src/firefly/js/visualize/task/PlotHipsTask.js b/src/firefly/js/visualize/task/PlotHipsTask.js index 7b2c1a742..eda634054 100644 --- a/src/firefly/js/visualize/task/PlotHipsTask.js +++ b/src/firefly/js/visualize/task/PlotHipsTask.js @@ -20,7 +20,7 @@ import {dlRoot, getDlAry, visRoot} from '../VisStoreRoots'; import {makeWorldPt} from '../Point.js'; import {WebPlot, isHiPS, isImage, isBlankHiPSURL} from '../WebPlot.js'; import {PlotAttribute} from '../PlotAttribute.js'; -import {getStatusFromFetchError} from '../../util/WebUtil.js'; +import {getStatusFromFetchError, isDefined} from '../../util/WebUtil.js'; import { findCurrentCenterPoint, getCenterOfProjection, getCorners, getDrawLayerByType, getDrawLayersByType, getFoV, getPlotViewById, primePlot, @@ -38,7 +38,7 @@ import {addNewMocLayer, isMOCFitsFromUploadAnalsysis, makeMocTableId, MOCInfo, U import HiPSMOC from '../../drawingLayers/HiPSMOC.js'; import {getRowCenterWorldPt} from '../saga/ActiveRowToImageWatcher'; import {getActiveTableId, getTblById} from '../../tables/TableUtil'; -import {locateOtherIfMatched, matchHiPStoPlotView} from './WcsMatchTask'; +import {locateOtherIfMatched, matchCubePlanes, matchHiPStoPlotView} from './WcsMatchTask'; import {upload} from '../../rpc/CoreServices.js'; import {fetchUrl} from '../../util/fetch'; import {getGpuJs} from '../rawData/GpuJsConfig.js'; @@ -246,7 +246,7 @@ async function makeHiPSPlot(rawAction, dispatcher) { return; } await getGpuJs(); // make sure the GPU code is loaded up front - createHiPSGridLayer(); + createHiPSGridLayers(); dispatchAddActionWatcher({ actions:[PLOT_HIPS, UPDATE_VIEW_SIZE], callback:watchForHiPSViewDim, @@ -310,17 +310,14 @@ export async function createHiPSMocLayer({ivoid, title, hipsUrl, plot, visible=f } } catch (e) { - showInfoPopup('Could not find the MOC for: '+ title, - 'Moc Not Found'); // eslint-disable-line quotes + showInfoPopup('Could not find the MOC for: '+ title, 'Moc Not Found'); console.log(`MOC not found at URL (this is not uncommon): ${e}`) ; } } -function createHiPSGridLayer() { - const dl= getDrawLayerByType(getDlAry(), HiPSGrid.TYPE_ID); - if (!dl) { - dispatchCreateDrawLayer(HiPSGrid.TYPE_ID); - } +function createHiPSGridLayers() { + const gridDl= getDrawLayerByType(getDlAry(), HiPSGrid.TYPE_ID); + if (!gridDl) dispatchCreateDrawLayer(HiPSGrid.TYPE_ID); } @@ -342,6 +339,7 @@ async function doHiPSChange(rawAction, dispatcher, getState) { const newPayload= {...payload, blank}; dispatcher( { type: CHANGE_HIPS, payload:newPayload }); locateOtherIfMatched(visRoot(),plotId); + if (isDefined(payload.cubeIdx)) matchCubePlanes(plotId); dispatcher( { type: ANY_REPLOT, payload:newPayload }); return; } @@ -383,6 +381,7 @@ async function doHiPSChange(rawAction, dispatcher, getState) { }); initCorrectCoordinateSys(getPlotViewById(visRoot(), plotId)); locateOtherIfMatched(visRoot(),plotId); + if (isDefined(payload.cubeIdx)) matchCubePlanes(plotId); dispatcher({type: ANY_REPLOT, payload}); } catch (error) { console.log(error); diff --git a/src/firefly/js/visualize/task/PlotImageTask.js b/src/firefly/js/visualize/task/PlotImageTask.js index 259eb3297..7fe96c5c4 100644 --- a/src/firefly/js/visualize/task/PlotImageTask.js +++ b/src/firefly/js/visualize/task/PlotImageTask.js @@ -20,7 +20,7 @@ import {Band} from '../Band.js'; import {PlotPref} from '../PlotPref.js'; import {makePostPlotTitle} from '../reducer/PlotTitle.js'; import {dispatchAddViewerItems } from '../MultiViewCntlr.js'; -import {getPlotViewById, getPlotViewIdListInOverlayGroup, hasWCSProjection} from '../PlotViewUtil.js'; +import {currentP, getPlotViewById, getPlotViewIdListInOverlayGroup, hasWCSProjection} from '../PlotViewUtil.js'; import {enableMatchingRelatedData} from '../RelatedDataUtil.js'; import {doFetchTable} from '../../tables/TableUtil.js'; import {callGetWebPlot, callGetWebPlot3Color, callGetWebPlotGroup} from '../../rpc/PlotServicesJson.js'; @@ -235,7 +235,7 @@ function processSuccessResult(dispatcher, payload, successAry) { const vr= visRoot(); if (vr.wcsMatchType && vr.positionLock) { - const matchId= getPlotViewById(vr,vr.mpwWcsPrimId)?.plotId ?? vr.activePlotId; + const matchId= currentP(vr.mpwWcsPrimId).plotId ?? vr.activePlotId; dispatchWcsMatch( {plotId:matchId, matchType:vr.wcsMatchType, lockMatch:true}); } } diff --git a/src/firefly/js/visualize/task/WcsMatchTask.js b/src/firefly/js/visualize/task/WcsMatchTask.js index dcf830595..f479b7871 100644 --- a/src/firefly/js/visualize/task/WcsMatchTask.js +++ b/src/firefly/js/visualize/task/WcsMatchTask.js @@ -5,7 +5,8 @@ import {isEmpty} from 'lodash'; import {dispatchAttachLayerToPlot, dispatchCreateDrawLayer} from '../DrawLayerDispatch'; import { - dispatchAttributeChange, dispatchChangeCenterOfProjection, dispatchChangeHiPS, dispatchFlip, dispatchPositionLocking, + dispatchAttributeChange, dispatchChangeCenterOfProjection, dispatchChangeHiPS, dispatchChangePrimePlot, + dispatchFlip, dispatchPositionLocking, dispatchRecenter, dispatchRotate, dispatchUpdateViewSize, dispatchZoom } from '../ImagePlotDispatch'; import { @@ -14,9 +15,11 @@ import { import {dlRoot, visRoot} from '../VisStoreRoots'; import {isEastLeftOfNorth, isPlotRotatedNorth} from '../WebPlotAnalysis'; import { - applyToOnePvOrAll, findCurrentCenterPoint, getCenterOfProjection, getCorners, getDrawLayerByType, + applyToOnePvOrAll, findCurrentCenterPoint, getCubePlaneIdx, getCenterOfProjection, getCorners, getCubeLength, + getDrawLayerByType, getMatchingRotationAngle, - getPlotViewAry, getPlotViewById, hasWCSProjection, isRotationMatching, primePlot, refreshP + getPlotViewAry, getPlotViewById, hasWCSProjection, isCube, isRotationMatching, operateOnOthersInPositionGroup, + primePlot, refreshP, currentP } from '../PlotViewUtil.js'; import {isHiPS, isImage} from '../WebPlot.js'; import {PlotAttribute} from '../PlotAttribute'; @@ -165,6 +168,7 @@ export function wcsMatchActionCreator(action) { payload: {wcsMatchCenterWP,wcsMatchType:false,mpwWcsPrimId:masterPv.plotId} }); } + if (isCube(masterPlot)) matchCubePlanes(masterPlot.plotId); }; } @@ -183,6 +187,30 @@ export function locateOtherIfMatched(vr,plotId) { } } +export function matchCubePlanes(plotId) { + setTimeout( + () => { + const {pv,plot}= currentP(plotId); + if (isCube(plot) && visRoot().positionLock) { + const idx= getCubePlaneIdx(plot); + const cubeLength= getCubeLength(pv); + operateOnOthersInPositionGroup(visRoot(), pv, + (testPv) => { + testPv= refreshP(testPv) ; + const testPlot= primePlot(testPv); + if (!isCube(testPlot)) return; + const {plotId}= testPlot; + if (getCubeLength(testPlot) === cubeLength && getCubePlaneIdx(testPlot) !== idx) { + isHiPS(testPlot) + ? dispatchChangeHiPS({plotId, cubeIdx:idx}) + : dispatchChangePrimePlot({plotId,primeIdx:idx}); + } + }, + true, true); + } + } ,5); +} + export const {matchImageToHips, matchHiPStoPlotView}= (() => { @@ -216,7 +244,7 @@ function imageToHips(hipsPv, imagePv) { const wp= getCenterOfProjection(hipsPlot); const imageCenter= CCUtil.getWorldCoords(imagePlot, findCurrentCenterPoint(imagePv)); if (!pointEquals(imageCenter,wp)) { - dispatchRecenter({plotId: imagePlot.plotId, centerPt:wp}); + dispatchRecenter({plotId: imagePlot.plotId, centerPt:wp, updateWcsPrimId:false }); imagePv= refreshP(imagePv); imagePlot= refreshP(imagePlot); } diff --git a/src/firefly/js/visualize/ui/Buttons.jsx b/src/firefly/js/visualize/ui/Buttons.jsx index a98c382b6..7ad3430cd 100644 --- a/src/firefly/js/visualize/ui/Buttons.jsx +++ b/src/firefly/js/visualize/ui/Buttons.jsx @@ -66,6 +66,7 @@ import OneXIcon from '@mui/icons-material/TimesOneMobiledataOutlined'; import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined'; import ChangeCircleIcon from '@mui/icons-material/ChangeCircle'; import ReverseIcon from '@mui/icons-material/FlipCameraAndroidOutlined'; +import ExtrqctTileIcon from '@mui/icons-material/FlipToBack'; import FiberManualRecordRoundedIcon from '@mui/icons-material/FiberManualRecordRounded'; @@ -169,6 +170,8 @@ export const ExtractLine= (props) => ( , sx:{'& .extractLine': {transform: 'rotate(142deg)'}}, ...props, }}/>); +export const ExtractTile= (props) => (, ...props, }}/>); + export const SearchDetailButton= (props) => ( , iconButtonSize:'44px', useDropDownIndicator: true, dropPosition: {left: 2, bottom: 0}, ...props diff --git a/src/firefly/js/visualize/ui/ColorDialog.jsx b/src/firefly/js/visualize/ui/ColorDialog.jsx index 11173a867..b3db2119b 100644 --- a/src/firefly/js/visualize/ui/ColorDialog.jsx +++ b/src/firefly/js/visualize/ui/ColorDialog.jsx @@ -66,7 +66,7 @@ function getStoreUpdate(oldS) { export const ColorDialog= memo(() => { const {plot,fields,rFields,gFields,bFields,rgbFields} = useStoreConnector(getStoreUpdate); - const [huePreserving, setHuePreserving]= useState(plot?.plotState.getRangeValues().rgbPreserveHue); + const [huePreserving, setHuePreserving]= useState(plot?.plotState?.getRangeValues()?.rgbPreserveHue ?? 0); if (!plot) return false; if (isImage(plot)) { diff --git a/src/firefly/js/visualize/ui/ExtractionWatchers.js b/src/firefly/js/visualize/ui/ExtractionWatchers.js index b9f3159d9..4a7b8d1a3 100644 --- a/src/firefly/js/visualize/ui/ExtractionWatchers.js +++ b/src/firefly/js/visualize/ui/ExtractionWatchers.js @@ -15,7 +15,7 @@ import { import {getCellValue, getColumnIdx, getMetaEntry, getTblById} from 'firefly/tables/TableUtil.js'; import {MetaConst} from 'firefly/data/MetaConst.js'; import { - convertHDUIdxToImageIdx, getDrawLayerById, getHDU, getHDUIndex, getImageCubeIdx, isImageCube, primePlot + convertHDUIdxToImageIdx, getDrawLayerById, getHDU, getHDUIndex, getCubePlaneIdx, isCube, primePlot } from 'firefly/visualize/PlotViewUtil.js'; import {makeImagePt, parseImagePt} from 'firefly/visualize/Point.js'; import SearchTarget from 'firefly/drawingLayers/SearchTarget.js'; @@ -48,7 +48,7 @@ function zAxisExtractionPlotWatcher(action,cancelSelf,{tbl_id}) { } if (!isTargetCube(tbl_id)) return {tbl_id}; const {table,plot}= getInfo(tbl_id); - const cubeIdx= getImageCubeIdx(plot); + const cubeIdx= getCubePlaneIdx(plot); if (cubeIdx!==table.highlightedRow) dispatchTableHighlight(tbl_id,cubeIdx,table.request); return {tbl_id}; } @@ -70,7 +70,7 @@ function zAxisExtractionTableWatcher(action,cancelSelf,{tbl_id, drawLayerId=unde if ((type===TABLE_UPDATE || type===TABLE_LOADED) && !firstLoadComplete) { firstLoadComplete= true; const {table,plot}= getInfo(tbl_id); - const cubeIdx= getImageCubeIdx(plot); + const cubeIdx= getCubePlaneIdx(plot); dispatchTableHighlight(tbl_id,cubeIdx,table.request); return retData(); } @@ -119,7 +119,7 @@ function zAxisExtractionTableWatcher(action,cancelSelf,{tbl_id, drawLayerId=unde function isTargetCube(tbl_id) { const {table,pv,plot,extractionType,hdus,hduNum}= getInfo(tbl_id); if (!pv || !plot|| !table || extractionType!=='z-axis') return; - if (!isImageCube(plot)) return false; + if (!isCube(plot)) return false; const imPt= parseImagePt(getMetaEntry(table,MetaConst.FITS_IM_PT)); if (isEmpty(hdus) || !imPt || !Object.keys(hdus).includes(hduNum+'')) return false; if (getColumnIdx(table, 'plane') <0) return false; diff --git a/src/firefly/js/visualize/ui/FitsHeaderView.jsx b/src/firefly/js/visualize/ui/FitsHeaderView.jsx index b2e8f32fa..9e17beef6 100644 --- a/src/firefly/js/visualize/ui/FitsHeaderView.jsx +++ b/src/firefly/js/visualize/ui/FitsHeaderView.jsx @@ -6,7 +6,7 @@ import {Stack, Typography} from '@mui/joy'; import React from 'react'; import DialogRootContainer from '../../ui/DialogRootContainer.jsx'; import {LayoutType, PopupPanel} from '../../ui/PopupPanel.jsx'; -import {primePlot, isImageCube, getCubePlaneCnt, currentP} from '../PlotViewUtil.js'; +import {primePlot, isCube, getCubeLength, currentP} from '../PlotViewUtil.js'; import {Tabs, Tab} from '../../ui/panel/TabPanel.jsx'; import {TablePanel} from '../../tables/ui/TablePanel.jsx'; import {dispatchShowDialog, dispatchHideDialog, isDialogVisible} from '../../core/ComponentCntlr.js'; @@ -215,7 +215,7 @@ function renderFileSizeAndPixelSize(plot, band, fitsHeaderInfo, isOnTab) { const fileSizeStr = getSizeAsString(tableModel?.tableMeta?.fileSize) ?? ''; let dimStr= `${plot.dataWidth} x ${plot.dataHeight}`; - if (isImageCube(plot)) dimStr+= ` x ${getCubePlaneCnt(plot)}`; + if (isCube(plot)) dimStr+= ` x ${getCubeLength(plot)}`; const overview= ( diff --git a/src/firefly/js/visualize/ui/MouseReadPopoutAll.jsx b/src/firefly/js/visualize/ui/MouseReadPopoutAll.jsx index 4b82eed40..8434ecbd4 100644 --- a/src/firefly/js/visualize/ui/MouseReadPopoutAll.jsx +++ b/src/firefly/js/visualize/ui/MouseReadPopoutAll.jsx @@ -15,7 +15,7 @@ import {MagnifiedView} from 'firefly/visualize/ui/MagnifiedView.jsx'; import {currentP, getPlotViewById, primePlot} from 'firefly/visualize/PlotViewUtil.js'; import {getFluxInfo, getFluxRadix, getNonFluxDisplayElements} from 'firefly/visualize/ui/MouseReadoutUIUtil.js'; import {DataReadoutItem, MouseReadoutLock} from 'firefly/visualize/ui/MouseReadout.jsx'; -import {isImage} from 'firefly/visualize/WebPlot.js'; +import {isHiPS, isImage} from 'firefly/visualize/WebPlot.js'; import {Band} from '../Band'; import {showMouseReadoutFluxRadixDialog} from './MouseReadoutOptionPopups.jsx'; @@ -81,7 +81,8 @@ function Readout({readout, readoutData, showHealpixPixel=false, radix}){ const isHiPS= readoutType===HIPS_STANDARD_READOUT; const image= readoutType===STANDARD_READOUT; - const {plotState}= currentP(plotId).plot ?? {}; + const {plot}= currentP(plotId); + const {plotState}= plot ?? {}; const redUsed= threeColor && image && plotState.isBandUsed(Band.RED); const greenUsed= threeColor && image && plotState.isBandUsed(Band.GREEN); const blueUsed= threeColor && image && plotState.isBandUsed(Band.BLUE); @@ -101,9 +102,9 @@ function Readout({readout, readoutData, showHealpixPixel=false, radix}){ columnGap: .5, rowGap: .75, gridTemplateColumns: '6em 14px auto', - gridTemplateRows: `2em 1.4em 1.4em 1.4em 1.4em${threeColor ? ' 1.4em 1.4em' : ''}`, + gridTemplateRows: `2em 1.4em 1.4em 1.4em 1.4em${(threeColor||plot.hasFits) ? ' 1.4em 1.4em' : ''}`, alignItems: 'center', - gridTemplateAreas: getGridTemplate(threeColor,isHiPS,waveLength,bandWidth, + gridTemplateAreas: getGridTemplate(threeColor,plot,waveLength,bandWidth, waveLengthRED,waveLengthGREEN,waveLengthBLUE), ...rS }}> @@ -121,6 +122,8 @@ function Readout({readout, readoutData, showHealpixPixel=false, radix}){ label={healpixPixelReadout.label} value={healpixPixelReadout.value}/> } {hipsPixel && } + {hipsPixel && plot.hasFits && } {image && !threeColor && showMouseReadoutFluxRadixDialog(readout.readoutPref)} @@ -192,7 +195,7 @@ function Readout({readout, readoutData, showHealpixPixel=false, radix}){ ); } -function getGridTemplate(threeColor,isHiPS, waveLength, bandWidth, waveLengthRED, waveLengthGREEN, waveLengthBLUE) { +function getGridTemplate(threeColor,plot, waveLength, bandWidth, waveLengthRED, waveLengthGREEN, waveLengthBLUE) { const resultAry= ['". . lock"', '"pixSizeLabel . pixSizeValue"', '"pixReadoutTopLabel clipboardIconTop pixReadoutTopValue"', @@ -215,8 +218,9 @@ function getGridTemplate(threeColor,isHiPS, waveLength, bandWidth, waveLengthRED '"bwBlueLabel . bwBlueValue"', ]; - if (isHiPS) { + if (isHiPS(plot)) { resultAry.push(...green); + if (plot.hasFits) resultAry.push(...blue); } else if (threeColor) { if (waveLengthRED) resultAry.push(...wlRed); diff --git a/src/firefly/js/visualize/ui/MouseReadoutBottomLine.jsx b/src/firefly/js/visualize/ui/MouseReadoutBottomLine.jsx index 3c89436c1..39fd73364 100644 --- a/src/firefly/js/visualize/ui/MouseReadoutBottomLine.jsx +++ b/src/firefly/js/visualize/ui/MouseReadoutBottomLine.jsx @@ -8,6 +8,8 @@ import {object, bool, number} from 'prop-types'; import BrowserInfo from '../../util/BrowserInfo.js'; import {EMPTY_BUNIT_DEFAULT} from '../FitsHeaderUtil'; import {dispatchChangePointSelection} from '../ImagePlotDispatch'; +import {currentP} from '../PlotViewUtil'; +import {isHiPS} from '../WebPlot'; import {showMouseReadoutFluxRadixDialog} from './MouseReadoutOptionPopups.jsx'; import {getNonFluxDisplayElements, getFluxInfo} from './MouseReadoutUIUtil.js'; import {CopyToClipboard} from './MouseReadout.jsx'; @@ -20,6 +22,7 @@ export function MouseReadoutBottomLine({readout, readoutData, readoutShowing, st const {current:divref}= useRef({element:undefined}); const [haveDivRef,setHaveDivRef]= useState(false); + const {plot}= currentP(readoutData.plotId); useEffect( () => { setHaveDivRef(Boolean(divref.element)); @@ -30,8 +33,8 @@ export function MouseReadoutBottomLine({readout, readoutData, readoutShowing, st const {readoutType}= readoutData; if (!readoutData.readoutItems) return (
); - const isHiPS= readoutType===HIPS_STANDARD_READOUT; - const displayEle= getNonFluxDisplayElements(readoutData, readout.readoutPref, isHiPS); + const hips= isHiPS(plot); + const displayEle= getNonFluxDisplayElements(readoutData, readout.readoutPref, hips); const {readout1, showReadout1PrefChange, waveLength, bandWidth}= displayEle; const r1Value= readout1?.value ??''; const wlValue= waveLength?.value ??''; @@ -63,10 +66,10 @@ export function MouseReadoutBottomLine({readout, readoutData, readoutShowing, st const {threeColor= false}= readoutData; const monoFont= radix===16; - const doWL= ((r1Value && fullSize) || !r1Value) && waveLength && !isHiPS; - const doFlux= fullSize && !isHiPS; + const doWL= ((r1Value && fullSize) || !r1Value) && waveLength && !hips; + const doFlux= fullSize && (!hips || (hips && plot.hasFits)); - const lockByClickLabelWidth= isHiPS ? 450 : threeColor ? 750 : 600; + const lockByClickLabelWidth= hips ? 450 : threeColor ? 750 : 600; const checkboxText= width>lockByClickLabelWidth ? lockByClick ? 'Click Lock: on': 'Click Lock: off' : ''; const label3C= threeColor && doFlux ? get3CLabel(fluxArray) : ''; diff --git a/src/firefly/js/visualize/ui/MouseReadoutOptionPopups.jsx b/src/firefly/js/visualize/ui/MouseReadoutOptionPopups.jsx index 5c51db3b5..f6433b3df 100644 --- a/src/firefly/js/visualize/ui/MouseReadoutOptionPopups.jsx +++ b/src/firefly/js/visualize/ui/MouseReadoutOptionPopups.jsx @@ -53,6 +53,7 @@ const hipsCoordOptions= [ {label: 'Super Galactic', value: MR_SUPER_GALACTIC}, {label: 'Ecliptic J2000', value: MR_ECLJ2000}, {label: 'Ecliptic B1950', value: MR_ECL1950}, + {label: 'FITS Image Pixel', value: MR_FITS_IP}, ]; diff --git a/src/firefly/js/visualize/ui/MouseReadoutUIUtil.js b/src/firefly/js/visualize/ui/MouseReadoutUIUtil.js index 4e05c6785..8c78118e8 100644 --- a/src/firefly/js/visualize/ui/MouseReadoutUIUtil.js +++ b/src/firefly/js/visualize/ui/MouseReadoutUIUtil.js @@ -11,7 +11,7 @@ import { MR_GALACTIC, MR_HEALPIX_NORDER, MR_HEALPIX_PIXEL, MR_PIXEL_SIZE, MR_SPIXEL_SIZE, MR_SUPER_GALACTIC, MR_WCS_COORDS, STATUS_NAN, STATUS_UNAVAILABLE, STATUS_UNDEFINED, STATUS_VALUE, TYPE_DECIMAL_INT, TYPE_EMPTY, TYPE_FLOAT, MR_BAND_WIDTH, EQ_TYPE, MR_WL_RED, MR_WL_GREEN, MR_WL_BLUE, MR_BAND_WIDTH_GREEN, MR_BAND_WIDTH_RED, - MR_BAND_WIDTH_BLUE + MR_BAND_WIDTH_BLUE, STATUS_RETRIEVING } from '../MouseReadoutCntlr.js'; import {convertCelestial} from '../VisUtil'; import {isCelestialImage} from '../WebPlot.js'; @@ -159,7 +159,7 @@ export function getNonFluxReadoutElements(readoutData, readoutPref, isHiPS= fals const retList={}; keysToUse.forEach( (key) => { - retList[key]=getReadoutElement(readoutItems, readoutPref[key], plotId, copyPref); + retList[key]=getReadoutElement(readoutItems, readoutPref[key], plotId, copyPref, isHiPS); }); return retList; @@ -173,9 +173,10 @@ export function getNonFluxReadoutElements(readoutData, readoutPref, isHiPS= fals * @param readoutKey Readout preference value * @param plotId * @param copyPref Readout Copy preference value + * @param isHiPS * @returns {*} */ -export function getReadoutElement(readoutItems, readoutKey, plotId, copyPref) { +export function getReadoutElement(readoutItems, readoutKey, plotId, copyPref, isHiPS= false) { if (!readoutItems) return {value:''}; const wp= readoutItems?.worldPt?.value; @@ -273,6 +274,7 @@ function makeFluxEntry(obj,radix=10) { const is16= radix===16; switch (status) { case STATUS_UNAVAILABLE: return {value: 'unavailable', label, unit:''}; + case STATUS_RETRIEVING: return {value: 'retrieving value', label, unit:''}; case STATUS_NAN: return is16 ? {value: `${valueBase16} (NaN)`, label, unit:''} : {value: 'NaN', label, unit:''}; case STATUS_UNDEFINED: return is16 ? {value: `${valueBase16} (undefined)`, label, unit:''} : {value: 'undefined', label, unit:''}; case STATUS_VALUE: diff --git a/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx b/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx index e213978ca..043b01f40 100644 --- a/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx +++ b/src/firefly/js/visualize/ui/VisCtxToolbarView.jsx @@ -21,6 +21,7 @@ import {StateInputField} from '../../ui/StatedInputfield.jsx'; import {DropDownVerticalSeparator, ToolbarButton, ToolbarHorizontalSeparator} from '../../ui/ToolbarButton.jsx'; import BrowserInfo from '../../util/BrowserInfo.js'; import Validate from '../../util/Validate.js'; +import {splitByWhiteSpace} from '../../util/WebUtil'; import {CoordinateSys} from '../CoordSys.js'; import {getExtName, getExtType, getHeader} from '../FitsHeaderUtil.js'; import { @@ -28,9 +29,10 @@ import { } from '../ImagePlotDispatch'; import {PlotAttribute} from '../PlotAttribute'; import { - canConvertBetweenHipsAndFits, convertHDUIdxToImageIdx, convertImageIdxToHDU, currentP, getCubePlaneCnt, + canConvertBetweenHipsAndFits, convertHDUIdxToImageIdx, convertImageIdxToHDU, currentP, getCubeLength, getFormattedWaveLengthUnits, getHDU, getHDUCount, getHDUIndex, getPtWavelength, - hasPlaneOnlyWLInfo, isImageCube, isMultiHDUFits, primePlot, pvEqualExScroll, refreshP, + getWaveLengthUnitsPrecision, + hasPlaneOnlyWLInfo, isCube, isMultiHDUFits, primePlot, pvEqualExScroll, refreshP, } from '../PlotViewUtil.js'; import {makeWorldPt} from '../Point.js'; import {convertToHiPS, convertToImage, doHiPSImageConversionIfNecessary} from '../task/PlotHipsTask.js'; @@ -393,7 +395,7 @@ export function MultiImageControllerView({plotView:pv}) { let length; let wlStr= ''; let startStr; - const cube= isImageCube(plot) || !image; + const cube= isCube(plot) || !image; const multiHdu= isMultiHDUFits(pv); let hduDesc= ''; let tooltip; @@ -419,10 +421,11 @@ export function MultiImageControllerView({plotView:pv}) { tooltip+= `${reqHduInfo}HDU: ${hduNum} ${nameOrType?', '+hduDesc:''}`; } if (plot.cubeIdx>-1) { - tooltip+= `${multiHdu ? ', ':''} Cube: ${plot.cubeIdx+1}/${getCubePlaneCnt(plot)}`; + tooltip+= `${multiHdu ? ', ':''} Cube: ${plot.cubeIdx+1}/${getCubeLength(plot)}`; if (hasPlaneOnlyWLInfo(plot)) { - const wl= doFormat(getPtWavelength(plot,undefined, plot.cubeIdx),4); + const precision= getWaveLengthUnitsPrecision(plot); + const wl= doFormat(getPtWavelength(plot,undefined, plot.cubeIdx),precision); const unitStr= getFormattedWaveLengthUnits(plot); wlStr= wl ? `${wl} ${unitStr}` : ''; } @@ -475,20 +478,26 @@ const doFormat= (v,precision) => isNaN(v) function getHipsCubeDesc(plot) { if (!isHiPS(plot)) return ''; const {hipsProperties}= plot; - const {data_cube_crpix3, data_cube_crval3, data_cube_cdelt3, data_cube_bunit3=''}= hipsProperties; - if (!data_cube_crpix3 || !data_cube_crval3 || !data_cube_cdelt3) return ''; - const crpix3= Number(data_cube_crpix3); - const crval3= Number(data_cube_crval3); - const cdelt3= Number(data_cube_cdelt3); - const dp= Math.abs(cdelt3)>10 ? 0 : 1- Math.trunc(Math.log10(Math.abs(cdelt3))); // number of decimal points suggestion of gpdf - if (isNaN(crpix3) || isNaN(crval3) || isNaN(cdelt3)) return ''; - const value = crval3 + ( plot.cubeIdx - crpix3 ) * cdelt3; - const bunit3= (data_cube_bunit3!=='null' && data_cube_bunit3!=='nil' && data_cube_bunit3!=='undefined') ? - data_cube_bunit3 : ''; - return `${doFormat(value,dp)} ${getFormattedWaveLengthUnits(bunit3)}`; -} + const {data_cube_crpix3, data_cube_crval3, data_cube_cdelt3, data_cube_crval3_arr, data_cube_bunit3=''}= hipsProperties; + const cubePlanePropValue= data_cube_crval3_arr && splitByWhiteSpace(data_cube_crval3_arr)?.[plot.cubeIdx]; + const bunit3= (data_cube_bunit3 && data_cube_bunit3!=='null' && data_cube_bunit3!=='nil' && data_cube_bunit3!=='undefined') ? + data_cube_bunit3 : ''; + if (cubePlanePropValue) { + return `${cubePlanePropValue} ${getFormattedWaveLengthUnits(bunit3)}`; + } + else { + if (!data_cube_crpix3 || !data_cube_crval3 || !data_cube_cdelt3 || !cubePlanePropValue) return ''; + const crpix3= Number(data_cube_crpix3); + const crval3= Number(data_cube_crval3); + const cdelt3= Number(data_cube_cdelt3); + const dp= Math.abs(cdelt3)>10 ? 0 : 1- Math.trunc(Math.log10(Math.abs(cdelt3))); // number of decimal points suggestion of gpdf + if (isNaN(crpix3) || isNaN(crval3) || isNaN(cdelt3)) return ''; + const value = crval3 + ( plot.cubeIdx - crpix3 ) * cdelt3; + return `${doFormat(value,dp)} ${getFormattedWaveLengthUnits(bunit3)}`; + } +} function getEmLength(len) { const size= Math.trunc(Math.log10(len)) + 1; @@ -505,7 +514,7 @@ function FrameNavigator({pv, currPlotIdx, minForInput, displayType}) { getPlotIdx: (pv,idx) => convertHDUIdxToImageIdx(pv,idx, 'follow'), }, cube: { - getLen: getCubePlaneCnt, + getLen: getCubeLength, getContextIdx: (pv,idx) => convertImageIdxToHDU(pv,idx).cubeIdx, getPlotIdx: (pv,idx) => convertHDUIdxToImageIdx(pv, getHDUIndex(pv, primePlot(pv)), idx) }, diff --git a/src/firefly/js/visualize/ui/VisMiniToolbar.jsx b/src/firefly/js/visualize/ui/VisMiniToolbar.jsx index f9c54acbe..ce38748e3 100644 --- a/src/firefly/js/visualize/ui/VisMiniToolbar.jsx +++ b/src/firefly/js/visualize/ui/VisMiniToolbar.jsx @@ -22,16 +22,18 @@ import {showRegionFileUploadPanel} from 'firefly/visualize/region/RegionFileUplo import {findUnactivatedRelatedData} from 'firefly/visualize/RelatedDataUtil.js'; import {ColorTableDropDownView, showColorDialog} from 'firefly/visualize/ui/ColorTableDropDownView.jsx'; import {showDrawingLayerPopup} from 'firefly/visualize/ui/DrawLayerPanel.jsx'; -import {endExtraction, LINE, POINTS, showExtractionDialog, Z_AXIS} from './extraction/ExtractionDialog.jsx'; +import {showExtractionDialog} from './extraction/ExtractionDialog.jsx'; import {showImageSelPanel} from 'firefly/visualize/ui/ImageSearchPanelV2.jsx'; import {MarkerDropDownView} from 'firefly/visualize/ui/MarkerDropDownView.jsx'; import {showMaskDialog} from 'firefly/visualize/ui/MaskAddPanel.jsx'; import {MatchLockDropDown} from 'firefly/visualize/ui/MatchLockDropDown.jsx'; import {showPlotInfoPopup} from 'firefly/visualize/ui/PlotInfoPopup.js'; +import {endExtraction, HIPS_TILE, LINE, POINTS, Z_AXIS} from './extraction/ExtractionUIUtil'; +import {showHiPSTileExtractionDialog} from './extraction/HiPSTileExtractionDialog'; import {SelectAreaButton} from './SelectAreaUIComponents.jsx'; import {SimpleLayerOnOffButton} from 'firefly/visualize/ui/SimpleLayerOnOffButton.jsx'; import {StretchDropDownView} from 'firefly/visualize/ui/StretchDropDownView.jsx'; -import {isHiPS} from 'firefly/visualize/WebPlot.js'; +import {isHiPS, isImage} from 'firefly/visualize/WebPlot.js'; import {getPreference} from '../../core/AppDataCntlr.js'; import {useStoreConnector} from '../../ui/SimpleComponent.jsx'; import { @@ -43,10 +45,11 @@ import {getDlAry, visRoot} from '../VisStoreRoots'; import {getMultiViewRoot, getViewer} from '../MultiViewCntlr.js'; import { getActivePlotView, getAllDrawLayersForPlot, getPlotViewById, hasWCSProjection, - isImageCube, isThreeColor, primePlot, pvEqualExScroll + isCube, isThreeColor, primePlot, pvEqualExScroll } from '../PlotViewUtil.js'; import { ColorButtonIcon, ColorDropDownButton, DistanceButton, DrawLayersButton, ExpandButton, ExtractLine, ExtractPoints, + ExtractTile, FlipYButton, InfoButton, RestoreButton, RotateButton, SaveButton, ToolsDropDown } from './Buttons.jsx'; import {ImageCenterDropDown, TARGET_LIST_PREF} from './ImageCenterDropDown.jsx'; @@ -294,7 +297,9 @@ const ColorButton= ({colorDrops,enabled,pv}) => ( function ToolsDrop({pv,mi, enabled, image, hips, modalEndInfo, showRotateLocked}) { - const showExtract= Boolean(image) && mi.extract; + const plot= primePlot(pv); + const hipsWithFits= isHiPS(plot) && plot.hasFits; + const showExtract= (isImage(plot) || hipsWithFits) && mi.extract; return ( @@ -350,38 +355,46 @@ const RotateFlipRow= ({image,mi,showRotateLocked,pv,enabled}) => ( function startExtraction(element,type,modalEndInfo) { modalEndInfo?.closeLayer?.('Extraction'); let ended= false; - showExtractionDialog(element, type, () => { - if (!ended) setModalEndInfo({}); - }); - setModalEndInfo({ - closeText: 'End Extraction', - key: 'Extraction', - closeLayer: () => { + const wasCanceled= () => !ended && setModalEndInfo({}); + const closeLayer= () => { ended= true; endExtraction(); clearModalEndInfo(); - }, - offOnNewPlot: false - }); + }; + + type===HIPS_TILE + ? showHiPSTileExtractionDialog(element, wasCanceled) + : showExtractionDialog(element, type, wasCanceled); + setModalEndInfo({ closeText: 'End Extraction', key: 'Extraction', offOnNewPlot: false, closeLayer }); } const ExtractRow= ({pv,enabled,modalEndInfo,mi}) => { - const standIm= !isThreeColor(pv); + const plot= primePlot(pv); + const standIm= isImage(plot) && !isThreeColor(pv); + const hipsWithFits= isHiPS(plot) && plot.hasFits; + const cube= standIm && isCube(primePlot(pv)); return ( Extract: - startExtraction(element,Z_AXIS,modalEndInfo)} visible={mi.extractZAxis}/> - startExtraction(element,LINE,modalEndInfo)} visible={mi.extractLine}/> - startExtraction(element,POINTS,modalEndInfo)} visible={mi.extractPoint}/> + {isHiPS(plot) && startExtraction(element,HIPS_TILE,modalEndInfo)} + visible={mi.extractPoint}/> } - ); + ); }; const LayersRow= ({style,image, pv,mi,enabled, modalEndInfo}) => ( diff --git a/src/firefly/js/visualize/ui/extraction/ExtractionDialog.jsx b/src/firefly/js/visualize/ui/extraction/ExtractionDialog.jsx index 9b74d856b..2909d7897 100644 --- a/src/firefly/js/visualize/ui/extraction/ExtractionDialog.jsx +++ b/src/firefly/js/visualize/ui/extraction/ExtractionDialog.jsx @@ -7,18 +7,12 @@ import {Box, Button, Divider, Stack, Tooltip, Typography} from '@mui/joy'; import {isUndefined} from 'lodash'; import React, {useEffect, useState} from 'react'; import {getAppOptions} from '../../../api/ApiUtil.js'; -import {CHART_RESIZE_DEBOUNCE, wrapResizeMonitor} from '../../../ui/ResizeMonitor'; -import { - dispatchAttachLayerToPlot, dispatchCreateDrawLayer, dispatchDestroyDrawLayer, dispatchDetachLayerFromPlot, - dispatchModifyCustomField -} from '../../DrawLayerDispatch'; -import {makeImagePt} from '../../Point'; import {allowPinnedCharts} from '../../../charts/ChartUtil'; import {ensureDefaultChart} from '../../../charts/ui/ChartsContainer.jsx'; import {pinChart} from '../../../charts/ui/PinnedChartContainer.jsx'; import {downloadChart, PlotlyWrapper} from '../../../charts/ui/PlotlyWrapper.jsx'; -import {dispatchHideDialog, dispatchShowDialog} from '../../../core/ComponentCntlr.js'; -import {dispatchAddActionWatcher, dispatchCancelActionWatcher} from '../../../core/MasterSaga.js'; +import {dispatchShowDialog} from '../../../core/ComponentCntlr.js'; +import {dispatchAddActionWatcher} from '../../../core/MasterSaga.js'; import ExtractLineTool, { addLineDistAttributesToPlots, COLUMN_SELECTION, FREE_SELECTION, LINE_SELECTION } from '../../../drawingLayers/ExtractLineTool.js'; @@ -31,39 +25,39 @@ import {FieldGroup} from '../../../ui/FieldGroup'; import HelpIcon from '../../../ui/HelpIcon.jsx'; import {ListBoxInputField, ListBoxInputFieldView} from '../../../ui/ListBoxInputField.jsx'; import {PopupPanel} from '../../../ui/PopupPanel.jsx'; +import {CHART_RESIZE_DEBOUNCE, wrapResizeMonitor} from '../../../ui/ResizeMonitor'; import {useFieldGroupValue, useStoreConnector} from '../../../ui/SimpleComponent.jsx'; import {ValidationField} from '../../../ui/ValidationField'; import {intValidator} from '../../../util/Validate'; import {CCUtil, CysConverter} from '../../CsysConverter.js'; +import {dispatchAttachLayerToPlot, dispatchCreateDrawLayer, dispatchModifyCustomField} from '../../DrawLayerDispatch'; import {getExtName, hasFloatingData} from '../../FitsHeaderUtil.js'; import {dispatchAttributeChange, dispatchChangePointSelection, dispatchChangePrimePlot} from '../../ImagePlotDispatch'; -import {PLOT_IMAGE} from '../../VisConst'; -import {getDlAry, visRoot} from '../../VisStoreRoots'; import {PlotAttribute} from '../../PlotAttribute.js'; import { convertHDUIdxToImageIdx, currentP, getCubePlaneFromWavelength, getDrawLayerByType, getHDU, getHDUIndex, - getImageCubeIdx, getPlotViewAry, hasWCSProjection, hasWLInfo, isDrawLayerAttached, - isImageCube, isMultiHDUFits, primePlot + getCubePlaneIdx, getPlotViewAry, hasWCSProjection, hasWLInfo, isDrawLayerAttached, isCube, isMultiHDUFits, + primePlot } from '../../PlotViewUtil.js'; +import {makeImagePt} from '../../Point'; +import {PLOT_IMAGE} from '../../VisConst'; +import {getDlAry, visRoot} from '../../VisStoreRoots'; import {computeDistance, computeScreenDistance, getLinePointAry} from '../../VisUtil.js'; import {genPointChartData, genSliceChartData, genZAxisChartData} from './ExtractionChart.jsx'; import {keepDataExtraction, keepZAxisExtraction} from './ExtractionTable.jsx'; +import { + cancelLineExtraction, EXTRACT_DIALOG_ID, endExtraction, EXTRACT_END_ID, ZAXIS_POINT_SELECTION_ID +} from './ExtractionUIUtil'; const CUBE_WARNING=` Values are taken directly from cube plane pixels without spatial interpolation, background subtraction, or PSF corrections. This is a quick-look tool for exploration, not a research-ready product. `; -const DIALOG_ID= 'extractionDialog'; const CHART_ID= 'extractionChart'; -const ZAXIS_POINT_SELECTION_ID= 'z-axisExtraction'; const SELECT_TYPE_TIP= 'Choose mouse selection type: mouse line lock, mouse column lock or free selection'; -export const Z_AXIS= 'Z_AXIS'; -export const LINE= 'LINE'; -export const POINTS= 'POINTS'; - const exTypeCntl= { Z_AXIS: { Panel: ZAxisExtractionPanel, @@ -88,14 +82,12 @@ function enableDrawLayer(typeId) { !isDrawLayerAttached(dl,pv.plotId) && dispatchAttachLayerToPlot(typeId,pv.plotId,true,true, true); } -const EXTRACT_END_ID= 'extractEndId'; - export function showExtractionDialog(element,extractionType,wasCanceled) { endExtraction(); exTypeCntl[extractionType].start(); - DialogRootContainer.defineDialog(DIALOG_ID, , element ); - dispatchShowDialog(DIALOG_ID); + DialogRootContainer.defineDialog(EXTRACT_DIALOG_ID, , element ); + dispatchShowDialog(EXTRACT_DIALOG_ID); dispatchAddActionWatcher( { @@ -106,13 +98,6 @@ export function showExtractionDialog(element,extractionType,wasCanceled) { } -export function endExtraction() { - cancelPointExtraction(); - cancelZaxisExtraction(); - cancelLineExtraction(); - dispatchCancelActionWatcher(EXTRACT_END_ID); -} - function ExtractDialog({extractionType,wasCanceled}) { const {pv, pvCnt} = useStoreConnector( getStoreState); const {canCreateExtractionTable}= getAppOptions().image; @@ -232,8 +217,8 @@ function makeLineExtractionTitle(pv,x1,y1,x2,y2) { const extName= getExtName(plot); hduInfo= (extName || ` HDU #${getHDU(plot)}`) + ' - '; } - if (getImageCubeIdx(plot)>-1) { - cubeInfo= `Plane #${getImageCubeIdx(plot)+1} - `; + if (getCubePlaneIdx(plot)>-1) { + cubeInfo= `Plane #${getCubePlaneIdx(plot)+1} - `; } return `Line Extract Preview - ${hduInfo}${cubeInfo}(${x1},${y1}) to (${x2},${y2})`; } @@ -255,7 +240,7 @@ function PointExtractionPanel({canCreateExtractionTable, pv, pvCnt}) { const {plotId,plotImageId}= plot ?? {}; const hduNum= getHDU(plot); - const plane= getImageCubeIdx(plot)>-1 ? getImageCubeIdx(plot) : 0; + const plane= getCubePlaneIdx(plot)>-1 ? getCubePlaneIdx(plot) : 0; const {x:chartX,y:chartY,chartXAxis:lastChartChartXAxis=chartXAxis}=plot?.attributes?.[PlotAttribute.SELECT_ACTIVE_CHART_PT] ?? {}; useEffect(() => { @@ -319,7 +304,7 @@ function LineExtractionPanel({canCreateExtractionTable, pv, pvCnt}) { const y2= Math.trunc(ipt2?.y ?? 0); const {plotId,plotImageId}= plot ?? {}; const hduNum= getHDU(plot); - const plane= getImageCubeIdx(plot)>-1 ? getImageCubeIdx(plot) : 0; + const plane= getCubePlaneIdx(plot)>-1 ? getCubePlaneIdx(plot) : 0; const {x:chartX,y:chartY}=plot?.attributes?.[PlotAttribute.SELECT_ACTIVE_CHART_PT] ?? {}; @@ -378,13 +363,13 @@ function LineExtractionPanel({canCreateExtractionTable, pv, pvCnt}) { const CubeStartUpHelp= ({plot}) => ( - {isImageCube(plot) ? + {isCube(plot) ? 'Click on a pixel to extract data from all planes of the cube' : 'Please choose a cube to extract z-axis data'} - {isImageCube(plot) && + {isCube(plot) && {CUBE_WARNING} } @@ -581,12 +566,12 @@ function ZAxisExtractionPanel({canCreateExtractionTable, pv}) { useEffect(() => { const updateChart= async () => { if (ipt && plot) { - if (!isImageCube(plot)) { + if (!isCube(plot)) { setChartParams({}); return; } const dataAry = await callGetCubeDrillDownAry(plot, hduNum, ipt, pointSize, combineOp, allRelatedHDUS); - const plane=getImageCubeIdx(plot); + const plane=getCubePlaneIdx(plot); const chartTitle= `Z Axis Preview - ${extName?extName+',':''} HDU #${hduNum}, Point: (${x},${y})`; setChartParams(genZAxisChartData(makeImagePt(x,y), pv, dataAry, plane , dataAry[plane] , pointSize, combineOp, chartTitle)); } @@ -693,27 +678,3 @@ async function keepExtractionAndPin(callKeepExtraction,allowpinChart=false) { } } -function cancelZaxisExtraction() { - dispatchChangePointSelection(ZAXIS_POINT_SELECTION_ID, false); - dispatchHideDialog(DIALOG_ID); -} - -function cancelLineExtraction() { - const {pv}= currentP(); - if (pv) { - dispatchDetachLayerFromPlot(ExtractLineTool.TYPE_ID,pv.plotId,true); - dispatchAttributeChange({plotId:pv.plotId,overlayColorScope:true, - changes:{[PlotAttribute.SELECT_ACTIVE_CHART_PT]: undefined }}); - dispatchDestroyDrawLayer(ExtractLineTool.TYPE_ID); - } - dispatchHideDialog(DIALOG_ID); -} - -function cancelPointExtraction() { - const {pv}= currentP(); - if (pv) { - dispatchDetachLayerFromPlot(ExtractPointsTool.TYPE_ID,pv.plotId,true); - dispatchDestroyDrawLayer(ExtractPointsTool.TYPE_ID); - } - dispatchHideDialog(DIALOG_ID); -} diff --git a/src/firefly/js/visualize/ui/extraction/ExtractionTable.jsx b/src/firefly/js/visualize/ui/extraction/ExtractionTable.jsx index 2265c710d..b4aee8863 100644 --- a/src/firefly/js/visualize/ui/extraction/ExtractionTable.jsx +++ b/src/firefly/js/visualize/ui/extraction/ExtractionTable.jsx @@ -15,9 +15,9 @@ import {CCUtil, CysConverter} from '../../CsysConverter'; import {getExtName} from '../../FitsHeaderUtil'; import {visRoot} from '../../VisStoreRoots'; import { - getAllWaveLengthsForCube, getHDU, getHduPlotStartIndexes, getImageCubeIdx, getPlotViewAry, + getAllWaveLengthsForCube, getHDU, getHduPlotStartIndexes, getCubePlaneIdx, getPlotViewAry, getPtWavelength, getWaveLengthUnits, - hasPixelLevelWLInfo, hasWCSProjection, hasWLInfo, isImageCube, isMultiHDUFits, primePlot, + hasPixelLevelWLInfo, hasWCSProjection, hasWLInfo, isCube, isMultiHDUFits, primePlot, } from '../../PlotViewUtil'; import {makeImagePt} from '../../Point'; import {getFluxUnits, isImage} from '../../WebPlot'; @@ -334,7 +334,7 @@ export function makeDataExtractionTable({baseImPtAry, pv, pvAry, extractionSizeX obj['wlUnit'+idx]= getWaveLengthUnits(workingPlot); obj['filename'+idx]= workingPlot.plotState.getWorkingFitsFileStr(); obj['refHDUNum'+idx]= getHDU(workingPlot); - obj['plane'+idx]= getImageCubeIdx(workingPlot)>-1 ? getImageCubeIdx(workingPlot) : 0; + obj['plane'+idx]= getCubePlaneIdx(workingPlot)>-1 ? getCubePlaneIdx(workingPlot) : 0; return obj; },epBase); if (exclusiveToPlot) { @@ -393,7 +393,7 @@ function makePlaneTitle(rootStr, pv, plot, cnt) { if (getExtName(plot)) hduStr = `- ${getExtName(plot)}`; else hduStr = `- HDU#${getHDU(plot)} `; } - if (isImageCube(plot)) cubeStr = `- Plane: ${getImageCubeIdx(plot) + 1}`; + if (isCube(plot)) cubeStr = `- Plane: ${getCubePlaneIdx(plot) + 1}`; return `${rootStr} ${cnt}${hduStr}${cubeStr}`; } diff --git a/src/firefly/js/visualize/ui/extraction/ExtractionUIUtil.js b/src/firefly/js/visualize/ui/extraction/ExtractionUIUtil.js new file mode 100644 index 000000000..39476de87 --- /dev/null +++ b/src/firefly/js/visualize/ui/extraction/ExtractionUIUtil.js @@ -0,0 +1,75 @@ +import {dispatchHideDialog} from '../../../core/ComponentCntlr'; +import {dispatchCancelActionWatcher} from '../../../core/MasterSaga'; +import ExtractHiPSTileTool from '../../../drawingLayers/ExtractHiPSTileTool'; +import ExtractLineTool from '../../../drawingLayers/ExtractLineTool'; +import ExtractPointsTool from '../../../drawingLayers/ExtractPointsTool'; +import {dispatchDestroyDrawLayer, dispatchDetachLayerFromPlot} from '../../DrawLayerDispatch'; +import {dispatchAttributeChange, dispatchChangePointSelection} from '../../ImagePlotDispatch'; +import {PlotAttribute} from '../../PlotAttribute'; +import {currentP, getPlotViewAry, primePlot} from '../../PlotViewUtil'; +import {visRoot} from '../../VisStoreRoots'; +import {isHiPS} from '../../WebPlot'; + + +export const EXTRACT_DIALOG_ID = 'extractionDialog'; +export const HIPS_TILE_EXTRACT_DIALOG_ID = 'hipsTileExtractionDialog'; +export const ZAXIS_POINT_SELECTION_ID = 'z-axisExtraction'; +export const EXTRACT_END_ID = 'extractEndId'; + +export const Z_AXIS = 'Z_AXIS'; +export const LINE = 'LINE'; +export const POINTS = 'POINTS'; +export const HIPS_TILE= 'HIPS_TILE'; + +export function endExtraction() { + cancelPointExtraction(); + cancelZaxisExtraction(); + cancelLineExtraction(); + cancelHiPSTileExtraction(); + dispatchCancelActionWatcher(EXTRACT_END_ID); +} + +export function cancelHiPSTileExtraction() { + const pvAry= getPlotViewAry(visRoot()).filter( (pv) => isHiPS(primePlot(pv))); + dispatchHideDialog(HIPS_TILE_EXTRACT_DIALOG_ID); + pvAry.forEach( (pv) => { + const plotId= pv.plotId; + dispatchDetachLayerFromPlot(ExtractHiPSTileTool.TYPE_ID, plotId); + dispatchAttributeChange({ + plotId, + changes: { + [PlotAttribute.ACTIVE_HIPS_CELL]: undefined, + [PlotAttribute.ACTIVE_HIPS_NORDER]: undefined + } + }); + }); + dispatchDestroyDrawLayer(ExtractHiPSTileTool.TYPE_ID); + dispatchHideDialog(EXTRACT_DIALOG_ID); +} + +function cancelZaxisExtraction() { + dispatchChangePointSelection(ZAXIS_POINT_SELECTION_ID, false); + dispatchHideDialog(EXTRACT_DIALOG_ID); +} + +export function cancelLineExtraction() { + const {pv} = currentP(); + if (pv) { + dispatchDetachLayerFromPlot(ExtractLineTool.TYPE_ID, pv.plotId, true); + dispatchAttributeChange({ + plotId: pv.plotId, + changes: {[PlotAttribute.SELECT_ACTIVE_CHART_PT]: undefined} + }); + dispatchDestroyDrawLayer(ExtractLineTool.TYPE_ID); + } + dispatchHideDialog(EXTRACT_DIALOG_ID); +} + +function cancelPointExtraction() { + const {pv} = currentP(); + if (pv) { + dispatchDetachLayerFromPlot(ExtractPointsTool.TYPE_ID, pv.plotId, true); + dispatchDestroyDrawLayer(ExtractPointsTool.TYPE_ID); + } + dispatchHideDialog(EXTRACT_DIALOG_ID); +} diff --git a/src/firefly/js/visualize/ui/extraction/HiPSTileExtractionDialog.jsx b/src/firefly/js/visualize/ui/extraction/HiPSTileExtractionDialog.jsx new file mode 100644 index 000000000..7c32d5226 --- /dev/null +++ b/src/firefly/js/visualize/ui/extraction/HiPSTileExtractionDialog.jsx @@ -0,0 +1,149 @@ +import {Stack, Typography} from '@mui/joy'; +import React, {useEffect, useState} from 'react'; +import {dispatchShowDialog} from '../../../core/ComponentCntlr'; +import ExtractHiPSTileTool from '../../../drawingLayers/ExtractHiPSTileTool'; +import {CheckboxGroupInputField} from '../../../ui/CheckboxGroupInputField'; +import {CompleteButton} from '../../../ui/CompleteButton'; +import DialogRootContainer from '../../../ui/DialogRootContainer'; +import {FieldGroup} from '../../../ui/FieldGroup'; +import {PopupPanel} from '../../../ui/PopupPanel'; +import {useFieldValueOnly, useStoreConnector} from '../../../ui/SimpleComponent'; +import {dispatchAttachLayerToPlot, dispatchCreateDrawLayer} from '../../DrawLayerDispatch'; +import {extractFitsFromHiPS} from '../../HiPSUtil'; +import {dispatchChangeActivePlotView, dispatchWcsMatch} from '../../ImagePlotDispatch'; +import {PlotAttribute} from '../../PlotAttribute'; +import {currentP, getDrawLayerByType, getPlotViewAry, isDrawLayerAttached, primePlot} from '../../PlotViewUtil'; +import {WcsMatchType} from '../../VisConst'; +import {getDlAry, visRoot} from '../../VisStoreRoots'; +import {isHiPS} from '../../WebPlot'; +import {endExtraction, HIPS_TILE_EXTRACT_DIALOG_ID} from './ExtractionUIUtil'; + + +export function showHiPSTileExtractionDialog(element, wasCanceled) { + endExtraction(); + const dialog= ; + DialogRootContainer.defineDialog(HIPS_TILE_EXTRACT_DIALOG_ID, dialog, element ); + dispatchShowDialog(HIPS_TILE_EXTRACT_DIALOG_ID); +} + + +function HiPSTileExtractionDialog({wasCanceled}) { + const {pv,plot} = useStoreConnector( () => currentP()); + const hipsPlotCnt = useStoreConnector( + () => getPlotViewAry(visRoot())?.filter( (pv) => isHiPS(primePlot(pv))).length ?? 0); + + const doCancel= () => { + endExtraction(); + wasCanceled?.(); + }; + + useEffect(() => { + startExtraction(pv.plotId); + }, [pv?.plotId]); + + + useEffect(() => { + if (!hipsPlotCnt) doCancel(); + }, [hipsPlotCnt]); + + + const title= isHiPS(plot) + ? `Extract: ${plot?.title ?? ''}` + : 'Not a HiPS image'; + + return( + + + + ); +} + + +function HiPSTileExtractionPanel({pv,plot}) { + return ( + + + + ); +} + + +function TileExtractContent({plot}) { + + const [warn,setWarn] = useState(false); + const useWcs= useFieldValueOnly('wcsMatch', 'wcs'); + const hipsCell= plot?.attributes[PlotAttribute.ACTIVE_HIPS_CELL]; + const norder= plot?.attributes[PlotAttribute.ACTIVE_HIPS_NORDER]; + + useEffect(() => { + if (hipsCell && norder && warn) { + setWarn(false); + } + }, [hipsCell,norder]); + + if (!plot) return; + + if (!isHiPS(plot)) { + return ( + + HiPS tile extraction only available for a HiPS display + + ); + } + + + return ( + + + Click on anywhere on the HiPS display and click 'Extract Tile' + + + {hipsCell && norder + ? `Extract tile: norder:${norder}, tile:${hipsCell?.ipix??'none'}` + : 'No tile selected' + } + + + {!plot.hasFitsCube && + + Warning: Some FITS tiles do not have valid WCS information + } + + extractTile(plot, useWcs==='wcs',setWarn) } /> + {warn && Click on image } + + + ); +} + +function extractTile(plot,useWcsMatch, setWarn) { + if (!plot?.hasFits) return; + const hipsCell= plot.attributes[PlotAttribute.ACTIVE_HIPS_CELL]; + const norder= plot.attributes[PlotAttribute.ACTIVE_HIPS_NORDER]; + if (!hipsCell || !norder) { + setWarn(true); + return; + } + extractFitsFromHiPS(plot,norder,hipsCell.ipix); + if (!useWcsMatch) return; + setTimeout(() => { + dispatchChangeActivePlotView(plot.plotId); + dispatchWcsMatch({ plotId: plot.plotId, matchType: WcsMatchType.Standard}); + }, 5 ); +} + +function startExtraction(plotId) { + const {pv}= currentP(plotId); + if (!pv) return; + const typeId= ExtractHiPSTileTool.TYPE_ID; + let extractDl= getDrawLayerByType(getDlAry(), typeId); + if (!extractDl) dispatchCreateDrawLayer(typeId); + extractDl= getDrawLayerByType(getDlAry(),typeId); + !isDrawLayerAttached(extractDl,pv.plotId) && dispatchAttachLayerToPlot(typeId,pv.plotId,true,true, true); +} \ No newline at end of file diff --git a/src/firefly/test/edu/caltech/ipac/util/serialization/SerializerTest.java b/src/firefly/test/edu/caltech/ipac/util/serialization/SerializerTest.java index b77b11f2b..15f732d7c 100644 --- a/src/firefly/test/edu/caltech/ipac/util/serialization/SerializerTest.java +++ b/src/firefly/test/edu/caltech/ipac/util/serialization/SerializerTest.java @@ -432,8 +432,8 @@ public void progressStatTest() { ); assertNotNull(normalDecoded); + assertEquals(normal.getKey(), normalDecoded.getKey()); assertEquals(normal.getId(), normalDecoded.getId()); - assertEquals(normal.getPlotId(), normalDecoded.getPlotId()); assertEquals(normal.getMessage(), normalDecoded.getMessage()); assertEquals(ProgressStat.PType.DOWNLOADING, normalDecoded.getType()); assertFalse(normalDecoded.isGroup()); @@ -443,7 +443,7 @@ public void progressStatTest() { // group progress // ============================================================ List members = List.of("id-1", "id-2", "id-3"); - ProgressStat group = new ProgressStat(members, "group-999"); + ProgressStat group = new ProgressStat("group-999", members); ProgressStat groupDecoded = Serializer.fromMessagePack( @@ -452,12 +452,12 @@ public void progressStatTest() { ); assertNotNull(groupDecoded); - assertEquals(group.getId(), groupDecoded.getId()); + assertEquals(group.getKey(), groupDecoded.getKey()); assertEquals(ProgressStat.PType.GROUP, groupDecoded.getType()); assertTrue(groupDecoded.isGroup()); assertEquals(members, groupDecoded.getMemberIDList()); assertNull(groupDecoded.getMessage()); - assertNull(groupDecoded.getPlotId()); + assertNull(groupDecoded.getId()); assertFalse(groupDecoded.isDone()); // ============================================================