From 1dee546bb07f56c4fc9ccee602f581b7ebb446e4 Mon Sep 17 00:00:00 2001 From: mmasoud1 Date: Sat, 6 Jan 2024 11:53:35 -0500 Subject: [PATCH] clean code --- index.html | 4 + js/histojs/analysisFunctionsv12.js | 1610 ++++++++++--------- js/libs/splitString.js | 17 + test/histojs_test/analysisFunctions_test.js | 20 +- 4 files changed, 924 insertions(+), 727 deletions(-) create mode 100644 js/libs/splitString.js diff --git a/index.html b/index.html index 2ae00c6..ae5232f 100644 --- a/index.html +++ b/index.html @@ -85,9 +85,13 @@ + + + + diff --git a/js/histojs/analysisFunctionsv12.js b/js/histojs/analysisFunctionsv12.js index 98a2c8e..3c46252 100644 --- a/js/histojs/analysisFunctionsv12.js +++ b/js/histojs/analysisFunctionsv12.js @@ -12570,11 +12570,11 @@ drawMarkersBoxPlotChart = (chartData) => { * @param {number} width_value * @param {number} height_value * @param {object} obj - * @param {bool} logFlag + * @param {bool} logFlag -> yAxis data log1p * */ - plotTileMarkersHistogram = (left_value, top_value, width_value, height_value, obj, logFlag = false) => { // logFlag -> yAxis data log1p + plotTileMarkersHistogram = (left_value, top_value, width_value, height_value, obj, logFlag = false) => { let features = []; if( isFeaturesLoaded() ) { @@ -12614,39 +12614,6 @@ drawMarkersBoxPlotChart = (chartData) => { } -// plotTileMarkersHistogram_V0 = (left_value, top_value, width_value, height_value, obj) => { - -// if( allTilesFeatures.length == 0) -// { -// let featureDataToPlot = getTileProp(left_value, top_value, width_value, height_value); -// featureDataToPlot.push(temp) -// } else { -// let tile = findObjectByKeyValue(allTilesFeatures, 'id', d3.select(obj).attr('id')); -// let chartData = {channelNames: []}; // channelNames will have frame names - -// for(let i = 0; i < featureKeys.length; i++){ -// chartData[featureKeys[i]] = []; -// } - -// //-- chartData = { labels: [], mean: [], max: [], std: []}; -// for(let n = 0; n < tile.features.length; n++){ - -// chartData.channelNames.push(tile.features[n].Frame); - -// for(let i = 0; i < featureKeys.length; i++){ -// chartData[featureKeys[i]].push(tile.features[n][featureKeys[i]]); -// } - -// //-- chartData.meanData.push(tile.features[n].mean); -// //-- chartData.maxData.push(tile.features[n].max); -// //-- chartData.stdData.push(tile.features[n].std); -// } - -// drawMarkersHistogramChart(chartData); -// } -// } - - /** * This function can be used with Boxplot option (plotFlag = true ) and Phenotypes option (plotFlag = false) @@ -12704,7 +12671,7 @@ drawMarkersBoxPlotChart = (chartData) => { * @memberof HistoJS * @since 1.0.0 * @version 1.0.0 - * @param {} data + * @param {Array} data : array of objects: [{Frame: "DAPI", channelNum: 0, OSDLayer: 0, min:1, ...}, {}] * */ @@ -12720,7 +12687,7 @@ drawMarkersBoxPlotChart = (chartData) => { * @memberof HistoJS * @since 1.0.0 * @version 1.0.0 - * @returns {} + * @returns {Array} array of objects e.g. [{Frame: "DAPI", channelNum: 0, OSDLayer: 0, min:1, ...}, {}] * */ @@ -12730,46 +12697,57 @@ drawMarkersBoxPlotChart = (chartData) => { /** - * This function can be used for channel based statisticals and cell based statistical + * This function to check if channel statistics data available * * @function * @memberof HistoJS * @since 1.0.0 * @version 1.0.0 - * @returns {} + * @returns {bool} * */ isGrpChannelsStatisticalDataAvailable =() => { return grpChannelsStatisticalData.length ? true : false; - - } - // Can be used for channel based statisticals and cell based statistical /** - * This function can be used with Boxplot option (plotFlag = true ) and Phenotypes option (plotFlag = false) + * This function used to reset variable * * @function * @memberof HistoJS * @since 1.0.0 * @version 1.0.0 - * @param {bool} plotFlag * - */ + */ + resetGrpChannelsStatisticalData = () => { grpChannelsStatisticalData = []; } - // TEST = () => { <<<<<<<<<<<<<<<<<<<<<<----------- - // let test = [] - // webix.ajax().sync().get("http://127.0.0.1:" + Opts.defaultRestApiPort + "/TEST", function(response) { - // test = JSON.parse(response); - // }); - // return test != "notExist" ? test : [] ; - // } - // calculate channel mean, max, min, std, median, q1, q3 + + //-- TEST = () => { <<<<<<<<<<<<<<<<<<<<<<----------- + //-- let test = [] + //-- webix.ajax().sync().get("http://127.0.0.1:" + Opts.defaultRestApiPort + "/TEST", function(response) { + //-- test = JSON.parse(response); + //-- }); + //-- return test != "notExist" ? test : [] ; + //-- } + + + /** + * This function used to calculate channel mean, max, min, std, median, q1, q3 + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {Array} e.g. [ {Frame: 'CD45', OSDLayer: 0, channelNum: 22, max: 255.0, mean: 5.174311939678205, + * median: 3.0, min: 0.0, q1: 1.0, q3: 8.0, std: 5.5190755543079115}, ... ] + * + */ + createChannelsStatisticalData = () => { let groupData = []; let curGroup = getSelectedGroup(); @@ -12825,357 +12803,400 @@ drawMarkersBoxPlotChart = (chartData) => { // median: 3.0, min: 0.0, q1: 1.0, q3: 8.0, std: 5.5190755543079115} return boxplotData; - } + /** + * This function calculate marker cells mean, max, min, std, median, q1, q3. + * instead of create them based on marker channel intensity, marker cells are used to find mean of all marker cells etc. + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {Array} e.g. [{Frame: 'CD45', max: 255.0, mean: 5.174311939678205, + * median: 3.0, min: 0.0, q1: 1.0, q3: 8.0, std: 5.5190755543079115}, ...] + * + */ -// calculate marker cells mean, max, min, std, median, q1, q3, -// instead of create them based on marker channel intensity, marker cells are used to find mean of all marker cells etc. -createMarkerCellsStatisticalData = () => { + createMarkerCellsStatisticalData = () => { - // e.g. "Structural Components__markers_morphology.csv" - // csv file has markers intensities for each cell in addition to cells morphological features - // it has each cell marker intensity e.g. CD45_max CD45_mean CD45_nonzero_mean - // Opts.cellFeatureToSelect : "_mean", // select from [ _mean, _max, _std, _nonzero_mean] - let markersMorphFileName = getGrpMarkersMorphFileName(); - let grpFeaturesFolder = getGrpFeaturesLocalPath(); + // e.g. "Structural Components__markers_morphology.csv" + // csv file has markers intensities for each cell in addition to cells morphological features + // it has each cell marker intensity e.g. CD45_max CD45_mean CD45_nonzero_mean + // e.g. Opts.cellFeatureToSelect : "_mean", // select from [ _mean, _max, _std, _nonzero_mean] + let markersMorphFileName = getGrpMarkersMorphFileName(); + let grpFeaturesFolder = getGrpFeaturesLocalPath(); + // For boxplot data file and location e.g. Structural Components_MarkerCells_Boxplot_Data.json + let boxplotFileName = getGrpMarkerCellsBoxplotFileName(); + let boxplotFolder = getGrpBoxplotLocalPath(); - // For boxplot data file and location e.g. Structural Components_MarkerCells_Boxplot_Data.json - let boxplotFileName = getGrpMarkerCellsBoxplotFileName(); - let boxplotFolder = getGrpBoxplotLocalPath(); + //-- groupMarkers e.g. Array(5) [ "DAPI", "KERATIN", "ASMA", "CD45", "IBA1" ] + let groupMarkers = getCurGrpChannelsName(); - // groupMarkers e.g. Array(5) [ "DAPI", "KERATIN", "ASMA", "CD45", "IBA1" ] - let groupMarkers = getCurGrpChannelsName(); + let boxplotData = []; - let boxplotData = []; + webix.ajax().sync().get("http://127.0.0.1:" + Opts.defaultRestApiPort + + "/createMarkerCellsStatisticalData", "&features_folder=" + grpFeaturesFolder + "&markers_morph_file=" + markersMorphFileName + + "&boxplot_file=" + boxplotFileName + "&boxplot_folder=" + boxplotFolder + "&grp_markers=" + JSON.stringify(groupMarkers) + + //-- "&neglect_zero=" + Opts.boxplotForAboveZeroCells + + "&isMarkerFeatureNormalizeRequired=" + Opts.isMarkerFeatureNormalizeRequired + + "&cellFeatureToNormalize=" + Opts.cellFeatureToNormalize , function(response) { + boxplotData = JSON.parse(response); + }); - webix.ajax().sync().get("http://127.0.0.1:" + Opts.defaultRestApiPort + - "/createMarkerCellsStatisticalData", "&features_folder=" + grpFeaturesFolder + "&markers_morph_file=" + markersMorphFileName + - "&boxplot_file=" + boxplotFileName + "&boxplot_folder=" + boxplotFolder + "&grp_markers=" + JSON.stringify(groupMarkers) + - // "&neglect_zero=" + Opts.boxplotForAboveZeroCells + - "&isMarkerFeatureNormalizeRequired=" + Opts.isMarkerFeatureNormalizeRequired + - "&cellFeatureToNormalize=" + Opts.cellFeatureToNormalize , function(response) { + //-- boxplot sample data e.g. {Frame: 'CD45', max: 255.0, mean: 5.174311939678205, + //-- median: 3.0, min: 0.0, q1: 1.0, q3: 8.0, std: 5.5190755543079115} - boxplotData = JSON.parse(response); - }); + return boxplotData; + } - // boxplot sample data e.g. {Frame: 'CD45', max: 255.0, mean: 5.174311939678205, - // median: 3.0, min: 0.0, q1: 1.0, q3: 8.0, std: 5.5190755543079115} - return boxplotData; -} + /** + * For each channel, plot the marker of this channel low, q1, median, q3 and high values + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ + plotMarkersBoxPlots = () => { + let isFileExistFlat = false; + if(Opts.isBoxplotChannelBased) { + isFileExistFlat = isLocalFileExist( getGrpBoxplotFileName(), getGrpBoxplotLocalPath() ); + } else { // if it is cell based + isFileExistFlat = isLocalFileExist( getGrpMarkerCellsBoxplotFileName(), getGrpBoxplotLocalPath() ); + } -//For each channel, plot the marker of this channel low, q1, median, q3 and high values -plotMarkersBoxPlots = () => { - let isFileExistFlat = false; - if(Opts.isBoxplotChannelBased) { - isFileExistFlat = isLocalFileExist( getGrpBoxplotFileName(), getGrpBoxplotLocalPath() ); + if( isFileExistFlat ) { + if (! isGrpChannelsStatisticalDataAvailable() ) { - } else { // if it is cell based - isFileExistFlat = isLocalFileExist( getGrpMarkerCellsBoxplotFileName(), getGrpBoxplotLocalPath() ); - } + if(Opts.isBoxplotChannelBased) { + // load the data + grpChannelsStatisticalData = readJsonFile(getGrpBoxplotFileName(), getGrpBoxplotLocalPath() ); + } else { // if it is cell based + grpChannelsStatisticalData = readJsonFile(getGrpMarkerCellsBoxplotFileName(), getGrpBoxplotLocalPath() ); + } + + } + + let chartData = {channelNames: [], boxplots:[]}; // channelNames will have frame names + chartData.boxplots = grpChannelsStatisticalData.map(chnlData => { + return [ chnlData.min, chnlData.q1, chnlData.median, chnlData.q3, chnlData.max] + }) + // e.g. chartData.channelNames = [ "CD45", "IBA1", "KERATIN", "ASMA", "DNA 1" ] + chartData.channelNames = grpChannelsStatisticalData.map(chnlData => chnlData.Frame) - if( isFileExistFlat ) { - if (! isGrpChannelsStatisticalDataAvailable() ) { - if(Opts.isBoxplotChannelBased) { - // load the data - grpChannelsStatisticalData = readJsonFile(getGrpBoxplotFileName(), getGrpBoxplotLocalPath() ); + drawMarkersBoxPlotChart(chartData); - } else { // if it is cell based - grpChannelsStatisticalData = readJsonFile(getGrpMarkerCellsBoxplotFileName(), getGrpBoxplotLocalPath() ); - } - - } - - let chartData = {channelNames: [], boxplots:[]}; // channelNames will have frame names - chartData.boxplots = grpChannelsStatisticalData.map(chnlData => { - return [ chnlData.min, chnlData.q1, chnlData.median, chnlData.q3, chnlData.max] - }) + } else { + // In case using createTilesFeature.allTilesAtOnce = false, to create tileByTile features + triggerHint("No markers boxplot data found, do you want to calculate them? " + + '[Yes]' + + '[No]', "error", 10000); + } + } - // e.g. chartData.channelNames = [ "CD45", "IBA1", "KERATIN", "ASMA", "DNA 1" ] - chartData.channelNames = grpChannelsStatisticalData.map(chnlData => chnlData.Frame) +//----------------------------------------------------------------------------------// +//------------------------- Tile Mouse events -------------------------------------// +//----------------------------------------------------------------------------------// + /** + * Event on make tile/cell selection + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ - drawMarkersBoxPlotChart(chartData); + function onSelectedTile(d, i) { // Add interactivity + //-- setSelectedTile( d3.select(this).attr('id') ); + setSelectedTile(this); + let curTile = d3.select(this); + - } else { - // In case using createTilesFeature.allTilesAtOnce = false, to create tileByTile features - triggerHint("No markers boxplot data found, do you want to calculate them? " + - '[Yes]' + - '[No]', "error", 10000); - } -} + if(d3.select(this).attr('class') == "spx"){ + allSelection.push(this.id); // to be used for undo .. + } -/////////////////////////// Tile Mouse events /////////////////////////////////////// + //-- if( !isSuperPixel() ){ + + let prevTileId = getLastSelectedTileId(); + let prevTile = d3.select("#" + prevTileId); + //-- let strokeWidth = 10; + if( (prevTileId != null) && (prevTileId != getSelectedTileId() )) { + let origTileColor = prevTile.attr('origColor'); + prevTile.style('fill', origTileColor) + prevTile.style("fill-opacity", getBoundaryFillOpacity()) + prevTile.style('stroke', 'blue'); + prevTile.style('stroke-width', Opts.selectedTileStrokeWidth); + prevTile.style('stroke-opacity', 1); + //-- prevTileId= d3.select(this).attr('id'); + } else if((prevTileId == null) && (! isFeaturesLoaded()) ){ + triggerHint(" No features found for selected tile, create features from Features menu", "info", 5000); + } -function onSelectedTile (d, i) { // Add interactivity + setLastSelectedTileId( curTile.attr('id') ); + + //-- } + + + curTile.style("fill-opacity", Opts.selectedTileFillOpacity); + curTile.style("fill", Opts.selectedTileFillColor); + //-- d3.select("#" + this.id).style("fill-opacity", 'none') + //-- d3.select("#" + this.id).style('stroke-width', Opts.selectedTileStrokeWidth) + //-- d3.select("#" + this.id).style('stroke', 'yellow') + //-- d3.select("#" + this.id).style('stroke', 'yellow') + + let bbox = find_bbox(this); + let entry = findObjectByKeyValue( Boundary_box, 'id', getSelectedTileId() ); // to check whether the entry exists or no.. + + if (entry == null) { + Boundary_box.push({id: this.attributes.id.nodeValue, index: this.attributes.index.nodeValue, + // xcent: this.attributes.Xcentroid.nodeValue, ycent:this.attributes.Ycentroid.nodeValue, + left: bbox['left'], top: bbox['top'], width: bbox['width'], height:bbox['height']}); + } - //-- setSelectedTile( d3.select(this).attr('id') ); - setSelectedTile(this); - let curTile = d3.select(this); - + if( isFeaturesLoaded() ){ + freezeInput("findSimilarTileBtn", false); + document.getElementById("curRoiFont").innerHTML = getSelectedTileId(); + } - if(d3.select(this).attr('class') == "spx"){ - allSelection.push(this.id); // to be used for undo .. - } + //-- currentIndex = this.attributes.index.nodeValue; + //-- loadCanvas(bbox['left'],bbox['top'],bbox['width'],bbox['height'],currentIndex.toString()); - - //-- if( !isSuperPixel() ){ - let prevTileId = getLastSelectedTileId(); - let prevTile = d3.select("#" + prevTileId); - //-- let strokeWidth = 10; - if( (prevTileId != null) && (prevTileId != getSelectedTileId() )) { - let origTileColor = prevTile.attr('origColor'); - prevTile.style('fill', origTileColor) - prevTile.style("fill-opacity", getBoundaryFillOpacity()) - prevTile.style('stroke', 'blue'); - prevTile.style('stroke-width', Opts.selectedTileStrokeWidth); - prevTile.style('stroke-opacity', 1); - //-- prevTileId= d3.select(this).attr('id'); - } else if((prevTileId == null) && (! isFeaturesLoaded()) ){ - triggerHint(" No features found for selected tile, create features from Features menu", "info", 5000); - } + + // --if ($$("Features").getValue()=="RGB") + // -- { + // -- PlotRGB(bbox['left'],bbox['top'],bbox['width'],bbox['height'],$$("NumOfBins").getValue()); + + // -- sortedDeEnDistances=[]; + // -- HistFeatures1D=[]; + // -- var currentTileFeatures = findObjectByKeyValue(allTilesFeatures, 'id', d3.select(this).attr('id')); + // -- HistFeatures1D=getHistFeatures(bbox['left'],bbox['top'],bbox['width'],bbox['height'],$$("NumOfBins").getValue()); + + // -- if($$("findSimilarTiles").getValue()==1) + // -- { + // -- lookupSimilars(HistFeatures1D); + + // -- } - setLastSelectedTileId( curTile.attr('id') ); - - //-- } - - - curTile.style("fill-opacity", Opts.selectedTileFillOpacity); - curTile.style("fill", Opts.selectedTileFillColor); - //-- d3.select("#" + this.id).style("fill-opacity", 'none') - //-- d3.select("#" + this.id).style('stroke-width', Opts.selectedTileStrokeWidth) - //-- d3.select("#" + this.id).style('stroke', 'yellow') - //-- d3.select("#" + this.id).style('stroke', 'yellow') - - let bbox = find_bbox(this); - let entry = findObjectByKeyValue( Boundary_box, 'id', getSelectedTileId() ); // to check whether the entry exists or no.. - - if (entry == null) { - Boundary_box.push({id: this.attributes.id.nodeValue, index: this.attributes.index.nodeValue, - // xcent: this.attributes.Xcentroid.nodeValue, ycent:this.attributes.Ycentroid.nodeValue, - left: bbox['left'], top: bbox['top'], width: bbox['width'], height:bbox['height']}); - } + // -- } + - if( isFeaturesLoaded() ){ - freezeInput("findSimilarTileBtn", false); - document.getElementById("curRoiFont").innerHTML = getSelectedTileId(); - } + /*----------------------------------------------------------------------- + $$("findSimilarTiles").enable(); <------------------------------------------------- + ------------------------------------------------------------------------*/ - //-- currentIndex = this.attributes.index.nodeValue; - //-- loadCanvas(bbox['left'],bbox['top'],bbox['width'],bbox['height'],currentIndex.toString()); + } // end of onSelectedTile - - // --if ($$("Features").getValue()=="RGB") - // -- { - // -- PlotRGB(bbox['left'],bbox['top'],bbox['width'],bbox['height'],$$("NumOfBins").getValue()); - - // -- sortedDeEnDistances=[]; - // -- HistFeatures1D=[]; - // -- var currentTileFeatures = findObjectByKeyValue(allTilesFeatures, 'id', d3.select(this).attr('id')); - // -- HistFeatures1D=getHistFeatures(bbox['left'],bbox['top'],bbox['width'],bbox['height'],$$("NumOfBins").getValue()); - - // -- if($$("findSimilarTiles").getValue()==1) - // -- { - // -- lookupSimilars(HistFeatures1D); - - // -- } - // -- } - + /** + * When mouse leave OSD overlay + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ - /*----------------------------------------------------------------------- - $$("findSimilarTiles").enable(); <------------------------------------------------- - ------------------------------------------------------------------------*/ + function handleMouseLeave(d, i) { // Add interactivity -} // end of onSelectedTile + if( isBoundariesLoaded() ){ + document.getElementById("currentTile").innerHTML = "Total Tiles : " + getTotalTilesNum(); + } else { + document.getElementById("currentTile").innerHTML = ""; + } + -///////////////////////////////////////////////////// -// when mouse leave OSD overlay -function handleMouseLeave (d, i) { // Add interactivity - if( isBoundariesLoaded() ){ - document.getElementById("currentTile").innerHTML = "Total Tiles : " + getTotalTilesNum(); - } else { - document.getElementById("currentTile").innerHTML = ""; - } - + if( isFeaturesLoaded() ){ + if( (getSelectedChartOperation() == "Histogram") || (getSelectedChartOperation() == "Histogram-log1p(y)" )) { + resetChartPlottingData(); + } + } - if( isFeaturesLoaded() ){ - if( (getSelectedChartOperation() == "Histogram") || (getSelectedChartOperation() == "Histogram-log1p(y)" )) { - resetChartPlottingData(); - } - } + } -} -/////////////////////////////////////////////////// -//On cell mouse over event - show histogram of each cell -function handleMouseOver (d, i) { // Add interactivity - let tileType = getTileType(); + /** + * On cell mouse over event - show histogram of each cell + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ - let index = findObjectByKeyValue(eval(tileType + "TilesLabel"), 'id', this.attributes.id.nodeValue, 'INDEX' ); // to check whether the entry exists or no.. + function handleMouseOver(d, i) { // Add interactivity - if(index == null) { - if( isSuperPixel() ) { - document.getElementById("currentTile").innerHTML = "SPX ID : " + this.attributes.index.nodeValue; - } - else { - document.getElementById("currentTile").innerHTML = "Grid ID : " + this.attributes.index.nodeValue; - } - } else { - if( isSuperPixel() ) { - document.getElementById("currentTile").innerHTML = "SPX ID : " + this.attributes.index.nodeValue + ", Label : " + eval(tileType+"TilesLabel")[index].tilelabel; - } - else { - document.getElementById("currentTile").innerHTML = "Grid ID : " + this.attributes.index.nodeValue + ", Label : " + eval(tileType+"TilesLabel")[index].tilelabel; - } - } + let tileType = getTileType(); - - d3.select(this).style('stroke', Opts.StrokeColorOnHover); - d3.select(this).style('stroke-width', Opts.StrokeWidthOnHover); - d3.select(this).style('stroke-opacity', Opts.StrokeOpacityOnHover); - - if( getSelectedChartOperation() == "Histogram"){ - let bbox = find_bbox(this); // drawing is the shape flag - plotTileMarkersHistogram(bbox['left'], bbox['top'], bbox['width'], bbox['height'], this); - } else if(getSelectedChartOperation() == "Histogram-log1p(y)"){ - let bbox = find_bbox(this); // drawing is the shape flag - plotTileMarkersHistogram(bbox['left'], bbox['top'], bbox['width'], bbox['height'], this, true); - } -} + let index = findObjectByKeyValue(eval(tileType + "TilesLabel"), 'id', this.attributes.id.nodeValue, 'INDEX' ); // to check whether the entry exists or no.. -/////////////////////////////////////////////////// -function handleTileMouseLeave (d, i){ + if(index == null) { + if( isSuperPixel() ) { + document.getElementById("currentTile").innerHTML = "SPX ID : " + this.attributes.index.nodeValue; + } + else { + document.getElementById("currentTile").innerHTML = "Grid ID : " + this.attributes.index.nodeValue; + } + } else { + if( isSuperPixel() ) { + document.getElementById("currentTile").innerHTML = "SPX ID : " + this.attributes.index.nodeValue + ", Label : " + eval(tileType+"TilesLabel")[index].tilelabel; + } + else { + document.getElementById("currentTile").innerHTML = "Grid ID : " + this.attributes.index.nodeValue + ", Label : " + eval(tileType+"TilesLabel")[index].tilelabel; + } + } - if( isSimilarRegionBtnEnabled() ){ - //-- d3.select(this).style('stroke', getStrokeColor()); - //-- d3.select(this).style('stroke-width', getStrokeWidth()); - //-- d3.select(this).style('stroke-opacity', getStrokeOpacity()); - if(d3.select(this).style('fill') == "rgb(255, 255, 255)") { - d3.select(this).style('stroke', 'none'); - } else { - d3.select(this).style('stroke', Opts.similarTileStrokeColor); - } + + d3.select(this).style('stroke', Opts.StrokeColorOnHover); + d3.select(this).style('stroke-width', Opts.StrokeWidthOnHover); + d3.select(this).style('stroke-opacity', Opts.StrokeOpacityOnHover); + + if( getSelectedChartOperation() == "Histogram"){ + let bbox = find_bbox(this); // drawing is the shape flag + plotTileMarkersHistogram(bbox['left'], bbox['top'], bbox['width'], bbox['height'], this); + } else if(getSelectedChartOperation() == "Histogram-log1p(y)"){ + let bbox = find_bbox(this); // drawing is the shape flag + plotTileMarkersHistogram(bbox['left'], bbox['top'], bbox['width'], bbox['height'], this, true); + } + } - //-- d3.selectAll(tileClass).style('fill', 'white'); - - //-- d3.selectAll(tileClass).style('fill-opacity', 0.1); - } else { - d3.select(this).style('stroke', getStrokeColor()); - d3.select(this).style('stroke-width', getStrokeWidth()); - d3.select(this).style('stroke-opacity', getStrokeOpacity()); + /** + * On cell mouse over leave + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ - } + function handleTileMouseLeave(d, i) { - /* $$("propChart2").clearAll(); <--------------------------------*/ //<<<<<<<<<<< + if( isSimilarRegionBtnEnabled() ){ + //-- d3.select(this).style('stroke', getStrokeColor()); + //-- d3.select(this).style('stroke-width', getStrokeWidth()); + //-- d3.select(this).style('stroke-opacity', getStrokeOpacity()); + if(d3.select(this).style('fill') == "rgb(255, 255, 255)") { + d3.select(this).style('stroke', 'none'); + } else { + d3.select(this).style('stroke', Opts.similarTileStrokeColor); + } -} + //-- d3.selectAll(tileClass).style('fill', 'white'); + //-- d3.selectAll(tileClass).style('fill-opacity', 0.1); -///////////////////////////////////// + } else { + d3.select(this).style('stroke', getStrokeColor()); + d3.select(this).style('stroke-width', getStrokeWidth()); + d3.select(this).style('stroke-opacity', getStrokeOpacity()); -function handleMouseRightClick(){ - let currentRightClickedTile = this; - setRightClickedTile(this); + } - if( isSuperPixel() ) { - menu1[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; - menu2[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; - menu3[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; - } else { - menu1[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; - menu2[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; - menu3[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; + /* $$("propChart2").clearAll(); <--------------------------------*/ //<<<<<<<<<<< } - -} -///////////////////////////////////////////////////////////////////////////// -// For future use -// function mark(elem, marker){ + /** + * On mouse right click and shows cell index + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ -// var tileType = "SPX"; -// var tileSize = 64 ; + function handleMouseRightClick() { + let currentRightClickedTile = this; + setRightClickedTile(this); -// var index = findObjectByKey(eval(tileType + "TilesLabel"), 'id', elem.id, 'INDEX' ); // to check whether the entry exists or no.. + if( isSuperPixel() ) { + menu1[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; + menu2[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; + menu3[0].title = "SPX " + currentRightClickedTile.attributes.index.nodeValue; + } else { + menu1[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; + menu2[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; + menu3[0].title = "Grid " + currentRightClickedTile.attributes.index.nodeValue; + } + } -// if(marker != "None") { -// if(index == null) { -// eval(tileType + "TilesLabel").push({id: element.id, index: element.attributes.index.nodeValue, size: tileSize, tileLabel: marker}) + /** + * Mark the cell if it is postive cell or negative cell to use for classification + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ -// } else { -// if(eval(tileType + "TilesLabel")[index].tilelabel != marker) { -// eval(tileType + "TilesLabel")[index].tilelabel = marker; -// } -// } -// } else { -// eval(tileType + "TilesLabel").splice(index, 1); -// if(eval(tileType + "TilesLabel").length == 0) { -// $$("saveLabels").disable(); -// $$("heatmap").disable(); -// $$("PrevAnnotated").disable(); -// $$("NextAnnotated").disable(); -// } -// } -// } + function mark(elem, marker) { + + let tileType = getTileType(); + let tileSize; -function mark(elem, marker){ - - let tileType = getTileType(); - let tileSize; - if( isSuperPixel()){ - tileSize = null ; - } else { - tileSize = getGridSize(); - } + if( isSuperPixel()){ + tileSize = null ; - var index = findObjectByKeyValue(eval(tileType + "TilesLabel"), 'id', elem.id, 'INDEX' ); // to check whether the entry exists or no.. + } else { + tileSize = getGridSize(); + } + // To check whether the entry exists or no.. + let index = findObjectByKeyValue(eval(tileType + "TilesLabel"), 'id', elem.id, 'INDEX' ); - if(marker != "None") { + if(marker != "None") { - if(index == null) { - eval(tileType + "TilesLabel").push({id: element.id, index: element.attributes.index.nodeValue, size: tileSize, tileLabel: marker}) + if(index == null) { + eval(tileType + "TilesLabel").push({id: element.id, index: element.attributes.index.nodeValue, size: tileSize, tileLabel: marker}) + + } else { + + if(eval(tileType + "TilesLabel")[index].tilelabel != marker) { + eval(tileType + "TilesLabel")[index].tilelabel = marker; + } + } + } else { + eval(tileType + "TilesLabel").splice(index, 1); + if(eval(tileType + "TilesLabel").length == 0) { + $$("saveLabels").disable(); + $$("heatmap").disable(); + $$("PrevAnnotated").disable(); + $$("NextAnnotated").disable(); + } + } + } + + ///////////////////--- Right click menus ---/////////////////////// - } else { - if(eval(tileType + "TilesLabel")[index].tilelabel != marker) { - eval(tileType + "TilesLabel")[index].tilelabel = marker; - } - } - } else { - eval(tileType + "TilesLabel").splice(index, 1); - if(eval(tileType + "TilesLabel").length == 0) { - $$("saveLabels").disable(); - $$("heatmap").disable(); - $$("PrevAnnotated").disable(); - $$("NextAnnotated").disable(); - } - } -} -////////////////////////////////////////// menu1 = [{ title: '', action: function(elm, d, i) { - } - }] @@ -13197,193 +13218,299 @@ function mark(elem, marker){ }] - - menu3 = [{ - title: '', - action: function(elm, d, i) { + title: '', + action: function(elm, d, i) { - } + } - },{ - - title: 'Mark Positive', - action: function(elm, d, i) { - element = getRightClickedTile(); - d3.selectAll("#" + element.id).style("fill", "green") - d3.selectAll("#" + element.id).style("fill-opacity", "1") - d3.selectAll("#" + element.id).style("stroke", "green") - d3.selectAll("#" + element.id).style("stroke-opacity", "1") - mark(element,"P"); - } - },{ - - title: 'Mark Negative', - action: function(elm, d, i) { - element = getRightClickedTile(); - d3.selectAll("#" + element.id).style("fill", "red") - d3.selectAll("#" + element.id).style("fill-opacity", "1") - d3.selectAll("#" + element.id).style("stroke", "red") - d3.selectAll("#" + element.id).style("stroke-opacity", "1") - mark(element,"N"); - } - - }, { - title: 'Remove Selection', - action: function(elm, d, i) { - element = getRightClickedTile(); - OrigColor = d3.select("#" + element.id).attr('origColor') + },{ - d3.selectAll("#" + element.id).style("fill", OrigColor) - d3.selectAll("#" + element.id).style("fill-opacity", $$("SpxOpacity").getValue()) - d3.selectAll("#" + element.id).style("stroke", 'none') - mark(element,"None"); - - } - }, { - title: 'Show Tile', - action: function(elm, d, i) { - currentRightClickedTile = getRightClickedTile(); - rightClickedbbox = find_bbox(currentRightClickedTile); - currentRightClickedIndex = currentRightClickedTile.attributes.index.nodeValue; - loadCanvas(rightClickedbbox['left'],rightClickedbbox['top'],rightClickedbbox['width'],rightClickedbbox['height'],currentRightClickedIndex.toString()); - - } - }] + title: 'Mark Positive', + action: function(elm, d, i) { + element = getRightClickedTile(); + d3.selectAll("#" + element.id).style("fill", "green") + d3.selectAll("#" + element.id).style("fill-opacity", "1") + d3.selectAll("#" + element.id).style("stroke", "green") + d3.selectAll("#" + element.id).style("stroke-opacity", "1") + mark(element,"P"); + } + },{ -////////////////////////////////////////////////////////////////// -//////////////////---------- Features ----------////////////////// -////////////////////////////////////////////////////////////////// + title: 'Mark Negative', + action: function(elm, d, i) { + element = getRightClickedTile(); + d3.selectAll("#" + element.id).style("fill", "red") + d3.selectAll("#" + element.id).style("fill-opacity", "1") + d3.selectAll("#" + element.id).style("stroke", "red") + d3.selectAll("#" + element.id).style("stroke-opacity", "1") + mark(element,"N"); + } -isFeaturesLoaded = () => { - return allTilesFeatures.length ? true : false; -} + }, { -resetTileFeatures = () => { - allTilesFeatures = []; - grpChannelsStatisticalData = []; - dapiMorphStatisticalData = {}; - cellBasicClassification = []; - allTilesFeaturesAndClassification =[]; - filteredNeighbors = {}; -} + title: 'Remove Selection', + action: function(elm, d, i) { + element = getRightClickedTile(); + OrigColor = d3.select("#" + element.id).attr('origColor') -getGrpFeaturesData = () => { - // to be coded -} + d3.selectAll("#" + element.id).style("fill", OrigColor) + d3.selectAll("#" + element.id).style("fill-opacity", $$("SpxOpacity").getValue()) + d3.selectAll("#" + element.id).style("stroke", 'none') + mark(element,"None"); + + } + }, { -isBoundariesLoaded = () => { - return d3.selectAll( getClassType() )._groups[0].length ? true : false; -} + title: 'Show Tile', + action: function(elm, d, i) { + currentRightClickedTile = getRightClickedTile(); + rightClickedbbox = find_bbox(currentRightClickedTile); + currentRightClickedIndex = currentRightClickedTile.attributes.index.nodeValue; + loadCanvas(rightClickedbbox['left'],rightClickedbbox['top'],rightClickedbbox['width'],rightClickedbbox['height'],currentRightClickedIndex.toString()); + + } + }] -getTotalTilesNum = () => { - return d3.selectAll( getClassType() )._groups[0].length; -} +//--------------------------------------------------------------// +//----------------------- Features -----------------------------// +//--------------------------------------------------------------// -zoomToTile = (obj) => { - // console.log(obj) - var obj_bbox = find_bbox(obj); - let topFrame = viewer.world.getItemAt(0); - //-- var zoomArea = viewer.viewport.imageToViewportRectangle(obj_bbox['left'], obj_bbox['top'], obj_bbox['width'], obj_bbox['height']); - var zoomArea = topFrame.imageToViewportRectangle(obj_bbox['left'], obj_bbox['top'], obj_bbox['width'], obj_bbox['height']); - viewer.viewport.fitBounds(zoomArea); -} + /** + * Check if features loaded. + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {bool} + * + */ -/////////////////////////////////////////////// -// function locateTile(spx_Class, bound_ID){ // locat SPX on WSI by its id -// d3.selectAll(spx_Class).each(function(d) { -// if(d3.select(this).attr('id') == bound_ID) { -// zoomToTile(this); -// } -// }); -// } + isFeaturesLoaded = () => { + return allTilesFeatures.length ? true : false; + } + /** + * Reset tile features + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ + resetTileFeatures = () => { + allTilesFeatures = []; + grpChannelsStatisticalData = []; + dapiMorphStatisticalData = {}; + cellBasicClassification = []; + allTilesFeaturesAndClassification =[]; + filteredNeighbors = {}; + } -findTile = () => { - let tileToFind = document.getElementById("tileSearchBox").value; - let tileId = isSuperPixel() ? 'spx-' + tileToFind : 'grid-' + tileToFind; - let tileClass = getClassType(); - if( isBoundariesLoaded() ) { - let existFlag = 0; - d3.selectAll(tileClass).each(function(d) { - if (d3.select(this).attr('id') == tileId){ - existFlag = 1; - } - }) + /** + * For future use.. + * + * @function + * @todo Write the doc. + * @todo Implement this function. + */ + getGrpFeaturesData = () => { + // to be coded + } - if(existFlag) { - let foundTile = document.getElementById(tileId); - zoomToTile(foundTile); - //-- locateTile(tileClass, tileId); - //-- existFlag = 0; - } else { - triggerHint("Not a valid entry"); - } - } else { - triggerHint("No loaded tiles") + /** + * Check if boundaries loaded. + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {bool} + * + */ + isBoundariesLoaded = () => { + return d3.selectAll( getClassType() )._groups[0].length ? true : false; + } + + + /** + * Get total cell number + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {number} + * + */ + getTotalTilesNum = () => { + return d3.selectAll( getClassType() )._groups[0].length; + } + + /** + * Zoom to specific cell + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @param {object} obj : object + * + */ + zoomToTile = (obj) => { + // console.log(obj) + var obj_bbox = find_bbox(obj); + let topFrame = viewer.world.getItemAt(0); + //-- var zoomArea = viewer.viewport.imageToViewportRectangle(obj_bbox['left'], obj_bbox['top'], obj_bbox['width'], obj_bbox['height']); + var zoomArea = topFrame.imageToViewportRectangle(obj_bbox['left'], obj_bbox['top'], obj_bbox['width'], obj_bbox['height']); + viewer.viewport.fitBounds(zoomArea); + } + + + /** + * @deprecated since version 1.0.0 + */ + function locateTile(spx_Class, bound_ID){ // locat SPX on WSI by its id + d3.selectAll(spx_Class).each(function(d) { + if(d3.select(this).attr('id') == bound_ID) { + zoomToTile(this); + } + }); } - -} + + /** + * Using search box to find cell by id and zoom to + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ + + findTile = () => { + let tileToFind = document.getElementById("tileSearchBox").value; + let tileId = isSuperPixel() ? 'spx-' + tileToFind : 'grid-' + tileToFind; + let tileClass = getClassType(); + + if( isBoundariesLoaded() ) { + let existFlag = 0; + d3.selectAll(tileClass).each(function(d) { + if (d3.select(this).attr('id') == tileId){ + existFlag = 1; + } + }) + + if(existFlag) { + let foundTile = document.getElementById(tileId); + zoomToTile(foundTile); + //-- locateTile(tileClass, tileId); + //-- existFlag = 0; + } else { + triggerHint("Not a valid entry"); + } + + } else { + triggerHint("No loaded tiles") + } + } /////////////////////////////////////////////////////////////////////////////////////// -initBoundariesFeatures = () => { - let featuresLoadedFlag = false; - - if( isLocalFileExist( getGrpFeaturesFileName(), getGrpFeaturesLocalPath()) ) { - // load the features - createLoadFeatures(); - //-- document.getElementById("createLoadFeaturesBtn").disabled = true; - if( isFeaturesLoaded() ) { - freezeInput("createLoadFeaturesBtn", true); - featuresLoadedFlag = true; - } else { - freezeInput("createLoadFeaturesBtn", false); - } - - } else { - if( isLocalFileExist( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath()) ) { - document.getElementById("createLoadFeaturesBtn").innerHTML = "Resume"; - triggerHint("Incomplete features found, click Resume from Features menu", "info", 7000); // was 7000 - } else { - document.getElementById("createLoadFeaturesBtn").innerHTML = "Create"; - triggerHint("No features found, click Create features from Features menu", "info", 7000); // was 7000 - } - - // Enable features Create/Resume switch - if( !isFeaturesCreateBtnEnabled() ) { - freezeInput("createLoadFeaturesBtn", false); + + /** + * init cell features by loading after loading boundaries + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * + */ + + initBoundariesFeatures = () => { + let featuresLoadedFlag = false; + + if( isLocalFileExist( getGrpFeaturesFileName(), getGrpFeaturesLocalPath()) ) { + // load the features + createLoadFeatures(); + //-- document.getElementById("createLoadFeaturesBtn").disabled = true; + if( isFeaturesLoaded() ) { + freezeInput("createLoadFeaturesBtn", true); + featuresLoadedFlag = true; + } else { + freezeInput("createLoadFeaturesBtn", false); + } + + } else { + if( isLocalFileExist( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath()) ) { + document.getElementById("createLoadFeaturesBtn").innerHTML = "Resume"; + triggerHint("Incomplete features found, click Resume from Features menu", "info", 7000); // was 7000 + } else { + document.getElementById("createLoadFeaturesBtn").innerHTML = "Create"; + triggerHint("No features found, click Create features from Features menu", "info", 7000); // was 7000 + } + + // Enable features Create/Resume switch + if( !isFeaturesCreateBtnEnabled() ) { + freezeInput("createLoadFeaturesBtn", false); + } + } - } + return featuresLoadedFlag; + } - return featuresLoadedFlag; -} + /** + * Get the class of selected boundary type + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {string} e.g. ".spx" + */ + getClassType = () => { + return isSuperPixel() ? ".spx" : ".grid"; + } - // Get the class of selected boundary type - getClassType = () => { - return isSuperPixel() ? ".spx" : ".grid"; - } + /** + * Get tile type + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {string} e.g. "SPX" + */ - // Get tile type - getTileType = () => { - return isSuperPixel() ? "SPX" : "Grid"; - } + getTileType = () => { + return isSuperPixel() ? "SPX" : "Grid"; + } - getTileIdPrefix = () => { - return isSuperPixel() ? "spx-" : "grid-"; - } - splitString = (string, size) => { // https://gist.github.com/hendriklammers/5231994 - let re = new RegExp('.{1,' + size + '}', 'g'); - return string.match(re); - } + /** + * Get tile id prefix + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {string} e.g. "spx-" + */ + + getTileIdPrefix = () => { + return isSuperPixel() ? "spx-" : "grid-"; + } + /** * For future use.. @@ -13397,6 +13524,20 @@ initBoundariesFeatures = () => { // For future use } + + /** + * Save feature data locally to local file + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @param {Array} featuresData : array of objects [ { label: 1, features: (5) […], area: 348.5, … }, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ] + * @param {string} fileName + * @param {string} dirLocalPath + * @returns {bool} saveSuccessFlag + */ + saveFeatureDataLocally = (featuresData, fileName, dirLocalPath ) => { let saveSuccessFlag = true; @@ -13438,316 +13579,333 @@ initBoundariesFeatures = () => { return saveSuccessFlag; } - isValidFeaturesData = (featuresData) =>{ - return (featuresData.length == getTotalTilesNum()) && featuresData.length ? true : false; - } - - createLoadFeatures = () => { - //--let fetchedFeatures = getFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); <<<<<<<<<<-------- - allTilesFeatures = readJsonFile( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); + /** + * Check if features data is valid and its length equal total cell number + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @param {Array} featuresData : array of objects [ { label: 1, features: (5) […], area: 348.5, … }, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ] + * @returns {bool} saveSuccessFlag + */ - - //-- let currentTileId = getSelectedTileId(); + isValidFeaturesData = (featuresData) =>{ + return (featuresData.length == getTotalTilesNum()) && featuresData.length ? true : false; + } - //-- resetTileFeatures(); <<<<<<<<<<<<<<<< ------------ - - //-- if((fetchedFeatures == null) || ( fetchedFeatures == "notExist")) { - if(! allTilesFeatures.length ) { - //--zoomSlideOut(); - viewerZoomHome(); // <<<<<<<<<<< ------------ enforce sync - webix.message("Creating features in progress..."); - - //-- let isIncompleteFileExist = false; - //-- let fetchedIncompletedFeatures = getFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); - allTilesFeatures = readJsonFile( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); - - //-- if((fetchedIncompletedFeatures != null) && ( fetchedIncompletedFeatures != "notExist")) { - //-- allTilesFeatures = JSON.parse(fetchedIncompletedFeatures); - //-- isIncompleteFileExist = true; - //-- } + /** + * Create or load features if exists + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @param {Array} featuresData : array of objects [ { label: 1, features: (5) […], area: 348.5, … }, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ] + * @returns {bool} saveSuccessFlag + */ - // backup plan in case it txt_allTilesFeatures failed to be created by json.strinify() - triggerHint("Wait while creating channels mean, max, std, norm features file","info", 5000); - let creationSuccessFlag = true; - let fileChunksToSaveTemp = []; - let maxFileSize = Opts.maxFileSize; - //-- let safeMargin = 2000; + createLoadFeatures = () => { + //--let fetchedFeatures = getFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); <<<<<<<<<<-------- + allTilesFeatures = readJsonFile( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); - try { - - if(Opts.createTilesFeature.allTilesAtOnce) { - - // let bbox = find_bbox(this); - // let curTileFeatures = getTileProp( bbox['left'], bbox['top'], bbox['width'], bbox['height']); + //-- let currentTileId = getSelectedTileId(); - if( isSuperPixel() ) { - allTilesFeatures = getAllSpxTilesFeature(); - } else { - allTilesFeatures = getAllGridTilesFeature(); - } + //-- resetTileFeatures(); <<<<<<<<<<<<<<<< ------------ + + //-- if((fetchedFeatures == null) || ( fetchedFeatures == "notExist")) { + if(! allTilesFeatures.length ) { + //--zoomSlideOut(); + viewerZoomHome(); // <<<<<<<<<<< ------------ enforce sync + webix.message("Creating features in progress..."); + + //-- let isIncompleteFileExist = false; + //-- let fetchedIncompletedFeatures = getFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); + allTilesFeatures = readJsonFile( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); + + //-- if((fetchedIncompletedFeatures != null) && ( fetchedIncompletedFeatures != "notExist")) { + //-- allTilesFeatures = JSON.parse(fetchedIncompletedFeatures); + //-- isIncompleteFileExist = true; + //-- } - if(allTilesFeatures == "Failed") { - allTilesFeatures = []; - triggerHint("Can not convert image channels to gray image.. ", "error", 5000); - return 0; - } + // backup plan in case it txt_allTilesFeatures failed to be created by json.strinify() + triggerHint("Wait while creating channels mean, max, std, norm features file","info", 5000); + let creationSuccessFlag = true; + let fileChunksToSaveTemp = []; + let maxFileSize = Opts.maxFileSize; + //-- let safeMargin = 2000; + + try { + if(Opts.createTilesFeature.allTilesAtOnce) { + + //-- let bbox = find_bbox(this); + //-- let curTileFeatures = getTileProp( bbox['left'], bbox['top'], bbox['width'], bbox['height']); - if(allTilesFeatures == "chNormFailed") { - allTilesFeatures = []; - triggerHint("Image normalization " + "Failed" + - " due to insufficient Memory, Would you like to bypass normalize step " + - '[Yes]' + - '[No]', "error", 60000); + if( isSuperPixel() ) { + allTilesFeatures = getAllSpxTilesFeature(); + } else { + allTilesFeatures = getAllGridTilesFeature(); + } + if(allTilesFeatures == "Failed") { + allTilesFeatures = []; + triggerHint("Can not convert image channels to gray image.. ", "error", 5000); + return 0; + } - console.log("Image normalization Failed due to insufficient Memory, there is a need to bypass normalization or to reduce/crop image size or free more memory, or increase memory size " ); - //-- Opts.isChannelNormalizeRequired = false; - //--setItemMetadataKeyValue(metaKey, metaValue) - return 0; - } + if(allTilesFeatures == "chNormFailed") { + allTilesFeatures = []; + triggerHint("Image normalization " + "Failed" + + " due to insufficient Memory, Would you like to bypass normalize step " + + '[Yes]' + + '[No]', "error", 60000); - //-- let metaKey = "settings"; - //-- let metaValue = Opts.isChannelNormalizeRequired == true ? {"imageNorm": true } : {"imageNorm": false }; - //-- setItemMetadataKeyValue(metaKey, metaValue); + console.log("Image normalization Failed due to insufficient Memory, there is a need to bypass normalization or to reduce/crop image size or free more memory, or increase memory size " ); + //-- Opts.isChannelNormalizeRequired = false; + //--setItemMetadataKeyValue(metaKey, metaValue) + return 0; + } - } else { // create features tile by tile, time consuming and high time complexity - - d3.selectAll(getClassType()).each(function(d) { + //-- let metaKey = "settings"; + //-- let metaValue = Opts.isChannelNormalizeRequired == true ? {"imageNorm": true } : {"imageNorm": false }; + //-- setItemMetadataKeyValue(metaKey, metaValue); - if( ! findObjectByKeyValue(allTilesFeatures, 'id', this.id) ) { // check in case of resume features creation interrupted before - let bbox = find_bbox(this); - let curTileFeatures = getTileProp( bbox['left'], bbox['top'], bbox['width'], bbox['height']); + } else { // create features tile by tile, time consuming and high time complexity + + d3.selectAll(getClassType()).each(function(d) { + if( ! findObjectByKeyValue(allTilesFeatures, 'id', this.id) ) { // check in case of resume features creation interrupted before + let bbox = find_bbox(this); + let curTileFeatures = getTileProp( bbox['left'], bbox['top'], bbox['width'], bbox['height']); - if( isSuperPixel() ) { - // need to have function to convert to DSA format - allTilesFeatures.push({id: this.id , coordinates: this.attributes.points, features: curTileFeatures}); - fileChunksToSaveTemp.push({id: this.id , coordinates: this.attributes.points, features: curTileFeatures}); - //-- allTilesFeatures.push({id:this.id , Frames: numOfFrames, features:curTileFeatures}) - } else { - allTilesFeatures.push({id: this.id, coordinates: bbox, features: curTileFeatures}); - fileChunksToSaveTemp.push({id: this.id, coordinates: bbox, features: curTileFeatures}); - } - // to save temp features file in case of interruption - if(JSON.stringify(fileChunksToSaveTemp).length >= (maxFileSize - (JSON.stringify(fileChunksToSaveTemp[0]).length) * 2 ) ) { - saveFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() , JSON.stringify(fileChunksToSaveTemp), "a", 0); - fileChunksToSaveTemp = []; - } + if( isSuperPixel() ) { + // need to have function to convert to DSA format + allTilesFeatures.push({id: this.id , coordinates: this.attributes.points, features: curTileFeatures}); + fileChunksToSaveTemp.push({id: this.id , coordinates: this.attributes.points, features: curTileFeatures}); + //-- allTilesFeatures.push({id:this.id , Frames: numOfFrames, features:curTileFeatures}) + } else { + allTilesFeatures.push({id: this.id, coordinates: bbox, features: curTileFeatures}); + fileChunksToSaveTemp.push({id: this.id, coordinates: bbox, features: curTileFeatures}); + } + // to save temp features file in case of interruption + if(JSON.stringify(fileChunksToSaveTemp).length >= (maxFileSize - (JSON.stringify(fileChunksToSaveTemp[0]).length) * 2 ) ) { + saveFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() , JSON.stringify(fileChunksToSaveTemp), "a", 0); + fileChunksToSaveTemp = []; + } - + //-- if(isIncompleteFileExist) { + //-- saveFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() , fileChunks[k], "a", 0); - //-- if(isIncompleteFileExist) { - //-- saveFeatures( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() , fileChunks[k], "a", 0); - - //-- } else if( allTilesFeatures.length == getTotalTilesNum() ){ - - //-- } else { - //-- saveFeatures( fileName, dirLocalPath, fileChunks[k], "a", -1); + //-- } else if( allTilesFeatures.length == getTotalTilesNum() ){ + + //-- } else { + //-- saveFeatures( fileName, dirLocalPath, fileChunks[k], "a", -1); - //-- } - } + //-- } + } - //-- if(this.id != currentTileId) { - d3.select(this).style('fill', Opts.defaultScanningFillColor); - d3.select(this).style('fill-opacity', Opts.defaultScanningFillOpacity); - //-- } + //-- if(this.id != currentTileId) { + d3.select(this).style('fill', Opts.defaultScanningFillColor); + d3.select(this).style('fill-opacity', Opts.defaultScanningFillOpacity); + //-- } - }) // end of d3.selectAll loop - - // Get All tiles at once // - } - - } catch(err) { - creationSuccessFlag = false; - console.error("Error during features creation: ", err); - } + }) // end of d3.selectAll loop + + // Get All tiles at once // + } + + } catch(err) { + creationSuccessFlag = false; + console.error("Error during features creation: ", err); + } - // let isSavedLocally = false; + //-- let isSavedLocally = false; - if(creationSuccessFlag) { - let isSavedLocally = saveFeatureDataLocally(allTilesFeatures, getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); - alert("Features creation is completed"); + if(creationSuccessFlag) { + let isSavedLocally = saveFeatureDataLocally(allTilesFeatures, getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); + alert("Features creation is completed"); - if(isSavedLocally) { + if(isSavedLocally) { - triggerHint("Successfully saved Features locally", "info", 5000); - if( isLocalFileExist( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath()) ) { - removeLocalFile( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); - } + triggerHint("Successfully saved Features locally", "info", 5000); + if( isLocalFileExist( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath()) ) { + removeLocalFile( getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); + } - } else { - triggerHint("Some or all features could not be saved, repeat create features ", "error", 10000); - console.log("Some or all features could not be saved, repeat create features "); - // rmove corrupted file - removeLocalFile( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); - } + } else { + triggerHint("Some or all features could not be saved, repeat create features ", "error", 10000); + console.log("Some or all features could not be saved, repeat create features "); + // rmove corrupted file + removeLocalFile( getGrpFeaturesFileName(), getGrpFeaturesLocalPath() ); + } - } else { - //-- saveFeatureDataLocally(allTilesFeatures, getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); - if( isRestApiAvailable() ){ - console.log("Try to resume features creation by clicking Create/Resume button again, "); - alert("Network error, check console (F12)."); - triggerHint("Try to resume features creation by clicking Create/Resume button again","info", 5000); } else { - console.log("RestApi flask is down, Try to restart it and resume features creation by clicking Create/Resume button again"); - alert("Network error, check console (F12)."); - triggerHint("RestApi flask is down, Try to restart it and resume features creation by clicking Create/Resume button again","info", 5000); - } - - return 0; - } - - - + //-- saveFeatureDataLocally(allTilesFeatures, getGrpFeaturesTemporaryFileName(), getGrpFeaturesLocalPath() ); + if( isRestApiAvailable() ){ + console.log("Try to resume features creation by clicking Create/Resume button again, "); + alert("Network error, check console (F12)."); + triggerHint("Try to resume features creation by clicking Create/Resume button again","info", 5000); + } else { + console.log("RestApi flask is down, Try to restart it and resume features creation by clicking Create/Resume button again"); + alert("Network error, check console (F12)."); + triggerHint("RestApi flask is down, Try to restart it and resume features creation by clicking Create/Resume button again","info", 5000); + } - // let errorFlag = false; + return 0; + } - // try { - // txt_allTilesFeatures = JSON.stringify(allTilesFeatures); - // let maxFileSize = Opts.maxFileSize;; // to send it to flask and avoid javascript syntaxerror "the url is malformed " - // if(txt_allTilesFeatures.length > maxFileSize) - // { - // let fileChunks = splitString(txt_allTilesFeatures, maxFileSize); // true means newline included + //-- let errorFlag = false; - // for(let k = 0; k < fileChunks.length; k++) { - // console.log("Save file part " + (k + 1) + "/" + fileChunks.length); - - // if(k == 0) { - // saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a", -1); // First chunk - // } + //-- try { + //-- txt_allTilesFeatures = JSON.stringify(allTilesFeatures); + //-- let maxFileSize = Opts.maxFileSize;; // to send it to flask and avoid javascript syntaxerror "the url is malformed " - // if( ((k + 1) < fileChunks.length) && (k > 0) ) { - // saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a"); - - // } + //-- if(txt_allTilesFeatures.length > maxFileSize) + //-- { + //-- let fileChunks = splitString(txt_allTilesFeatures, maxFileSize); // true means newline included - // if( (k + 1) == fileChunks.length ) { - // saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a", 1); // Set lastChunkFlag to 1, this is last chunk - // } + //-- for(let k = 0; k < fileChunks.length; k++) { + //-- console.log("Save file part " + (k + 1) + "/" + fileChunks.length); + + //-- if(k == 0) { + //-- saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a", -1); // First chunk + //-- } - // } - // } else { - // // saveFeatures(StorageItemName,txt_allTilesFeatures) - // saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), txt_allTilesFeatures); - // triggerHint("Saving Features locally in progress... "); - // } + //-- if( ((k + 1) < fileChunks.length) && (k > 0) ) { + //-- saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a"); + + //-- } - // } catch(err) { - // errorFlag = true; - // triggerHint("JSON.stringify can not convert big features file to string","error"); - // } + //-- if( (k + 1) == fileChunks.length ) { + //-- saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), fileChunks[k], "a", 1); // Set lastChunkFlag to 1, this is last chunk + //-- } + //-- } + //-- } else { + //-- // saveFeatures(StorageItemName,txt_allTilesFeatures) + //-- saveFeatures( getGrpFeaturesFileName(), getGrpFeaturesLocalPath(), txt_allTilesFeatures); + //-- triggerHint("Saving Features locally in progress... "); + //-- } + + //-- } catch(err) { + //-- errorFlag = true; + //-- triggerHint("JSON.stringify can not convert big features file to string","error"); + //-- } - - //-- freezeInput("createLoadFeaturesBtn", true); <<<<<<<<<<<<---------- - } else { // end of if(fetchedFeatures==null) - // if features exists locally - //-- allTilesFeatures = JSON.parse(fetchedFeatures); <<<<<<<<<<< ------- + + //-- freezeInput("createLoadFeaturesBtn", true); <<<<<<<<<<<<---------- - if(isValidFeaturesData(allTilesFeatures)) { - // webix.message(" local saved features loaded.. "); <<<<<<<<<<<<<<<< ---------- - triggerHint(" local saved features loaded.. ", "info"); - //-- freezeInput("createLoadFeaturesBtn", true); // disable create button <<<<<<<<<<<<---------- - - //-- $$("featuresCheckBoxes").enable(); <<<<<<<<<<<<<<<<<---------------------------- - //-- $$("similarityOptions").enable(); - //-- disableSimilarTilesBtn(false); - //-- freezeFeaturesControls(false); - } - } + } else { // end of if(fetchedFeatures==null) + // if features exists locally + //-- allTilesFeatures = JSON.parse(fetchedFeatures); <<<<<<<<<<< ------- - //check for dapi cells morphological statistical data e.g. area: min, max, "25%", "50%", "75%" - dapiMorphStatisticalData = readJsonFile( getDapiMorphStatFileName(), getItemFeaturesLocalPath() ); - // dapiMorphStatisticalData is Object { area: {…}, eccentricity: {…}, extent: {…}, ..} - //If dapi cells morphological statistical data file not exist, create them + if(isValidFeaturesData(allTilesFeatures)) { + // webix.message(" local saved features loaded.. "); <<<<<<<<<<<<<<<< ---------- + triggerHint(" local saved features loaded.. ", "info"); + //-- freezeInput("createLoadFeaturesBtn", true); // disable create button <<<<<<<<<<<<---------- + + //-- $$("featuresCheckBoxes").enable(); <<<<<<<<<<<<<<<<<---------------------------- + //-- $$("similarityOptions").enable(); + //-- disableSimilarTilesBtn(false); + //-- freezeFeaturesControls(false); + } + } - if(! Object.keys(dapiMorphStatisticalData).length) { - dapiMorphStatisticalData = getDapiCellsMorphStatData() - } + // check for dapi cells morphological statistical data e.g. area: min, max, "25%", "50%", "75%" + dapiMorphStatisticalData = readJsonFile( getDapiMorphStatFileName(), getItemFeaturesLocalPath() ); + // dapiMorphStatisticalData is Object { area: {…}, eccentricity: {…}, extent: {…}, ..} + // If dapi cells morphological statistical data file not exist, create them - // if loaded or created succesfully ... - if(Object.keys(dapiMorphStatisticalData).length) { - activateDapiCellsMorphOptionsList(dapiMorphStatisticalData); - } + if(! Object.keys(dapiMorphStatisticalData).length) { + dapiMorphStatisticalData = getDapiCellsMorphStatData() + } -} // end of function + // if loaded or created succesfully ... + if(Object.keys(dapiMorphStatisticalData).length) { + activateDapiCellsMorphOptionsList(dapiMorphStatisticalData); + } + } // end of function -showLoadingIcon = () => { - var defer = $.Deferred(); - document.getElementById("loadingIcon").style.display = 'block'; - setTimeout(function() { - defer.resolve(); // When this fires, the code in a().then(/..../); is executed. - }, 100); + /** + * Show the loading icon in the middle of screen + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {promise} + */ - return defer; -} + showLoadingIcon = () => { + var defer = $.Deferred(); + document.getElementById("loadingIcon").style.display = 'block'; + setTimeout(function() { + defer.resolve(); // When this fires, the code in a().then(/..../); is executed. + }, 100); -hideLoadingIcon = () => { - document.getElementById("loadingIcon").style.display = "none"; -} + return defer; + } -// getSelectedTileObj = () => { -// return currentTileObj; -// } + /** + * Hide the loading icon + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + */ -// setSelectedTileObj = (obj) => { -// currentTileObj = obj; -// } -// getSelectedTileId = () => { -// return currentTileId; -// } + hideLoadingIcon = () => { + document.getElementById("loadingIcon").style.display = "none"; + } -// setSelectedTile = (id) => { -// currentTileId = id; -// } - - /** - * Check if tile selected - * Tile is a SPX cell - * - * @function - * @memberof HistoJS - * @since 1.0.0 - * @version 1.0.0 - * @returns {bool} - */ + /** + * Check if tile selected + * Tile is a SPX cell + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {bool} + */ - isTileSelected = () => { - return currentGrpFeaturesSelectionStates.tile != null ? true : false; - } + isTileSelected = () => { + return currentGrpFeaturesSelectionStates.tile != null ? true : false; + } - /** - * Get selected tile - * - * @function - * @memberof HistoJS - * @since 1.0.0 - * @version 1.0.0 - * @returns {object} - * @example - * - * getSelectedTile() - * - * => - */ + /** + * Get selected tile + * + * @function + * @memberof HistoJS + * @since 1.0.0 + * @version 1.0.0 + * @returns {object} + * @example + * + * getSelectedTile() + * + * => + */ - getSelectedTile = () => { - return isTileSelected() ? currentGrpFeaturesSelectionStates.tile : null; - } + getSelectedTile = () => { + return isTileSelected() ? currentGrpFeaturesSelectionStates.tile : null; + } /** * Get selected tile id diff --git a/js/libs/splitString.js b/js/libs/splitString.js new file mode 100644 index 0000000..6cb369f --- /dev/null +++ b/js/libs/splitString.js @@ -0,0 +1,17 @@ +/** + * Split string into chunks of the given size + * @see {@link https://gist.github.com/hendriklammers/5231994} + * @author Hendrik Lammers + * @function + * @param {String} string is the String to split + * @param {Number} size is the size you of the cuts + * @returns {Array} an Array with the strings + * @example + * splitString("zzz",1) + * => Array(3) [ "z", "z", "z" ] + */ + +splitString = (string, size) => { + let re = new RegExp('.{1,' + size + '}', 'g'); + return string.match(re); +} \ No newline at end of file diff --git a/test/histojs_test/analysisFunctions_test.js b/test/histojs_test/analysisFunctions_test.js index 6a147cc..abd88d9 100644 --- a/test/histojs_test/analysisFunctions_test.js +++ b/test/histojs_test/analysisFunctions_test.js @@ -142,6 +142,24 @@ describe("Main Analysis Functions", function () { it('return cells distance in pixels ', function () { expect( computeCellsDistance({x_cent: 2126, y_cent:313},{x_cent: 2175, y_cent: 310}).toFixed(3) ).to.eql('49.092'); }); - }); + }); + + describe('#isFeaturesLoaded()', function () { + it('return if features loaded ', function () { + expect( isFeaturesLoaded() ).to.be.false; + }); + }); + + describe('#isBoundariesLoaded()', function () { + it('return if cell boundaries loaded ', function () { + expect( isFeaturesLoaded() ).to.be.false; + }); + }); + + // describe('#isValidFeaturesData()', function () { + // it('return if features data size is equal total cell num', function () { + // expect( isValidFeaturesData([]) ).to.be.false; + // }); + // }); }); \ No newline at end of file