diff --git a/src/firefly/html/firefly.html b/src/firefly/html/firefly.html index 683c1408b7..dd11285765 100644 --- a/src/firefly/html/firefly.html +++ b/src/firefly/html/firefly.html @@ -10,7 +10,10 @@ IRSA Viewer @@ -19,6 +22,9 @@
+ + + diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/ServerContext.java b/src/firefly/java/edu/caltech/ipac/firefly/server/ServerContext.java index 789a953a7a..31f5b64a01 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/ServerContext.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/ServerContext.java @@ -48,7 +48,7 @@ public class ServerContext { private static final int PFX_START_LEN= PFX_START.length(); public static final String PFX_END= "}"; private static final int PFX_TOTAL_CHAR= 3; - public static final boolean FITS_SECURITY = AppProperties.getBooleanProperty("visualize.fits.Security", true); + public static boolean FITS_SECURITY; public static final String VIS_SEARCH_PATH= "visualize.fits.search.path"; public final static Map _visSessionDirs= new ConcurrentHashMap(617); public static boolean DEBUG_MODE; @@ -146,6 +146,10 @@ public static void configInit() { // disable caching is it's a preference CacheManager.setDisabled(AppProperties.getBooleanProperty(CACHEMANAGER_DISABLED_PROP, false)); + + // Must be done after property init + FITS_SECURITY = AppProperties.getBooleanProperty("visualize.fits.Security", true); + // use EhCache for caching. CacheManager.setCacheProvider(EhcacheProvider.class.getName()); 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 2d81e1e4e5..ba12b3170c 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 @@ -287,6 +287,7 @@ private static ImagePlotInfo[] makeNewPlots(String workingCtxStr, VisContext.purgeOtherPlots(state); state.setOriginalImageIdx(idx, NO_BAND); state.setImageIdx(idx, NO_BAND); + state.setMultiImageFile(true,Band.NO_BAND); //todo: here plotInfo[0] = ImagePlotCreator.makeOneImagePerBand(workingCtxStr, state, readInfoMap, zoomChoice); break; 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 ed7b28f3b9..75fc532510 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 @@ -166,8 +166,9 @@ static ImagePlot createImagePlot(PlotState state, if (plot.getImageDataWidth()!=rWidth || plot.getImageDataWidth()!=rHeight) { String primDim= rWidth+"x"+rHeight; String overDim= plot.getImageDataWidth()+"x"+plot.getImageDataHeight(); - throw new FitsException("Mask Overlay does not match the primary plot dimensions ("+ - overDim+" vs "+primDim+")"); +// throw new FitsException("Mask Overlay does not match the primary plot dimensions ("+ +// overDim+" vs "+primDim+")"); + _log.warn( "Mask Overlay does not match the primary plot dimensions ("+ overDim+" vs "+primDim+")"); } } else { // standard diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotStateUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotStateUtil.java index f7523dcf32..5ca64751eb 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotStateUtil.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/PlotStateUtil.java @@ -73,6 +73,12 @@ public static void initRequestFromState(WebPlotRequest req, PlotState oldState, req.setInitialRangeValues(oldState.getRangeValues(band)); req.setInitialColorTable(oldState.getColorTableId()); req.setInitialZoomLevel(oldState.getZoomLevel()); + WebPlotRequest oldReq= oldState.getPrimaryRequest(); + if (oldReq.isPlotAsMask()) { + req.setPlotAsMask(true); + req.setMaskBits(req.getMaskBits()); + req.setMaskColors(req.getMaskColors().toArray(new String[3])); + } } 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 41ab8a3fb9..2f8c7ad7a6 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 @@ -16,7 +16,6 @@ import edu.caltech.ipac.firefly.visualize.InsertBandInitializer; import edu.caltech.ipac.firefly.visualize.PlotImages; import edu.caltech.ipac.firefly.visualize.PlotState; -import edu.caltech.ipac.firefly.visualize.ProjectionSerializer; import edu.caltech.ipac.firefly.visualize.StretchData; import edu.caltech.ipac.firefly.visualize.WebFitsData; import edu.caltech.ipac.firefly.visualize.WebPlotInitializer; @@ -71,7 +70,8 @@ public static JSONObject serializeWebPlotInitializerDeep(WebPlotInitializer wpIn } public static JSONObject serializeProjection(WebPlotInitializer wpInit) { - Projection proj= ProjectionSerializer.deserializeProjection(wpInit.getProjectionSerialized()); +// Projection proj= ProjectionSerializer.deserializeProjection(wpInit.getProjectionSerialized()); + Projection proj= wpInit.getProjection(); if (proj==null) return null; JSONObject map = new JSONObject(); map.put("coorindateSys", proj.getCoordinateSys().toString()); @@ -133,6 +133,10 @@ public static JSONObject serializeProjectionParams(ProjectionParams p) { map.put("map_distortion", p.map_distortion); map.put("keyword", p.keyword); + for(Map.Entry e : p.additionalHeaders.entrySet() ) { + map.put(e.getKey(),e.getValue()); + } + return map; } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerCommands.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerCommands.java index 0eab018a3c..3ee5bba167 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerCommands.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisServerCommands.java @@ -8,10 +8,8 @@ * Time: 1:27 PM */ -import edu.caltech.ipac.firefly.data.DataEntry; import edu.caltech.ipac.firefly.data.ServerParams; import edu.caltech.ipac.firefly.data.TableServerRequest; -import edu.caltech.ipac.firefly.data.table.TableMeta; import edu.caltech.ipac.firefly.server.ServerCommandAccess; import edu.caltech.ipac.firefly.server.servlets.CommandService; import edu.caltech.ipac.firefly.server.util.Logger; @@ -23,18 +21,18 @@ import edu.caltech.ipac.firefly.visualize.WebPlotRequest; import edu.caltech.ipac.firefly.visualize.WebPlotResult; import edu.caltech.ipac.firefly.visualize.draw.StaticDrawInfo; -import edu.caltech.ipac.util.FileUtil; import edu.caltech.ipac.util.DataGroup; import edu.caltech.ipac.visualize.plot.ImagePt; - import nom.tam.fits.FitsException; import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import java.io.BufferedReader; import java.io.IOException; -import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author Trey Roby @@ -77,7 +75,9 @@ public String doCommand(Map paramMap) throws IllegalArgumentEx SrvParam sp= new SrvParam(paramMap); - PlotState state= sp.getState(); +// PlotState state= sp.getState(); + PlotState stateAry[]= sp.getStateAry(); + PlotState state= stateAry[0]; ImagePt pt = sp.getRequiredImagePt("pt"); FileAndHeaderInfo fahAry[]; @@ -85,8 +85,18 @@ public String doCommand(Map paramMap) throws IllegalArgumentEx for(Band b : state.getBands()) { list.add(state.getFileAndHeaderInfo(b)); } + + if (stateAry.length>1) { + for(int i=1; (i paramMap) throws IllegalArgumentEx JSONObject data= new JSONObject(); Band[] bands = state.getBands(); - for (int i=0; i1) { + for(int i=1; (i paramMap) throws IllegalArgumentException { SrvParam sp= new SrvParam(paramMap); - PlotState state= sp.getState(); - WebPlotResult result = VisServerOps.flipImageOnY(state); + PlotState stateAry[]= sp.getStateAry(); +// PlotState state= sp.getState(); + WebPlotResult result = VisServerOps.flipImageOnY(stateAry); return WebPlotResultSerializer.createJson(result, sp.isJsonDeep()); } } 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 d42327e0f8..d2563a170c 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 @@ -461,7 +461,8 @@ public static WebPlotResult crop(PlotState state, ImagePt c1, ImagePt c2, boolea File workingFilsFile = PlotStateUtil.getWorkingFitsFile(state, bands[i]); String fName = workingFilsFile.getName(); - File cropFile = File.createTempFile(FileUtil.getBase(fName) + "-crop", + String multiStr= state.isMultiImageFile(bands[i]) ? "-multi" : "-"+state.getImageIdx(bands[i]); + File cropFile = File.createTempFile(FileUtil.getBase(fName) + multiStr + "-crop", "." + FileUtil.FITS, ServerContext.getVisSessionDir()); diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotInitializer.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotInitializer.java index 89a19b4578..8b1b7fdd80 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotInitializer.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotInitializer.java @@ -34,6 +34,7 @@ public class WebPlotInitializer implements Serializable, DataEntry { private WebFitsData _fitsData[]; private String _desc; private String _dataDesc; + private transient Projection _projection; @@ -55,6 +56,7 @@ public WebPlotInitializer(PlotState plotState, _initImages= images; _imageCoordSys= imageCoordSys; _projectionSerialized= ProjectionSerializer.serializeProjection(projection); + _projection= projection; _dataWidth= dataWidth; _dataHeight= dataHeight; _imageScaleFactor= imageScaleFactor; @@ -73,7 +75,7 @@ public WebPlotInitializer(PlotState plotState, public Projection getProjection() { // return _projection; - return ProjectionSerializer.deserializeProjection(_projectionSerialized); + return _projection!=null ? _projection : ProjectionSerializer.deserializeProjection(_projectionSerialized); } public String getProjectionSerialized() { return _projectionSerialized; } diff --git a/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotRequest.java b/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotRequest.java index 3b700e77bf..74a9b7f035 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotRequest.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/visualize/WebPlotRequest.java @@ -1149,7 +1149,12 @@ public void setMaskColors(String colors[]) { public List getMaskColors() { if (containsParam(MASK_COLORS)) { String data= getParam(MASK_COLORS); - return StringUtils.parseStringList(data,";"); + List retval= StringUtils.parseStringList(data,";"); + if (retval.size()==0 && data.length()>0) { + retval= new ArrayList<>(1); + retval.add(data); + } + return retval; } else { return Collections.emptyList(); diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/FitsRead.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/FitsRead.java index 8964de339e..ac34ca6647 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/FitsRead.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/FitsRead.java @@ -68,7 +68,7 @@ public class FitsRead implements Serializable { private final int extension_number; private final BasicHDU hdu; private float[] float1d; - private Fits fits; +// private Fits fits; private ImageHeader imageHeader; private Header header; private int indexInFile = -1; // -1 unknown, >=0 index in file @@ -89,8 +89,8 @@ public class FitsRead implements Serializable { private FitsRead(Fits fits, ImageHDU imageHdu) throws FitsException { //assign some instant variables - this.fits = fits; - hdu = imageHdu; +// this.fits = fits; + this.hdu = imageHdu; header = imageHdu.getHeader(); planeNumber = header.getIntValue("SPOT_PL", 0); @@ -132,8 +132,8 @@ private FitsRead(Fits fits, ImageHDU imageHdu) throws FitsException { private FitsRead(Fits fits, ImageHDU imageHdu, int maskExtension) throws FitsException { //assign some instant variables - this.fits = fits; - hdu = imageHdu; +// this.fits = fits; + this.hdu = imageHdu; header = imageHdu.getHeader(); planeNumber = header.getIntValue("SPOT_PL", 0); extension_number = header.getIntValue("SPOT_EXT", -1); @@ -146,7 +146,7 @@ private FitsRead(Fits fits, ImageHDU imageHdu, int maskExtension) throws FitsExc } //get the data and store into float array float1d = getImageHDUDataInFloatArray(imageHdu); - masks = getMasksInFits(maskExtension); + masks = getMasksInFits(fits, maskExtension); hist= computeHistogram(); @@ -162,7 +162,7 @@ private FitsRead(Fits fits, ImageHDU imageHdu, int maskExtension) throws FitsExc } - private short[] getMasksInFits(int maskExtension) throws FitsException { + private short[] getMasksInFits(Fits fits, int maskExtension) throws FitsException { //get all the Header Data Unit from the fits file BasicHDU[] HDUs = fits.read(); @@ -281,8 +281,10 @@ public static FitsImageCube createFitsImageCube(Fits fits)throws FitsException { public static FitsRead createFitsReadFlipLR(FitsRead aFitsReader) throws FitsException, GeomException { - FlipLR flipLr = new FlipLR(); - return (flipLr.do_flip(aFitsReader)); + + return new FlipXY(aFitsReader,"yAxis").doFlip(); +// FlipLR flipLr = new FlipLR(); +// return (flipLr.do_flip(aFitsReader)); } /** @@ -1653,9 +1655,9 @@ public boolean isSameProjection(FitsRead secondFitsread) { } - public Fits getFits() { - return (fits); - } +// public Fits getFits() { +// return (fits); +// } public BasicHDU getHDU() { return hdu; @@ -1838,7 +1840,7 @@ public static float[][] getDataFloat(ImageHDU imageHDU) public void freeResources() { float1d = null; - fits = null; +// fits = null; imageHeader = null; header = null; diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/ImageHeader.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/ImageHeader.java index 36858fec48..ee58877eac 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/ImageHeader.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/ImageHeader.java @@ -11,8 +11,12 @@ import edu.caltech.ipac.visualize.plot.projection.ProjectionParams; import nom.tam.fits.FitsException; import nom.tam.fits.Header; +import nom.tam.fits.HeaderCard; +import nom.tam.util.Cursor; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; public class ImageHeader implements Serializable @@ -73,6 +77,7 @@ public class ImageHeader implements Serializable public double bp[][] = new double[ProjectionParams.MAX_SIP_LENGTH][ProjectionParams.MAX_SIP_LENGTH]; public boolean map_distortion = false; public String keyword; + public Map additionalHeaders= new HashMap<>(77); public ImageHeader() @@ -101,6 +106,18 @@ public ImageHeader(Header header, long HDU_offset, int _plane_number) String ctype1_trim = null; long header_size = header.getOriginalSize(); + + Cursor extraIter= header.iterator(); + for(;extraIter.hasNext();) { + HeaderCard hc= (HeaderCard)extraIter.next(); + if (hc.getKey().startsWith("MP") || hc.getKey().startsWith("HIERARCH.MP")) { + additionalHeaders.put(hc.getKey(), hc.getValue()); + } + } + + + + data_offset = HDU_offset + header_size; plane_number = _plane_number; @@ -709,6 +726,7 @@ public Projection createProjection(CoordinateSys csys) { public static ProjectionParams createProjectionParams(ImageHeader hdr) { ProjectionParams params= new ProjectionParams(); + params.additionalHeaders= hdr.additionalHeaders; params.bitpix= hdr.bitpix; params.naxis = hdr.naxis; params.naxis1= hdr.naxis1; @@ -765,6 +783,8 @@ public static ProjectionParams createProjectionParams(ImageHeader hdr) { params.map_distortion= hdr.map_distortion; params.keyword= hdr.keyword; + + return params; } diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/projection/ProjectionParams.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/projection/ProjectionParams.java index d90c0763b9..8a6143bdec 100644 --- a/src/firefly/java/edu/caltech/ipac/visualize/plot/projection/ProjectionParams.java +++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/projection/ProjectionParams.java @@ -4,8 +4,9 @@ package edu.caltech.ipac.visualize.plot.projection; - import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; public class ProjectionParams implements Serializable { @@ -47,6 +48,7 @@ public class ProjectionParams implements Serializable { public double bp[][] = new double[MAX_SIP_LENGTH][MAX_SIP_LENGTH]; public boolean map_distortion = false; public String keyword; + public Map additionalHeaders= new HashMap<>(); public ProjectionParams() {} } diff --git a/src/firefly/js/core/ReduxFlux.js b/src/firefly/js/core/ReduxFlux.js index fe350a96dc..42a502d902 100644 --- a/src/firefly/js/core/ReduxFlux.js +++ b/src/firefly/js/core/ReduxFlux.js @@ -14,11 +14,11 @@ import FieldGroupCntlr, {valueChangeActionCreator,multiValueChangeActionCreator} from '../fieldGroup/FieldGroupCntlr.js'; import * as MouseReadoutCntlr from '../visualize/MouseReadoutCntlr.js'; import ImagePlotCntlr, {IMAGE_PLOT_KEY, - plotImageActionCreator, zoomActionCreator, + plotImageActionCreator, plotImageMaskActionCreator, zoomActionCreator, colorChangeActionCreator, stretchChangeActionCreator, rotateActionCreator, flipActionCreator, cropActionCreator, autoPlayActionCreator, changePrimeActionCreator, - restoreDefaultsActionCreator, + restoreDefaultsActionCreator, overlayPlotChangeAttributeActionCreator, changePointSelectionActionCreator} from '../visualize/ImagePlotCntlr.js'; import ExternalAccessCntlr from './ExternalAccessCntlr.js'; @@ -118,6 +118,8 @@ actionCreators.set(FieldGroupCntlr.VALUE_CHANGE, valueChangeActionCreator); actionCreators.set(FieldGroupCntlr.MULTI_VALUE_CHANGE, multiValueChangeActionCreator); actionCreators.set(ExternalAccessCntlr.EXTENSION_ACTIVATE, ExternalAccessCntlr.extensionActivateActionCreator); actionCreators.set(ImagePlotCntlr.PLOT_IMAGE, plotImageActionCreator); +actionCreators.set(ImagePlotCntlr.PLOT_MASK, plotImageMaskActionCreator); +actionCreators.set(ImagePlotCntlr.OVERLAY_PLOT_CHANGE_ATTRIBUTES, overlayPlotChangeAttributeActionCreator); actionCreators.set(ImagePlotCntlr.ZOOM_IMAGE, zoomActionCreator); actionCreators.set(ImagePlotCntlr.COLOR_CHANGE, colorChangeActionCreator); actionCreators.set(ImagePlotCntlr.STRETCH_CHANGE, stretchChangeActionCreator); diff --git a/src/firefly/js/drawingLayers/Catalog.js b/src/firefly/js/drawingLayers/Catalog.js index 283441d689..f4ea1c5d39 100644 --- a/src/firefly/js/drawingLayers/Catalog.js +++ b/src/firefly/js/drawingLayers/Catalog.js @@ -50,7 +50,7 @@ var createCnt= 0; function creator(initPayload) { const {catalogId, tableData, tableMeta, title, - selectInfo, columns, tableRequest, highlightedRow, color, + selectInfo, columns, tableRequest, highlightedRow, color, angleInRadian=false, dataTooBigForSelection=false, catalog=true,boxData=false }= initPayload; var drawingDef= makeDrawingDef(); drawingDef.symbol= DrawSymbol.SQUARE; @@ -86,6 +86,7 @@ function creator(initPayload) { dl.columns= columns; dl.catalog= catalog; dl.boxData= boxData; + dl.angleInRadian= angleInRadian; createCnt++; return dl; @@ -122,8 +123,15 @@ function makeHighlightDeferred(drawLayer,plotId,screenPt) { const id= window.setInterval( () => { if (done) { window.clearInterval(id); + const {tableMeta, tableData}= drawLayer; if (closestIdx > -1) { - dispatchTableHighlight(drawLayer.drawLayerId,closestIdx,tableRequest); + if (tableMeta.decimate_key) { + const colIdx= tableData.columns.findIndex((c) => c.name==='rowidx'); + dispatchTableHighlight(drawLayer.drawLayerId,tableData.data[closestIdx][colIdx],tableRequest); + } + else { + dispatchTableHighlight(drawLayer.drawLayerId,closestIdx,tableRequest); + } } } @@ -195,35 +203,42 @@ function getDrawData(dataType, plotId, drawLayer, action, lastDataRet) { * @return {[]} build and return an array of PointDataObj for drawing. */ function computeDrawLayer(drawLayer, tableData, columns) { - return drawLayer.boxData ? computeBoxDrawLayer(tableData,columns) : computePointDrawLayer(tableData,columns); + return drawLayer.boxData ? computeBoxDrawLayer(drawLayer, tableData,columns) : computePointDrawLayer(drawLayer, tableData,columns); } -function computePointDrawLayer(tableData, columns) { +function toAngle(d, radianToDegree) { + const v= Number(d); + return (!isNaN(v) && radianToDegree) ? v*180/Math.PI : v; +} + +function computePointDrawLayer(drawLayer, tableData, columns) { const lonIdx= findColIdx(tableData.columns, columns.lonCol); const latIdx= findColIdx(tableData.columns, columns.latCol); + const {angleInRadian:rad}= drawLayer; if (lonIdx<0 || latIdx<0) return null; return tableData.data.map( (d) => { - const wp= makeWorldPt( d[lonIdx], d[latIdx], columns.csys); + const wp= makeWorldPt( toAngle(d[lonIdx],rad), toAngle(d[latIdx],rad), columns.csys); return PointDataObj.make(wp, 5, DrawSymbol.SQUARE); }); } -function computeBoxDrawLayer(tableData, columns) { +function computeBoxDrawLayer(drawLayer, tableData, columns) { // const lonIdx= findColIdx(tableData.columns, columns.lonCol); // const latIdx= findColIdx(tableData.columns, columns.latCol); // if (lonIdx<0 || latIdx<0) return null; - - + + const {angleInRadian:rad}= drawLayer; + return tableData.data.map( (d) => { const fp= columns.map( (c) => { const lonIdx= findColIdx(tableData.columns, c.lonCol); const latIdx= findColIdx(tableData.columns, c.latCol); - return makeWorldPt( d[lonIdx], d[latIdx], c.csys); + return makeWorldPt( toAngle(d[lonIdx],rad), toAngle(d[latIdx],rad), c.csys); }); return FootprintObj.make([fp]); }); diff --git a/src/firefly/js/rpc/PlotServicesJson.js b/src/firefly/js/rpc/PlotServicesJson.js index f3d1a59019..e7599d22c9 100644 --- a/src/firefly/js/rpc/PlotServicesJson.js +++ b/src/firefly/js/rpc/PlotServicesJson.js @@ -24,7 +24,7 @@ export const callGetColorHistogram= function(state,band,width,height) { paramList.push({name:ServerParams.BAND, value: band.key}); paramList.push({name:ServerParams.JSON_DEEP,value:'true'}); - return doJsonRequest(ServerParams.HISTOGRAM, paramList) + return doJsonRequest(ServerParams.HISTOGRAM, paramList, true); }; /** @@ -39,7 +39,7 @@ export function callGetWebPlot3Color(redRequest, greenRequest, blueRequest) { if (greenRequest) paramList.push({name:ServerParams.GREEN_REQUEST, value:greenRequest.toString()}); if (blueRequest) paramList.push({name:ServerParams.BLUE_REQUEST, value:blueRequest.toString()}); paramList.push({name:ServerParams.JSON_DEEP,value:'true'}); - return doJsonRequest(ServerParams.CREATE_PLOT, paramList); + return doJsonRequest(ServerParams.CREATE_PLOT, paramList, true); }; /** @@ -63,6 +63,7 @@ export function callGetWebPlotGroup(reqAry, progressKey) { return doJsonRequest(ServerParams.CREATE_PLOT_GROUP, paramList,true); } + /** * * @param stateAry @@ -74,7 +75,7 @@ export function callRotateNorth(stateAry, north, newZoomLevel) { {name: ServerParams.NORTH, value: north + ''}, {name: ServerParams.ZOOM, value: newZoomLevel + ''}, ]); - return doJsonRequest(ServerParams.ROTATE_NORTH, params); + return doJsonRequest(ServerParams.ROTATE_NORTH, params, true); } /** @@ -90,7 +91,7 @@ export function callRotateToAngle(stateAry, rotate, angle, newZoomLevel) { {name: ServerParams.ANGLE, value: angle + ''}, {name: ServerParams.ZOOM, value: newZoomLevel + ''}, ]); - return doJsonRequest(ServerParams.ROTATE_ANGLE, params); + return doJsonRequest(ServerParams.ROTATE_ANGLE, params, true); } @@ -103,7 +104,7 @@ export function callGetAreaStatistics(state, ipt1, ipt2, ipt3, ipt4) { [ServerParams.PT3]: ipt3.toString(), [ServerParams.PT4]: ipt4.toString() }; - return doJsonRequest(ServerParams.STAT, params); + return doJsonRequest(ServerParams.STAT, params, true); } @@ -118,7 +119,7 @@ export function callSetZoomLevel(stateAry, level, isFullScreen) { {name:ServerParams.LEVEL, value:level}, {name:ServerParams.FULL_SCREEN, value : isFullScreen}, ]); - return doJsonRequest(ServerParams.ZOOM, params); + return doJsonRequest(ServerParams.ZOOM, params, true); } @@ -128,7 +129,7 @@ export function callChangeColor(state, colorTableId) { {name:ServerParams.JSON_DEEP,value:'true'}, {name:ServerParams.COLOR_IDX, value:colorTableId} ]; - return doJsonRequest(ServerParams.CHANGE_COLOR, params); + return doJsonRequest(ServerParams.CHANGE_COLOR, params, true); } export function callRecomputeStretch(state, stretchDataAry) { @@ -137,7 +138,7 @@ export function callRecomputeStretch(state, stretchDataAry) { [ServerParams.JSON_DEEP]: true }; stretchDataAry.forEach( (sd,idx) => params[ServerParams.STRETCH_DATA+idx]= JSON.stringify(sd)); - return doJsonRequest(ServerParams.STRETCH, params); + return doJsonRequest(ServerParams.STRETCH, params, true); } @@ -150,7 +151,7 @@ export function callCrop(stateAry, corner1ImagePt, corner2ImagePt, cropMultiAll) {name:ServerParams.CRO_MULTI_ALL, value: cropMultiAll +''} ]); - return doJsonRequest(ServerParams.CROP, params); + return doJsonRequest(ServerParams.CROP, params, true); } //LZ 3/22/16 DM-4494 @@ -161,35 +162,26 @@ export function callGetFitsHeaderInfo(plotState, tableId) { tableId }; - var result = doJsonRequest(ServerParams.FITS_HEADER, params); + var result = doJsonRequest(ServerParams.FITS_HEADER, params, true); return result;//doJsonRequest(ServerParams.FITS_HEADER, params); } export function callFlipImageOnY(stateAry) { - var state= stateAry[0]; //todo support state array, work must be done on server - //var params = makeParamsWithStateAry(stateAry,[ - // {name: ServerParams.JSON_DEEP, value: true}, - //]); - var params= { - [ServerParams.STATE]: state.toJson(), - [ServerParams.JSON_DEEP]: true - }; + var params = makeParamsWithStateAry(stateAry); + return doJsonRequest(ServerParams.FLIP_Y, params, true); +} - return doJsonRequest(ServerParams.FLIP_Y, params); -} +export function callGetFileFlux(stateAry, pt) { + var params = makeParamsWithStateAry(stateAry,[ + {name: [ServerParams.PT], value: pt.toString()} + ]); -export function callGetFileFlux(state, pt) { - var params= { - [ServerParams.STATE]: state.toJson(), - [ServerParams.PT]: pt.toString(), - [ServerParams.JSON_DEEP]: true - }; - return doJsonRequest(ServerParams.FILE_FLUX_JSON, params); + return doJsonRequest(ServerParams.FILE_FLUX_JSON, params,true); } export function getDS9Region(fileKey) { @@ -198,7 +190,7 @@ export function getDS9Region(fileKey) { [ServerParams.FILE_KEY]: fileKey, [ServerParams.JSON_DEEP]: true }; - return doJsonRequest(ServerParams.DS9_REGION, params); + return doJsonRequest(ServerParams.DS9_REGION, params, true); } diff --git a/src/firefly/js/tables/TableUtil.js b/src/firefly/js/tables/TableUtil.js index 74e268880e..e34453106a 100644 --- a/src/firefly/js/tables/TableUtil.js +++ b/src/firefly/js/tables/TableUtil.js @@ -555,4 +555,14 @@ export function uniqueTblId() { export function uniqueTblUiId() { return uniqueId('tbl_ui_id-'); +} + +/** + * This function provides a patch until we can reliably determine that the ra/dec columns use radians or degrees. + * @param tableOrMeta the table object or the tableMeta object + */ +export function isTableUsingRadians(tableOrMeta) { + if (!tableOrMeta) return false; + const tableMeta= tableOrMeta.tableMeta || tableOrMeta; + return has(tableMeta, 'HIERARCH.AFW_TABLE_VERSION'); } \ No newline at end of file diff --git a/src/firefly/js/ui/ColorPicker.jsx b/src/firefly/js/ui/ColorPicker.jsx index d975f5983b..2ab9a7bdea 100644 --- a/src/firefly/js/ui/ColorPicker.jsx +++ b/src/firefly/js/ui/ColorPicker.jsx @@ -11,10 +11,10 @@ import ColorPicker from 'react-color'; -export function showColorPickerDialog(color,callbackOnOK,cb) { +export function showColorPickerDialog(color,callbackOnOKOnly, callbackOnBoth, cb ) { const popup= ( - + ); DialogRootContainer.defineDialog('ColorPickerDialog', popup); @@ -23,13 +23,16 @@ export function showColorPickerDialog(color,callbackOnOK,cb) { var lastEv; -function ColorPickerWrapper ({callback,color,callbackOnOK}) { +function ColorPickerWrapper ({callback,color,callbackOnOKOnly, callbackOnBoth}) { return (
callbackOnOK ? lastEv=ev : callback(ev) } + onChangeComplete={ (ev) => { + lastEv=ev; + if (!callbackOnOKOnly) callback(ev, false); + } } color={color} /> - callbackOnOK ? callback(lastEv): null} + callbackOnOKOnly||callbackOnBoth ? callback(lastEv,true): null} dialogId='ColorPickerDialog'/>
); @@ -38,5 +41,6 @@ function ColorPickerWrapper ({callback,color,callbackOnOK}) { ColorPickerWrapper.propTypes= { callback: React.PropTypes.func.isRequired, color: React.PropTypes.string.isRequired, - callbackOnOK: React.PropTypes.bool.isRequired + callbackOnOKOnly: React.PropTypes.bool.isRequired, + callbackOnBoth: React.PropTypes.bool.isRequired }; diff --git a/src/firefly/js/visualize/ImageBoundsData.js b/src/firefly/js/visualize/ImageBoundsData.js index 771611b0ca..d3a09f6ada 100644 --- a/src/firefly/js/visualize/ImageBoundsData.js +++ b/src/firefly/js/visualize/ImageBoundsData.js @@ -45,6 +45,9 @@ export const makeRoughGuesser= function(cc) { var minDec=5000; var maxDec= -5000; + // if I can find the corners then rough guess will not work + if (!topLeft || !topRight || !bottomLeft || !bottomRight) return () => true; + [topLeft, topRight, bottomLeft, bottomRight].forEach( (wp) => { if (wp.getLon() < minRa) minRa= wp.getLon(); if (wp.getLon() > maxRa) maxRa= wp.getLon(); diff --git a/src/firefly/js/visualize/ImageOverlayTask.js b/src/firefly/js/visualize/ImageOverlayTask.js new file mode 100644 index 0000000000..44bfcd71cf --- /dev/null +++ b/src/firefly/js/visualize/ImageOverlayTask.js @@ -0,0 +1,171 @@ +/* + * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt + */ + +import {logError} from '../util/WebUtil.js'; +import ImagePlotCntlr, {makeUniqueRequestKey, IMAGE_PLOT_KEY, dispatchPlotMask} from './ImagePlotCntlr.js'; +import {primePlot, getOverlayByPvAndId, getPlotViewById} from './PlotViewUtil.js'; +import {PlotState, RotateType} from './PlotState.js'; +import {RequestType} from './RequestType.js'; +import {ZoomType} from './ZoomType.js'; +import {clone} from '../util/WebUtil.js'; +import {WebPlot} from './WebPlot.js'; +import {callGetWebPlot} from '../rpc/PlotServicesJson.js'; + + +function *getColor() { + const autoColor= ['#FF0000','#00FF00','#0000FF','#91D33D', '#AE14DB','#FF0000', '#EBAA38', '#F6E942']; + var nextColor=0; + while (true) { + yield autoColor[nextColor % autoColor.length]; + nextColor++; + } +} +const colorChoose= getColor(); + +/** + * + */ +const nextColor= () => colorChoose.next().value; + +/** + * + * @param rawAction + * @return {Function} + */ +export function plotImageMaskActionCreator(rawAction) { + return (dispatcher,getStore) => { + const vr= getStore()[IMAGE_PLOT_KEY]; + + const {plotId,imageOverlayId, maskValue, imageNumber, title,fileKey, maskNumber}= rawAction.payload; + var {color}= rawAction.payload; + if (!color) color= nextColor(); + + + const pv= getPlotViewById(vr, plotId); + const maskRequest= makeMaskRequest(fileKey,imageOverlayId ,pv,maskValue,imageNumber, color); + + + var payload= { + plotId, + maskValue, + maskNumber, + imageNumber, + color, + title, + imageOverlayId, + maskRequest, + requestKey: makeUniqueRequestKey() + }; + dispatcher({type:ImagePlotCntlr.PLOT_MASK_START, payload}); + + + + callGetWebPlot(maskRequest).then( (wpResult) => processMaskSuccessResponse(dispatcher,payload,wpResult) ) + .catch ( (e) => { + dispatcher( { type: ImagePlotCntlr.PLOT_MASK_FAIL, payload: clone(rawAction.payload, {error:e}) } ); + logError(`plot mask error, plotId: ${plotId}`, e); + }); + + + }; +} + +export function overlayPlotChangeAttributeActionCreator(rawAction) { + return (dispatcher,getStore) => { + dispatcher(rawAction); + if (rawAction.payload.doReplot) { + const {plotId,imageOverlayId}= rawAction.payload; + const vr= getStore()[IMAGE_PLOT_KEY]; + const opv= getOverlayByPvAndId(vr,plotId, imageOverlayId); + if (!opv) return; + const {imageNumber,color, maskValue, title}= opv; + dispatchPlotMask({plotId,imageOverlayId, maskValue, imageNumber, color, title}); + } + }; +} + + +/** + * + * @param fileKey + * @param imageOverlayId + * @param pv + * @param maskValue + * @param imageNumber + * @param color + * @return {*} + */ +function makeMaskRequest(fileKey, imageOverlayId, pv, maskValue, imageNumber, color) { + const plot= primePlot(pv); + const state= plot ? plot.plotState : null; + + var originalRequest= state ? state.getWebPlotRequest(): pv.request; + + const r= originalRequest.makeCopy(); + if (fileKey) { + r.setRequestType(RequestType.FILE); + r.setFileName(fileKey); + } + + r.setMaskBits(maskValue); + r.setPlotId(imageOverlayId); + r.setPlotAsMask(true); + r.setMaskColors([color]); + if (plot) { + r.setMaskRequiredWidth(plot.dataWidth); + r.setMaskRequiredHeight(plot.dataHeight); + } + r.setMultiImageIdx(imageNumber); + + //TODO check flip and set handle flip case + if (state) { + r.setZoomType(ZoomType.STANDARD); + r.setInitialZoomLevel(state.getZoomLevel()); + if (state.isRotated()) { + const rt= state.getRotateType(); + r.setMultiImageIdx(0); + if (rt===RotateType.NORTH) { + r.setRotateNorth(true); + } + else if (rt===RotateType.ANGLE) { + r.setRotate(true); + r.setRotationAngle(state.getRotationAngle()); + } + } + else { + r.setRotate(false); + r.setRotateNorth(false); + r.setRotationAngle(0); + } + r.setFlipY(state.isFlippedY()); + r.setFlipX(false);//todo handle flip x + } + return r; +} + + + +function processMaskSuccessResponse(dispatcher, payload, result) { + + if (result.success) { + const {PlotCreate}= result; + + const plotState= PlotState.makePlotStateWithJson(PlotCreate[0].plotState); + const imageOverlayId= plotState.getWebPlotRequest().getPlotId(); + + var plot= WebPlot.makeWebPlotData(imageOverlayId, PlotCreate[0], {}, true); + const resultPayload= clone(payload, {plot}); + dispatcher({type: ImagePlotCntlr.PLOT_MASK, payload: resultPayload}); + } + else { + const resultPayload= Object.assign({},payload); + // todo: add failure stuff to resultPayload here + resultPayload.briefDescription= result.briefFailReason; + resultPayload.description= 'Plot Failed- ' + result.userFailReason; + resultPayload.detailFailReason= result.detailFailReason; + dispatcher( { type: ImagePlotCntlr.PLOT_MASK_FAIL, payload:resultPayload} ); + } + +} + diff --git a/src/firefly/js/visualize/ImagePlotCntlr.js b/src/firefly/js/visualize/ImagePlotCntlr.js index 11e3986a92..b05553855d 100644 --- a/src/firefly/js/visualize/ImagePlotCntlr.js +++ b/src/firefly/js/visualize/ImagePlotCntlr.js @@ -29,6 +29,7 @@ import {dispatchReplaceImages, getExpandedViewerPlotIds, getMultiViewRoot, EXPANDED_MODE_RESERVED} from './MultiViewCntlr.js'; export {zoomActionCreator} from './ZoomUtil.js'; +export {plotImageMaskActionCreator, overlayPlotChangeAttributeActionCreator} from './ImageOverlayTask.js'; export {colorChangeActionCreator, stretchChangeActionCreator, @@ -125,6 +126,15 @@ const CHANGE_MOUSE_READOUT_MODE=`${PLOTS_PREFIX}.changeMouseReadoutMode`; /** Action Type: delete a plotView */ const DELETE_PLOT_VIEW=`${PLOTS_PREFIX}.deletePlotView`; + +const PLOT_MASK_START= `${PLOTS_PREFIX}.plotMaskStart`; +/** Action Type: add a mask image*/ +const PLOT_MASK=`${PLOTS_PREFIX}.plotMask`; +const PLOT_MASK_FAIL= `${PLOTS_PREFIX}.plotMaskFail`; +const DELETE_OVERLAY_PLOT=`${PLOTS_PREFIX}.deleteOverlayPlot`; +const OVERLAY_PLOT_CHANGE_ATTRIBUTES=`${PLOTS_PREFIX}.overlayPlotChangeAttributes`; + + const PLOT_PROGRESS_UPDATE= `${PLOTS_PREFIX}.PlotProgressUpdate`; const API_TOOLS_VIEW= `${PLOTS_PREFIX}.apiToolsView`; @@ -211,7 +221,9 @@ export default { CHANGE_POINT_SELECTION, CHANGE_EXPANDED_MODE, PLOT_PROGRESS_UPDATE, UPDATE_VIEW_SIZE, PROCESS_SCROLL, RECENTER, GROUP_LOCKING, RESTORE_DEFAULTS, CHANGE_PLOT_ATTRIBUTE,EXPANDED_AUTO_PLAY, - DELETE_PLOT_VIEW, CHANGE_ACTIVE_PLOT_VIEW, CHANGE_PRIME_PLOT + DELETE_PLOT_VIEW, CHANGE_ACTIVE_PLOT_VIEW, CHANGE_PRIME_PLOT, + PLOT_MASK, PLOT_MASK_START, PLOT_MASK_FAIL, DELETE_OVERLAY_PLOT, + OVERLAY_PLOT_CHANGE_ATTRIBUTES }; @@ -380,7 +392,7 @@ export function dispatchProcessScroll({plotId,scrollPt, dispatcher= flux.process * * Note - function parameter is a single object * @param {string} plotId - * @param { + * @param centerPt * @param {function} dispatcher only for special dispatching uses such as remote */ export function dispatchRecenter({plotId, centerPt, dispatcher= flux.process}) { @@ -431,6 +443,8 @@ export function dispatchPlotImage({plotId,wpRequest, threeColor=isArray(wpReques * * Note - function parameter is a single object * @param wpRequestAry + * @param viewerId + * @param pvOptions PlotView init Options * @param {function} dispatcher only for special dispatching uses such as remote */ export function dispatchPlotGroup({wpRequestAry, viewerId, pvOptions= {}, dispatcher= flux.process}) { @@ -438,6 +452,44 @@ export function dispatchPlotGroup({wpRequestAry, viewerId, pvOptions= {}, dispat } +/** + * Add a mask + * @param plotId + * @param maskValue power of 2, e.g 4, 8, 32, 128, etc + * @param maskNumber 2, e.g 4, 8, 32, 128, etc + * @param imageOverlayId + * @param imageNumber hdu number of fits + * @param {string} color - color is optional, if not specified, one is chosen + * @param title + * @param {function} dispatcher only for special dispatching uses such as remote + */ +export function dispatchPlotMask({plotId,imageOverlayId, maskValue, imageNumber, maskNumber=-1, color, title, dispatcher= flux.process}) { + dispatcher( { type: PLOT_MASK, payload: { plotId,imageOverlayId, maskValue, imageNumber, maskNumber, color, title} }); +} + +/** + * + * + * @param plotId + * @param imageOverlayId + * @param {function} dispatcher only for special dispatching uses such as remote + */ +export function dispatchDeleteOverlayPlot({plotId,imageOverlayId, dispatcher= flux.process}) { + dispatcher( { type: DELETE_OVERLAY_PLOT, payload: { plotId,imageOverlayId} }); +} + + +/** + * + * @param plotId + * @param imageOverlayId + * @param attributes any attribute in OverlayPlotView + * @param doReplot if false don't do a replot just change attributes + * @param {function} dispatcher only for special dispatching uses such as remote + */ +export function dispatchOverlayPlotChangeAttributes({plotId,imageOverlayId, attributes, doReplot=false, dispatcher= flux.process}) { + dispatcher( { type: OVERLAY_PLOT_CHANGE_ATTRIBUTES, payload: { plotId,imageOverlayId, attributes, doReplot} }); +} @@ -568,15 +620,6 @@ export function dispatchChangeExpandedMode(expandedMode) { } -/** - * - * @param readoutType - * @param newRadioValue - */ -export function dispatchChangeMouseReadout(readoutType, newRadioValue) { - flux.process({ type: CHANGE_MOUSE_READOUT_MODE, payload: {readoutType, newRadioValue} }); -} - /** * * @param autoPlayOn @@ -606,6 +649,7 @@ export function plotImageActionCreator(rawAction) { return PlotImageTask.makePlotImageAction(rawAction); } + export function restoreDefaultsActionCreator(rawAction) { return (dispatcher, getState) => { const vr= getState()[IMAGE_PLOT_KEY]; @@ -729,6 +773,10 @@ function reducer(state=initState(), action={}) { case CROP_START: case CROP_FAIL: case CROP: + case PLOT_MASK: + case PLOT_MASK_START: + case PLOT_MASK_FAIL: + case DELETE_OVERLAY_PLOT: retState= plotCreationReducer(state,action); break; @@ -749,6 +797,7 @@ function reducer(state=initState(), action={}) { case RECENTER: case GROUP_LOCKING: case PLOT_PROGRESS_UPDATE : + case OVERLAY_PLOT_CHANGE_ATTRIBUTES : case CHANGE_PRIME_PLOT : retState= plotChangeReducer(state,action); break; diff --git a/src/firefly/js/visualize/MenuItemKeys.js b/src/firefly/js/visualize/MenuItemKeys.js index 7ff061ea6d..6cdc49628b 100644 --- a/src/firefly/js/visualize/MenuItemKeys.js +++ b/src/firefly/js/visualize/MenuItemKeys.js @@ -2,6 +2,7 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ +import {get} from 'lodash'; export const MenuItemKeys= { fitsDownload : 'fitsDownload', @@ -32,11 +33,14 @@ export const MenuItemKeys= { fitsHeader: 'fitsHeader' }; -const defaultOff = [MenuItemKeys.lockImage, MenuItemKeys.irsaCatalog]; +const defaultOff = [MenuItemKeys.lockImage, MenuItemKeys.irsaCatalog, MenuItemKeys.maskOverlay]; -export const defMenuItemKeys= Object.keys(MenuItemKeys).reduce((obj,k) => { +const tempMiKeys= Object.keys(MenuItemKeys).reduce((obj,k) => { obj[k]= !defaultOff.includes(k); return obj; },{}); + +export const defMenuItemKeys= Object.assign({}, tempMiKeys, get(window.firefly, 'MenuItemKeys', {})); + diff --git a/src/firefly/js/visualize/PlotChangeTask.js b/src/firefly/js/visualize/PlotChangeTask.js index fc63b5a695..749e499279 100644 --- a/src/firefly/js/visualize/PlotChangeTask.js +++ b/src/firefly/js/visualize/PlotChangeTask.js @@ -322,7 +322,8 @@ function processPlotReplace(dispatcher, result, pv, makeSuccessAction, makeFailA var overlayPlotViews = []; resultAry.forEach((r, i) => { if (i === 0) return; - var plot = WebPlot.makeWebPlotData(pv.plotId, r.data[WebPlotResult.PLOT_CREATE], true); + const {imageOverlayId}= pv.overlayPlotViews[i-1]; + var plot = WebPlot.makeWebPlotData(imageOverlayId, r.data[WebPlotResult.PLOT_CREATE][0], {}, true); overlayPlotViews[i - 1] = {plot}; }); @@ -349,9 +350,8 @@ function getResultAry(result) { function makePlot(wpInit,pv) { - var plot= WebPlot.makeWebPlotData(pv.plotId, wpInit); + var plot= WebPlot.makeWebPlotData(pv.plotId, wpInit, primePlot(pv).attributes); plot.title= primePlot(pv).title; - plot.attributes= primePlot(pv).attributes; return plot; } diff --git a/src/firefly/js/visualize/PlotImageTask.js b/src/firefly/js/visualize/PlotImageTask.js index ca41d2f121..2b3b754bb0 100644 --- a/src/firefly/js/visualize/PlotImageTask.js +++ b/src/firefly/js/visualize/PlotImageTask.js @@ -5,7 +5,7 @@ import {get,isPlainObject,isArray} from 'lodash'; import {logError} from '../util/WebUtil.js'; import {WebPlotRequest, GridOnStatus} from './WebPlotRequest.js'; -import ImagePlotCntlr, {visRoot, makeUniqueRequestKey} from './ImagePlotCntlr.js'; +import ImagePlotCntlr, {visRoot, makeUniqueRequestKey, IMAGE_PLOT_KEY} from './ImagePlotCntlr.js'; import {dlRoot, dispatchCreateDrawLayer, dispatchAttachLayerToPlot} from './DrawLayerCntlr.js'; import {WebPlot,PlotAttribute} from './WebPlot.js'; import CsysConverter from './CsysConverter.js'; @@ -20,7 +20,7 @@ import ActiveTarget from '../drawingLayers/ActiveTarget.js'; import * as DrawLayerCntlr from './DrawLayerCntlr.js'; import {makePostPlotTitle} from './reducer/PlotTitle.js'; import {dispatchAddImages, EXPANDED_MODE_RESERVED} from './MultiViewCntlr.js'; -import {getDrawLayerByType, getConnectedPlotsIds} from './PlotViewUtil.js'; +import {getDrawLayerByType, getConnectedPlotsIds, getActivePlotView} from './PlotViewUtil.js'; import WebGrid from '../drawingLayers/WebGrid.js'; const INIT_STATUS_UPDATE_DELAY= 7000; @@ -131,10 +131,6 @@ function makePlotImageAction(rawAction) { - - - - //======================================== Private ====================================== //======================================== Private ====================================== //======================================== Private ====================================== @@ -185,7 +181,7 @@ export function modifyRequest(pvCtx, r, band) { retval.setInitialZoomLevel(zPref.zooomLevel); } - //for(Map.Entry entry : _reqMods.entrySet()) { //todo, I don't think I need this any more, use for defered loading + //for(Map.Entry entry : _reqMods.entrySet()) { //todo, I don't think I need this any more, use for deferred loading // retval.setParam(new Param(entry.getKey(), entry.getValue())); //} return retval; @@ -287,7 +283,7 @@ function getRequest(payload) { } -const handleSuccess= function(plotCreate, payload) { //TODO: finish +const handleSuccess= function(plotCreate, payload) { const plotState= PlotState.makePlotStateWithJson(plotCreate[0].plotState); const plotId= plotState.getWebPlotRequest().getPlotId(); diff --git a/src/firefly/js/visualize/PlotState.js b/src/firefly/js/visualize/PlotState.js index 54c6e08cd3..b545e45ade 100644 --- a/src/firefly/js/visualize/PlotState.js +++ b/src/firefly/js/visualize/PlotState.js @@ -189,7 +189,7 @@ export class PlotState { getPrimaryWebPlotRequest() { return this.get(this.firstBand()).getWebPlotRequest(); } - setBandVisible(band, visible) { this.get(band).setBandVisible(visible); } + //setBandVisible(band, visible) { this.get(band).setBandVisible(visible); } isBandVisible(band) { return this.get(band).isBandVisible(); } diff --git a/src/firefly/js/visualize/PlotViewUtil.js b/src/firefly/js/visualize/PlotViewUtil.js index 44060af2c1..7cf406eb20 100644 --- a/src/firefly/js/visualize/PlotViewUtil.js +++ b/src/firefly/js/visualize/PlotViewUtil.js @@ -4,12 +4,8 @@ import {difference,isArray,has,isString,omit,isEmpty} from 'lodash'; import {getPlotGroupById} from './PlotGroup.js'; import {makeImagePt, pointEquals} from './Point.js'; -import {CsysConverter} from './CsysConverter.js'; - - - - -const clone = (obj,params={}) => Object.assign({},obj,params); +import {CysConverter} from './CsysConverter.js'; +import {clone} from '../util/WebUtil.js'; /** @@ -156,6 +152,27 @@ export function operateOnOthersInGroup(visRoot,sourcePv,operationFunc, ignoreThr } } +/** + * + * @param {PlotView} plotView + * @param {String} imageOverlayId + * @return {OverlayPlotView} + */ +export function getOverlayById(plotView, imageOverlayId) { + return plotView.overlayPlotViews.find( (opv) => opv.imageOverlayId===imageOverlayId); +} + +/** + * + * @param ref + * @param plotId + * @param imageOverlayId + * @return {OverlayPlotView} + */ +export function getOverlayByPvAndId(ref,plotId,imageOverlayId) { + return getOverlayById(getPlotViewById(ref,plotId),imageOverlayId); +} + //-------------------------------------------------------------- //--------- Drawing Layer outside functions @@ -374,7 +391,8 @@ export function getOnePvOrGroup(plotViewAry, plotId,plotGroup) { /** - * make a new copy of the plotview array with an object set on a cloned copy of the matched plotview. + * First fine the PlotView with the plotId, then clone the PlotView with the changes specified in the object. + * Then return a new PlotView array with the changes. * @param ref visRoot or plotViewAry * @param {string} plotId * @param {{}} obj fields to replace @@ -417,7 +435,7 @@ export function isMultiImageFitsWithSameArea(pv) { var ic4= makeImagePt(w,h); var projName= plot.projection.getProjectionName(); - var cc= CsysConverter.make(plot); + var cc= CysConverter.make(plot); var c1= cc.getWorldCoords(ic1); var c2= cc.getWorldCoords(ic2); @@ -429,7 +447,7 @@ export function isMultiImageFitsWithSameArea(pv) { if (w!==p.dataWidth || h!==p.dataHeight) return false; if (projName!==p.projection.getProjectionName()) return false; - var pCC= CsysConverter.make(p); + var pCC= CysConverter.make(p); var iwc1= pCC.getWorldCoords(ic1); var iwc2= pCC.getWorldCoords(ic2); var iwc3= pCC.getWorldCoords(ic3); diff --git a/src/firefly/js/visualize/WebPlot.js b/src/firefly/js/visualize/WebPlot.js index d6c4ae268e..248930f349 100644 --- a/src/firefly/js/visualize/WebPlot.js +++ b/src/firefly/js/visualize/WebPlot.js @@ -114,10 +114,11 @@ export const WebPlot= { * * @param plotId * @param wpInit init data returned from server + * @param attributes any attributes to initialize * @param asOverlay * @return {WebPlot} */ - makeWebPlotData(plotId, wpInit, asOverlay= false) { + makeWebPlotData(plotId, wpInit, attributes= {}, asOverlay= false, ) { const projection= makeProjection(wpInit.projectionJson); var plotState= PlotState.makePlotStateWithJson(wpInit.plotState); @@ -136,6 +137,7 @@ export const WebPlot= { dataWidth : wpInit.dataWidth, dataHeight : wpInit.dataHeight, imageScaleFactor: wpInit.imageScaleFactor, + title : '', plotDesc : wpInit.desc, dataDesc : wpInit.dataDesc, @@ -143,9 +145,9 @@ export const WebPlot= { //=== Mutable ===================== screenSize: {width:wpInit.dataWidth*zf, height:wpInit.dataHeight*zf}, zoomFactor: zf, - percentOpaque : 1.0, + // percentOpaque : 1.0, alive : true, - attributes: {}, + attributes, viewPort: WebPlot.makeViewPort(0,0,0,0), //=== End Mutable ===================== asOverlay diff --git a/src/firefly/js/visualize/WebPlotRequest.js b/src/firefly/js/visualize/WebPlotRequest.js index 1d86813940..113ff03471 100644 --- a/src/firefly/js/visualize/WebPlotRequest.js +++ b/src/firefly/js/visualize/WebPlotRequest.js @@ -139,7 +139,13 @@ const C= { EXPANDED_TITLE_OPTIONS : 'ExpandedTitleOptions', EXPANDED_TITLE : 'ExpandedTitle', POST_TITLE: 'PostTitle', - PRE_TITLE: 'PreTitle' + PRE_TITLE: 'PreTitle', + + MASK_BITS: 'MaskBits', + PLOT_AS_MASK: 'PlotAsMask', + MASK_COLORS: 'MaskColors', + MASK_REQUIRED_WIDTH: 'MaskRequiredWidth', + MASK_REQUIRED_HEIGHT: 'MaskRequiredHeight' }; @@ -1230,6 +1236,42 @@ export class WebPlotRequest extends ServerRequest { } + setMaskBits(idx) { this.setParam(C.MASK_BITS,idx+''); } + getMaskBits() { return this.containsParam(C.MASK_BITS) ? this.getIntParam(C.MASK_BITS) : 0;} + + setPlotAsMask(plotAsMask) { this.setParam(C.PLOT_AS_MASK, plotAsMask+''); } + isPlotAsMask() { return this.getBooleanParam(C.PLOT_AS_MASK); } + + + setMaskColors(colors) { + if (isArray(colors)) { + this.setParam(C.MASK_COLORS, join(';',...colors)); + } + else { + this.setParam(C.MASK_COLORS, colors); + } + } + + getMaskColors() { + var retList= []; + if (this.containsParam(C.MASK_COLORS)) { + return this.getParam(C.MASK_COLORS).split(';'); + } + return retList; + } + + setMaskRequiredWidth(width) { this.setParam(C.MASK_REQUIRED_WIDTH, width+''); } + + getMaskRequiredWidth() { return this.getIntParam(C.MASK_REQUIRED_WIDTH,0); } + + setMaskRequiredHeight(height) { this.setParam(C.MASK_REQUIRED_HEIGHT, height+''); } + + getMaskRequiredHeight() { return this.getIntParam(C.ASK_REQUIRED_HEIGHT,0); } + + + + + /** * * @param overlayIdList diff --git a/src/firefly/js/visualize/draw/CanvasWrapper.jsx b/src/firefly/js/visualize/draw/CanvasWrapper.jsx index 77d179659b..5b6de7a92e 100644 --- a/src/firefly/js/visualize/draw/CanvasWrapper.jsx +++ b/src/firefly/js/visualize/draw/CanvasWrapper.jsx @@ -15,7 +15,9 @@ import {makeDrawingDef} from './DrawingDef.js'; function updateDrawer(drawer,plot, width, height, drawLayer) { var data, highlightData, selectIdxs; + if (!drawLayer) return; var {drawData}= drawLayer; + drawer.decimate= drawLayer.decimate; var plotId= plot? plot.plotId : null; if (Array.isArray(drawData)) { data= drawData; @@ -65,7 +67,7 @@ function makeCanvasLayers(drawLayer,drawer,w,h) { return retAry; } -const isVisible= (drawLayer,plotId) => drawLayer.visiblePlotIdAry.includes(plotId); +const isVisible= (drawLayer,plotId) => drawLayer && drawLayer.visiblePlotIdAry.includes(plotId); const getDataForPlot= (data,plotId) => { @@ -145,6 +147,7 @@ class CanvasWrapper extends React.Component { this.lastDrawLayer= getDrawLayer ? getDrawLayer() : drawLayer; var dl= this.lastDrawLayer; if (plot && !isVisible(dl,plot.plotId)) return false; + if (!dl) return false; if (Array.isArray(dl)) dl= makeDummyDrawLayer(this.lastDrawLayer); diff --git a/src/firefly/js/visualize/draw/DrawLayer.js b/src/firefly/js/visualize/draw/DrawLayer.js index 7b49d8bf02..10e8221c0f 100644 --- a/src/firefly/js/visualize/draw/DrawLayer.js +++ b/src/firefly/js/visualize/draw/DrawLayer.js @@ -170,6 +170,7 @@ function makeDrawLayer(drawLayerId, canUserDelete: true, destroyWhenAllDetached : false, // hint to controller, when all plots have been detached, destroy this layer helpLine : '', + decimate: false, // enable decimation // drawData contains the components that may be drawn. // Three keys are supported data, highlightData, selectedIdxAry diff --git a/src/firefly/js/visualize/iv/ImageViewerDecorate.jsx b/src/firefly/js/visualize/iv/ImageViewerDecorate.jsx index 2feb81074f..7ad70b5195 100644 --- a/src/firefly/js/visualize/iv/ImageViewerDecorate.jsx +++ b/src/firefly/js/visualize/iv/ImageViewerDecorate.jsx @@ -172,7 +172,7 @@ function makeInlineRightToolbar(visRoot,pv,dlAry,mousePlotId, handleInlineTools,
); @@ -188,7 +188,7 @@ function makeInlineRightToolbar(visRoot,pv,dlAry,mousePlotId, handleInlineTools, return (
- + {makeTileDrawers(plotView,viewPortWidth,viewPortHeight,scrollX,scrollY)} @@ -220,6 +217,43 @@ ImageViewerLayout.propTypes= { }; +/** + * + * @param pv + * @param viewPortWidth + * @param viewPortHeight + * @param scrollX + * @param scrollY + * @return {Array} + */ +function makeTileDrawers(pv,viewPortWidth, viewPortHeight, scrollX, scrollY ) { + + + var plot= primePlot(pv); + const rootDrawer= ( + + + ); + const drawers= pv.overlayPlotViews.filter( (opv) => opv.visible && opv.plot).map( (opv) => { + return ( + + ); + }); + drawers.unshift(rootDrawer); + return drawers; +} + + function makePrevDim(props) { var {width,height,externalWidth,externalHeight,plotView}= props; return { diff --git a/src/firefly/js/visualize/iv/TileDrawer.jsx b/src/firefly/js/visualize/iv/TileDrawer.jsx index 6bab5cb78d..39e5197848 100644 --- a/src/firefly/js/visualize/iv/TileDrawer.jsx +++ b/src/firefly/js/visualize/iv/TileDrawer.jsx @@ -43,11 +43,10 @@ export class TileDrawer extends Component { // } render() { - const { x, y, width, height, plot}= this.props; + const { x, y, width, height, plot, opacity}= this.props; var tileData=plot.serverImages; var tileZoomFactor=plot.plotState.getZoomLevel(); var zoomFactor=plot.zoomFactor; - var opacity=plot.percentOpaque; const scale= zoomFactor / tileZoomFactor; const style=Object.assign({},containerStyle, {width,height}); @@ -71,7 +70,8 @@ TileDrawer.propTypes= { y : PropTypes.number.isRequired, width : PropTypes.number.isRequired, height : PropTypes.number.isRequired, - plot : PropTypes.object.isRequired + plot : PropTypes.object.isRequired, + opacity : PropTypes.number.isRequired, }; diff --git a/src/firefly/js/visualize/iv/TileDrawerCanvas.jsx b/src/firefly/js/visualize/iv/TileDrawerCanvas.jsx index d64c1a6e83..a78af05265 100644 --- a/src/firefly/js/visualize/iv/TileDrawerCanvas.jsx +++ b/src/firefly/js/visualize/iv/TileDrawerCanvas.jsx @@ -59,11 +59,10 @@ export class TileDrawerCanvas extends Component { var {canvas}= this; if (!canvas) return; - var { x, y, width, height, plot}= this.props; + var { x, y, width, height, plot, opacity}= this.props; var tileData = plot.serverImages; var tileZoomFactor = plot.plotState.getZoomLevel(); var zoomFactor = plot.zoomFactor; - var opacity = plot.percentOpaque; const scale = zoomFactor / tileZoomFactor; if (this.loadedImages.serverData!==tileData.images) { @@ -158,7 +157,8 @@ TileDrawerCanvas.propTypes= { y : PropTypes.number.isRequired, width : PropTypes.number.isRequired, height : PropTypes.number.isRequired, - plot : PropTypes.object.isRequired + plot : PropTypes.object.isRequired, + opacity : PropTypes.number.isRequired, }; function makeScreenToVPConverter(plot) { diff --git a/src/firefly/js/visualize/projection/GnomonicProjection.js b/src/firefly/js/visualize/projection/GnomonicProjection.js index 9e3d1dab35..8012b46b5a 100644 --- a/src/firefly/js/visualize/projection/GnomonicProjection.js +++ b/src/firefly/js/visualize/projection/GnomonicProjection.js @@ -110,7 +110,7 @@ export const GnomonicProjection= { if (map_distortion) { // apply SIRTF distortion corrections fsamp_correction = 0.0; - var len= Math.fllor(Math.min(hdr.a_order+1, MAX_SIP_LENGTH)); + var len= Math.floor(Math.min(hdr.a_order+1, MAX_SIP_LENGTH)); for (i = 0; i < len; i++) { for (j = 0; j < len; j++) { if (i + j <= hdr.a_order) { diff --git a/src/firefly/js/visualize/reducer/HandlePlotChange.js b/src/firefly/js/visualize/reducer/HandlePlotChange.js index d9f5aad6c9..0f76c8407a 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotChange.js +++ b/src/firefly/js/visualize/reducer/HandlePlotChange.js @@ -3,7 +3,7 @@ */ import update from 'react-addons-update'; -import {get,isEmpty} from 'lodash'; +import {isEmpty} from 'lodash'; import Cntlr, {ExpandType} from '../ImagePlotCntlr.js'; import PlotView, {replacePlotView, replacePrimaryPlot, changePrimePlot} from './PlotView.js'; import {WebPlot, clonePlotWithZoom, PlotAttribute} from '../WebPlot.js'; @@ -13,12 +13,10 @@ import {PlotPref} from './../PlotPref.js'; import {primePlot, clonePvAry, clonePvAryWithPv, - matchPlotView, applyToOnePvOrGroup, getPlotViewIdxById, getPlotGroupIdxById, findPlotGroup, - hasGroupLock, getPlotViewById} from '../PlotViewUtil.js'; import {makeImagePt, makeWorldPt} from '../Point.js'; import {UserZoomTypes} from '../ZoomUtil.js'; @@ -100,6 +98,11 @@ export function reducer(state, action) { case Cntlr.CHANGE_PRIME_PLOT : retState= makeNewPrimePlot(state,action); break; + + case Cntlr.OVERLAY_PLOT_CHANGE_ATTRIBUTES : + retState= changeOverlayPlotAttributes(state,action); + break; + default: break; } @@ -270,7 +273,6 @@ function makeNewPrimePlot(state,action) { } function changeGroupLocking(state,action) { - var {plotGroupAry}= state; var {plotId,groupLocked}= action.payload; const {plotGroupId} = getPlotViewById(state,plotId); @@ -294,6 +296,18 @@ function workingServerCall(state,action) { } +function changeOverlayPlotAttributes(state,action) { + const {plotId, imageOverlayId, attributes}= action.payload; + const plotViewAry= state.plotViewAry + .map( (pv) => { + if (pv.plotId!==plotId) return pv; + const overlayPlotViews = pv.overlayPlotViews + .map( (opv) => opv.imageOverlayId!==imageOverlayId ? opv : clone(opv,attributes)); + return clone(pv, {overlayPlotViews}); + }); + return clone(state,{plotViewAry}); +} + function updatePlotProgress(state,action) { const {plotId, message:plottingStatus, done}= action.payload; //console.log(`updatePlotProgress: plotId;${plotId}, message:${plottingStatus}, done:${done}`); diff --git a/src/firefly/js/visualize/reducer/HandlePlotCreation.js b/src/firefly/js/visualize/reducer/HandlePlotCreation.js index c24d0b2295..8551d4e7e4 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotCreation.js +++ b/src/firefly/js/visualize/reducer/HandlePlotCreation.js @@ -2,10 +2,11 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ -import {get, uniqBy,unionBy} from 'lodash'; +import {get, uniqBy,unionBy, isEmpty} from 'lodash'; import Cntlr, {ExpandType} from '../ImagePlotCntlr.js'; import PlotView, {makePlotView} from './PlotView.js'; -import {getPlotViewById, clonePvAry} from '../PlotViewUtil.js'; +import {makeOverlayPlotView, replaceOverlayPlots} from './OverlayPlotView.js'; +import {getPlotViewById, clonePvAry, getOverlayById} from '../PlotViewUtil.js'; import PlotGroup from '../PlotGroup.js'; @@ -41,10 +42,26 @@ export function reducer(state, action) { break; case Cntlr.PLOT_IMAGE : retState= addPlot(state,action, true, true); - // activePlotId= action.payload.plotId; // todo: also process adding to history break; + case Cntlr.PLOT_MASK_START: + retState= newOverlayPrep(state,action); + break; + + case Cntlr.PLOT_MASK: + retState= addOverlay(state,action); + break; + + case Cntlr.DELETE_OVERLAY_PLOT: + retState= removeOverlay(state,action); + break; + + + case Cntlr.PLOT_MASK_FAIL: + retState= plotOverlayFail(state,action); + break; + case Cntlr.ROTATE_START : case Cntlr.FLIP_START: case Cntlr.CROP_START: @@ -57,12 +74,9 @@ export function reducer(state, action) { case Cntlr.CROP_FAIL: retState= endServerCallFail(state,action); break; + case Cntlr.ROTATE : - retState= addPlot(state,action, true, false); - break; case Cntlr.FLIP: - retState= addPlot(state,action, true, false); - break; case Cntlr.CROP: retState= addPlot(state,action, true, false); break; @@ -117,6 +131,66 @@ function addPlot(state,action, replace, setActive) { return clone(state, {plotViewAry,activePlotId}); } +function newOverlayPrep(state, action) { + const {plotId, imageOverlayId, imageNumber, maskValue, maskNumber, color, title, drawingDef}= action.payload; + + const pv= getPlotViewById(state, plotId); + if (!pv) return state; + + const overlayPv= getOverlayById(pv, imageOverlayId); + var oPvArray; + if (!overlayPv) { + oPvArray= isEmpty(pv.overlayPlotViews) ? [] : pv.overlayPlotViews.slice(0); + oPvArray.push(makeOverlayPlotView(imageOverlayId, plotId, title, imageNumber, + maskNumber, maskValue, color, drawingDef)); + } + else { + oPvArray= pv.overlayPlotViews.map( (opv) => opv.imageOverlayId===imageOverlayId ? + clone(overlayPv, {imageNumber, maskValue, color, drawingDef, plot:null} ) : opv); + } + + return clone(state, {plotViewAry:clonePvAry(state, plotId, {overlayPlotViews:oPvArray}) } ); +} + + +function addOverlay(state, action) { + const {plotId, imageOverlayId, plot, imageNumber, maskValue, color, drawingDef}= action.payload; + + const plotViewAry= state.plotViewAry.map( (pv) => { + if (pv.plotId!== plotId) return pv; + const overlayPlotViews= pv.overlayPlotViews.map( (opv) => { + if (opv.imageOverlayId!== imageOverlayId) return opv; + return replaceOverlayPlots(opv,plot); + }); + return clone(pv, {overlayPlotViews}); + }); + return clone(state, {plotViewAry}); +} + + +function removeOverlay(state, action) { + const {plotId, imageOverlayId}= action.payload; + const plotViewAry= state.plotViewAry.map( (pv) => { + if (pv.plotId!== plotId) return pv; + const overlayPlotViews= pv.overlayPlotViews.filter( (opv) => opv.imageOverlayId!== imageOverlayId); + return clone(pv, {overlayPlotViews}); + }); + return clone(state, {plotViewAry}); +} + + + +function plotOverlayFail(state,action) { + const {plotId, imageOverlayId, detailFailReason}= action.payload; + + const plotViewAry= state.plotViewAry.map( (pv) => { + if (pv.plotId!==plotId) return pv; + const overlayPlotViews= pv.overlayPlotViews.filter( (opv) => imageOverlayId!==opv.imageOverlayId); + return clone(pv, {overlayPlotViews, plottingStatus:'Overlay failed: '+detailFailReason, serverCall:'fail' }); + }); + + return clone(state, {plotViewAry}); +} function plotFail(state,action) { const {description, plotId}= action.payload; @@ -165,7 +239,8 @@ function preNewPlotPrep(plotViewAry,action) { var pv= getPlotViewById(plotViewAry,plotId); return pv ? clone(pv, { plottingStatus:'Plotting...', plots:[], - primeIdx:-1 + primeIdx:-1, + request: req }) : makePlotView(plotId, req,action.payload.pvOptions); }); diff --git a/src/firefly/js/visualize/reducer/OverlayPlotView.js b/src/firefly/js/visualize/reducer/OverlayPlotView.js new file mode 100644 index 0000000000..c81b35f027 --- /dev/null +++ b/src/firefly/js/visualize/reducer/OverlayPlotView.js @@ -0,0 +1,70 @@ +/* + * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt + */ + +import {clone} from '../../util/WebUtil.js' +import SimpleMemCache from '../../util/SimpleMemCache.js'; + +/** + * @typedef {Object} OverlayPlotView + * The container for the overlay plots + * + * @prop {String} imageOverlayId, immutable + * @prop {String} plotId associated plotId, plot id of image that is overlayed + * @prop {Boolean} maskOverlay true is this overlay is a mask + * @prop {Boolean} visible the image overlay is visibile + * @prop {Number} maskValue the mask value to plot + * @prop {Number} imageNumber the hdu of the image to plot, starts with 0 + * @prop {String} color the color, if overlay is a mask + * @prop {Number} opacity how transparent the overlay should be displayed + */ + + +/** + * + * @param imageOverlayId + * @param plotId plot id of image that is overlayed + * @param imageNumber + * @param maskValue + * @param maskNumber + * @param color the color, if overlay is a mask + * @param {String} title + * @param drawingDef + * @return {OverlayPlotView} + */ +export function makeOverlayPlotView(imageOverlayId, plotId, title, imageNumber, maskNumber, maskValue, color, drawingDef) { + + var opv= { + imageOverlayId, + plotId, + title, + plot: null, + drawingDef, + makeOverlay : true, + visible: true, + maskNumber, + maskValue, + imageNumber, + color, + opacity: .58, + plotCounter:0, // index of how many plots, used for making next ID + plottingStatus:'', + serverCall:'success' + }; + + return opv; +} + +export function replaceOverlayPlots(opv, plot) { + + opv= clone(opv, {plot}); + SimpleMemCache.clearCache(plot.plotImageId); + SimpleMemCache.clearCache(plot.plotImageId); + plot.plotImageId= `${opv.imageOverlayId}--${opv.plotCounter}`; + opv.plotCounter++; + + opv.plottingStatus=''; + opv.serverCall='success'; + + return opv; +} diff --git a/src/firefly/js/visualize/reducer/PlotView.js b/src/firefly/js/visualize/reducer/PlotView.js index f3d573d2a9..3b5aa001da 100644 --- a/src/firefly/js/visualize/reducer/PlotView.js +++ b/src/firefly/js/visualize/reducer/PlotView.js @@ -9,8 +9,9 @@ */ import update from 'react-addons-update'; -import {isEqual,unionBy, get} from 'lodash'; +import {isEqual, get} from 'lodash'; import {WebPlot, PlotAttribute} from './../WebPlot.js'; +import {clone} from '../../util/WebUtil.js'; import {WPConst} from './../WebPlotRequest.js'; import {RotateType} from './../PlotState.js'; import {makeScreenPt} from './../Point.js'; @@ -25,8 +26,6 @@ import {CCUtil, CysConverter} from './../CsysConverter.js'; import {defMenuItemKeys} from '../MenuItemKeys.js'; import {ExpandType} from '../ImagePlotCntlr.js'; -// export const DATASET_INFO_CONVERTER = 'DATASET_INFO_CONVERTER'; - const DEF_WORKING_MSG= 'Plotting '; @@ -90,6 +89,7 @@ export function makePlotView(plotId, req, pvOptions= {}) { plotGroupId: req.getPlotGroupId(), //should never change drawingSubGroupId: req.getDrawingSubGroupId(), //todo, string, this is an id, should never change plots:[], + request: req, plottingStatus:'Plotting...', serverCall:'success', // one of 'success', 'working', 'fail' primeIdx:-1, @@ -98,8 +98,7 @@ export function makePlotView(plotId, req, pvOptions= {}) { scrollX : -1, scrollY : -1, viewDim : {width:0, height:0}, // size of viewable area (div size: offsetWidth & offsetHeight) - overlayPlotViews: [], //todo - attributes: {}, //todo, i hope to remove this an only hold attributes on web plot + overlayPlotViews: [], menuItemKeys: makeMenuItemKeys(req,pvOptions,defMenuItemKeys), // normally wil not change plotViewCtx: createPlotViewContextData(req, pvOptions), @@ -111,7 +110,6 @@ export function makePlotView(plotId, req, pvOptions= {}) { workingMsg : DEF_WORKING_MSG, hasNewPlotContainer: req.getHasNewPlotContainer(), // if image selection dialog come up, allow to create a new MiniPlotWidth, todo control with MenuItemKeys saveCorners : req.getSaveCorners(), // save the four corners of the plot to the ActiveTarget singleton, todo - turnOnGridAfterPlot: req.getGridOn(), // turn on the grid after plot, todo expandedTitleOptions: req.getExpandedTitleOptions(), annotationOps : req.getAnnotationOps(), // how titles are drawn @@ -122,7 +120,7 @@ export function makePlotView(plotId, req, pvOptions= {}) { // todo- the follow should be removed when implemented, menuItemKeys will now control option visibility allowImageLock : false, // show the image lock button in the toolbar, todo - allowImageSelect: req.isAllowImageSelection(), // show the image selection button in the toolbar, user can change image, todo + allowImageSelect: req.isAllowImageSelection(), // i think i can remove, show the image selection button in the toolbar, user can change image, todo catalogButton : false // show the catalog select button, todo } @@ -136,7 +134,7 @@ export function makePlotView(plotId, req, pvOptions= {}) { function createPlotViewContextData(req, pvOptions) { return { userCanDeletePlots: get(pvOptions, 'userCanDeletePlots', true), - rotateNorthLock : false,// todo MAYBE!!! // rotate this plot north when plotting, + rotateNorthLock : false, userModifiedRotate: false, // the user modified the rotate status, todo zoomLockingEnabled : false, zoomLockingType: UserZoomTypes.FIT, // can be FIT or FILL @@ -194,8 +192,8 @@ export function changePrimePlot(pv, nextIdx) { */ function replacePlots(pv, plotAry, overlayPlotViews, expandedMode, keepPrimeIdx=false) { - pv= Object.assign({},pv); - pv.plotViewCtx= Object.assign({},pv.plotViewCtx); + pv= clone(pv); + pv.plotViewCtx= clone(pv.plotViewCtx); if (pv.plots && pv.plots.length) { pv.plots.forEach( (plot) => { @@ -206,7 +204,14 @@ function replacePlots(pv, plotAry, overlayPlotViews, expandedMode, keepPrimeIdx= } - if (overlayPlotViews) pv.overlayPlotViews= overlayPlotViews; + if (overlayPlotViews) { + const oPlotAry= overlayPlotViews.map( (opv) => opv.plot); + pv.overlayPlotViews= pv.overlayPlotViews.map( (opv) => { + const plot= oPlotAry.find( (p) => p.plotId===opv.imageOverlayId); + return plot ? clone(opv, {plot}) : opv; + }); + } + pv.plots= plotAry; @@ -227,7 +232,9 @@ function replacePlots(pv, plotAry, overlayPlotViews, expandedMode, keepPrimeIdx= setClientSideRequestOptions(pv,pv.plots[pv.primeIdx].plotState.getWebPlotRequest()); - pv.plotViewCtx.containsMultiImageFits= pv.plots.every( (p) => p.plotState.isMultiImageFile()); + if (pv.plots.length>1) { + pv.plotViewCtx.containsMultiImageFits= pv.plots.every( (p) => p.plotState.isMultiImageFile()); + } pv.plotViewCtx.containsMultipleCubes= pv.plots.every( (p) => p.plotState.getCubeCnt()>1); pv.plotViewCtx.rotateNorthLock= pv.plots[pv.primeIdx].plotState.getRotateType()===RotateType.NORTH; // todo, study this more, understand why if (expandedMode===ExpandType.COLLAPSE) pv.plotViewCtx.lastCollapsedZoomLevel= pv.plots[pv.primeIdx].zoomFactor; @@ -237,17 +244,6 @@ function replacePlots(pv, plotAry, overlayPlotViews, expandedMode, keepPrimeIdx= return pv; } -/** - * update the offset with and height of the primary div - * @param {object} pv - * @param {{width : number, height : number}} viewDim - * @return {object} the PlotView with the new viewDim - */ -//function updateViewDim(pv,viewDim) { -// return Object.assign({}, pv, {viewDim}); -//} - - /** * create a copy of the PlotView with a new scroll position and a new view port if necessary * The scroll position is the top left visible point. @@ -266,7 +262,7 @@ function updatePlotViewScrollXY(plotView,newScrollPt) { const cc= CysConverter.make(plot); newScrollPt= cc.getScreenCoords(newScrollPt); var {x:newSx,y:newSy}= newScrollPt; - var {width:oldVPW, height:oldVPH} = plot.viewPort.dim; + // var {width:oldVPW, height:oldVPH} = plot.viewPort.dim; //if (newSx===oldSx && newSy===oldSy && oldVPW && oldVPH) return plotView; newSx= checkBounds(newSx,plot.screenSize.width,scrollWidth); diff --git a/src/firefly/js/visualize/saga/CatalogWatcher.js b/src/firefly/js/visualize/saga/CatalogWatcher.js index ed83718299..f0d8afef77 100644 --- a/src/firefly/js/visualize/saga/CatalogWatcher.js +++ b/src/firefly/js/visualize/saga/CatalogWatcher.js @@ -3,11 +3,11 @@ */ import {take} from 'redux-saga/effects'; -import {isEmpty, get} from 'lodash'; +import {isEmpty, get, has} from 'lodash'; import {TABLE_LOADED, TABLE_SORT, TABLE_SELECT,TABLE_HIGHLIGHT,TABLE_REMOVE,TABLE_UPDATE} from '../../tables/TablesCntlr.js'; import {dispatchCreateDrawLayer,dispatchAttachLayerToPlot,dispatchDestroyDrawLayer, dispatchModifyCustomField} from '../DrawLayerCntlr.js'; import ImagePlotCntlr, {visRoot} from '../ImagePlotCntlr.js'; -import {getTblById, doFetchTable, getTableGroup, cloneRequest} from '../../tables/TableUtil.js'; +import {getTblById, doFetchTable, getTableGroup, cloneRequest, isTableUsingRadians} from '../../tables/TableUtil.js'; import {serializeDecimateInfo} from '../../tables/Decimate.js'; import {getDrawLayerById} from '../PlotViewUtil.js'; import {dlRoot} from '../DrawLayerCntlr.js'; @@ -143,9 +143,11 @@ function updateDrawingLayer(tbl_id, title, tableData, tableMeta, tableRequest, dataTooBigForSelection}); } else { // new drawing layer + const angleInRadian= isTableUsingRadians(tableMeta); dispatchCreateDrawLayer(Catalog.TYPE_ID, {catalogId:tbl_id, title, tableData, tableMeta, tableRequest, highlightedRow, - selectInfo, columns, dataTooBigForSelection, catalog:true}); + selectInfo, columns, dataTooBigForSelection, catalog:true, + angleInRadian}); dispatchAttachLayerToPlot(tbl_id, plotIdAry); } } diff --git a/src/firefly/js/visualize/saga/CoverageWatcher.js b/src/firefly/js/visualize/saga/CoverageWatcher.js index b2acf53a83..5837d67b3a 100644 --- a/src/firefly/js/visualize/saga/CoverageWatcher.js +++ b/src/firefly/js/visualize/saga/CoverageWatcher.js @@ -4,7 +4,7 @@ import {take} from 'redux-saga/effects'; import Enum from 'enum'; -import {get,isEmpty,flattenDeep,values} from 'lodash'; +import {has,get,isEmpty,flattenDeep,values} from 'lodash'; import {MetaConst} from '../../data/MetaConst.js'; import {TitleOptions} from '../WebPlotRequest.js'; import {CoordinateSys} from '../CoordSys.js'; @@ -14,7 +14,7 @@ import {TABLE_LOADED, TABLE_SELECT,TABLE_HIGHLIGHT, import ImagePlotCntlr, {visRoot, dispatchPlotImage, dispatchDeletePlotView} from '../ImagePlotCntlr.js'; import {primePlot} from '../PlotViewUtil.js'; import {REINIT_RESULT_VIEW} from '../../core/AppDataCntlr.js'; -import {doFetchTable, getTblById, getActiveTableId, getColumnIdx, getTableInGroup} from '../../tables/TableUtil.js'; +import {doFetchTable, getTblById, getActiveTableId, getColumnIdx, getTableInGroup, isTableUsingRadians} from '../../tables/TableUtil.js'; import MultiViewCntlr, {getViewerPlotIds, dispatchAddImages, getMultiViewRoot, getViewer} from '../MultiViewCntlr.js'; // import {serializeDecimateInfo} from '../../tables/Decimate.js'; //todo need to support import {DrawSymbol} from '../draw/PointDataObj.js'; @@ -194,7 +194,7 @@ function updateCoverage(tbl_id, viewerId, decimatedTables, options) { req.tbl_id = `cov-${tbl_id}`; if (decimatedTables[tbl_id] /*&& decimatedTables[tbl_id].tableMeta.tblFilePath===table.tableMeta.tblFilePath*/) { //todo support decimated data - updateCoverageWithData(table, options, tbl_id, decimatedTables[tbl_id], decimatedTables); + updateCoverageWithData(table, options, tbl_id, decimatedTables[tbl_id], decimatedTables, isTableUsingRadians(table)); } else { decimatedTables[tbl_id]= 'WORKING'; @@ -202,7 +202,7 @@ function updateCoverage(tbl_id, viewerId, decimatedTables, options) { (allRowsTable) => { if (get(allRowsTable, ['tableData', 'data'],[]).length>0) { decimatedTables[tbl_id]= allRowsTable; - updateCoverageWithData(table, options, tbl_id, allRowsTable, decimatedTables); + updateCoverageWithData(table, options, tbl_id, allRowsTable, decimatedTables, isTableUsingRadians(table)); } } ).catch( @@ -218,8 +218,8 @@ function updateCoverage(tbl_id, viewerId, decimatedTables, options) { -function updateCoverageWithData(table, options, tbl_id, allRowsTable, decimatedTables) { - const {centralPoint, maxRadius}= computeSize(options, decimatedTables, allRowsTable); +function updateCoverageWithData(table, options, tbl_id, allRowsTable, decimatedTables, usesRadians) { + const {centralPoint, maxRadius}= computeSize(options, decimatedTables, allRowsTable, usesRadians); if (!centralPoint || maxRadius<=0) return; @@ -252,7 +252,7 @@ function updateCoverageWithData(table, options, tbl_id, allRowsTable, decimatedT } } -function computeSize(options, decimatedTables,allRowsTable) { +function computeSize(options, decimatedTables,allRowsTable, usesRadians) { const ary= options.multiCoverage ? values(decimatedTables) : [allRowsTable]; var testAry= ary .filter( (t) => t && t!=='WORKING') @@ -261,10 +261,10 @@ function computeSize(options, decimatedTables,allRowsTable) { const covType= getCoverageType(options,t); switch (covType) { case CoverageType.X: - ptAry= getPtAryFromTable(options,t); + ptAry= getPtAryFromTable(options,t, usesRadians); break; case CoverageType.BOX: - ptAry= getBoxAryFromTable(options,t); + ptAry= getBoxAryFromTable(options,t, usesRadians); break; } @@ -327,6 +327,7 @@ function addToCoverageDrawing(plotId, options, table, allRowsTable, color) { const boxData= covType===CoverageType.BOTH || covType===CoverageType.BOX; const {tableMeta, tableData}= allRowsTable; const columns = boxData ? options.getCornersColumns(table) : options.getCenterColumns(table); + const angleInRadian= isTableUsingRadians(tableMeta); dispatchCreateDrawLayer(Catalog.TYPE_ID, { catalogId: table.tbl_id, title: `Coverage: ${table.title || table.tbl_id}`, @@ -337,7 +338,8 @@ function addToCoverageDrawing(plotId, options, table, allRowsTable, color) { highlightedRow: table.highlightedRow, catalog: false, columns, - boxData + boxData, + angleInRadian }); dispatchAttachLayerToPlot(table.tbl_id, plotId); } @@ -354,14 +356,22 @@ function getCoverageType(options,table) { const hasCorners= (options, table) =>!isEmpty(options.getCornersColumns(table)); +function toAngle(d, radianToDegree) { + const v= Number(d); + return (!isNaN(v) && radianToDegree) ? v*180/Math.PI : v; +} + +function makePt(lonStr,latStr, csys, radianToDegree) { + return makeWorldPt(toAngle(lonStr,radianToDegree), toAngle(latStr, radianToDegree), csys); +} -function getPtAryFromTable(options,table){ +function getPtAryFromTable(options,table, usesRadians){ const cDef= options.getCenterColumns(table); const {lonIdx,latIdx,csys}= cDef; - return table.tableData.data.map( (row) => makeWorldPt(row[lonIdx], row[latIdx], csys) ); + return table.tableData.data.map( (row) => makePt(row[lonIdx], row[latIdx], csys, usesRadians) ); } -function getBoxAryFromTable(options,table){ +function getBoxAryFromTable(options,table, usesRadians){ const cDefAry= options.getCornersColumns(table); return table.tableData.data .map( (row) => cDefAry diff --git a/src/firefly/js/visualize/saga/ImagePlotter.js b/src/firefly/js/visualize/saga/ImagePlotter.js index cee3d078e6..87bb0b3f57 100644 --- a/src/firefly/js/visualize/saga/ImagePlotter.js +++ b/src/firefly/js/visualize/saga/ImagePlotter.js @@ -2,7 +2,7 @@ * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt */ import {take} from 'redux-saga/effects'; -import {unionWith, isEmpty} from 'lodash'; +import {unionWith} from 'lodash'; import ImagePlotCntlr, {visRoot, makeUniqueRequestKey} from '../ImagePlotCntlr.js'; import {modifyRequest, processPlotImageSuccessResponse} from '../PlotImageTask.js'; import {callGetWebPlot, callGetWebPlotGroup, callGetWebPlot3Color} from '../../rpc/PlotServicesJson.js'; diff --git a/src/firefly/js/visualize/saga/MouseReadoutWatch.js b/src/firefly/js/visualize/saga/MouseReadoutWatch.js index a4d0b2836f..16fe82d7ef 100644 --- a/src/firefly/js/visualize/saga/MouseReadoutWatch.js +++ b/src/firefly/js/visualize/saga/MouseReadoutWatch.js @@ -12,7 +12,7 @@ import {callGetFileFlux} from '../../rpc/PlotServicesJson.js'; import {Band} from '../Band.js'; import {MouseState} from '../VisMouseSync.js'; import {isBlankImage} from '../WebPlot.js'; -import {primePlot} from '../PlotViewUtil.js'; +import {primePlot, getPlotStateAry, getActivePlotView} from '../PlotViewUtil.js'; import {mouseUpdatePromise} from '../VisMouseSync.js'; @@ -43,7 +43,9 @@ export function* watchReadout() { continue; } - var plot= primePlot(visRoot(),plotId); + const plotView= getActivePlotView(visRoot()); + var plot= primePlot(plotView); + var readout= makeReadout(plot,worldPt,screenPt,imagePt); const threeColor= plot.plotState.isThreeColor(); dispatchReadoutData(plotId,readout, threeColor); @@ -53,17 +55,17 @@ export function* watchReadout() { continue; } - mouseCtx= lockByClick ? yield call(processImmediateFlux,readout,plot,imagePt,threeColor) : - yield call(processDelayedFlux,readout,plot,imagePt,threeColor); + mouseCtx= lockByClick ? yield call(processImmediateFlux,readout,plotView,imagePt,threeColor) : + yield call(processDelayedFlux,readout,plotView,imagePt,threeColor); } } -function* processImmediateFlux(noFluxReadout,plot,imagePt, threeColor) { +function* processImmediateFlux(noFluxReadout,plotView,imagePt, threeColor) { try { - const fluxResult= yield call(doFluxCall, plot, imagePt); + const fluxResult= yield call(doFluxCall, plotView, imagePt); if (fluxResult) { - const readout= makeReadoutWithFlux(noFluxReadout,plot, fluxResult, threeColor); - dispatchReadoutData(plot.plotId,readout, threeColor); + const readout= makeReadoutWithFlux(noFluxReadout,primePlot(plotView), fluxResult, threeColor); + dispatchReadoutData(plotView.plotId,readout, threeColor); const mouseCtx = yield call(mouseUpdatePromise); return mouseCtx; } @@ -76,7 +78,7 @@ function* processImmediateFlux(noFluxReadout,plot,imagePt, threeColor) { } -function* processDelayedFlux(noFluxReadout,plot,imagePt, threeColor) { +function* processDelayedFlux(noFluxReadout,plotView,imagePt, threeColor) { var raceWinner = yield race({ mouseCtx: call(mouseUpdatePromise), timer: call(delay, 200) @@ -88,13 +90,13 @@ function* processDelayedFlux(noFluxReadout,plot,imagePt, threeColor) { try { raceWinner = yield race({ mouseCtx: call(mouseUpdatePromise), - fluxResult: call(doFluxCall, plot, imagePt,200) + fluxResult: call(doFluxCall, plotView, imagePt,200) }); if (raceWinner.mouseCtx) return raceWinner.mouseCtx; if (raceWinner.fluxResult) { - const readout= makeReadoutWithFlux(noFluxReadout,plot, raceWinner.fluxResult, threeColor); - dispatchReadoutData(plot.plotId,readout, threeColor); + const readout= makeReadoutWithFlux(noFluxReadout,primePlot(plotView), raceWinner.fluxResult, threeColor); + dispatchReadoutData(plotView.plotId,readout, threeColor); const mouseCtx = yield call(mouseUpdatePromise); return mouseCtx; } @@ -158,7 +160,11 @@ function makeReadoutWithFlux(readout, plot, fluxResult,threeColor) { makeValueReadoutItem(labels[idx], fluxData[idx].value,fluxData[idx].unit, 6)); } else { - readout['nobandFlux']= makeValueReadoutItem(labels[0], fluxData[0].value,fluxData[0].unit, 6); + readout.nobandFlux= makeValueReadoutItem(labels[0], fluxData[0].value,fluxData[0].unit, 6); + } + const oIdx= fluxData.findIndex( (d) => d.imageOverlay); + if (oIdx>-1) { + readout.imageOverlay= makeValueReadoutItem('mask', fluxData[oIdx].value, fluxData[oIdx].unit, 0); } return readout; } @@ -220,13 +226,14 @@ function showSingleBandFluxLabel(plot, band) { -function doFluxCall(plot,iPt) { - return callGetFileFlux(plot.plotState, iPt) +function doFluxCall(plotView,iPt) { + const plotStateAry= getPlotStateAry(plotView); + return callGetFileFlux(plotStateAry, iPt) .then((result) => { return result; }) .catch((e) => { - console.log(`flux error: ${plot.plotId}`, e); + console.log(`flux error: ${plotView.plotId}`, e); return ['', '', '']; }); } @@ -237,7 +244,7 @@ function getFlux(result, plot) { if (result.NO_BAND) { var fluxUnitStr = plot.webFitsData[Band.NO_BAND.value].fluxUnits; var fValue = parseFloat(result.NO_BAND); - return [{value: fValue, unit: fluxUnitStr}]; + fluxArray[0]= {value: fValue, unit: fluxUnitStr}; } else { const bands = plot.plotState.getBands(); @@ -258,7 +265,10 @@ function getFlux(result, plot) { fnum = parseFloat(result[bandName]); fluxArray[i]= {bandName, value:fnum, unit:unitStr}; } - return fluxArray; } + Object.keys(result) + .filter((k) => k.startsWith('overlay')) + .forEach( (k) => {fluxArray.push({ imageOverlay : true, value : parseFloat(result[k]), unit : 'mask' });}); + return fluxArray; } diff --git a/src/firefly/js/visualize/ui/DrawLayerItemView.jsx b/src/firefly/js/visualize/ui/DrawLayerItemView.jsx index 13fa5fe359..5f189a2ee2 100644 --- a/src/firefly/js/visualize/ui/DrawLayerItemView.jsx +++ b/src/firefly/js/visualize/ui/DrawLayerItemView.jsx @@ -3,13 +3,8 @@ */ import React from 'react'; -import {isDrawLayerVisible, getLayerTitle} from '../PlotViewUtil.js'; import {SimpleCanvas} from '../draw/SimpleCanvas.jsx'; import DrawUtil from '../draw/DrawUtil.js'; -import {ColorChangeType} from '../draw/DrawLayer.js'; -import {dispatchChangeDrawingDef, dispatchChangeVisibility, - dispatchDetachLayerFromPlot} from '../DrawLayerCntlr.js'; -import {showColorPickerDialog} from '../../ui/ColorPicker.jsx'; @@ -18,7 +13,10 @@ const bSty= { whiteSpace: 'nowrap' }; -function DrawLayerItemView({drawLayer,pv, maxTitleChars, lastItem, getUIComponent}) { +function DrawLayerItemView({maxTitleChars, lastItem, deleteLayer, + color, canUserChangeColor, canUserDelete, title, helpLine, + isPointData, drawingDef, + visible, changeVisible, modifyColor, UIComponent}) { var style= { width:'100%', height:'100%', @@ -38,7 +36,6 @@ function DrawLayerItemView({drawLayer,pv, maxTitleChars, lastItem, getUIComponen style.paddingBottom= 5; } - var plotId= pv.plotId; return (
- changeVisible(drawLayer, plotId)} - /> - {getTitleTag(drawLayer,pv,maxTitleChars)} + changeVisible()} /> + {getTitleTag(title,maxTitleChars)}
- {makeColorChange(drawLayer,pv)} - {makeShape(drawLayer,pv)} - {makeDelete(drawLayer,pv)} + {makeColorChange(color, canUserChangeColor,modifyColor)} + {makeShape(isPointData,drawingDef)} + {makeDelete(canUserDelete,deleteLayer)}
- {getUIComponent ? getUIComponent(drawLayer,pv) : ''} + {UIComponent || ''}
- {makeHelpLine(drawLayer.helpLine)} + {makeHelpLine(helpLine)}
); } DrawLayerItemView.propTypes= { - drawLayer : React.PropTypes.object.isRequired, - pv : React.PropTypes.object.isRequired, maxTitleChars : React.PropTypes.number.isRequired, lastItem : React.PropTypes.bool.isRequired, - getUIComponent: React.PropTypes.func + visible : React.PropTypes.bool.isRequired, + canUserChangeColor : React.PropTypes.any.isRequired, + color : React.PropTypes.string.isRequired, + title : React.PropTypes.any.isRequired, + helpLine : React.PropTypes.string.isRequired, + canUserDelete : React.PropTypes.bool.isRequired, + isPointData : React.PropTypes.bool.isRequired, + drawingDef : React.PropTypes.object, + deleteLayer : React.PropTypes.func, + changeVisible : React.PropTypes.func, + modifyColor : React.PropTypes.func, + UIComponent : React.PropTypes.object }; -function getTitleTag(drawLayer,pv,maxTitleChars) { +function getTitleTag(title, maxTitleChars) { const tStyle= { display:'inline-block', whiteSpace: 'nowrap', @@ -87,25 +90,25 @@ function getTitleTag(drawLayer,pv,maxTitleChars) { }; return ( -
{`Show: ${getLayerTitle(pv.plotId,drawLayer)}`}
+
{title}
); } -function makeColorChange(drawLayer,pv) { +function makeColorChange(color, canUserChangeColor, modifyColor) { const feedBackStyle= { width:10, height:10, - backgroundColor: drawLayer.drawingDef.color, + backgroundColor: color, display:'inline-block', marginLeft:5 }; - if (drawLayer.canUserChangeColor) { + if (canUserChangeColor) { return (
-
modifyColor(drawLayer,pv.plotId)} /> + ); @@ -116,14 +119,10 @@ function makeColorChange(drawLayer,pv) { } -function makeShape(drawLayer,pv) { - const shapeStyle= { - display:'inline-block', - whiteSpace: 'nowrap' - }; - if (drawLayer.isPointData) { +function makeShape(isPointData, drawingDef) { + if (isPointData) { return ( - drawOnCanvas(c,drawLayer)}/> + drawOnCanvas(c,drawingDef)}/> ); } else { @@ -132,6 +131,12 @@ function makeShape(drawLayer,pv) { } +function drawOnCanvas(c,drawingDef) { + if (!c) return; + DrawUtil.drawSymbol(c.getContext('2d'), 10,5,drawingDef,null,false); +} + + function makeHelpLine(helpLine) { if (helpLine) { return ( @@ -143,19 +148,14 @@ function makeHelpLine(helpLine) { } } -function drawOnCanvas(c,drawLayer) { - if (!c) return; - DrawUtil.drawSymbol(c.getContext('2d'), 10,5,drawLayer.drawingDef,null,false); -} - -function makeDelete(drawLayer,pv) { +function makeDelete(canUserDelete,deleteLayer) { const deleteStyle= { display:'inline-block', whiteSpace: 'nowrap' }; - if (drawLayer.canUserDelete) { + if (canUserDelete) { return ( - deleteLayer(drawLayer,pv.plotId)} style={deleteStyle}>Delete + deleteLayer()} style={deleteStyle}>Delete ); } else { @@ -165,21 +165,5 @@ function makeDelete(drawLayer,pv) { } -function modifyColor(dl,plotId) { - showColorPickerDialog(dl.drawingDef.color, dl.canUserChangeColor===ColorChangeType.STATIC, - (ev) => { - var {r,g,b,a}= ev.rgb; - var rgbStr= `rgba(${r},${g},${b},${a})`; - dispatchChangeDrawingDef(dl.displayGroupId, Object.assign({},dl.drawingDef,{color:rgbStr}),plotId); - }); -} - -function deleteLayer(dl,plotId) { - dispatchDetachLayerFromPlot(dl.displayGroupId,plotId,true, true, dl.destroyWhenAllDetached); -} - -function changeVisible(dl, plotId) { - dispatchChangeVisibility(dl.displayGroupId, !isDrawLayerVisible(dl,plotId),plotId ); -} export default DrawLayerItemView; diff --git a/src/firefly/js/visualize/ui/DrawLayerPanel.jsx b/src/firefly/js/visualize/ui/DrawLayerPanel.jsx index cb8180ad3f..2c8bc2898c 100644 --- a/src/firefly/js/visualize/ui/DrawLayerPanel.jsx +++ b/src/firefly/js/visualize/ui/DrawLayerPanel.jsx @@ -4,6 +4,7 @@ import React from 'react'; +import {get} from 'lodash'; import {dispatchShowDialog, dispatchHideDialog} from '../../core/ComponentCntlr.js'; import sCompare from 'react-addons-shallow-compare'; import {getActivePlotView, getAllDrawLayersForPlot} from '../PlotViewUtil.js'; @@ -13,6 +14,7 @@ import {getDlAry} from '../DrawLayerCntlr.js'; import {visRoot} from '../ImagePlotCntlr.js'; import DrawLayerPanelView from './DrawLayerPanelView.jsx'; import {flux} from '../../Firefly.js'; +import {readoutRoot} from '../../visualize/MouseReadoutCntlr.js'; export const DRAW_LAYER_POPUP= 'DrawLayerPopup'; @@ -57,7 +59,7 @@ class DrawLayerPanel extends React.Component { constructor(props) { super(props); var activePv= getActivePlotView(visRoot()); - this.state= {dlAry:getDlAry(),activePv}; + this.state= {dlAry:getDlAry(),activePv, mouseOverMaskValue:0}; } shouldComponentUpdate(np,ns) { return sCompare(this,np,ns); } @@ -74,15 +76,17 @@ class DrawLayerPanel extends React.Component { storeUpdate() { var state= this.state; var activePv= getActivePlotView(visRoot()); + const mouseOverMaskValue= get(readoutRoot(), 'standardReadout.readoutItems.imageOverlay.value',0); - if (activePv!==state.activePv || getDlAry()!==state.dlAry) { + if (activePv!==state.activePv || getDlAry()!==state.dlAry || mouseOverMaskValue!==this.state.mouseOverMaskValue) { const dlAry= getDlAry(); - var layers= getAllDrawLayersForPlot(dlAry,activePv.plotId); - if (layers.length) { - this.setState({dlAry,activePv}); + const imageOverlayLength= activePv ? activePv.overlayPlotViews.length : 0; + var layers= activePv ? getAllDrawLayersForPlot(dlAry,activePv.plotId) : []; + if ((layers.length + imageOverlayLength)>0) { + this.setState({dlAry,activePv, mouseOverMaskValue}); } else { - setTimeout(() => hideDrawingLayerPopup(),0) + setTimeout(() => hideDrawingLayerPopup(),0); } } } @@ -94,6 +98,7 @@ class DrawLayerPanel extends React.Component { ); } diff --git a/src/firefly/js/visualize/ui/DrawLayerPanelView.jsx b/src/firefly/js/visualize/ui/DrawLayerPanelView.jsx index fb089588b2..6ddf96ee3a 100644 --- a/src/firefly/js/visualize/ui/DrawLayerPanelView.jsx +++ b/src/firefly/js/visualize/ui/DrawLayerPanelView.jsx @@ -3,13 +3,19 @@ */ import React from 'react'; -import {getLayerTitle, getAllDrawLayersForPlot} from '../PlotViewUtil.js'; +import {padStart} from 'lodash'; +import {isDrawLayerVisible, getAllDrawLayersForPlot, getLayerTitle} from '../PlotViewUtil.js'; import DrawLayerItemView from './DrawLayerItemView.jsx'; +import {ColorChangeType} from '../draw/DrawLayer.js'; +import {dispatchChangeDrawingDef, dispatchChangeVisibility, + dispatchDetachLayerFromPlot} from '../DrawLayerCntlr.js'; +import {dispatchOverlayPlotChangeAttributes, dispatchDeleteOverlayPlot} from '../ImagePlotCntlr.js'; +import {showColorPickerDialog} from '../../ui/ColorPicker.jsx'; -function DrawLayerPanelView({dlAry, plotView, dialogId, drawLayerFactory}) { +function DrawLayerPanelView({dlAry, plotView, mouseOverMaskValue, drawLayerFactory}) { var style= {width:'calc(100% - 12px)', height:'100%', padding: 6, @@ -20,10 +26,11 @@ function DrawLayerPanelView({dlAry, plotView, dialogId, drawLayerFactory}) { var maxTitleChars= layers.reduce( (max,l) => { var t= getLayerTitle(plotView.plotId,l); return Math.max(max, t?t.length:0); - },3); + },20); return (
+ {makeImageLayerItemAry(plotView,maxTitleChars,layers.length===0, mouseOverMaskValue)} {makeDrawLayerItemAry(layers,plotView,maxTitleChars, drawLayerFactory)}
); @@ -33,21 +40,137 @@ function DrawLayerPanelView({dlAry, plotView, dialogId, drawLayerFactory}) { DrawLayerPanelView.propTypes= { plotView : React.PropTypes.object.isRequired, dlAry : React.PropTypes.array.isRequired, - dialogId : React.PropTypes.string.isRequired + dialogId : React.PropTypes.string.isRequired, + drawLayerFactory : React.PropTypes.object.isRequired, + mouseOverMaskValue : React.PropTypes.number.isRequired }; +function getUIComponent(dl,pv, factory) { + return factory.getGetUIComponentFunc(dl) && factory.getGetUIComponentFunc(dl)(dl,pv); +} + + function makeDrawLayerItemAry(layers,pv, maxTitleChars, factory) { var last= layers.length-1; return layers.map( (l,idx) => modifyColor(l,pv.plotId)} + deleteLayer={() => deleteLayer(l,pv.plotId)} + changeVisible={() => changeVisible(l,pv.plotId)} + UIComponent={getUIComponent(l,pv,factory)} />); } +function makeImageLayerItemAry(pv, maxTitleChars, hasLast, mouseOverMaskValue) { + if (!pv.overlayPlotViews) return []; + var last= pv.overlayPlotViews.length-1; + const retAry= pv.overlayPlotViews.map( (opv,idx) => + modifyMaskColor(opv)} + deleteLayer={() => deleteMaskLayer(opv)} + changeVisible={() => changeMaskVisible(opv)} + UIComponent={null} + />); + return retAry; + +} + +function makeOverlayTitle(opv,mouseOn) { + var {title, color, maskNumber}= opv; + if (opv.plot) { + const {header}= opv.plot.projection; + const titleKey= Object.keys(header) + .filter( (k) => k.includes('MP')) + .find( (k) => parseInt(header[k])===maskNumber); + var maskDesc= titleKey; + if (maskDesc) { + if (maskDesc.startsWith('HIERARCH')) maskDesc= maskDesc.substring(9); + title= `${title} - ${maskDesc}`; + } + } + + mouseOn= Boolean(mouseOn); + + return ( +
{title} + {mouseOn && over} +
+ ); +} + + +function modifyColor(dl,plotId) { + showColorPickerDialog(dl.drawingDef.color, dl.canUserChangeColor===ColorChangeType.STATIC, false, + (ev) => { + var {r,g,b,a}= ev.rgb; + var rgbStr= `rgba(${r},${g},${b},${a})`; + dispatchChangeDrawingDef(dl.displayGroupId, Object.assign({},dl.drawingDef,{color:rgbStr}),plotId); + }); +} + +const hexC= (v) => padStart(v.toString(16),2,'0'); + +function modifyMaskColor(opv) { + + const {color}= opv; + const rV= parseInt(color.substring(1,3),16); + const gV= parseInt(color.substring(3,5),16); + const bV= parseInt(color.substring(5,7),16); + + + var rgbStr= `rgba(${rV},${gV},${bV},${opv.opacity})`; + showColorPickerDialog(rgbStr, false, true, + (ev, okPushed) => { + const {plotId, imageOverlayId} = opv; + var {r,g,b,a}= ev.rgb; + const newColor= `#${hexC(r)}${hexC(g)}${hexC(b)}`; + const doReplot= okPushed && (newColor.toUpperCase()!==opv.color.toUpperCase()); + dispatchOverlayPlotChangeAttributes({plotId, imageOverlayId,doReplot, + attributes:{color:newColor,opacity:a}}); + }); +} + + + +function deleteLayer(dl,plotId) { + dispatchDetachLayerFromPlot(dl.displayGroupId,plotId,true, true, dl.destroyWhenAllDetached); +} + + +function deleteMaskLayer(opv) { + const {plotId, imageOverlayId} = opv; + dispatchDeleteOverlayPlot({plotId, imageOverlayId}); +} + + +function changeVisible(dl, plotId) { + dispatchChangeVisibility(dl.displayGroupId, !isDrawLayerVisible(dl,plotId),plotId ); +} + + +function changeMaskVisible(opv) { + const {plotId, imageOverlayId, visible} = opv; + dispatchOverlayPlotChangeAttributes({plotId, imageOverlayId, attributes:{visible:!visible}}); +} export default DrawLayerPanelView; diff --git a/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx b/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx index 6fce9edf18..b3171644e1 100644 --- a/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx +++ b/src/firefly/js/visualize/ui/ImageMetaDataToolbarView.jsx @@ -127,7 +127,7 @@ function InlineRightToolbarWrapper({visRoot,pv,dlAry}){ return (
+ + + ); + DialogRootContainer.defineDialog('MaskChooseDialog', content); + dispatchShowDialog('MaskChooseDialog'); +} + + + +export function MaskAddPanel({vr}) { + + + return ( + + + + + resultsSuccess(r,visRoot())} + dialogId='MaskChooseDialog' + /> + + + + ); + + + +} + +const maskIdRoot= 'MASK_'; +var maskCnt= 0; + +function resultsSuccess(request,vr) { + + console.log('make success'); + console.log(request); + const pv= getActivePlotView(vr); + const maskV= Number(request.maskIdx); + const hdu= Number(request.hduIdx); + dispatchPlotMask({plotId:pv.plotId,imageOverlayId:maskIdRoot+maskCnt, maskNumber:maskV, maskValue:Math.pow(2,maskV), + imageNumber:hdu, title:'bit # '+maskV}); + maskCnt++; +} + + diff --git a/src/firefly/js/visualize/ui/MultiViewStandardToolbar.jsx b/src/firefly/js/visualize/ui/MultiViewStandardToolbar.jsx index e260a44d3e..49b9165206 100644 --- a/src/firefly/js/visualize/ui/MultiViewStandardToolbar.jsx +++ b/src/firefly/js/visualize/ui/MultiViewStandardToolbar.jsx @@ -94,7 +94,7 @@ function makeInlineRightToolbar(visRoot,pv,dlAry){ return (
- + console.log('todo- mask dialog')}/> + onClick={showMaskDialog}/> - + @@ -391,15 +389,13 @@ function showImagePopup() { //================================================================================== //================================================================================== -export function LayerButton({plotId,visible}) { - const plotLayers= getAllDrawLayersForPlot(getDlAry(),plotId); - const enabled= !isEmpty(plotLayers); - +export function LayerButton({pv,visible}) { + const layerCnt= pv ? (getAllDrawLayersForPlot(getDlAry(),pv.plotId).length + pv.overlayPlotViews.length) : 0; return ( @@ -407,7 +403,7 @@ export function LayerButton({plotId,visible}) { } LayerButton.propTypes= { - plotId : PropTypes.string, + pv : PropTypes.object, visible : PropTypes.bool.isRequired, dlCount : PropTypes.number.isRequired // must be here. We don't use directly but it forces an update }; diff --git a/src/firefly/python/display/FireflyClient.py b/src/firefly/python/display/FireflyClient.py index 1b5cb7739b..efc89aa730 100644 --- a/src/firefly/python/display/FireflyClient.py +++ b/src/firefly/python/display/FireflyClient.py @@ -5,6 +5,7 @@ import time import socket import urlparse +import math __author__ = 'zhang' + ' Cindy Wang' @@ -32,19 +33,21 @@ class FireflyClient(WebSocketClient): # actions from Firefly ACTION_DICT = { - 'ShowFits': 'ImagePlotCntlr.PlotImage', - 'AddExtension': 'ExternalAccessCntlr/extensionAdd', - 'ShowTable': 'table.search', - 'ZoomImage': 'ImagePlotCntlr.ZoomImage', - 'PanImage': 'ImagePlotCntlr.recenter', - 'StretchImage': 'ImagePlotCntlr.StretchChange', - 'CreateRegionLayer': 'DrawLayerCntlr.RegionPlot.createLayer', - 'DeleteRegionLayer': 'DrawLayerCntlr.RegionPlot.deleteLayer', - 'AddRegionData': 'DrawLayerCntlr.RegionPlot.addRegion', - 'RemoveRegionData': 'DrawLayerCntlr.RegionPlot.removeRegion'} + 'ShowFits': 'ImagePlotCntlr.PlotImage', + 'AddExtension': 'ExternalAccessCntlr/extensionAdd', + 'ShowTable': 'table.search', + 'ZoomImage': 'ImagePlotCntlr.ZoomImage', + 'PanImage': 'ImagePlotCntlr.recenter', + 'StretchImage': 'ImagePlotCntlr.StretchChange', + 'CreateRegionLayer': 'DrawLayerCntlr.RegionPlot.createLayer', + 'DeleteRegionLayer': 'DrawLayerCntlr.RegionPlot.deleteLayer', + 'AddRegionData': 'DrawLayerCntlr.RegionPlot.addRegion', + 'RemoveRegionData': 'DrawLayerCntlr.RegionPlot.removeRegion', + 'PlotMask': 'ImagePlotCntlr.plotMask', + 'DeleteOverlayMask': 'ImagePlotCntlr.deleteOverlayPlot'} # id for table, region layer, extension - _item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0} + _item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0, 'MaskLayer': 0} # the constructor, define instance variables for the object def __init__(self, host=my_localhost, channel=None): @@ -60,6 +63,7 @@ def __init__(self, host=my_localhost, channel=None): self.listeners = {} self.channel = channel self.session = requests.Session() + # print 'websocket url:%s' % url self.connect() @@ -523,47 +527,47 @@ def remove_region_data(self, region_data, region_layer_id): return self.dispatch_remote_action_by_post(self.channel, FireflyClient.ACTION_DICT['RemoveRegionData'], payload) - def add_mask(self, mask_id, bit_number, image_number, color, plot_id, - bit_desc=None, file_on_server=None): + def add_mask(self, bit_number, image_number, plot_id, mask_id=None, color=None, title=None, + file_on_server=None): """ Add a mask layer - TODO - :param mask_id: id of mask - :param bit_number: bitNumber of the mask to overlay :param image_number: imageNumber of the mask layer - :param color: color as an html color (eg. #FF0000 (red) #00FF00 (green) + :param bit_number: bitNumber of the mask to overlay :param plot_id: plot id to overlay the mask on - :param bit_desc: (optional) description of the mask layer + :param mask_id: id of mask + :param color: color as an html color (eg. #FF0000 (red) #00FF00 (green) + :param title: title of the mask layer :param file_on_server: (optional) file to get the mask from, if None then get it from the original file :return: status of call """ - url = self.url_root + "?cmd=pushAddMask" - params = { - 'id': mask_id, - 'bitNumber': bit_number, - 'color': color, - 'plotId': plot_id, - 'imageNumber': image_number} + if not mask_id: + mask_id = FireflyClient._gen_item_id('MaskLayer') + if not title: + title = 'bit %23 ' + str(bit_number) - if bit_desc: - params['bitDesc'] = bit_desc + payload = {'plotId': plot_id, 'imageOverlayId': mask_id, 'imageNumber': image_number, + 'maskNumber': bit_number, 'maskValue': int(math.pow(2, bit_number)), 'title': title} + if color: + payload.update({'color': color}) if file_on_server: - params['fileKey'] = file_on_server - response = self.session.post(url, data=params) - status = json.loads(response.text) - return status[0] + payload.update({'fileKey': file_on_server}) + + return self.dispatch_remote_action(self.channel, + FireflyClient.ACTION_DICT['PlotMask'], payload) - def remove_mask(self, mask_id): + def remove_mask(self, plot_id, mask_id): """ - TODO Remove a mask layer + :param plot_id: plot id of the overlay the mask is on :param mask_id: id of mask :return: status of call """ - url = self.url_root + "?cmd=pushRemoveMask&id=%s" % mask_id - return self._send_url_as_get(url) + + payload = {'plotId': plot_id, 'imageOverlayId': mask_id} + return self.dispatch_remote_action(self.channel, + FireflyClient.ACTION_DICT['DeleteOverlayMask'], payload) # ----------------------------------------------------------------- # Range Values diff --git a/src/firefly/test/python/demo-notebook.ipynb b/src/firefly/test/python/demo-notebook.ipynb index ede9a5ce23..ffd0d4602e 100644 --- a/src/firefly/test/python/demo-notebook.ipynb +++ b/src/firefly/test/python/demo-notebook.ipynb @@ -44,6 +44,31 @@ "fc.launch_browser()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "file= fc.upload_file('./data/CORR-0903334-016.fits')\n", + "status = fc.show_fits(file, 'pmask', MultiImageIdx='0') \n", + "status = fc.add_mask(5, 1, 'pmask', 'maskid1')\n", + "status = fc.add_mask(10, 1, 'pmask', 'maskid2')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "status = fc.remove_mask('pmask', 'maskid1')" + ] + }, { "cell_type": "code", "execution_count": null, @@ -396,9 +421,9 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python [Root]", + "display_name": "Python 2", "language": "python", - "name": "Python [Root]" + "name": "python2" }, "language_info": { "codemirror_mode": { @@ -410,7 +435,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.12" + "version": "2.7.11" } }, "nbformat": 4,